Merge "ovsfw: Fix overlapping MAC addresses on integration bridge"

This commit is contained in:
Jenkins 2017-07-21 03:19:03 +00:00 committed by Gerrit Code Review
commit fe6a43b5e3
7 changed files with 279 additions and 14 deletions

View File

@ -146,8 +146,8 @@ connections into separate conntrack zones.
table=0, priority=100,in_port=1 actions=load:0x1->NXM_NX_REG5[],load:0x284->NXM_NX_REG6[],resubmit(,71)
table=0, priority=100,in_port=2 actions=load:0x2->NXM_NX_REG5[],load:0x284->NXM_NX_REG6[],resubmit(,71)
table=0, priority=90,dl_dst=fa:16:3e:a4:22:10 actions=load:0x1->NXM_NX_REG5[],load:0x284->NXM_NX_REG6[],resubmit(,81)
table=0, priority=90,dl_dst=fa:16:3e:24:57:c7 actions=load:0x2->NXM_NX_REG5[],load:0x284->NXM_NX_REG6[],resubmit(,81)
table=0, priority=90,dl_vlan=0x284,dl_dst=fa:16:3e:a4:22:10 actions=load:0x1->NXM_NX_REG5[],load:0x284->NXM_NX_REG6[],resubmit(,81)
table=0, priority=90,dl_vlan=0x284,dl_dst=fa:16:3e:24:57:c7 actions=load:0x2->NXM_NX_REG5[],load:0x284->NXM_NX_REG6[],resubmit(,81)
table=0, priority=0 actions=NORMAL
Following ``table 71`` implements arp spoofing protection, ip spoofing
@ -264,8 +264,8 @@ remaining egress connections are sent to normal switching.
::
table=73, priority=100,dl_dst=fa:16:3e:a4:22:10 actions=load:0x1->NXM_NX_REG5[],resubmit(,81)
table=73, priority=100,dl_dst=fa:16:3e:24:57:c7 actions=load:0x2->NXM_NX_REG5[],resubmit(,81)
table=73, priority=100,reg6=0x284,dl_dst=fa:16:3e:a4:22:10 actions=load:0x1->NXM_NX_REG5[],resubmit(,81)
table=73, priority=100,reg6=0x284,dl_dst=fa:16:3e:24:57:c7 actions=load:0x2->NXM_NX_REG5[],resubmit(,81)
table=73, priority=90,ct_state=+new-est,reg5=0x1 actions=ct(commit,zone=NXM_NX_REG6[0..15]),NORMAL
table=73, priority=90,ct_state=+new-est,reg5=0x2 actions=ct(commit,zone=NXM_NX_REG6[0..15]),NORMAL
table=73, priority=80,reg5=0x1 actions=NORMAL

View File

