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=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=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_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_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:24:57:c7 actions=load:0x2->NXM_NX_REG5[],load:0x284->NXM_NX_REG6[],resubmit(,81)
table=0, priority=0 actions=NORMAL table=0, priority=0 actions=NORMAL
Following ``table 71`` implements arp spoofing protection, ip spoofing 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,reg6=0x284,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: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=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=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 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: for table in ovs_consts.OVS_FIREWALL_TABLES:
self.int_br.br.add_flow(table=table, priority=0, actions='drop') 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): def get_ofport(self, port):
port_id = port['device'] port_id = port['device']
return self.sg_port_map.ports.get(port_id) 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. If ofport is nonexistent, create and return one.
""" """
port_id = port['device'] port_id = port['device']
ovs_port = self.int_br.br.get_vif_port_by_id(port_id) ovs_port = self.get_ovs_port(port_id)
if not ovs_port:
raise exceptions.OVSFWPortNotFound(port_id=port_id)
try: try:
of_port = self.sg_port_map.ports[port_id] of_port = self.sg_port_map.ports[port_id]
except KeyError: except KeyError:
port_vlan_id = get_tag_from_other_config( port_vlan_id = self._get_port_vlan_tag(ovs_port.port_name)
self.int_br.br, ovs_port.port_name)
of_port = OFPort(port, ovs_port, port_vlan_id) of_port = OFPort(port, ovs_port, port_vlan_id)
self.sg_port_map.create_port(of_port, port) self.sg_port_map.create_port(of_port, port)
else: else:
@ -442,6 +447,7 @@ class OVSFirewallDriver(firewall.FirewallDriver):
def prepare_port_filter(self, port): def prepare_port_filter(self, port):
if not firewall.port_sec_enabled(port): if not firewall.port_sec_enabled(port):
self._initialize_egress_no_port_security(port['device'])
return return
old_of_port = self.get_ofport(port) old_of_port = self.get_ofport(port)
of_port = self.get_or_create_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): if not firewall.port_sec_enabled(port):
self.remove_port_filter(port) self.remove_port_filter(port)
self._initialize_egress_no_port_security(port['device'])
return return
elif not self.is_port_managed(port): elif not self.is_port_managed(port):
self._remove_egress_no_port_security(port['device'])
self.prepare_port_filter(port) self.prepare_port_filter(port)
return return
old_of_port = self.get_ofport(port) old_of_port = self.get_ofport(port)
@ -519,6 +527,15 @@ class OVSFirewallDriver(firewall.FirewallDriver):
self.conj_ip_manager.sg_removed(sg_id) self.conj_ip_manager.sg_removed(sg_id)
self.sg_port_map.delete_sg(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): def filter_defer_apply_on(self):
self._deferred = True self._deferred = True
@ -559,6 +576,7 @@ class OVSFirewallDriver(firewall.FirewallDriver):
table=ovs_consts.TRANSIENT_TABLE, table=ovs_consts.TRANSIENT_TABLE,
priority=90, priority=90,
dl_dst=port.mac, dl_dst=port.mac,
dl_vlan='0x%x' % port.vlan_tag,
actions='set_field:{:d}->reg{:d},' actions='set_field:{:d}->reg{:d},'
'set_field:{:d}->reg{:d},' 'set_field:{:d}->reg{:d},'
'strip_vlan,resubmit(,{:d})'.format( 'strip_vlan,resubmit(,{:d})'.format(
@ -585,6 +603,50 @@ class OVSFirewallDriver(firewall.FirewallDriver):
actions='normal' 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): def _initialize_egress(self, port):
"""Identify egress traffic and send it to egress base""" """Identify egress traffic and send it to egress base"""
self._initialize_egress_ipv6_icmp(port) self._initialize_egress_ipv6_icmp(port)
@ -695,6 +757,7 @@ class OVSFirewallDriver(firewall.FirewallDriver):
table=ovs_consts.ACCEPT_OR_INGRESS_TABLE, table=ovs_consts.ACCEPT_OR_INGRESS_TABLE,
priority=100, priority=100,
dl_dst=port.mac, dl_dst=port.mac,
reg_net=port.vlan_tag,
actions='set_field:{:d}->reg{:d},resubmit(,{:d})'.format( actions='set_field:{:d}->reg{:d},resubmit(,{:d})'.format(
port.ofport, port.ofport,
ovsfw_consts.REG_PORT, ovsfw_consts.REG_PORT,
@ -935,14 +998,15 @@ class OVSFirewallDriver(firewall.FirewallDriver):
self._strict_delete_flow( self._strict_delete_flow(
priority=90, priority=90,
table=ovs_consts.TRANSIENT_TABLE, table=ovs_consts.TRANSIENT_TABLE,
dl_dst=port.mac) dl_dst=port.mac,
dl_vlan=port.vlan_tag)
self._strict_delete_flow( self._strict_delete_flow(
priority=100, priority=100,
table=ovs_consts.TRANSIENT_TABLE, table=ovs_consts.TRANSIENT_TABLE,
in_port=port.ofport) in_port=port.ofport)
self._delete_flows(reg_port=port.ofport) self._delete_flows(reg_port=port.ofport)
self._delete_flows(table=ovs_consts.ACCEPT_OR_INGRESS_TABLE, 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( def delete_flows_for_ip_addresses(
self, ip_addresses, direction, ethertype, vlan_tag): 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.agent import firewall
from neutron.common import constants as n_consts from neutron.common import constants as n_consts
from neutron.common import utils as common_utils 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 ( from neutron.plugins.ml2.drivers.openvswitch.agent.openflow.ovs_ofctl import (
br_int) br_int)
from neutron.tests.common import machine_fixtures 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 " "At least one packet got reply from %s namespace to %s "
"address." % self._get_namespace_and_address(direction))) "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): class OVSBaseConnectionTester(ConnectionTester):
@ -419,9 +427,47 @@ class OVSConnectionTester(OVSBaseConnectionTester):
def set_vm_tag(self, tag): def set_vm_tag(self, tag):
self.set_tag(self._vm.port.name, self.bridge, tag) self.set_tag(self._vm.port.name, self.bridge, tag)
self._vm.port.vlan_tag = tag
def set_peer_tag(self, tag): def set_peer_tag(self, tag):
self.set_tag(self._peer.port.name, self.bridge, 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): class OVSTrunkConnectionTester(OVSBaseConnectionTester):

View File

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

View File

@ -111,6 +111,8 @@ class TestSecurityGroupsSameNetwork(BaseSecurityGroupsSameNetworkTest):
5. a remote security group member addition works, and 5. a remote security group member addition works, and
6. an established connection stops by deleting a SG rule. 6. an established connection stops by deleting a SG rule.
7. test other protocol functionality by using SCTP protocol 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] index_to_sg = [0, 0, 1]
if self.firewall_driver == 'iptables_hybrid': if self.firewall_driver == 'iptables_hybrid':
@ -272,3 +274,100 @@ class TestSecurityGroupsSameNetwork(BaseSecurityGroupsSameNetworkTest):
self.assert_connection( self.assert_connection(
vms[1].namespace, vms[0].namespace, vms[0].ip, 3366, vms[1].namespace, vms[0].namespace, vms[0].ip, 3366,
net_helpers.NetcatTester.SCTP) 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 # FIXME(jlibosva): We should consider to call prepare_port_filter with
# deferred bridge depending on its performance # deferred bridge depending on its performance
self.firewall.prepare_port_filter(self.src_port_desc) 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): def initialize_iptables(self):
cfg.CONF.set_override('enable_ipset', self.enable_ipset, cfg.CONF.set_override('enable_ipset', self.enable_ipset,
@ -542,6 +544,8 @@ class FirewallTestCase(BaseFirewallTestCase):
self.assertEqual(packets_received, 0) self.assertEqual(packets_received, 0)
def test_remote_security_groups(self): def test_remote_security_groups(self):
self.tester.set_peer_port_as_vm_port()
remote_sg_id = 'remote_sg_id' remote_sg_id = 'remote_sg_id'
peer_port_desc = self._create_port_description( peer_port_desc = self._create_port_description(
self.tester.peer_port_id, self.tester.peer_port_id,

View File

@ -476,6 +476,7 @@ class TestOVSFirewallDriver(base.BaseTestCase):
self.port_ofport, TESTING_VLAN_TAG, self.port_ofport, TESTING_VLAN_TAG,
ovs_consts.BASE_INGRESS_TABLE), ovs_consts.BASE_INGRESS_TABLE),
dl_dst=self.port_mac, dl_dst=self.port_mac,
dl_vlan='0x%x' % TESTING_VLAN_TAG,
priority=90, priority=90,
table=ovs_consts.TRANSIENT_TABLE) table=ovs_consts.TRANSIENT_TABLE)
filter_rule = mock.call( filter_rule = mock.call(
@ -498,8 +499,10 @@ class TestOVSFirewallDriver(base.BaseTestCase):
'security_groups': [1], 'security_groups': [1],
'port_security_enabled': False} 'port_security_enabled': False}
self._prepare_security_group() self._prepare_security_group()
self.firewall.prepare_port_filter(port_dict) with mock.patch.object(
self.assertFalse(self.mock_bridge.br.add_flow.called) self.firewall, 'initialize_port_flows') as m_init_flows:
self.firewall.prepare_port_filter(port_dict)
self.assertFalse(m_init_flows.called)
def test_prepare_port_filter_initialized_port(self): def test_prepare_port_filter_initialized_port(self):
port_dict = {'device': 'port-id', port_dict = {'device': 'port-id',
@ -602,3 +605,51 @@ class TestOVSFirewallDriver(base.BaseTestCase):
self.firewall._cleanup_stale_sg() self.firewall._cleanup_stale_sg()
sg_removed_mock.assert_called_once_with(1) sg_removed_mock.assert_called_once_with(1)
delete_sg_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)