packetfu 1.1.11 → 1.1.12.pre

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 (193) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +3 -1
  3. data/.rspec +2 -0
  4. data/.travis.yml +2 -3
  5. data/README.md +127 -0
  6. data/examples/100kpackets.rb +11 -10
  7. data/examples/ackscan.rb +4 -1
  8. data/examples/arp.rb +4 -5
  9. data/examples/arphood.rb +5 -4
  10. data/examples/dissect_thinger.rb +10 -7
  11. data/examples/ethernet.rb +8 -3
  12. data/examples/ids.rb +22 -4
  13. data/examples/idsv2.rb +25 -6
  14. data/examples/ifconfig.rb +6 -3
  15. data/examples/new-simple-stats.rb +5 -6
  16. data/examples/packetfu-shell.rb +11 -48
  17. data/examples/pcap2pcapng.rb +32 -0
  18. data/examples/simple-sniffer.rb +9 -4
  19. data/examples/simple-stats.rb +7 -8
  20. data/examples/slammer.rb +2 -2
  21. data/examples/uniqpcap.rb +17 -7
  22. data/lib/packetfu.rb +10 -175
  23. data/lib/packetfu/capture.rb +2 -2
  24. data/lib/packetfu/common.rb +142 -0
  25. data/lib/packetfu/config.rb +8 -8
  26. data/lib/packetfu/inject.rb +3 -3
  27. data/lib/packetfu/packet.rb +22 -18
  28. data/lib/packetfu/pcap.rb +2 -1
  29. data/lib/packetfu/pcapng.rb +37 -0
  30. data/lib/packetfu/pcapng/block.rb +25 -0
  31. data/lib/packetfu/pcapng/epb.rb +112 -0
  32. data/lib/packetfu/pcapng/file.rb +316 -0
  33. data/lib/packetfu/pcapng/idb.rb +125 -0
  34. data/lib/packetfu/pcapng/shb.rb +146 -0
  35. data/lib/packetfu/pcapng/spb.rb +83 -0
  36. data/lib/packetfu/pcapng/unknown_block.rb +60 -0
  37. data/lib/packetfu/protos.rb +3 -0
  38. data/lib/packetfu/protos/arp.rb +10 -10
  39. data/lib/packetfu/protos/icmpv6.rb +131 -0
  40. data/lib/packetfu/protos/icmpv6/header.rb +69 -0
  41. data/lib/packetfu/protos/icmpv6/mixin.rb +14 -0
  42. data/lib/packetfu/protos/ip.rb +4 -5
  43. data/lib/packetfu/protos/ipv6/header.rb +2 -0
  44. data/lib/packetfu/protos/udp.rb +24 -12
  45. data/lib/packetfu/structfu.rb +27 -0
  46. data/lib/packetfu/utils.rb +55 -9
  47. data/lib/packetfu/version.rb +1 -1
  48. data/packetfu.gemspec +13 -7
  49. data/spec/arp_spec.rb +11 -5
  50. data/spec/eth_spec.rb +20 -11
  51. data/spec/fake_packets.rb +28 -0
  52. data/spec/hsrp_spec.rb +15 -0
  53. data/spec/icmp_spec.rb +12 -5
  54. data/spec/icmpv6_spec.rb +98 -0
  55. data/spec/invalid_spec.rb +28 -0
  56. data/spec/ip_spec.rb +10 -5
  57. data/spec/ipv4_icmp.pcap +0 -0
  58. data/spec/ipv4_udp.pcap +0 -0
  59. data/spec/ipv6_icmp.pcap +0 -0
  60. data/spec/ipv6_spec.rb +4 -0
  61. data/spec/ipv6_udp.pcap +0 -0
  62. data/spec/lldp_spec.rb +36 -0
  63. data/spec/octets_spec.rb +43 -0
  64. data/spec/packet_spec.rb +24 -0
  65. data/spec/packetfu_spec.rb +6 -1
  66. data/spec/pcap_spec.rb +286 -0
  67. data/spec/pcapng/epb_spec.rb +81 -0
  68. data/spec/pcapng/file_spec.rb +295 -0
  69. data/spec/pcapng/file_spec_helper.rb +45 -0
  70. data/spec/pcapng/idb_spec.rb +53 -0
  71. data/spec/pcapng/shb_spec.rb +42 -0
  72. data/spec/pcapng/spb_spec.rb +43 -0
  73. data/spec/pcapng/unknown_block_spec.rb +36 -0
  74. data/spec/spec_helper.rb +3 -31
  75. data/spec/tcp_spec.rb +4 -1
  76. data/spec/udp_spec.rb +149 -1
  77. data/spec/utils_spec.rb +98 -15
  78. data/test/pcapng-test/output_be/advanced/test100.pcapng +0 -0
  79. data/test/pcapng-test/output_be/advanced/test100.txt +11 -0
  80. data/test/pcapng-test/output_be/advanced/test101.pcapng +0 -0
  81. data/test/pcapng-test/output_be/advanced/test101.txt +11 -0
  82. data/test/pcapng-test/output_be/advanced/test102.pcapng +0 -0
  83. data/test/pcapng-test/output_be/advanced/test102.txt +14 -0
  84. data/test/pcapng-test/output_be/basic/test001.pcapng +0 -0
  85. data/test/pcapng-test/output_be/basic/test001.txt +9 -0
  86. data/test/pcapng-test/output_be/basic/test002.pcapng +0 -0
  87. data/test/pcapng-test/output_be/basic/test002.txt +7 -0
  88. data/test/pcapng-test/output_be/basic/test003.pcapng +0 -0
  89. data/test/pcapng-test/output_be/basic/test003.txt +8 -0
  90. data/test/pcapng-test/output_be/basic/test004.pcapng +0 -0
  91. data/test/pcapng-test/output_be/basic/test004.txt +9 -0
  92. data/test/pcapng-test/output_be/basic/test005.pcapng +0 -0
  93. data/test/pcapng-test/output_be/basic/test005.txt +9 -0
  94. data/test/pcapng-test/output_be/basic/test006.pcapng +0 -0
  95. data/test/pcapng-test/output_be/basic/test006.txt +9 -0
  96. data/test/pcapng-test/output_be/basic/test007.pcapng +0 -0
  97. data/test/pcapng-test/output_be/basic/test007.txt +9 -0
  98. data/test/pcapng-test/output_be/basic/test008.pcapng +0 -0
  99. data/test/pcapng-test/output_be/basic/test008.txt +9 -0
  100. data/test/pcapng-test/output_be/basic/test009.pcapng +0 -0
  101. data/test/pcapng-test/output_be/basic/test009.txt +9 -0
  102. data/test/pcapng-test/output_be/basic/test010.pcapng +0 -0
  103. data/test/pcapng-test/output_be/basic/test010.txt +9 -0
  104. data/test/pcapng-test/output_be/basic/test011.pcapng +0 -0
  105. data/test/pcapng-test/output_be/basic/test011.txt +10 -0
  106. data/test/pcapng-test/output_be/basic/test012.pcapng +0 -0
  107. data/test/pcapng-test/output_be/basic/test012.txt +10 -0
  108. data/test/pcapng-test/output_be/basic/test013.pcapng +0 -0
  109. data/test/pcapng-test/output_be/basic/test013.txt +9 -0
  110. data/test/pcapng-test/output_be/basic/test014.pcapng +0 -0
  111. data/test/pcapng-test/output_be/basic/test014.txt +9 -0
  112. data/test/pcapng-test/output_be/basic/test015.pcapng +0 -0
  113. data/test/pcapng-test/output_be/basic/test015.txt +9 -0
  114. data/test/pcapng-test/output_be/basic/test016.pcapng +0 -0
  115. data/test/pcapng-test/output_be/basic/test016.txt +11 -0
  116. data/test/pcapng-test/output_be/basic/test017.pcapng +0 -0
  117. data/test/pcapng-test/output_be/basic/test017.txt +9 -0
  118. data/test/pcapng-test/output_be/basic/test018.pcapng +0 -0
  119. data/test/pcapng-test/output_be/basic/test018.txt +12 -0
  120. data/test/pcapng-test/output_be/difficult/test200.pcapng +0 -0
  121. data/test/pcapng-test/output_be/difficult/test200.txt +8 -0
  122. data/test/pcapng-test/output_be/difficult/test201.pcapng +0 -0
  123. data/test/pcapng-test/output_be/difficult/test201.txt +11 -0
  124. data/test/pcapng-test/output_be/difficult/test202.pcapng +0 -0
  125. data/test/pcapng-test/output_be/difficult/test202.txt +14 -0
  126. data/test/pcapng-test/output_le/advanced/test100.pcapng +0 -0
  127. data/test/pcapng-test/output_le/advanced/test100.txt +11 -0
  128. data/test/pcapng-test/output_le/advanced/test101.pcapng +0 -0
  129. data/test/pcapng-test/output_le/advanced/test101.txt +11 -0
  130. data/test/pcapng-test/output_le/advanced/test102.pcapng +0 -0
  131. data/test/pcapng-test/output_le/advanced/test102.txt +14 -0
  132. data/test/pcapng-test/output_le/basic/test001.pcapng +0 -0
  133. data/test/pcapng-test/output_le/basic/test001.txt +9 -0
  134. data/test/pcapng-test/output_le/basic/test002.pcapng +0 -0
  135. data/test/pcapng-test/output_le/basic/test002.txt +7 -0
  136. data/test/pcapng-test/output_le/basic/test003.pcapng +0 -0
  137. data/test/pcapng-test/output_le/basic/test003.txt +8 -0
  138. data/test/pcapng-test/output_le/basic/test004.pcapng +0 -0
  139. data/test/pcapng-test/output_le/basic/test004.txt +9 -0
  140. data/test/pcapng-test/output_le/basic/test005.pcapng +0 -0
  141. data/test/pcapng-test/output_le/basic/test005.txt +9 -0
  142. data/test/pcapng-test/output_le/basic/test006.pcapng +0 -0
  143. data/test/pcapng-test/output_le/basic/test006.txt +9 -0
  144. data/test/pcapng-test/output_le/basic/test007.pcapng +0 -0
  145. data/test/pcapng-test/output_le/basic/test007.txt +9 -0
  146. data/test/pcapng-test/output_le/basic/test008.pcapng +0 -0
  147. data/test/pcapng-test/output_le/basic/test008.txt +9 -0
  148. data/test/pcapng-test/output_le/basic/test009.pcapng +0 -0
  149. data/test/pcapng-test/output_le/basic/test009.txt +9 -0
  150. data/test/pcapng-test/output_le/basic/test010.pcapng +0 -0
  151. data/test/pcapng-test/output_le/basic/test010.txt +9 -0
  152. data/test/pcapng-test/output_le/basic/test011.pcapng +0 -0
  153. data/test/pcapng-test/output_le/basic/test011.txt +10 -0
  154. data/test/pcapng-test/output_le/basic/test012.pcapng +0 -0
  155. data/test/pcapng-test/output_le/basic/test012.txt +10 -0
  156. data/test/pcapng-test/output_le/basic/test013.pcapng +0 -0
  157. data/test/pcapng-test/output_le/basic/test013.txt +9 -0
  158. data/test/pcapng-test/output_le/basic/test014.pcapng +0 -0
  159. data/test/pcapng-test/output_le/basic/test014.txt +9 -0
  160. data/test/pcapng-test/output_le/basic/test015.pcapng +0 -0
  161. data/test/pcapng-test/output_le/basic/test015.txt +9 -0
  162. data/test/pcapng-test/output_le/basic/test016.pcapng +0 -0
  163. data/test/pcapng-test/output_le/basic/test016.txt +11 -0
  164. data/test/pcapng-test/output_le/basic/test017.pcapng +0 -0
  165. data/test/pcapng-test/output_le/basic/test017.txt +9 -0
  166. data/test/pcapng-test/output_le/basic/test018.pcapng +0 -0
  167. data/test/pcapng-test/output_le/basic/test018.txt +12 -0
  168. data/test/pcapng-test/output_le/difficult/test200.pcapng +0 -0
  169. data/test/pcapng-test/output_le/difficult/test200.txt +8 -0
  170. data/test/pcapng-test/output_le/difficult/test201.pcapng +0 -0
  171. data/test/pcapng-test/output_le/difficult/test201.txt +11 -0
  172. data/test/pcapng-test/output_le/difficult/test202.pcapng +0 -0
  173. data/test/pcapng-test/output_le/difficult/test202.txt +14 -0
  174. data/test/sample-ipv6.pcapng +0 -0
  175. data/test/sample-spb.pcapng +0 -0
  176. data/test/sample.pcapng +0 -0
  177. data/test/sample2.pcapng +0 -0
  178. metadata +190 -68
  179. checksums.yaml.gz.sig +0 -0
  180. data.tar.gz.sig +0 -2
  181. data/INSTALL.rdoc +0 -40
  182. data/README.rdoc +0 -64
  183. data/examples/examples.rb +0 -4
  184. data/setup.rb +0 -1586
  185. data/test/func_lldp.rb +0 -25
  186. data/test/ptest.rb +0 -16
  187. data/test/test_eth.rb +0 -93
  188. data/test/test_hsrp.rb +0 -20
  189. data/test/test_invalid.rb +0 -28
  190. data/test/test_octets.rb +0 -36
  191. data/test/test_pcap.rb +0 -211
  192. data/test/test_udp.rb +0 -100
  193. metadata.gz.sig +0 -2
