packetfu 1.1.5 → 1.1.6

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.
Files changed (52) hide show
  1. data/.document +5 -2
  2. data/.gitignore +1 -0
  3. data/LICENSE.txt +1 -1
  4. data/bench/after-2012-07-28.txt +25 -0
  5. data/bench/before-2012-07-28.txt +25 -0
  6. data/bench/benchit.rb +68 -0
  7. data/bench/calc_delta.rb +17 -0
  8. data/bench/octets.rb +22 -0
  9. data/bench/octets_after.txt +8 -0
  10. data/bench/octets_after_refactor.txt +8 -0
  11. data/bench/octets_before.txt +8 -0
  12. data/lib/packetfu.rb +8 -3
  13. data/lib/packetfu/packet.rb +2 -2
  14. data/lib/packetfu/pcap.rb +20 -4
  15. data/lib/packetfu/protos/arp.rb +7 -160
  16. data/lib/packetfu/protos/arp/header.rb +160 -0
  17. data/lib/packetfu/protos/arp/mixin.rb +38 -0
  18. data/lib/packetfu/protos/eth.rb +5 -247
  19. data/lib/packetfu/protos/eth/header.rb +247 -0
  20. data/lib/packetfu/protos/eth/mixin.rb +20 -0
  21. data/lib/packetfu/protos/hsrp.rb +13 -123
  22. data/lib/packetfu/protos/hsrp/header.rb +120 -0
  23. data/lib/packetfu/protos/hsrp/mixin.rb +31 -0
  24. data/lib/packetfu/protos/icmp.rb +10 -97
  25. data/lib/packetfu/protos/icmp/header.rb +93 -0
  26. data/lib/packetfu/protos/icmp/mixin.rb +17 -0
  27. data/lib/packetfu/protos/ip.rb +7 -295
  28. data/lib/packetfu/protos/ip/header.rb +335 -0
  29. data/lib/packetfu/protos/ip/mixin.rb +43 -0
  30. data/lib/packetfu/protos/ipv6.rb +7 -191
  31. data/lib/packetfu/protos/ipv6/header.rb +190 -0
  32. data/lib/packetfu/protos/ipv6/mixin.rb +31 -0
  33. data/lib/packetfu/protos/tcp.rb +13 -939
  34. data/lib/packetfu/protos/tcp/ecn.rb +42 -0
  35. data/lib/packetfu/protos/tcp/flags.rb +83 -0
  36. data/lib/packetfu/protos/tcp/header.rb +307 -0
  37. data/lib/packetfu/protos/tcp/hlen.rb +40 -0
  38. data/lib/packetfu/protos/tcp/mixin.rb +48 -0
  39. data/lib/packetfu/protos/tcp/option.rb +323 -0
  40. data/lib/packetfu/protos/tcp/options.rb +106 -0
  41. data/lib/packetfu/protos/tcp/reserved.rb +42 -0
  42. data/lib/packetfu/protos/udp.rb +12 -110
  43. data/lib/packetfu/protos/udp/header.rb +107 -0
  44. data/lib/packetfu/protos/udp/mixin.rb +23 -0
  45. data/lib/packetfu/utils.rb +24 -24
  46. data/lib/packetfu/version.rb +1 -1
  47. data/packetfu.gemspec +2 -2
  48. data/test/test_ip.rb +0 -19
  49. data/test/test_octets.rb +18 -21
  50. data/test/test_tcp.rb +10 -0
  51. data/test/test_udp.rb +17 -0
  52. metadata +79 -50
