Browse Source

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

changes/48/763348/1
Zuul 9 months ago
committed by Gerrit Code Review
parent
commit
aba8a80a25
  1. 4
      neutron/plugins/ml2/drivers/openvswitch/agent/common/constants.py
  2. 164
      neutron/plugins/ml2/drivers/openvswitch/agent/ovs_neutron_agent.py
  3. 3
      neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/openflow/native/test_br_int.py
  4. 28
      neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/test_ovs_tunnel.py

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

164
neutron/plugins/ml2/drivers/openvswitch/agent/ovs_neutron_agent.py

@ -50,6 +50,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
@ -332,6 +333,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)
@ -483,6 +486,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
@ -490,35 +510,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)
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)
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)
# Flush firewall rules after ports are put on dead VLAN to be
# more secure
@ -1049,6 +1057,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.
@ -1058,18 +1082,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)
@ -1771,6 +1788,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)
@ -1881,13 +1899,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)
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)
if (isinstance(self.sg_agent.firewall,
agent_firewall.NoopFirewallDriver) or
not agent_sg_rpc.is_firewall_enabled()):
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:
@ -1904,17 +1947,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))
@ -1942,18 +1999,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)

3
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
@ -85,7 +86,7 @@ class OVSIntegrationBridgeTest(ovs_bridge_test_base.OVSBridgeTestBase):
],
match=ofpp.OFPMatch(),
priority=3,
table_id=61),
table_id=constants.TRANSIENT_EGRESS_TABLE),
active_bundle=None),
]
self.assertEqual(expected, self.mock.mock_calls)

28
neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/test_ovs_tunnel.py

@ -281,6 +281,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.
@ -754,6 +768,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):

Loading…
Cancel
Save