packetfu 1.1.9 → 1.1.10

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 (77) hide show
  1. data/bench/octets.rb +9 -9
  2. data/examples/100kpackets.rb +12 -12
  3. data/examples/ackscan.rb +16 -16
  4. data/examples/arp.rb +35 -35
  5. data/examples/arphood.rb +36 -36
  6. data/examples/dissect_thinger.rb +6 -6
  7. data/examples/new-simple-stats.rb +23 -23
  8. data/examples/packetfu-shell.rb +25 -25
  9. data/examples/simple-sniffer.rb +9 -9
  10. data/examples/simple-stats.rb +23 -23
  11. data/examples/slammer.rb +3 -3
  12. data/lib/packetfu.rb +127 -127
  13. data/lib/packetfu/capture.rb +169 -169
  14. data/lib/packetfu/config.rb +52 -52
  15. data/lib/packetfu/inject.rb +56 -56
  16. data/lib/packetfu/packet.rb +528 -528
  17. data/lib/packetfu/pcap.rb +579 -579
  18. data/lib/packetfu/protos/arp.rb +90 -90
  19. data/lib/packetfu/protos/arp/header.rb +158 -158
  20. data/lib/packetfu/protos/arp/mixin.rb +36 -36
  21. data/lib/packetfu/protos/eth.rb +44 -44
  22. data/lib/packetfu/protos/eth/header.rb +243 -243
  23. data/lib/packetfu/protos/eth/mixin.rb +3 -3
  24. data/lib/packetfu/protos/hsrp.rb +69 -69
  25. data/lib/packetfu/protos/hsrp/header.rb +107 -107
  26. data/lib/packetfu/protos/hsrp/mixin.rb +29 -29
  27. data/lib/packetfu/protos/icmp.rb +71 -71
  28. data/lib/packetfu/protos/icmp/header.rb +82 -82
  29. data/lib/packetfu/protos/icmp/mixin.rb +14 -14
  30. data/lib/packetfu/protos/invalid.rb +49 -49
  31. data/lib/packetfu/protos/ip.rb +69 -69
  32. data/lib/packetfu/protos/ip/header.rb +291 -291
  33. data/lib/packetfu/protos/ip/mixin.rb +40 -40
  34. data/lib/packetfu/protos/ipv6.rb +50 -50
  35. data/lib/packetfu/protos/ipv6/header.rb +188 -188
  36. data/lib/packetfu/protos/ipv6/mixin.rb +29 -29
  37. data/lib/packetfu/protos/tcp.rb +176 -176
  38. data/lib/packetfu/protos/tcp/ecn.rb +35 -35
  39. data/lib/packetfu/protos/tcp/flags.rb +74 -74
  40. data/lib/packetfu/protos/tcp/header.rb +268 -268
  41. data/lib/packetfu/protos/tcp/hlen.rb +32 -32
  42. data/lib/packetfu/protos/tcp/mixin.rb +46 -46
  43. data/lib/packetfu/protos/tcp/option.rb +321 -321
  44. data/lib/packetfu/protos/tcp/options.rb +95 -95
  45. data/lib/packetfu/protos/tcp/reserved.rb +35 -35
  46. data/lib/packetfu/protos/udp.rb +116 -116
  47. data/lib/packetfu/protos/udp/header.rb +91 -91
  48. data/lib/packetfu/protos/udp/mixin.rb +3 -3
  49. data/lib/packetfu/structfu.rb +280 -280
  50. data/lib/packetfu/utils.rb +226 -217
  51. data/lib/packetfu/version.rb +41 -41
  52. data/packetfu.gemspec +2 -1
  53. data/spec/ethpacket_spec.rb +48 -48
  54. data/spec/packet_spec.rb +57 -57
  55. data/spec/packet_subclasses_spec.rb +8 -8
  56. data/spec/packetfu_spec.rb +59 -59
  57. data/spec/structfu_spec.rb +268 -268
  58. data/spec/tcp_spec.rb +75 -75
  59. data/test/all_tests.rb +13 -13
  60. data/test/func_lldp.rb +3 -3
  61. data/test/ptest.rb +2 -2
  62. data/test/test_arp.rb +116 -116
  63. data/test/test_capture.rb +45 -45
  64. data/test/test_eth.rb +68 -68
  65. data/test/test_hsrp.rb +9 -9
  66. data/test/test_icmp.rb +52 -52
  67. data/test/test_inject.rb +18 -18
  68. data/test/test_invalid.rb +16 -16
  69. data/test/test_ip.rb +36 -36
  70. data/test/test_ip6.rb +48 -48
  71. data/test/test_octets.rb +21 -21
  72. data/test/test_packet.rb +154 -154
  73. data/test/test_pcap.rb +170 -170
  74. data/test/test_structfu.rb +97 -97
  75. data/test/test_tcp.rb +320 -320
  76. data/test/test_udp.rb +76 -76
  77. metadata +4 -3
@@ -3,607 +3,607 @@
3
3
 
4
4
  module StructFu
5
5
 
6
- # Set the endianness for the various Int classes. Takes either :little or :big.
7
- def set_endianness(e=nil)
8
- unless [:little, :big].include? e
9
- raise ArgumentError, "Unknown endianness for #{self.class}"
10
- end
11
- @int32 = e == :little ? Int32le : Int32be
12
- @int16 = e == :little ? Int16le : Int16be
13
- return e
14
- end
15
-
16
- # Instead of returning the "size" of the object, which is usually the
17
- # number of elements of the Struct, returns the size of the object after
18
- # a to_s. Essentially, a short version of self.to_size.size
19
- def sz
20
- self.to_s.size
21
- end
6
+ # Set the endianness for the various Int classes. Takes either :little or :big.
7
+ def set_endianness(e=nil)
8
+ unless [:little, :big].include? e
9
+ raise ArgumentError, "Unknown endianness for #{self.class}"
10
+ end
11
+ @int32 = e == :little ? Int32le : Int32be
12
+ @int16 = e == :little ? Int16le : Int16be
13
+ return e
14
+ end
15
+
16
+ # Instead of returning the "size" of the object, which is usually the
17
+ # number of elements of the Struct, returns the size of the object after
18
+ # a to_s. Essentially, a short version of self.to_size.size
19
+ def sz
20
+ self.to_s.size
21
+ end
22
22
 
23
23
  end
