From 7be2dc976a98e377ce05237eefaf79524904e5cd Mon Sep 17 00:00:00 2001 From: LIU Yulong Date: Wed, 11 Dec 2019 17:04:51 +0800 Subject: [PATCH] Change ARP reply MAC to router interface In some scenario, dvr router interface will try to ARP some device which is not hosted in the same host. When the ARP request send out, the ethernet source MAC will be changed to dvr_host_mac. Then thoses devices will reply ARP with the dvr_host_mac in ethernet dest MAC. So finally the dvr router interface will drop this, and the ARP get failed. This patch adds one flow for this, it will match the dest MAC, ARP op-code=2 and arp_tha address, then change the dest MAC to the right router interface's MAC address. Closes-Bug: #1913646 Related-Bug: #1859638 Change-Id: Ibc7f01450a3da026ca5c4fb667dada912cf472e3 --- .../agent/openflow/native/br_dvr_process.py | 30 +++++++++++++++++++ .../agent/openflow/native/br_int.py | 5 +++- .../agent/ovs_dvr_neutron_agent.py | 8 +++++ .../agent/test_ovs_neutron_agent.py | 12 ++++++++ 4 files changed, 54 insertions(+), 1 deletion(-) 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 c6f35a0fb6d..5182abee6c6 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 @@ -14,6 +14,7 @@ # License for the specific language governing permissions and limitations # under the License. +from os_ken.lib.packet import arp from os_ken.lib.packet import ether_types from os_ken.lib.packet import icmpv6 from os_ken.lib.packet import in_proto @@ -21,6 +22,35 @@ from os_ken.lib.packet import in_proto from neutron.plugins.ml2.drivers.openvswitch.agent.common import constants +class OVSDVRInterfaceMixin(object): + + def change_arp_destination_mac(self, target_mac_address, + orig_mac_address): + """Change destination MAC from dvr_host_mac to internal gateway MAC. + """ + (_dp, ofp, ofpp) = self._get_dp() + # TODO(liuyulong): except ARP, reconsider if we can change all IPv4 + # packet's destination MAC to internal gateway MAC based + # on the match rule nw_dst=gateway_ip. + match = ofpp.OFPMatch(eth_dst=orig_mac_address, + eth_type=ether_types.ETH_TYPE_ARP, + arp_tha=target_mac_address, + arp_op=arp.ARP_REPLY) + + # dst_mac: dvr_host_mac -> qr_dev_mac + actions = [ + ofpp.OFPActionSetField(eth_dst=target_mac_address), + ] + instructions = [ + ofpp.OFPInstructionActions(ofp.OFPIT_APPLY_ACTIONS, actions), + ofpp.OFPInstructionGotoTable(table_id=constants.TRANSIENT_TABLE)] + + self.install_instructions(table_id=constants.LOCAL_SWITCHING, + priority=99, + match=match, + instructions=instructions) + + class OVSDVRProcessMixin(object): """Common logic for br-tun and br-phys' DVR_PROCESS tables. 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 f52a3c2abad..415cfc1b2e9 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 @@ -27,6 +27,8 @@ from os_ken.lib.packet import in_proto from oslo_log import log as logging from neutron.plugins.ml2.drivers.openvswitch.agent.common import constants +from neutron.plugins.ml2.drivers.openvswitch.agent.openflow.native \ + import br_dvr_process from neutron.plugins.ml2.drivers.openvswitch.agent.openflow.native \ import ovs_bridge @@ -34,7 +36,8 @@ from neutron.plugins.ml2.drivers.openvswitch.agent.openflow.native \ LOG = logging.getLogger(__name__) -class OVSIntegrationBridge(ovs_bridge.OVSAgentBridge): +class OVSIntegrationBridge(ovs_bridge.OVSAgentBridge, + br_dvr_process.OVSDVRInterfaceMixin): """openvswitch agent br-int specific logic.""" of_tables = constants.INT_BR_ALL_TABLES 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 04f697f4882..268245a855d 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 @@ -515,6 +515,14 @@ class OVSDVRNeutronAgent(object): self.local_ports[port.vif_id] = ovsport ldm.add_dvr_ofport(port.vif_id, port.ofport) + if (ip_version == n_const.IP_VERSION_4 and + subnet_info.get('gateway_mac')): + # Change ARP reply destination MAC address from + # dvr_host_mac to gateway_mac. + self.int_br.change_arp_destination_mac( + target_mac_address=subnet_info['gateway_mac'], + orig_mac_address=self.dvr_mac_address) + def _bind_port_on_dvr_subnet(self, port, lvm, fixed_ips, device_owner): # Handle new compute port added use-case diff --git a/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/test_ovs_neutron_agent.py b/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/test_ovs_neutron_agent.py index 9a890424115..4ab67d866b6 100644 --- a/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/test_ovs_neutron_agent.py +++ b/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/test_ovs_neutron_agent.py @@ -3129,6 +3129,7 @@ class TestOvsDvrNeutronAgent(object): phys_br = mock.create_autospec(self.br_phys_cls('br-phys')) int_br.set_db_attribute.return_value = True int_br.db_get_val.return_value = {} + int_br.change_arp_destination_mac = mock.Mock() with mock.patch.object(self.agent.dvr_agent.plugin_rpc, 'get_subnet_for_dvr', return_value={'gateway_ip': gateway_ip, @@ -3176,6 +3177,11 @@ class TestOvsDvrNeutronAgent(object): ), ] + self._expected_port_bound(self._port, lvid, network_type=network_type) + if ip_version == n_const.IP_VERSION_4: + expected_on_int_br += [ + mock.call.change_arp_destination_mac( + target_mac_address=gateway_mac, + orig_mac_address=self.agent.dvr_agent.dvr_mac_address)] int_br.assert_has_calls(expected_on_int_br, any_order=True) tun_br.assert_not_called() phys_br.assert_has_calls(expected_on_phys_br) @@ -3222,6 +3228,7 @@ class TestOvsDvrNeutronAgent(object): phys_br = mock.create_autospec(self.br_phys_cls('br-phys')) int_br.set_db_attribute.return_value = True int_br.db_get_val.return_value = {} + int_br.change_arp_destination_mac = mock.Mock() with mock.patch.object(self.agent.dvr_agent.plugin_rpc, 'get_subnet_for_dvr', return_value={'gateway_ip': gateway_ip, @@ -3249,6 +3256,11 @@ class TestOvsDvrNeutronAgent(object): lvid = self.agent.vlan_manager.get(self._net_uuid).vlan expected_on_int_br = self._expected_port_bound( self._port, lvid) + if ip_version == n_const.IP_VERSION_4: + expected_on_int_br += [ + mock.call.change_arp_destination_mac( + target_mac_address=gateway_mac, + orig_mac_address=self.agent.dvr_agent.dvr_mac_address)] expected_on_tun_br = [ mock.call.provision_local_vlan( network_type=network_type,