From 09c4e0e970b02ed992aab762505d17f1decca551 Mon Sep 17 00:00:00 2001 From: Swaminathan Vasudevan Date: Thu, 11 Apr 2019 11:12:24 -0700 Subject: [PATCH] DVR: Modify DVR flows to allow ARP requests to hit ARP Responder table DVR does the ARP table update through the control plane, and does not allow any ARP requests to get out of the node. In order to address the allowed address pair VRRP IP issue with DVR, we need to add an ARP entry into the ARP Responder table for the allowed address pair IP ( which is taken care by the patch in [1]) This patch adds a rule in the br-int to redirect the packet destinated to the router to the actual router-port and also moves the arp filtering rule to the tunnel or the physical port based on the configuration. By adding the above rule it allows the ARP requests to reach the ARP Responder table and filters the ARP requests before it reaches the physical network or the tunnel. [1] https://review.opendev.org/#/c/601336/ Related-Bug: #1774459 Change-Id: I3905ea56ca0ff35bdd96c818719e6d63a3eb5a72 (cherry picked from commit 52b537ca22b2d7d81a84b2f75de577d8dffee94c) --- .zuul.yaml | 3 + .../openvswitch/agent/common/constants.py | 3 +- .../agent/openflow/native/br_dvr_process.py | 10 +-- .../agent/openflow/native/br_int.py | 67 +++++++++++++++++++ .../agent/ovs_dvr_neutron_agent.py | 41 +++++++++++- .../openvswitch/agent/ovs_neutron_agent.py | 3 +- .../openflow/native/ovs_bridge_test_base.py | 9 +-- 7 files changed, 124 insertions(+), 12 deletions(-) diff --git a/.zuul.yaml b/.zuul.yaml index 5e6bec57208..d7e1f7da6d4 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -271,6 +271,7 @@ enable_distributed_routing: True l2_population: True tunnel_types: vxlan + arp_responder: True ovs: tunnel_bridge: br-tun bridge_mappings: public:br-ex @@ -301,6 +302,7 @@ enable_distributed_routing: True l2_population: True tunnel_types: vxlan + arp_responder: True ovs: tunnel_bridge: br-tun bridge_mappings: public:br-ex @@ -348,6 +350,7 @@ enable_distributed_routing: True l2_population: True tunnel_types: vxlan,gre + arp_responder: True securitygroup: firewall_driver: iptables_hybrid $NEUTRON_L3_CONF: diff --git a/neutron/plugins/ml2/drivers/openvswitch/agent/common/constants.py b/neutron/plugins/ml2/drivers/openvswitch/agent/common/constants.py index 7ebe6566012..ac9bcf6d2f8 100644 --- a/neutron/plugins/ml2/drivers/openvswitch/agent/common/constants.py +++ b/neutron/plugins/ml2/drivers/openvswitch/agent/common/constants.py @@ -44,7 +44,8 @@ LOCAL_SWITCHING = 0 # Various tables for DVR use of integration bridge flows DVR_TO_SRC_MAC = 1 DVR_TO_SRC_MAC_VLAN = 2 - +ARP_DVR_MAC_TO_DST_MAC = 3 +ARP_DVR_MAC_TO_DST_MAC_VLAN = 4 CANARY_TABLE = 23 # Table for ARP poison/spoofing prevention rules diff --git a/neutron/plugins/ml2/drivers/openvswitch/agent/openflow/native/br_dvr_process.py b/neutron/plugins/ml2/drivers/openvswitch/agent/openflow/native/br_dvr_process.py index d4c7edfccd4..c6f35a0fb6d 100644 --- a/neutron/plugins/ml2/drivers/openvswitch/agent/openflow/native/br_dvr_process.py +++ b/neutron/plugins/ml2/drivers/openvswitch/agent/openflow/native/br_dvr_process.py @@ -18,6 +18,8 @@ from os_ken.lib.packet import ether_types from os_ken.lib.packet import icmpv6 from os_ken.lib.packet import in_proto +from neutron.plugins.ml2.drivers.openvswitch.agent.common import constants + class OVSDVRProcessMixin(object): """Common logic for br-tun and br-phys' DVR_PROCESS tables. @@ -37,7 +39,7 @@ class OVSDVRProcessMixin(object): (_dp, ofp, ofpp) = self._get_dp() match = self._dvr_process_ipv4_match(ofp, ofpp, vlan_tag=vlan_tag, gateway_ip=gateway_ip) - self.install_drop(table_id=self.dvr_process_table_id, + self.install_drop(table_id=constants.FLOOD_TO_TUN, priority=3, match=match) @@ -45,7 +47,7 @@ class OVSDVRProcessMixin(object): (_dp, ofp, ofpp) = self._get_dp() match = self._dvr_process_ipv4_match(ofp, ofpp, vlan_tag=vlan_tag, gateway_ip=gateway_ip) - self.uninstall_flows(table_id=self.dvr_process_table_id, + self.uninstall_flows(table_id=constants.FLOOD_TO_TUN, match=match) @staticmethod @@ -61,14 +63,14 @@ class OVSDVRProcessMixin(object): (_dp, ofp, ofpp) = self._get_dp() match = self._dvr_process_ipv6_match(ofp, ofpp, vlan_tag=vlan_tag, gateway_mac=gateway_mac) - self.install_drop(table_id=self.dvr_process_table_id, priority=3, + self.install_drop(table_id=constants.FLOOD_TO_TUN, priority=3, match=match) def delete_dvr_process_ipv6(self, vlan_tag, gateway_mac): (_dp, ofp, ofpp) = self._get_dp() match = self._dvr_process_ipv6_match(ofp, ofpp, vlan_tag=vlan_tag, gateway_mac=gateway_mac) - self.uninstall_flows(table_id=self.dvr_process_table_id, + self.uninstall_flows(table_id=constants.FLOOD_TO_TUN, match=match) @staticmethod diff --git a/neutron/plugins/ml2/drivers/openvswitch/agent/openflow/native/br_int.py b/neutron/plugins/ml2/drivers/openvswitch/agent/openflow/native/br_int.py index 54af6d7a850..7afa1cf1167 100644 --- a/neutron/plugins/ml2/drivers/openvswitch/agent/openflow/native/br_int.py +++ b/neutron/plugins/ml2/drivers/openvswitch/agent/openflow/native/br_int.py @@ -95,6 +95,42 @@ class OVSIntegrationBridge(ovs_bridge.OVSAgentBridge): match = self._local_vlan_match(ofp, ofpp, port, vlan_vid) self.uninstall_flows(match=match) + @staticmethod + def _arp_dvr_dst_mac_match(ofp, ofpp, vlan, dvr_mac): + # If eth_dst is equal to the dvr mac of this host, then + # flag it as matched. + return ofpp.OFPMatch(vlan_vid=vlan | ofp.OFPVID_PRESENT, + eth_dst=dvr_mac) + + @staticmethod + def _dvr_dst_mac_table_id(network_type): + if network_type == p_const.TYPE_VLAN: + return constants.ARP_DVR_MAC_TO_DST_MAC_VLAN + else: + return constants.ARP_DVR_MAC_TO_DST_MAC + + def install_dvr_dst_mac_for_arp(self, network_type, + vlan_tag, gateway_mac, dvr_mac, rtr_port): + table_id = self._dvr_dst_mac_table_id(network_type) + # Match the destination MAC with the DVR MAC + (_dp, ofp, ofpp) = self._get_dp() + match = self._arp_dvr_dst_mac_match(ofp, ofpp, vlan_tag, dvr_mac) + # Incoming packet will come with destination MAC of DVR host MAC from + # the ARP Responder. The Source MAC in this case will have the source + # MAC of the port MAC that responded from the ARP responder. + # So just remove the DVR host MAC from the 'eth_dst' and replace it + # with the gateway-mac. The packet should end up in the right the table + # for the packet to reach the router interface. + actions = [ + ofpp.OFPActionSetField(eth_dst=gateway_mac), + ofpp.OFPActionPopVlan(), + ofpp.OFPActionOutput(rtr_port, 0) + ] + self.install_apply_actions(table_id=table_id, + priority=5, + match=match, + actions=actions) + @staticmethod def _dvr_to_src_mac_match(ofp, ofpp, vlan_tag, dst_mac): return ofpp.OFPMatch(vlan_vid=vlan_tag | ofp.OFPVID_PRESENT, @@ -165,6 +201,37 @@ class OVSIntegrationBridge(ovs_bridge.OVSAgentBridge): self.uninstall_flows(table_id=constants.LOCAL_SWITCHING, in_port=port, eth_src=mac) + def delete_dvr_dst_mac_for_arp(self, network_type, + vlan_tag, gateway_mac, dvr_mac, rtr_port): + table_id = self._dvr_to_src_mac_table_id(network_type) + (_dp, ofp, ofpp) = self._get_dp() + match = self._arp_dvr_dst_mac_match(ofp, ofpp, vlan_tag, dvr_mac) + for table in table_id: + self.uninstall_flows( + strict=True, priority=5, table_id=table, match=match) + + def add_dvr_gateway_mac_arp_vlan(self, mac, port): + self.install_goto(table_id=constants.LOCAL_SWITCHING, + priority=5, + in_port=port, + eth_dst=mac, + dest_table_id=constants.ARP_DVR_MAC_TO_DST_MAC_VLAN) + + def remove_dvr_gateway_mac_arp_vlan(self, mac, port): + self.uninstall_flows(table_id=constants.LOCAL_SWITCHING, + eth_dst=mac) + + def add_dvr_gateway_mac_arp_tun(self, mac, port): + self.install_goto(table_id=constants.LOCAL_SWITCHING, + priority=5, + in_port=port, + eth_dst=mac, + dest_table_id=constants.ARP_DVR_MAC_TO_DST_MAC) + + def remove_dvr_gateway_mac_arp_tun(self, mac, port): + self.uninstall_flows(table_id=constants.LOCAL_SWITCHING, + eth_dst=mac) + @staticmethod def _arp_reply_match(ofp, ofpp, port): return ofpp.OFPMatch(in_port=port, diff --git a/neutron/plugins/ml2/drivers/openvswitch/agent/ovs_dvr_neutron_agent.py b/neutron/plugins/ml2/drivers/openvswitch/agent/ovs_dvr_neutron_agent.py index fbbb3a9174f..44b1ad04aab 100644 --- a/neutron/plugins/ml2/drivers/openvswitch/agent/ovs_dvr_neutron_agent.py +++ b/neutron/plugins/ml2/drivers/openvswitch/agent/ovs_dvr_neutron_agent.py @@ -119,7 +119,8 @@ class OVSDVRNeutronAgent(object): patch_int_ofport=constants.OFPORT_INVALID, patch_tun_ofport=constants.OFPORT_INVALID, host=None, enable_tunneling=False, - enable_distributed_routing=False): + enable_distributed_routing=False, + arp_responder_enabled=False): self.context = context self.plugin_rpc = plugin_rpc self.host = host @@ -133,6 +134,7 @@ class OVSDVRNeutronAgent(object): patch_int_ofport, patch_tun_ofport) self.reset_dvr_parameters() self.dvr_mac_address = None + self.arp_responder_enabled = arp_responder_enabled if self.enable_distributed_routing: self.get_dvr_mac_address() @@ -262,6 +264,10 @@ class OVSDVRNeutronAgent(object): phys_br.add_dvr_mac_vlan(mac=mac, port=self.phys_ofports[physical_network]) + def _add_arp_dvr_mac_for_phys_br(self, physical_network, mac): + self.int_br.add_dvr_gateway_mac_arp_vlan( + mac=mac, port=self.int_ofports[physical_network]) + def _remove_dvr_mac_for_phys_br(self, physical_network, mac): # REVISIT(yamamoto): match in_port as well? self.int_br.remove_dvr_mac_vlan(mac=mac) @@ -273,6 +279,10 @@ class OVSDVRNeutronAgent(object): self.int_br.add_dvr_mac_tun(mac=mac, port=self.patch_tun_ofport) self.tun_br.add_dvr_mac_tun(mac=mac, port=self.patch_int_ofport) + def _add_arp_dvr_mac_for_tun_br(self, mac): + self.int_br.add_dvr_gateway_mac_arp_tun( + mac=mac, port=self.patch_tun_ofport) + def _remove_dvr_mac_for_tun_br(self, mac): self.int_br.remove_dvr_mac_tun(mac=mac, port=self.patch_tun_ofport) # REVISIT(yamamoto): match in_port as well? @@ -286,6 +296,13 @@ class OVSDVRNeutronAgent(object): LOG.debug("Added DVR MAC flow for %s", mac) self.registered_dvr_macs.add(mac) + def _add_dvr_mac_for_arp(self, mac): + for physical_network in self.bridge_mappings: + self._add_arp_dvr_mac_for_phys_br(physical_network, mac) + if self.enable_tunneling: + self._add_arp_dvr_mac_for_tun_br(mac) + LOG.debug("Added ARP DVR MAC flow for %s", mac) + def _remove_dvr_mac(self, mac): for physical_network in self.bridge_mappings: self._remove_dvr_mac_for_phys_br(physical_network, mac) @@ -301,6 +318,8 @@ class OVSDVRNeutronAgent(object): c_mac = netaddr.EUI(mac['mac_address'], dialect=netaddr.mac_unix_expanded) if c_mac == self.dvr_mac_address: + self._add_dvr_mac_for_arp(c_mac) + LOG.debug("Added the DVR MAC rule for ARP %s", c_mac) continue self._add_dvr_mac(c_mac) @@ -406,6 +425,15 @@ class OVSDVRNeutronAgent(object): gateway_mac=subnet_info['gateway_mac'], dst_mac=comp_ovsport.get_mac(), dst_port=comp_ovsport.get_ofport()) + # Add the following flow rule only when ARP RESPONDER is + # enabled + if self.arp_responder_enabled: + self.int_br.install_dvr_dst_mac_for_arp( + lvm.network_type, + vlan_tag=lvm.vlan, + gateway_mac=port.vif_mac, + dvr_mac=self.dvr_mac_address, + rtr_port=port.ofport) if lvm.network_type == n_const.TYPE_VLAN: # TODO(vivek) remove the IPv6 related flows once SNAT is not @@ -600,7 +628,16 @@ class OVSDVRNeutronAgent(object): network_type=network_type, vlan_tag=vlan_to_use, dst_mac=comp_port.get_mac()) ldm.remove_all_compute_ofports() - + # If ARP Responder enabled, remove the rule that redirects + # the dvr_mac_address destination to the router port, since + # the router port is removed or unbound. + if self.arp_responder_enabled: + self.int_br.delete_dvr_dst_mac_for_arp( + network_type=network_type, + vlan_tag=vlan_to_use, + gateway_mac=port.vif_mac, + dvr_mac=self.dvr_mac_address, + rtr_port=port.ofport) if ldm.get_csnat_ofport() == constants.OFPORT_INVALID: # if there is no csnat port for this subnet, remove # this subnet from local_dvr_map, as no dvr (or) csnat diff --git a/neutron/plugins/ml2/drivers/openvswitch/agent/ovs_neutron_agent.py b/neutron/plugins/ml2/drivers/openvswitch/agent/ovs_neutron_agent.py index 4a86df3a21d..eb1a59bb306 100644 --- a/neutron/plugins/ml2/drivers/openvswitch/agent/ovs_neutron_agent.py +++ b/neutron/plugins/ml2/drivers/openvswitch/agent/ovs_neutron_agent.py @@ -243,7 +243,8 @@ class OVSNeutronAgent(l2population_rpc.L2populationRpcCallBackTunnelMixin, self.patch_tun_ofport, host, self.enable_tunneling, - self.enable_distributed_routing) + self.enable_distributed_routing, + self.arp_responder_enabled) if self.enable_distributed_routing: self.dvr_agent.setup_dvr_flows() diff --git a/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/openflow/native/ovs_bridge_test_base.py b/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/openflow/native/ovs_bridge_test_base.py index 20b48aac003..2b170691e71 100644 --- a/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/openflow/native/ovs_bridge_test_base.py +++ b/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/openflow/native/ovs_bridge_test_base.py @@ -17,6 +17,7 @@ import mock from oslo_utils import importutils +from neutron.plugins.ml2.drivers.openvswitch.agent.common import constants from neutron.tests.unit.plugins.ml2.drivers.openvswitch.agent \ import ovs_test_base @@ -169,7 +170,7 @@ class OVSDVRProcessTestMixin(object): arp_tpa=gateway_ip, vlan_vid=vlan_tag | ofp.OFPVID_PRESENT), priority=3, - table_id=self.dvr_process_table_id), + table_id=constants.FLOOD_TO_TUN), active_bundle=None), ] self.assertEqual(expected, self.mock.mock_calls) @@ -181,7 +182,7 @@ class OVSDVRProcessTestMixin(object): gateway_ip=gateway_ip) (dp, ofp, ofpp) = self._get_dp() expected = [ - call.uninstall_flows(table_id=self.dvr_process_table_id, + call.uninstall_flows(table_id=constants.FLOOD_TO_TUN, match=ofpp.OFPMatch( eth_type=self.ether_types.ETH_TYPE_ARP, arp_tpa=gateway_ip, @@ -206,7 +207,7 @@ class OVSDVRProcessTestMixin(object): ip_proto=self.in_proto.IPPROTO_ICMPV6, vlan_vid=vlan_tag | ofp.OFPVID_PRESENT), priority=3, - table_id=self.dvr_process_table_id), + table_id=constants.FLOOD_TO_TUN), active_bundle=None), ] self.assertEqual(expected, self.mock.mock_calls) @@ -218,7 +219,7 @@ class OVSDVRProcessTestMixin(object): gateway_mac=gateway_mac) (dp, ofp, ofpp) = self._get_dp() expected = [ - call.uninstall_flows(table_id=self.dvr_process_table_id, + call.uninstall_flows(table_id=constants.FLOOD_TO_TUN, match=ofpp.OFPMatch( eth_src=gateway_mac, eth_type=self.ether_types.ETH_TYPE_IPV6,