From aa58542e823d23d233524cd5639c7ec4bb757769 Mon Sep 17 00:00:00 2001 From: LIU Yulong Date: Sat, 15 Jun 2019 22:48:53 +0800 Subject: [PATCH] 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 --- .../internals/openvswitch_firewall.rst | 2 + .../linux/openvswitch_firewall/firewall.py | 62 ++++++++++- .../openvswitch_firewall/test_firewall.py | 101 +++++++++++++++++- ...ype-conntrack-direct-d3d544f8471ed4ff.yaml | 8 ++ 4 files changed, 169 insertions(+), 4 deletions(-) create mode 100644 releasenotes/notes/vlan-type-conntrack-direct-d3d544f8471ed4ff.yaml diff --git a/doc/source/contributor/internals/openvswitch_firewall.rst b/doc/source/contributor/internals/openvswitch_firewall.rst index 779df4d7143..5809e88fe3d 100644 --- a/doc/source/contributor/internals/openvswitch_firewall.rst +++ b/doc/source/contributor/internals/openvswitch_firewall.rst @@ -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. :: diff --git a/neutron/agent/linux/openvswitch_firewall/firewall.py b/neutron/agent/linux/openvswitch_firewall/firewall.py index 0775a320c80..1706e335765 100644 --- a/neutron/agent/linux/openvswitch_firewall/firewall.py +++ b/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)) @@ -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, diff --git a/neutron/tests/unit/agent/linux/openvswitch_firewall/test_firewall.py b/neutron/tests/unit/agent/linux/openvswitch_firewall/test_firewall.py index 8157ee2822d..5b08b00b728 100644 --- a/neutron/tests/unit/agent/linux/openvswitch_firewall/test_firewall.py +++ b/neutron/tests/unit/agent/linux/openvswitch_firewall/test_firewall.py @@ -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]} diff --git a/releasenotes/notes/vlan-type-conntrack-direct-d3d544f8471ed4ff.yaml b/releasenotes/notes/vlan-type-conntrack-direct-d3d544f8471ed4ff.yaml new file mode 100644 index 00000000000..ac9acf91bfd --- /dev/null +++ b/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 `_.