From be9c9e3458ce61a299faebd974322f376c34c15e Mon Sep 17 00:00:00 2001 From: Dustin Lundquist Date: Wed, 30 Dec 2015 15:59:36 -0800 Subject: [PATCH] Do not prohibit VXLAN over IPv6 9fc45cee in introduced a regression prohibiting using VXLAN over IPv6. Relax restriction on local_ip, but validate that both local_ip and vxlan_group of the same address family. Move existing validation of vxlan_group into validate_vxlan_group_with_local_ip() method and refactor existing tests for that validation. Change-Id: I3d67732d2e1f3e079fee336b403744596fb7db77 Depends-On: I6440445b80637a5a9f4de052cf5ea1fbd8dcf7d1 Closes-Bug: #1531660 --- .../linuxbridge/agent/common/config.py | 3 +- .../agent/linuxbridge_neutron_agent.py | 38 ++++++++++------ .../agent/test_linuxbridge_neutron_agent.py | 45 ++++++++++++++++--- 3 files changed, 66 insertions(+), 20 deletions(-) diff --git a/neutron/plugins/ml2/drivers/linuxbridge/agent/common/config.py b/neutron/plugins/ml2/drivers/linuxbridge/agent/common/config.py index df0ef7559..b2d192e1f 100644 --- a/neutron/plugins/ml2/drivers/linuxbridge/agent/common/config.py +++ b/neutron/plugins/ml2/drivers/linuxbridge/agent/common/config.py @@ -40,8 +40,7 @@ vxlan_opts = [ "To reserve a unique group for each possible " "(24-bit) VNI, use a /8 such as 239.0.0.0/8. This " "setting must be the same on all the agents.")), - cfg.IPOpt('local_ip', version=4, - help=_("Local IP address of the VXLAN endpoints.")), + cfg.IPOpt('local_ip', help=_("Local IP address of the VXLAN endpoints.")), cfg.BoolOpt('l2_population', default=False, help=_("Extension to use alongside ml2 plugin's l2population " "mechanism driver. It enables the plugin to populate " diff --git a/neutron/plugins/ml2/drivers/linuxbridge/agent/linuxbridge_neutron_agent.py b/neutron/plugins/ml2/drivers/linuxbridge/agent/linuxbridge_neutron_agent.py index de8f51c38..ee95c2e3e 100644 --- a/neutron/plugins/ml2/drivers/linuxbridge/agent/linuxbridge_neutron_agent.py +++ b/neutron/plugins/ml2/drivers/linuxbridge/agent/linuxbridge_neutron_agent.py @@ -79,6 +79,7 @@ class LinuxBridgeManager(object): self.vxlan_mode = lconst.VXLAN_NONE if cfg.CONF.VXLAN.enable_vxlan: device = self.get_local_ip_device(self.local_ip) + self.validate_vxlan_group_with_local_ip() self.local_int = device.name self.check_vxlan_support() # Store network mapping to segments @@ -100,6 +101,26 @@ class LinuxBridgeManager(object): {'brq': bridge, 'net': physnet}) sys.exit(1) + def validate_vxlan_group_with_local_ip(self): + if not cfg.CONF.VXLAN.vxlan_group: + return + try: + ip_addr = netaddr.IPAddress(self.local_ip) + # Ensure the configured group address/range is valid and multicast + group_net = netaddr.IPNetwork(cfg.CONF.VXLAN.vxlan_group) + if not group_net.is_multicast(): + raise ValueError() + if not ip_addr.version == group_net.version: + raise ValueError() + except (netaddr.core.AddrFormatError, ValueError): + LOG.error(_LE("Invalid VXLAN Group: %(group)s, must be an address " + "or network (in CIDR notation) in a multicast " + "range of the same address family as local_ip: " + "%(ip)s"), + {'group': cfg.CONF.VXLAN.vxlan_group, + 'ip': self.local_ip}) + sys.exit(1) + def get_local_ip_device(self, local_ip): """Return the device with local_ip on the host.""" device = self.ip.get_device_by_ip(local_ip) @@ -146,19 +167,10 @@ class LinuxBridgeManager(object): "incorrect vxlan device name"), segmentation_id) def get_vxlan_group(self, segmentation_id): - try: - # Ensure the configured group address/range is valid and multicast - net = netaddr.IPNetwork(cfg.CONF.VXLAN.vxlan_group) - if not net.is_multicast(): - raise ValueError() - # Map the segmentation ID to (one of) the group address(es) - return str(net.network + - (int(segmentation_id) & int(net.hostmask))) - except (netaddr.core.AddrFormatError, ValueError): - LOG.warning(_LW("Invalid VXLAN Group: %s, must be an address " - "or network (in CIDR notation) in a multicast " - "range"), - cfg.CONF.VXLAN.vxlan_group) + net = netaddr.IPNetwork(cfg.CONF.VXLAN.vxlan_group) + # Map the segmentation ID to (one of) the group address(es) + return str(net.network + + (int(segmentation_id) & int(net.hostmask))) def get_deletable_bridges(self): bridge_list = bridge_lib.get_bridge_names() diff --git a/neutron/tests/unit/plugins/ml2/drivers/linuxbridge/agent/test_linuxbridge_neutron_agent.py b/neutron/tests/unit/plugins/ml2/drivers/linuxbridge/agent/test_linuxbridge_neutron_agent.py index 0fc3b5cd5..c738adeed 100644 --- a/neutron/tests/unit/plugins/ml2/drivers/linuxbridge/agent/test_linuxbridge_neutron_agent.py +++ b/neutron/tests/unit/plugins/ml2/drivers/linuxbridge/agent/test_linuxbridge_neutron_agent.py @@ -32,6 +32,8 @@ from neutron.plugins.ml2.drivers.linuxbridge.agent \ from neutron.tests import base LOCAL_IP = '192.168.0.33' +LOCAL_IPV6 = '2001:db8:1::33' +VXLAN_GROUPV6 = 'ff05::/120' PORT_1 = 'abcdef01-12ddssdfds-fdsfsd' DEVICE_1 = 'tapabcdef01-12' NETWORK_ID = '57653b20-ed5b-4ed0-a31d-06f84e3fd909' @@ -62,6 +64,7 @@ def get_linuxbridge_manager(bridge_mappings, interface_mappings): mock.patch.object(ip_lib, 'device_exists', return_value=True),\ mock.patch.object(linuxbridge_neutron_agent.LinuxBridgeManager, 'check_vxlan_support'): + cfg.CONF.set_override('local_ip', LOCAL_IP, 'VXLAN') return linuxbridge_neutron_agent.LinuxBridgeManager( bridge_mappings, interface_mappings) @@ -114,6 +117,7 @@ class TestLinuxBridgeAgent(base.BaseTestCase): 'neutron.agent.firewall.NoopFirewallDriver', group='SECURITYGROUP') cfg.CONF.set_default('quitting_rpc_timeout', 10, 'AGENT') + cfg.CONF.set_override('local_ip', LOCAL_IP, 'VXLAN') self.get_devices_p = mock.patch.object(ip_lib.IPWrapper, 'get_devices') self.get_devices = self.get_devices_p.start() self.get_devices.return_value = [ip_lib.IPDevice('eth77')] @@ -565,6 +569,31 @@ class TestLinuxBridgeManager(base.BaseTestCase): self.assertEqual(1, log.call_count) exit.assert_called_once_with(1) + def _test_vxlan_group_validation(self, bad_local_ip, bad_vxlan_group): + with mock.patch.object(ip_lib.IPWrapper, + 'get_device_by_ip', + return_value=FAKE_DEFAULT_DEV),\ + mock.patch.object(sys, 'exit') as exit,\ + mock.patch.object(linuxbridge_neutron_agent.LOG, + 'error') as log: + self.lbm.local_ip = bad_local_ip + cfg.CONF.set_override('vxlan_group', bad_vxlan_group, 'VXLAN') + self.lbm.validate_vxlan_group_with_local_ip() + self.assertEqual(1, log.call_count) + exit.assert_called_once_with(1) + + def test_vxlan_group_validation_with_mismatched_local_ip(self): + self._test_vxlan_group_validation(LOCAL_IP, VXLAN_GROUPV6) + + def test_vxlan_group_validation_with_unicast_group(self): + self._test_vxlan_group_validation(LOCAL_IP, '240.0.0.0') + + def test_vxlan_group_validation_with_invalid_cidr(self): + self._test_vxlan_group_validation(LOCAL_IP, '224.0.0.1/') + + def test_vxlan_group_validation_with_v6_unicast_group(self): + self._test_vxlan_group_validation(LOCAL_IPV6, '2001:db8::') + def test_get_existing_bridge_name(self): phy_net = 'physnet0' self.assertEqual('br-eth2', @@ -609,10 +638,17 @@ class TestLinuxBridgeManager(base.BaseTestCase): self.assertEqual('239.1.2.0', self.lbm.get_vxlan_group(vn_id)) vn_id = 257 self.assertEqual('239.1.2.1', self.lbm.get_vxlan_group(vn_id)) - cfg.CONF.set_override('vxlan_group', '240.0.0.0', 'VXLAN') - self.assertIsNone(self.lbm.get_vxlan_group(vn_id)) - cfg.CONF.set_override('vxlan_group', '224.0.0.1/', 'VXLAN') - self.assertIsNone(self.lbm.get_vxlan_group(vn_id)) + + def test_get_vxlan_group_with_ipv6(self): + cfg.CONF.set_override('local_ip', LOCAL_IPV6, 'VXLAN') + self.lbm.local_ip = LOCAL_IPV6 + cfg.CONF.set_override('vxlan_group', VXLAN_GROUPV6, 'VXLAN') + vn_id = p_const.MAX_VXLAN_VNI + self.assertEqual('ff05::ff', self.lbm.get_vxlan_group(vn_id)) + vn_id = 256 + self.assertEqual('ff05::', self.lbm.get_vxlan_group(vn_id)) + vn_id = 257 + self.assertEqual('ff05::1', self.lbm.get_vxlan_group(vn_id)) def test_get_deletable_bridges(self): br_list = ["br-int", "brq1", "brq2", "brq-user"] @@ -1174,7 +1210,6 @@ class TestLinuxBridgeManager(base.BaseTestCase): class TestLinuxBridgeRpcCallbacks(base.BaseTestCase): def setUp(self): - cfg.CONF.set_override('local_ip', LOCAL_IP, 'VXLAN') super(TestLinuxBridgeRpcCallbacks, self).setUp() class FakeLBAgent(object): -- 2.45.2