@@ -1,10 +1,10 @@
1
1
  # -*- coding: binary -*-
2
2
  module PacketFu
3
3
 
4
- # The Config class holds various bits of useful default information
5
- # for packet creation. If initialized without arguments, @iface will be
4
+ # The Config class holds various bits of useful default information
5
+ # for packet creation. If initialized without arguments, @iface will be
6
6
  # set to ENV['IFACE'] or Pcap.lookupdev (or lo), and the @pcapfile will
7
- # be set to "/tmp/out.pcap" # (yes, it's Linux-biased, sorry, fixing
7
+ # be set to "/tmp/out.pcap" # (yes, it's Linux-biased, sorry, fixing
8
8
  # this is a TODO.)
9
9
  #
10
10
  # Any number of instance variables can be passed in to the intialize function (as a
@@ -23,7 +23,7 @@ module PacketFu
23
23
  # obj.config(:baz => "bat")
24
24
  # obj.config #=> {:iface=>"eth0", :baz=>"bat", :pcapfile=>"/tmp/out.pcap", :foo=>"bar"}
25
25
  class Config
26
- attr_accessor :eth_saddr, # The discovered eth_saddr
26
+ attr_accessor :eth_saddr, # The discovered eth_saddr
27
27
  :eth_daddr, # The discovered eth_daddr (ie, the gateway)
