diff --git a/doc/source/contributor/internals/openvswitch_firewall.rst b/doc/source/contributor/internals/openvswitch_firewall.rst index b7f88c37390..82ac9f3c9d3 100644 --- a/doc/source/contributor/internals/openvswitch_firewall.rst +++ b/doc/source/contributor/internals/openvswitch_firewall.rst @@ -208,16 +208,16 @@ solicitation and neighbour advertisement. :: - table=71, priority=95,icmp6,reg5=0x1,in_port=1,icmp_type=130 actions=resubmit(,94) - table=71, priority=95,icmp6,reg5=0x1,in_port=1,icmp_type=131 actions=resubmit(,94) - table=71, priority=95,icmp6,reg5=0x1,in_port=1,icmp_type=132 actions=resubmit(,94) - table=71, priority=95,icmp6,reg5=0x1,in_port=1,icmp_type=135 actions=resubmit(,94) - table=71, priority=95,icmp6,reg5=0x1,in_port=1,icmp_type=136 actions=resubmit(,94) - table=71, priority=95,icmp6,reg5=0x2,in_port=2,icmp_type=130 actions=resubmit(,94) - table=71, priority=95,icmp6,reg5=0x2,in_port=2,icmp_type=131 actions=resubmit(,94) - table=71, priority=95,icmp6,reg5=0x2,in_port=2,icmp_type=132 actions=resubmit(,94) - table=71, priority=95,icmp6,reg5=0x2,in_port=2,icmp_type=135 actions=resubmit(,94) - table=71, priority=95,icmp6,reg5=0x2,in_port=2,icmp_type=136 actions=resubmit(,94) + table=71, priority=95,icmp6,reg5=0x1,in_port=1,dl_src=fa:16:3e:a4:22:11,ipv6_src=fe80::11,icmp_type=130 actions=resubmit(,94) + table=71, priority=95,icmp6,reg5=0x1,in_port=1,dl_src=fa:16:3e:a4:22:11,ipv6_src=fe80::11,icmp_type=131 actions=resubmit(,94) + table=71, priority=95,icmp6,reg5=0x1,in_port=1,dl_src=fa:16:3e:a4:22:11,ipv6_src=fe80::11,icmp_type=132 actions=resubmit(,94) + table=71, priority=95,icmp6,reg5=0x1,in_port=1,dl_src=fa:16:3e:a4:22:11,ipv6_src=fe80::11,icmp_type=135 actions=resubmit(,94) + table=71, priority=95,icmp6,reg5=0x1,in_port=1,dl_src=fa:16:3e:a4:22:11,ipv6_src=fe80::11,icmp_type=136 actions=resubmit(,94) + table=71, priority=95,icmp6,reg5=0x2,in_port=2,dl_src=fa:16:3e:a4:22:22,ipv6_src=fe80::22,icmp_type=130 actions=resubmit(,94) + table=71, priority=95,icmp6,reg5=0x2,in_port=2,dl_src=fa:16:3e:a4:22:22,ipv6_src=fe80::22,icmp_type=131 actions=resubmit(,94) + table=71, priority=95,icmp6,reg5=0x2,in_port=2,dl_src=fa:16:3e:a4:22:22,ipv6_src=fe80::22,icmp_type=132 actions=resubmit(,94) + table=71, priority=95,icmp6,reg5=0x2,in_port=2,dl_src=fa:16:3e:a4:22:22,ipv6_src=fe80::22,icmp_type=135 actions=resubmit(,94) + table=71, priority=95,icmp6,reg5=0x2,in_port=2,dl_src=fa:16:3e:a4:22:22,ipv6_src=fe80::22,icmp_type=136 actions=resubmit(,94) Following rules implement arp spoofing protection diff --git a/neutron/agent/linux/openvswitch_firewall/firewall.py b/neutron/agent/linux/openvswitch_firewall/firewall.py index 1a68e4431a3..ebb6576521e 100644 --- a/neutron/agent/linux/openvswitch_firewall/firewall.py +++ b/neutron/agent/linux/openvswitch_firewall/firewall.py @@ -880,19 +880,24 @@ class OVSFirewallDriver(firewall.FirewallDriver): self._initialize_egress(port) self._initialize_ingress(port) - def _initialize_egress_ipv6_icmp(self, port): - for icmp_type in firewall.ICMPV6_ALLOWED_EGRESS_TYPES: - self._add_flow( - table=ovs_consts.BASE_EGRESS_TABLE, - priority=95, - in_port=port.ofport, - reg_port=port.ofport, - dl_type=constants.ETHERTYPE_IPV6, - nw_proto=lib_const.PROTO_NUM_IPV6_ICMP, - icmp_type=icmp_type, - actions='resubmit(,%d)' % ( - ovs_consts.ACCEPTED_EGRESS_TRAFFIC_NORMAL_TABLE) - ) + def _initialize_egress_ipv6_icmp(self, port, allowed_pairs): + # NOTE(slaweq): should we include also fe80::/64 (link-local) subnet + # in the allowed pairs here? + for mac_addr, ip_addr in allowed_pairs: + for icmp_type in firewall.ICMPV6_ALLOWED_EGRESS_TYPES: + self._add_flow( + table=ovs_consts.BASE_EGRESS_TABLE, + priority=95, + in_port=port.ofport, + reg_port=port.ofport, + dl_type=constants.ETHERTYPE_IPV6, + nw_proto=lib_const.PROTO_NUM_IPV6_ICMP, + icmp_type=icmp_type, + dl_src=mac_addr, + ipv6_src=ip_addr, + actions='resubmit(,%d)' % ( + ovs_consts.ACCEPTED_EGRESS_TRAFFIC_NORMAL_TABLE) + ) def _initialize_egress_no_port_security(self, port_id, ovs_ports=None): try: @@ -965,7 +970,6 @@ class OVSFirewallDriver(firewall.FirewallDriver): def _initialize_egress(self, port): """Identify egress traffic and send it to egress base""" - self._initialize_egress_ipv6_icmp(port) # Apply mac/ip pairs for IPv4 allowed_pairs = port.allowed_pairs_v4.union( @@ -998,6 +1002,7 @@ class OVSFirewallDriver(firewall.FirewallDriver): # Apply mac/ip pairs for IPv6 allowed_pairs = port.allowed_pairs_v6.union( {(port.mac, ip_addr) for ip_addr in port.ipv6_addresses}) + self._initialize_egress_ipv6_icmp(port, allowed_pairs) for mac_addr, ip_addr in allowed_pairs: self._add_flow( table=ovs_consts.BASE_EGRESS_TABLE, diff --git a/neutron/tests/unit/agent/linux/openvswitch_firewall/test_firewall.py b/neutron/tests/unit/agent/linux/openvswitch_firewall/test_firewall.py index 88d4a48726c..015f257dae3 100644 --- a/neutron/tests/unit/agent/linux/openvswitch_firewall/test_firewall.py +++ b/neutron/tests/unit/agent/linux/openvswitch_firewall/test_firewall.py @@ -39,8 +39,14 @@ TESTING_SEGMENT = 1000 def create_ofport(port_dict, network_type=None, physical_network=None): + allowed_pairs_v4 = ovsfw.OFPort._get_allowed_pairs( + port_dict, version=constants.IPv4) + allowed_pairs_v6 = ovsfw.OFPort._get_allowed_pairs( + port_dict, version=constants.IPv6) ovs_port = mock.Mock(vif_mac='00:00:00:00:00:00', ofport=1, - port_name="port-name") + port_name="port-name", + allowed_pairs_v4=allowed_pairs_v4, + allowed_pairs_v6=allowed_pairs_v6) return ovsfw.OFPort(port_dict, ovs_port, vlan_tag=TESTING_VLAN_TAG, segment_id=TESTING_SEGMENT, network_type=network_type, @@ -937,6 +943,38 @@ class TestOVSFirewallDriver(base.BaseTestCase): with testtools.ExpectedException(exceptions.OVSFWPortNotHandled): self.firewall._remove_egress_no_port_security('foo') + def test__initialize_egress_ipv6_icmp(self): + port_dict = { + 'device': 'port-id', + 'security_groups': [1], + 'fixed_ips': ["10.0.0.1"], + 'allowed_address_pairs': [ + {'mac_address': 'aa:bb:cc:dd:ee:ff', + 'ip_address': '192.168.1.1'}, + {'mac_address': 'aa:bb:cc:dd:ee:ff', + 'ip_address': '2003::1'} + ]} + of_port = create_ofport(port_dict) + self.mock_bridge.br.db_get_val.return_value = {'tag': TESTING_VLAN_TAG} + self.firewall._initialize_egress_ipv6_icmp( + of_port, set([('aa:bb:cc:dd:ee:ff', '2003::1')])) + expected_calls = [] + for icmp_type in firewall.ICMPV6_ALLOWED_EGRESS_TYPES: + expected_calls.append( + mock.call( + table=ovs_consts.BASE_EGRESS_TABLE, + priority=95, + in_port=TESTING_VLAN_TAG, + reg5=TESTING_VLAN_TAG, + dl_type='0x86dd', + nw_proto=constants.PROTO_NUM_IPV6_ICMP, + icmp_type=icmp_type, + dl_src='aa:bb:cc:dd:ee:ff', + ipv6_src='2003::1', + actions='resubmit(,%d)' % ( + ovs_consts.ACCEPTED_EGRESS_TRAFFIC_NORMAL_TABLE))) + self.mock_bridge.br.add_flow.assert_has_calls(expected_calls) + def test_process_trusted_ports_caches_port_id(self): vif_port = ovs_lib.VifPort('name', 1, 'id', 'mac', mock.ANY) with mock.patch.object(self.firewall.int_br.br, 'get_vifs_by_ids',