diff --git a/neutron/agent/firewall.py b/neutron/agent/firewall.py index 4a54c6a2399..71df95dbd9f 100644 --- a/neutron/agent/firewall.py +++ b/neutron/agent/firewall.py @@ -36,8 +36,11 @@ ICMPV6_ALLOWED_INGRESS_TYPES = (n_const.ICMPV6_TYPE_MLD_QUERY, # List of ICMPv6 types that should be permitted (egress) by default. ICMPV6_ALLOWED_EGRESS_TYPES = (n_const.ICMPV6_TYPE_MLD_QUERY, n_const.ICMPV6_TYPE_RS, - n_const.ICMPV6_TYPE_NS, - n_const.ICMPV6_TYPE_NA) + n_const.ICMPV6_TYPE_NS) + +# List of ICMPv6 types that should be permitted depending on payload content +# to avoid spoofing (egress) by default. +ICMPV6_RESTRICTED_EGRESS_TYPES = (n_const.ICMPV6_TYPE_NA, ) def port_sec_enabled(port): diff --git a/neutron/agent/linux/openvswitch_firewall/firewall.py b/neutron/agent/linux/openvswitch_firewall/firewall.py index a33ebc48f7f..17f3d4f6075 100644 --- a/neutron/agent/linux/openvswitch_firewall/firewall.py +++ b/neutron/agent/linux/openvswitch_firewall/firewall.py @@ -909,8 +909,7 @@ class OVSFirewallDriver(firewall.FirewallDriver): self._initialize_ingress(port) 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? + allowed_pairs = allowed_pairs.union({(port.mac, port.lla_address)}) for mac_addr, ip_addr in allowed_pairs: for icmp_type in firewall.ICMPV6_ALLOWED_EGRESS_TYPES: self._add_flow( @@ -926,6 +925,19 @@ class OVSFirewallDriver(firewall.FirewallDriver): actions='resubmit(,%d)' % ( ovs_consts.ACCEPTED_EGRESS_TRAFFIC_NORMAL_TABLE) ) + for icmp_type in firewall.ICMPV6_RESTRICTED_EGRESS_TYPES: + self._add_flow( + table=ovs_consts.BASE_EGRESS_TABLE, + priority=95, + in_port=port.ofport, + reg_port=port.ofport, + dl_type=lib_const.ETHERTYPE_IPV6, + nw_proto=lib_const.PROTO_NUM_IPV6_ICMP, + icmp_type=icmp_type, + nd_target=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: @@ -1000,9 +1012,9 @@ class OVSFirewallDriver(firewall.FirewallDriver): """Identify egress traffic and send it to egress base""" # Apply mac/ip pairs for IPv4 - allowed_pairs = port.allowed_pairs_v4.union( + allowed_mac_ipv4_pairs = port.allowed_pairs_v4.union( {(port.mac, ip_addr) for ip_addr in port.ipv4_addresses}) - for mac_addr, ip_addr in allowed_pairs: + for mac_addr, ip_addr in allowed_mac_ipv4_pairs: self._add_flow( table=ovs_consts.BASE_EGRESS_TABLE, priority=95, @@ -1028,10 +1040,10 @@ class OVSFirewallDriver(firewall.FirewallDriver): ) # Apply mac/ip pairs for IPv6 - allowed_pairs = port.allowed_pairs_v6.union( + allowed_mac_ipv6_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._initialize_egress_ipv6_icmp(port, allowed_mac_ipv6_pairs) + for mac_addr, ip_addr in allowed_mac_ipv6_pairs: self._add_flow( table=ovs_consts.BASE_EGRESS_TABLE, priority=65, @@ -1046,21 +1058,31 @@ class OVSFirewallDriver(firewall.FirewallDriver): ) # DHCP discovery - for dl_type, src_port, dst_port in ( - (lib_const.ETHERTYPE_IP, 68, 67), - (lib_const.ETHERTYPE_IPV6, 546, 547)): - self._add_flow( - table=ovs_consts.BASE_EGRESS_TABLE, - priority=80, - reg_port=port.ofport, - in_port=port.ofport, - dl_type=dl_type, - nw_proto=lib_const.PROTO_NUM_UDP, - tp_src=src_port, - tp_dst=dst_port, - actions='resubmit(,{:d})'.format( - ovs_consts.ACCEPT_OR_INGRESS_TABLE) - ) + additional_ipv4_filters = [ + {"dl_src": mac, "nw_src": ip} + for mac, ip in allowed_mac_ipv4_pairs] + additional_ipv4_filters.append( + {"dl_src": port.mac, "nw_src": "0.0.0.0"}) + additional_ipv6_filters = [ + {"dl_src": mac, "ipv6_src": ip} + for mac, ip in allowed_mac_ipv6_pairs] + for dl_type, src_port, dst_port, filters_list in ( + (lib_const.ETHERTYPE_IP, 68, 67, additional_ipv4_filters), + (lib_const.ETHERTYPE_IPV6, 546, 547, additional_ipv6_filters)): + for additional_filters in filters_list: + self._add_flow( + table=ovs_consts.BASE_EGRESS_TABLE, + priority=80, + reg_port=port.ofport, + in_port=port.ofport, + dl_type=dl_type, + nw_proto=lib_const.PROTO_NUM_UDP, + tp_src=src_port, + tp_dst=dst_port, + actions='resubmit(,{:d})'.format( + ovs_consts.ACCEPT_OR_INGRESS_TABLE), + **additional_filters + ) # Ban dhcp service running on an instance for dl_type, src_port, dst_port in ( (lib_const.ETHERTYPE_IP, 67, 68), 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 e672dcc3f4e..289a1f1645d 100644 --- a/neutron/tests/unit/agent/linux/openvswitch_firewall/test_firewall.py +++ b/neutron/tests/unit/agent/linux/openvswitch_firewall/test_firewall.py @@ -1029,6 +1029,19 @@ class TestOVSFirewallDriver(base.BaseTestCase): ipv6_src='2003::1', actions='resubmit(,%d)' % ( ovs_consts.ACCEPTED_EGRESS_TRAFFIC_NORMAL_TABLE))) + for icmp_type in agent_firewall.ICMPV6_RESTRICTED_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, + nd_target='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):