]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#3583] Addressred review comments
authorThomas Markwalder <tmark@isc.org>
Fri, 11 Oct 2024 16:22:39 +0000 (12:22 -0400)
committerThomas Markwalder <tmark@isc.org>
Tue, 15 Oct 2024 17:51:57 +0000 (13:51 -0400)
modified:
    doc/sphinx/arm/classify.rst
    src/bin/dhcp4/dhcp4_srv.cc
    src/bin/dhcp4/tests/config_parser_unittest.cc
    src/bin/dhcp6/tests/config_parser_unittest.cc
    src/hooks/dhcp/mysql/mysql_cb_dhcp4.cc
    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/cfg_option.h
    src/lib/dhcpsrv/parsers/option_data_parser.cc
    src/lib/dhcpsrv/parsers/simple_parser4.cc
    src/lib/dhcpsrv/parsers/simple_parser6.cc
    src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc
    src/lib/dhcpsrv/testutils/generic_backend_unittest.cc

15 files changed:
doc/sphinx/arm/classify.rst
src/bin/dhcp4/dhcp4_srv.cc
src/bin/dhcp4/tests/config_parser_unittest.cc
src/bin/dhcp6/tests/config_parser_unittest.cc
src/hooks/dhcp/mysql/mysql_cb_dhcp4.cc
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/cfg_option.h
src/lib/dhcpsrv/parsers/option_data_parser.cc
src/lib/dhcpsrv/parsers/simple_parser4.cc
src/lib/dhcpsrv/parsers/simple_parser6.cc
src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc
src/lib/dhcpsrv/testutils/generic_backend_unittest.cc

index 1ec6556be0122e0ff9ca6ca474e717d71ea7fd7d..d28fe3aca3415a4a71eb248487fc19d477cd17dd 100644 (file)
@@ -1246,6 +1246,12 @@ the same value for option "foo":
         }]
     }
 
+The ``client-classes`` list is allowed in an option specification at
+any scope.  Option class-tagging is enforced at the time options are
+being added to the response which occurs after lease assignment just
+before the response is to be sent to the client.
+
+
 When ``never-send`` for an option is true at any scope, all 
 ``client-classes`` entries for that option are ignored. The
 option will not included.
index 99cad8b16fd719d161a05f6c91d9f1fdc5740f0d..6c590940d1ae9d18fc942d45860050a50c455ff5 100644 (file)
@@ -2342,6 +2342,7 @@ Dhcpv4Srv::appendRequestedVendorOptions(Dhcpv4Exchange& ex) {
         }
     }
 
