Browse Source

Local mac direct flow for non-openflow firewall

When there is no openflow firewall, aka the ovs agent security group
is disabled or Noop/HybridIptable, this patch will introduce a different
ingress pipeline for bridge ports which will avoid ingress flood:
(1) table=0,  in_port=patch_bridge,dl_vlan=physical_vlan action=mod_vlan:local_vlan,goto:60 (original)
(2) table=60, in_port=patch_bridge                       action=goto:61                     (new)
(3) table=61, dl_dst=local_port_mac,dl_vlan=local_vlan,  action=strip_vlan,output:<ofport>  (changes)

And changes the local ports pipeline:
(1) table=0,  in_port=local_ofport                       action=goto:25                  (original)
(2) table=25, in_port=local_ofport,dl_src=local_port_mac action=goto:60                  (original)
(3) table=60, in_port=local_ofport,dl_src=local_port_mac action=local_vlan->reg6,goto:61 (changes)
(4) table=61, dl_dst=local_port_mac,reg6=local_vlan,     action=output:<ofport>          (changes)

Closes-Bug: #1884708
Closes-Bug: #1881070
Related-Bug: #1732067
Related-Bug: #1866445
Related-Bug: #1883321

Change-Id: Iecf9cffaf02616342f1727ad7db85545d8adbec2
(cherry picked from commit 959d8b6d73)
changes/65/759365/1
LIU Yulong 1 year ago
committed by Slawek Kaplonski
parent
commit
c06895e8e7
  1. 4
      neutron/plugins/ml2/drivers/openvswitch/agent/common/constants.py
  2. 164
      neutron/plugins/ml2/drivers/openvswitch/agent/ovs_neutron_agent.py
  3. 2
      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

@ -59,7 +59,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
@ -89,6 +90,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

@ -56,6 +56,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 xenapi_root_helper
from neutron.agent import rpc as agent_rpc
from neutron.agent import securitygroups_rpc as agent_sg_rpc
@ -374,6 +375,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)
@ -670,6 +673,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
@ -677,35 +697,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
@ -1289,6 +1297,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.
@ -1298,18 +1322,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)
@ -2013,6 +2030,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)
@ -2123,13 +2141,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:
@ -2146,17 +2189,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))
@ -2184,18 +2241,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)

2
neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/openflow/native/test_br_int.py

@ -87,7 +87,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

@ -284,6 +284,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.
@ -755,6 +769,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 TunnelTestUseVethIntercoOSKen(TunnelTestUseVethInterco,
ovs_test_base.OVSOSKenTestBase):

Loading…
Cancel
Save