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
This commit is contained in:
Brian Haley 2016-05-23 15:50:06 -04:00
parent de0456db7c
commit 51a697817d
10 changed files with 145 additions and 60 deletions

View File

@ -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,
}

View File

@ -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."))
]

View File

@ -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 "

View File

@ -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 <physical_network>:<bridge> "

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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.