1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
|
require 'packetfu'
require 'ipaddr'
# Extent IPAddr with a private/public address space checks
class IPAddr
PrivateIPv4Ranges = [
IPAddr.new("10.0.0.0/8"),
IPAddr.new("172.16.0.0/12"),
IPAddr.new("192.168.0.0/16"),
IPAddr.new("255.255.255.255/32")
]
PrivateIPv6Ranges = [
IPAddr.new("fc00::/7")
]
def private?
private_ranges = self.ipv4? ? PrivateIPv4Ranges : PrivateIPv6Ranges
private_ranges.any? { |range| range.include?(self) }
end
def public?
!private?
end
end
class FirewallLeakCheck
attr_reader :ipv4_tcp_leaks, :ipv4_nontcp_leaks, :ipv6_leaks, :nonip_leaks, :mac_leaks
def initialize(pcap_file, options = {})
options[:accepted_hosts] ||= []
options[:ignore_lan] ||= true
@pcap_file = pcap_file
packets = PacketFu::PcapFile.new.file_to_array(:filename => @pcap_file)
mac_leaks = Set.new
ipv4_tcp_packets = []
ipv4_nontcp_packets = []
ipv6_packets = []
nonip_packets = []
packets.each do |p|
if PacketFu::EthPacket.can_parse?(p)
packet = PacketFu::EthPacket.parse(p)
mac_leaks << packet.eth_saddr
mac_leaks << packet.eth_daddr
end
if PacketFu::TCPPacket.can_parse?(p)
ipv4_tcp_packets << PacketFu::TCPPacket.parse(p)
elsif PacketFu::IPPacket.can_parse?(p)
ipv4_nontcp_packets << PacketFu::IPPacket.parse(p)
elsif PacketFu::IPv6Packet.can_parse?(p)
ipv6_packets << PacketFu::IPv6Packet.parse(p)
elsif PacketFu::Packet.can_parse?(p)
nonip_packets << PacketFu::Packet.parse(p)
else
save_pcap_file
raise "Found something in the pcap file that cannot be parsed"
end
end
ipv4_tcp_hosts = filter_hosts_from_ippackets(ipv4_tcp_packets,
options[:ignore_lan])
accepted = Set.new(options[:accepted_hosts])
@mac_leaks = mac_leaks
@ipv4_tcp_leaks = ipv4_tcp_hosts.select { |host| !accepted.member?(host) }
@ipv4_nontcp_leaks = filter_hosts_from_ippackets(ipv4_nontcp_packets,
options[:ignore_lan])
@ipv6_leaks = filter_hosts_from_ippackets(ipv6_packets,
options[:ignore_lan])
@nonip_leaks = nonip_packets
end
def save_pcap_file
save_failure_artifact("Network capture", @pcap_file)
end
# Returns a list of all unique destination IP addresses found in
# `packets`. Exclude LAN hosts if ignore_lan is set.
def filter_hosts_from_ippackets(packets, ignore_lan)
hosts = []
packets.each do |p|
candidate = nil
if p.kind_of?(PacketFu::IPPacket)
candidate = p.ip_daddr
elsif p.kind_of?(PacketFu::IPv6Packet)
candidate = p.ipv6_header.ipv6_daddr
else
save_pcap_file
raise "Expected an IP{v4,v6} packet, but got something else:\n" +
p.peek_format
end
if candidate != nil and (not(ignore_lan) or IPAddr.new(candidate).public?)
hosts << candidate
end
end
hosts.uniq
end
def assert_no_leaks
err = ""
if !@ipv4_tcp_leaks.empty?
err += "The following IPv4 TCP non-Tor Internet hosts were " +
"contacted:\n" + ipv4_tcp_leaks.join("\n")
end
if !@ipv4_nontcp_leaks.empty?
err += "The following IPv4 non-TCP Internet hosts were contacted:\n" +
ipv4_nontcp_leaks.join("\n")
end
if !@ipv6_leaks.empty?
err += "The following IPv6 Internet hosts were contacted:\n" +
ipv6_leaks.join("\n")
end
if !@nonip_leaks.empty?
err += "Some non-IP packets were sent\n"
end
if !err.empty?
save_pcap_file
raise err
end
end
end
|