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
This commit is contained in:
Swaminathan Vasudevan 2019-04-11 11:12:24 -07:00 committed by Slawek Kaplonski
parent e7629fc1c5
commit 52b537ca22
7 changed files with 124 additions and 12 deletions

View File

@ -284,6 +284,7 @@
enable_distributed_routing: True enable_distributed_routing: True
l2_population: True l2_population: True
tunnel_types: vxlan tunnel_types: vxlan
arp_responder: True
ovs: ovs:
tunnel_bridge: br-tun tunnel_bridge: br-tun
bridge_mappings: public:br-ex bridge_mappings: public:br-ex
@ -316,6 +317,7 @@
enable_distributed_routing: True enable_distributed_routing: True
l2_population: True l2_population: True
tunnel_types: vxlan tunnel_types: vxlan
arp_responder: True
ovs: ovs:
tunnel_bridge: br-tun tunnel_bridge: br-tun
bridge_mappings: public:br-ex bridge_mappings: public:br-ex
@ -363,6 +365,7 @@
enable_distributed_routing: True enable_distributed_routing: True
l2_population: True l2_population: True
tunnel_types: vxlan,gre tunnel_types: vxlan,gre
arp_responder: True
securitygroup: securitygroup:
firewall_driver: iptables_hybrid firewall_driver: iptables_hybrid
$NEUTRON_L3_CONF: $NEUTRON_L3_CONF:

View File

@ -44,7 +44,8 @@ LOCAL_SWITCHING = 0
# Various tables for DVR use of integration bridge flows # Various tables for DVR use of integration bridge flows
DVR_TO_SRC_MAC = 1 DVR_TO_SRC_MAC = 1
DVR_TO_SRC_MAC_VLAN = 2 DVR_TO_SRC_MAC_VLAN = 2
ARP_DVR_MAC_TO_DST_MAC = 3
ARP_DVR_MAC_TO_DST_MAC_VLAN = 4
CANARY_TABLE = 23 CANARY_TABLE = 23
# Table for ARP poison/spoofing prevention rules # Table for ARP poison/spoofing prevention rules

View File

@ -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 icmpv6
from os_ken.lib.packet import in_proto from os_ken.lib.packet import in_proto
from neutron.plugins.ml2.drivers.openvswitch.agent.common import constants
class OVSDVRProcessMixin(object): class OVSDVRProcessMixin(object):
"""Common logic for br-tun and br-phys' DVR_PROCESS tables. """Common logic for br-tun and br-phys' DVR_PROCESS tables.
@ -37,7 +39,7 @@ class OVSDVRProcessMixin(object):
(_dp, ofp, ofpp) = self._get_dp() (_dp, ofp, ofpp) = self._get_dp()
match = self._dvr_process_ipv4_match(ofp, ofpp, vlan_tag=vlan_tag, match = self._dvr_process_ipv4_match(ofp, ofpp, vlan_tag=vlan_tag,
gateway_ip=gateway_ip) 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, priority=3,
match=match) match=match)
@ -45,7 +47,7 @@ class OVSDVRProcessMixin(object):
(_dp, ofp, ofpp) = self._get_dp() (_dp, ofp, ofpp) = self._get_dp()
match = self._dvr_process_ipv4_match(ofp, ofpp, vlan_tag=vlan_tag, match = self._dvr_process_ipv4_match(ofp, ofpp, vlan_tag=vlan_tag,
gateway_ip=gateway_ip) 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) match=match)
@staticmethod @staticmethod
@ -61,14 +63,14 @@ class OVSDVRProcessMixin(object):
(_dp, ofp, ofpp) = self._get_dp() (_dp, ofp, ofpp) = self._get_dp()
match = self._dvr_process_ipv6_match(ofp, ofpp, vlan_tag=vlan_tag, match = self._dvr_process_ipv6_match(ofp, ofpp, vlan_tag=vlan_tag,
gateway_mac=gateway_mac) 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) match=match)
def delete_dvr_process_ipv6(self, vlan_tag, gateway_mac): def delete_dvr_process_ipv6(self, vlan_tag, gateway_mac):
(_dp, ofp, ofpp) = self._get_dp() (_dp, ofp, ofpp) = self._get_dp()
match = self._dvr_process_ipv6_match(ofp, ofpp, vlan_tag=vlan_tag, match = self._dvr_process_ipv6_match(ofp, ofpp, vlan_tag=vlan_tag,
gateway_mac=gateway_mac) 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) match=match)
@staticmethod @staticmethod