24
24
 
25
25
  module PacketFu
26
26
 
27
- # PcapHeader represents the header portion of a libpcap file (the packets
28
- # themselves are in the PcapPackets array). See
29
- # http://wiki.wireshark.org/Development/LibpcapFileFormat for details.
30
- #
31
- # Depending on the endianness (set with :endian), elements are either
32
- # :little endian or :big endian.
33
- #
34
- # ==== PcapHeader Definition
35
- #
36
- # Symbol :endian Default: :little
37
- # Int32 :magic Default: 0xa1b2c3d4 # :big is 0xd4c3b2a1
38
- # Int16 :ver_major Default: 2
39
- # Int16 :ver_minor Default: 4
40
- # Int32 :thiszone
41
- # Int32 :sigfigs
42
- # Int32 :snaplen Default: 0xffff
43
- # Int32 :network Default: 1
44
- class PcapHeader < Struct.new(:endian, :magic, :ver_major, :ver_minor,
45
- :thiszone, :sigfigs, :snaplen, :network)
46
- include StructFu
47
-
48
- MAGIC_INT32 = 0xa1b2c3d4
49
- MAGIC_LITTLE = [MAGIC_INT32].pack("V")
50
- MAGIC_BIG = [MAGIC_INT32].pack("N")
51
-
52
- def initialize(args={})
53
- set_endianness(args[:endian] ||= :little)
54
- init_fields(args)
55
- super(args[:endian], args[:magic], args[:ver_major],
56
- args[:ver_minor], args[:thiszone], args[:sigfigs],
57
- args[:snaplen], args[:network])
58
- end
59
-
60
- # Called by initialize to set the initial fields.
61
- def init_fields(args={})
62
- args[:magic] = @int32.new(args[:magic] || PcapHeader::MAGIC_INT32)
63
- args[:ver_major] = @int16.new(args[:ver_major] || 2)
64
- args[:ver_minor] ||= @int16.new(args[:ver_minor] || 4)
65
- args[:thiszone] ||= @int32.new(args[:thiszone])
66
- args[:sigfigs] ||= @int32.new(args[:sigfigs])
67
- args[:snaplen] ||= @int32.new(args[:snaplen] || 0xffff)
68
- args[:network] ||= @int32.new(args[:network] || 1)
69
- return args
70
- end
71
-
72
- # Returns the object in string form.
73
- def to_s
74
- self.to_a[1,7].map {|x| x.to_s}.join
75
- end
76
-
77
- # Reads a string to populate the object.
78
- # TODO: Need to test this by getting a hold of a big endian pcap file.
79
- # Conversion from big to little shouldn't be that big of a deal.
80
- def read(str)
81
- force_binary(str)
82
- return self if str.nil?
83
- str.force_encoding(Encoding::BINARY) if str.respond_to? :force_encoding
84
- if str[0,4] == self[:magic].to_s
85
- self[:magic].read str[0,4]
86
- self[:ver_major].read str[4,2]
87
- self[:ver_minor].read str[6,2]
88
- self[:thiszone].read str[8,4]
89
- self[:sigfigs].read str[12,4]
90
- self[:snaplen].read str[16,4]
91
- self[:network].read str[20,4]
92
- else
93
- raise "Incorrect magic for libpcap"
94
- end
95
- self
96
- end
97
-
98
- end
99
-
100
- # The Timestamp class defines how Timestamps appear in libpcap files.
101
- #
102
- # ==== Header Definition
103
- #
104
- # Symbol :endian Default: :little
105
- # Int32 :sec
106
- # Int32 :usec
107
- class Timestamp < Struct.new(:endian, :sec, :usec)
108
- include StructFu
109
-
110
- def initialize(args={})
111
- set_endianness(args[:endian] ||= :little)
112
- init_fields(args)
113
- super(args[:endian], args[:sec], args[:usec])
114
- end
115
-
116
- # Called by initialize to set the initial fields.
117
- def init_fields(args={})
118
- args[:sec] = @int32.new(args[:sec])
119
- args[:usec] = @int32.new(args[:usec])
120
- return args
121
- end
122
-
123
- # Returns the object in string form.
124
- def to_s
125
- self.to_a[1,2].map {|x| x.to_s}.join
126
- end
127
-
128
- # Reads a string to populate the object.
129
- def read(str)
130
- force_binary(str)
131
- return self if str.nil?
132
- self[:sec].read str[0,4]
133
- self[:usec].read str[4,4]
134
- self
135
- end
136
-
137
- end
138
-
139
- # PcapPacket defines how individual packets are stored in a libpcap-formatted
140
- # file.
141
- #
142
- # ==== Header Definition
143
- #
144
- # Timestamp :timestamp
145
- # Int32 :incl_len
146
- # Int32 :orig_len
147
- # String :data
148
- class PcapPacket < Struct.new(:endian, :timestamp, :incl_len,
149
- :orig_len, :data)
150
- include StructFu
151
- def initialize(args={})
152
- set_endianness(args[:endian] ||= :little)
153
- init_fields(args)
154
- super(args[:endian], args[:timestamp], args[:incl_len],
155
- args[:orig_len], args[:data])
156
- end
157
-
158
- # Called by initialize to set the initial fields.
159
- def init_fields(args={})
160
- args[:timestamp] = Timestamp.new(:endian => args[:endian]).read(args[:timestamp])
161
- args[:incl_len] = args[:incl_len].nil? ? @int32.new(args[:data].to_s.size) : @int32.new(args[:incl_len])
162
- args[:orig_len] = @int32.new(args[:orig_len])
163
- args[:data] = StructFu::String.new.read(args[:data])
164
- end
165
-
166
- # Returns the object in string form.
167
- def to_s
168
- self.to_a[1,4].map {|x| x.to_s}.join
169
- end
170
-
171
- # Reads a string to populate the object.
172
- def read(str)
173
- return unless str
174
- force_binary(str)
175
- self[:timestamp].read str[0,8]
176
- self[:incl_len].read str[8,4]
177
- self[:orig_len].read str[12,4]
178
- self[:data].read str[16,self[:incl_len].to_i]
179
- self
180
- end
181
-
182
- end
183
-
184
- # PcapPackets is a collection of PcapPacket objects.
185
- class PcapPackets < Array
186
-
187
- include StructFu
188
-
189
- attr_accessor :endian # probably ought to be read-only but who am i.
190
-
191
- def initialize(args={})
192
- @endian = args[:endian] || :little
193
- end
194
-
195
- def force_binary(str)
196
- str.force_encoding Encoding::BINARY if str.respond_to? :force_encoding
197
- end
198
-
199
- # Reads a string to populate the object. Note, this read takes in the
200
- # whole pcap file, since we need to see the magic to know what
201
- # endianness we're dealing with.
202
- def read(str)
203
- force_binary(str)
204
- return self if str.nil?
205
- if str[0,4] == PcapHeader::MAGIC_BIG
206
- @endian = :big
207
- elsif str[0,4] == PcapHeader::MAGIC_LITTLE
208
- @endian = :little
209
- else
210
- raise ArgumentError, "Unknown file format for #{self.class}"
211
- end
212
- body = str[24,str.size]
213
- while body.size > 16 # TODO: catch exceptions on malformed packets at end
214
- p = PcapPacket.new(:endian => @endian)
215
- p.read(body)
216
- self<<p
217
- body = body[p.sz,body.size]
218
- end
219
- self
220
- end
221
-
222
- def to_s
223
- self.join
224
- end
225
-
226
- end
227
-
228
- # PcapFile is a complete libpcap file struct, made up of two elements, a
229
- # PcapHeader and PcapPackets.
230
- #
231
- # See http://wiki.wireshark.org/Development/LibpcapFileFormat
232
- #
233
- # PcapFile also can behave as a singleton class, which is usually the better
234
- # way to handle pcap files of really any size, since it doesn't require
235
- # storing packets before handing them off to a given block. This is really
236
- # the way to go.
237
- class PcapFile < Struct.new(:endian, :head, :body)
238
- include StructFu
239
-
240
- class << self
241
-
242
- # Takes a given file and returns an array of the packet bytes. Here
243
- # for backwards compatibilty.
244
- def file_to_array(fname)
245
- PcapFile.new.file_to_array(:f => fname)
246
- end
247
-
248
- # Takes a given file name, and reads out the packets. If given a block,
249
- # it will yield back a PcapPacket object per packet found.
250
- def read(fname,&block)
251
- file_header = PcapHeader.new
252
- pcap_packets = PcapPackets.new
253
- unless File.readable? fname
254
- raise ArgumentError, "Cannot read file `#{fname}'"
255
- end
256
- begin
257
- file_handle = File.open(fname, "rb")
258
- file_header.read file_handle.read(24)
259
- packet_count = 0
260
- pcap_packet = PcapPacket.new(:endian => file_header.endian)
261
- while pcap_packet.read file_handle.read(16) do
262
- len = pcap_packet.incl_len
263
- pcap_packet.data = StructFu::String.new.read(file_handle.read(len.to_i))
264
- packet_count += 1
265
- if pcap_packet.data.size < len.to_i
266
- warn "Packet ##{packet_count} is corrupted: expected #{len.to_i}, got #{pcap_packet.data.size}. Exiting."
267
- break
268
- end
269
- if block
270
- yield pcap_packet
271
- else
272
- pcap_packets << pcap_packet.clone
273
- end
274
- end
275
- ensure
276
- file_handle.close
277
- end
278
- block ? packet_count : pcap_packets
279
- end
280
-
281
- # Takes a filename, and an optional block. If a block is given,
282
- # yield back the raw packet data from the given file. Otherwise,
283
- # return an array of parsed packets.
284
- def read_packet_bytes(fname,&block)
285
- count = 0
286
- packets = [] unless block
287
- read(fname) do |packet|
288
- if block
289
- count += 1
290
- yield packet.data.to_s
291
- else
292
- packets << packet.data.to_s
293
- end
294
- end
295
- block ? count : packets
296
- end
297
-
298
- alias :file_to_array :read_packet_bytes
299
-
300
- # Takes a filename, and an optional block. If a block is given,
301
- # yield back parsed packets from the given file. Otherwise, return
302
- # an array of parsed packets.
303
- #
304
- # This is a brazillian times faster than the old methods of extracting
305
- # packets from files.
306
- def read_packets(fname,&block)
307
- count = 0
308
- packets = [] unless block
309
- read_packet_bytes(fname) do |packet|
310
- if block
311
- count += 1
312
- yield Packet.parse(packet)
313
- else
314
- packets << Packet.parse(packet)
315
- end
316
- end
317
- block ? count : packets
318
- end
319
-
320
- end
321
-
322
- def initialize(args={})
323
- init_fields(args)
324
- @filename = args.delete :filename
325
- super(args[:endian], args[:head], args[:body])
326
- end
327
-
328
- # Called by initialize to set the initial fields.
329
- def init_fields(args={})
330
- args[:head] = PcapHeader.new(:endian => args[:endian]).read(args[:head])
331
- args[:body] = PcapPackets.new(:endian => args[:endian]).read(args[:body])
332
- return args
333
- end
334
-
335
- # Returns the object in string form.
336
- def to_s
337
- self[:head].to_s + self[:body].map {|p| p.to_s}.join
338
- end
339
-
340
- # Clears the contents of the PcapFile.
341
- def clear
342
- self[:body].clear
343
- end
344
-
345
- # Reads a string to populate the object. Note that this appends new packets to
346
- # any existing packets in the PcapFile.
347
- def read(str)
348
- force_binary(str)
349
- self[:head].read str[0,24]
350
- self[:body].read str
351
- self
352
- end
353
-
354
- # Clears the contents of the PcapFile prior to reading in a new string.
355
- def read!(str)
356
- clear
357
- force_binary(str)
358
- self.read str
359
- end
360
-
361
- # A shorthand method for opening a file and reading in the packets. Note
362
- # that readfile clears any existing packets, since that seems to be the
363
- # typical use.
364
- def readfile(file)
365
- fdata = File.open(file, "rb") {|f| f.read}
366
- self.read! fdata
367
- end
368
-
369
- # Calls the class method with this object's @filename
370
- def read_packet_bytes(fname=@filename,&block)
371
- raise ArgumentError, "Need a file" unless fname
372
- return self.class.read_packet_bytes(fname, &block)
373
- end
374
-
375
- # Calls the class method with this object's @filename
376
- def read_packets(fname=@filename,&block)
377
- raise ArgumentError, "Need a file" unless fname
378
- return self.class.read_packets(fname, &block)
379
- end
380
-
381
- # file_to_array() translates a libpcap file into an array of packets.
382
- # Note that this strips out pcap timestamps -- if you'd like to retain
383
- # timestamps and other libpcap file information, you will want to
384
- # use read() instead.
385
- def file_to_array(args={})
386
- filename = args[:filename] || args[:file] || args[:f]
387
- if filename
388
- self.read! File.open(filename, "rb") {|f| f.read}
389
- end
390
- if args[:keep_timestamps] || args[:keep_ts] || args[:ts]
391
- self[:body].map {|x| {x.timestamp.to_s => x.data.to_s} }
392
- else
393
- self[:body].map {|x| x.data.to_s}
394
- end
395
- end
396
-
397
- alias_method :f2a, :file_to_array
398
-
399
- # Takes an array of packets (as generated by file_to_array), and writes them
400
- # to a file. Valid arguments are:
401
- #
402
- # :filename
403
- # :array # Can either be an array of packet data, or a hash-value pair of timestamp => data.
404
- # :timestamp # Sets an initial timestamp
405
- # :ts_inc # Sets the increment between timestamps. Defaults to 1 second.
406
- # :append # If true, then the packets are appended to the end of a file.
407
- def array_to_file(args={})
408
- if args.kind_of? Hash
409
- filename = args[:filename] || args[:file] || args[:f]
410
- arr = args[:array] || args[:arr] || args[:a]
411
- ts = args[:timestamp] || args[:ts] || Time.now.to_i
412
- ts_inc = args[:timestamp_increment] || args[:ts_inc] || 1
413
- append = !!args[:append]
414
- elsif args.kind_of? Array
415
- arr = args
416
- filename = append = nil
417
- else
418
- raise ArgumentError, "Unknown argument. Need either a Hash or Array."
419
- end
420
- unless arr.kind_of? Array
421
- raise ArgumentError, "Need an array to read packets from"
422
- end
423
- arr.each_with_index do |p,i|
424
- if p.kind_of? Hash # Binary timestamps are included
425
- this_ts = p.keys.first
426
- this_incl_len = p.values.first.size
427
- this_orig_len = this_incl_len
428
- this_data = p.values.first
429
- else # it's an array
430
- this_ts = Timestamp.new(:endian => self[:endian], :sec => ts + (ts_inc * i)).to_s
431
- this_incl_len = p.to_s.size
432
- this_orig_len = this_incl_len
433
- this_data = p.to_s
434
- end
435
- this_pkt = PcapPacket.new({:endian => self[:endian],
436
- :timestamp => this_ts,
437
- :incl_len => this_incl_len,
438
- :orig_len => this_orig_len,
439
- :data => this_data }
440
- )
441
- self[:body] << this_pkt
442
- end
443
- if filename
444
- self.to_f(:filename => filename, :append => append)
445
- else
446
- self
447
- end
448
- end
449
-
450
- alias_method :a2f, :array_to_file
451
-
452
- # Just like array_to_file, but clears any existing packets from the array first.
453
- def array_to_file!(arr)
454
- clear
455
- array_to_file(arr)
456
- end
457
-
458
- alias_method :a2f!, :array_to_file!
459
-
460
- # Writes the PcapFile to a file. Takes the following arguments:
461
- #
462
- # :filename # The file to write to.
463
- # :append # If set to true, the packets are appended to the file, rather than overwriting.
464
- def to_file(args={})
465
- filename = args[:filename] || args[:file] || args[:f]
466
- unless (!filename.nil? || filename.kind_of?(String))
467
- raise ArgumentError, "Need a :filename for #{self.class}"
468
- end
469
- append = args[:append]
470
- if append
471
- if File.exists? filename
472
- File.open(filename,'ab') {|file| file.write(self.body.to_s)}
473
- else
474
- File.open(filename,'wb') {|file| file.write(self.to_s)}
475
- end
476
- else
477
- File.open(filename,'wb') {|file| file.write(self.to_s)}
478
- end
479
- [filename, self.body.sz, self.body.size]
480
- end
481
-
482
- alias_method :to_f, :to_file
483
-
484
- # Shorthand method for writing to a file. Can take either :file => 'name.pcap' or
485
- # simply 'name.pcap'
486
- def write(filename='out.pcap')
487
- if filename.kind_of?(Hash)
488
- f = filename[:filename] || filename[:file] || filename[:f] || 'out.pcap'
489
- else
490
- f = filename.to_s
491
- end
492
- self.to_file(:filename => f.to_s, :append => false)
493
- end
494
-
495
- # Shorthand method for appending to a file. Can take either :file => 'name.pcap' or
496
- # simply 'name.pcap'
497
- def append(filename='out.pcap')
498
- if filename.kind_of?(Hash)
499
- f = filename[:filename] || filename[:file] || filename[:f] || 'out.pcap'
500
- else
501
- f = filename.to_s
502
- end
503
- self.to_file(:filename => f, :append => true)
504
- end
505
-
506
- end
27
+ # PcapHeader represents the header portion of a libpcap file (the packets
28
+ # themselves are in the PcapPackets array). See
29
+ # http://wiki.wireshark.org/Development/LibpcapFileFormat for details.
30
+ #
31
+ # Depending on the endianness (set with :endian), elements are either
32
+ # :little endian or :big endian.
33
+ #
34
+ # ==== PcapHeader Definition
35
+ #
36
+ # Symbol :endian Default: :little
37
+ # Int32 :magic Default: 0xa1b2c3d4 # :big is 0xd4c3b2a1
38
+ # Int16 :ver_major Default: 2
39
+ # Int16 :ver_minor Default: 4
40
+ # Int32 :thiszone
41
+ # Int32 :sigfigs
42
+ # Int32 :snaplen Default: 0xffff
43
+ # Int32 :network Default: 1
44
+ class PcapHeader < Struct.new(:endian, :magic, :ver_major, :ver_minor,
45
+ :thiszone, :sigfigs, :snaplen, :network)
46
+ include StructFu
47
+
48
+ MAGIC_INT32 = 0xa1b2c3d4
49
+ MAGIC_LITTLE = [MAGIC_INT32].pack("V")
50
+ MAGIC_BIG = [MAGIC_INT32].pack("N")
51
+
52
+ def initialize(args={})
53
+ set_endianness(args[:endian] ||= :little)
54
+ init_fields(args)
55
+ super(args[:endian], args[:magic], args[:ver_major],
56
+ args[:ver_minor], args[:thiszone], args[:sigfigs],
57
+ args[:snaplen], args[:network])
58
+ end
59
+
60
+ # Called by initialize to set the initial fields.
61
+ def init_fields(args={})
62
+ args[:magic] = @int32.new(args[:magic] || PcapHeader::MAGIC_INT32)
63
+ args[:ver_major] = @int16.new(args[:ver_major] || 2)
64
+ args[:ver_minor] ||= @int16.new(args[:ver_minor] || 4)
65
+ args[:thiszone] ||= @int32.new(args[:thiszone])
66
+ args[:sigfigs] ||= @int32.new(args[:sigfigs])
67
+ args[:snaplen] ||= @int32.new(args[:snaplen] || 0xffff)
68
+ args[:network] ||= @int32.new(args[:network] || 1)
69
+ return args
70
+ end
71
+
72
+ # Returns the object in string form.
73
+ def to_s
74
+ self.to_a[1,7].map {|x| x.to_s}.join
75
+ end
76
+
77
+ # Reads a string to populate the object.
78
+ # TODO: Need to test this by getting a hold of a big endian pcap file.
79
+ # Conversion from big to little shouldn't be that big of a deal.
80
+ def read(str)
81
+ force_binary(str)
82
+ return self if str.nil?
83
+ str.force_encoding(Encoding::BINARY) if str.respond_to? :force_encoding
84
+ if str[0,4] == self[:magic].to_s
85
+ self[:magic].read str[0,4]
86
+ self[:ver_major].read str[4,2]
87
+ self[:ver_minor].read str[6,2]
88
+ self[:thiszone].read str[8,4]
89
+ self[:sigfigs].read str[12,4]
90
+ self[:snaplen].read str[16,4]
91
+ self[:network].read str[20,4]
92
+ else
93
+ raise "Incorrect magic for libpcap"
94
+ end
95
+ self
96
+ end
97
+
98
+ end
99
+
100
+ # The Timestamp class defines how Timestamps appear in libpcap files.
101
+ #
102
+ # ==== Header Definition
103
+ #
104
+ # Symbol :endian Default: :little
105
+ # Int32 :sec
106
+ # Int32 :usec
107
+ class Timestamp < Struct.new(:endian, :sec, :usec)
108
+ include StructFu
109
+
110
+ def initialize(args={})
111
+ set_endianness(args[:endian] ||= :little)
112
+ init_fields(args)
113
+ super(args[:endian], args[:sec], args[:usec])
114
+ end
115
+
116
+ # Called by initialize to set the initial fields.
117
+ def init_fields(args={})
118
+ args[:sec] = @int32.new(args[:sec])
119
+ args[:usec] = @int32.new(args[:usec])
120
+ return args
121
+ end
122
+
123
+ # Returns the object in string form.
124
+ def to_s
125
+ self.to_a[1,2].map {|x| x.to_s}.join
126
+ end
127
+
128
+ # Reads a string to populate the object.
129
+ def read(str)
130
+ force_binary(str)
131
+ return self if str.nil?
132
+ self[:sec].read str[0,4]
133
+ self[:usec].read str[4,4]
134
+ self
135
+ end
136
+
137
+ end
138
+
139
+ # PcapPacket defines how individual packets are stored in a libpcap-formatted
140
+ # file.
141
+ #
142
+ # ==== Header Definition
143
+ #
144
+ # Timestamp :timestamp
145
+ # Int32 :incl_len
146
+ # Int32 :orig_len
147
+ # String :data
148
+ class PcapPacket < Struct.new(:endian, :timestamp, :incl_len,
149
+ :orig_len, :data)
150
+ include StructFu
151
+ def initialize(args={})
152
+ set_endianness(args[:endian] ||= :little)
153
+ init_fields(args)
154
+ super(args[:endian], args[:timestamp], args[:incl_len],
155
+ args[:orig_len], args[:data])
156
+ end
157
+
158
+ # Called by initialize to set the initial fields.
159
+ def init_fields(args={})
160
+ args[:timestamp] = Timestamp.new(:endian => args[:endian]).read(args[:timestamp])
161
+ args[:incl_len] = args[:incl_len].nil? ? @int32.new(args[:data].to_s.size) : @int32.new(args[:incl_len])
162
+ args[:orig_len] = @int32.new(args[:orig_len])
163
+ args[:data] = StructFu::String.new.read(args[:data])
164
+ end
165
+
166
+ # Returns the object in string form.
167
+ def to_s
168
+ self.to_a[1,4].map {|x| x.to_s}.join
169
+ end
170
+
171
+ # Reads a string to populate the object.
172
+ def read(str)
173
+ return unless str
174
+ force_binary(str)
175
+ self[:timestamp].read str[0,8]
176
+ self[:incl_len].read str[8,4]
177
+ self[:orig_len].read str[12,4]
178
+ self[:data].read str[16,self[:incl_len].to_i]
179
+ self
180
+ end
181
+
182
+ end
183
+
184
+ # PcapPackets is a collection of PcapPacket objects.
185
+ class PcapPackets < Array
186
+
187
+ include StructFu
188
+
189
+ attr_accessor :endian # probably ought to be read-only but who am i.
190
+
191
+ def initialize(args={})
192
+ @endian = args[:endian] || :little
193
+ end
194
+
195
+ def force_binary(str)
196
+ str.force_encoding Encoding::BINARY if str.respond_to? :force_encoding
197
+ end
198
+
199
+ # Reads a string to populate the object. Note, this read takes in the
200
+ # whole pcap file, since we need to see the magic to know what
201
+ # endianness we're dealing with.
202
+ def read(str)
203
+ force_binary(str)
204
+ return self if str.nil?
205
+ if str[0,4] == PcapHeader::MAGIC_BIG
206
+ @endian = :big
207
+ elsif str[0,4] == PcapHeader::MAGIC_LITTLE
208
+ @endian = :little
209
+ else
210
+ raise ArgumentError, "Unknown file format for #{self.class}"
211
+ end
212
+ body = str[24,str.size]
213
+ while body.size > 16 # TODO: catch exceptions on malformed packets at end
214
+ p = PcapPacket.new(:endian => @endian)
215
+ p.read(body)
216
+ self<<p
217
+ body = body[p.sz,body.size]
218
+ end
219
+ self
220
+ end
221
+
222
+ def to_s
223
+ self.join
224
+ end
225
+
226
+ end
227
+
228
+ # PcapFile is a complete libpcap file struct, made up of two elements, a
229
+ # PcapHeader and PcapPackets.
230
+ #
231
+ # See http://wiki.wireshark.org/Development/LibpcapFileFormat
232
+ #
233
+ # PcapFile also can behave as a singleton class, which is usually the better
234
+ # way to handle pcap files of really any size, since it doesn't require
235
+ # storing packets before handing them off to a given block. This is really
236
+ # the way to go.
237
+ class PcapFile < Struct.new(:endian, :head, :body)
238
+ include StructFu
239
+
240
+ class << self
241
+
242
+ # Takes a given file and returns an array of the packet bytes. Here
243
+ # for backwards compatibilty.
244
+ def file_to_array(fname)
245
+ PcapFile.new.file_to_array(:f => fname)
246
+ end
247
+
248
+ # Takes a given file name, and reads out the packets. If given a block,
249
+ # it will yield back a PcapPacket object per packet found.
250
+ def read(fname,&block)
251
+ file_header = PcapHeader.new
252
+ pcap_packets = PcapPackets.new
253
+ unless File.readable? fname
254
+ raise ArgumentError, "Cannot read file `#{fname}'"
255
+ end
256
+ begin
257
+ file_handle = File.open(fname, "rb")
258
+ file_header.read file_handle.read(24)
259
+ packet_count = 0
260
+ pcap_packet = PcapPacket.new(:endian => file_header.endian)
261
+ while pcap_packet.read file_handle.read(16) do
262
+ len = pcap_packet.incl_len
263
+ pcap_packet.data = StructFu::String.new.read(file_handle.read(len.to_i))
264
+ packet_count += 1
265
+ if pcap_packet.data.size < len.to_i
266
+ warn "Packet ##{packet_count} is corrupted: expected #{len.to_i}, got #{pcap_packet.data.size}. Exiting."
267
+ break
268
+ end
269
+ if block
270
+ yield pcap_packet
271
+ else
272
+ pcap_packets << pcap_packet.clone
273
+ end
274
+ end
275
+ ensure
276
+ file_handle.close
277
+ end
278
+ block ? packet_count : pcap_packets
279
+ end
280
+
281
+ # Takes a filename, and an optional block. If a block is given,
282
+ # yield back the raw packet data from the given file. Otherwise,
283
+ # return an array of parsed packets.
284
+ def read_packet_bytes(fname,&block)
285
+ count = 0
286
+ packets = [] unless block
287
+ read(fname) do |packet|
288
+ if block
289
+ count += 1
290
+ yield packet.data.to_s
291
+ else
292
+ packets << packet.data.to_s
293
+ end
294
+ end
295
+ block ? count : packets
296
+ end
297
+
298
+ alias :file_to_array :read_packet_bytes
299
+
300
+ # Takes a filename, and an optional block. If a block is given,
301
+ # yield back parsed packets from the given file. Otherwise, return
302
+ # an array of parsed packets.
303
+ #
304
+ # This is a brazillian times faster than the old methods of extracting
305
+ # packets from files.
306
+ def read_packets(fname,&block)
307
+ count = 0
308
+ packets = [] unless block
309
+ read_packet_bytes(fname) do |packet|
310
+ if block
311
+ count += 1
312
+ yield Packet.parse(packet)
313
+ else
314
+ packets << Packet.parse(packet)
315
+ end
316
+ end
317
+ block ? count : packets
318
+ end
319
+
320
+ end
321
+
322
+ def initialize(args={})
323
+ init_fields(args)
324
+ @filename = args.delete :filename
325
+ super(args[:endian], args[:head], args[:body])
326
+ end
327
+
328
+ # Called by initialize to set the initial fields.
329
+ def init_fields(args={})
330
+ args[:head] = PcapHeader.new(:endian => args[:endian]).read(args[:head])
331
+ args[:body] = PcapPackets.new(:endian => args[:endian]).read(args[:body])
332
+ return args
333
+ end
334
+
335
+ # Returns the object in string form.
336
+ def to_s
337
+ self[:head].to_s + self[:body].map {|p| p.to_s}.join
338
+ end
339
+
340
+ # Clears the contents of the PcapFile.
341
+ def clear
342
+ self[:body].clear
343
+ end
344
+
345
+ # Reads a string to populate the object. Note that this appends new packets to
346
+ # any existing packets in the PcapFile.
347
+ def read(str)
348
+ force_binary(str)
349
+ self[:head].read str[0,24]
350
+ self[:body].read str
351
+ self
352
+ end
353
+
354
+ # Clears the contents of the PcapFile prior to reading in a new string.
355
+ def read!(str)
356
+ clear
357
+ force_binary(str)
358
+ self.read str
359
+ end
360
+
361
+ # A shorthand method for opening a file and reading in the packets. Note
362
+ # that readfile clears any existing packets, since that seems to be the
363
+ # typical use.
364
+ def readfile(file)
365
+ fdata = File.open(file, "rb") {|f| f.read}
366
+ self.read! fdata
367
+ end
368
+
369
+ # Calls the class method with this object's @filename
370
+ def read_packet_bytes(fname=@filename,&block)
371
+ raise ArgumentError, "Need a file" unless fname
372
+ return self.class.read_packet_bytes(fname, &block)
373
+ end
374
+
375
+ # Calls the class method with this object's @filename
376
+ def read_packets(fname=@filename,&block)
377
+ raise ArgumentError, "Need a file" unless fname
378
+ return self.class.read_packets(fname, &block)
379
+ end
380
+
381
+ # file_to_array() translates a libpcap file into an array of packets.
382
+ # Note that this strips out pcap timestamps -- if you'd like to retain
383
+ # timestamps and other libpcap file information, you will want to
384
+ # use read() instead.
385
+ def file_to_array(args={})
386
+ filename = args[:filename] || args[:file] || args[:f]
387
+ if filename
388
+ self.read! File.open(filename, "rb") {|f| f.read}
389
+ end
390
+ if args[:keep_timestamps] || args[:keep_ts] || args[:ts]
391
+ self[:body].map {|x| {x.timestamp.to_s => x.data.to_s} }
392
+ else
393
+ self[:body].map {|x| x.data.to_s}
394
+ end
395
+ end
396
+
397
+ alias_method :f2a, :file_to_array
398
+
399
+ # Takes an array of packets (as generated by file_to_array), and writes them
400
+ # to a file. Valid arguments are:
401
+ #
402
+ # :filename
403
+ # :array # Can either be an array of packet data, or a hash-value pair of timestamp => data.
404
+ # :timestamp # Sets an initial timestamp
405
+ # :ts_inc # Sets the increment between timestamps. Defaults to 1 second.
406
+ # :append # If true, then the packets are appended to the end of a file.
407
+ def array_to_file(args={})
408
+ if args.kind_of? Hash
409
+ filename = args[:filename] || args[:file] || args[:f]
410
+ arr = args[:array] || args[:arr] || args[:a]
411
+ ts = args[:timestamp] || args[:ts] || Time.now.to_i
412
+ ts_inc = args[:timestamp_increment] || args[:ts_inc] || 1
413
+ append = !!args[:append]
414
+ elsif args.kind_of? Array
415
+ arr = args
416
+ filename = append = nil
417
+ else
418
+ raise ArgumentError, "Unknown argument. Need either a Hash or Array."
419
+ end
420
+ unless arr.kind_of? Array
421
+ raise ArgumentError, "Need an array to read packets from"
422
+ end
423
+ arr.each_with_index do |p,i|
424
+ if p.kind_of? Hash # Binary timestamps are included
425
+ this_ts = p.keys.first
426
+ this_incl_len = p.values.first.size
427
+ this_orig_len = this_incl_len
428
+ this_data = p.values.first
429
+ else # it's an array
430
+ this_ts = Timestamp.new(:endian => self[:endian], :sec => ts + (ts_inc * i)).to_s
431
+ this_incl_len = p.to_s.size
432
+ this_orig_len = this_incl_len
433
+ this_data = p.to_s
434
+ end
435
+ this_pkt = PcapPacket.new({:endian => self[:endian],
436
+ :timestamp => this_ts,
437
+ :incl_len => this_incl_len,
438
+ :orig_len => this_orig_len,
439
+ :data => this_data }
440
+ )
441
+ self[:body] << this_pkt
442
+ end
443
+ if filename
444
+ self.to_f(:filename => filename, :append => append)
445
+ else
446
+ self
447
+ end
448
+ end
449
+
450
+ alias_method :a2f, :array_to_file
451
+
452
+ # Just like array_to_file, but clears any existing packets from the array first.
453
+ def array_to_file!(arr)
454
+ clear
455
+ array_to_file(arr)
456
+ end
457
+
458
+ alias_method :a2f!, :array_to_file!
459
+
460
+ # Writes the PcapFile to a file. Takes the following arguments:
461
+ #
462
+ # :filename # The file to write to.
463
+ # :append # If set to true, the packets are appended to the file, rather than overwriting.
464
+ def to_file(args={})
465
+ filename = args[:filename] || args[:file] || args[:f]
466
+ unless (!filename.nil? || filename.kind_of?(String))
467
+ raise ArgumentError, "Need a :filename for #{self.class}"
468
+ end
469
+ append = args[:append]
470
+ if append
471
+ if File.exists? filename
472
+ File.open(filename,'ab') {|file| file.write(self.body.to_s)}
473
+ else
474
+ File.open(filename,'wb') {|file| file.write(self.to_s)}
475
+ end
476
+ else
477
+ File.open(filename,'wb') {|file| file.write(self.to_s)}
478
+ end
479
+ [filename, self.body.sz, self.body.size]
480
+ end
481
+
482
+ alias_method :to_f, :to_file
483
+
484
+ # Shorthand method for writing to a file. Can take either :file => 'name.pcap' or
485
+ # simply 'name.pcap'
486
+ def write(filename='out.pcap')
487
+ if filename.kind_of?(Hash)
488
+ f = filename[:filename] || filename[:file] || filename[:f] || 'out.pcap'
489
+ else
490
+ f = filename.to_s
491
+ end
492
+ self.to_file(:filename => f.to_s, :append => false)
493
+ end
494
+
495
+ # Shorthand method for appending to a file. Can take either :file => 'name.pcap' or
496
+ # simply 'name.pcap'
497
+ def append(filename='out.pcap')
498
+ if filename.kind_of?(Hash)
499
+ f = filename[:filename] || filename[:file] || filename[:f] || 'out.pcap'
500
+ else
501
+ f = filename.to_s
502
+ end
503
+ self.to_file(:filename => f, :append => true)
504
+ end
505
+
506
+ end
507
507
 
