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,