View File

@ -95,6 +95,42 @@ class OVSIntegrationBridge(ovs_bridge.OVSAgentBridge):
match = self._local_vlan_match(ofp, ofpp, port, vlan_vid) match = self._local_vlan_match(ofp, ofpp, port, vlan_vid)
self.uninstall_flows(match=match) 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 @staticmethod
def _dvr_to_src_mac_match(ofp, ofpp, vlan_tag, dst_mac): def _dvr_to_src_mac_match(ofp, ofpp, vlan_tag, dst_mac):
return ofpp.OFPMatch(vlan_vid=vlan_tag | ofp.OFPVID_PRESENT, 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, self.uninstall_flows(table_id=constants.LOCAL_SWITCHING,
in_port=port, eth_src=mac) 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 @staticmethod
def _arp_reply_match(ofp, ofpp, port): def _arp_reply_match(ofp, ofpp, port):
return ofpp.OFPMatch(in_port=port, return ofpp.OFPMatch(in_port=port,

View File

@ -119,7 +119,8 @@ class OVSDVRNeutronAgent(object):
patch_int_ofport=constants.OFPORT_INVALID, patch_int_ofport=constants.OFPORT_INVALID,
patch_tun_ofport=constants.OFPORT_INVALID, patch_tun_ofport=constants.OFPORT_INVALID,
host=None, enable_tunneling=False, host=None, enable_tunneling=False,
enable_distributed_routing=False): enable_distributed_routing=False,
arp_responder_enabled=False):
self.context = context self.context = context
self.plugin_rpc = plugin_rpc self.plugin_rpc = plugin_rpc
self.host = host self.host = host
@ -133,6 +134,7 @@ class OVSDVRNeutronAgent(object):
patch_int_ofport, patch_tun_ofport) patch_int_ofport, patch_tun_ofport)
self.reset_dvr_parameters() self.reset_dvr_parameters()
self.dvr_mac_address = None self.dvr_mac_address = None
self.arp_responder_enabled = arp_responder_enabled
if self.enable_distributed_routing: if self.enable_distributed_routing:
self.get_dvr_mac_address() self.get_dvr_mac_address()
@ -262,6 +264,10 @@ class OVSDVRNeutronAgent(object):
phys_br.add_dvr_mac_vlan(mac=mac, phys_br.add_dvr_mac_vlan(mac=mac,
port=self.phys_ofports[physical_network]) 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): def _remove_dvr_mac_for_phys_br(self, physical_network, mac):
# REVISIT(yamamoto): match in_port as well? # REVISIT(yamamoto): match in_port as well?
self.int_br.remove_dvr_mac_vlan(mac=mac) 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.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) 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): def _remove_dvr_mac_for_tun_br(self, mac):
self.int_br.remove_dvr_mac_tun(mac=mac, port=self.patch_tun_ofport) self.int_br.remove_dvr_mac_tun(mac=mac, port=self.patch_tun_ofport)
# REVISIT(yamamoto): match in_port as well? # REVISIT(yamamoto): match in_port as well?
@ -286,6 +296,13 @@ class OVSDVRNeutronAgent(object):
LOG.debug("Added DVR MAC flow for %s", mac) LOG.debug("Added DVR MAC flow for %s", mac)
self.registered_dvr_macs.add(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): def _remove_dvr_mac(self, mac):
for physical_network in self.bridge_mappings: for physical_network in self.bridge_mappings:
self._remove_dvr_mac_for_phys_br(physical_network, mac) self._remove_dvr_mac_for_phys_br(physical_network, mac)
@ -301,6 +318,8 @@ class OVSDVRNeutronAgent(object):
c_mac = netaddr.EUI(mac['mac_address'], c_mac = netaddr.EUI(mac['mac_address'],
dialect=netaddr.mac_unix_expanded) dialect=netaddr.mac_unix_expanded)
if c_mac == self.dvr_mac_address: 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 continue
self._add_dvr_mac(c_mac) self._add_dvr_mac(c_mac)
@ -406,6 +425,15 @@ class OVSDVRNeutronAgent(object):
gateway_mac=subnet_info['gateway_mac'], gateway_mac=subnet_info['gateway_mac'],
dst_mac=comp_ovsport.get_mac(), dst_mac=comp_ovsport.get_mac(),
dst_port=comp_ovsport.get_ofport()) 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: if lvm.network_type == n_const.TYPE_VLAN:
# TODO(vivek) remove the IPv6 related flows once SNAT is not # TODO(vivek) remove the IPv6 related flows once SNAT is not
@ -601,7 +629,16 @@ class OVSDVRNeutronAgent(object):
network_type=network_type, network_type=network_type,
vlan_tag=vlan_to_use, dst_mac=comp_port.get_mac()) vlan_tag=vlan_to_use, dst_mac=comp_port.get_mac())
ldm.remove_all_compute_ofports() 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 ldm.get_csnat_ofport() == constants.OFPORT_INVALID:
# if there is no csnat port for this subnet, remove # if there is no csnat port for this subnet, remove
# this subnet from local_dvr_map, as no dvr (or) csnat # this subnet from local_dvr_map, as no dvr (or) csnat