508
508
  end
509
509
 
510
510
  module PacketFu
511
511
 
512
- # Read is largely deprecated. It was current in PacketFu 0.2.0, but isn't all that useful
513
- # in 0.3.0 and beyond. Expect it to go away completely by version 1.0. So, the main use
514
- # of this class is to learn how to do exactly the same things using the PcapFile object.
515
- class Read
512
+ # Read is largely deprecated. It was current in PacketFu 0.2.0, but isn't all that useful
513
+ # in 0.3.0 and beyond. Expect it to go away completely by version 1.0. So, the main use
514
+ # of this class is to learn how to do exactly the same things using the PcapFile object.
515
+ class Read
516
516
 
517
- class << self
517
+ class << self
518
518
 
519
- # Reads the magic string of a pcap file, and determines
520
- # if it's :little or :big endian.
521
- def get_byte_order(pcap_file)
522
- byte_order = ((pcap_file[0,4] == PcapHeader::MAGIC_LITTLE) ? :little : :big)
523
- return byte_order
524
- end
519
+ # Reads the magic string of a pcap file, and determines
520
+ # if it's :little or :big endian.
521
+ def get_byte_order(pcap_file)
522
+ byte_order = ((pcap_file[0,4] == PcapHeader::MAGIC_LITTLE) ? :little : :big)
523
+ return byte_order
524
+ end
525
525
 
