]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#3592] modified in lib dhcp and dhcpsrv
authorThomas Markwalder <tmark@isc.org>
Wed, 30 Oct 2024 18:31:18 +0000 (14:31 -0400)
committerThomas Markwalder <tmark@isc.org>
Tue, 26 Nov 2024 17:19:56 +0000 (17:19 +0000)
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:
src/lib/dhcp/classify.cc
src/lib/dhcp/classify.h
src/lib/dhcp/tests/classify_unittest.cc
src/lib/dhcpsrv/cfg_option.cc
src/lib/dhcpsrv/dhcpsrv_messages.cc
src/lib/dhcpsrv/dhcpsrv_messages.h
src/lib/dhcpsrv/dhcpsrv_messages.mes
src/lib/dhcpsrv/network.cc
src/lib/dhcpsrv/network.h
src/lib/dhcpsrv/parsers/base_network_parser.cc
src/lib/dhcpsrv/parsers/base_network_parser.h
src/lib/dhcpsrv/parsers/dhcp_parsers.cc
src/lib/dhcpsrv/parsers/shared_network_parser.cc
src/lib/dhcpsrv/parsers/simple_parser4.cc
src/lib/dhcpsrv/parsers/simple_parser6.cc
src/lib/dhcpsrv/pool.cc
src/lib/dhcpsrv/pool.h
src/lib/dhcpsrv/shared_network.cc
src/lib/dhcpsrv/tests/cfg_subnets4_unittest.cc
src/lib/dhcpsrv/tests/cfg_subnets6_unittest.cc
src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc
src/lib/dhcpsrv/tests/network_unittest.cc
src/lib/dhcpsrv/tests/pool_unittest.cc
src/lib/dhcpsrv/tests/shared_network_parser_unittest.cc
src/lib/dhcpsrv/tests/shared_network_unittest.cc
src/lib/dhcpsrv/tests/subnet_unittest.cc
src/lib/dhcpsrv/testutils/generic_cb_dhcp4_unittest.cc
src/lib/dhcpsrv/testutils/generic_cb_dhcp6_unittest.cc

index 828b1376044676e33143746079233265a98f22dd..7a001ba6211cdcb980972cdf5e34f69c56925228 100644 (file)
@@ -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;
index d617952690750b6a042e6de169eed9906f269344..69cdffd41738b2a44414c169cdc475f697ba573c 100644 (file)
 /// @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
index 075af19547609522f5e565ab1b35227caa410a9d..f635817e637014fcb4e3cd9bb5b6d2674af1afd8 100644 (file)
@@ -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));
+}
index 4471fcd16752da9dc37669f1487152e9e2a71cfa..5fbb2dee5bbafa961d9fa0c32510da15c761a891 100644 (file)
@@ -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()
index 34e868c03b4c8185dc087091136efa572ded8915..51c71a47141805c3316d4da80ac33b58a10b3f37 100644 (file)
@@ -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",
index a0ae287b01409fc046d73220d6371f25723b8962..b586981a1cf836ba8c349c0b53021ed6a17794bd 100644 (file)
@@ -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;
index cdb3a42c63026386c813db3e496f1261315782fe..88c05fc78f84dd5c191a1150ab2d46ae32c4272b 100644 (file)
@@ -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.
index bb9511fffbb7af9002c1632bcdf18a5e668d14a7..8ea291f6a4137abbee8a17654e9c4e1a7853a9bb 100644 (file)
@@ -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
index fb934c4e2748cc340973f1c033635c9eca30c7b8..a460b677cfcecbe281420b723d817c1b3c6671d2 100644 (file)
@@ -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 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
     ///
index 85e97ebbf6fd3900464ee79207d1c1441fb18ff6..cc3bb5b45d85bbcf95cd622b13b37bda69fcd292 100644 (file)
@@ -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
index c4e2a14b4ecedc388bf7d1f487c91e948a8ae9ff..f61cca20020c4e18c23b890a3c04268495b19490 100644 (file)
@@ -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
index f6be7c794a7cf38f173ad90be5a3326da1323c65..2d6e0631d5286b2faffb038db99f3c72f7353d37 100644 (file)
@@ -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));
index d9d8a0e7c07be4278e29de53606555cd7e2a702f..100eef35e3c0b7b81466c26dc9afc6d60b0dffc5 100644 (file)
@@ -153,18 +153,16 @@ 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,
@@ -313,18 +311,16 @@ 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,
index 0319814249dac8ca17f8ed7e8f9620b27a35c87a..b170be6fc5c39827ea86125510dd7c4fcdcc150b 100644 (file)
@@ -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 },
index 89fedf0a819dd797773013a9ac54a2cea5016e48..b158afdf37bdd66ca90704ccbd41339eb5f66965 100644 (file)
@@ -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 },
index bb4da024832e6e66b272edfa60357316306e9efa..2c80084e6efcd8d2802836e1f7ab9f4a86b53d66 100644 (file)
@@ -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_) {
index 85301790c3bc4ae97ecea47af51758c52bdae99b..7d2ad92090c27d64fd55b03e7f8063da106cba7d 100644 (file)
@@ -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
     ///
index eedfe41b32b80489fec06ae67ebdb303d8369ee5..acf3ee5666a3e664bbd1ea1ac8a0ff49fa748eec 100644 (file)
@@ -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);
index 6d56ef1951faaac749c1313b45d6abb5647c9b48..43b5be363e11233eb316c96e3db58afb4429c8e6 100644 (file)
@@ -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"
index 4cc07fa0ab331ebaacbc294c4235354bd2f9fa58..72fc840c7388cc73bdfe4bbc29ef28ff50491806 100644 (file)
@@ -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"
index 6415e197a2790200eba956e8d5706c80b1e12e18..4b46de77df219478a17195fdb1c9d4b6ab0aeb01 100644 (file)
@@ -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
index a1b57743aa25e5ff605431b13f686b81ca61841f..f0d2ac6801b3d1e494ca35a924edd61c767ff1bd 100644 (file)
@@ -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,
index 548d36bba8da938027a9960420b9c7ebb0ceafe5..a3424a3fd949c06e6bf2170ba05f1a4a5cb59d2d 100644 (file)
@@ -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));
index adb72e1ad00a0ef75205e6ffad008051489bac6b..8474fe3fc1ea6eaa3a46fe150fcd4270f807002d 100644 (file)
@@ -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
index 2cb76b289c5ddcd4cf3fa8af85a2a3d5c4eccdde..2006b1c4742187a5e1dbdbbeccb222cb7a6577c4 100644 (file)
@@ -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());
index 1badddbd12d7834c8b0d0e4ea639084214f81a1e..1a5d875f96cfa0b93c795871c7abd3f3098591f8 100644 (file)
@@ -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));
index c7452c86b374a130750a552b9deccb32f9ca2380..a35a1e79f3663531dfdc13da1fcc90e4ba038ebc 100644 (file)
@@ -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());
index 5130e923bcc9618b1080c972c8dbc0e1869da1da..593a51209c9b9cad9ee751156a12ed4ff3e3514d 100644 (file)
@@ -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());