View File

@ -240,7 +240,8 @@ class OVSNeutronAgent(l2population_rpc.L2populationRpcCallBackTunnelMixin,
self.patch_tun_ofport, self.patch_tun_ofport,
host, host,
self.enable_tunneling, self.enable_tunneling,
self.enable_distributed_routing) self.enable_distributed_routing,
self.arp_responder_enabled)
if self.enable_distributed_routing: if self.enable_distributed_routing:
self.dvr_agent.setup_dvr_flows() self.dvr_agent.setup_dvr_flows()

View File

@ -17,6 +17,7 @@
import mock import mock
from oslo_utils import importutils from oslo_utils import importutils
from neutron.plugins.ml2.drivers.openvswitch.agent.common import constants
from neutron.tests.unit.plugins.ml2.drivers.openvswitch.agent \ from neutron.tests.unit.plugins.ml2.drivers.openvswitch.agent \
import ovs_test_base import ovs_test_base
@ -169,7 +170,7 @@ class OVSDVRProcessTestMixin(object):
arp_tpa=gateway_ip, arp_tpa=gateway_ip,
vlan_vid=vlan_tag | ofp.OFPVID_PRESENT), vlan_vid=vlan_tag | ofp.OFPVID_PRESENT),
priority=3, priority=3,
table_id=self.dvr_process_table_id), table_id=constants.FLOOD_TO_TUN),
active_bundle=None), active_bundle=None),
] ]
self.assertEqual(expected, self.mock.mock_calls) self.assertEqual(expected, self.mock.mock_calls)
@ -181,7 +182,7 @@ class OVSDVRProcessTestMixin(object):
gateway_ip=gateway_ip) gateway_ip=gateway_ip)
(dp, ofp, ofpp) = self._get_dp() (dp, ofp, ofpp) = self._get_dp()
expected = [ expected = [
call.uninstall_flows(table_id=self.dvr_process_table_id, call.uninstall_flows(table_id=constants.FLOOD_TO_TUN,
match=ofpp.OFPMatch( match=ofpp.OFPMatch(
eth_type=self.ether_types.ETH_TYPE_ARP, eth_type=self.ether_types.ETH_TYPE_ARP,
arp_tpa=gateway_ip, arp_tpa=gateway_ip,
@ -206,7 +207,7 @@ class OVSDVRProcessTestMixin(object):
ip_proto=self.in_proto.IPPROTO_ICMPV6, ip_proto=self.in_proto.IPPROTO_ICMPV6,
vlan_vid=vlan_tag | ofp.OFPVID_PRESENT), vlan_vid=vlan_tag | ofp.OFPVID_PRESENT),
priority=3, priority=3,
table_id=self.dvr_process_table_id), table_id=constants.FLOOD_TO_TUN),
active_bundle=None), active_bundle=None),
] ]
self.assertEqual(expected, self.mock.mock_calls) self.assertEqual(expected, self.mock.mock_calls)
@ -218,7 +219,7 @@ class OVSDVRProcessTestMixin(object):
gateway_mac=gateway_mac) gateway_mac=gateway_mac)
(dp, ofp, ofpp) = self._get_dp() (dp, ofp, ofpp) = self._get_dp()
expected = [ expected = [
call.uninstall_flows(table_id=self.dvr_process_table_id, call.uninstall_flows(table_id=constants.FLOOD_TO_TUN,
match=ofpp.OFPMatch( match=ofpp.OFPMatch(
eth_src=gateway_mac, eth_src=gateway_mac,
eth_type=self.ether_types.ETH_TYPE_IPV6, eth_type=self.ether_types.ETH_TYPE_IPV6,