summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBrian Haley <haleyb.dev@gmail.com>2023-03-01 00:52:38 -0500
committerBrian Haley <haleyb.dev@gmail.com>2023-04-26 12:22:30 -0400
commit88ce859b568248a0ee2f47a5d91c1708b774d20e (patch)
tree1b8117a50dfb6a4369de8c4c3b3b4f3a611c364a
parent5cd0388eb7cac84fc1aaa425184bf4af67ed0608 (diff)
downloadneutron-88ce859b568248a0ee2f47a5d91c1708b774d20e.tar.gz
Change API to validate network MTU minimums
A network's MTU is now only valid if it is the minimum value allowed based on the IP version of the associated subnets, 68 for IPv4 and 1280 for IPv6. This minimum is now enforced in the following ways: 1) When a subnet is associated with a network, validate the MTU is large enough for the IP version. Not only would the subnet be unusable if it was allowed, but the Linux kernel can fail adding addresses and configuring network settings like the MTU. 2) When a network MTU is changed, validate the MTU is large enough for any currently associated subnets. Allowing a smaller MTU would render any existing subnets unusable. Closes-bug: #1988069 Change-Id: Ia4017a8737f9a7c63945df546c8a7243b2673ceb
-rw-r--r--doc/source/admin/config-ipv6.rst13
-rw-r--r--doc/source/admin/config-mtu.rst7
-rw-r--r--doc/source/admin/shared/deploy-selfservice-initialnetworks.txt7
-rw-r--r--neutron/common/_constants.py3
-rw-r--r--neutron/db/db_base_plugin_v2.py45
-rw-r--r--neutron/exceptions/mtu.py28
-rw-r--r--neutron/tests/unit/db/test_db_base_plugin_v2.py84
-rw-r--r--neutron/tests/unit/fake_resources.py1
-rw-r--r--releasenotes/releasenotes/notes/network_subnet_mtu_validation-c221f22efcfae927.yaml22
9 files changed, 205 insertions, 5 deletions
diff --git a/doc/source/admin/config-ipv6.rst b/doc/source/admin/config-ipv6.rst
index 65b5fb9a41..8f2395f6fb 100644
--- a/doc/source/admin/config-ipv6.rst
+++ b/doc/source/admin/config-ipv6.rst
@@ -195,8 +195,8 @@ Project network considerations
Dataplane
---------
-Both the Linux bridge and the Open vSwitch dataplane modules support
-forwarding IPv6
+All dataplane modules, including OVN, Open vSwitch and Linux bridge,
+support forwarding IPv6
packets amongst the guests and router ports. Similar to IPv4, there is no
special configuration or setup required to enable the dataplane to properly
forward packets from the source to the destination using IPv6. Note that these
@@ -204,6 +204,15 @@ dataplanes will forward Link-local Address (LLA) packets between hosts on the
same network just fine without any participation or setup by OpenStack
components after the ports are all connected and MAC addresses learned.
+.. warning::
+ The only exception to this is the setting of the MTU value on
+ the network an IPv6 subnet is created on. If the MTU is less than
+ 1280 octets (the minimum link MTU value specified in
+ `RFC 8200 <https://www.rfc-editor.org/rfc/rfc8200>`__), then it
+ could lead to issues configuring both IPv6 and IPv4 addresses on
+ the network, leaving the subnets unusable. For that reason, the API
+ validates the MTU value when subnets are created to avoid this issue.
+
Addresses for subnets
---------------------
diff --git a/doc/source/admin/config-mtu.rst b/doc/source/admin/config-mtu.rst
index 4207809e46..78892c7563 100644
--- a/doc/source/admin/config-mtu.rst
+++ b/doc/source/admin/config-mtu.rst
@@ -130,6 +130,13 @@ IPv6. IPv6 uses RA via the L3 agent because the DHCP agent only supports
IPv4. Instances using IPv4 and IPv6 should obtain the same MTU value
regardless of method.
+.. note::
+
+ If you are using an MTU value on your network below 1280, please
+ read the warning listed in the
+ `IPv6 configuration guide <./config-ipv6.html#project-network-considerations>`__
+ before creating any subnets.
+
Networks with enabled vlan transparency
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
diff --git a/doc/source/admin/shared/deploy-selfservice-initialnetworks.txt b/doc/source/admin/shared/deploy-selfservice-initialnetworks.txt
index 022bfca392..0e3f85dcfa 100644
--- a/doc/source/admin/shared/deploy-selfservice-initialnetworks.txt
+++ b/doc/source/admin/shared/deploy-selfservice-initialnetworks.txt
@@ -38,6 +38,13 @@ NAT for IPv4 network traffic and directly routes IPv6 network traffic.
| status | ACTIVE |
+-------------------------+--------------+
+ .. note::
+
+ If you are using an MTU value on your network below 1280, please
+ read the warning listed in the
+ `IPv6 configuration guide <../config-ipv6.html#project-network-considerations>`__
+ before creating any subnets.
+
#. Create a IPv4 subnet on the self-service network.
.. code-block:: console
diff --git a/neutron/common/_constants.py b/neutron/common/_constants.py
index 03c07eb71a..40745ecc86 100644
--- a/neutron/common/_constants.py
+++ b/neutron/common/_constants.py
@@ -89,3 +89,6 @@ LOWEST_AGENT_BINDING_INDEX = 1
# Neutron-lib defines this with a /64 but it should be /128
METADATA_V6_CIDR = constants.METADATA_V6_IP + '/128'
+
+# TODO(haleyb): move this constant to neutron_lib.constants
+IPV4_MIN_MTU = 68
diff --git a/neutron/db/db_base_plugin_v2.py b/neutron/db/db_base_plugin_v2.py
index ca430b2cd5..dbbe94de7a 100644
--- a/neutron/db/db_base_plugin_v2.py
+++ b/neutron/db/db_base_plugin_v2.py
@@ -58,6 +58,7 @@ from neutron.db import ipam_pluggable_backend
from neutron.db import models_v2
from neutron.db import rbac_db_mixin as rbac_mixin
from neutron.db import standardattrdescription_db as stattr_db
+from neutron.exceptions import mtu as mtu_exc
from neutron.extensions import subnetpool_prefix_ops
from neutron import ipam
from neutron.ipam import exceptions as ipam_exc
@@ -466,6 +467,10 @@ class NeutronDbPluginV2(db_base_plugin_common.DbBasePluginCommon,
# context.
getattr(network, 'rbac_entries')
+ # validate 'mtu' parameter
+ if 'mtu' in n:
+ self._validate_change_network_mtu(context, id, n['mtu'])
+
# The filter call removes attributes from the body received from
# the API that are logically tied to network resources but are
# stored in other database tables handled by extensions
@@ -473,6 +478,28 @@ class NeutronDbPluginV2(db_base_plugin_common.DbBasePluginCommon,
ndb_utils.filter_non_model_columns(n, models_v2.Network))
return self._make_network_dict(network, context=context)
+ def _validate_change_network_mtu(self, context, id, mtu):
+ # can support either ip_version
+ if mtu >= constants.IPV6_MIN_MTU:
+ return
+
+ subnets = self._get_subnets_by_network(context, id)
+ if len(subnets) == 0:
+ return
+
+ # at least one subnet present, if below IPv4 minimum we fail early
+ if mtu < _constants.IPV4_MIN_MTU:
+ raise mtu_exc.NetworkMTUSubnetConflict(
+ net_id=id, mtu=_constants.IPV4_MIN_MTU)
+
+ # We do not need to check IPv4 subnets as they will have been
+ # caught by above IPV4_MIN_MTU check
+ for subnet in subnets:
+ if (subnet.ip_version == constants.IP_VERSION_6 and
+ mtu < constants.IPV6_MIN_MTU):
+ raise mtu_exc.NetworkMTUSubnetConflict(
+ net_id=id, mtu=constants.IPV6_MIN_MTU)
+
def _ensure_network_not_in_use(self, context, net_id):
non_auto_ports = context.session.query(
models_v2.Port.id).filter_by(network_id=net_id).filter(
@@ -715,6 +742,23 @@ class NeutronDbPluginV2(db_base_plugin_common.DbBasePluginCommon,
"Prefix Delegation.")
raise exc.BadRequest(resource='subnets', msg=reason)
+ def _validate_subnet_network_mtu(self, network, subnet):
+ """Validates that network mtu is correct for subnet association"""
+ mtu = network.mtu
+ if not mtu or mtu >= constants.IPV6_MIN_MTU:
+ return
+
+ # if below IPv4 minimum we fail early
+ if mtu < _constants.IPV4_MIN_MTU:
+ raise mtu_exc.NetworkMTUSubnetConflict(net_id=network.id, mtu=mtu)
+
+ # We do not need to check IPv4 subnets as they will have been
+ # caught by above IPV4_MIN_MTU check
+ ip_version = subnet.get('ip_version')
+ if (ip_version == constants.IP_VERSION_6 and
+ mtu < constants.IPV6_MIN_MTU):
+ raise mtu_exc.NetworkMTUSubnetConflict(net_id=network.id, mtu=mtu)
+
def _update_router_gw_ports(self, context, network, subnet):
l3plugin = directory.get_plugin(plugin_constants.L3)
if l3plugin:
@@ -876,6 +920,7 @@ class NeutronDbPluginV2(db_base_plugin_common.DbBasePluginCommon,
with db_api.CONTEXT_WRITER.using(context):
network = self._get_network(context,
subnet['subnet']['network_id'])
+ self._validate_subnet_network_mtu(network, s)
subnet, ipam_subnet = self.ipam.allocate_subnet(context,
network,
subnet['subnet'],
diff --git a/neutron/exceptions/mtu.py b/neutron/exceptions/mtu.py
new file mode 100644
index 0000000000..02672d8059
--- /dev/null
+++ b/neutron/exceptions/mtu.py
@@ -0,0 +1,28 @@
+# Copyright (c) 2023 Canonical Ltd.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from neutron_lib import exceptions as e
+
+from neutron._i18n import _
+
+
+# TODO(haleyb): Move to n-lib
+class NetworkMTUSubnetConflict(e.Conflict):
+ """A conflict error due to MTU being invalid on said network.
+
+ :param net_id: The UUID of the network
+ :param mtu: The minimum MTU required by a subnet for the network
+ """
+ message = _("MTU of %(net_id)s is not valid, subnet requires a "
+ "minimum of %(mtu)s")
diff --git a/neutron/tests/unit/db/test_db_base_plugin_v2.py b/neutron/tests/unit/db/test_db_base_plugin_v2.py
index a25ccc4f0d..5dd9bbd905 100644
--- a/neutron/tests/unit/db/test_db_base_plugin_v2.py
+++ b/neutron/tests/unit/db/test_db_base_plugin_v2.py
@@ -49,6 +49,7 @@ import neutron
from neutron.api import api_common
from neutron.api import extensions
from neutron.api.v2 import router
+from neutron.common import _constants as common_constants
from neutron.common import ipv6_utils
from neutron.common.ovn import utils as ovn_utils
from neutron.common import test_lib
@@ -60,6 +61,7 @@ from neutron.db import ipam_backend_mixin
from neutron.db.models import l3 as l3_models
from neutron.db.models import securitygroup as sg_models
from neutron.db import models_v2
+from neutron.exceptions import mtu as mtu_exc
from neutron.ipam.drivers.neutrondb_ipam import driver as ipam_driver
from neutron.ipam import exceptions as ipam_exc
from neutron.objects import network as network_obj
@@ -373,7 +375,7 @@ class NeutronDbPluginV2TestCase(testlib_api.WebTestCase):
'admin_state_up': admin_state_up,
'tenant_id': tenant_id}}
for arg in (('admin_state_up', 'tenant_id', 'shared',
- 'vlan_transparent',
+ 'vlan_transparent', 'mtu',
'availability_zone_hints') + (arg_list or ())):
# Arg must be present
if arg in kwargs:
@@ -7066,7 +7068,8 @@ class NeutronDbPluginV2AsMixinTestCase(NeutronDbPluginV2TestCase,
super(NeutronDbPluginV2AsMixinTestCase, self).setUp()
self.plugin = importutils.import_object(DB_PLUGIN_KLASS)
self.context = context.get_admin_context()
- self.net_data = {'network': {'id': 'fake-id',
+ self.net_id = uuidutils.generate_uuid()
+ self.net_data = {'network': {'id': self.net_id,
'name': 'net1',
'admin_state_up': True,
'tenant_id': TEST_TENANT_ID,
@@ -7075,7 +7078,7 @@ class NeutronDbPluginV2AsMixinTestCase(NeutronDbPluginV2TestCase,
def test_create_network_with_default_status(self):
net = self.plugin.create_network(self.context, self.net_data)
default_net_create_status = 'ACTIVE'
- expected = [('id', 'fake-id'), ('name', 'net1'),
+ expected = [('id', self.net_id), ('name', 'net1'),
('admin_state_up', True), ('tenant_id', TEST_TENANT_ID),
('shared', False), ('status', default_net_create_status)]
for k, v in expected:
@@ -7113,6 +7116,81 @@ class NeutronDbPluginV2AsMixinTestCase(NeutronDbPluginV2TestCase,
new_subnetpool_id,
None)
+ def test_create_subnet_invalid_network_mtu_ipv4_returns_409(self):
+ self.net_data['network']['mtu'] = common_constants.IPV4_MIN_MTU - 1
+ net = self.plugin.create_network(self.context, self.net_data)
+ self._create_subnet(self.fmt,
+ net['id'],
+ '10.0.0.0/24',
+ webob.exc.HTTPConflict.code)
+
+ def test_create_subnet_invalid_network_mtu_ipv6_returns_409(self):
+ self.net_data['network']['mtu'] = constants.IPV6_MIN_MTU - 1
+ net = self.plugin.create_network(self.context, self.net_data)
+ self._create_subnet(self.fmt,
+ net['id'],
+ '2001:db8:0:1::/64',
+ webob.exc.HTTPConflict.code,
+ ip_version=constants.IP_VERSION_6)
+
+ def test_update_network_invalid_mtu(self):
+ self.net_data['network']['mtu'] = 1500
+ net = self.plugin.create_network(self.context, self.net_data)
+
+ # This should succeed with no subnets
+ self.net_data['network']['mtu'] = common_constants.IPV4_MIN_MTU - 1
+ self.plugin.update_network(self.context, net['id'], self.net_data)
+
+ # reset mtu
+ self.net_data['network']['mtu'] = 1500
+ self.plugin.update_network(self.context, net['id'], self.net_data)
+
+ self._create_subnet(self.fmt,
+ net['id'],
+ '10.0.0.0/24',
+ ip_version=constants.IP_VERSION_4)
+
+ # These should succeed with just an IPv4 subnet present
+ self.net_data['network']['mtu'] = constants.IPV6_MIN_MTU
+ self.plugin.update_network(self.context, net['id'], self.net_data)
+ self.net_data['network']['mtu'] = constants.IPV6_MIN_MTU - 1
+ self.plugin.update_network(self.context, net['id'], self.net_data)
+ self.net_data['network']['mtu'] = common_constants.IPV4_MIN_MTU
+ self.plugin.update_network(self.context, net['id'], self.net_data)
+
+ # This should fail with any subnets present
+ self.net_data['network']['mtu'] = common_constants.IPV4_MIN_MTU - 1
+ with testlib_api.ExpectedException(mtu_exc.NetworkMTUSubnetConflict):
+ self.plugin.update_network(self.context, net['id'], self.net_data)
+
+ def test_update_network_invalid_mtu_ipv4_ipv6(self):
+ self.net_data['network']['mtu'] = 1500
+ net = self.plugin.create_network(self.context, self.net_data)
+
+ self._create_subnet(self.fmt,
+ net['id'],
+ '10.0.0.0/24',
+ ip_version=constants.IP_VERSION_4)
+ self._create_subnet(self.fmt,
+ net['id'],
+ '2001:db8:0:1::/64',
+ ip_version=constants.IP_VERSION_6)
+
+ # This should succeed with both subnets present
+ self.net_data['network']['mtu'] = constants.IPV6_MIN_MTU
+ self.plugin.update_network(self.context, net['id'], self.net_data)
+
+ # These should all fail with both subnets present
+ with testlib_api.ExpectedException(mtu_exc.NetworkMTUSubnetConflict):
+ self.net_data['network']['mtu'] = constants.IPV6_MIN_MTU - 1
+ self.plugin.update_network(self.context, net['id'], self.net_data)
+ with testlib_api.ExpectedException(mtu_exc.NetworkMTUSubnetConflict):
+ self.net_data['network']['mtu'] = common_constants.IPV4_MIN_MTU
+ self.plugin.update_network(self.context, net['id'], self.net_data)
+ with testlib_api.ExpectedException(mtu_exc.NetworkMTUSubnetConflict):
+ self.net_data['network']['mtu'] = common_constants.IPV4_MIN_MTU - 1
+ self.plugin.update_network(self.context, net['id'], self.net_data)
+
class TestNetworks(testlib_api.SqlTestCase):
def setUp(self):
diff --git a/neutron/tests/unit/fake_resources.py b/neutron/tests/unit/fake_resources.py
index ff2e0c5401..ca745f6b64 100644
--- a/neutron/tests/unit/fake_resources.py
+++ b/neutron/tests/unit/fake_resources.py
@@ -322,6 +322,7 @@ class FakeNetwork(object):
'availability_zone_hints': [],
'is_default': False,
'standard_attr_id': 1,
+ 'mtu': 1500,
}
# Overwrite default attributes.
diff --git a/releasenotes/releasenotes/notes/network_subnet_mtu_validation-c221f22efcfae927.yaml b/releasenotes/releasenotes/notes/network_subnet_mtu_validation-c221f22efcfae927.yaml
new file mode 100644
index 0000000000..f30421c61a
--- /dev/null
+++ b/releasenotes/releasenotes/notes/network_subnet_mtu_validation-c221f22efcfae927.yaml
@@ -0,0 +1,22 @@
+---
+fixes:
+ - |
+ The Neutron API has been changed to validate network MTU minimums.
+ A network's MTU is now only valid if it is the minimum value
+ allowed based on the IP version of the associated subnets,
+ 68 for IPv4 and 1280 for IPv6.
+
+ This minimum is now enforced in the following ways:
+
+ * When a subnet is associated with a network, validate
+ the MTU is large enough for the IP version. Not only
+ would the subnet be unusable if it was allowed, but the
+ Linux kernel can fail adding addresses and configuring
+ network settings like the MTU.
+
+ * When a network MTU is changed, validate the MTU is large
+ enough for any currently associated subnets. Allowing a
+ smaller MTU would render any existing subnets unusable.
+
+ See bug `1988069 <https://bugs.launchpad.net/neutron/+bug/1988069>`_
+ for more information.