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
)
This commit is contained in:
parent
4bb046c5b8
commit
c06895e8e7
|
@ -59,7 +59,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
|
||||||
|
@ -89,6 +90,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,
|
||||||
|
|
|
@ -56,6 +56,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 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
|
||||||
from neutron.agent import securitygroups_rpc as agent_sg_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.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)
|
||||||
|
@ -670,6 +673,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
|
||||||
|
@ -677,35 +697,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
|
||||||
|
@ -1289,6 +1297,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.
|
||||||
|
|
||||||
|
@ -1298,18 +1322,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)
|
||||||
|
|
||||||
|
@ -2013,6 +2030,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)
|
||||||
|
@ -2123,13 +2141,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:
|
||||||
|
@ -2146,17 +2189,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))
|
||||||
|
|
||||||
|
@ -2184,18 +2241,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)
|
||||||
|
|
|
@ -87,7 +87,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)
|
||||||
|
|
|
@ -284,6 +284,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.
|
||||||
|
|
||||||
|
@ -755,6 +769,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 TunnelTestUseVethIntercoOSKen(TunnelTestUseVethInterco,
|
class TunnelTestUseVethIntercoOSKen(TunnelTestUseVethInterco,
|
||||||
ovs_test_base.OVSOSKenTestBase):
|
ovs_test_base.OVSOSKenTestBase):
|
||||||
|
|
Loading…
Reference in New Issue