+    const auto& cclasses = query->getClasses();
     for (uint32_t vendor_id : vendor_ids) {
 
         std::set<uint8_t> cancelled_opts;
@@ -2406,7 +2407,7 @@ Dhcpv4Srv::appendRequestedVendorOptions(Dhcpv4Exchange& ex) {
             if (!vendor_rsp->getOption(opt)) {
                 for (auto const& copts : co_list) {
                     OptionDescriptor desc = copts->get(vendor_id, opt);
-                    if (desc.option_ && desc.allowedForClientClasses(query->getClasses())) {
+                    if (desc.option_ && desc.allowedForClientClasses(cclasses)) {
                         vendor_rsp->addOption(desc.option_);
                         added = true;
                         break;
@@ -2446,6 +2447,7 @@ Dhcpv4Srv::appendBasicOptions(Dhcpv4Exchange& ex) {
     }
 
     Pkt4Ptr resp = ex.getResponse();
+    const auto& cclasses = ex.getQuery()->getClasses();
 
     // Try to find all 'required' options in the outgoing
     // message. Those that are not present will be added.
@@ -2456,7 +2458,7 @@ Dhcpv4Srv::appendBasicOptions(Dhcpv4Exchange& ex) {
             for (auto const& copts : co_list) {
                 OptionDescriptor desc = copts->get(DHCP4_OPTION_SPACE, required);
                 /// @todo TKM - not sure if otion class-tagging should be allowed here?
-                if (desc.option_ && desc.allowedForClientClasses(ex.getQuery()->getClasses())) {
+                if (desc.option_ && desc.allowedForClientClasses(cclasses)) {
                     resp->addOption(desc.option_);
                     break;
                 }
@@ -3635,7 +3637,7 @@ Dhcpv4Srv::setFixedFields(Dhcpv4Exchange& ex) {
 
     // Step 2: Try to set the values based on classes.
     // Any values defined in classes will override those from subnet level.
-    const ClientClasses classes = query->getClasses();
+    const ClientClasses& classes = query->getClasses();
     if (!classes.empty()) {
 
         // Let's get class definitions
index d310a17ebd72d9c6a094c703b216b11488e51927..8c81dfad8e20047f80e10fa24cb7de1325fb3d38 100644 (file)
@@ -8013,4 +8013,38 @@ TEST_F(Dhcp4ParserTest, storeDdnsConflictResolutionMode) {
     }
 }
 
+//This test verifies that duplicates in option-data.client-classes
+// are ignored and do not affect class order.
+TEST_F(Dhcp4ParserTest, optionClientClassesDuplicateCheck) {
+    std::string config = "{ " + genIfaceConfig() + ","
+        R"^(
+        "option-data": [{
+            "name": "domain-name",
+            "data": "example.com",
+            "client-classes": [ "foo", "bar", "foo", "bar" ]
+        }],
+        "rebind-timer": 2000,
+        "renew-timer": 1000,
+        "subnet4": [],
+        "valid-lifetime": 400
+        })^";
+
+    ConstElementPtr json;
+    ASSERT_NO_THROW(json = parseDHCP4(config));
+    extractConfig(config);
+
+    ConstElementPtr status;
+    ASSERT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+    checkResult(status, 0);
+
+    CfgOptionPtr cfg = CfgMgr::instance().getStagingCfg()->getCfgOption();
+    const auto desc = cfg->get(DHCP4_OPTION_SPACE, DHO_DOMAIN_NAME);
+    ASSERT_TRUE(desc.option_);
+    ASSERT_EQ(desc.client_classes_.size(), 2);
+    auto cclasses = desc.client_classes_.begin();
+    EXPECT_EQ(*cclasses, "foo");
+    ++cclasses;
+    EXPECT_EQ(*cclasses, "bar");
+}
+
 }  // namespace
index 3b50a5b57340ba48519947b6fcab8ac8f29a502e..1d7c73616c8be49dc40f3425a69ea780e87a4929 100644 (file)
@@ -8987,4 +8987,38 @@ TEST_F(Dhcp6ParserTest, storeDdnsConflictResolutionMode) {
     }
 }
 
+// This test verifies that duplicates in option-data.client-classes
+// are ignored and do not affect class order.
+TEST_F(Dhcp6ParserTest, optionClientClassesDuplicateCheck) {
+    std::string config = "{ " + genIfaceConfig() + ","
+        R"^(
+        "option-data": [{
+            "name": "domain-search",
+            "data": "example.com",
+            "client-classes": [ "foo", "bar", "foo", "bar" ]
+        }],
+        "rebind-timer": 2000,
+        "renew-timer": 1000,
+        "subnet6": [],
+        "valid-lifetime": 400
+        })^";
+
+    ConstElementPtr json;
+    ASSERT_NO_THROW(json = parseDHCP6(config));
+    extractConfig(config);
+
+    ConstElementPtr status;
+    ASSERT_NO_THROW(status = configureDhcp6Server(srv_, json));
+    checkResult(status, 0);
+
+    CfgOptionPtr cfg = CfgMgr::instance().getStagingCfg()->getCfgOption();
+    const auto desc = cfg->get(DHCP6_OPTION_SPACE, D6O_DOMAIN_SEARCH);
+    ASSERT_TRUE(desc.option_);
+    ASSERT_EQ(desc.client_classes_.size(), 2);
+    auto cclasses = desc.client_classes_.begin();
+    EXPECT_EQ(*cclasses, "foo");
+    ++cclasses;
+    EXPECT_EQ(*cclasses, "bar");
+}
+
 }  // namespace
index 27bc8fbc0f4f68f1cd07bd3d10e780b0cfbfe624..436b0839500de5923f6aa09e48e01c470daccfb7 100644 (file)
@@ -1870,7 +1870,7 @@ public:
             cc_binding,
             MySqlBinding::createString(tag),
             MySqlBinding::createInteger<uint8_t>(option->option_->getType()),
-            MySqlBinding::condCreateString(option->space_name_),
+            MySqlBinding::condCreateString(option->space_name_)
         };
 
         MySqlTransaction transaction(conn_);
@@ -2074,7 +2074,7 @@ public:
             cc_binding,
             MySqlBinding::createString(shared_network_name),
             MySqlBinding::createInteger<uint8_t>(option->option_->getType()),
-            MySqlBinding::condCreateString(option->space_name_),
+            MySqlBinding::condCreateString(option->space_name_)
         };
 
         boost::scoped_ptr<MySqlTransaction> transaction;
index b5ed577761f7d5ebe784e8bbd5a65ceab35d80aa..86f6b5367fb3b0ce04bd26b30c2399d4d3c3da95 100644 (file)
@@ -82,7 +82,7 @@ ClientClasses::toElement() const {
 }
 
 void
-ClientClasses::fromElement(isc::data::ElementPtr cc_list) {
+ClientClasses::fromElement(isc::data::ConstElementPtr cc_list) {
     if (cc_list) {
         clear();
         if (cc_list->getType() != Element::list) {
@@ -95,7 +95,7 @@ ClientClasses::fromElement(isc::data::ElementPtr cc_list) {
                 isc_throw(BadValue, "elements of list must be valid strings"); 
             }
 
-            insert(cclass->stringValue());
+            static_cast<void>(insert(cclass->stringValue()));
         }
     }
 }
index b07e22f68b04a60f67bb1570186a38b493c9b050..91e42da980031197a2f9e3eb8bebc035de1c6453 100644 (file)
@@ -124,10 +124,10 @@ namespace dhcp {
 
         /// @brief Copy constructor.
         ///
-        /// @param ClientClasses object to be copied.
+        /// @param other ClientClasses object to be copied.
         ClientClasses(const ClientClasses& other);
 
-        /// @brief Assigns the contents of on container to another
+        /// @brief Assigns the contents of on container to another.
         ClientClasses& operator=(const ClientClasses& other);
 
         /// @brief Compares two ClientClasses container for equality
@@ -135,7 +135,7 @@ namespace dhcp {
         /// @return True if the two containers are equal, false otherwise.
         bool equals(const ClientClasses& other) const;
 
-        /// @brief Compares two ClientClasses container for equality
+        /// @brief Compares two ClientClasses containers for equality.
         ///
         /// @return True if the two containers are equal, false otherwise.
         bool operator==(const ClientClasses& other) const {
@@ -227,9 +227,12 @@ namespace dhcp {
 
         /// @brief Sets contents from a ListElement
         ///
+        /// @param list JSON list of classes from which to populate
+        /// the container.
+        ///
         /// @throw BadValue if the element is not a list or contents
         /// are invalid
-        void fromElement(isc::data::ElementPtr list);
+        void fromElement(isc::data::ConstElementPtr list);
 
     private:
         /// @brief container part
index b0e58073ca93de6c3620a8a5b3333afa49d495d6..4c3be5ec9c5023398b5957749cab1e610df734a3 100644 (file)
@@ -253,4 +253,16 @@ TEST(ClassifyTest, ClientClassesFromElement) {
     cclasses_element = Element::createList();
     ASSERT_NO_THROW(classes.fromElement(cclasses_element));
     EXPECT_TRUE(classes.empty());
+
+    cclasses_element->add(Element::create("foo"));
+    cclasses_element->add(Element::create("bar"));
+    cclasses_element->add(Element::create("foo"));
+    cclasses_element->add(Element::create("bar"));
+
+    ASSERT_NO_THROW(classes.fromElement(cclasses_element));
+    ASSERT_EQ(classes.size(), 2);
+    auto cclass = classes.begin();
+    EXPECT_EQ(*cclass, "foo");
+    ++cclass;
+    EXPECT_EQ(*cclass, "bar");
 }
index 83bbedf5287e858b91b3e18ef308e94236a84851..1fca0521be13ec5c410c480a9fa706db67eb0fe5 100644 (file)
@@ -64,17 +64,25 @@ OptionDescriptor::allowedForClientClasses(const ClientClasses& cclasses) const {
     if (client_classes_.empty()) {
         return (true);
     }
-    
-    for (const auto& cclass : client_classes_) {
-        if (cclasses.contains(cclass)) {
-            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);
 }
 
-
 CfgOption::CfgOption()
     : encapsulated_(false) {
 }
@@ -108,7 +116,7 @@ CfgOption::add(const OptionDescriptor& desc, const std::string& option_space) {
     if (!desc.option_) {
         isc_throw(isc::BadValue, "option being configured must not be NULL");
 
-    } else  if (!OptionSpace::validateName(option_space)) {
+    } else if (!OptionSpace::validateName(option_space)) {
         isc_throw(isc::BadValue, "invalid option space name: '"
                   << option_space << "'");
     }
index 50e82a3f20e506179a1828887944ff4bdeb62f9e..f9c5382331b9e7f993403c7693753c3a03d96a2a 100644 (file)
@@ -817,8 +817,6 @@ private:
     VendorOptionSpaceCollection vendor_options_;
 };
 
-class CfgOption; // forward declaration 
-
 /// @name Pointers to the @c CfgOption objects.
 //@{
 
index 2710ffe624fea04d0050b806f516f458a6af824b..e75b524674dc8240e4a17d6522cd1e0d7ea48125 100644 (file)
@@ -441,11 +441,15 @@ OptionDataParser::createOption(ConstElementPtr option_data) {
         desc.setContext(user_context);
     }
 
+#if 1
+    desc.client_classes_.fromElement(client_classes);
+#else
     if (client_classes) {
         for (auto const& class_element : client_classes->listValue()) {
             desc.addClientClass(class_element->stringValue());
         }
     }
+#endif
 
     // All went good, so we can set the option space name.
     return (make_pair(desc, space_param));
index de0f7a63bae2a94ac7bcd43f353ae239ae6d6d79..ac47dcb0b04cb1e746d847bf6629cca2fd8c27bd 100644 (file)
@@ -190,8 +190,8 @@ const SimpleKeywords SimpleParser4::OPTION4_PARAMETERS = {
     { "never-send",     Element::boolean },
     { "user-context",   Element::map },
     { "comment",        Element::string },
-    { "metadata",       Element::map },
-    { "client-classes", Element::list }
+    { "client-classes", Element::list },
+    { "metadata",       Element::map }
 };
 
 /// @brief This table defines default values for options in DHCPv4.
index f58ceecb31e22039969e7035b3b8fe75187e9817..6359a1e96091ada5d635bea961fd2f13a82bc7f2 100644 (file)
@@ -184,8 +184,8 @@ const SimpleKeywords SimpleParser6::OPTION6_PARAMETERS = {
     { "never-send",     Element::boolean },
     { "user-context",   Element::map },
     { "comment",        Element::string },
-    { "metadata",       Element::map },
-    { "client-classes", Element::list }
+    { "client-classes", Element::list },
+    { "metadata",       Element::map }
 };
 
 /// @brief This table defines default values for options in DHCPv6.
index 6af3945a310b416f4374aa0630f6721684466305..d9fa7332040f83e0c1f265ea7d6991b913a3c805 100644 (file)
@@ -462,11 +462,12 @@ public:
         return (option_ptr);
     }
 
-    /// @brief Find the OptionDescriptor for a given space and code within the parser
-    /// context.
+    /// @brief Find the OptionDescriptor for a given space and code within
+    /// the parser context.
+    ///
     /// @param space is the space name of the desired option.
     /// @param code is the numeric "type" of the desired option.
-    /// @return an OptionDecriptorPtr to the descriptor found or an empty ptr
+    /// @return an OptionDecriptorPtr to the descriptor found or an empty ptr.
     OptionDescriptorPtr getOptionDescriptor(std::string space, uint32_t code) {
         OptionDescriptorPtr od_ptr;
         const auto &cfg_options = CfgMgr::instance().getStagingCfg()->getCfgOption();
@@ -1974,7 +1975,7 @@ TEST_F(ParseConfigTest, optionDataClientClassesEmpty4) {
     ASSERT_TRUE(od);
     EXPECT_TRUE(od->client_classes_.empty());
 
-    // We skip unparse test because client-classes is only emitited if not empty.
+    // We skip unparse test because client-classes is only emitted if not empty.
 }
 
 /// @brief Check parsing of a v6 option with a client-class list.
@@ -2053,7 +2054,7 @@ TEST_F(ParseConfigTest, optionDataClientClassesEmpty6) {
     ASSERT_TRUE(od);
     EXPECT_TRUE(od->client_classes_.empty());
 
-    // We skip unparse test because client-classes is only emitited if not empty.
+    // We skip unparse test because client-classes is only emitted if not empty.
 }
 
 // hooks-libraries element that does not contain anything.
index 8214676751ccb148c0fec2346bec180817894f03..ead2c5ea26c60a79c26291e8aa9319642ed26e0a 100644 (file)
@@ -109,6 +109,15 @@ GenericBackendTest::testOptionsEquivalent(const OptionDescriptor& ref_option,
     EXPECT_EQ(ref_option.persistent_, tested_option.persistent_);
     EXPECT_EQ(ref_option.cancelled_, tested_option.cancelled_);
     EXPECT_EQ(ref_option.space_name_, tested_option.space_name_);
+    EXPECT_EQ(ref_option.client_classes_, tested_option.client_classes_);
+    auto ref_ctx = ref_option.getContext();
+    auto test_ctx = tested_option.getContext();
+    if (ref_ctx) {
+        ASSERT_TRUE(test_ctx);
+        EXPECT_EQ(*test_ctx, *ref_ctx);
+    } else {
+        EXPECT_FALSE(test_ctx);
+    }
 }
 
 void