Merge "Process ingress multicast traffic for 224.0.0.X separately"

This commit is contained in:
Zuul 2020-10-11 20:23:38 +00:00 committed by Gerrit Code Review
commit 0b4d6fe4ab
9 changed files with 197 additions and 2 deletions

View File

@ -495,6 +495,51 @@ same as in |table_72|.
migrated to a port on a different node, then the new port won't contain
conntrack information about previous traffic that happened with VIP.
Multicast traffic for addresses in 224.0.0.X
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
By default, as commented in [1]_, "packets with a destination IP (DIP) address
in the 224.0.0.X range which are not IGMP must be forwarded on all ports." That
means those packets will be forwarded to all ports regardless of any ingress
rule. Therefore those packets are processed independently. Any ingress packet
incoming from an local VM is sent to the multicast ingress table, |table_101|.
This table has one rule that sends the received packets directly to the
physical bridges, the tunnel bridges and the multicast rule processing table,
|table_102|. Port 1 (output:1) is the integration bridge to physical bridge
patch port.
::
table=60, priority=50,ip,dl_vlan=1,nw_dst=224.0.0.0/24 actions=load:0x1->NXM_NX_REG6[],strip_vlan,resubmit(,101)
table=60, priority=50,ip,dl_vlan=1,nw_dst=224.0.0.0/24 actions=load:0x2->NXM_NX_REG6[],strip_vlan,resubmit(,101)
table=101, priority=70 actions=output:1,resubmit(,102)
table=101, priority=0 actions=drop
The goal of this table is to avoid the NORMAl action processing for those
packets, not allowing OVS to forward them to all ports. Instead of this, those
packets are sent to other hosts via the physical and the tunnel bridges.
The packets comming from external sources are sent directly to |table_102|.
::
table=73, priority=95,ip,nw_dst=224.0.0.0/24 actions=resubmit(,102)
The next table will process the ingress rules for those multicast packets
according to the protocol number defined in each rule, per network (internal
VLAN used by Neutron to segment the tenant traffic). The OVS firewall class
``OVSFirewallDriver`` instance will keep a list of ports per internal VLAN and
rule. When a rule is added or updated, a OpenFlow rule will be added to this
|table_102|. This rule matches the rule protocol and outputs the packets to all
ports assigned to this rule in a specific VLAN network.
::
table=102, priority=70,ip,reg6=0x1,nw_proto=112 actions=output:11
table=102, priority=70,ip,reg6=0x2,nw_proto=122 actions=output:12
table=102, priority=0 actions=drop
OVS firewall integration points
-------------------------------
@ -568,3 +613,5 @@ switched to the OVS driver.
.. |table_92| replace:: ``table 92`` (ACCEPTED_INGRESS_TRAFFIC)
.. |table_93| replace:: ``table 93`` (DROPPED_TRAFFIC)
.. |table_94| replace:: ``table 94`` (ACCEPTED_EGRESS_TRAFFIC_NORMAL)
.. |table_101| replace:: ``table 101`` (MCAST_INGRESS_TABLE)
.. |table_102| replace:: ``table 102`` (MCAST_RULES_INGRESS_TABLE)

View File

