diff --git a/neutron/agent/linux/iptables_firewall.py b/neutron/agent/linux/iptables_firewall.py index afc815255a9..f2b58da32f4 100644 --- a/neutron/agent/linux/iptables_firewall.py +++ b/neutron/agent/linux/iptables_firewall.py @@ -40,6 +40,7 @@ SPOOF_FILTER = 'spoof-filter' CHAIN_NAME_PREFIX = {firewall.INGRESS_DIRECTION: 'i', firewall.EGRESS_DIRECTION: 'o', SPOOF_FILTER: 's'} +ICMPV6_ALLOWED_UNSPEC_ADDR_TYPES = [131, 135, 143] IPSET_DIRECTION = {firewall.INGRESS_DIRECTION: 'src', firewall.EGRESS_DIRECTION: 'dst'} # length of all device prefixes (e.g. qvo, tap, qvb) @@ -379,21 +380,25 @@ class IptablesFirewallDriver(firewall.FirewallDriver): mac_ipv4_pairs.append((mac, ip_address)) else: mac_ipv6_pairs.append((mac, ip_address)) + lla = str(ipv6_utils.get_ipv6_addr_by_EUI64( + constants.IPV6_LLA_PREFIX, mac)) + mac_ipv6_pairs.append((mac, lla)) def _spoofing_rule(self, port, ipv4_rules, ipv6_rules): + # Fixed rules for traffic sourced from unspecified addresses: 0.0.0.0 + # and :: # Allow dhcp client discovery and request ipv4_rules += [comment_rule('-s 0.0.0.0/32 -d 255.255.255.255/32 ' '-p udp -m udp --sport 68 --dport 67 ' '-j RETURN', comment=ic.DHCP_CLIENT)] - # Drop Router Advts from the port. - ipv6_rules += [comment_rule('-p ipv6-icmp -m icmp6 --icmpv6-type %s ' - '-j DROP' % constants.ICMPV6_TYPE_RA, - comment=ic.IPV6_RA_DROP)] - ipv6_rules += [comment_rule('-p ipv6-icmp -j RETURN', - comment=ic.IPV6_ICMP_ALLOW)] - ipv6_rules += [comment_rule('-p udp -m udp --sport 546 ' - '-m udp --dport 547 ' - '-j RETURN', comment=ic.DHCP_CLIENT)] + # Allow neighbor solicitation and multicast listener discovery + # from the unspecified address for duplicate address detection + for icmp6_type in ICMPV6_ALLOWED_UNSPEC_ADDR_TYPES: + ipv6_rules += [comment_rule('-s ::/128 -d ff02::/16 ' + '-p ipv6-icmp -m icmp6 ' + '--icmpv6-type %s -j RETURN' % + icmp6_type, + comment=ic.IPV6_ICMP_ALLOW)] mac_ipv4_pairs = [] mac_ipv6_pairs = [] @@ -415,9 +420,19 @@ class IptablesFirewallDriver(firewall.FirewallDriver): mac_ipv4_pairs, ipv4_rules) self._setup_spoof_filter_chain(port, self.iptables.ipv6['filter'], mac_ipv6_pairs, ipv6_rules) + # Fixed rules for traffic after source address is verified # Allow dhcp client renewal and rebinding ipv4_rules += [comment_rule('-p udp -m udp --sport 68 --dport 67 ' '-j RETURN', comment=ic.DHCP_CLIENT)] + # Drop Router Advts from the port. + ipv6_rules += [comment_rule('-p ipv6-icmp -m icmp6 --icmpv6-type %s ' + '-j DROP' % constants.ICMPV6_TYPE_RA, + comment=ic.IPV6_RA_DROP)] + ipv6_rules += [comment_rule('-p ipv6-icmp -j RETURN', + comment=ic.IPV6_ICMP_ALLOW)] + ipv6_rules += [comment_rule('-p udp -m udp --sport 546 ' + '-m udp --dport 547 ' + '-j RETURN', comment=ic.DHCP_CLIENT)] def _drop_dhcp_rule(self, ipv4_rules, ipv6_rules): #Note(nati) Drop dhcp packet from VM diff --git a/neutron/tests/functional/agent/test_firewall.py b/neutron/tests/functional/agent/test_firewall.py index f189f59685c..f57751edd35 100644 --- a/neutron/tests/functional/agent/test_firewall.py +++ b/neutron/tests/functional/agent/test_firewall.py @@ -660,3 +660,23 @@ class FirewallTestCaseIPv6(BaseFirewallTestCase): direction=self.tester.EGRESS) self.tester.assert_no_connection(protocol=self.tester.ICMP, direction=self.tester.EGRESS) + + @skip_if_firewall('openvswitch') + def test_ip_spoofing(self): + sg_rules = [{'ethertype': constants.IPv6, + 'direction': firewall.INGRESS_DIRECTION, + 'protocol': constants.PROTO_NAME_ICMP}] + self._apply_security_group_rules(self.FAKE_SECURITY_GROUP_ID, sg_rules) + not_allowed_ip = "%s/64" % ( + netaddr.IPAddress(self.tester.vm_ip_address) + 1) + + self.tester.assert_connection(protocol=self.tester.ICMP, + direction=self.tester.INGRESS) + self.tester.vm_ip_cidr = not_allowed_ip + self.tester.assert_no_connection(protocol=self.tester.ICMP, + direction=self.tester.INGRESS) + self.tester.assert_no_connection(protocol=self.tester.ICMP, + direction=self.tester.EGRESS) + self.tester.assert_no_connection(protocol=self.tester.UDP, + src_port=546, dst_port=547, + direction=self.tester.EGRESS) diff --git a/neutron/tests/unit/agent/linux/test_iptables_firewall.py b/neutron/tests/unit/agent/linux/test_iptables_firewall.py index 36f61e86c48..e05aee6b73e 100644 --- a/neutron/tests/unit/agent/linux/test_iptables_firewall.py +++ b/neutron/tests/unit/agent/linux/test_iptables_firewall.py @@ -952,17 +952,22 @@ class IptablesFirewallTestCase(BaseIptablesFirewallTestCase): if ethertype == 'IPv6': filter_inst = self.v6filter_inst - dhcp_rule = [mock.call.add_rule('ofake_dev', '-p ipv6-icmp ' - '-m icmp6 ' - '--icmpv6-type %s -j DROP' - % constants.ICMPV6_TYPE_RA, + dhcp_rule = [mock.call.add_rule('ofake_dev', + '-s ::/128 -d ff02::/16 ' + '-p ipv6-icmp -m icmp6 ' + '--icmpv6-type 131 -j RETURN', comment=None), mock.call.add_rule('ofake_dev', - '-p ipv6-icmp -j RETURN', + '-s ::/128 -d ff02::/16 ' + '-p ipv6-icmp -m icmp6 ' + '--icmpv6-type %s -j RETURN' % + constants.ICMPV6_TYPE_NC, comment=None), - mock.call.add_rule('ofake_dev', '-p udp -m udp ' - '--sport 546 -m udp --dport 547 ' - '-j RETURN', comment=None)] + mock.call.add_rule('ofake_dev', + '-s ::/128 -d ff02::/16 ' + '-p ipv6-icmp -m icmp6 ' + '--icmpv6-type 143 -j RETURN', + comment=None)] sg = [rule] port['security_group_rules'] = sg self.firewall.prepare_port_filter(port) @@ -1025,10 +1030,15 @@ class IptablesFirewallTestCase(BaseIptablesFirewallTestCase): 'sfake_dev', '-s %s -m mac --mac-source FF:FF:FF:FF:FF:FF -j RETURN' % prefix, - comment=ic.PAIR_ALLOW), - mock.call.add_rule( - 'sfake_dev', '-j DROP', - comment=ic.PAIR_DROP)] + comment=ic.PAIR_ALLOW)] + + if ethertype == 'IPv6': + calls.append(mock.call.add_rule('sfake_dev', + '-s fe80::fdff:ffff:feff:ffff/128 -m mac ' + '--mac-source FF:FF:FF:FF:FF:FF -j RETURN', + comment=ic.PAIR_ALLOW)) + calls.append(mock.call.add_rule('sfake_dev', '-j DROP', + comment=ic.PAIR_DROP)) calls += dhcp_rule calls.append(mock.call.add_rule('ofake_dev', '-j $sfake_dev', comment=None)) @@ -1042,6 +1052,17 @@ class IptablesFirewallTestCase(BaseIptablesFirewallTestCase): '-p udp -m udp --sport 67 -m udp --dport 68 -j DROP', comment=None)) if ethertype == 'IPv6': + calls.append(mock.call.add_rule('ofake_dev', + '-p ipv6-icmp -m icmp6 ' + '--icmpv6-type %s -j DROP' % + constants.ICMPV6_TYPE_RA, + comment=None)) + calls.append(mock.call.add_rule('ofake_dev', + '-p ipv6-icmp -j RETURN', + comment=None)) + calls.append(mock.call.add_rule('ofake_dev', '-p udp -m udp ' + '--sport 546 -m udp --dport 547 ' + '-j RETURN', comment=None)) calls.append(mock.call.add_rule( 'ofake_dev', '-p udp -m udp --sport 547 -m udp --dport 546 -j DROP', @@ -1862,6 +1883,7 @@ class IptablesFirewallEnhancedIpsetTestCase(BaseIptablesFirewallTestCase): fake_ipv4_pair.append((mac_unix, ipv4)) fake_ipv6_pair = [] fake_ipv6_pair.append((mac_unix, ipv6)) + fake_ipv6_pair.append((mac_unix, 'fe80::fdff:ffff:fe0f:ffff')) mac_ipv4_pairs = [] mac_ipv6_pairs = [] diff --git a/neutron/tests/unit/agent/test_securitygroups_rpc.py b/neutron/tests/unit/agent/test_securitygroups_rpc.py index 3cdc1abadf0..beb811f8577 100644 --- a/neutron/tests/unit/agent/test_securitygroups_rpc.py +++ b/neutron/tests/unit/agent/test_securitygroups_rpc.py @@ -2536,13 +2536,19 @@ IPTABLES_FILTER_V6_1 = """# Generated by iptables_manager -I %(bn)s-i_port1 6 -m state --state RELATED,ESTABLISHED -j RETURN -I %(bn)s-i_port1 7 -m state --state INVALID -j DROP -I %(bn)s-i_port1 8 -j %(bn)s-sg-fallback --I %(bn)s-o_port1 1 -p ipv6-icmp -m icmp6 --icmpv6-type 134 -j DROP --I %(bn)s-o_port1 2 -p ipv6-icmp -j RETURN --I %(bn)s-o_port1 3 -p udp -m udp --sport 546 -m udp --dport 547 -j RETURN --I %(bn)s-o_port1 4 -p udp -m udp --sport 547 -m udp --dport 546 -j DROP --I %(bn)s-o_port1 5 -m state --state RELATED,ESTABLISHED -j RETURN --I %(bn)s-o_port1 6 -m state --state INVALID -j DROP --I %(bn)s-o_port1 7 -j %(bn)s-sg-fallback +-I %(bn)s-o_port1 1 -s ::/128 -d ff02::/16 -p ipv6-icmp -m icmp6 \ +--icmpv6-type 131 -j RETURN +-I %(bn)s-o_port1 2 -s ::/128 -d ff02::/16 -p ipv6-icmp -m icmp6 \ +--icmpv6-type 135 -j RETURN +-I %(bn)s-o_port1 3 -s ::/128 -d ff02::/16 -p ipv6-icmp -m icmp6 \ +--icmpv6-type 143 -j RETURN +-I %(bn)s-o_port1 4 -p ipv6-icmp -m icmp6 --icmpv6-type 134 -j DROP +-I %(bn)s-o_port1 5 -p ipv6-icmp -j RETURN +-I %(bn)s-o_port1 6 -p udp -m udp --sport 546 -m udp --dport 547 -j RETURN +-I %(bn)s-o_port1 7 -p udp -m udp --sport 547 -m udp --dport 546 -j DROP +-I %(bn)s-o_port1 8 -m state --state RELATED,ESTABLISHED -j RETURN +-I %(bn)s-o_port1 9 -m state --state INVALID -j DROP +-I %(bn)s-o_port1 10 -j %(bn)s-sg-fallback -I %(bn)s-sg-chain 1 %(physdev_mod)s --physdev-INGRESS tap_port1 \ %(physdev_is_bridged)s -j %(bn)s-i_port1 -I %(bn)s-sg-chain 2 %(physdev_mod)s --physdev-EGRESS tap_port1 \ @@ -2608,20 +2614,32 @@ IPTABLES_FILTER_V6_2 = """# Generated by iptables_manager -I %(bn)s-i_%(port2)s 6 -m state --state RELATED,ESTABLISHED -j RETURN -I %(bn)s-i_%(port2)s 7 -m state --state INVALID -j DROP -I %(bn)s-i_%(port2)s 8 -j %(bn)s-sg-fallback --I %(bn)s-o_%(port1)s 1 -p ipv6-icmp -m icmp6 --icmpv6-type 134 -j DROP --I %(bn)s-o_%(port1)s 2 -p ipv6-icmp -j RETURN --I %(bn)s-o_%(port1)s 3 -p udp -m udp --sport 546 -m udp --dport 547 -j RETURN --I %(bn)s-o_%(port1)s 4 -p udp -m udp --sport 547 -m udp --dport 546 -j DROP --I %(bn)s-o_%(port1)s 5 -m state --state RELATED,ESTABLISHED -j RETURN --I %(bn)s-o_%(port1)s 6 -m state --state INVALID -j DROP --I %(bn)s-o_%(port1)s 7 -j %(bn)s-sg-fallback --I %(bn)s-o_%(port2)s 1 -p ipv6-icmp -m icmp6 --icmpv6-type 134 -j DROP --I %(bn)s-o_%(port2)s 2 -p ipv6-icmp -j RETURN --I %(bn)s-o_%(port2)s 3 -p udp -m udp --sport 546 -m udp --dport 547 -j RETURN --I %(bn)s-o_%(port2)s 4 -p udp -m udp --sport 547 -m udp --dport 546 -j DROP --I %(bn)s-o_%(port2)s 5 -m state --state RELATED,ESTABLISHED -j RETURN --I %(bn)s-o_%(port2)s 6 -m state --state INVALID -j DROP --I %(bn)s-o_%(port2)s 7 -j %(bn)s-sg-fallback +-I %(bn)s-o_%(port1)s 1 -s ::/128 -d ff02::/16 -p ipv6-icmp -m icmp6 \ +--icmpv6-type 131 -j RETURN +-I %(bn)s-o_%(port1)s 2 -s ::/128 -d ff02::/16 -p ipv6-icmp -m icmp6 \ +--icmpv6-type 135 -j RETURN +-I %(bn)s-o_%(port1)s 3 -s ::/128 -d ff02::/16 -p ipv6-icmp -m icmp6 \ +--icmpv6-type 143 -j RETURN +-I %(bn)s-o_%(port1)s 4 -p ipv6-icmp -m icmp6 --icmpv6-type 134 -j DROP +-I %(bn)s-o_%(port1)s 5 -p ipv6-icmp -j RETURN +-I %(bn)s-o_%(port1)s 6 -p udp -m udp --sport 546 -m udp --dport 547 -j RETURN +-I %(bn)s-o_%(port1)s 7 -p udp -m udp --sport 547 -m udp --dport 546 -j DROP +-I %(bn)s-o_%(port1)s 8 -m state --state RELATED,ESTABLISHED -j RETURN +-I %(bn)s-o_%(port1)s 9 -m state --state INVALID -j DROP +-I %(bn)s-o_%(port1)s 10 -j %(bn)s-sg-fallback +-I %(bn)s-o_%(port2)s 1 -s ::/128 -d ff02::/16 -p ipv6-icmp -m icmp6 \ +--icmpv6-type 131 -j RETURN +-I %(bn)s-o_%(port2)s 2 -s ::/128 -d ff02::/16 -p ipv6-icmp -m icmp6 \ +--icmpv6-type 135 -j RETURN +-I %(bn)s-o_%(port2)s 3 -s ::/128 -d ff02::/16 -p ipv6-icmp -m icmp6 \ +--icmpv6-type 143 -j RETURN +-I %(bn)s-o_%(port2)s 4 -p ipv6-icmp -m icmp6 --icmpv6-type 134 -j DROP +-I %(bn)s-o_%(port2)s 5 -p ipv6-icmp -j RETURN +-I %(bn)s-o_%(port2)s 6 -p udp -m udp --sport 546 -m udp --dport 547 -j RETURN +-I %(bn)s-o_%(port2)s 7 -p udp -m udp --sport 547 -m udp --dport 546 -j DROP +-I %(bn)s-o_%(port2)s 8 -m state --state RELATED,ESTABLISHED -j RETURN +-I %(bn)s-o_%(port2)s 9 -m state --state INVALID -j DROP +-I %(bn)s-o_%(port2)s 10 -j %(bn)s-sg-fallback -I %(bn)s-sg-chain 1 %(physdev_mod)s --physdev-INGRESS tap_%(port1)s \ %(physdev_is_bridged)s -j %(bn)s-i_%(port1)s -I %(bn)s-sg-chain 2 %(physdev_mod)s --physdev-EGRESS tap_%(port1)s \