From: Francis Dupont Date: Mon, 20 Jul 2026 20:35:01 +0000 (+0200) Subject: [#4496] Checkpoint X-Git-Tag: Kea-3.3.0~29 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=6e91a7bb4d928bc883445f06c7a5d00a4d72b7aa;p=thirdparty%2Fkea.git [#4496] Checkpoint --- diff --git a/src/lib/dhcpsrv/cfg_option.cc b/src/lib/dhcpsrv/cfg_option.cc index bccf7bb561..3a07578b1c 100644 --- a/src/lib/dhcpsrv/cfg_option.cc +++ b/src/lib/dhcpsrv/cfg_option.cc @@ -77,6 +77,10 @@ OptionDescriptor::allowedForClientClasses(const ClientClasses& cclasses) const { return (client_classes_.intersects(cclasses)); } +std::set 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_); } } diff --git a/src/lib/dhcpsrv/cfg_option.h b/src/lib/dhcpsrv/cfg_option.h index d5c2558523..9727a70aee 100644 --- a/src/lib/dhcpsrv/cfg_option.h +++ b/src/lib/dhcpsrv/cfg_option.h @@ -26,6 +26,7 @@ #include #include #include +#include #include #include @@ -603,6 +604,10 @@ public: return (encapsulated_); } + /// @brief Option spaces which can encapsulate mutiple sub-options + /// of the same type. + static std::set multiple_encapsulating_spaces_; + /// @brief Returns all options for the specified option space. /// /// This method will not return vendor options, i.e. having option space diff --git a/src/lib/dhcpsrv/tests/cfg_option_unittest.cc b/src/lib/dhcpsrv/tests/cfg_option_unittest.cc index cfd8520d2f..6625174123 100644 --- a/src/lib/dhcpsrv/tests/cfg_option_unittest.cc +++ b/src/lib/dhcpsrv/tests/cfg_option_unittest.cc @@ -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;