@ -160,6 +160,11 @@ class FirewallDriver(object, metaclass=abc.ABCMeta):
def remove_trusted_ports(self, port_ids):
pass
def setup_multicast_traffic(self, phy_br_ofports, tun_br_ofports,
enable_tunneling):
"""Setup filters for multicast traffic"""
pass
class NoopFirewallDriver(FirewallDriver):
"""Noop Firewall Driver.

View File

@ -36,6 +36,7 @@ from neutron.agent.linux.openvswitch_firewall import constants as ovsfw_consts
from neutron.agent.linux.openvswitch_firewall import exceptions
from neutron.agent.linux.openvswitch_firewall import iptables
from neutron.agent.linux.openvswitch_firewall import rules
from neutron.common import _constants as n_const
from neutron.plugins.ml2.drivers.openvswitch.agent.common import constants \
as ovs_consts
@ -479,6 +480,8 @@ class OVSFirewallDriver(firewall.FirewallDriver):
self.sg_port_map = SGPortMap()
self.conj_ip_manager = ConjIPFlowManager(self)
self.sg_to_delete = set()
self.vlan_rule_ofports = collections.defaultdict(
lambda: collections.defaultdict(set))
self._update_cookie = None
self._deferred = False
self.iptables_helper = iptables.Helper(self.int_br.br)
@ -849,6 +852,31 @@ class OVSFirewallDriver(firewall.FirewallDriver):
dl_dst=mac,
vlan_tci=ovs_consts.FLAT_VLAN_TCI)
def setup_multicast_traffic(self, phy_br_ofports, tun_br_ofports,
enable_tunneling):
ofports = list(phy_br_ofports.values())
if enable_tunneling:
for network_type in ovs_consts.TUNNEL_NETWORK_TYPES:
ofports += list(tun_br_ofports[network_type].values())
actions = ''
for ofport in ofports:
actions += 'output:{:d},'.format(ofport)
actions += 'resubmit(,{:d})'.format(
ovs_consts.MCAST_RULES_INGRESS_TABLE)
self._add_flow(
table=ovs_consts.MCAST_INGRESS_TABLE,
priority=70,
actions=actions)
self._add_flow(
table=ovs_consts.ACCEPT_OR_INGRESS_TABLE,
priority=95,
dl_type=lib_const.ETHERTYPE_IP,
nw_dst=n_const.LOCAL_NETWORK_CONTROL_BLOCK,
actions='resubmit(,{:d})'.format(
ovs_consts.MCAST_RULES_INGRESS_TABLE))
def initialize_port_flows(self, port):
"""Set base flows for port
@ -891,6 +919,19 @@ class OVSFirewallDriver(firewall.FirewallDriver):
ovs_consts.BASE_INGRESS_TABLE),
)
self._add_flow(
table=ovs_consts.TRANSIENT_TABLE,
priority=50,
dl_vlan='0x%x' % port.vlan_tag,
dl_type=lib_const.ETHERTYPE_IP,
nw_dst=n_const.LOCAL_NETWORK_CONTROL_BLOCK,
actions='set_field:{:d}->reg{:d},'
'strip_vlan,resubmit(,{:d})'.format(
port.vlan_tag,
ovsfw_consts.REG_NET,
ovs_consts.MCAST_INGRESS_TABLE),
)
self._initialize_egress(port)
self._initialize_ingress(port)
@ -1441,6 +1482,21 @@ class OVSFirewallDriver(firewall.FirewallDriver):
self.conj_ip_manager.update_flows_for_vlan(port.vlan_tag)
modified_vlan_protocols = set()
for rule in self._create_rules_generator_for_port(port):
if (rule['ethertype'] != lib_const.IPv4 or
rule['direction'] != lib_const.INGRESS_DIRECTION or
not rule.get('protocol')):
continue
self.vlan_rule_ofports[port.vlan_tag][
rule['protocol']].add(port.ofport)
modified_vlan_protocols.add((port.vlan_tag, rule['protocol']))
for vlan_tag, protocol in modified_vlan_protocols:
flow = rules.create_mcast_flow_from_vlan_protocol(
vlan_tag, protocol, self.vlan_rule_ofports[vlan_tag][protocol])
self._add_flow(**flow)
def _create_rules_generator_for_port(self, port):
for sec_group in port.sec_groups:
for rule in sec_group.raw_rules:
@ -1469,6 +1525,28 @@ class OVSFirewallDriver(firewall.FirewallDriver):
in_port=port.ofport)
self._delete_flows(reg_port=port.ofport)
modified_vlan_protocols = set()
for protocol, ofports in self.vlan_rule_ofports[port.vlan_tag].items():
if port.ofport not in ofports:
continue
self.vlan_rule_ofports[port.vlan_tag][protocol].discard(
port.ofport)
modified_vlan_protocols.add((port.vlan_tag, protocol))
for vlan_tag, protocol in modified_vlan_protocols:
ofports = self.vlan_rule_ofports[vlan_tag][protocol]
if ofports:
flow = rules.create_mcast_flow_from_vlan_protocol(
vlan_tag, protocol, ofports)
self._add_flow(**flow)
else:
self._strict_delete_flow(
priority=70,
table=ovs_consts.MCAST_RULES_INGRESS_TABLE,
dl_type=lib_const.ETHERTYPE_IP,
nw_proto=protocol,
reg_net=vlan_tag)
def delete_flows_for_flow_state(
self, flow_state, addr_to_conj, direction, ethertype, vlan_tag):
# Remove rules for deleted IPs and action=conjunction(conj_id, 1/2)

View File

@ -204,6 +204,22 @@ def create_flows_from_rule_and_port(rule, port, conjunction=False):
return flows
def create_mcast_flow_from_vlan_protocol(vlan_tag, protocol, ofports):
"""Create ingress flows for multicast traffic, destination IP 224.0.0.x"""
flow = {
'table': ovs_consts.MCAST_RULES_INGRESS_TABLE,
'priority': 70,
'dl_type': n_consts.ETHERTYPE_IP,
'nw_proto': protocol,
'reg_net': vlan_tag}
flow['actions'] = ''
for ofport in ofports:
flow['actions'] += 'output:{:d},'.format(ofport)
return flow
def populate_flow_common(direction, flow_template, port):
"""Initialize common flow fields."""
if direction == n_consts.INGRESS_DIRECTION:

View File

@ -131,6 +131,12 @@ class SecurityGroupAgentRpc(object):
def init_ovs_dvr_firewall(self, dvr_agent):
dvr_agent.set_firewall(self.firewall)
@skip_if_noopfirewall_or_firewall_disabled
def setup_multicast_traffic(self, phy_br_ofports, tun_br_ofports,
enable_tunneling):
self.firewall.setup_multicast_traffic(phy_br_ofports, tun_br_ofports,
enable_tunneling)
@skip_if_noopfirewall_or_firewall_disabled
def prepare_devices_filter(self, device_ids):
if not device_ids:

View File

