diff --git a/neutron/plugins/ml2/drivers/openvswitch/agent/common/constants.py b/neutron/plugins/ml2/drivers/openvswitch/agent/common/constants.py index 9f5fd336cab..08a6751f02a 100644 --- a/neutron/plugins/ml2/drivers/openvswitch/agent/common/constants.py +++ b/neutron/plugins/ml2/drivers/openvswitch/agent/common/constants.py @@ -55,7 +55,8 @@ MAC_SPOOF_TABLE = 25 # Table to decide whether further filtering is needed TRANSIENT_TABLE = 60 -TRANSIENT_EGRESS_TABLE = 61 +LOCAL_MAC_DIRECT = 61 +TRANSIENT_EGRESS_TABLE = 62 # Tables used for ovs firewall BASE_EGRESS_TABLE = 71 @@ -86,6 +87,7 @@ INT_BR_ALL_TABLES = ( CANARY_TABLE, ARP_SPOOF_TABLE, MAC_SPOOF_TABLE, + LOCAL_MAC_DIRECT, TRANSIENT_TABLE, TRANSIENT_EGRESS_TABLE, BASE_EGRESS_TABLE, 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 9e990b5ac24..79e2c205a4d 100644 --- a/neutron/plugins/ml2/drivers/openvswitch/agent/ovs_neutron_agent.py +++ b/neutron/plugins/ml2/drivers/openvswitch/agent/ovs_neutron_agent.py @@ -48,6 +48,7 @@ from neutron.agent.common import polling from neutron.agent.common import utils from neutron.agent import firewall as agent_firewall from neutron.agent.l2 import l2_agent_extensions_manager as ext_manager +from neutron.agent.linux import iptables_firewall from neutron.agent.linux import ovsdb_monitor from neutron.agent.linux import xenapi_root_helper from neutron.agent import rpc as agent_rpc @@ -326,6 +327,8 @@ class OVSNeutronAgent(l2population_rpc.L2populationRpcCallBackTunnelMixin, self.quitting_rpc_timeout = agent_conf.quitting_rpc_timeout + self.install_ingress_direct_goto_flows() + def _parse_bridge_mappings(self, bridge_mappings): try: return helpers.parse_mappings(bridge_mappings) @@ -457,6 +460,23 @@ class OVSNeutronAgent(l2population_rpc.L2populationRpcCallBackTunnelMixin, lvm = self.vlan_manager.get(network_id) return lvm.vlan + def _deferred_delete_direct_flows(self, ports): + if not self.direct_for_non_openflow_firewall: + return + with self.int_br.deferred(full_ordered=True, + use_bundle=True) as int_br: + for port_id in ports: + lvm, vif_port, _net_id = self._get_port_lvm_and_vif(port_id) + try: + self.delete_accepted_egress_direct_flow( + int_br, + vif_port.ofport, + vif_port.vif_mac, + lvm.vlan) + except Exception as err: + LOG.debug("Failed to remove accepted egress flows " + "for port %s, error: %s", port_id, err) + def process_deleted_ports(self, port_info): # don't try to process removed ports as deleted ports since # they are already gone @@ -464,35 +484,23 @@ class OVSNeutronAgent(l2population_rpc.L2populationRpcCallBackTunnelMixin, self.deleted_ports -= port_info['removed'] deleted_ports = list(self.deleted_ports) - with self.int_br.deferred(full_ordered=True, - use_bundle=True) as int_br: - while self.deleted_ports: - port_id = self.deleted_ports.pop() - port = self.int_br.get_vif_port_by_id(port_id) + self._deferred_delete_direct_flows(self.deleted_ports) - if (isinstance(self.sg_agent.firewall, - agent_firewall.NoopFirewallDriver) or - not agent_sg_rpc.is_firewall_enabled()): - try: - self.delete_accepted_egress_direct_flow( - int_br, - port.ofport, - port.mac, self._get_port_local_vlan(port_id)) - except Exception as err: - LOG.debug("Failed to remove accepted egress flows " - "for port %s, error: %s", port_id, err) + while self.deleted_ports: + port_id = self.deleted_ports.pop() + port = self.int_br.get_vif_port_by_id(port_id) - self._clean_network_ports(port_id) - self.ext_manager.delete_port(self.context, - {"vif_port": port, - "port_id": port_id}) - # move to dead VLAN so deleted ports no - # longer have access to the network - if port: - # don't log errors since there is a chance someone will be - # removing the port from the bridge at the same time - self.port_dead(port, log_errors=False) - self.port_unbound(port_id) + self._clean_network_ports(port_id) + self.ext_manager.delete_port(self.context, + {"vif_port": port, + "port_id": port_id}) + # move to dead VLAN so deleted ports no + # longer have access to the network + if port: + # don't log errors since there is a chance someone will be + # removing the port from the bridge at the same time + self.port_dead(port, log_errors=False) + self.port_unbound(port_id) # Flush firewall rules after ports are put on dead VLAN to be # more secure @@ -1003,6 +1011,22 @@ class OVSNeutronAgent(l2population_rpc.L2populationRpcCallBackTunnelMixin, else: bridge.delete_arp_spoofing_protection(port=vif.ofport) + def _get_port_lvm_and_vif(self, vif_id, net_uuid=None): + try: + net_uuid = net_uuid or self.vlan_manager.get_net_uuid(vif_id) + except vlanmanager.VifIdNotFound: + LOG.info('net_uuid %s not managed by VLAN manager', + net_uuid) + return None, None, None + + lvm = self.vlan_manager.get(net_uuid) + + if vif_id in lvm.vif_ports: + vif_port = lvm.vif_ports[vif_id] + return lvm, vif_port, net_uuid + + return lvm, None, net_uuid + def port_unbound(self, vif_id, net_uuid=None): '''Unbind port. @@ -1012,18 +1036,11 @@ class OVSNeutronAgent(l2population_rpc.L2populationRpcCallBackTunnelMixin, :param vif_id: the id of the vif :param net_uuid: the net_uuid this port is associated with. ''' - try: - net_uuid = net_uuid or self.vlan_manager.get_net_uuid(vif_id) - except vlanmanager.VifIdNotFound: - LOG.info( - 'port_unbound(): net_uuid %s not managed by VLAN manager', - net_uuid) + lvm, vif_port, net_uuid = self._get_port_lvm_and_vif(vif_id, net_uuid) + if not lvm: return - lvm = self.vlan_manager.get(net_uuid) - - if vif_id in lvm.vif_ports: - vif_port = lvm.vif_ports[vif_id] + if vif_port and vif_id in lvm.vif_ports: self.dvr_agent.unbind_port_from_dvr(vif_port, lvm) lvm.vif_ports.pop(vif_id, None) @@ -1714,6 +1731,7 @@ class OVSNeutronAgent(l2population_rpc.L2populationRpcCallBackTunnelMixin, self.conf.host) failed_devices = set(devices_down.get('failed_devices_down')) LOG.debug("Port removal failed for %s", failed_devices) + self._deferred_delete_direct_flows(devices) for device in devices: self.ext_manager.delete_port(self.context, {'port_id': device}) self.port_unbound(device) @@ -1819,13 +1837,38 @@ class OVSNeutronAgent(l2population_rpc.L2populationRpcCallBackTunnelMixin, 'elapsed': time.time() - start}) return failed_devices - def process_install_ports_egress_flows(self, ports): - if not self.conf.AGENT.explicitly_egress_direct: - return + @property + def direct_for_non_openflow_firewall(self): + return ((isinstance(self.sg_agent.firewall, + agent_firewall.NoopFirewallDriver) or + isinstance( + self.sg_agent.firewall, + iptables_firewall.OVSHybridIptablesFirewallDriver) or + not agent_sg_rpc.is_firewall_enabled()) and + self.conf.AGENT.explicitly_egress_direct) - if (isinstance(self.sg_agent.firewall, - agent_firewall.NoopFirewallDriver) or - not agent_sg_rpc.is_firewall_enabled()): + def install_ingress_direct_goto_flows(self): + if self.direct_for_non_openflow_firewall: + for physical_network in self.bridge_mappings: + self.int_br.install_goto( + table_id=constants.TRANSIENT_TABLE, + dest_table_id=constants.LOCAL_MAC_DIRECT, + priority=4, # a bit higher than NORMAL + in_port=self.int_ofports[physical_network]) + + if self.enable_tunneling: + self.int_br.install_goto( + table_id=constants.TRANSIENT_TABLE, + dest_table_id=constants.LOCAL_MAC_DIRECT, + priority=4, # a bit higher than NORMAL + in_port=self.patch_tun_ofport) + + self.int_br.install_goto( + table_id=constants.LOCAL_MAC_DIRECT, + dest_table_id=constants.TRANSIENT_EGRESS_TABLE) + + def process_install_ports_egress_flows(self, ports): + if self.direct_for_non_openflow_firewall: with self.int_br.deferred(full_ordered=True, use_bundle=True) as int_br: for port in ports: @@ -1842,17 +1885,31 @@ class OVSNeutronAgent(l2population_rpc.L2populationRpcCallBackTunnelMixin, lvm = self.vlan_manager.get(port_detail['network_id']) port = port_detail['vif_port'] + # Adding the local vlan to register 6 in case of MAC overlapping + # in different networks. br_int.add_flow( table=constants.TRANSIENT_TABLE, priority=9, in_port=port.ofport, dl_src=port_detail['mac_address'], - actions='resubmit(,{:d})'.format( - constants.TRANSIENT_EGRESS_TABLE)) + actions='set_field:{:d}->reg6,' + 'resubmit(,{:d})'.format( + lvm.vlan, + constants.LOCAL_MAC_DIRECT)) + # For packets from patch ports. br_int.add_flow( - table=constants.TRANSIENT_EGRESS_TABLE, + table=constants.LOCAL_MAC_DIRECT, priority=12, + dl_vlan=lvm.vlan, + dl_dst=port_detail['mac_address'], + actions='strip_vlan,output:{:d}'.format(port.ofport)) + + # For packets from internal ports or VM ports. + br_int.add_flow( + table=constants.LOCAL_MAC_DIRECT, + priority=12, + reg6=lvm.vlan, dl_dst=port_detail['mac_address'], actions='output:{:d}'.format(port.ofport)) @@ -1880,18 +1937,23 @@ class OVSNeutronAgent(l2population_rpc.L2populationRpcCallBackTunnelMixin, patch_ofport)) def delete_accepted_egress_direct_flow(self, br_int, ofport, mac, vlan): - if not self.conf.AGENT.explicitly_egress_direct: + if not self.direct_for_non_openflow_firewall: return br_int.delete_flows( table=constants.TRANSIENT_TABLE, in_port=ofport, dl_src=mac) - self.delete_flows( - table=constants.TRANSIENT_EGRESS_TABLE, + br_int.delete_flows( + table=constants.LOCAL_MAC_DIRECT, + dl_vlan=vlan, + dl_dst=mac) + br_int.delete_flows( + table=constants.LOCAL_MAC_DIRECT, + reg6=vlan, dl_dst=mac) - self.delete_flows( + br_int.delete_flows( table=constants.TRANSIENT_EGRESS_TABLE, dl_src=mac, in_port=ofport) diff --git a/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/openflow/native/test_br_int.py b/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/openflow/native/test_br_int.py index a5aae6e46f0..f1e4cb1a916 100644 --- a/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/openflow/native/test_br_int.py +++ b/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/openflow/native/test_br_int.py @@ -16,6 +16,7 @@ import mock +from neutron.plugins.ml2.drivers.openvswitch.agent.common import constants from neutron.tests.unit.plugins.ml2.drivers.openvswitch.agent.openflow.native \ import ovs_bridge_test_base @@ -80,7 +81,7 @@ class OVSIntegrationBridgeTest(ovs_bridge_test_base.OVSBridgeTestBase): ], match=ofpp.OFPMatch(), priority=3, - table_id=61)), + table_id=constants.TRANSIENT_EGRESS_TABLE)), ] self.assertEqual(expected, self.mock.mock_calls) diff --git a/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/test_ovs_tunnel.py b/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/test_ovs_tunnel.py index 92589a7e384..e25a7c37abc 100644 --- a/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/test_ovs_tunnel.py +++ b/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/test_ovs_tunnel.py @@ -280,6 +280,20 @@ class TunnelTest(object): self.intb_expected = [] self.execute_expected = [] + self.mock_int_bridge_expected += [ + mock.call.install_goto( + dest_table_id=constants.LOCAL_MAC_DIRECT, + in_port=self.MAP_TUN_INT_OFPORT, + priority=4, table_id=constants.TRANSIENT_TABLE), + mock.call.install_goto( + dest_table_id=constants.LOCAL_MAC_DIRECT, + in_port=self.TUN_OFPORT, + priority=4, table_id=constants.TRANSIENT_TABLE), + mock.call.install_goto( + dest_table_id=constants.TRANSIENT_EGRESS_TABLE, + table_id=constants.LOCAL_MAC_DIRECT), + ] + def _build_agent(self, **config_opts_agent): """Configure and initialize OVS agent. @@ -753,6 +767,20 @@ class TunnelTestUseVethInterco(TunnelTest): self.execute_expected = [mock.call(['udevadm', 'settle', '--timeout=10'])] + self.mock_int_bridge_expected += [ + mock.call.install_goto( + dest_table_id=constants.LOCAL_MAC_DIRECT, + in_port=self.MAP_TUN_INT_OFPORT, + priority=4, table_id=constants.TRANSIENT_TABLE), + mock.call.install_goto( + dest_table_id=constants.LOCAL_MAC_DIRECT, + in_port=self.TUN_OFPORT, + priority=4, table_id=constants.TRANSIENT_TABLE), + mock.call.install_goto( + dest_table_id=constants.TRANSIENT_EGRESS_TABLE, + table_id=constants.LOCAL_MAC_DIRECT), + ] + class TunnelTestUseVethIntercoOFCtl(TunnelTestUseVethInterco, ovs_test_base.OVSOFCtlTestBase):