Merge "Local mac direct flow for non-openflow firewall" into stable/rocky

This commit is contained in:
Zuul 2020-11-12 13:42:24 +00:00 committed by Gerrit Code Review
commit aba8a80a25
4 changed files with 145 additions and 52 deletions

View File

@ -55,7 +55,8 @@ MAC_SPOOF_TABLE = 25
# Table to decide whether further filtering is needed # Table to decide whether further filtering is needed
TRANSIENT_TABLE = 60 TRANSIENT_TABLE = 60
TRANSIENT_EGRESS_TABLE = 61 LOCAL_MAC_DIRECT = 61
TRANSIENT_EGRESS_TABLE = 62
# Tables used for ovs firewall # Tables used for ovs firewall
BASE_EGRESS_TABLE = 71 BASE_EGRESS_TABLE = 71
@ -86,6 +87,7 @@ INT_BR_ALL_TABLES = (
CANARY_TABLE, CANARY_TABLE,
ARP_SPOOF_TABLE, ARP_SPOOF_TABLE,
MAC_SPOOF_TABLE, MAC_SPOOF_TABLE,
LOCAL_MAC_DIRECT,
TRANSIENT_TABLE, TRANSIENT_TABLE,
TRANSIENT_EGRESS_TABLE, TRANSIENT_EGRESS_TABLE,
BASE_EGRESS_TABLE, BASE_EGRESS_TABLE,

View File

@ -50,6 +50,7 @@ from neutron.agent.common import polling
from neutron.agent.common import utils from neutron.agent.common import utils
from neutron.agent import firewall as agent_firewall from neutron.agent import firewall as agent_firewall
from neutron.agent.l2 import l2_agent_extensions_manager as ext_manager 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 ovsdb_monitor
from neutron.agent.linux import xenapi_root_helper from neutron.agent.linux import xenapi_root_helper
from neutron.agent import rpc as agent_rpc from neutron.agent import rpc as agent_rpc
@ -332,6 +333,8 @@ class OVSNeutronAgent(l2population_rpc.L2populationRpcCallBackTunnelMixin,
self.quitting_rpc_timeout = agent_conf.quitting_rpc_timeout self.quitting_rpc_timeout = agent_conf.quitting_rpc_timeout
self.install_ingress_direct_goto_flows()
def _parse_bridge_mappings(self, bridge_mappings): def _parse_bridge_mappings(self, bridge_mappings):
try: try:
return helpers.parse_mappings(bridge_mappings) return helpers.parse_mappings(bridge_mappings)
@ -483,6 +486,23 @@ class OVSNeutronAgent(l2population_rpc.L2populationRpcCallBackTunnelMixin,
lvm = self.vlan_manager.get(network_id) lvm = self.vlan_manager.get(network_id)
return lvm.vlan 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): def process_deleted_ports(self, port_info):
# don't try to process removed ports as deleted ports since # don't try to process removed ports as deleted ports since
# they are already gone # they are already gone
@ -490,35 +510,23 @@ class OVSNeutronAgent(l2population_rpc.L2populationRpcCallBackTunnelMixin,
self.deleted_ports -= port_info['removed'] self.deleted_ports -= port_info['removed']
deleted_ports = list(self.deleted_ports) deleted_ports = list(self.deleted_ports)
with self.int_br.deferred(full_ordered=True, self._deferred_delete_direct_flows(self.deleted_ports)
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)
if (isinstance(self.sg_agent.firewall, while self.deleted_ports:
agent_firewall.NoopFirewallDriver) or port_id = self.deleted_ports.pop()
not agent_sg_rpc.is_firewall_enabled()): port = self.int_br.get_vif_port_by_id(port_id)
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)
self._clean_network_ports(port_id) self._clean_network_ports(port_id)
self.ext_manager.delete_port(self.context, self.ext_manager.delete_port(self.context,
{"vif_port": port, {"vif_port": port,
"port_id": port_id}) "port_id": port_id})
# move to dead VLAN so deleted ports no # move to dead VLAN so deleted ports no
# longer have access to the network # longer have access to the network
if port: if port:
# don't log errors since there is a chance someone will be # don't log errors since there is a chance someone will be
# removing the port from the bridge at the same time # removing the port from the bridge at the same time
self.port_dead(port, log_errors=False) self.port_dead(port, log_errors=False)
self.port_unbound(port_id) self.port_unbound(port_id)
# Flush firewall rules after ports are put on dead VLAN to be # Flush firewall rules after ports are put on dead VLAN to be
# more secure # more secure
@ -1049,6 +1057,22 @@ class OVSNeutronAgent(l2population_rpc.L2populationRpcCallBackTunnelMixin,
else: else:
bridge.delete_arp_spoofing_protection(port=vif.ofport) 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): def port_unbound(self, vif_id, net_uuid=None):
'''Unbind port. '''Unbind port.
@ -1058,18 +1082,11 @@ class OVSNeutronAgent(l2population_rpc.L2populationRpcCallBackTunnelMixin,
:param vif_id: the id of the vif :param vif_id: the id of the vif
:param net_uuid: the net_uuid this port is associated with. :param net_uuid: the net_uuid this port is associated with.
''' '''
try: lvm, vif_port, net_uuid = self._get_port_lvm_and_vif(vif_id, net_uuid)
net_uuid = net_uuid or self.vlan_manager.get_net_uuid(vif_id) if not lvm:
except vlanmanager.VifIdNotFound:
LOG.info(
'port_unbound(): net_uuid %s not managed by VLAN manager',
net_uuid)
return return
lvm = self.vlan_manager.get(net_uuid) if vif_port and vif_id in lvm.vif_ports:
if vif_id in lvm.vif_ports:
vif_port = lvm.vif_ports[vif_id]
self.dvr_agent.unbind_port_from_dvr(vif_port, lvm) self.dvr_agent.unbind_port_from_dvr(vif_port, lvm)
lvm.vif_ports.pop(vif_id, None) lvm.vif_ports.pop(vif_id, None)
@ -1771,6 +1788,7 @@ class OVSNeutronAgent(l2population_rpc.L2populationRpcCallBackTunnelMixin,
self.conf.host) self.conf.host)
failed_devices = set(devices_down.get('failed_devices_down')) failed_devices = set(devices_down.get('failed_devices_down'))
LOG.debug("Port removal failed for %s", failed_devices) LOG.debug("Port removal failed for %s", failed_devices)
self._deferred_delete_direct_flows(devices)
for device in devices: for device in devices:
self.ext_manager.delete_port(self.context, {'port_id': device}) self.ext_manager.delete_port(self.context, {'port_id': device})
self.port_unbound(device) self.port_unbound(device)
@ -1881,13 +1899,38 @@ class OVSNeutronAgent(l2population_rpc.L2populationRpcCallBackTunnelMixin,
'elapsed': time.time() - start}) 'elapsed': time.time() - start})
return failed_devices return failed_devices
def process_install_ports_egress_flows(self, ports): @property
if not self.conf.AGENT.explicitly_egress_direct: def direct_for_non_openflow_firewall(self):
return 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, def install_ingress_direct_goto_flows(self):
agent_firewall.NoopFirewallDriver) or if self.direct_for_non_openflow_firewall:
not agent_sg_rpc.is_firewall_enabled()): 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, with self.int_br.deferred(full_ordered=True,
use_bundle=True) as int_br: use_bundle=True) as int_br:
for port in ports: for port in ports:
@ -1904,17 +1947,31 @@ class OVSNeutronAgent(l2population_rpc.L2populationRpcCallBackTunnelMixin,
lvm = self.vlan_manager.get(port_detail['network_id']) lvm = self.vlan_manager.get(port_detail['network_id'])
port = port_detail['vif_port'] port = port_detail['vif_port']
# Adding the local vlan to register 6 in case of MAC overlapping
# in different networks.
br_int.add_flow( br_int.add_flow(
table=constants.TRANSIENT_TABLE, table=constants.TRANSIENT_TABLE,
priority=9, priority=9,
in_port=port.ofport, in_port=port.ofport,
dl_src=port_detail['mac_address'], dl_src=port_detail['mac_address'],
actions='resubmit(,{:d})'.format( actions='set_field:{:d}->reg6,'
constants.TRANSIENT_EGRESS_TABLE)) 'resubmit(,{:d})'.format(
lvm.vlan,
constants.LOCAL_MAC_DIRECT))
# For packets from patch ports.
br_int.add_flow( br_int.add_flow(
table=constants.TRANSIENT_EGRESS_TABLE, table=constants.LOCAL_MAC_DIRECT,
priority=12, 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'], dl_dst=port_detail['mac_address'],
actions='output:{:d}'.format(port.ofport)) actions='output:{:d}'.format(port.ofport))
@ -1942,18 +1999,23 @@ class OVSNeutronAgent(l2population_rpc.L2populationRpcCallBackTunnelMixin,
patch_ofport)) patch_ofport))
def delete_accepted_egress_direct_flow(self, br_int, ofport, mac, vlan): 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 return
br_int.delete_flows( br_int.delete_flows(
table=constants.TRANSIENT_TABLE, table=constants.TRANSIENT_TABLE,
in_port=ofport, in_port=ofport,
dl_src=mac) dl_src=mac)
self.delete_flows( br_int.delete_flows(
table=constants.TRANSIENT_EGRESS_TABLE, 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) dl_dst=mac)
self.delete_flows( br_int.delete_flows(
table=constants.TRANSIENT_EGRESS_TABLE, table=constants.TRANSIENT_EGRESS_TABLE,
dl_src=mac, dl_src=mac,
in_port=ofport) in_port=ofport)

