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