From b38c56f5b6b0f0b03ff8f2799ccdde2cabfba5a7 Mon Sep 17 00:00:00 2001 From: gessau Date: Tue, 14 May 2013 21:57:47 -0400 Subject: [PATCH] Check network vlan ranges for correctness. Check that the range beginning and end tags are valid values 1-4094. Supply two global constants for min/max vlan tags and update all local usage of these values to use the global constants. Fixes: Bug #1169266 Change-Id: I054a8bebd16d95ea40414e3cecb6d24a970c730f --- quantum/common/constants.py | 3 + quantum/common/exceptions.py | 8 +- quantum/common/utils.py | 5 + quantum/plugins/brocade/vlanbm.py | 6 +- quantum/plugins/common/utils.py | 21 +++- quantum/plugins/hyperv/common/constants.py | 2 - .../plugins/linuxbridge/lb_quantum_plugin.py | 9 +- quantum/plugins/mlnx/common/constants.py | 2 - quantum/plugins/mlnx/mlnx_plugin.py | 10 +- quantum/plugins/nicira/QuantumPlugin.py | 17 +++- .../openvswitch/agent/ovs_quantum_agent.py | 13 +-- .../plugins/openvswitch/ovs_quantum_plugin.py | 9 +- quantum/tests/unit/nec/test_db.py | 3 +- quantum/tests/unit/test_common_utils.py | 97 +++++++++++++++++++ 14 files changed, 171 insertions(+), 34 deletions(-) diff --git a/quantum/common/constants.py b/quantum/common/constants.py index 9557b97ddc6..06b0c53d2c3 100644 --- a/quantum/common/constants.py +++ b/quantum/common/constants.py @@ -37,6 +37,9 @@ IPv6 = 'IPv6' UDP_PROTOCOL = 17 DHCP_RESPONSE_PORT = 68 +MIN_VLAN_TAG = 1 +MAX_VLAN_TAG = 4094 + EXT_NS = '_extension_ns' XML_NS_V20 = 'http://openstack.org/quantum/api/v2.0' XSI_NAMESPACE = "http://www.w3.org/2001/XMLSchema-instance" diff --git a/quantum/common/exceptions.py b/quantum/common/exceptions.py index 3c98f29936a..1cc164251a5 100644 --- a/quantum/common/exceptions.py +++ b/quantum/common/exceptions.py @@ -260,4 +260,10 @@ class GatewayConflictWithAllocationPools(InUse): class NetworkVlanRangeError(QuantumException): - message = _("Invalid network VLAN range: '%(range)s' - '%(error)s'") + message = _("Invalid network VLAN range: '%(vlan_range)s' - '%(error)s'") + + def __init__(self, **kwargs): + # Convert vlan_range tuple to 'start:end' format for display + if isinstance(kwargs['vlan_range'], tuple): + kwargs['vlan_range'] = "%d:%d" % kwargs['vlan_range'] + super(NetworkVlanRangeError, self).__init__(**kwargs) diff --git a/quantum/common/utils.py b/quantum/common/utils.py index 62b46463e5c..647f38ea2eb 100644 --- a/quantum/common/utils.py +++ b/quantum/common/utils.py @@ -29,6 +29,7 @@ import socket from eventlet.green import subprocess from oslo.config import cfg +from quantum.common import constants as q_const from quantum.openstack.common import log as logging @@ -193,3 +194,7 @@ def is_extension_supported(plugin, ext_alias): def log_opt_values(log): cfg.CONF.log_opt_values(log, std_logging.DEBUG) + + +def is_valid_vlan_tag(vlan): + return q_const.MIN_VLAN_TAG <= vlan <= q_const.MAX_VLAN_TAG diff --git a/quantum/plugins/brocade/vlanbm.py b/quantum/plugins/brocade/vlanbm.py index 8dbf41e86c2..b4431d4951e 100644 --- a/quantum/plugins/brocade/vlanbm.py +++ b/quantum/plugins/brocade/vlanbm.py @@ -21,11 +21,13 @@ """A Vlan Bitmap class to handle allocation/de-allocation of vlan ids.""" + +from quantum.common import constants from quantum.plugins.brocade.db import models as brocade_db -MIN_VLAN = 2 -MAX_VLAN = 4094 +MIN_VLAN = constants.MIN_VLAN_TAG + 1 +MAX_VLAN = constants.MAX_VLAN_TAG class VlanBitmap(object): diff --git a/quantum/plugins/common/utils.py b/quantum/plugins/common/utils.py index f0b692c4289..f5651b38970 100644 --- a/quantum/plugins/common/utils.py +++ b/quantum/plugins/common/utils.py @@ -19,6 +19,20 @@ Common utilities and helper functions for Openstack Networking Plugins. """ from quantum.common import exceptions as q_exc +from quantum.common import utils + + +def verify_vlan_range(vlan_range): + """Raise an exception for invalid tags or malformed range.""" + for vlan_tag in vlan_range: + if not utils.is_valid_vlan_tag(vlan_tag): + raise q_exc.NetworkVlanRangeError( + vlan_range=vlan_range, + error=_("%s is not a valid VLAN tag") % vlan_tag) + if vlan_range[1] < vlan_range[0]: + raise q_exc.NetworkVlanRangeError( + vlan_range=vlan_range, + error=_("End of VLAN range is less than start of VLAN range")) def parse_network_vlan_range(network_vlan_range): @@ -27,10 +41,11 @@ def parse_network_vlan_range(network_vlan_range): if ':' in entry: try: network, vlan_min, vlan_max = entry.split(':') - vlan_min, vlan_max = int(vlan_min), int(vlan_max) + vlan_range = (int(vlan_min), int(vlan_max)) except ValueError as ex: - raise q_exc.NetworkVlanRangeError(range=entry, error=ex) - return network, (vlan_min, vlan_max) + raise q_exc.NetworkVlanRangeError(vlan_range=entry, error=ex) + verify_vlan_range(vlan_range) + return network, vlan_range else: return entry, None diff --git a/quantum/plugins/hyperv/common/constants.py b/quantum/plugins/hyperv/common/constants.py index 03330fb8962..dcce7ba3b25 100644 --- a/quantum/plugins/hyperv/common/constants.py +++ b/quantum/plugins/hyperv/common/constants.py @@ -21,8 +21,6 @@ TUNNEL = 'tunnel' # Special vlan_id value in ovs_vlan_allocations table indicating flat network FLAT_VLAN_ID = -1 -VLAN_ID_MIN = 1 -VLAN_ID_MAX = 4096 # Values for network_type TYPE_LOCAL = 'local' diff --git a/quantum/plugins/linuxbridge/lb_quantum_plugin.py b/quantum/plugins/linuxbridge/lb_quantum_plugin.py index 57a83c5efd2..9d800894f42 100644 --- a/quantum/plugins/linuxbridge/lb_quantum_plugin.py +++ b/quantum/plugins/linuxbridge/lb_quantum_plugin.py @@ -25,6 +25,7 @@ from quantum.common import constants as q_const from quantum.common import exceptions as q_exc from quantum.common import rpc as q_rpc from quantum.common import topics +from quantum.common import utils from quantum.db import agents_db from quantum.db import agentschedulers_db from quantum.db import api as db_api @@ -312,9 +313,11 @@ class LinuxBridgePluginV2(db_base_plugin_v2.QuantumDbPluginV2, if not segmentation_id_set: msg = _("provider:segmentation_id required") raise q_exc.InvalidInput(error_message=msg) - if segmentation_id < 1 or segmentation_id > 4094: - msg = _("provider:segmentation_id out of range " - "(1 through 4094)") + if not utils.is_valid_vlan_tag(segmentation_id): + msg = (_("provider:segmentation_id out of range " + "(%(min_id)s through %(max_id)s)") % + {'min_id': q_const.MIN_VLAN_TAG, + 'max_id': q_const.MAX_VLAN_TAG}) raise q_exc.InvalidInput(error_message=msg) elif network_type == constants.TYPE_LOCAL: if physical_network_set: diff --git a/quantum/plugins/mlnx/common/constants.py b/quantum/plugins/mlnx/common/constants.py index eb6c1b8ef7a..58b22cad608 100644 --- a/quantum/plugins/mlnx/common/constants.py +++ b/quantum/plugins/mlnx/common/constants.py @@ -17,8 +17,6 @@ LOCAL_VLAN_ID = -2 FLAT_VLAN_ID = -1 -VLAN_ID_MIN = 1 -VLAN_ID_MAX = 4096 # Values for network_type TYPE_LOCAL = 'local' diff --git a/quantum/plugins/mlnx/mlnx_plugin.py b/quantum/plugins/mlnx/mlnx_plugin.py index b258ad8d15a..d3c56481f24 100644 --- a/quantum/plugins/mlnx/mlnx_plugin.py +++ b/quantum/plugins/mlnx/mlnx_plugin.py @@ -21,8 +21,10 @@ from oslo.config import cfg from quantum.agent import securitygroups_rpc as sg_rpc from quantum.api.v2 import attributes +from quantum.common import constants as q_const from quantum.common import exceptions as q_exc from quantum.common import topics +from quantum.common import utils from quantum.db import agents_db from quantum.db import db_base_plugin_v2 from quantum.db import l3_db @@ -192,9 +194,11 @@ class MellanoxEswitchPlugin(db_base_plugin_v2.QuantumDbPluginV2, if not segmentation_id_set: msg = _("provider:segmentation_id required") raise q_exc.InvalidInput(error_message=msg) - if segmentation_id < 1 or segmentation_id > 4094: - msg = _("provider:segmentation_id out of range " - "(1 through 4094)") + if not utils.is_valid_vlan_tag(segmentation_id): + msg = (_("provider:segmentation_id out of range " + "(%(min_id)s through %(max_id)s)") % + {'min_id': q_const.MIN_VLAN_TAG, + 'max_id': q_const.MAX_VLAN_TAG}) raise q_exc.InvalidInput(error_message=msg) def _process_local_net(self, physical_network_set, segmentation_id_set): diff --git a/quantum/plugins/nicira/QuantumPlugin.py b/quantum/plugins/nicira/QuantumPlugin.py index 8787e1d65c1..6ff9a1e4b84 100644 --- a/quantum/plugins/nicira/QuantumPlugin.py +++ b/quantum/plugins/nicira/QuantumPlugin.py @@ -34,6 +34,7 @@ from quantum.common import constants from quantum.common import exceptions as q_exc from quantum.common import rpc as q_rpc from quantum.common import topics +from quantum.common import utils from quantum import context as q_context from quantum.db import agents_db from quantum.db import agentschedulers_db @@ -697,8 +698,12 @@ class NvpPluginV2(db_base_plugin_v2.QuantumDbPluginV2, err_msg = _("Segmentation ID must be specified with " "vlan network type") elif (segmentation_id_set and - (segmentation_id < 1 or segmentation_id > 4094)): - err_msg = _("%s out of range (1 to 4094)") % segmentation_id + not utils.is_valid_vlan_tag(segmentation_id)): + err_msg = (_("%(segmentation_id)s out of range " + "(%(min_id)s through %(max_id)s)") % + {'segmentation_id': segmentation_id, + 'min_id': constants.MIN_VLAN_TAG, + 'max_id': constants.MAX_VLAN_TAG}) else: # Verify segment is not already allocated binding = nicira_db.get_network_binding_by_vlanid( @@ -708,8 +713,12 @@ class NvpPluginV2(db_base_plugin_v2.QuantumDbPluginV2, physical_network=physical_network) elif network_type == NetworkTypes.L3_EXT: if (segmentation_id_set and - (segmentation_id < 1 or segmentation_id > 4094)): - err_msg = _("%s out of range (1 to 4094)") % segmentation_id + not utils.is_valid_vlan_tag(segmentation_id)): + err_msg = (_("%(segmentation_id)s out of range " + "(%(min_id)s through %(max_id)s)") % + {'segmentation_id': segmentation_id, + 'min_id': constants.MIN_VLAN_TAG, + 'max_id': constants.MAX_VLAN_TAG}) else: err_msg = _("%(net_type_param)s %(net_type_value)s not " "supported") % {'net_type_param': pnet.NETWORK_TYPE, diff --git a/quantum/plugins/openvswitch/agent/ovs_quantum_agent.py b/quantum/plugins/openvswitch/agent/ovs_quantum_agent.py index eda4c0b91da..2d078cf6d9b 100644 --- a/quantum/plugins/openvswitch/agent/ovs_quantum_agent.py +++ b/quantum/plugins/openvswitch/agent/ovs_quantum_agent.py @@ -47,7 +47,7 @@ from quantum.plugins.openvswitch.common import constants LOG = logging.getLogger(__name__) # A placeholder for dead vlans. -DEAD_VLAN_TAG = "4095" +DEAD_VLAN_TAG = str(q_const.MAX_VLAN_TAG + 1) # A class to represent a VIF (i.e., a port that has 'iface-id' and 'vif-mac' @@ -139,12 +139,6 @@ class OVSQuantumAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin): modifying, or stripping VLAN tags as necessary. ''' - # Lower bound on available vlans. - MIN_VLAN_TAG = 1 - - # Upper bound on available vlans. - MAX_VLAN_TAG = 4094 - # history # 1.0 Initial version # 1.1 Support Security Group RPC @@ -164,9 +158,8 @@ class OVSQuantumAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin): :param enable_tunneling: if True enable GRE networks. ''' self.root_helper = root_helper - self.available_local_vlans = set( - xrange(OVSQuantumAgent.MIN_VLAN_TAG, - OVSQuantumAgent.MAX_VLAN_TAG)) + self.available_local_vlans = set(xrange(q_const.MIN_VLAN_TAG, + q_const.MAX_VLAN_TAG)) self.int_br = self.setup_integration_br(integ_br) self.setup_physical_bridges(bridge_mappings) self.local_vlan_map = {} diff --git a/quantum/plugins/openvswitch/ovs_quantum_plugin.py b/quantum/plugins/openvswitch/ovs_quantum_plugin.py index e2bb77f1f42..e8d49f8d6e3 100644 --- a/quantum/plugins/openvswitch/ovs_quantum_plugin.py +++ b/quantum/plugins/openvswitch/ovs_quantum_plugin.py @@ -32,6 +32,7 @@ from quantum.common import constants as q_const from quantum.common import exceptions as q_exc from quantum.common import rpc as q_rpc from quantum.common import topics +from quantum.common import utils from quantum.db import agents_db from quantum.db import agentschedulers_db from quantum.db import db_base_plugin_v2 @@ -376,9 +377,11 @@ class OVSQuantumPluginV2(db_base_plugin_v2.QuantumDbPluginV2, if not segmentation_id_set: msg = _("provider:segmentation_id required") raise q_exc.InvalidInput(error_message=msg) - if segmentation_id < 1 or segmentation_id > 4094: - msg = _("provider:segmentation_id out of range " - "(1 through 4094)") + if not utils.is_valid_vlan_tag(segmentation_id): + msg = (_("provider:segmentation_id out of range " + "(%(min_id)s through %(max_id)s)") % + {'min_id': q_const.MIN_VLAN_TAG, + 'max_id': q_const.MAX_VLAN_TAG}) raise q_exc.InvalidInput(error_message=msg) elif network_type == constants.TYPE_GRE: if not self.enable_tunneling: diff --git a/quantum/tests/unit/nec/test_db.py b/quantum/tests/unit/nec/test_db.py index 08a2f8c3fb0..4ae4f5c5955 100644 --- a/quantum/tests/unit/nec/test_db.py +++ b/quantum/tests/unit/nec/test_db.py @@ -17,6 +17,7 @@ import random +from quantum.common import constants as q_const from quantum.db import api as db_api from quantum.openstack.common import uuidutils from quantum.plugins.nec.common import exceptions as nexc @@ -47,7 +48,7 @@ class NECPluginV2DBTestBase(base.BaseTestCase): port_id = uuidutils.generate_uuid() datapath_id = hex(random.randint(0, 0xffffffff)) port_no = random.randint(1, 100) - vlan_id = random.randint(0, 4095) + vlan_id = random.randint(q_const.MIN_VLAN_TAG, q_const.MAX_VLAN_TAG) mac = ':'.join(["%02x" % random.randint(0, 0xff) for x in range(6)]) none = uuidutils.generate_uuid() return port_id, datapath_id, port_no, vlan_id, mac, none diff --git a/quantum/tests/unit/test_common_utils.py b/quantum/tests/unit/test_common_utils.py index 2973861bfb4..21d7b136e36 100644 --- a/quantum/tests/unit/test_common_utils.py +++ b/quantum/tests/unit/test_common_utils.py @@ -68,6 +68,8 @@ class UtilTestParseVlanRanges(base.BaseTestCase): _err_too_few = "' - 'need more than 2 values to unpack'" _err_too_many = "' - 'too many values to unpack'" _err_not_int = "' - 'invalid literal for int() with base 10: '%s''" + _err_bad_vlan = "' - '%s is not a valid VLAN tag'" + _err_range = "' - 'End of VLAN range is less than start of VLAN range'" def _range_too_few_err(self, nv_range): return self._err_prefix + nv_range + self._err_too_few @@ -78,6 +80,82 @@ class UtilTestParseVlanRanges(base.BaseTestCase): def _vlan_not_int_err(self, nv_range, vlan): return self._err_prefix + nv_range + (self._err_not_int % vlan) + def _nrange_invalid_vlan(self, nv_range, n): + vlan = nv_range.split(':')[n] + v_range = ':'.join(nv_range.split(':')[1:]) + return self._err_prefix + v_range + (self._err_bad_vlan % vlan) + + def _vrange_invalid_vlan(self, v_range_tuple, n): + vlan = v_range_tuple[n - 1] + v_range_str = '%d:%d' % v_range_tuple + return self._err_prefix + v_range_str + (self._err_bad_vlan % vlan) + + def _vrange_invalid(self, v_range_tuple): + v_range_str = '%d:%d' % v_range_tuple + return self._err_prefix + v_range_str + self._err_range + + +class TestVlanRangeVerifyValid(UtilTestParseVlanRanges): + def verify_range(self, vlan_range): + return plugin_utils.verify_vlan_range(vlan_range) + + def test_range_valid_ranges(self): + self.assertEqual(self.verify_range((1, 2)), None) + self.assertEqual(self.verify_range((1, 1999)), None) + self.assertEqual(self.verify_range((100, 100)), None) + self.assertEqual(self.verify_range((100, 200)), None) + self.assertEqual(self.verify_range((4001, 4094)), None) + self.assertEqual(self.verify_range((1, 4094)), None) + + def check_one_vlan_invalid(self, bad_range, which): + expected_msg = self._vrange_invalid_vlan(bad_range, which) + err = self.assertRaises(q_exc.NetworkVlanRangeError, + self.verify_range, bad_range) + self.assertEqual(str(err), expected_msg) + + def test_range_first_vlan_invalid_negative(self): + self.check_one_vlan_invalid((-1, 199), 1) + + def test_range_first_vlan_invalid_zero(self): + self.check_one_vlan_invalid((0, 199), 1) + + def test_range_first_vlan_invalid_limit_plus_one(self): + self.check_one_vlan_invalid((4095, 199), 1) + + def test_range_first_vlan_invalid_too_big(self): + self.check_one_vlan_invalid((9999, 199), 1) + + def test_range_second_vlan_invalid_negative(self): + self.check_one_vlan_invalid((299, -1), 2) + + def test_range_second_vlan_invalid_zero(self): + self.check_one_vlan_invalid((299, 0), 2) + + def test_range_second_vlan_invalid_limit_plus_one(self): + self.check_one_vlan_invalid((299, 4095), 2) + + def test_range_second_vlan_invalid_too_big(self): + self.check_one_vlan_invalid((299, 9999), 2) + + def test_range_both_vlans_invalid_01(self): + self.check_one_vlan_invalid((-1, 0), 1) + + def test_range_both_vlans_invalid_02(self): + self.check_one_vlan_invalid((0, 4095), 1) + + def test_range_both_vlans_invalid_03(self): + self.check_one_vlan_invalid((4095, 9999), 1) + + def test_range_both_vlans_invalid_04(self): + self.check_one_vlan_invalid((9999, -1), 1) + + def test_range_reversed(self): + bad_range = (95, 10) + expected_msg = self._vrange_invalid(bad_range) + err = self.assertRaises(q_exc.NetworkVlanRangeError, + self.verify_range, bad_range) + self.assertEqual(str(err), expected_msg) + class TestParseOneVlanRange(UtilTestParseVlanRanges): def parse_one(self, cfg_entry): @@ -121,6 +199,25 @@ class TestParseOneVlanRange(UtilTestParseVlanRanges): self.parse_one, config_str) self.assertEqual(str(err), expected_msg) + def test_parse_one_net_and_max_range(self): + config_str = "net1:1:4094" + expected_networks = ("net1", (1, 4094)) + self.assertEqual(self.parse_one(config_str), expected_networks) + + def test_parse_one_net_range_bad_vlan1(self): + config_str = "net1:9000:150" + expected_msg = self._nrange_invalid_vlan(config_str, 1) + err = self.assertRaises(q_exc.NetworkVlanRangeError, + self.parse_one, config_str) + self.assertEqual(str(err), expected_msg) + + def test_parse_one_net_range_bad_vlan2(self): + config_str = "net1:4000:4999" + expected_msg = self._nrange_invalid_vlan(config_str, 2) + err = self.assertRaises(q_exc.NetworkVlanRangeError, + self.parse_one, config_str) + self.assertEqual(str(err), expected_msg) + class TestParseVlanRangeList(UtilTestParseVlanRanges): def parse_list(self, cfg_entries):