View File

@ -16,6 +16,7 @@
import mock import mock
from neutron.plugins.ml2.drivers.openvswitch.agent.common import constants
from neutron.tests.unit.plugins.ml2.drivers.openvswitch.agent.openflow.native \ from neutron.tests.unit.plugins.ml2.drivers.openvswitch.agent.openflow.native \
import ovs_bridge_test_base import ovs_bridge_test_base
@ -85,7 +86,7 @@ class OVSIntegrationBridgeTest(ovs_bridge_test_base.OVSBridgeTestBase):
], ],
match=ofpp.OFPMatch(), match=ofpp.OFPMatch(),
priority=3, priority=3,
table_id=61), table_id=constants.TRANSIENT_EGRESS_TABLE),
active_bundle=None), active_bundle=None),
] ]
self.assertEqual(expected, self.mock.mock_calls) self.assertEqual(expected, self.mock.mock_calls)

View File

@ -281,6 +281,20 @@ class TunnelTest(object):
self.intb_expected = [] self.intb_expected = []
self.execute_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): def _build_agent(self, **config_opts_agent):
"""Configure and initialize OVS agent. """Configure and initialize OVS agent.
@ -754,6 +768,20 @@ class TunnelTestUseVethInterco(TunnelTest):
self.execute_expected = [mock.call(['udevadm', 'settle', self.execute_expected = [mock.call(['udevadm', 'settle',
'--timeout=10'])] '--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, class TunnelTestUseVethIntercoOFCtl(TunnelTestUseVethInterco,
ovs_test_base.OVSOFCtlTestBase): ovs_test_base.OVSOFCtlTestBase):