@@ -0,0 +1,48 @@
1
+ module PacketFu
2
+ # This Mixin simplifies access to the TCPHeaders. Mix this in with your
3
+ # packet interface, and it will add methods that essentially delegate to
4
+ # the 'tcp_header' method (assuming that it is a TCPHeader object)
5
+ module TCPHeaderMixin
6
+ def tcp_src=(v); self.tcp_header.tcp_src= v; end
7
+ def tcp_src; self.tcp_header.tcp_src; end
8
+ def tcp_dst=(v); self.tcp_header.tcp_dst= v; end
9
+ def tcp_dst; self.tcp_header.tcp_dst; end
10
+ def tcp_seq=(v); self.tcp_header.tcp_seq= v; end
11
+ def tcp_seq; self.tcp_header.tcp_seq; end
12
+ def tcp_ack=(v); self.tcp_header.tcp_ack= v; end
13
+ def tcp_ack; self.tcp_header.tcp_ack; end
14
+ def tcp_win=(v); self.tcp_header.tcp_win= v; end
15
+ def tcp_win; self.tcp_header.tcp_win; end
16
+ def tcp_sum=(v); self.tcp_header.tcp_sum= v; end
17
+ def tcp_sum; self.tcp_header.tcp_sum; end
18
+ def tcp_urg=(v); self.tcp_header.tcp_urg= v; end
19
+ def tcp_urg; self.tcp_header.tcp_urg; end
20
+ def tcp_hlen; self.tcp_header.tcp_hlen; end
21
+ def tcp_hlen=(v); self.tcp_header.tcp_hlen= v; end
22
+ def tcp_reserved; self.tcp_header.tcp_reserved; end
23
+ def tcp_reserved=(v); self.tcp_header.tcp_reserved= v; end
24
+ def tcp_ecn; self.tcp_header.tcp_ecn; end
25
+ def tcp_ecn=(v); self.tcp_header.tcp_ecn= v; end
26
+ def tcp_opts; self.tcp_header.tcp_opts; end
27
+ def tcp_opts=(v); self.tcp_header.tcp_opts= v; end
28
+ def tcp_calc_seq; self.tcp_header.tcp_calc_seq; end
29
+ def tcp_calc_src; self.tcp_header.tcp_calc_src; end
30
+ def tcp_opts_len; self.tcp_header.tcp_opts_len; end
31
+ def tcp_calc_hlen; self.tcp_header.tcp_calc_hlen; end
32
+ def tcp_options; self.tcp_header.tcp_options; end
33
+ def tcp_flags_dotmap; self.tcp_header.tcp_flags_dotmap; end
34
+ def tcp_options=(v); self.tcp_header.tcp_options= v; end
35
+ def tcp_sport; self.tcp_header.tcp_sport; end
36
+ def tcp_sport=(v); self.tcp_header.tcp_sport= v; end
37
+ def tcp_dport; self.tcp_header.tcp_dport; end
38
+ def tcp_dport=(v); self.tcp_header.tcp_dport= v; end
39
+ def tcp_recalc(*v); self.tcp_header.tcp_recalc(*v); end
40
+ def tcp_flags_readable; self.tcp_header.tcp_flags_readable; end
41
+ def tcp_ack_readable; self.tcp_header.tcp_ack_readable; end
42
+ def tcp_seq_readable; self.tcp_header.tcp_seq_readable; end
43
+ def tcp_sum_readable; self.tcp_header.tcp_sum_readable; end
44
+ def tcp_opts_readable; self.tcp_header.tcp_opts_readable; end
45
+ def tcp_flags; self.tcp_header.tcp_flags; end
46
+ def tcp_flags=(v); self.tcp_header.tcp_flags= v; end
47
+ end
48
+ end
@@ -0,0 +1,323 @@
1
+ module PacketFu
2
+ # TcpOption is the base class for all TCP options. Note that TcpOption#len
3
+ # returns the size of the entire option, while TcpOption#optlen is the struct
4
+ # for the TCP Option Length field.
5
+ #
6
+ # Subclassed options should set the correct TcpOption#kind by redefining
7
+ # initialize. They should also deal with various value types there by setting
8
+ # them explicitly with an accompanying StructFu#typecast for the setter.
9
+ #
10
+ # By default, values are presumed to be strings, unless they are Numeric, in
11
+ # which case a guess is made to the width of the Numeric based on the given
12
+ # optlen.
13
+ #
14
+ # Note that normally, optlen is /not/ enforced for directly setting values,
15
+ # so the user is perfectly capable of setting incorrect lengths.
16
+ class TcpOption < Struct.new(:kind, :optlen, :value)
17
+
18
+ include StructFu
19
+
20
+ def initialize(args={})
21
+ super(
22
+ Int8.new(args[:kind]),
23
+ Int8.new(args[:optlen])
24
+ )
25
+ if args[:value].kind_of? Numeric
26
+ self[:value] = case args[:optlen]
27
+ when 3; Int8.new(args[:value])
28
+ when 4; Int16.new(args[:value])
29
+ when 6; Int32.new(args[:value])
30
+ else; StructFu::String.new.read(args[:value])
31
+ end
32
+ else
33
+ self[:value] = StructFu::String.new.read(args[:value])
34
+ end
35
+ end
36
+
37
+ # Returns the object in string form.
38
+ def to_s
39
+ self[:kind].to_s +
40
+ (self[:optlen].value.nil? ? nil : self[:optlen]).to_s +
41
+ (self[:value].nil? ? nil : self[:value]).to_s
42
+ end
43
+
44
+ # Reads a string to populate the object.
45
+ def read(str)
46
+ force_binary(str)
47
+ return self if str.nil?
48
+ self[:kind].read(str[0,1])
49
+ if str[1,1]
50
+ self[:optlen].read(str[1,1])
51
+ if str[2,1] && optlen.value > 2
52
+ self[:value].read(str[2,optlen.value-2])
53
+ end
54
+ end
55
+ self
56
+ end
57
+
58
+ # The default decode for an unknown option. Known options should redefine this.
59
+ def decode
60
+ unk = "unk-#{self.kind.to_i}"
61
+ (self[:optlen].to_i > 2 && self[:value].to_s.size > 1) ? [unk,self[:value]].join(":") : unk
62
+ end
63
+
64
+ # Setter for the "kind" byte of this option.
65
+ def kind=(i); typecast i; end
66
+ # Setter for the "option length" byte for this option.
67
+ def optlen=(i); typecast i; end
68
+
69
+ # Setter for the value of this option.
70
+ def value=(i)
71
+ if i.kind_of? Numeric
72
+ typecast i
73
+ elsif i.respond_to? :to_s
74
+ self[:value] = i
75
+ else
76
+ self[:value] = ''
77
+ end
78
+ end
79
+
80
+ # Generally, encoding a value is going to be just a read. Some
81
+ # options will treat things a little differently; TS for example,
82
+ # takes two values and concatenates them.
83
+ def encode(str)
84
+ self[:value] = self.class.new(:value => str).value
85
+ end
86
+
87
+ # Returns true if this option has an optlen. Some don't.
88
+ def has_optlen?
89
+ (kind.value && kind.value < 2) ? false : true
90
+ end
91
+
92
+ # Returns true if this option has a value. Some don't.
93
+ def has_value?
94
+ (value.respond_to? :to_s && value.to_s.size > 0) ? false : true
95
+ end
96
+
97
+ # End of Line option. Usually used to terminate a string of options.
98
+ #
99
+ # http://www.networksorcery.com/enp/protocol/tcp/option000.htm
100
+ class EOL < TcpOption
101
+ def initialize(args={})
102
+ super(
103
+ args.merge(:kind => 0)
104
+ )
105
+ end
106
+
107
+ def decode
108
+ "EOL"
109
+ end
110
+
111
+ end
112
+
113
+ # No Operation option. Usually used to pad out options to fit a 4-byte alignment.
114
+ #
115
+ # http://www.networksorcery.com/enp/protocol/tcp/option001.htm
116
+ class NOP < TcpOption
117
+ def initialize(args={})
118
+ super(
119
+ args.merge(:kind => 1)
120
+ )
121
+ end
122
+
123
+ def decode
124
+ "NOP"
125
+ end
126
+
127
+ end
128
+
129
+ # Maximum Segment Size option.
130
+ #
131
+ # http://www.networksorcery.com/enp/protocol/tcp/option002.htm
132
+ class MSS < TcpOption
133
+ def initialize(args={})
134
+ super(
135
+ args.merge(:kind => 2,
136
+ :optlen => 4
137
+ )
138
+ )
139
+ self[:value] = Int16.new(args[:value])
140
+ end
141
+
142
+ def value=(i); typecast i; end
143
+
144
+ # MSS options with lengths other than 4 are malformed.
145
+ def decode
146
+ if self[:optlen].to_i == 4
147
+ "MSS:#{self[:value].to_i}"
148
+ else
149
+ "MSS-bad:#{self[:value]}"
150
+ end
151
+ end
152
+
153
+ end
154
+
155
+ # Window Size option.
156
+ #
157
+ # http://www.networksorcery.com/enp/protocol/tcp/option003.htm
158
+ class WS < TcpOption
159
+ def initialize(args={})
160
+ super(
161
+ args.merge(:kind => 3,
162
+ :optlen => 3
163
+ )
164
+ )
165
+ self[:value] = Int8.new(args[:value])
166
+ end
167
+
168
+ def value=(i); typecast i; end
169
+
170
+ # WS options with lengths other than 3 are malformed.
171
+ def decode
172
+ if self[:optlen].to_i == 3
173
+ "WS:#{self[:value].to_i}"
174
+ else
175
+ "WS-bad:#{self[:value]}"
176
+ end
177
+ end
178
+
179
+ end
180
+
181
+ # Selective Acknowlegment OK option.
182
+ #
183
+ # http://www.networksorcery.com/enp/protocol/tcp/option004.htm
184
+ class SACKOK < TcpOption
185
+ def initialize(args={})
186
+ super(
187
+ args.merge(:kind => 4,
188
+ :optlen => 2)
189
+ )
190
+ end
191
+
192
+ # SACKOK options with sizes other than 2 are malformed.
193
+ def decode
194
+ if self[:optlen].to_i == 2
195
+ "SACKOK"
196
+ else
197
+ "SACKOK-bad:#{self[:value]}"
198
+ end
199
+ end
200
+
201
+ end
202
+
203
+ # Selective Acknowledgement option.
204
+ #
205
+ # http://www.networksorcery.com/enp/protocol/tcp/option004.htm
206
+ #
207
+ # Note that SACK always takes its optlen from the size of the string.
208
+ class SACK < TcpOption
209
+ def initialize(args={})
210
+ super(
211
+ args.merge(:kind => 5,
212
+ :optlen => ((args[:value] || "").size + 2)
213
+ )
214
+ )
215
+ end
216
+
217
+ def optlen=(i); typecast i; end
218
+
219
+ def value=(i)
220
+ self[:optlen] = Int8.new(i.to_s.size + 2)
221
+ self[:value] = StructFu::String.new(i)
222
+ end
223
+
224
+ def decode
225
+ "SACK:#{self[:value]}"
226
+ end
227
+
228
+ def encode(str)
229
+ temp_obj = self.class.new(:value => str)
230
+ self[:value] = temp_obj.value
231
+ self[:optlen] = temp_obj.optlen.value
232
+ self
233
+ end
234
+
235
+ end
236
+
237
+ # Echo option.
238
+ #
239
+ # http://www.networksorcery.com/enp/protocol/tcp/option006.htm
240
+ class ECHO < TcpOption
241
+ def initialize(args={})
242
+ super(
243
+ args.merge(:kind => 6,
244
+ :optlen => 6
245
+ )
246
+ )
247
+ end
248
+
249
+ # ECHO options with lengths other than 6 are malformed.
250
+ def decode
251
+ if self[:optlen].to_i == 6
252
+ "ECHO:#{self[:value]}"
253
+ else
254
+ "ECHO-bad:#{self[:value]}"
255
+ end
256
+ end
257
+
258
+ end
259
+
260
+ # Echo Reply option.
261
+ #
262
+ # http://www.networksorcery.com/enp/protocol/tcp/option007.htm
263
+ class ECHOREPLY < TcpOption
264
+ def initialize(args={})
265
+ super(
266
+ args.merge(:kind => 7,
267
+ :optlen => 6
268
+ )
269
+ )
270
+ end
271
+
272
+ # ECHOREPLY options with lengths other than 6 are malformed.
273
+ def decode
274
+ if self[:optlen].to_i == 6
275
+ "ECHOREPLY:#{self[:value]}"
276
+ else
277
+ "ECHOREPLY-bad:#{self[:value]}"
278
+ end
279
+ end
280
+
281
+ end
282
+
283
+ # Timestamp option
284
+ #
285
+ # http://www.networksorcery.com/enp/protocol/tcp/option008.htm
286
+ class TS < TcpOption
287
+ def initialize(args={})
288
+ super(
289
+ args.merge(:kind => 8,
290
+ :optlen => 10
291
+ )
292
+ )
293
+ self[:value] = StructFu::String.new.read(args[:value] || "\x00" * 8)
294
+ end
295
+
296
+ # TS options with lengths other than 10 are malformed.
297
+ def decode
298
+ if self[:optlen].to_i == 10
299
+ val1,val2 = self[:value].unpack("NN")
300
+ "TS:#{val1};#{val2}"
301
+ else
302
+ "TS-bad:#{self[:value]}"
303
+ end
304
+ end
305
+
306
+ # TS options are in the format of "TS:[timestamp value];[timestamp secret]" Both
307
+ # should be written as decimal numbers.
308
+ def encode(str)
309
+ if str =~ /^([0-9]+);([0-9]+)$/
310
+ tsval,tsecr = str.split(";").map {|x| x.to_i}
311
+ if tsval <= 0xffffffff && tsecr <= 0xffffffff
312
+ self[:value] = StructFu::String.new([tsval,tsecr].pack("NN"))
313
+ else
314
+ self[:value] = StructFu::String.new(str)
315
+ end
316
+ else
317
+ self[:value] = StructFu::String.new(str)
318
+ end
319
+ end
320
+
321
+ end
322
+ end
323
+ end
@@ -0,0 +1,106 @@
1
+ require 'packetfu/protos/tcp/option'
2
+
3
+ module PacketFu
4
+
5
+ class TcpOptions < Array
6
+
7
+ include StructFu
8
+
9
+ # If args[:pad] is set, the options line is automatically padded out
10
+ # with NOPs.
11
+ def to_s(args={})
12
+ opts = self.map {|x| x.to_s}.join
13
+ if args[:pad]
14
+ unless (opts.size % 4).zero?
15
+ (4 - (opts.size % 4)).times { opts << "\x01" }
16
+ end
17
+ end
18
+ opts
19
+ end
20
+
21
+ # Reads a string to populate the object.
22
+ def read(str)
23
+ self.clear
24
+ PacketFu.force_binary(str)
25
+ return self if(!str.respond_to? :to_s || str.nil?)
26
+ i = 0
27
+ while i < str.to_s.size
28
+ this_opt = case str[i,1].unpack("C").first
29
+ when 0; ::PacketFu::TcpOption::EOL.new
30
+ when 1; ::PacketFu::TcpOption::NOP.new
31
+ when 2; ::PacketFu::TcpOption::MSS.new
32
+ when 3; ::PacketFu::TcpOption::WS.new
33
+ when 4; ::PacketFu::TcpOption::SACKOK.new
34
+ when 5; ::PacketFu::TcpOption::SACK.new
35
+ when 6; ::PacketFu::TcpOption::ECHO.new
36
+ when 7; ::PacketFu::TcpOption::ECHOREPLY.new
37
+ when 8; ::PacketFu::TcpOption::TS.new
38
+ else; ::PacketFu::TcpOption.new
39
+ end
40
+ this_opt.read str[i,str.size]
41
+ unless this_opt.has_optlen?
42
+ this_opt.value = nil
43
+ this_opt.optlen = nil
44
+ end
45
+ self << this_opt
46
+ i += this_opt.sz
47
+ end
48
+ self
49
+ end
50
+
51
+ # Decode parses the TcpOptions object's member options, and produces a
52
+ # human-readable string by iterating over each element's decode() function.
53
+ # If TcpOptions elements were not initially created as TcpOptions, an
54
+ # attempt will be made to convert them.
55
+ #
56
+ # The output of decode is suitable as input for TcpOptions#encode.
57
+ def decode
58
+ decoded = self.map do |x|
59
+ if x.kind_of? TcpOption
60
+ x.decode
61
+ else
62
+ x = TcpOptions.new.read(x).decode
63
+ end
64
+ end
65
+ decoded.join(",")
66
+ end
67
+
68
+ # Encode takes a human-readable string and appends the corresponding
69
+ # binary options to the TcpOptions object. To completely replace the contents
70
+ # of the object, use TcpOptions#encode! instead.
71
+ #
72
+ # Options are comma-delimited, and are identical to the output of the
73
+ # TcpOptions#decode function. Note that the syntax can be unforgiving, so
74
+ # it may be easier to create the subclassed TcpOptions themselves directly,
75
+ # but this method can be less typing if you know what you're doing.
76
+ #
77
+ # Note that by using TcpOptions#encode, strings supplied as values which
78
+ # can be converted to numbers will be converted first.
79
+ #
80
+ # === Example
81
+ #
82
+ # t = TcpOptions.new
83
+ # t.encode("MS:1460,WS:6")
84
+ # t.to_s # => "\002\004\005\264\002\003\006"
85
+ # t.encode("NOP")
86
+ # t.to_s # => "\002\004\005\264\002\003\006\001"
87
+ def encode(str)
88
+ opts = str.split(/[\s]*,[\s]*/)
89
+ opts.each do |o|
90
+ kind,value = o.split(/[\s]*:[\s]*/)
91
+ klass = TcpOption.const_get(kind.upcase)
92
+ value = value.to_i if value =~ /^[0-9]+$/
93
+ this_opt = klass.new
94
+ this_opt.encode(value)
95
+ self << this_opt
96
+ end
97
+ self
98
+ end
99
+
100
+ # Like TcpOption#encode, except the entire contents are replaced.
101
+ def encode!(str)
102
+ self.clear if self.size > 0
103
+ encode(str)
104
+ end
105
+ end
106
+ end