Merge "Change API to validate network MTU minimums"
This commit is contained in:
commit
fd17662611
|
@ -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
|
||||
---------------------
|
||||
|
||||
|
|
|
@ -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
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'],
|
||||
|
|
|
@ -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")
|
|
@ -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):
|
||||
|
|
|
@ -322,6 +322,7 @@ class FakeNetwork(object):
|
|||
'availability_zone_hints': [],
|
||||
'is_default': False,
|
||||
'standard_attr_id': 1,
|
||||
'mtu': 1500,
|
||||
}
|
||||
|
||||
# Overwrite default attributes.
|
||||
|
|
|
@ -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.
|
Loading…
Reference in New Issue