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. Change-Id: Ieded0654d0ad16235ec923b822dcd842bd7735e5 Closes-Bug: #1831534
This commit is contained in:
parent
4f6b8bb3e5
commit
aa58542e82
@ -207,6 +207,8 @@ identifying the port (for egress traffic based on the switch port number, and
|
||||
for ingress traffic based on the network id and destination MAC address);
|
||||
``register 6`` contains a value identifying the network (which is also the
|
||||
OVSDB 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.
|
||||
|
||||
::
|
||||
|
||||
|
@ -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))
|
||||
@ -517,6 +534,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)
|
||||
@ -532,12 +553,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)
|
||||
@ -695,6 +720,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
|
||||
|
||||
@ -718,6 +770,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,
|
||||
@ -1212,6 +1267,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,
|
||||
|
@ -32,12 +32,14 @@ from neutron.plugins.ml2.drivers.openvswitch.agent.openflow.native \
|
||||
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):
|
||||
@ -554,6 +556,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]}
|
||||
|
@ -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…
Reference in New Issue
Block a user