28
28
  :eth_src, # The discovered eth_src in binary form.
29
29
  :eth_dst, # The discovered eth_dst (gateway) in binary form.
@@ -31,11 +31,11 @@ module PacketFu
31
31
  :ip_src, # The discovered ip_src in binary form.
32
32
  :iface, # The declared interface.
33
33
  :pcapfile # A declared default file to write to.
34
-
34
+
35
35
  def initialize(args={})
36
36
  if Process.euid.zero?
37
37
  @iface = args[:iface] || ENV['IFACE'] || Pcap.lookupdev || "lo"
38
- end
38
+ end
39
39
  @pcapfile = "/tmp/out.pcap"
40
40
  args.each_pair { |k,v| self.instance_variable_set(("@#{k}"),v) }
41
41
  end
@@ -46,9 +46,9 @@ module PacketFu
46
46
  arg.each_pair {|k,v| self.instance_variable_set(("@" + k.to_s).intern, v)}
47
47
  else
48
48
  config_hash = {}
49
- self.instance_variables.each do |v|
49
+ self.instance_variables.each do |v|
50
50
  key = v.to_s.gsub(/^@/,"").to_sym
51
- config_hash[key] = self.instance_variable_get(v)
51
+ config_hash[key] = self.instance_variable_get(v)
52
52
  end
53
53
  config_hash
54
54
  end
@@ -4,7 +4,7 @@ module PacketFu
4
4
  # The Inject class handles injecting arrays of binary data on the wire.
5
5
  #