@ -408,6 +408,15 @@ class OVSFirewallDriver(firewall.FirewallDriver):
for table in ovs_consts.OVS_FIREWALL_TABLES:
self.int_br.br.add_flow(table=table, priority=0, actions='drop')
def get_ovs_port(self, port_id):
ovs_port = self.int_br.br.get_vif_port_by_id(port_id)
if not ovs_port:
raise exceptions.OVSFWPortNotFound(port_id=port_id)
return ovs_port
def _get_port_vlan_tag(self, port_name):
return get_tag_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)
@ -418,15 +427,11 @@ class OVSFirewallDriver(firewall.FirewallDriver):
If ofport is nonexistent, create and return one.
"""
port_id = port['device']
ovs_port = self.int_br.br.get_vif_port_by_id(port_id)
if not ovs_port:
raise exceptions.OVSFWPortNotFound(port_id=port_id)
ovs_port = self.get_ovs_port(port_id)
try:
of_port = self.sg_port_map.ports[port_id]
except KeyError:
port_vlan_id = get_tag_from_other_config(
self.int_br.br, ovs_port.port_name)
port_vlan_id = self._get_port_vlan_tag(ovs_port.port_name)
of_port = OFPort(port, ovs_port, port_vlan_id)
self.sg_port_map.create_port(of_port, port)
else:
@ -442,6 +447,7 @@ class OVSFirewallDriver(firewall.FirewallDriver):
def prepare_port_filter(self, port):
if not firewall.port_sec_enabled(port):
self._initialize_egress_no_port_security(port['device'])
return
old_of_port = self.get_ofport(port)
of_port = self.get_or_create_ofport(port)
@ -462,8 +468,10 @@ class OVSFirewallDriver(firewall.FirewallDriver):
"""
if not firewall.port_sec_enabled(port):
self.remove_port_filter(port)
self._initialize_egress_no_port_security(port['device'])
return
elif not self.is_port_managed(port):
self._remove_egress_no_port_security(port['device'])
self.prepare_port_filter(port)
return
old_of_port = self.get_ofport(port)
@ -519,6 +527,15 @@ class OVSFirewallDriver(firewall.FirewallDriver):
self.conj_ip_manager.sg_removed(sg_id)
self.sg_port_map.delete_sg(sg_id)
def process_trusted_ports(self, port_ids):
"""Pass packets from these ports directly to ingress pipeline."""
for port_id in port_ids:
self._initialize_egress_no_port_security(port_id)
def remove_trusted_ports(self, port_ids):
for port_id in port_ids:
self._remove_egress_no_port_security(port_id)
def filter_defer_apply_on(self):
self._deferred = True
@ -559,6 +576,7 @@ class OVSFirewallDriver(firewall.FirewallDriver):
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(
@ -585,6 +603,50 @@ class OVSFirewallDriver(firewall.FirewallDriver):
actions='normal'
)
def _initialize_egress_no_port_security(self, port_id):
ovs_port = self.get_ovs_port(port_id)
try:
vlan_tag = self._get_port_vlan_tag(ovs_port.port_name)
except exceptions.OVSFWTagNotFound:
# It's a patch port, don't set anything
return
self._add_flow(
table=ovs_consts.TRANSIENT_TABLE,
priority=100,
in_port=ovs_port.ofport,
actions='set_field:%d->reg%d,'
'set_field:%d->reg%d,'
'resubmit(,%d)' % (
ovs_port.ofport,
ovsfw_consts.REG_PORT,
vlan_tag,
ovsfw_consts.REG_NET,
ovs_consts.ACCEPT_OR_INGRESS_TABLE)
)
self._add_flow(
table=ovs_consts.ACCEPT_OR_INGRESS_TABLE,
priority=80,
reg_port=ovs_port.ofport,
actions='normal'
)
def _remove_egress_no_port_security(self, port_id):
ovs_port = self.get_ovs_port(port_id)
try:
# Test if it's a patch port
self._get_port_vlan_tag(ovs_port.port_name)
except exceptions.OVSFWTagNotFound:
# It's a patch port, don't do anything
return
self._delete_flows(
table=ovs_consts.TRANSIENT_TABLE,
in_port=ovs_port.ofport
)
self._delete_flows(
table=ovs_consts.ACCEPT_OR_INGRESS_TABLE,
reg_port=ovs_port.ofport
)
def _initialize_egress(self, port):
"""Identify egress traffic and send it to egress base"""
self._initialize_egress_ipv6_icmp(port)
@ -695,6 +757,7 @@ class OVSFirewallDriver(firewall.FirewallDriver):
table=ovs_consts.ACCEPT_OR_INGRESS_TABLE,
priority=100,
dl_dst=port.mac,
reg_net=port.vlan_tag,
actions='set_field:{:d}->reg{:d},resubmit(,{:d})'.format(
port.ofport,
ovsfw_consts.REG_PORT,
@ -935,14 +998,15 @@ class OVSFirewallDriver(firewall.FirewallDriver):
self._strict_delete_flow(
priority=90,
table=ovs_consts.TRANSIENT_TABLE,
dl_dst=port.mac)
dl_dst=port.mac,
dl_vlan=port.vlan_tag)
self._strict_delete_flow(
priority=100,
table=ovs_consts.TRANSIENT_TABLE,
in_port=port.ofport)
self._delete_flows(reg_port=port.ofport)
self._delete_flows(table=ovs_consts.ACCEPT_OR_INGRESS_TABLE,
dl_dst=port.mac)
dl_dst=port.mac, reg_net=port.vlan_tag)
def delete_flows_for_ip_addresses(
self, ip_addresses, direction, ethertype, vlan_tag):

