diff options
author | Thomas Markwalder <tmark@isc.org> | 2024-10-30 19:31:18 +0100 |
---|---|---|
committer | Thomas Markwalder <tmark@isc.org> | 2024-11-26 18:19:56 +0100 |
commit | a41077e5dc26924e95cb6e581cd1e4c59c4444bf (patch) | |
tree | 4b41b30b1270285e08a42eedd60e9a289c984a50 | |
parent | [#3661] addressed review comments (diff) | |
download | kea-a41077e5dc26924e95cb6e581cd1e4c59c4444bf.tar.xz kea-a41077e5dc26924e95cb6e581cd1e4c59c4444bf.zip |
[#3592] modified in lib dhcp and dhcpsrv
src/lib/dhcp/classify.*
ClientClasses:intersects() - new function
src/lib/dhcp/tests/classify_unittest.cc
TEST(ClassifyTest, ClientClassesIntersects) - new test
src/lib/dhcpsrv/cfg_option.cc
OptionDescriptor::allowedForClientClasses() use inet intersects() function
src/lib/dhcpsrv/dhcpsrv_messages.mes
DHCPSRV_CLIENT_CLASS_DEPRECATED - new message
src/lib/dhcpsrv/network.*
Network - replaced client_class_ string with client_classes_ container
Network::clientSupported() - uses new intersects() function
Network::allowClientClass() - modified to insert
Network::toElement() - updated
src/lib/dhcpsrv/parsers/base_network_parser.*
BaseNetworkParser::getClientClassesElem() - new function
src/lib/dhcpsrv/parsers/dhcp_parsers.cc
src/lib/dhcpsrv/parsers/shared_network_parser.cc
Updated parsers to use BaseNetworkParser::getClientClassesElem()
src/lib/dhcpsrv/parsers/simple_parser4.cc
src/lib/dhcpsrv/parsers/simple_parser6.cc
Added client-classes
src/lib/dhcpsrv/pool.*
replaced client_class_ string with client_classes_ container
Pool::clientSupported()- use new intersects() function
src/lib/dhcpsrv/shared_network.cc
ShareNetwork::getPreferredSubnet() - updated
src/lib/dhcpsrv/tests/cfg_subnets4_unittest.cc
src/lib/dhcpsrv/tests/cfg_subnets6_unittest.cc
Updated tests
src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc
TEST_F(DhcpParserTest, deprecatedClientClassSubnet4)
TEST_F(DhcpParserTest, deprecatedClientClassSubnet6) {
TEST_F(DhcpParserTest, deprecatedClientClassPool4) {
TEST_F(DhcpParserTest, deprecatedClientClassPool6) {
src/lib/dhcpsrv/tests/network_unittest.cc
Removed inheritance support check
src/lib/dhcpsrv/tests/pool_unittest.cc
Updated tests
src/lib/dhcpsrv/tests/shared_network_parser_unittest.cc
TEST_F(SharedNetwork4ParserTest, deprecatedClientClass)
TEST_F(SharedNetwork6ParserTest, deprecatedClientClass)
src/lib/dhcpsrv/tests/shared_network_unittest.cc
src/lib/dhcpsrv/tests/subnet_unittest.cc
Updated tests
src/lib/dhcpsrv/testutils/generic_cb_dhcp4_unittest.cc
src/lib/dhcpsrv/testutils/generic_cb_dhcp6_unittest.cc
Updated tests
28 files changed, 565 insertions, 193 deletions
diff --git a/src/lib/dhcp/classify.cc b/src/lib/dhcp/classify.cc index 828b137604..7a001ba621 100644 --- a/src/lib/dhcp/classify.cc +++ b/src/lib/dhcp/classify.cc @@ -57,6 +57,26 @@ ClientClasses::contains(const ClientClass& x) const { return (idx.count(x) != 0); } +bool +ClientClasses::intersects(const ClientClasses& cclasses) const { + if (cclasses.size() > size()) { + for (const auto& cclass : *this) { + if (cclasses.contains(cclass)) { + return (true); + } + } + } + else { + for (const auto& cclass : cclasses) { + if (contains(cclass)) { + return (true); + } + } + } + + return (false); +} + std::string ClientClasses::toText(const std::string& separator) const { std::stringstream s; diff --git a/src/lib/dhcp/classify.h b/src/lib/dhcp/classify.h index d617952690..69cdffd417 100644 --- a/src/lib/dhcp/classify.h +++ b/src/lib/dhcp/classify.h @@ -21,10 +21,7 @@ /// @file classify.h /// /// @brief Defines elements for storing the names of client classes -/// -/// This file defines common elements used to track the client classes -/// that may be associated with a given packet. In order to minimize the -/// exposure of the DHCP library to server side concepts such as client +/// /// This file defines common elements used to track the client classes /// that may be associated with a given packet. In order to minimize the /// exposure of the DHCP library to server side concepts such as client /// classification the classes herein provide a mechanism to maintain lists /// of class names, rather than the classes they represent. It is the /// upper layers' prerogative to use these names as they see fit. @@ -200,6 +197,14 @@ namespace dhcp { } /// @} + /// @brief returns whether this container has at least one class + /// in given container. + /// + /// @param cclasses list of classes to check for intersection with + /// @return true if this container has at least one class that is + /// also in cclasses, false otherwise. + bool intersects(const ClientClasses& cclasses) const; + /// @brief returns if class x belongs to the defined classes /// /// @param x client class to be checked diff --git a/src/lib/dhcp/tests/classify_unittest.cc b/src/lib/dhcp/tests/classify_unittest.cc index 075af19547..f635817e63 100644 --- a/src/lib/dhcp/tests/classify_unittest.cc +++ b/src/lib/dhcp/tests/classify_unittest.cc @@ -266,3 +266,28 @@ TEST(ClassifyTest, ClientClassesFromElement) { ++cclass; EXPECT_EQ(*cclass, "bar"); } + +// Check that the ClientClasses::intersects function works. +TEST(ClassifyTest, ClientClassesIntersects) { + ClientClasses classes1; + ClientClasses classes2; + + EXPECT_FALSE(classes1.intersects(classes2)); + EXPECT_FALSE(classes2.intersects(classes1)); + + classes1.insert("one"); + + EXPECT_FALSE(classes1.intersects(classes2)); + EXPECT_FALSE(classes2.intersects(classes1)); + + classes2.insert("two"); + classes2.insert("three"); + + EXPECT_FALSE(classes1.intersects(classes2)); + EXPECT_FALSE(classes2.intersects(classes1)); + + classes2.insert("one"); + + EXPECT_TRUE(classes1.intersects(classes2)); + EXPECT_TRUE(classes2.intersects(classes1)); +} diff --git a/src/lib/dhcpsrv/cfg_option.cc b/src/lib/dhcpsrv/cfg_option.cc index 4471fcd167..5fbb2dee5b 100644 --- a/src/lib/dhcpsrv/cfg_option.cc +++ b/src/lib/dhcpsrv/cfg_option.cc @@ -65,22 +65,7 @@ OptionDescriptor::allowedForClientClasses(const ClientClasses& cclasses) const { return (true); } - if (cclasses.size() > client_classes_.size()) { - for (const auto& cclass : client_classes_) { - if (cclasses.contains(cclass)) { - return (true); - } - } - } - else { - for (const auto& cclass : cclasses) { - if (client_classes_.contains(cclass)) { - return (true); - } - } - } - - return (false); + return (client_classes_.intersects(cclasses)); } CfgOption::CfgOption() diff --git a/src/lib/dhcpsrv/dhcpsrv_messages.cc b/src/lib/dhcpsrv/dhcpsrv_messages.cc index 34e868c03b..51c71a4714 100644 --- a/src/lib/dhcpsrv/dhcpsrv_messages.cc +++ b/src/lib/dhcpsrv/dhcpsrv_messages.cc @@ -47,6 +47,7 @@ extern const isc::log::MessageID DHCPSRV_CFGMGR_USE_ADDRESS = "DHCPSRV_CFGMGR_US extern const isc::log::MessageID DHCPSRV_CFGMGR_USE_ALLOCATOR = "DHCPSRV_CFGMGR_USE_ALLOCATOR"; extern const isc::log::MessageID DHCPSRV_CFGMGR_USE_UNICAST = "DHCPSRV_CFGMGR_USE_UNICAST"; extern const isc::log::MessageID DHCPSRV_CLASS_WITH_ADDTIONAL_AND_LIFETIMES = "DHCPSRV_CLASS_WITH_ADDTIONAL_AND_LIFETIMES"; +extern const isc::log::MessageID DHCPSRV_CLIENT_CLASS_DEPRECATED = "DHCPSRV_CLIENT_CLASS_DEPRECATED"; extern const isc::log::MessageID DHCPSRV_CLOSE_DB = "DHCPSRV_CLOSE_DB"; extern const isc::log::MessageID DHCPSRV_DDNS_TTL_PERCENT_TOO_SMALL = "DHCPSRV_DDNS_TTL_PERCENT_TOO_SMALL"; extern const isc::log::MessageID DHCPSRV_DHCP4O6_RECEIVED_BAD_PACKET = "DHCPSRV_DHCP4O6_RECEIVED_BAD_PACKET"; @@ -217,6 +218,7 @@ const char* values[] = { "DHCPSRV_CFGMGR_USE_ALLOCATOR", "using the %1 allocator for %2 leases in subnet %3", "DHCPSRV_CFGMGR_USE_UNICAST", "listening on unicast address %1, on interface %2", "DHCPSRV_CLASS_WITH_ADDTIONAL_AND_LIFETIMES", "class: %1 has 'only-in-additional-list' true while specifying one or more lease life time values. Life time values will be ignored.", + "DHCPSRV_CLIENT_CLASS_DEPRECATED", "The parameter 'client-class' is deprecated. Use 'client-classes' list parameter instead", "DHCPSRV_CLOSE_DB", "closing currently open %1 database", "DHCPSRV_DDNS_TTL_PERCENT_TOO_SMALL", "ddns-ttl-percent %1 of lease lifetime %2 is too small, ignoring it", "DHCPSRV_DHCP4O6_RECEIVED_BAD_PACKET", "received bad DHCPv4o6 packet: %1", diff --git a/src/lib/dhcpsrv/dhcpsrv_messages.h b/src/lib/dhcpsrv/dhcpsrv_messages.h index a0ae287b01..b586981a1c 100644 --- a/src/lib/dhcpsrv/dhcpsrv_messages.h +++ b/src/lib/dhcpsrv/dhcpsrv_messages.h @@ -48,6 +48,7 @@ extern const isc::log::MessageID DHCPSRV_CFGMGR_USE_ADDRESS; extern const isc::log::MessageID DHCPSRV_CFGMGR_USE_ALLOCATOR; extern const isc::log::MessageID DHCPSRV_CFGMGR_USE_UNICAST; extern const isc::log::MessageID DHCPSRV_CLASS_WITH_ADDTIONAL_AND_LIFETIMES; +extern const isc::log::MessageID DHCPSRV_CLIENT_CLASS_DEPRECATED; extern const isc::log::MessageID DHCPSRV_CLOSE_DB; extern const isc::log::MessageID DHCPSRV_DDNS_TTL_PERCENT_TOO_SMALL; extern const isc::log::MessageID DHCPSRV_DHCP4O6_RECEIVED_BAD_PACKET; diff --git a/src/lib/dhcpsrv/dhcpsrv_messages.mes b/src/lib/dhcpsrv/dhcpsrv_messages.mes index cdb3a42c63..88c05fc78f 100644 --- a/src/lib/dhcpsrv/dhcpsrv_messages.mes +++ b/src/lib/dhcpsrv/dhcpsrv_messages.mes @@ -958,7 +958,7 @@ included in the message. The database access string specified a database type (given in the message) that is unknown to the software. This is a configuration error. -% DHCPSRV_CLASS_WITH_ADDTIONAL_AND_LIFETIMES class: %1 has 'only-in-additional-list' true while specifying one or more lease life time values. Life time values will be ignored. +% DHCPSRV_CLASS_WITH_ADDITIONAL_AND_LIFETIMES class: %1 has 'only-in-additional-list' true while specifying one or more lease life time values. Life time values will be ignored. This warning is emitted whenever a class is configured with 'only-in-addition-list' true as well as specifying one or more lease life time parameters (e.g. 'valid-lifetime', @@ -966,3 +966,8 @@ more lease life time parameters (e.g. 'valid-lifetime', are evaluated after lease assignment, thus parameters that would otherwise impact lease life times will have no affect. +% DHCPSRV_CLIENT_CLASS_DEPRECATED The parameter 'client-class' is deprecated. Use 'client-classes' list parameter instead +This warning message is emitted when configuration parsing detects +the use of the deprecated 'client-class' parameter. It has +been replaced by 'client-classes'. Users should migrate +to the new list parameter. diff --git a/src/lib/dhcpsrv/network.cc b/src/lib/dhcpsrv/network.cc index bb9511fffb..8ea291f6a4 100644 --- a/src/lib/dhcpsrv/network.cc +++ b/src/lib/dhcpsrv/network.cc @@ -67,18 +67,19 @@ Network::getRelayAddresses() const { bool Network::clientSupported(const isc::dhcp::ClientClasses& classes) const { - if (client_class_.empty()) { - // There is no class defined for this network, so we do - // support everyone. + if (client_classes_.empty()) { + // Empty list for this network, so we support everyone. return (true); } - return (classes.contains(client_class_)); + return (client_classes_.intersects(classes)); } void Network::allowClientClass(const isc::dhcp::ClientClass& class_name) { - client_class_ = class_name; + if (!client_classes_.contains(class_name)) { + client_classes_.insert(class_name); + } } void @@ -88,11 +89,6 @@ Network::addAdditionalClass(const isc::dhcp::ClientClass& class_name) { } } -const ClientClasses& -Network::getAdditionalClasses() const { - return (additional_classes_); -} - Optional<IOAddress> Network::getGlobalProperty(Optional<IOAddress> property, const int global_index, @@ -135,19 +131,15 @@ Network::toElement() const { relay_map->set("ip-addresses", address_list); map->set("relay", relay_map); - // Set client-class - if (!client_class_.unspecified()) { - map->set("client-class", Element::create(client_class_.get())); + // Set client-classes + if (!client_classes_.empty()) { + map->set("client-classes", client_classes_.toElement()); } // Set evaluate-additional-classes - const ClientClasses& classes = getAdditionalClasses(); - if (!classes.empty()) { - ElementPtr class_list = Element::createList(); - for (auto const& it : classes) { - class_list->add(Element::create(it)); - } - map->set("evaluate-additional-classes", class_list); + if (!additional_classes_.empty()) { + map->set("evaluate-additional-classes", + additional_classes_.toElement()); } // T1, T2, and Valid are optional for SharedNetworks, and diff --git a/src/lib/dhcpsrv/network.h b/src/lib/dhcpsrv/network.h index fb934c4e27..a460b677cf 100644 --- a/src/lib/dhcpsrv/network.h +++ b/src/lib/dhcpsrv/network.h @@ -211,7 +211,7 @@ public: /// @brief Constructor. Network() - : iface_name_(), client_class_(), t1_(), t2_(), valid_(), + : iface_name_(), client_classes_(), t1_(), t2_(), valid_(), reservations_global_(false, true), reservations_in_subnet_(true, true), reservations_out_of_pool_(false, true), cfg_option_(new CfgOption()), calculate_tee_times_(), t1_percent_(), t2_percent_(), @@ -327,8 +327,8 @@ public: /// @return True if a relay with the given address is found, false otherwise bool hasRelayAddress(const asiolink::IOAddress& address) const; - /// @brief Checks whether this network supports client that belongs to - /// specified classes. + /// @brief Checks whether this network supports a client that belongs to + /// the specified classes. /// /// This method checks whether a client that belongs to given classes can /// use this network. For example, if this class is reserved for client @@ -336,45 +336,41 @@ public: /// it is supported. On the other hand, client belonging to classes /// "foobar" and "zyxxy" is not supported. /// - /// @note: changed the planned white and black lists idea to a simple - /// client class name. - /// /// @param client_classes list of all classes the client belongs to /// @return true if client can be supported, false otherwise virtual bool clientSupported(const isc::dhcp::ClientClasses& client_classes) const; - /// @brief Sets the supported class to class class_name + /// @brief Adds class clas_name to the allowed client classes list. /// /// @param class_name client class to be supported by this network void allowClientClass(const isc::dhcp::ClientClass& class_name); + /// @brief Returns the list of allowed client classes. + const ClientClasses& getClientClasses() const { + return (client_classes_); + } + + /// @brief Returns the mutable list of allowed client classes. + ClientClasses& getMutableClientClasses() { + return (client_classes_); + } + /// @brief Adds class class_name to the additional classes list. /// /// @param class_name client class to add void addAdditionalClass(const isc::dhcp::ClientClass& class_name); /// @brief Returns the additional classes list. - const ClientClasses& getAdditionalClasses() const; + const ClientClasses& getAdditionalClasses() const { + return (additional_classes_); + } /// @brief Returns the mutable additional classes list. ClientClasses& getMutableAdditionalClasses() { return (additional_classes_); } - /// @brief returns the client class - /// - /// @note The returned reference is only valid as long as the object - /// returned it is valid. - /// - /// @param inheritance inheritance mode to be used. - /// @return client class @ref client_class_ - util::Optional<ClientClass> - getClientClass(const Inheritance& inheritance = Inheritance::ALL) const { - return (getProperty<Network>(&Network::getClientClass, client_class_, - inheritance)); - } - /// @brief Return valid-lifetime for addresses in that prefix /// /// @param inheritance inheritance mode to be used. @@ -1145,12 +1141,13 @@ protected: /// See @ref RelayInfo for detailed description. RelayInfo relay_; - /// @brief Optional definition of a client class + /// @brief List of client classes allowed to use this network. /// - /// If defined, only clients belonging to that class will be allowed to use - /// this particular network. The default value for this is an empty string, - /// which means that any client is allowed, regardless of its class. - util::Optional<ClientClass> client_class_; + /// If not empty, only clients belonging to at least one of the classes + /// in this list will be allowed to use this particular network. By default + /// the list is empty which means that any client is allowed, regardless + /// of its class membership. + ClientClasses client_classes_; /// @brief Additional classes /// diff --git a/src/lib/dhcpsrv/parsers/base_network_parser.cc b/src/lib/dhcpsrv/parsers/base_network_parser.cc index 85e97ebbf6..cc3bb5b45d 100644 --- a/src/lib/dhcpsrv/parsers/base_network_parser.cc +++ b/src/lib/dhcpsrv/parsers/base_network_parser.cc @@ -281,5 +281,42 @@ BaseNetworkParser::getAdditionalClassesElem(ConstElementPtr params, } } +void +BaseNetworkParser::getClientClassesElem(ConstElementPtr params, + ClassAdderFunc adder_func) { + // Try setting up client client classes. + ConstElementPtr class_elem = params->get("client-class"); + ConstElementPtr class_list = params->get("client-classes"); + if (class_elem) { + if (!class_list) { + LOG_WARN(dhcpsrv_logger, DHCPSRV_CLIENT_CLASS_DEPRECATED); + if (class_elem->getType() != Element::string) { + isc_throw(DhcpConfigError, "invalid class name (" << class_elem->getPosition() << ")"); + } + + if (!class_elem->stringValue().empty()) { + (adder_func)(class_elem->stringValue()); + } + } else { + isc_throw(isc::dhcp::DhcpConfigError, + "cannot specify both 'client-class' and " + "'client-classes'. Use only the latter."); + } + } + + if (class_list) { + const std::vector<data::ElementPtr>& classes = class_list->listValue(); + for (auto const& cclass : classes) { + if ((cclass->getType() != Element::string) || + cclass->stringValue().empty()) { + isc_throw(DhcpConfigError, "invalid class name (" << cclass->getPosition() << ")"); + } + + (adder_func)(cclass->stringValue()); + } + } +} + + } // end of namespace isc::dhcp } // end of namespace isc diff --git a/src/lib/dhcpsrv/parsers/base_network_parser.h b/src/lib/dhcpsrv/parsers/base_network_parser.h index c4e2a14b4e..f61cca2002 100644 --- a/src/lib/dhcpsrv/parsers/base_network_parser.h +++ b/src/lib/dhcpsrv/parsers/base_network_parser.h @@ -138,6 +138,16 @@ public: /// @throw DhcpConfigError if both entries are present. static void getAdditionalClassesElem(data::ConstElementPtr params, ClassAdderFunc adder_func); + + /// @brief Fetches the element for either 'client-classes' or deprecated + /// 'client-class' + /// + /// @param params configuration element tree to search. + /// @param adder_func function to add class names to an object's client class list. + /// @return Element referred to or an empty pointer. + /// @throw DhcpConfigError if both entries are present. + static void getClientClassesElem(data::ConstElementPtr params, + ClassAdderFunc adder_func); }; } // end of namespace isc::dhcp diff --git a/src/lib/dhcpsrv/parsers/dhcp_parsers.cc b/src/lib/dhcpsrv/parsers/dhcp_parsers.cc index f6be7c794a..2d6e0631d5 100644 --- a/src/lib/dhcpsrv/parsers/dhcp_parsers.cc +++ b/src/lib/dhcpsrv/parsers/dhcp_parsers.cc @@ -523,6 +523,11 @@ PoolParser::parse(PoolStoragePtr pools, } } + // Setup client class list. + BaseNetworkParser::getClientClassesElem(pool_structure, + std::bind(&Pool::allowClientClass, + pool, ph::_1)); + // Setup additional class list. BaseNetworkParser::getAdditionalClassesElem(pool_structure, std::bind(&Pool::addAdditionalClass, @@ -890,6 +895,10 @@ Subnet4ConfigParser::initSubnet(data::ConstElementPtr params, } } + // Setup client class list. + getClientClassesElem(params, std::bind(&Network::allowClientClass, + subnet4, ph::_1)); + // Setup additional class list. getAdditionalClassesElem(params, std::bind(&Network::addAdditionalClass, subnet4, ph::_1)); @@ -1167,6 +1176,11 @@ PdPoolParser::parse(PoolStoragePtr pools, ConstElementPtr pd_pool, } } + // Setup client class list. + BaseNetworkParser::getClientClassesElem(pd_pool, + std::bind(&Pool::allowClientClass, + pool_, ph::_1)); + // Setup additional class list. BaseNetworkParser::getAdditionalClassesElem(pd_pool, std::bind(&Pool::addAdditionalClass, @@ -1399,6 +1413,10 @@ Subnet6ConfigParser::initSubnet(data::ConstElementPtr params, } } + // Setup client class list. + getClientClassesElem(params, std::bind(&Network::allowClientClass, + subnet6, ph::_1)); + // Setup additional class list. getAdditionalClassesElem(params, std::bind(&Network::addAdditionalClass, subnet6, ph::_1)); diff --git a/src/lib/dhcpsrv/parsers/shared_network_parser.cc b/src/lib/dhcpsrv/parsers/shared_network_parser.cc index d9d8a0e7c0..100eef35e3 100644 --- a/src/lib/dhcpsrv/parsers/shared_network_parser.cc +++ b/src/lib/dhcpsrv/parsers/shared_network_parser.cc @@ -153,19 +153,17 @@ SharedNetwork4Parser::parse(const data::ConstElementPtr& shared_network_data, } } - if (shared_network_data->contains("client-class")) { - std::string client_class = getString(shared_network_data, "client-class"); - if (!client_class.empty()) { - shared_network->allowClientClass(client_class); - } - } - ConstElementPtr user_context = shared_network_data->get("user-context"); if (user_context) { shared_network->setContext(user_context); } // Setup additional class list. + getClientClassesElem(shared_network_data, + std::bind(&Network::allowClientClass, + shared_network, ph::_1)); + + // Setup additional class list. getAdditionalClassesElem(shared_network_data, std::bind(&Network::addAdditionalClass, shared_network, ph::_1)); @@ -313,19 +311,17 @@ SharedNetwork6Parser::parse(const data::ConstElementPtr& shared_network_data, parser->parse(cfg_option, json, encapsulate_options); } - if (shared_network_data->contains("client-class")) { - std::string client_class = getString(shared_network_data, "client-class"); - if (!client_class.empty()) { - shared_network->allowClientClass(client_class); - } - } - ConstElementPtr user_context = shared_network_data->get("user-context"); if (user_context) { shared_network->setContext(user_context); } // Setup additional class list. + getClientClassesElem(shared_network_data, + std::bind(&Network::allowClientClass, + shared_network, ph::_1)); + + // Setup additional class list. getAdditionalClassesElem(shared_network_data, std::bind(&Network::addAdditionalClass, shared_network, ph::_1)); diff --git a/src/lib/dhcpsrv/parsers/simple_parser4.cc b/src/lib/dhcpsrv/parsers/simple_parser4.cc index 0319814249..b170be6fc5 100644 --- a/src/lib/dhcpsrv/parsers/simple_parser4.cc +++ b/src/lib/dhcpsrv/parsers/simple_parser4.cc @@ -223,6 +223,7 @@ const SimpleKeywords SimpleParser4::SUBNET4_PARAMETERS = { { "interface", Element::string }, { "id", Element::integer }, { "client-class", Element::string }, + { "client-classes", Element::list }, { "require-client-classes", Element::list }, { "evaluate-additional-classes", Element::list }, { "reservations", Element::list }, @@ -271,7 +272,6 @@ const SimpleKeywords SimpleParser4::SUBNET4_PARAMETERS = { /// interface. const SimpleDefaults SimpleParser4::SUBNET4_DEFAULTS = { { "interface", Element::string, "" }, - { "client-class", Element::string, "" }, { "4o6-interface", Element::string, "" }, { "4o6-interface-id", Element::string, "" }, { "4o6-subnet", Element::string, "" }, @@ -291,7 +291,6 @@ const SimpleDefaults SimpleParser4::SHARED_SUBNET4_DEFAULTS = { /// @brief This table defines default values for each IPv4 shared network. const SimpleDefaults SimpleParser4::SHARED_NETWORK4_DEFAULTS = { - { "client-class", Element::string, "" }, { "interface", Element::string, "" } }; @@ -331,6 +330,7 @@ const SimpleKeywords SimpleParser4::POOL4_PARAMETERS = { { "pool-id", Element::integer }, { "option-data", Element::list }, { "client-class", Element::string }, + { "client-classes", Element::list }, { "require-client-classes", Element::list }, { "evaluate-additional-classes", Element::list }, { "user-context", Element::map }, @@ -360,6 +360,7 @@ const SimpleKeywords SimpleParser4::SHARED_NETWORK4_PARAMETERS = { { "reservations-in-subnet", Element::boolean }, { "reservations-out-of-pool", Element::boolean }, { "client-class", Element::string }, + { "client-classes", Element::list }, { "require-client-classes", Element::list }, { "evaluate-additional-classes", Element::list }, { "valid-lifetime", Element::integer }, diff --git a/src/lib/dhcpsrv/parsers/simple_parser6.cc b/src/lib/dhcpsrv/parsers/simple_parser6.cc index 89fedf0a81..b158afdf37 100644 --- a/src/lib/dhcpsrv/parsers/simple_parser6.cc +++ b/src/lib/dhcpsrv/parsers/simple_parser6.cc @@ -53,7 +53,8 @@ const SimpleKeywords SimpleParser6::GLOBAL6_PARAMETERS = { { "mac-sources", Element::list }, { "relay-supplied-options", Element::list }, { "host-reservation-identifiers", Element::list }, - { "client-classes", Element::list }, + { "client-class", Element::string }, + { "client-classes", Element::string }, { "option-def", Element::list }, { "option-data", Element::list }, { "hooks-libraries", Element::list }, @@ -223,6 +224,7 @@ const SimpleKeywords SimpleParser6::SUBNET6_PARAMETERS = { { "id", Element::integer }, { "rapid-commit", Element::boolean }, { "client-class", Element::string }, + { "client-classes", Element::list }, { "require-client-classes", Element::list }, { "evaluate-additional-classes", Element::list }, { "reservations", Element::list }, @@ -262,7 +264,6 @@ const SimpleKeywords SimpleParser6::SUBNET6_PARAMETERS = { /// defined on global level. const SimpleDefaults SimpleParser6::SUBNET6_DEFAULTS = { { "interface", Element::string, "" }, - { "client-class", Element::string, "" }, { "rapid-commit", Element::boolean, "false" }, // rapid-commit disabled by default { "interface-id", Element::string, "" } }; @@ -277,7 +278,6 @@ const SimpleDefaults SimpleParser6::SHARED_SUBNET6_DEFAULTS = { /// @brief This table defines default values for each IPv6 shared network. const SimpleDefaults SimpleParser6::SHARED_NETWORK6_DEFAULTS = { - { "client-class", Element::string, "" }, { "interface", Element::string, "" }, { "interface-id", Element::string, "" }, { "rapid-commit", Element::boolean, "false" } // rapid-commit disabled by default @@ -322,6 +322,7 @@ const SimpleKeywords SimpleParser6::POOL6_PARAMETERS = { { "pool-id", Element::integer }, { "option-data", Element::list }, { "client-class", Element::string }, + { "client-classes", Element::list }, { "require-client-classes", Element::list }, { "evaluate-additional-classes", Element::list }, { "user-context", Element::map }, @@ -341,6 +342,7 @@ const SimpleKeywords SimpleParser6::PD_POOL6_PARAMETERS = { { "pool-id", Element::integer }, { "option-data", Element::list }, { "client-class", Element::string }, + { "client-classes", Element::list }, { "require-client-classes", Element::list }, { "evaluate-additional-classes", Element::list }, { "excluded-prefix", Element::string }, @@ -368,6 +370,7 @@ const SimpleKeywords SimpleParser6::SHARED_NETWORK6_PARAMETERS = { { "reservations-in-subnet", Element::boolean }, { "reservations-out-of-pool", Element::boolean }, { "client-class", Element::string }, + { "client-classes", Element::list }, { "require-client-classes", Element::list }, { "evaluate-additional-classes", Element::list }, { "preferred-lifetime", Element::integer }, diff --git a/src/lib/dhcpsrv/pool.cc b/src/lib/dhcpsrv/pool.cc index bb4da02483..2c80084e6e 100644 --- a/src/lib/dhcpsrv/pool.cc +++ b/src/lib/dhcpsrv/pool.cc @@ -21,19 +21,31 @@ namespace dhcp { Pool::Pool(Lease::Type type, const isc::asiolink::IOAddress& first, const isc::asiolink::IOAddress& last) : id_(0), first_(first), last_(last), type_(type), capacity_(0), - cfg_option_(new CfgOption()), client_class_("") { + cfg_option_(new CfgOption()) { } -bool Pool::inRange(const isc::asiolink::IOAddress& addr) const { +bool +Pool::inRange(const isc::asiolink::IOAddress& addr) const { return (first_ <= addr && addr <= last_); } -bool Pool::clientSupported(const ClientClasses& classes) const { - return (client_class_.empty() || classes.contains(client_class_)); +bool +Pool::clientSupported(const ClientClasses& classes) const { + return (client_classes_.empty() || client_classes_.intersects(classes)); } -void Pool::allowClientClass(const ClientClass& class_name) { - client_class_ = class_name; +void +Pool::allowClientClass(const ClientClass& class_name) { + if (!client_classes_.contains(class_name)) { + client_classes_.insert(class_name); + } +} + +void +Pool::addAdditionalClass(const isc::dhcp::ClientClass& class_name) { + if (!additional_classes_.contains(class_name)) { + additional_classes_.insert(class_name); + } } std::string @@ -115,20 +127,15 @@ Pool::toElement() const { ConstCfgOptionPtr opts = getCfgOption(); map->set("option-data", opts->toElement()); - // Set client-class - const ClientClass& cclass = getClientClass(); - if (!cclass.empty()) { - map->set("client-class", Element::create(cclass)); + // Set client-classes + if (!client_classes_.empty()) { + map->set("client-classes", client_classes_.toElement()); } // Set evaluate-additional-classes - const ClientClasses& classes = getAdditionalClasses(); - if (!classes.empty()) { - ElementPtr class_list = Element::createList(); - for (auto const& it : classes) { - class_list->add(Element::create(it)); - } - map->set("evaluate-additional-classes", class_list); + if (!additional_classes_.empty()) { + map->set("evaluate-additional-classes", + additional_classes_.toElement()); } if (id_) { diff --git a/src/lib/dhcpsrv/pool.h b/src/lib/dhcpsrv/pool.h index 85301790c3..7d2ad92090 100644 --- a/src/lib/dhcpsrv/pool.h +++ b/src/lib/dhcpsrv/pool.h @@ -114,35 +114,29 @@ public: /// @brief Checks whether this pool supports client that belongs to /// specified classes. /// - /// @todo: currently doing the same as network which needs improving. - /// /// @param client_classes list of all classes the client belongs to /// @return true if client can be supported, false otherwise bool clientSupported(const ClientClasses& client_classes) const; - /// @brief Sets the supported class to class class_name + /// @brief Adds class clas_name to the allowed client classes list. /// - /// @param class_name client class to be supported by this pool - void allowClientClass(const ClientClass& class_name); + /// @param class_name client class to be supported by this network + void allowClientClass(const isc::dhcp::ClientClass& class_name); - /// @brief returns the client class - /// - /// @note The returned reference is only valid as long as the object - /// returned is valid. - /// - /// @return client class @ref client_class_ - const ClientClass& getClientClass() const { - return (client_class_); + /// @brief Returns the list of allowed client classes. + const ClientClasses& getClientClasses() const { + return (client_classes_); + } + + /// @brief Returns the mutable list of allowed client classes. + ClientClasses& getMutableClientClasses() { + return (client_classes_); } /// @brief Adds class class_name to the additional classes list. /// /// @param class_name client class to add - void addAdditionalClass(const ClientClass& class_name) { - if (!additional_classes_.contains(class_name)) { - additional_classes_.insert(class_name); - } - } + void addAdditionalClass(const ClientClass& class_name); /// @brief Returns the additional classes list. const ClientClasses& getAdditionalClasses() const { @@ -216,10 +210,13 @@ protected: /// @brief Pointer to the option data configuration for this pool. CfgOptionPtr cfg_option_; - /// @brief Optional definition of a client class + /// @brief List of client classes allowed to use this pool. /// - /// @ref Network::client_class_ - ClientClass client_class_; + /// If not empty, only clients belonging to at least one of the classes + /// in this list will be allowed to use this particular pool. By default + /// the list is empty which means that any client is allowed, regardless + /// of its class membership. + ClientClasses client_classes_; /// @brief Additional classes /// diff --git a/src/lib/dhcpsrv/shared_network.cc b/src/lib/dhcpsrv/shared_network.cc index eedfe41b32..acf3ee5666 100644 --- a/src/lib/dhcpsrv/shared_network.cc +++ b/src/lib/dhcpsrv/shared_network.cc @@ -308,7 +308,7 @@ public: if (preferred_subnet == s) { continue; } - if (s->getClientClass().get() != selected_subnet->getClientClass().get()) { + if (s->getClientClasses() != selected_subnet->getClientClasses()) { continue; } auto current_subnet_state = s->getAllocationState(lease_type); diff --git a/src/lib/dhcpsrv/tests/cfg_subnets4_unittest.cc b/src/lib/dhcpsrv/tests/cfg_subnets4_unittest.cc index 6d56ef1951..43b5be363e 100644 --- a/src/lib/dhcpsrv/tests/cfg_subnets4_unittest.cc +++ b/src/lib/dhcpsrv/tests/cfg_subnets4_unittest.cc @@ -1082,6 +1082,7 @@ TEST(CfgSubnets4Test, unparseSubnet) { Subnet4Ptr subnet3(new Subnet4(IOAddress("192.0.2.128"), 26, 1, 2, 3, 125)); subnet1->allowClientClass("foo"); + subnet1->allowClientClass("bar"); subnet1->setT1Percent(0.45); subnet1->setT2Percent(0.70); @@ -1141,7 +1142,7 @@ TEST(CfgSubnets4Test, unparseSubnet) { " \"valid-lifetime\": 3,\n" " \"min-valid-lifetime\": 3,\n" " \"max-valid-lifetime\": 3,\n" - " \"client-class\": \"foo\",\n" + " \"client-classes\": [ \"foo\", \"bar\" ],\n" " \"4o6-interface\": \"\",\n" " \"4o6-interface-id\": \"\",\n" " \"4o6-subnet\": \"\",\n" @@ -1253,7 +1254,7 @@ TEST(CfgSubnets4Test, unparsePool) { " \"option-data\": [ ],\n" " \"pool\": \"192.0.2.64/26\",\n" " \"user-context\": { \"foo\": \"bar\" },\n" - " \"client-class\": \"bar\",\n" + " \"client-classes\": [ \"bar\" ],\n" " \"evaluate-additional-classes\": [ \"foo\" ]\n" " }\n" " ]\n" @@ -1426,7 +1427,7 @@ TEST(CfgSubnets4Test, teeTimePercentValidation) { " \"next-server\": \"\", \n" " \"server-hostname\": \"\", \n" " \"boot-file-name\": \"\", \n" - " \"client-class\": \"\", \n" + " \"client-classes\": [] , \n" " \"evaluate-additional-classes\": [] \n," " \"reservations-global\": false, \n" " \"reservations-in-subnet\": true, \n" @@ -1495,8 +1496,8 @@ TEST(CfgSubnets4Test, validLifetimeValidation) { " \"next-server\": \"\", \n" " \"server-hostname\": \"\", \n" " \"boot-file-name\": \"\", \n" - " \"client-class\": \"\", \n" - " \"evaluate-additional-classes\": [] \n," + " \"client-classes\": [], \n" + " \"evaluate-additional-classes\": [], \n" " \"reservations-global\": false, \n" " \"reservations-in-subnet\": true, \n" " \"reservations-out-of-pool\": false, \n" @@ -1757,8 +1758,8 @@ TEST(CfgSubnets4Test, hostnameSanitizierValidation) { " \"next-server\": \"\", \n" " \"server-hostname\": \"\", \n" " \"boot-file-name\": \"\", \n" - " \"client-class\": \"\", \n" - " \"evaluate-additional-classes\": [] \n," + " \"client-classes\": [], \n" + " \"evaluate-additional-classes\": [], \n" " \"reservations-global\": false, \n" " \"reservations-in-subnet\": true, \n" " \"reservations-out-of-pool\": false, \n" @@ -1837,8 +1838,8 @@ TEST(CfgSubnets4Test, cacheParamValidation) { " \"next-server\": \"\", \n" " \"server-hostname\": \"\", \n" " \"boot-file-name\": \"\", \n" - " \"client-class\": \"\", \n" - " \"evaluate-additional-classes\": [] \n," + " \"client-classes\": [], \n" + " \"evaluate-additional-classes\": [], \n" " \"reservations-global\": false, \n" " \"reservations-in-subnet\": true, \n" " \"reservations-out-of-pool\": false, \n" diff --git a/src/lib/dhcpsrv/tests/cfg_subnets6_unittest.cc b/src/lib/dhcpsrv/tests/cfg_subnets6_unittest.cc index 4cc07fa0ab..72fc840c73 100644 --- a/src/lib/dhcpsrv/tests/cfg_subnets6_unittest.cc +++ b/src/lib/dhcpsrv/tests/cfg_subnets6_unittest.cc @@ -357,7 +357,9 @@ TEST(CfgSubnets6Test, selectSubnetByNetworkRelayAddress) { EXPECT_EQ(subnet3, cfg.selectSubnet(selector)); // Modify the client classes associated with the first two subnets. + subnet1->getMutableClientClasses().clear(); subnet1->allowClientClass("subnet1"); + subnet2->getMutableClientClasses().clear(); subnet2->allowClientClass("subnet2"); // This time the non-matching classes should prevent selection. @@ -677,6 +679,7 @@ TEST(CfgSubnets6Test, unparseSubnet) { OptionPtr ifaceid = generateInterfaceId("relay.eth0"); subnet1->setInterfaceId(ifaceid); subnet1->allowClientClass("foo"); + subnet1->allowClientClass("bar"); subnet1->setT1Percent(0.45); subnet1->setT2Percent(0.70); @@ -737,7 +740,7 @@ TEST(CfgSubnets6Test, unparseSubnet) { " \"valid-lifetime\": 4,\n" " \"min-valid-lifetime\": 4,\n" " \"max-valid-lifetime\": 4,\n" - " \"client-class\": \"foo\",\n" + " \"client-classes\": [ \"foo\", \"bar\" ],\n" " \"pools\": [ ],\n" " \"pd-pools\": [ ],\n" " \"option-data\": [ ],\n" @@ -846,7 +849,7 @@ TEST(CfgSubnets6Test, unparsePool) { " \"pool\": \"2001:db8:1:1::/64\",\n" " \"user-context\": { \"foo\": \"bar\" },\n" " \"option-data\": [ ],\n" - " \"client-class\": \"bar\",\n" + " \"client-classes\": [ \"bar\" ],\n" " \"evaluate-additional-classes\": [ \"foo\" ]\n" " }\n" " ],\n" @@ -909,7 +912,7 @@ TEST(CfgSubnets6Test, unparsePdPool) { " \"excluded-prefix\": \"2001:db8:3::\",\n" " \"excluded-prefix-len\": 64,\n" " \"option-data\": [ ],\n" - " \"client-class\": \"bar\"\n" + " \"client-classes\": [ \"bar\" ]\n" " }\n" " ],\n" " \"option-data\": [ ]\n" @@ -1227,8 +1230,8 @@ TEST(CfgSubnets6Test, teeTimePercentValidation) { " \"renew-timer\": 100, \n" " \"rebind-timer\": 200, \n" " \"valid-lifetime\": 300, \n" - " \"client-class\": \"\", \n" - " \"evaluate-additional-classes\": [] \n," + " \"client-classes\": [], \n" + " \"evaluate-additional-classes\": [], \n" " \"reservations-global\": false, \n" " \"reservations-in-subnet\": true, \n" " \"reservations-out-of-pool\": false \n" @@ -1292,8 +1295,8 @@ TEST(CfgSubnets6Test, preferredLifetimeValidation) { " \"renew-timer\": 100, \n" " \"rebind-timer\": 200, \n" " \"valid-lifetime\": 300, \n" - " \"client-class\": \"\", \n" - " \"evaluate-additional-classes\": [] \n," + " \"client-classes\": [], \n" + " \"evaluate-additional-classes\": [], \n" " \"reservations-global\": false, \n" " \"reservations-in-subnet\": true, \n" " \"reservations-out-of-pool\": false \n" @@ -1547,8 +1550,8 @@ TEST(CfgSubnets6Test, hostnameSanitizierValidation) { " \"renew-timer\": 100, \n" " \"rebind-timer\": 200, \n" " \"valid-lifetime\": 300, \n" - " \"client-class\": \"\", \n" - " \"evaluate-additional-classes\": [] \n," + " \"client-classes\": [], \n" + " \"evaluate-additional-classes\": [], \n" " \"reservations-global\": false, \n" " \"reservations-in-subnet\": true, \n" " \"reservations-out-of-pool\": false \n" @@ -1619,8 +1622,8 @@ TEST(CfgSubnets6Test, cacheParamValidation) { " \"renew-timer\": 100, \n" " \"rebind-timer\": 200, \n" " \"valid-lifetime\": 300, \n" - " \"client-class\": \"\", \n" - " \"evaluate-additional-classes\": [] \n," + " \"client-classes\": [], \n" + " \"evaluate-additional-classes\": [], \n" " \"reservations-global\": false, \n" " \"reservations-in-subnet\": true, \n" " \"reservations-out-of-pool\": false \n" diff --git a/src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc b/src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc index 6415e197a2..4b46de77df 100644 --- a/src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc +++ b/src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc @@ -2898,8 +2898,7 @@ TEST_F(ParseConfigTest, defaultSubnet4) { EXPECT_TRUE(subnet->getIface().unspecified()); EXPECT_TRUE(subnet->getIface().empty()); - EXPECT_TRUE(subnet->getClientClass().unspecified()); - EXPECT_TRUE(subnet->getClientClass().empty()); + EXPECT_TRUE(subnet->getClientClasses().empty()); EXPECT_TRUE(subnet->getValid().unspecified()); EXPECT_EQ(0, subnet->getValid().get()); @@ -3017,8 +3016,7 @@ TEST_F(ParseConfigTest, defaultSubnet6) { EXPECT_TRUE(subnet->getIface().unspecified()); EXPECT_TRUE(subnet->getIface().empty()); - EXPECT_TRUE(subnet->getClientClass().unspecified()); - EXPECT_TRUE(subnet->getClientClass().empty()); + EXPECT_TRUE(subnet->getClientClasses().empty()); EXPECT_TRUE(subnet->getValid().unspecified()); EXPECT_EQ(0, subnet->getValid().get()); @@ -3122,8 +3120,7 @@ TEST_F(ParseConfigTest, defaultSharedNetwork4) { EXPECT_TRUE(network->getIface().unspecified()); EXPECT_TRUE(network->getIface().empty()); - EXPECT_TRUE(network->getClientClass().unspecified()); - EXPECT_TRUE(network->getClientClass().empty()); + EXPECT_TRUE(network->getClientClasses().empty()); EXPECT_TRUE(network->getValid().unspecified()); EXPECT_EQ(0, network->getValid().get()); @@ -3215,8 +3212,7 @@ TEST_F(ParseConfigTest, defaultSharedNetwork6) { EXPECT_TRUE(network->getIface().unspecified()); EXPECT_TRUE(network->getIface().empty()); - EXPECT_TRUE(network->getClientClass().unspecified()); - EXPECT_TRUE(network->getClientClass().empty()); + EXPECT_TRUE(network->getClientClasses().empty()); EXPECT_TRUE(network->getValid().unspecified()); EXPECT_EQ(0, network->getValid().get()); @@ -3916,4 +3912,207 @@ TEST_F(DhcpParserTest, deprecatedRequireClientClassesPool6) { " 'evaluate-additional-classes'. Use only the latter."); } +// Verify that deprecated client-class is handled properly +// by Subnet4 parser. +TEST_F(DhcpParserTest, deprecatedClientClassSubnet4) { + // Valid empty entry. + std::string config = + R"^({ + "id": 1, + "subnet": "192.0.2.0/24", + "client-class": "" + })^"; + + ElementPtr config_element = Element::fromJSON(config); + + // Parse configuration specified above. + Subnet4ConfigParser parser(AF_INET); + Subnet4Ptr subnet; + + ASSERT_NO_THROW(subnet = parser.parse(config_element)); + ASSERT_TRUE(subnet); + + EXPECT_EQ(subnet->getClientClasses().size(), 0); + + // Valid entry. + config = + R"^({ + "id": 1, + "subnet": "192.0.2.0/24", + "client-class": "one" + })^"; + + config_element = Element::fromJSON(config); + + // Parse configuration specified above. + ASSERT_NO_THROW(subnet = parser.parse(config_element)); + ASSERT_TRUE(subnet); + + const auto cclasses = subnet->getClientClasses(); + EXPECT_EQ(cclasses.size(), 1); + auto cclass = cclasses.begin(); + EXPECT_EQ(*cclass, "one"); + + // Invalid entry specifies both parameters. + config = + R"^({ + "id": 1, + "subnet": "192.0.2.0/24", + "client-class": "one", + "client-classes": [ "one", "two" ] + })^"; + + config_element = Element::fromJSON(config); + + // Should throw a complaint. + ASSERT_THROW_MSG(parser.parse(config_element), + DhcpConfigError, + "subnet configuration failed: " + "cannot specify both 'client-class' and" + " 'client-classes'. Use only the latter."); +} + +// Verify that deprecated client-class is handled properly +// by Subnet6 parser. +TEST_F(DhcpParserTest, deprecatedClientClassSubnet6) { + // Valid empty entry. + std::string config = + R"^({ + "id": 1, + "subnet": "2001:db8::/64", + "client-class": "" + })^"; + + ElementPtr config_element = Element::fromJSON(config); + + // Parse configuration specified above. + Subnet6ConfigParser parser(AF_INET); + Subnet6Ptr subnet; + + ASSERT_NO_THROW(subnet = parser.parse(config_element)); + ASSERT_TRUE(subnet); + + EXPECT_EQ(subnet->getClientClasses().size(), 0); + + // Valid entry. + config = + R"^({ + "id": 1, + "subnet": "2001:db8::/64", + "client-class": "one" + })^"; + + config_element = Element::fromJSON(config); + + // Parse configuration specified above. + ASSERT_NO_THROW(subnet = parser.parse(config_element)); + ASSERT_TRUE(subnet); + + const auto cclasses = subnet->getClientClasses(); + EXPECT_EQ(cclasses.size(), 1); + auto cclass = cclasses.begin(); + EXPECT_EQ(*cclass, "one"); + + // Invalid entry specifies both parameters. + config = + R"^({ + "id": 1, + "subnet": "2001:db8::/64", + "client-class": "one", + "client-classes": [ "one", "two" ] + })^"; + + config_element = Element::fromJSON(config); + + // Should throw a complaint. + ASSERT_THROW_MSG(parser.parse(config_element), + DhcpConfigError, + "subnet configuration failed: " + "cannot specify both 'client-class' and" + " 'client-classes'. Use only the latter."); +} + +// Verify that deprecated client-class is handled properly +// by Pool4 parser. +TEST_F(DhcpParserTest, deprecatedClientClassPool4) { + // Valid entry. + std::string config = + R"^({ + "pool": "192.0.2.0/24", + "client-class": "one" + })^"; + + ElementPtr config_element = Element::fromJSON(config); + + // Parse configuration specified above. + Pool4Parser parser; + PoolStoragePtr pools(new PoolStorage()); + + ASSERT_NO_THROW(parser.parse(pools, config_element, AF_INET)); + EXPECT_EQ(1, pools->size()); + + const auto cclasses = (*pools)[0]->getClientClasses(); + EXPECT_EQ(cclasses.size(), 1); + auto cclass = cclasses.begin(); + EXPECT_EQ(*cclass, "one"); + + // Invalid entry specifies both parameters. + config = + R"^({ + "pool": "192.0.2.0/24", + "client-class": "one", + "client-classes": [ "one", "two" ] + })^"; + + config_element = Element::fromJSON(config); + + // Should throw a complaint. + ASSERT_THROW_MSG(parser.parse(pools, config_element, AF_INET), + DhcpConfigError, + "cannot specify both 'client-class' and" + " 'client-classes'. Use only the latter."); +} + +// Verify that deprecated client-class is handled properly +// by Pool6 parser. We only test TYPE_NA and as the same code is +// used for either v6 pool type. +TEST_F(DhcpParserTest, deprecatedClientClassPool6) { + // Valid entry. + std::string config = + R"^({ + "pool": "2001:db8::/64", + "client-class": "one" + })^"; + + ElementPtr config_element = Element::fromJSON(config); + + // Parse configuration specified above. + Pool6Parser parser; + PoolStoragePtr pools(new PoolStorage()); + + ASSERT_NO_THROW(parser.parse(pools, config_element, AF_INET6, Lease::TYPE_NA)); + EXPECT_EQ(1, pools->size()); + + const auto cclasses = (*pools)[0]->getClientClasses(); + EXPECT_EQ(cclasses.size(), 1); + auto cclass = cclasses.begin(); + EXPECT_EQ(*cclass, "one"); + + // Invalid entry specifies both parameters. + config = + R"^({ + "pool": "2001:db8::/64", + "client-class": "one", + "client-classes": [ "one", "two" ] + })^"; + + config_element = Element::fromJSON(config); + + // Should throw a complaint. + ASSERT_THROW_MSG(parser.parse(pools, config_element, AF_INET6, Lease::TYPE_NA), + DhcpConfigError, + "cannot specify both 'client-class' and" + " 'client-classes'. Use only the latter."); +} + } // Anonymous namespace diff --git a/src/lib/dhcpsrv/tests/network_unittest.cc b/src/lib/dhcpsrv/tests/network_unittest.cc index a1b57743aa..f0d2ac6801 100644 --- a/src/lib/dhcpsrv/tests/network_unittest.cc +++ b/src/lib/dhcpsrv/tests/network_unittest.cc @@ -186,13 +186,6 @@ TEST_F(NetworkTest, inheritanceSupport4) { // For each parameter for which inheritance is supported run // the test that checks if the values are inherited properly. - - { - SCOPED_TRACE("client_class"); - testNetworkInheritance<TestNetwork>(&Network::getClientClass, - &Network::allowClientClass, - "n", "g", false); - } { SCOPED_TRACE("valid-lifetime"); testNetworkInheritance<TestNetwork>(&Network::getValid, &Network::setValid, diff --git a/src/lib/dhcpsrv/tests/pool_unittest.cc b/src/lib/dhcpsrv/tests/pool_unittest.cc index 548d36bba8..a3424a3fd9 100644 --- a/src/lib/dhcpsrv/tests/pool_unittest.cc +++ b/src/lib/dhcpsrv/tests/pool_unittest.cc @@ -196,8 +196,8 @@ TEST(Pool4Test, userContext) { EXPECT_EQ(ctx->str(), pool->getContext()->str()); } -// This test checks that handling for client-class is valid. -TEST(Pool4Test, clientClass) { +// This test checks that handling for client-classes is valid. +TEST(Pool4Test, clientClasses) { // Create a pool. Pool4Ptr pool(new Pool4(IOAddress("192.0.2.0"), IOAddress("192.0.2.255"))); @@ -220,7 +220,7 @@ TEST(Pool4Test, clientClass) { three_classes.insert("baz"); // No class restrictions defined, any client should be supported - EXPECT_TRUE(pool->getClientClass().empty()); + EXPECT_TRUE(pool->getClientClasses().empty()); EXPECT_TRUE(pool->clientSupported(no_class)); EXPECT_TRUE(pool->clientSupported(foo_class)); EXPECT_TRUE(pool->clientSupported(bar_class)); @@ -228,7 +228,7 @@ TEST(Pool4Test, clientClass) { // Let's allow only clients belonging to "bar" class. pool->allowClientClass("bar"); - EXPECT_EQ("bar", pool->getClientClass()); + EXPECT_TRUE(pool->getClientClasses().contains("bar")); EXPECT_FALSE(pool->clientSupported(no_class)); EXPECT_FALSE(pool->clientSupported(foo_class)); @@ -624,7 +624,7 @@ TEST(Pool6Test, clientClass) { three_classes.insert("baz"); // No class restrictions defined, any client should be supported - EXPECT_TRUE(pool.getClientClass().empty()); + EXPECT_TRUE(pool.getClientClasses().empty()); EXPECT_TRUE(pool.clientSupported(no_class)); EXPECT_TRUE(pool.clientSupported(foo_class)); EXPECT_TRUE(pool.clientSupported(bar_class)); @@ -632,7 +632,7 @@ TEST(Pool6Test, clientClass) { // Let's allow only clients belonging to "bar" class. pool.allowClientClass("bar"); - EXPECT_EQ("bar", pool.getClientClass()); + EXPECT_TRUE(pool.getClientClasses().contains("bar")); EXPECT_FALSE(pool.clientSupported(no_class)); EXPECT_FALSE(pool.clientSupported(foo_class)); diff --git a/src/lib/dhcpsrv/tests/shared_network_parser_unittest.cc b/src/lib/dhcpsrv/tests/shared_network_parser_unittest.cc index adb72e1ad0..8474fe3fc1 100644 --- a/src/lib/dhcpsrv/tests/shared_network_parser_unittest.cc +++ b/src/lib/dhcpsrv/tests/shared_network_parser_unittest.cc @@ -254,7 +254,7 @@ TEST_F(SharedNetwork4ParserTest, parse) { // Check basic parameters. EXPECT_TRUE(network->getAuthoritative()); - EXPECT_EQ("srv1", network->getClientClass().get()); + EXPECT_TRUE(network->getClientClasses().contains("srv1")); EXPECT_EQ("bird", network->getName()); EXPECT_EQ("eth1961", network->getIface().get()); EXPECT_EQ(99, network->getT1().get()); @@ -369,7 +369,7 @@ TEST_F(SharedNetwork4ParserTest, clientClassMatchClientIdAuthoritative) { network = parser.parse(config_element); ASSERT_TRUE(network); - EXPECT_EQ("alpha", network->getClientClass().get()); + EXPECT_TRUE(network->getClientClasses().contains("alpha")); EXPECT_FALSE(network->getMatchClientId()); @@ -669,7 +669,7 @@ TEST_F(SharedNetwork6ParserTest, parse) { ASSERT_TRUE(network); // Check basic parameters. - EXPECT_EQ("srv1", network->getClientClass().get()); + EXPECT_TRUE(network->getClientClasses().contains("srv1")); EXPECT_EQ("bird", network->getName()); EXPECT_EQ("eth1961", network->getIface().get()); EXPECT_EQ(211, network->getPreferred().get()); @@ -867,7 +867,7 @@ TEST_F(SharedNetwork6ParserTest, clientClass) { network = parser.parse(config_element); ASSERT_TRUE(network); - EXPECT_EQ("alpha", network->getClientClass().get()); + EXPECT_TRUE(network->getClientClasses().contains("alpha")); } // This test verifies that it's possible to specify evaluate-additional-classes @@ -1087,7 +1087,6 @@ TEST_F(SharedNetwork4ParserTest, deprecatedRequireClientClasses) { " (<string>:1:2)"); } - // Verify that deprecated require-client-classes is handled properly // by v6 parser. TEST_F(SharedNetwork6ParserTest, deprecatedRequireClientClasses) { @@ -1095,8 +1094,7 @@ TEST_F(SharedNetwork6ParserTest, deprecatedRequireClientClasses) { std::string config = R"^({ "name": "foo", - "require-client-classes": [ "one", "two" ] - })^"; + "require-client-classes": [ "one", "two" ] })^"; ElementPtr config_element = Element::fromJSON(config); @@ -1132,4 +1130,89 @@ TEST_F(SharedNetwork6ParserTest, deprecatedRequireClientClasses) { " (<string>:1:2)"); } +// Verify that deprecated client-class is handled properly +// by v4 parser. +TEST_F(SharedNetwork4ParserTest, deprecatedClientClass) { + // Valid entry. + std::string config = + R"^({ + "name": "foo", + "client-class": "one" + })^"; + + ElementPtr config_element = Element::fromJSON(config); + + // Parse configuration specified above. + SharedNetwork4Parser parser; + SharedNetwork4Ptr network; + + ASSERT_NO_THROW(network = parser.parse(config_element)); + ASSERT_TRUE(network); + + const auto cclasses = network->getClientClasses(); + EXPECT_EQ(cclasses.size(), 1); + auto cclass = cclasses.begin(); + EXPECT_EQ(*cclass, "one"); + + // Invalid entry specifies both parameters. + config = + R"^({ + "name": "foo", + "client-class": "one", + "client-classes": [ "one", "two" ] + })^"; + + config_element = Element::fromJSON(config); + + // Should throw a complaint. + ASSERT_THROW_MSG(parser.parse(config_element), + DhcpConfigError, + "cannot specify both 'client-class' and" + " 'client-classes'. Use only the latter." + " (<string>:1:2)"); +} + +// Verify that deprecated client-class is handled properly +// by v6 parser. +TEST_F(SharedNetwork6ParserTest, deprecatedClientClass) { + // Valid entry. + std::string config = + R"^({ + "name": "foo", + "client-class": "one" + })^"; + + ElementPtr config_element = Element::fromJSON(config); + + // Parse configuration specified above. + SharedNetwork6Parser parser; + SharedNetwork6Ptr network; + + ASSERT_NO_THROW(network = parser.parse(config_element)); + ASSERT_TRUE(network); + + const auto cclasses = network->getClientClasses(); + EXPECT_EQ(cclasses.size(), 1); + auto cclass = cclasses.begin(); + EXPECT_EQ(*cclass, "one"); + + // Invalid entry specifies both parameters. + config = + R"^({ + "name": "foo", + "client-class": "one", + "client-classes": [ "one", "two" ] + })^"; + + config_element = Element::fromJSON(config); + + // Should throw a complaint. + ASSERT_THROW_MSG(parser.parse(config_element), + DhcpConfigError, + "cannot specify both 'client-class' and" + " 'client-classes'. Use only the latter." + " (<string>:1:2)"); +} + + } // end of anonymous namespace diff --git a/src/lib/dhcpsrv/tests/shared_network_unittest.cc b/src/lib/dhcpsrv/tests/shared_network_unittest.cc index 2cb76b289c..2006b1c474 100644 --- a/src/lib/dhcpsrv/tests/shared_network_unittest.cc +++ b/src/lib/dhcpsrv/tests/shared_network_unittest.cc @@ -77,8 +77,7 @@ TEST(SharedNetwork4Test, defaults) { EXPECT_TRUE(network->getIface().unspecified()); EXPECT_TRUE(network->getIface().empty()); - EXPECT_TRUE(network->getClientClass().unspecified()); - EXPECT_TRUE(network->getClientClass().empty()); + EXPECT_TRUE(network->getClientClasses().empty()); EXPECT_TRUE(network->getValid().unspecified()); EXPECT_EQ(0, network->getValid().get()); @@ -829,8 +828,7 @@ TEST(SharedNetwork6Test, defaults) { EXPECT_TRUE(network->getIface().unspecified()); EXPECT_TRUE(network->getIface().empty()); - EXPECT_TRUE(network->getClientClass().unspecified()); - EXPECT_TRUE(network->getClientClass().empty()); + EXPECT_TRUE(network->getClientClasses().empty()); EXPECT_TRUE(network->getValid().unspecified()); EXPECT_EQ(0, network->getValid().get()); diff --git a/src/lib/dhcpsrv/tests/subnet_unittest.cc b/src/lib/dhcpsrv/tests/subnet_unittest.cc index 1badddbd12..1a5d875f96 100644 --- a/src/lib/dhcpsrv/tests/subnet_unittest.cc +++ b/src/lib/dhcpsrv/tests/subnet_unittest.cc @@ -82,8 +82,7 @@ TEST(Subnet4Test, defaults) { EXPECT_TRUE(subnet.getIface().unspecified()); EXPECT_TRUE(subnet.getIface().empty()); - EXPECT_TRUE(subnet.getClientClass().unspecified()); - EXPECT_TRUE(subnet.getClientClass().empty()); + EXPECT_TRUE(subnet.getClientClasses().empty()); EXPECT_TRUE(subnet.getValid().unspecified()); EXPECT_EQ(0, subnet.getValid().get()); @@ -542,7 +541,7 @@ TEST(Subnet4Test, clientClass) { four_classes.insert("network"); // No class restrictions defined, any client should be supported - EXPECT_TRUE(subnet->getClientClass().empty()); + EXPECT_TRUE(subnet->getClientClasses().empty()); EXPECT_TRUE(subnet->clientSupported(no_class)); EXPECT_TRUE(subnet->clientSupported(foo_class)); EXPECT_TRUE(subnet->clientSupported(bar_class)); @@ -550,7 +549,7 @@ TEST(Subnet4Test, clientClass) { // Let's allow only clients belonging to "bar" class. subnet->allowClientClass("bar"); - EXPECT_EQ("bar", subnet->getClientClass().get()); + EXPECT_TRUE(subnet->getClientClasses().contains("bar")); EXPECT_FALSE(subnet->clientSupported(no_class)); EXPECT_FALSE(subnet->clientSupported(foo_class)); @@ -880,8 +879,7 @@ TEST(SharedNetwork6Test, defaults) { EXPECT_TRUE(subnet.getIface().unspecified()); EXPECT_TRUE(subnet.getIface().empty()); - EXPECT_TRUE(subnet.getClientClass().unspecified()); - EXPECT_TRUE(subnet.getClientClass().empty()); + EXPECT_TRUE(subnet.getClientClasses().empty()); EXPECT_TRUE(subnet.getValid().unspecified()); EXPECT_EQ(0, subnet.getValid().get()); @@ -1261,7 +1259,7 @@ TEST(Subnet6Test, clientClass) { four_classes.insert("network"); // No class restrictions defined, any client should be supported - EXPECT_TRUE(subnet->getClientClass().empty()); + EXPECT_TRUE(subnet->getClientClasses().empty()); EXPECT_TRUE(subnet->clientSupported(no_class)); EXPECT_TRUE(subnet->clientSupported(foo_class)); EXPECT_TRUE(subnet->clientSupported(bar_class)); @@ -1269,7 +1267,7 @@ TEST(Subnet6Test, clientClass) { // Let's allow only clients belonging to "bar" class. subnet->allowClientClass("bar"); - EXPECT_EQ("bar", subnet->getClientClass().get()); + EXPECT_TRUE(subnet->getClientClasses().contains("bar")); EXPECT_FALSE(subnet->clientSupported(no_class)); EXPECT_FALSE(subnet->clientSupported(foo_class)); diff --git a/src/lib/dhcpsrv/testutils/generic_cb_dhcp4_unittest.cc b/src/lib/dhcpsrv/testutils/generic_cb_dhcp4_unittest.cc index c7452c86b3..a35a1e79f3 100644 --- a/src/lib/dhcpsrv/testutils/generic_cb_dhcp4_unittest.cc +++ b/src/lib/dhcpsrv/testutils/generic_cb_dhcp4_unittest.cc @@ -1145,8 +1145,7 @@ GenericConfigBackendDHCPv4Test::getSubnet4WithOptionalUnspecifiedTest() { EXPECT_TRUE(returned_subnet->getIface().unspecified()); EXPECT_TRUE(returned_subnet->getIface().empty()); - EXPECT_TRUE(returned_subnet->getClientClass().unspecified()); - EXPECT_TRUE(returned_subnet->getClientClass().empty()); + EXPECT_TRUE(returned_subnet->getClientClasses().empty()); EXPECT_TRUE(returned_subnet->getValid().unspecified()); EXPECT_EQ(0, returned_subnet->getValid().get()); @@ -2159,8 +2158,7 @@ GenericConfigBackendDHCPv4Test::getSharedNetwork4WithOptionalUnspecifiedTest() { EXPECT_TRUE(returned_network->getIface().unspecified()); EXPECT_TRUE(returned_network->getIface().empty()); - EXPECT_TRUE(returned_network->getClientClass().unspecified()); - EXPECT_TRUE(returned_network->getClientClass().empty()); + EXPECT_TRUE(returned_network->getClientClasses().empty()); EXPECT_TRUE(returned_network->getValid().unspecified()); EXPECT_EQ(0, returned_network->getValid().get()); diff --git a/src/lib/dhcpsrv/testutils/generic_cb_dhcp6_unittest.cc b/src/lib/dhcpsrv/testutils/generic_cb_dhcp6_unittest.cc index 5130e923bc..593a51209c 100644 --- a/src/lib/dhcpsrv/testutils/generic_cb_dhcp6_unittest.cc +++ b/src/lib/dhcpsrv/testutils/generic_cb_dhcp6_unittest.cc @@ -1171,8 +1171,7 @@ GenericConfigBackendDHCPv6Test::getSubnet6WithOptionalUnspecifiedTest() { EXPECT_TRUE(returned_subnet->getIface().unspecified()); EXPECT_TRUE(returned_subnet->getIface().empty()); - EXPECT_TRUE(returned_subnet->getClientClass().unspecified()); - EXPECT_TRUE(returned_subnet->getClientClass().empty()); + EXPECT_TRUE(returned_subnet->getClientClasses().empty()); EXPECT_TRUE(returned_subnet->getValid().unspecified()); EXPECT_EQ(0, returned_subnet->getValid().get()); @@ -2177,8 +2176,7 @@ GenericConfigBackendDHCPv6Test::getSharedNetwork6WithOptionalUnspecifiedTest() { EXPECT_TRUE(returned_network->getIface().unspecified()); EXPECT_TRUE(returned_network->getIface().empty()); - EXPECT_TRUE(returned_network->getClientClass().unspecified()); - EXPECT_TRUE(returned_network->getClientClass().empty()); + EXPECT_TRUE(returned_network->getClientClasses().empty()); EXPECT_TRUE(returned_network->getValid().unspecified()); EXPECT_EQ(0, returned_network->getValid().get()); |