packetfu 1.1.11 → 1.1.12.pre
Sign up to get free protection for your applications and to get access to all the features.
- 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
|