From: yangxurong Date: Thu, 24 Apr 2014 01:26:22 +0000 (+0800) Subject: Tunnel ID range validation for VXLAN/GRE networks X-Git-Url: https://review.fuel-infra.org/gitweb?a=commitdiff_plain;h=22bec67395b52a879ad4eb37b150eead9d4bb444;p=openstack-build%2Fneutron-build.git Tunnel ID range validation for VXLAN/GRE networks Currently, there is no check which validates the values of tunnel range for VXLAN/GRE networks. The VXLAN VNI is 24 bit which have range between 1 to 2^24 - 1. Similarly, GRE key field is 32 bit which have range between 1 to 2^32 - 1. Closes-Bug: #1306488 Co-Authored-By: romilg romilg@hp.com Change-Id: Idb3d0f41166df589a1e90394d9319901b5f9b439 --- diff --git a/neutron/common/constants.py b/neutron/common/constants.py index b053b1ae3..756a9bb8b 100644 --- a/neutron/common/constants.py +++ b/neutron/common/constants.py @@ -61,7 +61,15 @@ DHCP_RESPONSE_PORT = 68 MIN_VLAN_TAG = 1 MAX_VLAN_TAG = 4094 -MAX_VXLAN_VNI = 16777215 + +# For GRE Tunnel +MIN_GRE_ID = 1 +MAX_GRE_ID = 2 ** 32 - 1 + +# For VXLAN Tunnel +MIN_VXLAN_VNI = 1 +MAX_VXLAN_VNI = 2 ** 24 - 1 + FLOODING_ENTRY = ['00:00:00:00:00:00', '0.0.0.0'] EXT_NS_COMP = '_backward_comp_e_ns' diff --git a/neutron/common/exceptions.py b/neutron/common/exceptions.py index 3124ea781..be62388aa 100644 --- a/neutron/common/exceptions.py +++ b/neutron/common/exceptions.py @@ -305,6 +305,17 @@ class NetworkVlanRangeError(NeutronException): super(NetworkVlanRangeError, self).__init__(**kwargs) +class NetworkTunnelRangeError(NeutronException): + message = _("Invalid network Tunnel range: " + "'%(tunnel_range)s' - %(error)s") + + def __init__(self, **kwargs): + # Convert tunnel_range tuple to 'start:end' format for display + if isinstance(kwargs['tunnel_range'], tuple): + kwargs['tunnel_range'] = "%d:%d" % kwargs['tunnel_range'] + super(NetworkTunnelRangeError, self).__init__(**kwargs) + + class NetworkVxlanPortRangeError(NeutronException): message = _("Invalid network VXLAN port range: '%(vxlan_range)s'") diff --git a/neutron/common/utils.py b/neutron/common/utils.py index 006e3b7dc..f890f6ee4 100644 --- a/neutron/common/utils.py +++ b/neutron/common/utils.py @@ -272,6 +272,14 @@ def is_valid_vlan_tag(vlan): return q_const.MIN_VLAN_TAG <= vlan <= q_const.MAX_VLAN_TAG +def is_valid_gre_id(gre_id): + return q_const.MIN_GRE_ID <= gre_id <= q_const.MAX_GRE_ID + + +def is_valid_vxlan_vni(vni): + return q_const.MIN_VXLAN_VNI <= vni <= q_const.MAX_VXLAN_VNI + + def get_random_mac(base_mac): mac = [int(base_mac[0], 16), int(base_mac[1], 16), int(base_mac[2], 16), random.randint(0x00, 0xff), diff --git a/neutron/plugins/common/utils.py b/neutron/plugins/common/utils.py index fbc6f69e7..6abd52554 100644 --- a/neutron/plugins/common/utils.py +++ b/neutron/plugins/common/utils.py @@ -18,7 +18,25 @@ Common utilities and helper functions for Openstack Networking Plugins. from neutron.common import exceptions as n_exc from neutron.common import utils -from neutron.plugins.common import constants +from neutron.plugins.common import constants as p_const + + +def verify_tunnel_range(tunnel_range, tunnel_type): + """Raise an exception for invalid tunnel range or malformed range.""" + mappings = {p_const.TYPE_GRE: utils.is_valid_gre_id, + p_const.TYPE_VXLAN: utils.is_valid_vxlan_vni} + if tunnel_type in mappings: + for ident in tunnel_range: + if not mappings[tunnel_type](ident): + raise n_exc.NetworkTunnelRangeError( + tunnel_range=tunnel_range, + error=_("%(id)s is not a valid %(type)s identifier") % + {'id': ident, 'type': tunnel_type}) + if tunnel_range[1] < tunnel_range[0]: + raise n_exc.NetworkTunnelRangeError( + tunnel_range=tunnel_range, + error=_("End of tunnel range is less " + "than start of tunnel range")) def verify_vlan_range(vlan_range): @@ -62,6 +80,6 @@ def parse_network_vlan_ranges(network_vlan_ranges_cfg_entries): def in_pending_status(status): - return status in (constants.PENDING_CREATE, - constants.PENDING_UPDATE, - constants.PENDING_DELETE) + return status in (p_const.PENDING_CREATE, + p_const.PENDING_UPDATE, + p_const.PENDING_DELETE) diff --git a/neutron/plugins/ml2/drivers/type_gre.py b/neutron/plugins/ml2/drivers/type_gre.py index 8cc62f0bb..310386d97 100644 --- a/neutron/plugins/ml2/drivers/type_gre.py +++ b/neutron/plugins/ml2/drivers/type_gre.py @@ -19,6 +19,7 @@ from six import moves import sqlalchemy as sa from sqlalchemy import sql +from neutron.common import exceptions as exc from neutron.db import api as db_api from neutron.db import model_base from neutron.openstack.common.gettextutils import _LE @@ -68,7 +69,12 @@ class GreTypeDriver(type_tunnel.TunnelTypeDriver): return p_const.TYPE_GRE def initialize(self): - self._initialize(cfg.CONF.ml2_type_gre.tunnel_id_ranges) + try: + self._initialize(cfg.CONF.ml2_type_gre.tunnel_id_ranges) + except exc.NetworkTunnelRangeError: + LOG.exception(_("Failed to parse tunnel_id_ranges. " + "Service terminated!")) + raise SystemExit() def sync_allocations(self): diff --git a/neutron/plugins/ml2/drivers/type_tunnel.py b/neutron/plugins/ml2/drivers/type_tunnel.py index 819cd7e73..cddb894b7 100644 --- a/neutron/plugins/ml2/drivers/type_tunnel.py +++ b/neutron/plugins/ml2/drivers/type_tunnel.py @@ -16,8 +16,10 @@ import abc from neutron.common import exceptions as exc from neutron.common import topics +from neutron.openstack.common.gettextutils import _LI from neutron.openstack.common.gettextutils import _LW from neutron.openstack.common import log +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 @@ -59,25 +61,23 @@ class TunnelTypeDriver(helpers.TypeDriverHelper): def _initialize(self, raw_tunnel_ranges): self.tunnel_ranges = [] - self._parse_tunnel_ranges(raw_tunnel_ranges, - self.tunnel_ranges, - self.get_type()) + self._parse_tunnel_ranges(raw_tunnel_ranges, self.tunnel_ranges) self.sync_allocations() - def _parse_tunnel_ranges(self, tunnel_ranges, current_range, tunnel_type): + def _parse_tunnel_ranges(self, tunnel_ranges, current_range): for entry in tunnel_ranges: entry = entry.strip() try: tun_min, tun_max = entry.split(':') tun_min = tun_min.strip() tun_max = tun_max.strip() - current_range.append((int(tun_min), int(tun_max))) + tunnel_range = int(tun_min), int(tun_max) except ValueError as ex: - LOG.error(_("Invalid tunnel ID range: '%(range)s' - %(e)s. " - "Agent terminated!"), - {'range': tunnel_ranges, 'e': ex}) - LOG.info(_("%(type)s ID ranges: %(range)s"), - {'type': tunnel_type, 'range': current_range}) + raise exc.NetworkTunnelRangeError(tunnel_range=entry, error=ex) + plugin_utils.verify_tunnel_range(tunnel_range, self.get_type()) + current_range.append(tunnel_range) + LOG.info(_LI("%(type)s ID ranges: %(range)s"), + {'type': self.get_type(), 'range': current_range}) def is_partial_segment(self, segment): return segment.get(api.SEGMENTATION_ID) is None diff --git a/neutron/plugins/ml2/drivers/type_vxlan.py b/neutron/plugins/ml2/drivers/type_vxlan.py index cb271934c..1d6d58244 100644 --- a/neutron/plugins/ml2/drivers/type_vxlan.py +++ b/neutron/plugins/ml2/drivers/type_vxlan.py @@ -20,6 +20,7 @@ from six import moves import sqlalchemy as sa from sqlalchemy import sql +from neutron.common import exceptions as exc from neutron.db import api as db_api from neutron.db import model_base from neutron.openstack.common.gettextutils import _LE @@ -76,7 +77,12 @@ class VxlanTypeDriver(type_tunnel.TunnelTypeDriver): return p_const.TYPE_VXLAN def initialize(self): - self._initialize(cfg.CONF.ml2_type_vxlan.vni_ranges) + try: + self._initialize(cfg.CONF.ml2_type_vxlan.vni_ranges) + except exc.NetworkTunnelRangeError: + LOG.exception(_("Failed to parse vni_ranges. " + "Service terminated!")) + raise SystemExit() def sync_allocations(self): diff --git a/neutron/tests/unit/test_common_utils.py b/neutron/tests/unit/test_common_utils.py index d42ce4e92..96b9f170d 100644 --- a/neutron/tests/unit/test_common_utils.py +++ b/neutron/tests/unit/test_common_utils.py @@ -16,8 +16,10 @@ import eventlet import mock import testtools +from neutron.common import constants from neutron.common import exceptions as n_exc from neutron.common import utils +from neutron.plugins.common import constants as p_const from neutron.plugins.common import utils as plugin_utils from neutron.tests import base @@ -65,6 +67,68 @@ class TestParseMappings(base.BaseTestCase): self.assertEqual(self.parse(['']), {}) +class TestParseTunnelRangesMixin(object): + TUN_MIN = None + TUN_MAX = None + TYPE = None + _err_prefix = "Invalid network Tunnel range: '%d:%d' - " + _err_suffix = "%s is not a valid %s identifier" + _err_range = "End of tunnel range is less than start of tunnel range" + + def _build_invalid_tunnel_range_msg(self, t_range_tuple, n): + bad_id = t_range_tuple[n - 1] + return (self._err_prefix % t_range_tuple) + (self._err_suffix + % (bad_id, self.TYPE)) + + def _build_range_reversed_msg(self, t_range_tuple): + return (self._err_prefix % t_range_tuple) + self._err_range + + def _verify_range(self, tunnel_range): + return plugin_utils.verify_tunnel_range(tunnel_range, self.TYPE) + + def _check_range_valid_ranges(self, tunnel_range): + self.assertIsNone(self._verify_range(tunnel_range)) + + def _check_range_invalid_ranges(self, bad_range, which): + expected_msg = self._build_invalid_tunnel_range_msg(bad_range, which) + err = self.assertRaises(n_exc.NetworkTunnelRangeError, + self._verify_range, bad_range) + self.assertEqual(expected_msg, str(err)) + + def _check_range_reversed(self, bad_range): + err = self.assertRaises(n_exc.NetworkTunnelRangeError, + self._verify_range, bad_range) + expected_msg = self._build_range_reversed_msg(bad_range) + self.assertEqual(expected_msg, str(err)) + + def test_range_tunnel_id_valid(self): + self._check_range_valid_ranges((self.TUN_MIN, self.TUN_MAX)) + + def test_range_tunnel_id_invalid(self): + self._check_range_invalid_ranges((-1, self.TUN_MAX), 1) + self._check_range_invalid_ranges((self.TUN_MIN, + self.TUN_MAX + 1), 2) + self._check_range_invalid_ranges((self.TUN_MIN - 1, + self.TUN_MAX + 1), 1) + + def test_range_tunnel_id_reversed(self): + self._check_range_reversed((self.TUN_MAX, self.TUN_MIN)) + + +class TestGreTunnelRangeVerifyValid(TestParseTunnelRangesMixin, + base.BaseTestCase): + TUN_MIN = constants.MIN_GRE_ID + TUN_MAX = constants.MAX_GRE_ID + TYPE = p_const.TYPE_GRE + + +class TestVxlanTunnelRangeVerifyValid(TestParseTunnelRangesMixin, + base.BaseTestCase): + TUN_MIN = constants.MIN_VXLAN_VNI + TUN_MAX = constants.MAX_VXLAN_VNI + TYPE = p_const.TYPE_VXLAN + + class UtilTestParseVlanRanges(base.BaseTestCase): _err_prefix = "Invalid network VLAN range: '" _err_too_few = "' - 'need more than 2 values to unpack'"