]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#4496] Checkpoint
authorFrancis Dupont <fdupont@isc.org>
Mon, 20 Jul 2026 20:35:01 +0000 (22:35 +0200)
committerFrancis Dupont <fdupont@isc.org>
Wed, 22 Jul 2026 12:13:16 +0000 (14:13 +0200)
src/lib/dhcpsrv/cfg_option.cc
src/lib/dhcpsrv/cfg_option.h
src/lib/dhcpsrv/tests/cfg_option_unittest.cc

index bccf7bb56101172698ebba4ba043e8e26c269887..3a07578b1c7186113409be1ed6dcad7af0d9dedb 100644 (file)
@@ -77,6 +77,10 @@ OptionDescriptor::allowedForClientClasses(const ClientClasses& cclasses) const {
     return (client_classes_.intersects(cclasses));
 }
 
+std::set<std::string> CfgOption::multiple_encapsulating_spaces_ = {
+    V6_NTP_SERVER_SPACE
+};
+
 CfgOption::CfgOption()
     : encapsulated_(false) {
 }
@@ -325,24 +329,28 @@ CfgOption::encapsulateInternal(const OptionPtr& option) {
     // Get encapsulated option space for the option.
     const std::string& encap_space = option->getEncapsulatedSpace();
     // Empty value means that no option space is encapsulated.
-    if (!encap_space.empty()) {
-        if (encap_space == DHCP4_OPTION_SPACE || encap_space == DHCP6_OPTION_SPACE) {
-            return;
+    if (encap_space.empty()) {
+        return;
+    }
+    if (encap_space == DHCP4_OPTION_SPACE || encap_space == DHCP6_OPTION_SPACE) {
+        return;
+    }
+    // Retrieve all options from the encapsulated option space.
+    OptionContainerPtr encap_options = getAll(encap_space);
+    for (auto const& encap_opt : *encap_options) {
+        if (option.get() == encap_opt.option_.get()) {
+            // Avoid recursion by not adding options to themselves.
+            continue;
         }
-        // Retrieve all options from the encapsulated option space.
-        OptionContainerPtr encap_options = getAll(encap_space);
-        for (auto const& encap_opt : *encap_options) {
-            if (option.get() == encap_opt.option_.get()) {
-                // Avoid recursion by not adding options to themselves.
-                continue;
-            }
 
-            // Add sub-option if there isn't one added already.
-            if (!option->getOption(encap_opt.option_->getType())) {
-                option->addOption(encap_opt.option_);
-            }
-            encapsulateInternal(encap_opt.option_);
+        // Add sub-option if there isn't one added already, or
+        // if encapsulating space is a multiple exception.
+        OptionPtr existing = option->getOption(encap_opt.option_->getType());
+        if (!existing ||
+            (multiple_encapsulating_spaces_.count(encap_space) > 0)) {
+            option->addOption(encap_opt.option_);
         }
+        encapsulateInternal(encap_opt.option_);
     }
 }
 
index d5c25585238f6b77d9c7bb2df05092c23a96ac56..9727a70aeed744a0e23ec3c7877cfd255b4c1e2f 100644 (file)
@@ -26,6 +26,7 @@
 #include <boost/shared_ptr.hpp>
 #include <stdint.h>
 #include <list>
+#include <set>
 #include <string>
 #include <vector>
 
@@ -603,6 +604,10 @@ public:
         return (encapsulated_);
     }
 
+    /// @brief Option spaces which can encapsulate mutiple sub-options
+    /// of the same type.
+    static std::set<std::string> multiple_encapsulating_spaces_;
+
     /// @brief Returns all options for the specified option space.
     ///
     /// This method will not return vendor options, i.e. having option space
index cfd8520d2ffacd7f8203ae9dc32127bd9a36d6f4..6625174123692659a9c3b88b276b260634b85432 100644 (file)
@@ -801,6 +801,46 @@ TEST_F(CfgOptionTest, encapsulate) {
     }
 }
 
+// This test verifies that multiple encapsulating spaces are supported,
+TEST_F(CfgOptionTest, multipleEncapsulatingSpaces) {
+    CfgOption cfg;
+
+    // Add a ntp-server option.
+    OptionPtr ntp_server(new Option(Option::V6, D6O_NTP_SERVER));
+    ntp_server->setEncapsulatedSpace(V6_NTP_SERVER_SPACE);
+    ASSERT_NO_THROW(cfg.add(ntp_server, false, false, DHCP6_OPTION_SPACE));
+
+    // Get ntp-server-address sub-option definition.
+    OptionDefinitionPtr def =
+        LibDHCP::getOptionDef(V6_NTP_SERVER_SPACE, NTP_SUBOPTION_SRV_ADDR);
+    ASSERT_TRUE(def);
+
+    // Add sub-options for 2001:db8::77 and 2001:db8::88.
+    IOAddress addr1("2001:db8::77");
+    auto const& buf1 = addr1.toBytes();
+    OptionCustomPtr sub1(new OptionCustom(*def, Option::V6, buf1));
+    ASSERT_NO_THROW(cfg.add(sub1, false, false, V6_NTP_SERVER_SPACE));
+    IOAddress addr2("2001:db8::88");
+    auto const& buf2 = addr2.toBytes();
+    OptionCustomPtr sub2(new OptionCustom(*def, Option::V6, buf2));
+    ASSERT_NO_THROW(cfg.add(sub2, false, false, V6_NTP_SERVER_SPACE));
+
+    // Encapsulate.
+    ASSERT_NO_THROW(cfg.encapsulate());
+
+    // Check we have ntp-server with 2 (not 1) sub-options.
+    OptionDescriptor desc = cfg.get(DHCP6_OPTION_SPACE, D6O_NTP_SERVER);
+    OptionPtr opt = desc.option_;
+    ASSERT_TRUE(opt);
+    EXPECT_EQ(D6O_NTP_SERVER, opt->getType());
+    EXPECT_EQ(V6_NTP_SERVER_SPACE, opt->getEncapsulatedSpace());
+    auto const& subs = opt->getOptions();
+    ASSERT_EQ(2U, subs.size());
+    for (auto const& sub : subs) {
+        EXPECT_EQ(NTP_SUBOPTION_SRV_ADDR, sub.first);
+    }
+}
+
 // This test verifies that an option can be deleted from the configuration.
 TEST_F(CfgOptionTest, deleteOptions) {
     CfgOption cfg;