6
6
  # To inject single packets, use PacketFu::Packet.to_w() instead.
7
- class Inject
7
+ class Inject
8
8
  attr_accessor :array, :stream, :show_live # Leave these public and open.
9
9
  attr_reader :iface, :snaplen, :promisc, :timeout # Cant change after the init.
10
10
 
@@ -19,7 +19,7 @@ module PacketFu
19
19
  end
20
20
 
21
21
  # Takes an array, and injects them onto an interface. Note that
22
- # complete packets (Ethernet headers on down) are expected.
22
+ # complete packets (Ethernet headers on down) are expected.
23
23
  #
24
24
  # === Parameters
25
25
  #
@@ -48,7 +48,7 @@ module PacketFu
48
48
  pkt_count +=1
49
49
  puts "Sent Packet \##{pkt_count} (#{pkt.size})" if show_live
50
50
  end
51
- # Return # of packets sent, array size, and array total size
51
+ # Return # of packets sent, array size, and array total size
52
52
  [pkt_count, pkt_array.size, pkt_array.join.size]
53
53
  end
54
54
 
@@ -1,9 +1,10 @@
1
1
  # -*- coding: binary -*-
2
+
2
3
  module PacketFu
3
4
 
4
5
  # Packet is the parent class of EthPacket, IPPacket, UDPPacket, TCPPacket, and all
5
6
  # other packets. It acts as both a singleton class, so things like
6
- # Packet.parse can happen, and as an abstract class to provide
7
+ # Packet.parse can happen, and as an abstract class to provide
7
8
  # subclasses some structure.
8
9
  class Packet
9
10
 
@@ -24,7 +25,7 @@ module PacketFu
24
25
  end
25
26
 
26
27
  # Parse() creates the correct packet type based on the data, and returns the apporpiate
27
- # Packet subclass object.
28
+ # Packet subclass object.
28
29
  #
29
30
  # There is an assumption here that all incoming packets are either EthPacket
30
31
  # or InvalidPacket types. This will be addressed pretty soon.
@@ -41,7 +42,10 @@ module PacketFu
41
42
  else
42
43
  classes = PacketFu.packet_classes_by_layer_without_application
43
44
  end
44
- p = classes.detect { |pclass| pclass.can_parse?(packet) }.new
45
+
46
+ new_args = {}
47
+ new_args[:on_ipv6] = true if IPv6Packet.can_parse?(packet)
48
+ p = classes.detect { |pclass| pclass.can_parse?(packet) }.new(new_args)
45
49
  parsed_packet = p.read(packet,args)
46
50
  end
47
51
 
@@ -108,7 +112,7 @@ module PacketFu
108
112
  inj.array = [@headers[0].to_s]
109
113
  inj.inject
110
114
  end
111
-
115
+
112
116
  # Recalculates all the calcuated fields for all headers in the packet.
113
117
  # This is important since read() wipes out all the calculated fields
114
118
  # such as length and checksum and what all.
@@ -142,7 +146,7 @@ module PacketFu
142
146
  # A note on the :strip => true argument: If :strip is set, defined lengths of data will
143
147
  # be believed, and any trailers (such as frame check sequences) will be chopped off. This
144
148
  # helps to ensure well-formed packets, at the cost of losing perhaps important FCS data.
145
- #
149
+ #
146
150
  # If :strip is false, header lengths are /not/ believed, and all data will be piped in.
147
151
  # When capturing from the wire, this is usually fine, but recalculating the length before
148
152
  # saving or re-transmitting will absolutely change the data payload; FCS data will become
@@ -169,7 +173,7 @@ module PacketFu
169
173
 
170
174
  # Packets are bundles of lots of objects, so copying them
171
175
  # is a little complicated -- a dup of a packet is actually
172
- # full of pass-by-reference stuff in the @headers, so
176
+ # full of pass-by-reference stuff in the @headers, so
173
177
  # if you change one, you're changing all this copies, too.
174
178
  #
175
179
  # Normally, this doesn't seem to be a big deal, and it's
@@ -215,7 +219,7 @@ module PacketFu
215
219
  # format.
216
220
  #
217
221
  # === Format
218
- #
222
+ #
219
223
  # * A one or two character protocol initial. It should be unique
220
224
  # * The packet size
221
225
  # * Useful data in a human-usable form.
@@ -229,7 +233,7 @@ module PacketFu
229
233
  # #=> "T 1054 10.10.10.105:55000 -> 192.168.145.105:80 [......] S:adc7155b|I:8dd0"
230
234
  # tcp_packet.peek.size
231
235
  # #=> 79
232
- #
236
+ #
233
237
  def peek_format
234
238
  peek_data = ["? "]
235
239
  peek_data << "%-5d" % self.to_s.size
@@ -237,9 +241,9 @@ module PacketFu
237
241
  peek_data.join
238
242
  end
239
243
 
240
- # Defines the layer this packet type lives at, based on the number of headers it
244
+ # Defines the layer this packet type lives at, based on the number of headers it
241
245
  # requires. Note that this has little to do with the OSI model, since TCP/IP
242
- # doesn't really have Session and Presentation layers.
246
+ # doesn't really have Session and Presentation layers.
243
247
  #
244
248
  # Ethernet and the like are layer 1, IP, IPv6, and ARP are layer 2,
245
249
  # TCP, UDP, and other transport protocols are layer 3, and application
@@ -299,7 +303,7 @@ module PacketFu
299
303
  end
300
304
 
301
305
  # If @inspect_style is :default (or :ugly), the inspect output is the usual
