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
This commit is contained in:
LIU Yulong 2019-12-11 17:04:51 +08:00
parent 00d5f7f2ab
commit 7be2dc976a
4 changed files with 54 additions and 1 deletions

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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,