packetgen 0.3.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,254 @@
1
+ module PacketGen
2
+ module Header
3
+ class TCP
4
+
5
+ # Base class to describe a TCP option
6
+ # @author Sylvain Daubert
7
+ class Option < Struct.new(:kind, :length, :value)
8
+ include StructFu
9
+
10
+ EOL_KIND = 0
11
+ NOP_KIND = 1
12
+ MSS_KIND = 2
13
+ WS_KIND = 3
14
+ SACKOK_KIND = 4
15
+ SACK_KIND = 5
16
+ ECHO_KIND = 6
17
+ ECHOREPLY_KIND = 7
18
+ TS_KIND = 8
19
+
20
+ # @param [hash] options
21
+ # @option options [Integer] :kind
22
+ # @option options [Integer] :length
23
+ # @option options [Integer,String] :value
24
+ def initialize(options={})
25
+ super Int8.new(options[:kind]),
26
+ Int8.new(options[:length])
27
+
28
+ case options[:value]
29
+ when Integer
30
+ klass = case self[:length].to_i
31
+ when 3; Int8
32
+ when 4; Int16
33
+ when 6; Int32
34
+ else
35
+ raise ArgumentError, 'impossible length'
36
+ end
37
+ self[:value] = klass.new(options[:value])
38
+ when NilClass
39
+ self[:value] = StructFu::String.new
40
+ else
41
+ self[:value] = StructFu::String.new.read(options[:value])
42
+ self[:length].read(self[:value].sz + 2) unless options[:length]
43
+ end
44
+ end
45
+
46
+ # Read a TCP option from a string
47
+ # @param [String] str binary string
48
+ # @return [self]
49
+ def read(str)
50
+ return self if str.nil?
51
+ force_binary str
52
+ self[:kind].read str[0, 1]
53
+ if str[1, 1]
54
+ self[:length].read str[1, 1]
55
+ if str[2, 1] && length > 2
56
+ self[:value].read str[2, length - 2]
57
+ end
58
+ end
59
+ self
60
+ end
61
+
62
+ # Getter for kind attribute
63
+ # @return [Integer]
64
+ def kind
65
+ self[:kind].to_i
66
+ end
67
+
68
+ # Setter for kind attribute
69
+ # @param [Integer] i
70
+ # @return [Integer]
71
+ def kind=(i)
72
+ self[:kind].read i
73
+ end
74
+
75
+ # Getter for length attribute
76
+ # @return [Integer]
77
+ def length
78
+ self[:length].to_i
79
+ end
80
+
81
+ # Setter for length attribute
82
+ # @param [Integer] i
83
+ # @return [Integer]
84
+ def length=(i)
85
+ self[:length].read i
86
+ end
87
+
88
+ # Say if option has a length
89
+ # @return [Boolean]
90
+ def has_length?
91
+ self[:kind].value && kind >= 2
92
+ end
93
+
94
+ # Getter for value attribute
95
+ # @return [String, Integer]
96
+ def value
97
+ case self[:value]
98
+ when StructFu::Int
99
+ self[:value].to_i
100
+ else
101
+ self[:value].to_s
102
+ end
103
+ end
104
+
105
+ # Setter for value attribute
106
+ # @param [String, Integer] v
107
+ # @return [String, Integer]
108
+ def value=(v)
109
+ self[:value].read v
110
+ end
111
+
112
+ # Get binary string
113
+ # @return [String]
114
+ def to_s
115
+ str = self[:kind].to_s
116
+ str << self[:length].to_s unless self[:length].value.nil?
117
+ str << self[:value].to_s if length > 2
118
+ str
119
+ end
120
+
121
+ # Get option as a human readable string
122
+ # @return [String]
123
+ def to_human
124
+ str = self.class == Option ? "unk-#{kind}" : self.class.to_s.sub(/.*::/, '')
125
+ if length > 2 and self[:value].to_s.size > 0
126
+ str << ":#{self[:value].to_s.inspect}"
127
+ end
128
+ str
129
+ end
130
+
131
+ # @return [String]
132
+ def inspect
133
+ str = "#<#{self.class} kind=#{self[:kind].value.inspect} "
134
+ str << "length=#{self[:length].value.inspect} " if self[:length].value
135
+ str << "value=#{self[:value].inspect}>"
136
+ end
137
+ end
138
+
139
+ # End Of Option TCP option
140
+ # @author Sylvain Daubert
141
+ class EOL < Option
142
+ # @see Option#initialize
143
+ def initialize(options={})
144
+ super options.merge!(kind: EOL_KIND)
145
+ end
146
+ end
147
+
148
+ # No OPeration TCP option
149
+ # @author Sylvain Daubert
150
+ class NOP < Option
151
+ # @see Option#initialize
152
+ def initialize(options={})
153
+ super options.merge!(kind: NOP_KIND)
154
+ end
155
+ end
156
+
157
+ # Maximum Segment Size TCP option
158
+ # @author Sylvain Daubert
159
+ class MSS < Option
160
+ # @see Option#initialize
161
+ def initialize(options={})
162
+ super options.merge!(kind: MSS_KIND, length: 4)
163
+ self[:value] = Int16.new(options[:value])
164
+ end
165
+
166
+ # @return [String]
167
+ def to_human
168
+ "MSS:#{value}"
169
+ end
170
+ end
171
+
172
+ # Window Size TCP option
173
+ # @author Sylvain Daubert
174
+ class WS < Option
175
+ # @see Option#initialize
176
+ def initialize(options={})
177
+ super options.merge!(kind: WS_KIND, length: 3)
178
+ self[:value] = Int8.new(options[:value])
179
+ end
180
+
181
+ # @return [String]
182
+ def to_human
183
+ "WS:#{value}"
184
+ end
185
+ end
186
+
187
+ # Selective Acknowledgment OK TCP option
188
+ # @author Sylvain Daubert
189
+ class SACKOK < Option
190
+ # @see Option#initialize
191
+ def initialize(options={})
192
+ super options.merge!(kind: SACKOK_KIND, length: 2)
193
+ end
194
+ end
195
+
196
+ # Selective Acknowledgment TCP option
197
+ # @author Sylvain Daubert
198
+ class SACK < Option
199
+ # @see Option#initialize
200
+ def initialize(options={})
201
+ super options.merge!(kind: SACK_KIND)
202
+ self[:length].read(2) if self[:value].to_s == ''
203
+ end
204
+ end
205
+
206
+ # Echo TCP option
207
+ # @author Sylvain Daubert
208
+ class ECHO < Option
209
+ # @see Option#initialize
210
+ def initialize(options={})
211
+ super options.merge!(kind: ECHO_KIND, length: 6)
212
+ self[:value] = Int32.new(options[:value])
213
+ end
214
+
215
+ # @return [String]
216
+ def to_human
217
+ "WS:#{value}"
218
+ end
219
+ end
220
+
221
+ # Echo Reply TCP option
222
+ # @author Sylvain Daubert
223
+ class ECHOREPLY < Option
224
+ # @see Option#initialize
225
+ def initialize(options={})
226
+ super options.merge!(kind: ECHOREPLY_KIND, length: 6)
227
+ self[:value] = Int32.new(options[:value])
228
+ end
229
+
230
+ # @return [String]
231
+ def to_human
232
+ "WS:#{value}"
233
+ end
234
+ end
235
+
236
+ # Timestamp TCP option
237
+ # @author Sylvain Daubert
238
+ class TS < Option
239
+ # @see Option#initialize
240
+ def initialize(options={})
241
+ super options.merge!(kind: TS_KIND, length: 10)
242
+ self[:value] = StructFu::String.new.read(options[:value] || "\0" * 8)
243
+ end
244
+
245
+ # @return [String]
246
+ def to_human
247
+ value, echo_reply = self[:value].unpack('NN')
248
+ "TS:#{value};#{echo_reply}"
249
+ end
250
+ end
251
+ end
252
+ end
253
+ end
254
+
@@ -0,0 +1,86 @@
1
+ module PacketGen
2
+ module Header
3
+ class TCP
4
+
5
+ # Container for TCP options in {TCP TCP header}.
6
+ # @author Sylvain Daubert
7
+ class Options < Array
8
+
9
+ # Get {Option} subclasses
10
+ # @return [Array<Class>]
11
+ def self.option_classes
12
+ return @klasses if defined? @klasses
13
+ @klasses = []
14
+ Option.constants.each do |cst|
15
+ next unless cst.to_s.end_with? '_KIND'
16
+ optname = cst.to_s.sub(/_KIND/, '')
17
+ @klasses[Option.const_get(cst)] = TCP.const_get(optname)
18
+ end
19
+ @klasses
20
+ end
21
+
22
+ # Read TCP header options from a string
23
+ # @param [String] str binary string
24
+ # @return [self]
25
+ def read(str)
26
+ clear
27
+ return self if str.nil?
28
+ PacketGen.force_binary str
29
+
30
+ i = 0
31
+ klasses = self.class.option_classes
32
+ while i < str.to_s.length
33
+ kind = str[i, 1].unpack('C').first
34
+ this_option = if klasses[kind].nil?
35
+ Option.new
36
+ else
37
+ klasses[kind].new
38
+ end
39
+ this_option.read str[i, str.size]
40
+ unless this_option.has_length?
41
+ this_option.length = nil
42
+ this_option.value = nil
43
+ end
44
+ self << this_option
45
+ i += this_option.sz
46
+ end
47
+ self
48
+ end
49
+
50
+ # Add a well-known option
51
+ # @param [String] opt option name
52
+ # @param [Object] value
53
+ # @return [self]
54
+ # @raise [ArgumentError] unknown option
55
+ def add(opt, value=nil)
56
+ raise ArgumentError, "unknown option #{opt}" unless TCP.const_defined?(opt)
57
+ klass = TCP.const_get(opt)
58
+ raise ArgumentError "unknown option #{opt}" unless klass < Option
59
+ option = klass.new(value: value)
60
+ self << option
61
+ self
62
+ end
63
+
64
+ # Get options binary string
65
+ # @return [String]
66
+ def to_s
67
+ map(&:to_s).join
68
+ end
69
+
70
+ # Get a human readable string
71
+ # @return [String]
72
+ def to_human
73
+ map(&:to_human).join(', ')
74
+ end
75
+
76
+ # Get options size in bytes
77
+ # @return [Integer]
78
+ def sz
79
+ to_s.length
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
85
+
86
+ require_relative 'option'
@@ -0,0 +1,299 @@
1
+ module PacketGen
2
+ module Header
3
+
4
+ # A TCP header consists of:
5
+ # * a source port ({#sport}, {Int16} type),
6
+ # * a destination port ({#dport}, +Int16+ type),
7
+ # * a sequence number ({#seqnum}, {Int32} type),
8
+ # * an acknownledge number ({#acknum}, +Int32+ type),
9
+ # * a 16-bit field ({#u16}, +Int16+ type) composed of:
10
+ # * a 4-bit {#data_offset} value,
11
+ # * a 3-bit {#reserved} field,
12
+ # * a 9-bit {#flags} field,
13
+ # * a {#window} field (+Int16+ type),
14
+ # * a {#checksum} field (+Int16+ type),
15
+ # * a urgent pointer ({#urg_pointer}, +Int16+ type),
16
+ # * an optional {#options} field ({Options} type),
17
+ # * and a {#body} ({String} type).
18
+ #
19
+ # == Create a TCP header
20
+ # # standalone
21
+ # tcph = PacketGen::Header::TCP.new
22
+ # # in a IP packet
23
+ # pkt = PacketGen.gen('IP').add('TCP')
24
+ # # access to TCP header
25
+ # pkt.tcp # => PacketGen::Header::TCP
26
+ #
27
+ # == TCP attributes
28
+ # tcph.sport = 4500
29
+ # tcph.dport = 80
30
+ # tcph.seqnum = 43
31
+ # tcph.acknum = 0x45678925
32
+ # tcph.wsize = 0x240
33
+ # tcph.urg_pointer = 0x40
34
+ # tcph.body.read 'this is a body'
35
+ #
36
+ # == Flags
37
+ # TCP flags may be accesed as a 9-bit integer:
38
+ # tcph.flags = 0x1002
39
+ # Each flag may be accessed independently:
40
+ # tcph.flag_syn? # => Boolean
41
+ # tcph.flag_rst = true
42
+ #
43
+ # == Options
44
+ # {#options} TCP attribute is a {Options}. {Option} may added to it:
45
+ # tcph.options << PacketGen::Header::TCP::MSS.new(1250)
46
+ # Another way is to use {Options#add}:
47
+ # tcph.options.add 'MSS', 1250
48
+ # @author Sylvain Daubert
49
+ class TCP < Struct.new(:sport, :dport, :seqnum, :acknum, :u16,
50
+ :window, :checksum, :urg_pointer, :options, :body)
51
+ include StructFu
52
+ include HeaderMethods
53
+ extend HeaderClassMethods
54
+ end
55
+ end
56
+ end
57
+
58
+ # Need to load Options now, as this is used through define_bit_fields_on,
59
+ # which make a call to TCP.new, which needs Options
60
+ require_relative 'tcp/options'
61
+
62
+ module PacketGen
63
+ module Header
64
+ class TCP
65
+ # IP protocol number for TCP
66
+ IP_PROTOCOL = 6
67
+
68
+ # @param [Hash] options
69
+ # @option options [Integer] :sport
70
+ # @option options [Integer] :dport
71
+ # @option options [Integer] :seqnum
72
+ # @option options [Integer] :acknum
73
+ # @option options [Integer] :data_offset
74
+ # @option options [Integer] :reserved
75
+ # @option options [Integer] :flags
76
+ # @option options [Integer] :window
77
+ # @option options [Integer] :checksum
78
+ # @option options [Integer] :urg_pointer
79
+ # @option options [String] :body
80
+ def initialize(options={})
81
+ super Int16.new(options[:sport]),
82
+ Int16.new(options[:dport]),
83
+ Int32.new(options[:seqnum] || rand(2**32)),
84
+ Int32.new(options[:acknum]),
85
+ Int16.new,
86
+ Int16.new(options[:window] || options[:wsize]),
87
+ Int16.new(options[:checksum]),
88
+ Int16.new(options[:urg_pointer]),
89
+ Options.new,
90
+ StructFu::String.new.read(options[:body])
91
+
92
+ doff = options[:data_offset] || options[:hlen] || 5
93
+ rsv = options[:reserved] || 0
94
+ flgs = options[:flags] || 0
95
+ self.u16.read (((doff << 3) | rsv) << 9) | flgs
96
+ end
97
+
98
+ # @!attribute data_offset
99
+ # @return [Integer] 4-bit data offsetfrom {#u16}
100
+ # @!attribute reserved
101
+ # @return [Integer] 3-bit reserved from {#u16}
102
+ # @!attribute flags
103
+ # @return [Integer] 9-bit flags from {#u16}
104
+ define_bit_fields_on :u16, :data_offset, 4, :reserved, 3, :flags, 9
105
+ alias :hlen :data_offset
106
+ alias :hlen= :data_offset=
107
+
108
+ # @!attribute flag_ns
109
+ # @return [Boolean] 1-bit NS flag
110
+ # @!attribute flag_cwr
111
+ # @return [Boolean] 1-bit CWR flag
112
+ # @!attribute flag_ece
113
+ # @return [Boolean] 1-bit ECE flag
114
+ # @!attribute flag_urg
115
+ # @return [Boolean] 1-bit URG flag
116
+ # @!attribute flag_ack
117
+ # @return [Boolean] 1-bit ACK flag
118
+ # @!attribute flag_psh
119
+ # @return [Boolean] 1-bit PSH flag
120
+ # @!attribute flag_rst
121
+ # @return [Boolean] 1-bit RST flag
122
+ # @!attribute flag_syn
123
+ # @return [Boolean] 1-bit SYN flag
124
+ # @!attribute flag_fin
125
+ # @return [Boolean] 1-bit FIN flag
126
+ define_bit_fields_on :u16, :_, 7, :flag_ns, :flag_cwr, :flag_ece, :flag_urg,
127
+ :flag_ack, :flag_psh, :flag_rst, :flag_syn, :flag_fin
128
+ # Read a TCP header from a string
129
+ # @param [String] str binary string
130
+ # @return [self]
131
+ def read(str)
132
+ return self if str.nil?
133
+ raise ParseError, 'string too short for TCP' if str.size < self.sz
134
+ force_binary str
135
+ self[:sport].read str[0, 2]
136
+ self[:dport].read str[2, 2]
137
+ self[:seqnum].read str[4, 4]
138
+ self[:acknum].read str[8, 4]
139
+ self[:u16].read str[12, 2]
140
+ self[:window].read str[14, 2]
141
+ self[:checksum].read str[16, 2]
142
+ self[:urg_pointer].read str[18, 2]
143
+ self[:options].read str[20, (self.data_offset - 5) * 4] if self.data_offset > 5
144
+ self[:body].read str[self.data_offset * 4..-1]
145
+ end
146
+
147
+ # Compute checksum and set +checksum+ field
148
+ # @return [Integer]
149
+ def calc_checksum
150
+ sum = ip_header(self).pseudo_header_checksum
151
+ sum += IP_PROTOCOL
152
+ sum += self.sz
153
+ str = self.to_s
154
+ str << "\x00" if str.length % 2 == 1
155
+ sum += str.unpack('n*').reduce(:+)
156
+
157
+ while sum > 0xffff do
158
+ sum = (sum & 0xffff) + (sum >> 16)
159
+ end
160
+ sum = ~sum & 0xffff
161
+ self[:checksum].value = (sum == 0) ? 0xffff : sum
162
+ end
163
+
164
+ # Compute header length and set +data_offset+ field
165
+ # @return [Integer]
166
+ def calc_length
167
+ self[:data_offset] = 5 + self[:options].sz / 4
168
+ end
169
+
170
+ # Getter for source port
171
+ # @return [Integer]
172
+ def sport
173
+ self[:sport].to_i
174
+ end
175
+ alias :source_port :sport
176
+
177
+ # Setter for source port
178
+ # @param [Integer] port
179
+ # @return [Integer]
180
+ def sport=(port)
181
+ self[:sport].read port
182
+ end
183
+ alias :source_port= :sport=
184
+
185
+ # Getter for destination port
186
+ # @return [Integer]
187
+ def dport
188
+ self[:dport].to_i
189
+ end
190
+ alias :destination_port :dport
191
+
192
+ # Setter for destination port
193
+ # @param [Integer] port
194
+ # @return [Integer]
195
+ def dport=(port)
196
+ self[:dport].read port
197
+ end
198
+ alias :destination_port= :dport=
199
+
200
+ # Getter for seqnum attribuute
201
+ # @return [Integer]
202
+ def seqnum
203
+ self[:seqnum].to_i
204
+ end
205
+ alias :sequence_number :seqnum
206
+
207
+ # Setter for seqnum attribuute
208
+ # @param [Integer] seq
209
+ # @return [Integer]
210
+ def seqnum=(seq)
211
+ self[:seqnum].read seq
212
+ end
213
+ alias :sequence_number= :seqnum=
214
+
215
+ # Getter for acknum attribuute
216
+ # @return [Integer]
217
+ def acknum
218
+ self[:acknum].to_i
219
+ end
220
+ alias :acknowledgment_number :acknum
221
+
222
+ # Setter for acknum attribuute
223
+ # @param [Integer] ack
224
+ # @return [Integer]
225
+ def acknum=(ack)
226
+ self[:acknum].read ack
227
+ end
228
+ alias :acknowledgment_number= :acknum=
229
+
230
+ alias :hlen :data_offset
231
+ alias :hlen= :data_offset=
232
+
233
+ # Getter for window attribuute
234
+ # @return [Integer]
235
+ def window
236
+ self[:window].to_i
237
+ end
238
+ alias :wsize :window
239
+
240
+ # Setter for window attribuute
241
+ # @param [Integer] window
242
+ # @return [Integer]
243
+ def window=(window)
244
+ self[:window].read window
245
+ end
246
+ alias :wsize= :window=
247
+
248
+ # Getter for checksum attribuute
249
+ # @return [Integer]
250
+ def checksum
251
+ self[:checksum].to_i
252
+ end
253
+
254
+ # Setter for checksum attribuute
255
+ # @param [Integer] sum
256
+ # @return [Integer]
257
+ def checksum=(sum)
258
+ self[:checksum].read sum
259
+ end
260
+
261
+ # Getter for urg_pointer attribuute
262
+ # @return [Integer]
263
+ def urg_pointer
264
+ self[:urg_pointer].to_i
265
+ end
266
+
267
+ # Setter for urg_pointer attribuute
268
+ # @param [Integer] urg
269
+ # @return [Integer]
270
+ def urg_pointer=(urg)
271
+ self[:urg_pointer].read urg
272
+ end
273
+
274
+ # @return [String]
275
+ def inspect
276
+ str = Inspect.dashed_line(self.class, 2)
277
+ shift = Inspect.shift_level(2)
278
+ to_h.each do |attr, value|
279
+ next if attr == :body
280
+ str << Inspect.inspect_attribute(attr, value, 2)
281
+ if attr == :u16
282
+ doff = Inspect.int_dec_hex(data_offset, 1)
283
+ str << shift + Inspect::INSPECT_FMT_ATTR % ['', 'data_offset', doff]
284
+ str << shift + Inspect::INSPECT_FMT_ATTR % ['', 'reserved', reserved]
285
+ flags = ''
286
+ %w(ns cwr ece urg ack psh rst syn fin).each do |fl|
287
+ flags << (send("flag_#{fl}?") ? fl[0].upcase : '.')
288
+ end
289
+ str << shift + Inspect::INSPECT_FMT_ATTR % ['', 'flags', flags]
290
+ end
291
+ end
292
+ str
293
+ end
294
+ end
295
+
296
+ IP.bind_header TCP, protocol: TCP::IP_PROTOCOL
297
+ IPv6.bind_header TCP, next: TCP::IP_PROTOCOL
298
+ end
299
+ end