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.
- checksums.yaml +4 -4
- data/.gitignore +3 -1
- data/.rspec +2 -0
- data/.travis.yml +2 -3
- data/README.md +127 -0
- data/examples/100kpackets.rb +11 -10
- data/examples/ackscan.rb +4 -1
- data/examples/arp.rb +4 -5
- data/examples/arphood.rb +5 -4
- data/examples/dissect_thinger.rb +10 -7
- data/examples/ethernet.rb +8 -3
- data/examples/ids.rb +22 -4
- data/examples/idsv2.rb +25 -6
- data/examples/ifconfig.rb +6 -3
- data/examples/new-simple-stats.rb +5 -6
- data/examples/packetfu-shell.rb +11 -48
- data/examples/pcap2pcapng.rb +32 -0
- data/examples/simple-sniffer.rb +9 -4
- data/examples/simple-stats.rb +7 -8
- data/examples/slammer.rb +2 -2
- data/examples/uniqpcap.rb +17 -7
- data/lib/packetfu.rb +10 -175
- data/lib/packetfu/capture.rb +2 -2
- data/lib/packetfu/common.rb +142 -0
- data/lib/packetfu/config.rb +8 -8
- data/lib/packetfu/inject.rb +3 -3
- data/lib/packetfu/packet.rb +22 -18
- data/lib/packetfu/pcap.rb +2 -1
- data/lib/packetfu/pcapng.rb +37 -0
- data/lib/packetfu/pcapng/block.rb +25 -0
- data/lib/packetfu/pcapng/epb.rb +112 -0
- data/lib/packetfu/pcapng/file.rb +316 -0
- data/lib/packetfu/pcapng/idb.rb +125 -0
- data/lib/packetfu/pcapng/shb.rb +146 -0
- data/lib/packetfu/pcapng/spb.rb +83 -0
- data/lib/packetfu/pcapng/unknown_block.rb +60 -0
- data/lib/packetfu/protos.rb +3 -0
- data/lib/packetfu/protos/arp.rb +10 -10
- data/lib/packetfu/protos/icmpv6.rb +131 -0
- data/lib/packetfu/protos/icmpv6/header.rb +69 -0
- data/lib/packetfu/protos/icmpv6/mixin.rb +14 -0
- data/lib/packetfu/protos/ip.rb +4 -5
- data/lib/packetfu/protos/ipv6/header.rb +2 -0
- data/lib/packetfu/protos/udp.rb +24 -12
- data/lib/packetfu/structfu.rb +27 -0
- data/lib/packetfu/utils.rb +55 -9
- data/lib/packetfu/version.rb +1 -1
- data/packetfu.gemspec +13 -7
- data/spec/arp_spec.rb +11 -5
- data/spec/eth_spec.rb +20 -11
- data/spec/fake_packets.rb +28 -0
- data/spec/hsrp_spec.rb +15 -0
- data/spec/icmp_spec.rb +12 -5
- data/spec/icmpv6_spec.rb +98 -0
- data/spec/invalid_spec.rb +28 -0
- data/spec/ip_spec.rb +10 -5
- data/spec/ipv4_icmp.pcap +0 -0
- data/spec/ipv4_udp.pcap +0 -0
- data/spec/ipv6_icmp.pcap +0 -0
- data/spec/ipv6_spec.rb +4 -0
- data/spec/ipv6_udp.pcap +0 -0
- data/spec/lldp_spec.rb +36 -0
- data/spec/octets_spec.rb +43 -0
- data/spec/packet_spec.rb +24 -0
- data/spec/packetfu_spec.rb +6 -1
- data/spec/pcap_spec.rb +286 -0
- data/spec/pcapng/epb_spec.rb +81 -0
- data/spec/pcapng/file_spec.rb +295 -0
- data/spec/pcapng/file_spec_helper.rb +45 -0
- data/spec/pcapng/idb_spec.rb +53 -0
- data/spec/pcapng/shb_spec.rb +42 -0
- data/spec/pcapng/spb_spec.rb +43 -0
- data/spec/pcapng/unknown_block_spec.rb +36 -0
- data/spec/spec_helper.rb +3 -31
- data/spec/tcp_spec.rb +4 -1
- data/spec/udp_spec.rb +149 -1
- data/spec/utils_spec.rb +98 -15
- data/test/pcapng-test/output_be/advanced/test100.pcapng +0 -0
- data/test/pcapng-test/output_be/advanced/test100.txt +11 -0
- data/test/pcapng-test/output_be/advanced/test101.pcapng +0 -0
- data/test/pcapng-test/output_be/advanced/test101.txt +11 -0
- data/test/pcapng-test/output_be/advanced/test102.pcapng +0 -0
- data/test/pcapng-test/output_be/advanced/test102.txt +14 -0
- data/test/pcapng-test/output_be/basic/test001.pcapng +0 -0
- data/test/pcapng-test/output_be/basic/test001.txt +9 -0
- data/test/pcapng-test/output_be/basic/test002.pcapng +0 -0
- data/test/pcapng-test/output_be/basic/test002.txt +7 -0
- data/test/pcapng-test/output_be/basic/test003.pcapng +0 -0
- data/test/pcapng-test/output_be/basic/test003.txt +8 -0
- data/test/pcapng-test/output_be/basic/test004.pcapng +0 -0
- data/test/pcapng-test/output_be/basic/test004.txt +9 -0
- data/test/pcapng-test/output_be/basic/test005.pcapng +0 -0
- data/test/pcapng-test/output_be/basic/test005.txt +9 -0
- data/test/pcapng-test/output_be/basic/test006.pcapng +0 -0
- data/test/pcapng-test/output_be/basic/test006.txt +9 -0
- data/test/pcapng-test/output_be/basic/test007.pcapng +0 -0
- data/test/pcapng-test/output_be/basic/test007.txt +9 -0
- data/test/pcapng-test/output_be/basic/test008.pcapng +0 -0
- data/test/pcapng-test/output_be/basic/test008.txt +9 -0
- data/test/pcapng-test/output_be/basic/test009.pcapng +0 -0
- data/test/pcapng-test/output_be/basic/test009.txt +9 -0
- data/test/pcapng-test/output_be/basic/test010.pcapng +0 -0
- data/test/pcapng-test/output_be/basic/test010.txt +9 -0
- data/test/pcapng-test/output_be/basic/test011.pcapng +0 -0
- data/test/pcapng-test/output_be/basic/test011.txt +10 -0
- data/test/pcapng-test/output_be/basic/test012.pcapng +0 -0
- data/test/pcapng-test/output_be/basic/test012.txt +10 -0
- data/test/pcapng-test/output_be/basic/test013.pcapng +0 -0
- data/test/pcapng-test/output_be/basic/test013.txt +9 -0
- data/test/pcapng-test/output_be/basic/test014.pcapng +0 -0
- data/test/pcapng-test/output_be/basic/test014.txt +9 -0
- data/test/pcapng-test/output_be/basic/test015.pcapng +0 -0
- data/test/pcapng-test/output_be/basic/test015.txt +9 -0
- data/test/pcapng-test/output_be/basic/test016.pcapng +0 -0
- data/test/pcapng-test/output_be/basic/test016.txt +11 -0
- data/test/pcapng-test/output_be/basic/test017.pcapng +0 -0
- data/test/pcapng-test/output_be/basic/test017.txt +9 -0
- data/test/pcapng-test/output_be/basic/test018.pcapng +0 -0
- data/test/pcapng-test/output_be/basic/test018.txt +12 -0
- data/test/pcapng-test/output_be/difficult/test200.pcapng +0 -0
- data/test/pcapng-test/output_be/difficult/test200.txt +8 -0
- data/test/pcapng-test/output_be/difficult/test201.pcapng +0 -0
- data/test/pcapng-test/output_be/difficult/test201.txt +11 -0
- data/test/pcapng-test/output_be/difficult/test202.pcapng +0 -0
- data/test/pcapng-test/output_be/difficult/test202.txt +14 -0
- data/test/pcapng-test/output_le/advanced/test100.pcapng +0 -0
- data/test/pcapng-test/output_le/advanced/test100.txt +11 -0
- data/test/pcapng-test/output_le/advanced/test101.pcapng +0 -0
- data/test/pcapng-test/output_le/advanced/test101.txt +11 -0
- data/test/pcapng-test/output_le/advanced/test102.pcapng +0 -0
- data/test/pcapng-test/output_le/advanced/test102.txt +14 -0
- data/test/pcapng-test/output_le/basic/test001.pcapng +0 -0
- data/test/pcapng-test/output_le/basic/test001.txt +9 -0
- data/test/pcapng-test/output_le/basic/test002.pcapng +0 -0
- data/test/pcapng-test/output_le/basic/test002.txt +7 -0
- data/test/pcapng-test/output_le/basic/test003.pcapng +0 -0
- data/test/pcapng-test/output_le/basic/test003.txt +8 -0
- data/test/pcapng-test/output_le/basic/test004.pcapng +0 -0
- data/test/pcapng-test/output_le/basic/test004.txt +9 -0
- data/test/pcapng-test/output_le/basic/test005.pcapng +0 -0
- data/test/pcapng-test/output_le/basic/test005.txt +9 -0
- data/test/pcapng-test/output_le/basic/test006.pcapng +0 -0
- data/test/pcapng-test/output_le/basic/test006.txt +9 -0
- data/test/pcapng-test/output_le/basic/test007.pcapng +0 -0
- data/test/pcapng-test/output_le/basic/test007.txt +9 -0
- data/test/pcapng-test/output_le/basic/test008.pcapng +0 -0
- data/test/pcapng-test/output_le/basic/test008.txt +9 -0
- data/test/pcapng-test/output_le/basic/test009.pcapng +0 -0
- data/test/pcapng-test/output_le/basic/test009.txt +9 -0
- data/test/pcapng-test/output_le/basic/test010.pcapng +0 -0
- data/test/pcapng-test/output_le/basic/test010.txt +9 -0
- data/test/pcapng-test/output_le/basic/test011.pcapng +0 -0
- data/test/pcapng-test/output_le/basic/test011.txt +10 -0
- data/test/pcapng-test/output_le/basic/test012.pcapng +0 -0
- data/test/pcapng-test/output_le/basic/test012.txt +10 -0
- data/test/pcapng-test/output_le/basic/test013.pcapng +0 -0
- data/test/pcapng-test/output_le/basic/test013.txt +9 -0
- data/test/pcapng-test/output_le/basic/test014.pcapng +0 -0
- data/test/pcapng-test/output_le/basic/test014.txt +9 -0
- data/test/pcapng-test/output_le/basic/test015.pcapng +0 -0
- data/test/pcapng-test/output_le/basic/test015.txt +9 -0
- data/test/pcapng-test/output_le/basic/test016.pcapng +0 -0
- data/test/pcapng-test/output_le/basic/test016.txt +11 -0
- data/test/pcapng-test/output_le/basic/test017.pcapng +0 -0
- data/test/pcapng-test/output_le/basic/test017.txt +9 -0
- data/test/pcapng-test/output_le/basic/test018.pcapng +0 -0
- data/test/pcapng-test/output_le/basic/test018.txt +12 -0
- data/test/pcapng-test/output_le/difficult/test200.pcapng +0 -0
- data/test/pcapng-test/output_le/difficult/test200.txt +8 -0
- data/test/pcapng-test/output_le/difficult/test201.pcapng +0 -0
- data/test/pcapng-test/output_le/difficult/test201.txt +11 -0
- data/test/pcapng-test/output_le/difficult/test202.pcapng +0 -0
- data/test/pcapng-test/output_le/difficult/test202.txt +14 -0
- data/test/sample-ipv6.pcapng +0 -0
- data/test/sample-spb.pcapng +0 -0
- data/test/sample.pcapng +0 -0
- data/test/sample2.pcapng +0 -0
- metadata +190 -68
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -2
- data/INSTALL.rdoc +0 -40
- data/README.rdoc +0 -64
- data/examples/examples.rb +0 -4
- data/setup.rb +0 -1586
- data/test/func_lldp.rb +0 -25
- data/test/ptest.rb +0 -16
- data/test/test_eth.rb +0 -93
- data/test/test_hsrp.rb +0 -20
- data/test/test_invalid.rb +0 -28
- data/test/test_octets.rb +0 -36
- data/test/test_pcap.rb +0 -211
- data/test/test_udp.rb +0 -100
- metadata.gz.sig +0 -2
data/lib/packetfu/config.rb
CHANGED
@@ -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
|
data/lib/packetfu/inject.rb
CHANGED
@@ -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
|
|
data/lib/packetfu/packet.rb
CHANGED
@@ -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
|
-
|
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)
|
data/lib/packetfu/pcap.rb
CHANGED
@@ -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
|