@ -77,3 +77,8 @@ IDPOOL_SELECT_SIZE = 100
# IP allocations being cleaned up by cascade.
AUTO_DELETE_PORT_OWNERS = [constants.DEVICE_OWNER_DHCP,
constants.DEVICE_OWNER_DISTRIBUTED]
# NOTE(ralonsoh): move to n-lib
# https://www.iana.org/assignments/multicast-addresses/
# multicast-addresses.xhtml#multicast-addresses-1
LOCAL_NETWORK_CONTROL_BLOCK = '224.0.0.0/24'

View File

@ -68,6 +68,8 @@ RULES_EGRESS_TABLE = 72
ACCEPT_OR_INGRESS_TABLE = 73
BASE_INGRESS_TABLE = 81
RULES_INGRESS_TABLE = 82
MCAST_INGRESS_TABLE = 101
MCAST_RULES_INGRESS_TABLE = 102
OVS_FIREWALL_TABLES = (
BASE_EGRESS_TABLE,
@ -75,6 +77,8 @@ OVS_FIREWALL_TABLES = (
ACCEPT_OR_INGRESS_TABLE,
BASE_INGRESS_TABLE,
RULES_INGRESS_TABLE,
MCAST_INGRESS_TABLE,
MCAST_RULES_INGRESS_TABLE,
)
# Tables for parties interacting with ovs firewall
@ -100,7 +104,10 @@ INT_BR_ALL_TABLES = (
RULES_INGRESS_TABLE,
ACCEPTED_EGRESS_TRAFFIC_TABLE,
ACCEPTED_INGRESS_TRAFFIC_TABLE,
DROPPED_TRAFFIC_TABLE)
DROPPED_TRAFFIC_TABLE,
MCAST_INGRESS_TABLE,
MCAST_RULES_INGRESS_TABLE,
)
# --- Tunnel bridge (tun_br)

View File

@ -307,6 +307,8 @@ class OVSNeutronAgent(l2population_rpc.L2populationRpcCallBackTunnelMixin,
self.sg_agent)
self.sg_agent.init_ovs_dvr_firewall(self.dvr_agent)
self.sg_agent.setup_multicast_traffic(
self.phys_ofports, self.tun_br_ofports, self.enable_tunneling)
# we default to False to provide backward compat with out of tree
# firewall drivers that expect the logic that existed on the Neutron

View File

@ -27,6 +27,7 @@ from neutron.agent.common import utils
from neutron.agent.linux.openvswitch_firewall import constants as ovsfw_consts
from neutron.agent.linux.openvswitch_firewall import exceptions
from neutron.agent.linux.openvswitch_firewall import firewall as ovsfw
from neutron.common import _constants as n_const
from neutron.conf.agent import securitygroups_rpc
from neutron.plugins.ml2.drivers.openvswitch.agent.common import constants \
as ovs_consts
@ -508,7 +509,12 @@ class TestOVSFirewallDriver(base.BaseTestCase):
mock.call(actions='drop', priority=0,
table=ovs_consts.BASE_INGRESS_TABLE),
mock.call(actions='drop', priority=0,
table=ovs_consts.RULES_INGRESS_TABLE)]
table=ovs_consts.RULES_INGRESS_TABLE),
mock.call(actions='drop', priority=0,
table=ovs_consts.MCAST_INGRESS_TABLE),
mock.call(actions='drop', priority=0,
table=ovs_consts.MCAST_RULES_INGRESS_TABLE),
]
actual_calls = self.firewall.int_br.br.add_flow.call_args_list
self.assertEqual(expected_calls, actual_calls)
@ -1046,6 +1052,29 @@ class TestOVSFirewallDriver(base.BaseTestCase):
addr_to_conj = {'addr1': {8, 16, 24}}
self._test_delete_flows_for_flow_state(addr_to_conj, False)
def test_setup_multicast_traffic(self):
phy_br_ofports = {1: 1, 2: 2}
tun_br_ofports = {constants.TYPE_GRE: {'ip': 3},
constants.TYPE_VXLAN: {'ip': 4},
constants.TYPE_GENEVE: {'ip': 5}}
actions1 = ''
for ofport in range(1, 6):
actions1 += 'output:%s,' % ofport
actions1 += 'resubmit(,%s)' % ovs_consts.MCAST_RULES_INGRESS_TABLE
actions2 = 'resubmit(,%s)' % ovs_consts.MCAST_RULES_INGRESS_TABLE
with mock.patch.object(self.firewall, '_add_flow') as mock_add_flow:
self.firewall.setup_multicast_traffic(phy_br_ofports,
tun_br_ofports, True)
calls = [mock.call(table=ovs_consts.MCAST_INGRESS_TABLE,
priority=70, actions=actions1),
mock.call(table=ovs_consts.ACCEPT_OR_INGRESS_TABLE,
priority=95, dl_type=constants.ETHERTYPE_IP,
nw_dst=n_const.LOCAL_NETWORK_CONTROL_BLOCK,
actions=actions2)]
mock_add_flow.assert_has_calls(calls)
class TestCookieContext(base.BaseTestCase):
def setUp(self):