Browse Source

Add VLAN type conntrack direct flow

For vlan type network, we add a segment match flow
to the openflow security group ingress table. Then
the packets will be recorded in conntrack table, and
the reply packets can be processed properly.

Conflicts:
    doc/source/contributor/internals/openvswitch_firewall.rst

Change-Id: Ieded0654d0ad16235ec923b822dcd842bd7735e5
Closes-Bug: #1831534
(cherry picked from commit aa58542e82)
changes/82/710182/3
LIU Yulong 2 years ago
committed by Slawek Kaplonski
parent
commit
dabb77fcbc
  1. 2
      doc/source/contributor/internals/openvswitch_firewall.rst
  2. 62
      neutron/agent/linux/openvswitch_firewall/firewall.py
  3. 101
      neutron/tests/unit/agent/linux/openvswitch_firewall/test_firewall.py
  4. 8
      releasenotes/notes/vlan-type-conntrack-direct-d3d544f8471ed4ff.yaml

2
doc/source/contributor/internals/openvswitch_firewall.rst

@ -172,6 +172,8 @@ traffic.
Egress flow is determined by switch port number and ingress flow is determined
by destination mac address. ``register 6`` contains port tag to isolate
connections into separate conntrack zones.
For VLAN networks, the physical VLAN tag will be used to act as an extra
match rule to do such identifying work as well.
::

62
neutron/agent/linux/openvswitch_firewall/firewall.py

