packetfu 1.1.13 → 2.0.0
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 +5 -5
- checksums.yaml.gz.sig +0 -0
- data/.github/ISSUE_TEMPLATE.md +29 -0
- data/.github/workflows/verify.yml +72 -0
- data/.travis.yml +10 -6
- data/LICENSE.txt +1 -1
- data/README.md +8 -8
- data/certs/todb.pem +25 -0
- data/examples/100kpackets.rb +2 -2
- data/examples/ackscan.rb +7 -6
- data/examples/pcap2pcapng.rb +2 -2
- data/examples/readpcap.rb +28 -0
- data/lib/packetfu/capture.rb +1 -1
- data/lib/packetfu/config.rb +2 -2
- data/lib/packetfu/inject.rb +1 -1
- data/lib/packetfu/packet.rb +6 -3
- data/lib/packetfu/pcap.rb +25 -25
- data/lib/packetfu/pcapng/file.rb +1 -1
- data/lib/packetfu/protos/arp.rb +1 -8
- data/lib/packetfu/protos/eth.rb +0 -7
- data/lib/packetfu/protos/hsrp.rb +0 -7
- data/lib/packetfu/protos/icmp/header.rb +7 -10
- data/lib/packetfu/protos/icmp.rb +0 -7
- data/lib/packetfu/protos/icmpv6.rb +4 -17
- data/lib/packetfu/protos/ip/header.rb +2 -2
- data/lib/packetfu/protos/ip/mixin.rb +9 -0
- data/lib/packetfu/protos/ip.rb +0 -8
- data/lib/packetfu/protos/ipv6/mixin.rb +12 -0
- data/lib/packetfu/protos/ipv6.rb +0 -7
- data/lib/packetfu/protos/lldp.rb +1 -8
- data/lib/packetfu/protos/tcp.rb +73 -30
- data/lib/packetfu/protos/udp/header.rb +4 -5
- data/lib/packetfu/protos/udp.rb +6 -18
- data/lib/packetfu/structfu.rb +1 -1
- data/lib/packetfu/version.rb +1 -1
- data/packetfu.gemspec +10 -18
- data/spec/arp_spec.rb +1 -1
- data/spec/capture_spec.rb +137 -0
- data/spec/eth_spec.rb +1 -1
- data/spec/icmp_spec.rb +1 -1
- data/spec/icmpv6_spec.rb +1 -1
- data/spec/inject_spec.rb +95 -0
- data/spec/ip_spec.rb +23 -1
- data/spec/packetfu_spec.rb +1 -1
- data/spec/pcap_spec.rb +3 -3
- data/spec/pcapng/file_spec.rb +1 -1
- data/spec/spec_helper.rb +4 -2
- data/spec/structfu_spec.rb +86 -82
- data/spec/tcp_spec.rb +155 -53
- data/test/sample-ipv6.pcap +0 -0
- data.tar.gz.sig +0 -0
- metadata +64 -37
- metadata.gz.sig +0 -0
- data/test/test_capture.rb +0 -58
- data/test/test_inject.rb +0 -31
- data/test/test_structfu.rb +0 -114
@@ -23,7 +23,7 @@ module PacketFu
|
|
23
23
|
# icmpv6_pkt.ipv6_saddr="2000::1234"
|
24
24
|
# icmpv6_pkt.ipv6_daddr="2000::5678"
|
25
25
|
#
|
26
|
-
# icmpv6_pkt.recalc
|
26
|
+
# icmpv6_pkt.recalc
|
27
27
|
# icmpv6_pkt.to_f('/tmp/icmpv6.pcap')
|
28
28
|
#
|
29
29
|
# == Parameters
|
@@ -49,13 +49,6 @@ module PacketFu
|
|
49
49
|
return true
|
50
50
|
end
|
51
51
|
|
52
|
-
def read(str=nil, args={})
|
53
|
-
raise "Cannot parse `#{str}'" unless self.class.can_parse?(str)
|
54
|
-
@eth_header.read(str)
|
55
|
-
super(args)
|
56
|
-
self
|
57
|
-
end
|
58
|
-
|
59
52
|
def initialize(args={})
|
60
53
|
@eth_header = EthHeader.new(args).read(args[:eth])
|
61
54
|
@ipv6_header = IPv6Header.new(args).read(args[:ipv6])
|
@@ -72,12 +65,7 @@ module PacketFu
|
|
72
65
|
|
73
66
|
# Calculates the checksum for the object.
|
74
67
|
def icmpv6_calc_sum
|
75
|
-
checksum =
|
76
|
-
|
77
|
-
# Compute sum on pseudo-header
|
78
|
-
[ipv6_src, ipv6_dst].each do |iaddr|
|
79
|
-
8.times { |i| checksum += (iaddr >> (i*16)) & 0xffff }
|
80
|
-
end
|
68
|
+
checksum = ipv6_calc_sum_on_addr
|
81
69
|
checksum += PacketFu::ICMPv6Header::PROTOCOL_NUMBER
|
82
70
|
checksum += ipv6_len
|
83
71
|
# Then compute it on ICMPv6 header + payload
|
@@ -96,9 +84,8 @@ module PacketFu
|
|
96
84
|
end
|
97
85
|
|
98
86
|
# Recalculates the calculatable fields for ICMPv6.
|
99
|
-
def icmpv6_recalc(arg
|
100
|
-
|
101
|
-
case arg
|
87
|
+
def icmpv6_recalc(arg = :all)
|
88
|
+
case arg.to_sym
|
102
89
|
when :icmpv6_sum
|
103
90
|
self.icmpv6_sum = icmpv6_calc_sum
|
104
91
|
when :all
|
@@ -107,7 +107,7 @@ module PacketFu
|
|
107
107
|
# Int16 :ip_len, Default: calculated
|
108
108
|
# Int16 :ip_id, Default: calculated # IRL, hardly random.
|
109
109
|
# Int16 :ip_frag, Default: 0 # TODO: Break out the bits
|
110
|
-
# Int8 :ip_ttl, Default:
|
110
|
+
# Int8 :ip_ttl, Default: 64 # https://www.iana.org/assignments/ip-parameters/ip-parameters.xml
|
111
111
|
# Int8 :ip_proto, Default: 0x01 # TCP: 0x06, UDP 0x11, ICMP 0x01
|
112
112
|
# Int16 :ip_sum, Default: calculated
|
113
113
|
# Octets :ip_src
|
@@ -131,7 +131,7 @@ module PacketFu
|
|
131
131
|
Int16.new(args[:ip_len] || 20),
|
132
132
|
Int16.new(args[:ip_id] || ip_calc_id),
|
133
133
|
Int16.new(args[:ip_frag]),
|
134
|
-
Int8.new(args[:ip_ttl] ||
|
134
|
+
Int8.new(args[:ip_ttl] || 64),
|
135
135
|
Int8.new(args[:ip_proto]),
|
136
136
|
Int16.new(args[:ip_sum] || ip_calc_sum),
|
137
137
|
Octets.new.read(args[:ip_src] || "\x00\x00\x00\x00"),
|
@@ -40,5 +40,14 @@ module PacketFu
|
|
40
40
|
def ip_ttl=(v); self.ip_header.ip_ttl= v; end
|
41
41
|
def ip_v; self.ip_header.ip_v ; end
|
42
42
|
def ip_v=(v); self.ip_header.ip_v= v; end
|
43
|
+
|
44
|
+
# Add method to each packet class on IP to ease checksum computation
|
45
|
+
def ip_calc_sum_on_addr(cksum=0)
|
46
|
+
checksum = cksum
|
47
|
+
checksum += (ip_src.to_i >> 16)
|
48
|
+
checksum += (ip_src.to_i & 0xffff)
|
49
|
+
checksum += (ip_dst.to_i >> 16)
|
50
|
+
checksum += (ip_dst.to_i & 0xffff)
|
51
|
+
end
|
43
52
|
end
|
44
53
|
end
|
data/lib/packetfu/protos/ip.rb
CHANGED
@@ -16,7 +16,6 @@ module PacketFu
|
|
16
16
|
# ip_pkt.ip_saddr="10.20.30.40"
|
17
17
|
# ip_pkt.ip_daddr="192.168.1.1"
|
18
18
|
# ip_pkt.ip_proto=1
|
19
|
-
# ip_pkt.ip_ttl=64
|
20
19
|
# ip_pkt.ip_payload="\x00\x00\x12\x34\x00\x01\x00\x01"+
|
21
20
|
# "Lovingly hand-crafted echo responses delivered directly to your door."
|
22
21
|
# ip_pkt.recalc
|
@@ -54,13 +53,6 @@ module PacketFu
|
|
54
53
|
end
|
55
54
|
end
|
56
55
|
|
57
|
-
def read(str=nil, args={})
|
58
|
-
raise "Cannot parse `#{str}'" unless self.class.can_parse?(str)
|
59
|
-
@eth_header.read(str)
|
60
|
-
super(args)
|
61
|
-
self
|
62
|
-
end
|
63
|
-
|
64
56
|
# Creates a new IPPacket object.
|
65
57
|
def initialize(args={})
|
66
58
|
@eth_header = EthHeader.new(args).read(args[:eth])
|
@@ -28,5 +28,17 @@ module PacketFu
|
|
28
28
|
def ipv6_daddr=(v); self.ipv6_header.ipv6_daddr= v; end
|
29
29
|
def ipv6_src_readable; self.ipv6_header.ipv6_src_readable; end
|
30
30
|
def ipv6_dst_readable; self.ipv6_header.ipv6_dst_readable; end
|
31
|
+
def ipv6?; not self.ipv6_header.nil?; end
|
32
|
+
|
33
|
+
# Add method to each packet class on IPv6 to ease checksum computation
|
34
|
+
def ipv6_calc_sum_on_addr(cksum=0)
|
35
|
+
checksum = cksum
|
36
|
+
[ipv6_src, ipv6_dst].each do |iaddr|
|
37
|
+
8.times do |i|
|
38
|
+
checksum += (iaddr >> (i * 16)) & 0xffff
|
39
|
+
end
|
40
|
+
end
|
41
|
+
checksum
|
42
|
+
end
|
31
43
|
end
|
32
44
|
end
|
data/lib/packetfu/protos/ipv6.rb
CHANGED
@@ -34,13 +34,6 @@ module PacketFu
|
|
34
34
|
true
|
35
35
|
end
|
36
36
|
|
37
|
-
def read(str=nil,args={})
|
38
|
-
raise "Cannot parse `#{str}'" unless self.class.can_parse?(str)
|
39
|
-
@eth_header.read(str)
|
40
|
-
super(args)
|
41
|
-
self
|
42
|
-
end
|
43
|
-
|
44
37
|
def initialize(args={})
|
45
38
|
@eth_header = (args[:eth] || EthHeader.new)
|
46
39
|
@ipv6_header = (args[:ipv6] || IPv6Header.new)
|
data/lib/packetfu/protos/lldp.rb
CHANGED
@@ -8,7 +8,7 @@ require 'packetfu/protos/lldp/mixin'
|
|
8
8
|
module PacketFu
|
9
9
|
|
10
10
|
class LLDPPacket < Packet
|
11
|
-
MAGIC = Regexp.new("^\x01\x80\xc2\x00\x00[\x0e\x03\x00]",
|
11
|
+
MAGIC = Regexp.new("^\x01\x80\xc2\x00\x00[\x0e\x03\x00]".force_encoding('ASCII-8BIT'), Regexp::NOENCODING)
|
12
12
|
include ::PacketFu::EthHeaderMixin
|
13
13
|
include ::PacketFu::LLDPHeaderMixin
|
14
14
|
|
@@ -22,13 +22,6 @@ module PacketFu
|
|
22
22
|
true
|
23
23
|
end
|
24
24
|
|
25
|
-
def read(str=nil,args={})
|
26
|
-
raise "Cannot parse `#{str}'" unless self.class.can_parse?(str)
|
27
|
-
@eth_header.read(str)
|
28
|
-
super(args)
|
29
|
-
self
|
30
|
-
end
|
31
|
-
|
32
25
|
def initialize(args={})
|
33
26
|
@eth_header = EthHeader.new(args).read(args[:eth])
|
34
27
|
@lldp_header = LLDPHeader.new(args).read(args[:lldp])
|
data/lib/packetfu/protos/tcp.rb
CHANGED
@@ -8,6 +8,9 @@ require 'packetfu/protos/tcp/mixin'
|
|
8
8
|
require 'packetfu/protos/ip/header'
|
9
9
|
require 'packetfu/protos/ip/mixin'
|
10
10
|
|
11
|
+
require 'packetfu/protos/ipv6/header'
|
12
|
+
require 'packetfu/protos/ipv6/mixin'
|
13
|
+
|
11
14
|
module PacketFu
|
12
15
|
# TCPPacket is used to construct TCP packets. They contain an EthHeader, an IPHeader, and a TCPHeader.
|
13
16
|
#
|
@@ -25,6 +28,16 @@ module PacketFu
|
|
25
28
|
# tcp_pkt.recalc
|
26
29
|
# tcp_pkt.to_f('/tmp/tcp.pcap')
|
27
30
|
#
|
31
|
+
# tcp6_pkt = PacketFu::TCPPacket.new(:on_ipv6 => true)
|
32
|
+
# tcp6_pkt.tcp_flags.syn=1
|
33
|
+
# tcp6_pkt.tcp_dst=80
|
34
|
+
# tcp6_pkt.tcp_win=5840
|
35
|
+
# tcp6_pkt.tcp_options="mss:1460,sack.ok,ts:#{rand(0xffffffff)};0,nop,ws:7"
|
36
|
+
# tcp6_pkt.ipv6_saddr="4::1"
|
37
|
+
# tcp6_pkt.ipv6_daddr="12:3::4567"
|
38
|
+
# tcp6_pkt.recalc
|
39
|
+
# tcp6_pkt.to_f('/tmp/udp.pcap')
|
40
|
+
#
|
28
41
|
# == Parameters
|
29
42
|
# :eth
|
30
43
|
# A pre-generated EthHeader object.
|
@@ -41,42 +54,58 @@ module PacketFu
|
|
41
54
|
class TCPPacket < Packet
|
42
55
|
include ::PacketFu::EthHeaderMixin
|
43
56
|
include ::PacketFu::IPHeaderMixin
|
57
|
+
include ::PacketFu::IPv6HeaderMixin
|
44
58
|
include ::PacketFu::TCPHeaderMixin
|
45
59
|
|
46
|
-
attr_accessor :eth_header, :ip_header, :tcp_header
|
60
|
+
attr_accessor :eth_header, :ip_header, :ipv6_header, :tcp_header
|
47
61
|
|
48
62
|
def self.can_parse?(str)
|
49
63
|
return false unless str.size >= 54
|
50
64
|
return false unless EthPacket.can_parse? str
|
51
|
-
|
52
|
-
|
53
|
-
|
65
|
+
if IPPacket.can_parse? str
|
66
|
+
return true if str[23,1] == "\x06"
|
67
|
+
elsif IPv6Packet.can_parse? str
|
68
|
+
return true if str[20,1] == "\x06"
|
69
|
+
end
|
70
|
+
return false
|
54
71
|
end
|
55
72
|
|
56
73
|
def read(str=nil, args={})
|
57
|
-
|
58
|
-
@eth_header.read(str)
|
59
|
-
|
74
|
+
super
|
60
75
|
# Strip off any extra data, if we are asked to do so.
|
61
76
|
if args[:strip]
|
62
77
|
tcp_body_len = self.ip_len - self.ip_hlen - (self.tcp_hlen * 4)
|
63
78
|
@tcp_header.body.read(@tcp_header.body.to_s[0,tcp_body_len])
|
79
|
+
tcp_calc_sum
|
80
|
+
@ip_header.ip_recalc
|
64
81
|
end
|
65
|
-
super(args)
|
66
82
|
self
|
67
83
|
end
|
68
84
|
|
69
85
|
def initialize(args={})
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
86
|
+
if args[:on_ipv6] or args[:ipv6]
|
87
|
+
@eth_header = EthHeader.new(args.merge(:eth_proto => 0x86dd)).read(args[:eth])
|
88
|
+
@ipv6_header = IPv6Header.new(args).read(args[:ipv6])
|
89
|
+
@tcp_header = TCPHeader.new(args).read(args[:tcp])
|
90
|
+
|
91
|
+
@ipv6_header.body = @tcp_header
|
92
|
+
@eth_header.body = @ipv6_header
|
93
|
+
@headers = [@eth_header, @ipv6_header, @tcp_header]
|
94
|
+
|
95
|
+
@ipv6_header.ipv6_next = 0x06
|
96
|
+
else
|
97
|
+
@eth_header = EthHeader.new(args.merge(:eth_proto => 0x0800)).read(args[:eth])
|
98
|
+
@ip_header = IPHeader.new(args).read(args[:ip])
|
99
|
+
@tcp_header = TCPHeader.new(args).read(args[:tcp])
|
74
100
|
|
75
|
-
|
76
|
-
|
77
|
-
|
101
|
+
@ip_header.body = @tcp_header
|
102
|
+
@eth_header.body = @ip_header
|
103
|
+
@headers = [@eth_header, @ip_header, @tcp_header]
|
104
|
+
|
105
|
+
@ip_header.ip_proto = 0x06
|
106
|
+
end
|
107
|
+
@tcp_header.flavor = args[:flavor].to_s.downcase
|
78
108
|
|
79
|
-
@ip_header.ip_proto=0x06
|
80
109
|
super
|
81
110
|
if args[:flavor]
|
82
111
|
tcp_calc_flavor(@tcp_header.flavor)
|
@@ -118,12 +147,16 @@ module PacketFu
|
|
118
147
|
# from the IP header, too.
|
119
148
|
#++
|
120
149
|
def tcp_calc_sum
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
150
|
+
if @ipv6_header
|
151
|
+
checksum = ipv6_calc_sum_on_addr
|
152
|
+
tcp_len = ipv6_len
|
153
|
+
else
|
154
|
+
checksum = ip_calc_sum_on_addr
|
155
|
+
tcp_len = ip_len.to_i - ((ip_hl.to_i) * 4)
|
156
|
+
end
|
157
|
+
|
125
158
|
checksum += 0x06 # TCP Protocol.
|
126
|
-
checksum +=
|
159
|
+
checksum += tcp_len
|
127
160
|
checksum += tcp_src
|
128
161
|
checksum += tcp_dst
|
129
162
|
checksum += (tcp_seq.to_i >> 16)
|
@@ -140,8 +173,8 @@ module PacketFu
|
|
140
173
|
|
141
174
|
chk_tcp_opts = (tcp_opts.to_s.size % 2 == 0 ? tcp_opts.to_s : tcp_opts.to_s + "\x00")
|
142
175
|
chk_tcp_opts.unpack("n*").each {|x| checksum = checksum + x }
|
143
|
-
if (
|
144
|
-
real_tcp_payload = payload[0,(
|
176
|
+
if (tcp_len - (tcp_hlen * 4)) >= 0
|
177
|
+
real_tcp_payload = payload[0, (tcp_len - (tcp_hlen * 4))] # Can't forget those pesky FCSes!
|
145
178
|
else
|
146
179
|
real_tcp_payload = payload # Something's amiss here so don't bother figuring out where the real payload is.
|
147
180
|
end
|
@@ -181,19 +214,29 @@ module PacketFu
|
|
181
214
|
# source and dest information, packet flags, sequence
|
182
215
|
# number, and IPID.
|
183
216
|
def peek_format
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
217
|
+
if ipv6?
|
218
|
+
peek_data = ["6T "]
|
219
|
+
peek_data << "%-5d" % self.to_s.size
|
220
|
+
peek_data << "%-31s" % "#{self.ipv6_saddr}:#{self.tcp_src}"
|
221
|
+
peek_data << "->"
|
222
|
+
peek_data << "%31s" % "#{self.ipv6_daddr}:#{self.tcp_dst}"
|
223
|
+
else
|
224
|
+
peek_data = ["T "]
|
225
|
+
peek_data << "%-5d" % self.to_s.size
|
226
|
+
peek_data << "%-21s" % "#{self.ip_saddr}:#{self.tcp_src}"
|
227
|
+
peek_data << "->"
|
228
|
+
peek_data << "%21s" % "#{self.ip_daddr}:#{self.tcp_dst}"
|
229
|
+
end
|
189
230
|
flags = ' ['
|
190
231
|
flags << self.tcp_flags_dotmap
|
191
232
|
flags << '] '
|
192
233
|
peek_data << flags
|
193
234
|
peek_data << "S:"
|
194
235
|
peek_data << "%08x" % self.tcp_seq
|
195
|
-
|
196
|
-
|
236
|
+
unless ipv6?
|
237
|
+
peek_data << "|I:"
|
238
|
+
peek_data << "%04x" % self.ip_id
|
239
|
+
end
|
197
240
|
peek_data.join
|
198
241
|
end
|
199
242
|
|
@@ -10,7 +10,7 @@ module PacketFu
|
|
10
10
|
# Int16 :udp_src
|
11
11
|
# Int16 :udp_dst
|
12
12
|
# Int16 :udp_len Default: calculated
|
13
|
-
# Int16 :udp_sum Default: 0. Often calculated.
|
13
|
+
# Int16 :udp_sum Default: 0. Often calculated.
|
14
14
|
# String :body
|
15
15
|
class UDPHeader < Struct.new(:udp_src, :udp_dst, :udp_len, :udp_sum, :body)
|
16
16
|
|
@@ -66,9 +66,8 @@ module PacketFu
|
|
66
66
|
end
|
67
67
|
|
68
68
|
# Recalculates calculated fields for UDP.
|
69
|
-
def udp_recalc(
|
70
|
-
|
71
|
-
case args
|
69
|
+
def udp_recalc(arg = :all)
|
70
|
+
case arg.to_sym
|
72
71
|
when :udp_len
|
73
72
|
self.udp_len = udp_calc_len
|
74
73
|
when :all
|
@@ -92,7 +91,7 @@ module PacketFu
|
|
92
91
|
def udp_dport
|
93
92
|
self.udp_dst
|
94
93
|
end
|
95
|
-
|
94
|
+
|
96
95
|
# Equivalent to udp_dst=
|
97
96
|
def udp_dport=(arg)
|
98
97
|
self.udp_dst=(arg)
|
data/lib/packetfu/protos/udp.rb
CHANGED
@@ -64,13 +64,13 @@ module PacketFu
|
|
64
64
|
end
|
65
65
|
|
66
66
|
def read(str=nil, args={})
|
67
|
-
|
68
|
-
@eth_header.read(str)
|
67
|
+
super
|
69
68
|
if args[:strip]
|
70
69
|
udp_body_len = self.ip_len - self.ip_hlen - 8
|
71
70
|
@udp_header.body.read(@udp_header.body.to_s[0,udp_body_len])
|
71
|
+
udp_calc_sum
|
72
|
+
@ip_header.ip_recalc unless ipv6?
|
72
73
|
end
|
73
|
-
super(args)
|
74
74
|
self
|
75
75
|
end
|
76
76
|
|
@@ -103,19 +103,12 @@ module PacketFu
|
|
103
103
|
def udp_calc_sum
|
104
104
|
# This is /not/ delegated down to @udp_header since we need info
|
105
105
|
# from the IP header, too.
|
106
|
-
checksum = 0
|
107
106
|
if @ipv6_header
|
108
|
-
|
109
|
-
8.times do |i|
|
110
|
-
checksum += (iaddr >> (i * 16)) & 0xffff
|
111
|
-
end
|
112
|
-
end
|
107
|
+
checksum = ipv6_calc_sum_on_addr
|
113
108
|
else
|
114
|
-
checksum
|
115
|
-
checksum += (ip_src.to_i & 0xffff)
|
116
|
-
checksum += (ip_dst.to_i >> 16)
|
117
|
-
checksum += (ip_dst.to_i & 0xffff)
|
109
|
+
checksum = ip_calc_sum_on_addr
|
118
110
|
end
|
111
|
+
|
119
112
|
checksum += 0x11
|
120
113
|
checksum += udp_len.to_i
|
121
114
|
checksum += udp_src.to_i
|
@@ -179,11 +172,6 @@ module PacketFu
|
|
179
172
|
end
|
180
173
|
end
|
181
174
|
|
182
|
-
# Is that packet an UDP on IPv6 packet ?
|
183
|
-
def ipv6?
|
184
|
-
not @ipv6_header.nil?
|
185
|
-
end
|
186
|
-
|
187
175
|
end
|
188
176
|
|
189
177
|
end
|
data/lib/packetfu/structfu.rb
CHANGED
data/lib/packetfu/version.rb
CHANGED
data/packetfu.gemspec
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
require 'rake'
|
2
1
|
require './lib/packetfu/version'
|
3
2
|
|
4
3
|
Gem::Specification.new do |s|
|
@@ -14,26 +13,19 @@ Gem::Specification.new do |s|
|
|
14
13
|
ease and fun they expect from Ruby.
|
15
14
|
}
|
16
15
|
s.files = `git ls-files`.split($/)
|
17
|
-
s.license = 'BSD'
|
18
|
-
s.required_ruby_version = '>= 2.
|
19
|
-
s.add_dependency('pcaprub')
|
16
|
+
s.license = 'BSD-3-Clause'
|
17
|
+
s.required_ruby_version = '>= 2.7.0'
|
18
|
+
s.add_dependency('pcaprub', '~> 0.13.1')
|
20
19
|
s.add_development_dependency('rake')
|
21
|
-
s.add_development_dependency('rspec')
|
22
|
-
s.add_development_dependency('rspec-its')
|
23
|
-
s.add_development_dependency('sdoc')
|
24
|
-
s.add_development_dependency('pry')
|
25
|
-
s.add_development_dependency('coveralls')
|
26
|
-
|
20
|
+
s.add_development_dependency('rspec', '~> 3.0')
|
21
|
+
s.add_development_dependency('rspec-its', '~> 1.2')
|
22
|
+
s.add_development_dependency('sdoc', '~> 0.4')
|
23
|
+
s.add_development_dependency('pry-byebug')
|
24
|
+
s.add_development_dependency('coveralls', '~> 0.8')
|
27
25
|
|
28
26
|
s.extra_rdoc_files = %w[.document README.md]
|
29
27
|
s.test_files = (s.files & (Dir['spec/**/*_spec.rb'] + Dir['test/test_*.rb']) )
|
30
|
-
s.rubyforge_project = 'packetfu'
|
31
|
-
|
32
|
-
cert = File.expand_path("~/.ssh/gem-private_key_todb.pem")
|
33
|
-
|
34
|
-
if File.exist?(cert) and File.readable?(cert)
|
35
|
-
s.signing_key = cert
|
36
|
-
s.cert_chain = ['gem-public_cert.pem']
|
37
|
-
end
|
38
28
|
|
29
|
+
s.cert_chain = ['certs/todb.pem']
|
30
|
+
s.signing_key = File.expand_path("~/.ssh/gem-private_key.pem") if $0 =~ /gem\z/
|
39
31
|
end
|
data/spec/arp_spec.rb
CHANGED
@@ -190,7 +190,7 @@ describe ARPPacket do
|
|
190
190
|
expect(@temp_file.read).to eql("")
|
191
191
|
|
192
192
|
@arp_packet.to_f(@temp_file.path, 'a')
|
193
|
-
expect(File.
|
193
|
+
expect(File.exist?(@temp_file.path)).to be(true)
|
194
194
|
expect(@temp_file.read.size).to be >= 76
|
195
195
|
end
|
196
196
|
end
|
@@ -0,0 +1,137 @@
|
|
1
|
+
# -*- coding: binary -*-
|
2
|
+
require 'spec_helper'
|
3
|
+
require 'packetfu/protos/eth'
|
4
|
+
require 'packetfu/protos/ip'
|
5
|
+
require 'packetfu/protos/ipv6'
|
6
|
+
require 'packetfu/protos/tcp'
|
7
|
+
require 'packetfu/protos/icmp'
|
8
|
+
require 'packetfu/config'
|
9
|
+
require 'packetfu/pcap'
|
10
|
+
require 'packetfu/utils'
|
11
|
+
require 'tempfile'
|
12
|
+
|
13
|
+
include PacketFu
|
14
|
+
|
15
|
+
describe Capture do
|
16
|
+
|
17
|
+
if Process.uid != 0
|
18
|
+
warn "Not running as root, PacketFu::Capture capabilities that require root will be skipped"
|
19
|
+
end
|
20
|
+
|
21
|
+
context "when creating an object from scratch" do
|
22
|
+
before :each do
|
23
|
+
@capture = PacketFu::Capture.new
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should have sane defaults" do
|
27
|
+
expect(@capture.array).to be_kind_of(Array)
|
28
|
+
expect(@capture.stream).to be_kind_of(Array)
|
29
|
+
expect(@capture.iface).to be_kind_of(String)
|
30
|
+
expect(@capture.snaplen).to eql(65535)
|
31
|
+
expect(@capture.promisc).to eql(false)
|
32
|
+
expect(@capture.timeout).to eql(1)
|
33
|
+
|
34
|
+
# Requires root/sudo to get this...
|
35
|
+
if Process.uid == 0
|
36
|
+
expect(@capture.filter).to eql(nil)
|
37
|
+
else
|
38
|
+
expect{@capture.filter}.to raise_error(RuntimeError)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should allow creating a capture object with non-std attributes" do
|
43
|
+
# Can only run this if we're root
|
44
|
+
if Process.uid == 0
|
45
|
+
options = {
|
46
|
+
:iface => PacketFu::Utils::default_int,
|
47
|
+
:snaplen => 0xfffe,
|
48
|
+
:promisc => true,
|
49
|
+
:timeout => 5,
|
50
|
+
:filter => "not port 22",
|
51
|
+
}
|
52
|
+
@capture = PacketFu::Capture.new(options)
|
53
|
+
|
54
|
+
expect(@capture.array).to be_kind_of(Array)
|
55
|
+
expect(@capture.stream).to be_kind_of(PCAPRUB::Pcap)
|
56
|
+
expect(@capture.iface).to eql(options[:iface])
|
57
|
+
expect(@capture.snaplen).to eql(options[:snaplen])
|
58
|
+
expect(@capture.promisc).to eql(options[:promisc])
|
59
|
+
expect(@capture.timeout).to eql(options[:timeout])
|
60
|
+
expect(@capture.filter).to eql(options[:filter])
|
61
|
+
expect(@capture.bpf).to eql(options[:filter])
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
context "when capturing traffic on the wire" do
|
67
|
+
# Can only run this if we're root
|
68
|
+
if Process.uid == 0
|
69
|
+
it "should capture an ICMP echo request from the wire" do
|
70
|
+
daddr = PacketFu::Utils.rand_routable_daddr.to_s
|
71
|
+
|
72
|
+
def do_capture_test(daddr)
|
73
|
+
begin
|
74
|
+
Timeout::timeout(3) {
|
75
|
+
cap = PacketFu::Capture.new(:iface => PacketFu::Utils::default_int, :start => true)
|
76
|
+
cap.stream.each do |p|
|
77
|
+
pkt = PacketFu::Packet.parse p
|
78
|
+
next unless pkt.is_icmp?
|
79
|
+
|
80
|
+
if pkt.ip_daddr == daddr and pkt.icmp_type == 8
|
81
|
+
return true
|
82
|
+
end
|
83
|
+
end
|
84
|
+
}
|
85
|
+
rescue Timeout::Error
|
86
|
+
return false
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
capture_thread = Thread.new { expect(do_capture_test(daddr)).to eql(true) }
|
91
|
+
%x{ping -c 1 #{daddr}}
|
92
|
+
capture_thread.join
|
93
|
+
end
|
94
|
+
|
95
|
+
it "should capture only capture ICMP echo requests we ask for from the wire" do
|
96
|
+
daddr = PacketFu::Utils.rand_routable_daddr.to_s
|
97
|
+
daddr2 = PacketFu::Utils.rand_routable_daddr.to_s
|
98
|
+
|
99
|
+
def do_bpf_capture_test(daddr, daddr2)
|
100
|
+
count = 0
|
101
|
+
valid_icmp = false
|
102
|
+
invalid_icmp = false
|
103
|
+
|
104
|
+
begin
|
105
|
+
Timeout::timeout(3) {
|
106
|
+
cap = PacketFu::Capture.new(:iface => PacketFu::Utils::default_int, :start => true, :filter => "icmp and dst host #{daddr}")
|
107
|
+
cap.stream.each do |p|
|
108
|
+
pkt = PacketFu::Packet.parse p
|
109
|
+
next unless pkt.is_icmp?
|
110
|
+
count += 1
|
111
|
+
|
112
|
+
if pkt.ip_daddr == daddr and pkt.icmp_type == 8
|
113
|
+
valid_icmp = true
|
114
|
+
elsif pkt.ip_daddr == daddr2 and pkt.icmp_type == 8
|
115
|
+
invalid_icmp = true
|
116
|
+
end
|
117
|
+
end
|
118
|
+
}
|
119
|
+
rescue Timeout::Error
|
120
|
+
### do nothing, we need to wait for the timeout anyways
|
121
|
+
end
|
122
|
+
|
123
|
+
if count == 1 && valid_icmp == true && invalid_icmp == false
|
124
|
+
return true
|
125
|
+
else
|
126
|
+
return false
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
capture_thread = Thread.new { expect(do_bpf_capture_test(daddr,daddr2)).to eql(true) }
|
131
|
+
%x{ping -c 1 #{daddr}}
|
132
|
+
%x{ping -c 1 #{daddr2}}
|
133
|
+
capture_thread.join
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
data/spec/eth_spec.rb
CHANGED
data/spec/icmp_spec.rb
CHANGED
@@ -87,7 +87,7 @@ describe ICMPPacket, "when read from a pcap file" do
|
|
87
87
|
expect(@temp_file.read).to eql("")
|
88
88
|
|
89
89
|
@icmp_packet.to_f(@temp_file.path, 'a')
|
90
|
-
expect(File.
|
90
|
+
expect(File.exist?(@temp_file.path))
|
91
91
|
expect(@temp_file.read.size).to be >= 79
|
92
92
|
end
|
93
93
|
|
data/spec/icmpv6_spec.rb
CHANGED
@@ -80,7 +80,7 @@ describe ICMPv6Packet, "when read from a pcap file" do
|
|
80
80
|
expect(@temp_file.read).to eql("")
|
81
81
|
|
82
82
|
@icmpv6_packet.to_f(@temp_file.path, 'a')
|
83
|
-
expect(File.
|
83
|
+
expect(File.exist?(@temp_file.path))
|
84
84
|
expect(@temp_file.read.size).to be >= 79
|
85
85
|
end
|
86
86
|
|