packetfu 1.1.8 → 1.1.9

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 (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