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
This commit is contained in:
yangxurong 2014-04-24 09:26:22 +08:00 committed by Romil Gupta
parent 5ba47e8ca3
commit 22bec67395
8 changed files with 138 additions and 17 deletions

View 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'

View 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'")

View 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),

View 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)

View 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):

View 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

View 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):

View 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'"