View File

@ -22,6 +22,8 @@ from oslo_utils import uuidutils
from neutron.agent import firewall
from neutron.common import constants as n_consts
from neutron.common import utils as common_utils
from neutron.plugins.ml2.drivers.openvswitch.agent.common import (
constants as ovs_consts)
from neutron.plugins.ml2.drivers.openvswitch.agent.openflow.ovs_ofctl import (
br_int)
from neutron.tests.common import machine_fixtures
@ -360,6 +362,12 @@ class ConnectionTester(fixtures.Fixture):
"At least one packet got reply from %s namespace to %s "
"address." % self._get_namespace_and_address(direction)))
def set_peer_port_as_patch_port(self):
pass
def set_peer_port_as_vm_port(self):
pass
class OVSBaseConnectionTester(ConnectionTester):
@ -419,9 +427,47 @@ class OVSConnectionTester(OVSBaseConnectionTester):
def set_vm_tag(self, tag):
self.set_tag(self._vm.port.name, self.bridge, tag)
self._vm.port.vlan_tag = tag
def set_peer_tag(self, tag):
self.set_tag(self._peer.port.name, self.bridge, tag)
self._peer.port.vlan_tag = tag
def set_peer_port_as_patch_port(self):
"""As packets coming from tunneling bridges are always tagged with
local VLAN tag, this flows will simulate the behavior.
"""
self.bridge.add_flow(
table=ovs_consts.LOCAL_SWITCHING,
priority=110,
vlan_tci=0,
in_port=self.bridge.get_port_ofport(self._peer.port.name),
actions='mod_vlan_vid:0x%x,'
'resubmit(,%d)' % (
self._peer.port.vlan_tag,
ovs_consts.LOCAL_SWITCHING)
)
self.bridge.add_flow(
table=ovs_consts.TRANSIENT_TABLE,
priority=4,
dl_vlan='0x%x' % self._peer.port.vlan_tag,
actions='strip_vlan,normal'
)
def set_peer_port_as_vm_port(self):
"""Remove flows simulating traffic from tunneling bridges.
This method is opposite to set_peer_port_as_patch_port().
"""
self.bridge.delete_flows(
table=ovs_consts.LOCAL_SWITCHING,
vlan_tci=0,
in_port=self.bridge.get_port_ofport(self._peer.port.name),
)
self.bridge.delete_flows(
table=ovs_consts.TRANSIENT_TABLE,
dl_vlan='0x%x' % self._peer.port.vlan_tag,
)
class OVSTrunkConnectionTester(OVSBaseConnectionTester):

View File

@ -737,6 +737,7 @@ class OVSPortFixture(PortFixture):
hybrid_plug=False):
super(OVSPortFixture, self).__init__(bridge, namespace, mac, port_id)
self.hybrid_plug = hybrid_plug
self.vlan_tag = None
def _create_bridge_fixture(self):
return OVSBridgeFixture()

View File

