]> review.fuel-infra Code Review - openstack-build/neutron-build.git/commitdiff
Tunnel ID range validation for VXLAN/GRE networks
authoryangxurong <yangxurong@huawei.com>
Thu, 24 Apr 2014 01:26:22 +0000 (09:26 +0800)
committerRomil Gupta <romilg@hp.com>
Mon, 15 Sep 2014 15:41:09 +0000 (08:41 -0700)
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

neutron/common/constants.py
neutron/common/exceptions.py
neutron/common/utils.py
neutron/plugins/common/utils.py
neutron/plugins/ml2/drivers/type_gre.py
neutron/plugins/ml2/drivers/type_tunnel.py
neutron/plugins/ml2/drivers/type_vxlan.py
neutron/tests/unit/test_common_utils.py

index b053b1ae3cb5c70298bffa3e594b022039653e6e..756a9bb8bec0442ed93eaf4aee285836ac2cc802 100644 (file)
@@ -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'
index 3124ea781934d932b92644b810e0e78b36b16f6e..be62388aa6e5d62488cd9089134dfc093f386572 100644 (file)
@@ -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'")
 
index 006e3b7dc89e03a819d4304efc6cfd3aa94182ee..f890f6ee46c1cf32837df79abf4ea15e1f29f007 100644 (file)
@@ -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),
index fbc6f69e739a178ce3868ad71bddcaa6dfa97d5a..6abd52554cbe29b681cee51e7925054a1e6c4b1e 100644 (file)
@@ -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)
index 8cc62f0bbde22874d0280066542bed1bcd03c8fd..310386d974402fe7698ff5310729abcdcb893bfa 100644 (file)
@@ -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):
 
index 819cd7e739394a886748c7f4cc1d18af9b95c2e3..cddb894b798753ef20baab4ebfa87572ccf68b9a 100644 (file)
@@ -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
index cb271934c94d3c0b1c743dbbc87453c16426426c..1d6d58244559e09aa3f32c92f9e2d03fdf685782 100644 (file)
@@ -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):
 
index d42ce4e92620e329a18e372ceb036a25aa880b68..96b9f170d75a95b0ae7ae2296f793e5dc0b0504f 100644 (file)
@@ -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'"