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.