302
- # inspect.
306
+ # inspect.
303
307
  #
304
308
  # If @inspect_style is :hex (or :pretty), the inspect output is
305
309
  # a much more compact hexdump-style, with a shortened set of packet header
@@ -335,7 +339,7 @@ module PacketFu
335
339
  table << [proto,[]]
336
340
  header.class.members.each do |elem|
337
341
  elem_sym = elem.to_sym # to_sym needed for 1.8
338
- next if elem_sym == :body
342
+ next if elem_sym == :body
339
343
  elem_type_value = []
340
344
  elem_type_value[0] = elem
341
345
  readable_element = "#{elem}_readable"
@@ -344,7 +348,7 @@ module PacketFu
344
348
  else
345
349
  elem_type_value[1] = header.send(elem)
346
350
  end
347
- elem_type_value[2] = header[elem.to_sym].class.name
351
+ elem_type_value[2] = header[elem.to_sym].class.name
348
352
  table[table_idx][1] << elem_type_value
349
353
  end
350
354
  end
@@ -364,7 +368,7 @@ module PacketFu
364
368
  dtable = self.dissection_table
365
369
  hex_body = nil
366
370
  if dtable.last.kind_of?(Array) and dtable.last.first == :body
367
- body = dtable.pop
371
+ body = dtable.pop
368
372
  hex_body = hexify(body[1])
369
373
  end
370
374
  elem_widths = [0,0,0]
@@ -376,11 +380,11 @@ module PacketFu
376
380
  end
377
381
  end
378
382
  end
379
- total_width = elem_widths.inject(0) {|sum,x| sum+x}
383
+ total_width = elem_widths.inject(0) {|sum,x| sum+x}
380
384
  table = ""
381
385
  dtable.each do |proto|
382
386
  table << "--"
383
- table << proto[0]
387
+ table << proto[0]
384
388
  if total_width > proto[0].size
385
389
  table << ("-" * (total_width - proto[0].size + 2))
386
390
  else
@@ -464,7 +468,7 @@ module PacketFu
464
468
  alias_method :protocol, :proto
465
469
  alias_method :length, :size
466
470
 
467
- # the Packet class should not be instantiated directly, since it's an
471
+ # the Packet class should not be instantiated directly, since it's an
468
472
  # abstract class that real packet types inherit from. Sadly, this
469
473
  # makes the Packet class more difficult to test directly.
470
474
  def initialize(args={})
@@ -493,7 +497,7 @@ module PacketFu
493
497
 
494
498
  #method_missing() delegates protocol-specific field actions to the apporpraite
495
499
  #class variable (which contains the associated packet type)
496
- #This register-of-protocols style switch will work for the
500
+ #This register-of-protocols style switch will work for the
497
501
  #forseeable future (there aren't /that/ many packet types), and it's a handy
498
502
  #way to know at a glance what packet types are supported.
499
503
  def method_missing(sym, *args, &block)
@@ -8,6 +8,7 @@ module StructFu
8
8
  unless [:little, :big].include? e
9
9
  raise ArgumentError, "Unknown endianness for #{self.class}"
10
10
  end
11
+ @int64 = e == :little ? Int64le : Int64be
11
12
  @int32 = e == :little ? Int32le : Int32be
12
13
  @int16 = e == :little ? Int16le : Int16be
13
14
  return e
@@ -422,7 +423,7 @@ module PacketFu
422
423
  end
423
424
  arr.each_with_index do |p,i|
424
425
  if p.kind_of? Hash # Binary timestamps are included
425
- this_ts = p.keys.first
426
+ this_ts = p.keys.first.dup
426
427
  this_incl_len = p.values.first.size
427
428
  this_orig_len = this_incl_len
428
429
  this_data = p.values.first
