From 51a697817da849c8f9dae9651f17cd863e170fdc Mon Sep 17 00:00:00 2001 From: Brian Haley Date: Mon, 23 May 2016 15:50:06 -0400 Subject: [PATCH] Change tunnel MTU calculation to support IPv6 The IPv6 header is twice the size of the IPv4 header, 40 vs 20 bytes, but the tunnel overhead constants are static, only accounting for an IPv4 header in all cases. In order to be correct it needs to treat the tunnel overhead different from the IP overhead at L3. This required removing the 20 byte IP overhead from the tunnel type overhead constants and creating a new option, ml2.overlay_ip_version, in order for the server to know which version will be used, since it calculates the MTU for the network. A version mis-match will now cause a tunnel sync to fail on the server. Moved all MTU tests to a common location to remove duplication. DocImpact Change-Id: Ia2546c4c71ff48b9fe2817fbad22b1fbf85f325b Closes-bug: #1584940 --- neutron/plugins/common/constants.py | 14 ++-- neutron/plugins/ml2/config.py | 6 +- .../linuxbridge/agent/common/config.py | 8 ++- .../openvswitch/agent/common/config.py | 8 ++- neutron/plugins/ml2/drivers/type_tunnel.py | 15 ++++- .../plugins/ml2/drivers/base_type_tunnel.py | 66 +++++++++++++++++++ .../plugins/ml2/drivers/test_type_geneve.py | 7 ++ .../unit/plugins/ml2/drivers/test_type_gre.py | 32 ++------- .../plugins/ml2/drivers/test_type_vxlan.py | 32 ++------- ...erlay_ip_version-ml2-e6438b570844ef5c.yaml | 17 +++++ 10 files changed, 145 insertions(+), 60 deletions(-) create mode 100644 releasenotes/notes/overlay_ip_version-ml2-e6438b570844ef5c.yaml diff --git a/neutron/plugins/common/constants.py b/neutron/plugins/common/constants.py index 4b385149343..474d23d3687 100644 --- a/neutron/plugins/common/constants.py +++ b/neutron/plugins/common/constants.py @@ -91,7 +91,13 @@ MIN_VXLAN_VNI = 1 MAX_VXLAN_VNI = 2 ** 24 - 1 VXLAN_UDP_PORT = 4789 -# Network Type MTU overhead -GENEVE_ENCAP_MIN_OVERHEAD = 50 -GRE_ENCAP_OVERHEAD = 42 -VXLAN_ENCAP_OVERHEAD = 50 +# Overlay (tunnel) protocol overhead +GENEVE_ENCAP_MIN_OVERHEAD = 30 +GRE_ENCAP_OVERHEAD = 22 +VXLAN_ENCAP_OVERHEAD = 30 + +# IP header length +IP_HEADER_LENGTH = { + 4: 20, + 6: 40, +} diff --git a/neutron/plugins/ml2/config.py b/neutron/plugins/ml2/config.py index cd4d7ac219e..eecafd7059b 100644 --- a/neutron/plugins/ml2/config.py +++ b/neutron/plugins/ml2/config.py @@ -61,7 +61,11 @@ ml2_opts = [ "will have the same type as tenant networks. Allowed " "values for external_network_type config option depend " "on the network type values configured in type_drivers " - "config option.")) + "config option.")), + cfg.IntOpt('overlay_ip_version', + default=4, + help=_("IP version of all overlay (tunnel) network endpoints. " + "Use a value of 4 for IPv4 or 6 for IPv6.")) ] diff --git a/neutron/plugins/ml2/drivers/linuxbridge/agent/common/config.py b/neutron/plugins/ml2/drivers/linuxbridge/agent/common/config.py index 0a1043cb575..204dd53a279 100644 --- a/neutron/plugins/ml2/drivers/linuxbridge/agent/common/config.py +++ b/neutron/plugins/ml2/drivers/linuxbridge/agent/common/config.py @@ -40,7 +40,13 @@ 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', help=_("Local IP address of the VXLAN endpoints.")), + cfg.IPOpt('local_ip', + help=_("IP address of local overlay (tunnel) network endpoint. " + "Use either an IPv4 or IPv6 address that resides on one " + "of the host network interfaces. The IP version of this " + "value must match the value of the 'overlay_ip_version' " + "option in the ML2 plug-in configuration file on the " + "neutron server node(s).")), 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/openvswitch/agent/common/config.py b/neutron/plugins/ml2/drivers/openvswitch/agent/common/config.py index 95897f9f282..eb8948a458f 100644 --- a/neutron/plugins/ml2/drivers/openvswitch/agent/common/config.py +++ b/neutron/plugins/ml2/drivers/openvswitch/agent/common/config.py @@ -43,8 +43,12 @@ ovs_opts = [ help=_("Peer patch port in tunnel bridge for integration " "bridge.")), cfg.IPOpt('local_ip', - help=_("Local IP address of tunnel endpoint. Can be either " - "an IPv4 or IPv6 address.")), + help=_("IP address of local overlay (tunnel) network endpoint. " + "Use either an IPv4 or IPv6 address that resides on one " + "of the host network interfaces. The IP version of this " + "value must match the value of the 'overlay_ip_version' " + "option in the ML2 plug-in configuration file on the " + "neutron server node(s).")), cfg.ListOpt('bridge_mappings', default=DEFAULT_BRIDGE_MAPPINGS, help=_("Comma-separated list of : " diff --git a/neutron/plugins/ml2/drivers/type_tunnel.py b/neutron/plugins/ml2/drivers/type_tunnel.py index 7d861b4e2c2..31abab5f16e 100644 --- a/neutron/plugins/ml2/drivers/type_tunnel.py +++ b/neutron/plugins/ml2/drivers/type_tunnel.py @@ -16,6 +16,7 @@ import abc import itertools import operator +import netaddr from neutron_lib import exceptions as exc from oslo_config import cfg from oslo_db import api as oslo_db_api @@ -28,6 +29,7 @@ from sqlalchemy import or_ from neutron._i18n import _, _LI, _LW from neutron.common import topics from neutron.db import api as db_api +from neutron.plugins.common import constants as p_const from neutron.plugins.common import utils as plugin_utils from neutron.plugins.ml2 import driver_api as api from neutron.plugins.ml2.drivers import helpers @@ -251,7 +253,9 @@ class TunnelTypeDriver(helpers.SegmentTypeDriver): mtu.append(seg_mtu) if cfg.CONF.ml2.path_mtu > 0: mtu.append(cfg.CONF.ml2.path_mtu) - return min(mtu) if mtu else 0 + version = cfg.CONF.ml2.overlay_ip_version + ip_header_length = p_const.IP_HEADER_LENGTH[version] + return min(mtu) - ip_header_length if mtu else 0 class EndpointTunnelTypeDriver(TunnelTypeDriver): @@ -324,12 +328,19 @@ class TunnelRpcCallbackMixin(object): msg = _("Tunnel IP value needed by the ML2 plugin") raise exc.InvalidInput(error_message=msg) + host = kwargs.get('host') + version = netaddr.IPAddress(tunnel_ip).version + if version != cfg.CONF.ml2.overlay_ip_version: + msg = (_("Tunnel IP version does not match ML2 " + "overlay_ip_version, host: %(host)s, tunnel_ip: %(ip)s"), + {'host': host, 'ip': tunnel_ip}) + raise exc.InvalidInput(error_message=msg) + tunnel_type = kwargs.get('tunnel_type') if not tunnel_type: msg = _("Network type value needed by the ML2 plugin") raise exc.InvalidInput(error_message=msg) - host = kwargs.get('host') driver = self._type_manager.drivers.get(tunnel_type) if driver: # The given conditional statements will verify the following diff --git a/neutron/tests/unit/plugins/ml2/drivers/base_type_tunnel.py b/neutron/tests/unit/plugins/ml2/drivers/base_type_tunnel.py index 4b1b3164358..82900258c42 100644 --- a/neutron/tests/unit/plugins/ml2/drivers/base_type_tunnel.py +++ b/neutron/tests/unit/plugins/ml2/drivers/base_type_tunnel.py @@ -20,11 +20,14 @@ import testtools from testtools import matchers from neutron.db import api as db +from neutron.plugins.common import constants as p_const +from neutron.plugins.ml2 import config from neutron.plugins.ml2 import driver_api as api from neutron.plugins.ml2.drivers import type_tunnel TUNNEL_IP_ONE = "10.10.10.10" TUNNEL_IP_TWO = "10.10.10.20" +TUNNEL_IPV6_ONE = "2001:db8:1::10" HOST_ONE = 'fake_host_one' HOST_TWO = 'fake_host_two' TUN_MIN = 100 @@ -355,6 +358,12 @@ class TunnelRpcCallbackTestMixin(object): 'host': HOST_ONE} self._test_tunnel_sync(kwargs) + def test_tunnel_sync_called_with_host_passed_ipv6(self): + config.cfg.CONF.set_override('overlay_ip_version', 6, group='ml2') + kwargs = {'tunnel_ip': TUNNEL_IPV6_ONE, 'tunnel_type': self.TYPE, + 'host': HOST_ONE} + self._test_tunnel_sync(kwargs) + def test_tunnel_sync_called_for_existing_endpoint(self): self.driver.add_endpoint(TUNNEL_IP_ONE, HOST_ONE) @@ -391,3 +400,60 @@ class TunnelRpcCallbackTestMixin(object): def test_tunnel_sync_called_without_tunnel_type(self): kwargs = {'tunnel_ip': TUNNEL_IP_ONE, 'host': None} self._test_tunnel_sync_raises(kwargs) + + def test_tunnel_sync_called_with_tunnel_overlay_mismatch(self): + config.cfg.CONF.set_override('overlay_ip_version', 6, group='ml2') + kwargs = {'tunnel_ip': TUNNEL_IP_ONE, 'tunnel_type': self.TYPE, + 'host': HOST_ONE} + self._test_tunnel_sync_raises(kwargs) + + def test_tunnel_sync_called_with_tunnel_overlay_mismatch_ipv6(self): + config.cfg.CONF.set_override('overlay_ip_version', 4, group='ml2') + kwargs = {'tunnel_ip': TUNNEL_IPV6_ONE, 'tunnel_type': self.TYPE, + 'host': HOST_ONE} + self._test_tunnel_sync_raises(kwargs) + + +class TunnelTypeMTUTestMixin(object): + + DRIVER_CLASS = None + TYPE = None + ENCAP_OVERHEAD = 0 + + def setUp(self): + super(TunnelTypeMTUTestMixin, self).setUp() + self.driver = self.DRIVER_CLASS() + + def _test_get_mtu(self, ip_version): + config.cfg.CONF.set_override('overlay_ip_version', ip_version, + group='ml2') + ip_header_length = p_const.IP_HEADER_LENGTH[ip_version] + + config.cfg.CONF.set_override('global_physnet_mtu', 1500) + config.cfg.CONF.set_override('path_mtu', 1475, group='ml2') + self.driver.physnet_mtus = {'physnet1': 1450, 'physnet2': 1400} + self.assertEqual(1475 - self.ENCAP_OVERHEAD - ip_header_length, + self.driver.get_mtu('physnet1')) + + config.cfg.CONF.set_override('global_physnet_mtu', 1450) + config.cfg.CONF.set_override('path_mtu', 1475, group='ml2') + self.driver.physnet_mtus = {'physnet1': 1400, 'physnet2': 1425} + self.assertEqual(1450 - self.ENCAP_OVERHEAD - ip_header_length, + self.driver.get_mtu('physnet1')) + + config.cfg.CONF.set_override('global_physnet_mtu', 0) + config.cfg.CONF.set_override('path_mtu', 1450, group='ml2') + self.driver.physnet_mtus = {'physnet1': 1425, 'physnet2': 1400} + self.assertEqual(1450 - self.ENCAP_OVERHEAD - ip_header_length, + self.driver.get_mtu('physnet1')) + + config.cfg.CONF.set_override('global_physnet_mtu', 0) + config.cfg.CONF.set_override('path_mtu', 0, group='ml2') + self.driver.physnet_mtus = {} + self.assertEqual(0, self.driver.get_mtu('physnet1')) + + def test_get_mtu_ipv4(self): + self._test_get_mtu(4) + + def test_get_mtu_ipv6(self): + self._test_get_mtu(6) diff --git a/neutron/tests/unit/plugins/ml2/drivers/test_type_geneve.py b/neutron/tests/unit/plugins/ml2/drivers/test_type_geneve.py index fb0ffdfc43b..17905e45a52 100644 --- a/neutron/tests/unit/plugins/ml2/drivers/test_type_geneve.py +++ b/neutron/tests/unit/plugins/ml2/drivers/test_type_geneve.py @@ -53,3 +53,10 @@ class GeneveTypeRpcCallbackTest(base_type_tunnel.TunnelRpcCallbackTestMixin, testlib_api.SqlTestCase): DRIVER_CLASS = type_geneve.GeneveTypeDriver TYPE = p_const.TYPE_GENEVE + + +class GeneveTypeTunnelMTUTest(base_type_tunnel.TunnelTypeMTUTestMixin, + testlib_api.SqlTestCase): + DRIVER_CLASS = type_geneve.GeneveTypeDriver + TYPE = p_const.TYPE_GENEVE + ENCAP_OVERHEAD = p_const.GENEVE_ENCAP_MIN_OVERHEAD diff --git a/neutron/tests/unit/plugins/ml2/drivers/test_type_gre.py b/neutron/tests/unit/plugins/ml2/drivers/test_type_gre.py index 788e13a825c..6fae8f8964a 100644 --- a/neutron/tests/unit/plugins/ml2/drivers/test_type_gre.py +++ b/neutron/tests/unit/plugins/ml2/drivers/test_type_gre.py @@ -14,7 +14,6 @@ # under the License. from neutron.plugins.common import constants as p_const -from neutron.plugins.ml2 import config from neutron.plugins.ml2.drivers import type_gre from neutron.tests.unit.plugins.ml2.drivers import base_type_tunnel from neutron.tests.unit.plugins.ml2 import test_rpc @@ -55,30 +54,6 @@ class GreTypeTest(base_type_tunnel.TunnelTypeTestMixin, elif endpoint['ip_address'] == base_type_tunnel.TUNNEL_IP_TWO: self.assertEqual(base_type_tunnel.HOST_TWO, endpoint['host']) - def test_get_mtu(self): - config.cfg.CONF.set_override('global_physnet_mtu', 1500) - config.cfg.CONF.set_override('path_mtu', 1475, group='ml2') - self.driver.physnet_mtus = {'physnet1': 1450, 'physnet2': 1400} - self.assertEqual(1475 - p_const.GRE_ENCAP_OVERHEAD, - self.driver.get_mtu('physnet1')) - - config.cfg.CONF.set_override('global_physnet_mtu', 1425) - config.cfg.CONF.set_override('path_mtu', 1475, group='ml2') - self.driver.physnet_mtus = {'physnet1': 1400, 'physnet2': 1400} - self.assertEqual(1425 - p_const.GRE_ENCAP_OVERHEAD, - self.driver.get_mtu('physnet1')) - - config.cfg.CONF.set_override('global_physnet_mtu', 0) - config.cfg.CONF.set_override('path_mtu', 1475, group='ml2') - self.driver.physnet_mtus = {'physnet1': 1450, 'physnet2': 1425} - self.assertEqual(1475 - p_const.GRE_ENCAP_OVERHEAD, - self.driver.get_mtu('physnet2')) - - config.cfg.CONF.set_override('global_physnet_mtu', 0) - config.cfg.CONF.set_override('path_mtu', 0, group='ml2') - self.driver.physnet_mtus = {} - self.assertEqual(0, self.driver.get_mtu('physnet1')) - class GreTypeMultiRangeTest(base_type_tunnel.TunnelTypeMultiRangeTestMixin, testlib_api.SqlTestCase): @@ -90,3 +65,10 @@ class GreTypeRpcCallbackTest(base_type_tunnel.TunnelRpcCallbackTestMixin, testlib_api.SqlTestCase): DRIVER_CLASS = type_gre.GreTypeDriver TYPE = p_const.TYPE_GRE + + +class GreTypeTunnelMTUTest(base_type_tunnel.TunnelTypeMTUTestMixin, + testlib_api.SqlTestCase): + DRIVER_CLASS = type_gre.GreTypeDriver + TYPE = p_const.TYPE_GRE + ENCAP_OVERHEAD = p_const.GRE_ENCAP_OVERHEAD diff --git a/neutron/tests/unit/plugins/ml2/drivers/test_type_vxlan.py b/neutron/tests/unit/plugins/ml2/drivers/test_type_vxlan.py index cae9f6e516f..70a13cf292b 100644 --- a/neutron/tests/unit/plugins/ml2/drivers/test_type_vxlan.py +++ b/neutron/tests/unit/plugins/ml2/drivers/test_type_vxlan.py @@ -14,7 +14,6 @@ # under the License. from neutron.plugins.common import constants as p_const -from neutron.plugins.ml2 import config from neutron.plugins.ml2.drivers import type_vxlan from neutron.tests.unit.plugins.ml2.drivers import base_type_tunnel from neutron.tests.unit.plugins.ml2 import test_rpc @@ -65,30 +64,6 @@ class VxlanTypeTest(base_type_tunnel.TunnelTypeTestMixin, self.assertEqual(VXLAN_UDP_PORT_TWO, endpoint['udp_port']) self.assertEqual(base_type_tunnel.HOST_TWO, endpoint['host']) - def test_get_mtu(self): - config.cfg.CONF.set_override('global_physnet_mtu', 1500) - config.cfg.CONF.set_override('path_mtu', 1475, group='ml2') - self.driver.physnet_mtus = {'physnet1': 1450, 'physnet2': 1400} - self.assertEqual(1475 - p_const.VXLAN_ENCAP_OVERHEAD, - self.driver.get_mtu('physnet1')) - - config.cfg.CONF.set_override('global_physnet_mtu', 1450) - config.cfg.CONF.set_override('path_mtu', 1475, group='ml2') - self.driver.physnet_mtus = {'physnet1': 1400, 'physnet2': 1425} - self.assertEqual(1450 - p_const.VXLAN_ENCAP_OVERHEAD, - self.driver.get_mtu('physnet1')) - - config.cfg.CONF.set_override('global_physnet_mtu', 0) - config.cfg.CONF.set_override('path_mtu', 1450, group='ml2') - self.driver.physnet_mtus = {'physnet1': 1425, 'physnet2': 1400} - self.assertEqual(1450 - p_const.VXLAN_ENCAP_OVERHEAD, - self.driver.get_mtu('physnet1')) - - config.cfg.CONF.set_override('global_physnet_mtu', 0) - config.cfg.CONF.set_override('path_mtu', 0, group='ml2') - self.driver.physnet_mtus = {} - self.assertEqual(0, self.driver.get_mtu('physnet1')) - class VxlanTypeMultiRangeTest(base_type_tunnel.TunnelTypeMultiRangeTestMixin, testlib_api.SqlTestCase): @@ -100,3 +75,10 @@ class VxlanTypeRpcCallbackTest(base_type_tunnel.TunnelRpcCallbackTestMixin, testlib_api.SqlTestCase): DRIVER_CLASS = type_vxlan.VxlanTypeDriver TYPE = p_const.TYPE_VXLAN + + +class VxlanTypeTunnelMTUTest(base_type_tunnel.TunnelTypeMTUTestMixin, + testlib_api.SqlTestCase): + DRIVER_CLASS = type_vxlan.VxlanTypeDriver + TYPE = p_const.TYPE_VXLAN + ENCAP_OVERHEAD = p_const.VXLAN_ENCAP_OVERHEAD diff --git a/releasenotes/notes/overlay_ip_version-ml2-e6438b570844ef5c.yaml b/releasenotes/notes/overlay_ip_version-ml2-e6438b570844ef5c.yaml new file mode 100644 index 00000000000..38bcc841b72 --- /dev/null +++ b/releasenotes/notes/overlay_ip_version-ml2-e6438b570844ef5c.yaml @@ -0,0 +1,17 @@ +--- +prelude: > + Properly calculate overlay (tunnel) protocol overhead for + environments using IPv4 or IPv6 endpoints. The ML2 plug-in + configuration file contains a new configuration option, + 'overlay_ip_version', in the '[ml2]' section that indicates + the IP version of all overlay network endpoints. Use '4' for + IPv4 and '6' for IPv6. Defaults to '4'. Additionally, all + layer-2 agents must use the same IP version for endpoints. +upgrade: + - Define the 'overlay_ip_version' option and value + appropriate for the environment. Only required if not + using the Default of '4'. +other: + - The value of the 'overlay_ip_version' option adds either + 20 bytes for IPv4 or 40 bytes for IPv6 to determine the total + tunnel overhead amount.