Skip to content

Commit

Permalink
Tunnel ID range validation for VXLAN/GRE networks
Browse files Browse the repository at this point in the history
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
  • Loading branch information
yangxurong authored and Romil Gupta committed Sep 15, 2014
1 parent 5ba47e8 commit 22bec67
Show file tree
Hide file tree
Showing 8 changed files with 138 additions and 17 deletions.
10 changes: 9 additions & 1 deletion neutron/common/constants.py
Expand Up @@ -61,7 +61,15 @@

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'
Expand Down
11 changes: 11 additions & 0 deletions neutron/common/exceptions.py
Expand Up @@ -305,6 +305,17 @@ def __init__(self, **kwargs):
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'")

Expand Down
8 changes: 8 additions & 0 deletions neutron/common/utils.py
Expand Up @@ -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),
Expand Down
26 changes: 22 additions & 4 deletions neutron/plugins/common/utils.py
Expand Up @@ -18,7 +18,25 @@

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):
Expand Down Expand Up @@ -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)
8 changes: 7 additions & 1 deletion neutron/plugins/ml2/drivers/type_gre.py
Expand Up @@ -19,6 +19,7 @@
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
Expand Down Expand Up @@ -68,7 +69,12 @@ def get_type(self):
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):

Expand Down
20 changes: 10 additions & 10 deletions neutron/plugins/ml2/drivers/type_tunnel.py
Expand Up @@ -16,8 +16,10 @@

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

Expand Down Expand Up @@ -59,25 +61,23 @@ def get_endpoints(self):

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
Expand Down
8 changes: 7 additions & 1 deletion neutron/plugins/ml2/drivers/type_vxlan.py
Expand Up @@ -20,6 +20,7 @@
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
Expand Down Expand Up @@ -76,7 +77,12 @@ def get_type(self):
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):

Expand Down
64 changes: 64 additions & 0 deletions neutron/tests/unit/test_common_utils.py
Expand Up @@ -16,8 +16,10 @@
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

Expand Down Expand Up @@ -65,6 +67,68 @@ def test_parse_mappings_succeeds_for_no_mappings(self):
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'"
Expand Down

0 comments on commit 22bec67

Please sign in to comment.