526
- # set_byte_order is pretty much totally deprecated.
527
- def set_byte_order(byte_order)
528
- PacketFu.instance_variable_set(:@byte_order,byte_order)
529
- return true
530
- end
526
+ # set_byte_order is pretty much totally deprecated.
527
+ def set_byte_order(byte_order)
528
+ PacketFu.instance_variable_set(:@byte_order,byte_order)
529
+ return true
530
+ end
531
531
 
532
- # A wrapper for PcapFile#file_to_array, but only returns the array. Actually
533
- # using the PcapFile object is going to be more useful.
534
- def file_to_array(args={})
535
- filename = args[:filename] || args[:file] || args[:out]
536
- raise ArgumentError, "Need a :filename in string form to read from." if (filename.nil? || filename.class != String)
537
- PcapFile.new.file_to_array(args)
538
- end
532
+ # A wrapper for PcapFile#file_to_array, but only returns the array. Actually
533
+ # using the PcapFile object is going to be more useful.
534
+ def file_to_array(args={})
535
+ filename = args[:filename] || args[:file] || args[:out]
536
+ raise ArgumentError, "Need a :filename in string form to read from." if (filename.nil? || filename.class != String)
537
+ PcapFile.new.file_to_array(args)
538
+ end
539
539
 
540
- alias_method :f2a, :file_to_array
540
+ alias_method :f2a, :file_to_array
541
541
 
