packetfu 1.1.8 → 1.1.9

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