@ -66,6 +66,22 @@ def create_reg_numbers(flow_params):
flow_params, ovsfw_consts.REG_REMOTE_GROUP, 'reg_remote_group')
def get_segmentation_id_from_other_config(bridge, port_name):
"""Return segmentation_id stored in OVSDB other_config metadata.
:param bridge: OVSBridge instance where port is.
:param port_name: Name of the port.
"""
try:
other_config = bridge.db_get_val(
'Port', port_name, 'other_config')
network_type = other_config.get('network_type')
if lib_const.TYPE_VLAN == network_type:
return int(other_config.get('segmentation_id'))
except (TypeError, ValueError):
pass
def get_tag_from_other_config(bridge, port_name):
"""Return tag stored in OVSDB other_config metadata.
@ -119,9 +135,10 @@ class SecurityGroup(object):
class OFPort(object):
def __init__(self, port_dict, ovs_port, vlan_tag):
def __init__(self, port_dict, ovs_port, vlan_tag, segment_id=None):
self.id = port_dict['device']
self.vlan_tag = vlan_tag
self.segment_id = segment_id
self.mac = ovs_port.vif_mac
self.lla_address = str(netutils.get_ipv6_addr_by_EUI64(
lib_const.IPv6_LLA_PREFIX, self.mac))
@ -516,6 +533,10 @@ class OVSFirewallDriver(firewall.FirewallDriver):
def _get_port_vlan_tag(self, port_name):
return get_tag_from_other_config(self.int_br.br, port_name)
def _get_port_segmentation_id(self, port_name):
return get_segmentation_id_from_other_config(
self.int_br.br, port_name)
def get_ofport(self, port):
port_id = port['device']
return self.sg_port_map.ports.get(port_id)
@ -531,12 +552,16 @@ class OVSFirewallDriver(firewall.FirewallDriver):
of_port = self.sg_port_map.ports[port_id]
except KeyError:
port_vlan_id = self._get_port_vlan_tag(ovs_port.port_name)
of_port = OFPort(port, ovs_port, port_vlan_id)
segment_id = self._get_port_segmentation_id(
ovs_port.port_name)
of_port = OFPort(port, ovs_port, port_vlan_id,
segment_id)
self.sg_port_map.create_port(of_port, port)
else:
if of_port.ofport != ovs_port.ofport:
self.sg_port_map.remove_port(of_port)
of_port = OFPort(port, ovs_port, of_port.vlan_tag)
of_port = OFPort(port, ovs_port, of_port.vlan_tag,
of_port.segment_id)
self.sg_port_map.create_port(of_port, port)
else:
self.sg_port_map.update_port(of_port, port)
@ -706,6 +731,33 @@ class OVSFirewallDriver(firewall.FirewallDriver):
return {id_: port.neutron_port_dict
for id_, port in self.sg_port_map.ports.items()}
def install_vlan_direct_flow(self, mac, segment_id, ofport, local_vlan):
# If the port segment_id is not None/0, the
# port's network type must be VLAN type.
if segment_id:
self._add_flow(
table=ovs_consts.TRANSIENT_TABLE,
priority=90,
dl_dst=mac,
dl_vlan='0x%x' % segment_id,
actions='set_field:{:d}->reg{:d},'
'set_field:{:d}->reg{:d},'
'strip_vlan,resubmit(,{:d})'.format(
ofport,
ovsfw_consts.REG_PORT,
# This always needs the local vlan.
local_vlan,
ovsfw_consts.REG_NET,
ovs_consts.BASE_INGRESS_TABLE)
)
def delete_vlan_direct_flow(self, mac, segment_id):
if segment_id:
self._strict_delete_flow(priority=90,
table=ovs_consts.TRANSIENT_TABLE,
dl_dst=mac,
dl_vlan=segment_id)
def initialize_port_flows(self, port):
"""Set base flows for port
@ -729,6 +781,9 @@ class OVSFirewallDriver(firewall.FirewallDriver):
# Identify ingress flows
for mac_addr in port.all_allowed_macs:
self.install_vlan_direct_flow(
mac_addr, port.segment_id, port.ofport, port.vlan_tag)
self._add_flow(
table=ovs_consts.TRANSIENT_TABLE,
priority=90,
@ -1232,6 +1287,7 @@ class OVSFirewallDriver(firewall.FirewallDriver):
table=ovs_consts.TRANSIENT_TABLE,
dl_dst=mac_addr,
dl_vlan=port.vlan_tag)
self.delete_vlan_direct_flow(mac_addr, port.segment_id)
self._delete_flows(table=ovs_consts.ACCEPT_OR_INGRESS_TABLE,
dl_dst=mac_addr, reg_net=port.vlan_tag)
self._strict_delete_flow(priority=100,

101
neutron/tests/unit/agent/linux/openvswitch_firewall/test_firewall.py

@ -32,12 +32,14 @@ from neutron.plugins.ml2.drivers.openvswitch.agent.openflow.ovs_ofctl \
from neutron.tests import base
TESTING_VLAN_TAG = 1
TESTING_SEGMENT = 1000
def create_ofport(port_dict):
ovs_port = mock.Mock(vif_mac='00:00:00:00:00:00', ofport=1,
port_name="port-name")
return ovsfw.OFPort(port_dict, ovs_port, vlan_tag=TESTING_VLAN_TAG)
return ovsfw.OFPort(port_dict, ovs_port, vlan_tag=TESTING_VLAN_TAG,
segment_id=TESTING_SEGMENT)
class TestCreateRegNumbers(base.BaseTestCase):
@ -580,6 +582,103 @@ class TestOVSFirewallDriver(base.BaseTestCase):
self.firewall.prepare_port_filter(port_dict)
self.assertFalse(m_init_flows.called)
def test_initialize_port_flows_vlan_dvr_conntrack_direct(self):
port_dict = {
'device': 'port-id',
'security_groups': [1]}
of_port = create_ofport(port_dict)
self.firewall.sg_port_map.ports[of_port.id] = of_port
port = self.firewall.get_or_create_ofport(port_dict)
self.firewall.initialize_port_flows(port)
call_args1 = {
'table': ovs_consts.TRANSIENT_TABLE,
'priority': 100,
'in_port': port.ofport,
'actions': 'set_field:{:d}->reg{:d},'
'set_field:{:d}->reg{:d},'
'resubmit(,{:d})'.format(
port.ofport,
ovsfw_consts.REG_PORT,
port.vlan_tag,
ovsfw_consts.REG_NET,
ovs_consts.BASE_EGRESS_TABLE)}
egress_flow_call = mock.call(**call_args1)
call_args2 = {
'table': ovs_consts.TRANSIENT_TABLE,
'priority': 90,
'dl_dst': port.mac,
'dl_vlan': '0x%x' % port.segment_id,
'actions': 'set_field:{:d}->reg{:d},'
'set_field:{:d}->reg{:d},'
'strip_vlan,resubmit(,{:d})'.format(
port.ofport,
ovsfw_consts.REG_PORT,
port.vlan_tag,
ovsfw_consts.REG_NET,
ovs_consts.BASE_INGRESS_TABLE)}
ingress_flow_call1 = mock.call(**call_args2)
call_args3 = {
'table': ovs_consts.TRANSIENT_TABLE,
'priority': 90,
'dl_dst': port.mac,
'dl_vlan': '0x%x' % port.vlan_tag,
'actions': 'set_field:{:d}->reg{:d},'
'set_field:{:d}->reg{:d},'
'strip_vlan,resubmit(,{:d})'.format(
port.ofport,
ovsfw_consts.REG_PORT,
port.vlan_tag,
ovsfw_consts.REG_NET,
ovs_consts.BASE_INGRESS_TABLE)}
ingress_flow_call2 = mock.call(**call_args3)
self.mock_bridge.br.add_flow.assert_has_calls(
[egress_flow_call, ingress_flow_call1, ingress_flow_call2])
def test_delete_all_port_flows(self):
port_dict = {
'device': 'port-id',
'security_groups': [1]}
of_port = create_ofport(port_dict)
self.firewall.sg_port_map.ports[of_port.id] = of_port
port = self.firewall.get_or_create_ofport(port_dict)
self.firewall.delete_all_port_flows(port)
call_args1 = {"strict": True,
"priority": 90,
"table": ovs_consts.TRANSIENT_TABLE,
"dl_dst": port.mac,
"dl_vlan": port.vlan_tag}
flow1 = mock.call(**call_args1)
call_args2 = {"strict": True,
"priority": 90,
"table": ovs_consts.TRANSIENT_TABLE,
"dl_dst": port.mac,
"dl_vlan": port.segment_id}
flow2 = mock.call(**call_args2)
call_args3 = {"table": ovs_consts.ACCEPT_OR_INGRESS_TABLE,
"dl_dst": port.mac,
"reg6": port.vlan_tag}
flow3 = mock.call(**call_args3)
call_args4 = {"in_port": port.ofport,
"strict": True,
"priority": 100,
"table": ovs_consts.TRANSIENT_TABLE}
flow4 = mock.call(**call_args4)
call_args5 = {"reg5": port.ofport}
flow5 = mock.call(**call_args5)
self.mock_bridge.br.delete_flows.assert_has_calls(
[flow1, flow2, flow3, flow4, flow5])
def test_prepare_port_filter_initialized_port(self):
port_dict = {'device': 'port-id',
'security_groups': [1]}

8
releasenotes/notes/vlan-type-conntrack-direct-d3d544f8471ed4ff.yaml

@ -0,0 +1,8 @@
---
fixes:
- |
Add a new match rule based on physical VLAN tag for OpenFlow firewall
traffic identifying mechanism to the TRANSIENT table. This fixes the
distributed router east-west traffic between VLAN type networks.
For more information, see bug
`1831534 <https://bugs.launchpad.net/neutron/+bug/1831534>`_.
Loading…
Cancel
Save