542
- end
542
+ end
543
543
 
544
- end
544
+ end
545
545
 
546
546
  end
547
547
 
548
548
  module PacketFu
549
549
 
550
- # Write is largely deprecated. It was current in PacketFu 0.2.0, but isn't all that useful
551
- # in 0.3.0 and beyond. Expect it to go away completely by version 1.0, as working with
552
- # PacketFu::PcapFile directly is generally going to be more rewarding.
553
- class Write
554
-
555
- class << self
556
-
557
- # format_packets: Pretty much totally deprecated.
558
- def format_packets(args={})
559
- arr = args[:arr] || args[:array] || []
560
- ts = args[:ts] || args[:timestamp] || Time.now.to_i
561
- ts_inc = args[:ts_inc] || args[:timestamp_increment]
562
- pkts = PcapFile.new.array_to_file(:endian => PacketFu.instance_variable_get(:@byte_order),
563
- :arr => arr,
564
- :ts => ts,
565
- :ts_inc => ts_inc)
566
- pkts.body
567
- end
568
-
569
- # array_to_file is a largely deprecated function for writing arrays of pcaps to a file.
570
- # Use PcapFile#array_to_file instead.
571
- def array_to_file(args={})
572
- filename = args[:filename] || args[:file] || args[:out] || :nowrite
573
- arr = args[:arr] || args[:array] || []
574
- ts = args[:ts] || args[:timestamp] || args[:time_stamp] || Time.now.to_f
575
- ts_inc = args[:ts_inc] || args[:timestamp_increment] || args[:time_stamp_increment]
576
- byte_order = args[:byte_order] || args[:byteorder] || args[:endian] || args[:endianness] || :little
577
- append = args[:append]
578
- Read.set_byte_order(byte_order) if [:big, :little].include? byte_order
579
- pf = PcapFile.new
580
- pf.array_to_file(:endian => PacketFu.instance_variable_get(:@byte_order),
581
- :arr => arr,
582
- :ts => ts,
583
- :ts_inc => ts_inc)
584
- if filename && filename != :nowrite
585
- if append
586
- pf.append(filename)
587
- else
588
- pf.write(filename)
589
- end
590
- return [filename,pf.to_s.size,arr.size,ts,ts_inc]
591
- else
592
- return [nil,pf.to_s.size,arr.size,ts,ts_inc]
593
- end
594
-
595
- end
596
-
597
- alias_method :a2f, :array_to_file
598
-
599
- # Shorthand method for appending to a file. Also shouldn't use.
600
- def append(args={})
601
- array_to_file(args.merge(:append => true))
602
- end
603
-
604
- end
605
-
606
- end
550
+ # Write is largely deprecated. It was current in PacketFu 0.2.0, but isn't all that useful
551
+ # in 0.3.0 and beyond. Expect it to go away completely by version 1.0, as working with
552
+ # PacketFu::PcapFile directly is generally going to be more rewarding.
553
+ class Write
554
+
555
+ class << self
556
+
557
+ # format_packets: Pretty much totally deprecated.
558
+ def format_packets(args={})
559
+ arr = args[:arr] || args[:array] || []
560
+ ts = args[:ts] || args[:timestamp] || Time.now.to_i
561
+ ts_inc = args[:ts_inc] || args[:timestamp_increment]
562
+ pkts = PcapFile.new.array_to_file(:endian => PacketFu.instance_variable_get(:@byte_order),
563
+ :arr => arr,
564
+ :ts => ts,
565
+ :ts_inc => ts_inc)
566
+ pkts.body
567
+ end
568
+
569
+ # array_to_file is a largely deprecated function for writing arrays of pcaps to a file.
570
+ # Use PcapFile#array_to_file instead.
571
+ def array_to_file(args={})
572
+ filename = args[:filename] || args[:file] || args[:out] || :nowrite
573
+ arr = args[:arr] || args[:array] || []
574
+ ts = args[:ts] || args[:timestamp] || args[:time_stamp] || Time.now.to_f
575
+ ts_inc = args[:ts_inc] || args[:timestamp_increment] || args[:time_stamp_increment]
576
+ byte_order = args[:byte_order] || args[:byteorder] || args[:endian] || args[:endianness] || :little
577
+ append = args[:append]
578
+ Read.set_byte_order(byte_order) if [:big, :little].include? byte_order
579
+ pf = PcapFile.new
580
+ pf.array_to_file(:endian => PacketFu.instance_variable_get(:@byte_order),
581
+ :arr => arr,
582
+ :ts => ts,
583
+ :ts_inc => ts_inc)
584
+ if filename && filename != :nowrite
585
+ if append
586
+ pf.append(filename)
587
+ else
588
+ pf.write(filename)
589
+ end
590
+ return [filename,pf.to_s.size,arr.size,ts,ts_inc]
591
+ else
592
+ return [nil,pf.to_s.size,arr.size,ts,ts_inc]
593
+ end
594
+
595
+ end
596
+
597
+ alias_method :a2f, :array_to_file
598
+
599
+ # Shorthand method for appending to a file. Also shouldn't use.
600
+ def append(args={})
601
+ array_to_file(args.merge(:append => true))
602
+ end
603
+
604
+ end
605
+
606
+ end
607
607
 
608
608
  end
609
609