@@ -0,0 +1,37 @@
1
+ # -*- coding: binary -*-
2
+
3
+ module PacketFu
4
+
5
+ # Module to handle PCAP-NG file format.
6
+ # See http://xml2rfc.tools.ietf.org/cgi-bin/xml2rfc.cgi?url=https://raw.githubusercontent.com/pcapng/pcapng/master/draft-tuexen-opsawg-pcapng.xml&modeAsFormat=html/ascii&type=ascii#format_idb
7
+ module PcapNG
8
+
9
+ # Section Header Block type number
10
+ SHB_TYPE = StructFu::Int32.new(0x0A0D0D0A, :little)
11
+ # Interface Description Block type number
12
+ IDB_TYPE = StructFu::Int32.new(1, :little)
13
+ # Simple Packet Block type number
14
+ SPB_TYPE = StructFu::Int32.new(3, :little)
15
+ # Enhanced Packet Block type number
16
+ EPB_TYPE = StructFu::Int32.new(6, :little)
17
+
18
+ # Various LINKTYPE values from http://www.tcpdump.org/linktypes.html
19
+ # FIXME: only ETHERNET type is defined as this is the only link layer
20
+ # type supported by PacketFu
21
+ LINKTYPE_ETHERNET = 1
22
+
23
+ class Error < StandardError; end
24
+ class InvalidFileError < Error; end
25
+
26
+ end
27
+
28
+ end
29
+
30
+
31
+ require_relative 'pcapng/block.rb'
32
+ require_relative 'pcapng/unknown_block.rb'
33
+ require_relative 'pcapng/shb.rb'
34
+ require_relative 'pcapng/idb.rb'
35
+ require_relative 'pcapng/epb.rb'
36
+ require_relative 'pcapng/spb.rb'
37
+ require_relative 'pcapng/file.rb'
@@ -0,0 +1,25 @@
1
+ # -*- coding: binary -*-
2
+ module PacketFu
3
+ module PcapNG
4
+
5
+ module Block
6
+
7
+ # Calculate block length and update :block_len and block_len2 fields
8
+ def recalc_block_len
9
+ len = to_a.map(&:to_s).join.size
10
+ self[:block_len].value = self[:block_len2].value = len
11
+ end
12
+
13
+ # Pad given field to 32 bit boundary, if needed
14
+ def pad_field(*fields)
15
+ fields.each do |field|
16
+ unless self[field].size % 4 == 0
17
+ self[field] << "\x00" * (4 - (self[field].size % 4))
18
+ end
19
+ end
20
+ end
21
+
22
+ end
23
+
24
+ end
25
+ end
@@ -0,0 +1,112 @@
1
+ require 'stringio'
2
+
3
+ module PacketFu
4
+ module PcapNG
5
+
6
+ # Pcapng::EPB represents a Extended Packet Block (EPB) of a pcapng file.
7
+ #
8
+ # == Pcapng::EPB Definition
9
+ # Int32 :type Default: 0x00000006
10
+ # Int32 :block_len
11
+ # Int32 :interface_id
12
+ # Int32 :tsh (timestamp high)
13
+ # Int32 :tsl (timestamp low)
14
+ # Int32 :cap_len
15
+ # Int32 :orig_len
16
+ # String :data
17
+ # String :options
18
+ # Int32 :block_len2
19
+ class EPB < Struct.new(:type, :block_len, :interface_id, :tsh, :tsl,
20
+ :cap_len, :orig_len, :data, :options, :block_len2)
21
+ include StructFu
22
+ include Block
23
+ attr_accessor :endian
24
+ attr_accessor :interface
25
+
26
+ MIN_SIZE = 8*4
27
+
28
+ def initialize(args={})
29
+ @endian = set_endianness(args[:endian] || :little)
30
+ init_fields(args)
31
+ super(args[:type], args[:block_len], args[:interface_id], args[:tsh],
32
+ args[:tsl], args[:cap_len], args[:orig_len], args[:data],
33
+ args[:options], args[:block_len2])
34
+ end
35
+
36
+ # Used by #initialize to set the initial fields
37
+ def init_fields(args={})
38
+ args[:type] = @int32.new(args[:type] || PcapNG::EPB_TYPE.to_i)
39
+ args[:block_len] = @int32.new(args[:block_len] || MIN_SIZE)
40
+ args[:interface_id] = @int32.new(args[:interface_id] || 0)
41
+ args[:tsh] = @int32.new(args[:tsh] || 0)
42
+ args[:tsl] = @int32.new(args[:tsl] || 0)
43
+ args[:cap_len] = @int32.new(args[:cap_len] || 0)
44
+ args[:orig_len] = @int32.new(args[:orig_len] || 0)
45
+ args[:data] = StructFu::String.new(args[:data] || '')
46
+ args[:options] = StructFu::String.new(args[:options] || '')
47
+ args[:block_len2] = @int32.new(args[:block_len2] || MIN_SIZE)
48
+ args
49
+ end
50
+
51
+ def has_options?
52
+ self[:options].size > 0
53
+ end
54
+
55
+ # Reads a String or a IO to populate the object
56
+ def read(str_or_io)
57
+ if str_or_io.respond_to? :read
58
+ io = str_or_io
59
+ else
60
+ io = StringIO.new(force_binary(str_or_io.to_s))
61
+ end
62
+ return self if io.eof?
63
+
64
+ self[:type].read io.read(4)
65
+ self[:block_len].read io.read(4)
66
+ self[:interface_id].read io.read(4)
67
+ self[:tsh].read io.read(4)
68
+ self[:tsl].read io.read(4)
69
+ self[:cap_len].read io.read(4)
70
+ self[:orig_len].read io.read(4)
71
+ self[:data].read io.read(self[:cap_len].to_i)
72
+ data_pad_len = (4 - (self[:cap_len].to_i % 4)) % 4
73
+ io.read data_pad_len
74
+ options_len = self[:block_len].to_i - self[:cap_len].to_i - data_pad_len
75
+ options_len -= MIN_SIZE
76
+ self[:options].read io.read(options_len)
77
+ self[:block_len2].read io.read(4)
78
+
79
+ unless self[:block_len].to_i == self[:block_len2].to_i
80
+ raise InvalidFileError, 'Incoherency in Extended Packet Block'
81
+ end
82
+
83
+ self
84
+ end
85
+
86
+ # Return timestamp as a Time object
87
+ def timestamp
88
+ Time.at((self[:tsh].to_i << 32 | self[:tsl].to_i) * ts_resol)
89
+ end
90
+
91
+ # Return the object as a String
92
+ def to_s
93
+ pad_field :data, :options
94
+ recalc_block_len
95
+ to_a.map(&:to_s).join
96
+ end
97
+
98
+
99
+ private
100
+
101
+ def ts_resol
102
+ if @interface.nil?
103
+ 1E-6
104
+ else
105
+ @interface.ts_resol
106
+ end
107
+ end
108
+
109
+ end
110
+
111
+ end
112
+ end
@@ -0,0 +1,316 @@
1
+ require_relative 'shb'
2
+
3
+ module PacketFu
4
+ module PcapNG
5
+
6
+ # PcapNG::File is a complete Pcap-NG file handler.
7
+ class File
8
+ attr_accessor :sections
9
+
10
+ def initialize
11
+ @sections = []
12
+ end
13
+
14
+ # Read a string to populate the object. Note that this appends new blocks to
15
+ # the Pcapng::File object.
16
+ def read(str)
17
+ PacketFu.force_binary(str)
18
+ io = StringIO.new(str)
19
+ parse_section(io)
20
+ self
21
+ end
22
+
23
+ # Clear the contents of the Pcapng::File prior to reading in a new string.
24
+ # This string should contain a Section Header Block and an Interface Description
25
+ # Block to create a conform pcapng file.
26
+ def read!(str)
27
+ clear
28
+ PacketFu.force_binary(str)
29
+ read(str)
30
+ end
31
+
32
+ # Read a given file and analyze it.
33
+ # If given a block, it will yield PcapNG::EPB or PcapNG::SPB objects.
34
+ # This is the only way to get packet timestamps.
35
+ def readfile(fname, &blk)
36
+ unless ::File.readable?(fname)
37
+ raise ArgumentError, "cannot read file #{fname}"
38
+ end
39
+
40
+ ::File.open(fname, 'rb') do |f|
41
+ while !f.eof? do
42
+ parse_section(f)
43
+ end
44
+ end
45
+
46
+ if blk
47
+ count = 0
48
+ @sections.each do |section|
49
+ section.interfaces.each do |intf|
50
+ intf.packets.each { |pkt| count += 1; yield pkt }
51
+ end
52
+ end
53
+ count
54
+ end
55
+ end
56
+
57
+ # Give an array of parsed packets (raw data from packets).
58
+ # If a block is given, yield raw packet data from the given file.
59
+ def read_packet_bytes(fname, &blk)
60
+ count = 0
61
+ packets = [] unless blk
62
+
63
+ readfile(fname) do |packet|
64
+ if blk
65
+ count += 1
66
+ yield packet.data.to_s
67
+ else
68
+ packets << packet.data.to_s
69
+ end
70
+ end
71
+
72
+ blk ? count : packets
73
+ end
74
+
75
+ # Return an array of parsed packets.
76
+ # If a block is given, yield parsed packets from the given file.
77
+ def read_packets(fname, &blk)
78
+ count = 0
79
+ packets = [] unless blk
80
+
81
+ read_packet_bytes(fname) do |packet|
82
+ if blk
83
+ count += 1
84
+ yield Packet.parse(packet)
85
+ else
86
+ packets << Packet.parse(packet)
87
+ end
88
+ end
89
+
90
+ blk ? count : packets
91
+ end
92
+
93
+ # Return the object as a String
94
+ def to_s
95
+ @sections.map { |section| section.to_s }.join
96
+ end
97
+
98
+ # Clear the contents of the Pcapng::File.
99
+ def clear
100
+ @sections.clear
101
+ end
102
+
103
+ # #file_to_array translates a Pcap-NG file into an array of packets.
104
+ # Note that this strips out timestamps -- if you'd like to retain
105
+ # timestamps and other pcapng file information, you will want to
106
+ # use #read instead.
107
+ #
108
+ # Valid arguments are:
109
+ # * :filename If given, object is cleared and filename is analyzed
110
+ # before generating array. Else, array is generated
111
+ # from self.
112
+ # * :keep_timestamps If true, generates an array of hashes, each one with
113
+ # timestamp as key and packet as value. There is one hash
114
+ # per packet.
115
+ def file_to_array(args={})
116
+ filename = args[:filename] || args[:file]
117
+ if filename
118
+ clear
119
+ readfile filename
120
+ end
121
+
122
+ ary = []
123
+ @sections.each do |section|
124
+ section.interfaces.each do |itf|
125
+ if args[:keep_timestamps] || args[:keep_ts]
126
+ ary.concat itf.packets.map { |pkt| { pkt.timestamp => pkt.data.to_s } }
127
+ else
128
+ ary.concat itf.packets.map { |pkt| pkt.data.to_s}
129
+ end
130
+ end
131
+ end
132
+ ary
133
+ end
134
+
135
+ # Writes the Pcapng::File to a file. Takes the following arguments:
136
+ # :filename # The file to write to.
137
+ # :append # If set to true, the packets are appended to the file, rather
138
+ # # than overwriting.
139
+ def to_file(args={})
140
+ filename = args[:filename] || args[:file]
141
+ unless (!filename.nil? || filename.kind_of?(String))
142
+ raise ArgumentError, "Need a :filename for #{self.class}"
143
+ end
144
+
145
+ append = args[:append]
146
+ mode = ''
147
+ if append and ::File.exists? filename
148
+ mode = 'ab'
149
+ else
150
+ mode = 'wb'
151
+ end
152
+ ::File.open(filename,mode) {|f| f.write(self.to_s)}
153
+ [filename, self.to_s.size]
154
+ end
155
+
156
+ alias_method :to_f, :to_file
157
+
158
+ # Shorthand method for writing to a file. Can take either :file => 'name.pcapng'
159
+ # or simply 'name.pcapng'
160
+ def write(filename='out.pcapng')
161
+ if filename.kind_of?(Hash)
162
+ f = filename[:filename] || filename[:file] || 'out.pcapng'
163
+ else
164
+ f = filename.to_s
165
+ end
166
+ self.to_file(:filename => f.to_s, :append => false)
167
+ end
168
+
169
+ # Shorthand method for appendong to a file. Can take either
170
+ # :file => 'name.pcapng' or simply 'name.pcapng'
171
+ def append(filename='out.pcapng')
172
+ if filename.kind_of?(Hash)
173
+ f = filename[:filename] || filename[:file] || 'out.pcapng'
174
+ else
175
+ f = filename.to_s
176
+ end
177
+ self.to_file(:filename => f.to_s, :append => true)
178
+ end
179
+
180
+ # Takes an array of packets or a Hash.
181
+ #
182
+ # Array: as generated by file_to_array or Array of Packet objects.
183
+ # update Pcapng::File object without writing file on disk
184
+ # Hash: take packets from args and write them to a file. Valid arguments are:
185
+ # :filename # do not write file on disk if not given
186
+ # :array # Can either be an array of packet data, or a hash-value pair
187
+ # # of timestamp => data.
188
+ # :timestamp # Sets an initial timestamp (Time object)
189
+ # :ts_inc # Sets the increment between timestamps. Defaults to 1 second.
190
+ # :append # If true, then the packets are appended to the end of a file.
191
+ def array_to_file(args={})
192
+ case args
193
+ when Hash
194
+ filename = args[:filename] || args[:file]
195
+ ary = args[:array] || args[:arr]
196
+ unless ary.kind_of? Array
197
+ raise ArgumentError, ':array parameter needs to be an array'
198
+ end
199
+ ts = args[:timestamp] || args[:ts] || Time.now
200
+ ts_inc = args[:ts_inc] || 1
201
+ append = !!args[:append]
202
+ when Array
203
+ ary = args
204
+ ts = Time.now
205
+ ts_inc = 1
206
+ filename = nil
207
+ append = false
208
+ else
209
+ raise ArgumentError, 'unknown argument. Need either a Hash or Array'
210
+ end
211
+
212
+ section = SHB.new
213
+ @sections << section
214
+ itf = IDB.new(:endian => section.endian)
215
+ classify_block section, itf
216
+
217
+ ary.each_with_index do |pkt, i|
218
+ case pkt
219
+ when Hash
220
+ this_ts = pkt.keys.first.to_i
221
+ this_cap_len = pkt.values.first.to_s.size
222
+ this_data = pkt.values.first.to_s
223
+ else
224
+ this_ts = (ts + ts_inc * i).to_i
225
+ this_cap_len = pkt.to_s.size
226
+ this_data = pkt.to_s
227
+ end
228
+ this_ts = (this_ts / itf.ts_resol).to_i
229
+ this_tsh = this_ts >> 32
230
+ this_tsl = this_ts & 0xffffffff
231
+ this_pkt = EPB.new(:endian => section.endian,
232
+ :interface_id => 0,
233
+ :tsh => this_tsh,
234
+ :tsl => this_tsl,
235
+ :cap_len => this_cap_len,
236
+ :orig_len => this_cap_len,
237
+ :data => this_data)
238
+ classify_block section, this_pkt
239
+ end
240
+
241
+ if filename
242
+ self.to_f(:filename => filename, :append => append)
243
+ else
244
+ self
245
+ end
246
+ end
247
+
248
+
249
+ private
250
+
251
+ def parse_section(io)
252
+ shb = SHB.new
253
+ type = StructFu::Int32.new(0, shb.endian).read(io.read(4))
254
+ io.seek(-4, IO::SEEK_CUR)
255
+ shb = parse(type, io, shb)
256
+ raise InvalidFileError, 'no Section header found' unless shb.is_a?(SHB)
257
+
258
+ if shb.section_len.to_i != 0xffffffffffffffff
259
+ # Section length is defined
260
+ section = StringIO.new(io.read(shb.section_len.to_i))
261
+ while !section.eof? do
262
+ shb = @sections.last
263
+ type = StructFu::Int32.new(0, shb.endian).read(section.read(4))
264
+ section.seek(-4, IO::SEEK_CUR)
265
+ block = parse(type, section, shb)
266
+ end
267
+ else
268
+ # section length is undefined
269
+ while !io.eof?
270
+ shb = @sections.last
271
+ type = StructFu::Int32.new(0, shb.endian).read(io.read(4))
272
+ io.seek(-4, IO::SEEK_CUR)
273
+ block = parse(type, io, shb)
274
+ end
275
+ end
276
+ end
277
+
278
+ def parse(type, io, shb)
279
+ types = PcapNG.constants(false).select { |c| c.to_s =~ /_TYPE/ }.
280
+ map { |c| [PcapNG.const_get(c).to_i, c] }
281
+ types = Hash[types]
282
+
283
+ if types.has_key?(type.to_i)
284
+ klass = PcapNG.const_get(types[type.to_i].to_s.gsub(/_TYPE/, '').to_sym)
285
+ block = klass.new(endian: shb.endian)
286
+ else
287
+ block = UnknownBlock.new(endian: shb.endian)
288
+ end
289
+
290
+ classify_block shb, block
291
+ block.read(io)
292
+ end
293
+
294
+ def classify_block(shb, block)
295
+ case block
296
+ when SHB
297
+ @sections << block
298
+ when IDB
299
+ shb << block
300
+ block.section = shb
301
+ when EPB
302
+ shb.interfaces[block.interface_id.to_i] << block
303
+ block.interface = shb.interfaces[block.interface_id.to_i]
304
+ when SPB
305
+ shb.interfaces[0] << block
306
+ block.interface = shb.interfaces[0]
307
+ else
308
+ shb.unknown_blocks << block
309
+ block.section = shb
310
+ end
311
+ end
312
+
313
+ end
314
+
315
+ end
316
+ end