@ -111,6 +111,8 @@ class TestSecurityGroupsSameNetwork(BaseSecurityGroupsSameNetworkTest):
5. a remote security group member addition works, and
6. an established connection stops by deleting a SG rule.
7. test other protocol functionality by using SCTP protocol
8. test two vms with same mac on the same host in different
networks
"""
index_to_sg = [0, 0, 1]
if self.firewall_driver == 'iptables_hybrid':
@ -272,3 +274,100 @@ class TestSecurityGroupsSameNetwork(BaseSecurityGroupsSameNetworkTest):
self.assert_connection(
vms[1].namespace, vms[0].namespace, vms[0].ip, 3366,
net_helpers.NetcatTester.SCTP)
# 8. test two vms with same mac on the same host in different networks
self._test_overlapping_mac_addresses()
def _create_vm_on_host(
self, project_id, network_id, sg_id, host, mac_address=None):
if mac_address:
port = self.safe_client.create_port(
project_id, network_id, host.hostname,
security_groups=[sg_id], mac_address=mac_address)
else:
port = self.safe_client.create_port(
project_id, network_id, host.hostname,
security_groups=[sg_id])
return self.useFixture(
machine.FakeFullstackMachine(
host, network_id, project_id, self.safe_client,
neutron_port=port))
def _create_three_vms_first_has_static_mac(
self, project_id, allowed_port, subnet_cidr):
"""Create three vms.
First VM has a static mac and is placed on first host. Second VM is
placed on the first host and third VM is placed on second host.
"""
network = self.safe_client.create_network(project_id)
self.safe_client.create_subnet(
project_id, network['id'], subnet_cidr)
sg = self.safe_client.create_security_group(project_id)
self.safe_client.create_security_group_rule(
project_id, sg['id'],
direction='ingress',
ethertype=constants.IPv4,
protocol=constants.PROTO_NAME_TCP,
port_range_min=allowed_port, port_range_max=allowed_port)
vms = [self._create_vm_on_host(
project_id, network['id'], sg['id'], self.environment.hosts[0],
mac_address="fa:16:3e:de:ad:fe")]
if self.firewall_driver == 'iptables_hybrid':
# iptables lack isolation between agents, use only a single host
vms.extend([
self._create_vm_on_host(
project_id, network['id'], sg['id'],
self.environment.hosts[0])
for _ in range(2)])
else:
vms.extend([
self._create_vm_on_host(
project_id, network['id'], sg['id'], host)
for host in self.environment.hosts[:2]])
map(lambda vm: vm.block_until_boot(), vms)
return vms
def verify_connectivity_between_vms(self, src_vm, dst_vm, protocol, port):
self.assert_connection(
src_vm.namespace, dst_vm.namespace, dst_vm.ip, port,
protocol)
def verify_no_connectivity_between_vms(
self, src_vm, dst_vm, protocol, port):
self.assert_no_connection(
src_vm.namespace, dst_vm.namespace, dst_vm.ip, port, protocol)
def _test_overlapping_mac_addresses(self):
project1 = uuidutils.generate_uuid()
p1_allowed = 4444
project2 = uuidutils.generate_uuid()
p2_allowed = 4445
p1_vms = self._create_three_vms_first_has_static_mac(
project1, p1_allowed, '20.0.2.0/24')
p2_vms = self._create_three_vms_first_has_static_mac(
project2, p2_allowed, '20.0.3.0/24')
have_connectivity = [
(p1_vms[0], p1_vms[1], p1_allowed),
(p1_vms[1], p1_vms[2], p1_allowed),
(p2_vms[0], p2_vms[1], p2_allowed),
(p2_vms[1], p2_vms[2], p2_allowed),
]
for vm1, vm2, port in have_connectivity:
self.verify_connectivity_between_vms(
vm1, vm2, net_helpers.NetcatTester.TCP, port)
self.verify_connectivity_between_vms(
vm2, vm1, net_helpers.NetcatTester.TCP, port)
self.verify_no_connectivity_between_vms(
vm1, vm2, net_helpers.NetcatTester.TCP, port + 1)
self.verify_no_connectivity_between_vms(
vm2, vm1, net_helpers.NetcatTester.TCP, port + 1)

View File

@ -111,6 +111,8 @@ class BaseFirewallTestCase(base.BaseSudoTestCase):
# FIXME(jlibosva): We should consider to call prepare_port_filter with
# deferred bridge depending on its performance
self.firewall.prepare_port_filter(self.src_port_desc)
# Traffic coming from patch-port is always VLAN tagged
self.tester.set_peer_port_as_patch_port()
def initialize_iptables(self):
cfg.CONF.set_override('enable_ipset', self.enable_ipset,
@ -542,6 +544,8 @@ class FirewallTestCase(BaseFirewallTestCase):
self.assertEqual(packets_received, 0)
def test_remote_security_groups(self):
self.tester.set_peer_port_as_vm_port()
remote_sg_id = 'remote_sg_id'
peer_port_desc = self._create_port_description(
self.tester.peer_port_id,

View File

@ -476,6 +476,7 @@ class TestOVSFirewallDriver(base.BaseTestCase):
self.port_ofport, TESTING_VLAN_TAG,
ovs_consts.BASE_INGRESS_TABLE),
dl_dst=self.port_mac,
dl_vlan='0x%x' % TESTING_VLAN_TAG,
priority=90,
table=ovs_consts.TRANSIENT_TABLE)
filter_rule = mock.call(
@ -498,8 +499,10 @@ class TestOVSFirewallDriver(base.BaseTestCase):
'security_groups': [1],
'port_security_enabled': False}
self._prepare_security_group()
with mock.patch.object(
self.firewall, 'initialize_port_flows') as m_init_flows:
self.firewall.prepare_port_filter(port_dict)
self.assertFalse(self.mock_bridge.br.add_flow.called)
self.assertFalse(m_init_flows.called)
def test_prepare_port_filter_initialized_port(self):
port_dict = {'device': 'port-id',
@ -602,3 +605,51 @@ class TestOVSFirewallDriver(base.BaseTestCase):
self.firewall._cleanup_stale_sg()
sg_removed_mock.assert_called_once_with(1)
delete_sg_mock.assert_called_once_with(1)
def test_get_ovs_port(self):
ovs_port = self.firewall.get_ovs_port('port_id')
self.assertEqual(self.fake_ovs_port, ovs_port)
def test_get_ovs_port_non_existent(self):
self.mock_bridge.br.get_vif_port_by_id.return_value = None
with testtools.ExpectedException(exceptions.OVSFWPortNotFound):
self.firewall.get_ovs_port('port_id')
def test__initialize_egress_no_port_security_sends_to_egress(self):
self.mock_bridge.br.db_get_val.return_value = {'tag': TESTING_VLAN_TAG}
self.firewall._initialize_egress_no_port_security('port_id')
expected_call = mock.call(
table=ovs_consts.TRANSIENT_TABLE,
priority=100,
in_port=self.fake_ovs_port.ofport,
actions='set_field:%d->reg%d,'
'set_field:%d->reg%d,'
'resubmit(,%d)' % (
self.fake_ovs_port.ofport,
ovsfw_consts.REG_PORT,
TESTING_VLAN_TAG,
ovsfw_consts.REG_NET,
ovs_consts.ACCEPT_OR_INGRESS_TABLE)
)
calls = self.mock_bridge.br.add_flow.call_args_list
self.assertIn(expected_call, calls)
def test__initialize_egress_no_port_security_no_tag(self):
self.mock_bridge.br.db_get_val.return_value = {}
self.firewall._initialize_egress_no_port_security('port_id')
self.assertFalse(self.mock_bridge.br.add_flow.called)
def test__remove_egress_no_port_security_deletes_flow(self):
self.mock_bridge.br.db_get_val.return_value = {'tag': TESTING_VLAN_TAG}
self.firewall._remove_egress_no_port_security('port_id')
expected_call = mock.call(
table=ovs_consts.TRANSIENT_TABLE,
in_port=self.fake_ovs_port.ofport,
)
calls = self.mock_bridge.br.delete_flows.call_args_list
self.assertIn(expected_call, calls)
def test__remove_egress_no_port_security_no_tag(self):
self.mock_bridge.br.db_get_val.return_value = {}
self.firewall._remove_egress_no_port_security('port_id')
self.assertFalse(self.mock_bridge.br.delete_flows.called)