packetgen 0.3.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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