diff --git a/doc/sphinx/arm/dhcp6-srv.rst b/doc/sphinx/arm/dhcp6-srv.rst index 61b124f364..4b21372adc 100644 --- a/doc/sphinx/arm/dhcp6-srv.rst +++ b/doc/sphinx/arm/dhcp6-srv.rst @@ -1736,6 +1736,8 @@ types are given in :ref:`dhcp-types`. +--------------------------+-----------------+-----------------+-----------------+ | relay-id | 53 | binary | false | +--------------------------+-----------------+-----------------+-----------------+ + | ntp-server | 56 | empty | false | + +--------------------------+-----------------+-----------------+-----------------+ | v6-access-domain | 57 | fqdn | false | +--------------------------+-----------------+-----------------+-----------------+ | sip-ua-cs-list | 58 | fqdn | true | @@ -2190,6 +2192,15 @@ Further examples are provided in Kea sources in the ``all-options.json`` file in the ``doc/examples/kea6`` directory. The DHCPv4 option is nearly identical, and is described in :ref:`dnr4-options`. +.. _ntp-server-suboptions: + +NTP Server Suboptions +--------------------- + +NTP server option is a contaier of suboptions: ntp-server-address (1), ntp-server-multicast (2) +carrying an IPv6 address, and ntp-server-fqdn (3) carrying a FQDN in wire format defined +in the "v6-ntp-server-suboptions" option space. Each option instance carries one and only one +suboption as required by `RFC 5908 `__. .. _dhcp6-custom-options: @@ -7751,6 +7762,10 @@ The following standards are currently supported in Kea: query types) is supported. This requires the leasequery hook. See :ref:`hooks-lease-query` for details. +- *Network Time Protocol (NTP) Server Option for DHCPv6*: + `RFC 5908 `__: The NTP server option and its + suboptions are supported. See :ref:`ntp-server-suboptions` for details. + - *DHCPv6 Options for Network Boot*: `RFC 5970 `__: The network boot options are supported. diff --git a/src/bin/dhcp6/dhcp6_srv.cc b/src/bin/dhcp6/dhcp6_srv.cc index 226011395d..03162b8f90 100644 --- a/src/bin/dhcp6/dhcp6_srv.cc +++ b/src/bin/dhcp6/dhcp6_srv.cc @@ -1384,6 +1384,7 @@ Dhcpv6Srv::processPacketPktSend(hooks::CalloutHandlePtr& callout_handle, if (!skip_pack) { try { + LibDHCP::splitNtpServerOptions6(rsp->options_); rsp->pack(); } catch (const std::exception& e) { LOG_ERROR(options6_logger, DHCP6_PACK_FAIL) diff --git a/src/lib/dhcp/libdhcp++.cc b/src/lib/dhcp/libdhcp++.cc index 0e0dd448c8..ef22b682cc 100644 --- a/src/lib/dhcp/libdhcp++.cc +++ b/src/lib/dhcp/libdhcp++.cc @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -1251,6 +1252,25 @@ LibDHCP::packOptions6(OutputBuffer& buf, const OptionCollection& options) { } } +void +LibDHCP::splitNtpServerOptions6(OptionCollection& options) { + pair + range = options.equal_range(D6O_NTP_SERVER); + if (range.first == range.second) { + return; + } + auto ntp_servers = OptionCollection(range.first, range.second); + static_cast(options.erase(range.first, range.second)); + auto def = D6O_NTP_SERVER_DEF(); + for (auto opt : ntp_servers) { + for (auto sub : opt.second->getOptions()) { + auto new_option(new OptionCustom(def, Option::V6)); + new_option->addOption(sub.second); + options.insert(make_pair(D6O_NTP_SERVER, new_option)); + } + } +} + void LibDHCP::OptionFactoryRegister(Option::Universe u, uint16_t opt_type, Option::Factory* factory) { @@ -1311,6 +1331,7 @@ LibDHCP::initOptionDefs() { static_cast(LibDHCP::D6O_LQ_QUERY_DEF()); static_cast(LibDHCP::D6O_CLIENT_DATA_DEF()); static_cast(LibDHCP::D6O_LQ_RELAY_DATA_DEF()); + static_cast(LibDHCP::D6O_NTP_SERVER_DEF()); static_cast(LibDHCP::D6O_BOOTFILE_URL_DEF()); static_cast(LibDHCP::D6O_RSOO_DEF()); @@ -1587,6 +1608,24 @@ LibDHCP::D6O_LQ_RELAY_DATA_DEF() { return (*def); } +const OptionDefinition& +LibDHCP::D6O_NTP_SERVER_DEF() { + static OptionDefinitionPtr def = + LibDHCP::getOptionDef(DHCP6_OPTION_SPACE, D6O_NTP_SERVER); + static bool check_once(true); + if (check_once) { + isc_throw_assert(def); + isc_throw_assert(def->getName() == "ntp-server"); + isc_throw_assert(def->getCode() == D6O_NTP_SERVER); + isc_throw_assert(def->getType() == OPT_EMPTY_TYPE); + isc_throw_assert(!def->getArrayType()); + isc_throw_assert(def->getEncapsulatedSpace() == V6_NTP_SERVER_SPACE); + isc_throw_assert(def->getOptionSpaceName() == DHCP6_OPTION_SPACE); + check_once = false; + } + return (*def); +} + const OptionDefinition& LibDHCP::D6O_BOOTFILE_URL_DEF() { static OptionDefinitionPtr def = diff --git a/src/lib/dhcp/libdhcp++.h b/src/lib/dhcp/libdhcp++.h index 89a02be275..a2a0578699 100644 --- a/src/lib/dhcp/libdhcp++.h +++ b/src/lib/dhcp/libdhcp++.h @@ -256,6 +256,13 @@ public: static void packOptions6(isc::util::OutputBuffer& buf, const isc::dhcp::OptionCollection& options); + /// @brief Split NTP server option to one suboption per instance. + /// + /// See RFC 5908 for the requirement. + /// @param options The option container which needs to be updated with split + /// options. + static void splitNtpServerOptions6(isc::dhcp::OptionCollection& options); + /// @brief Parses provided buffer as DHCPv6 options and creates /// Option objects. /// @@ -448,6 +455,9 @@ public: /// @brief Get definition of D6O_LQ_RELAY_DATA option. static const OptionDefinition& D6O_LQ_RELAY_DATA_DEF(); + /// @brief Get definition of D6O_NTP_SERVER option. + static const OptionDefinition& D6O_NTP_SERVER_DEF(); + /// @brief Get definition of D6O_BOOTFILE_URL option. static const OptionDefinition& D6O_BOOTFILE_URL_DEF(); diff --git a/src/lib/dhcp/tests/libdhcp++_unittest.cc b/src/lib/dhcp/tests/libdhcp++_unittest.cc index 3d4ee37fe1..d4c006cfea 100644 --- a/src/lib/dhcp/tests/libdhcp++_unittest.cc +++ b/src/lib/dhcp/tests/libdhcp++_unittest.cc @@ -3797,10 +3797,8 @@ TEST_F(LibDhcpTest, v6NtpServer) { fqdn->toText()); // Build back the NTP server option. - OptionDefinitionPtr opt_def = - LibDHCP::getOptionDef(DHCP6_OPTION_SPACE, D6O_NTP_SERVER); - ASSERT_TRUE(opt_def); - ASSERT_NO_THROW(option.reset(new OptionCustom(*opt_def, Option::V6))); + auto opt_def = LibDHCP::D6O_NTP_SERVER_DEF(); + ASSERT_NO_THROW(option.reset(new OptionCustom(opt_def, Option::V6))); ASSERT_TRUE(option); // Add address. @@ -3849,4 +3847,49 @@ TEST_F(LibDhcpTest, v6NtpServer) { EXPECT_TRUE(memcmp(&bin[0], outbuf.getData(), bin.size()) == 0); } +// Check splitNtpServerOptions6. +TEST_F(LibDhcpTest, splitNtpServerOptions6) { + OptionCollection col; + auto def = LibDHCP::D6O_NTP_SERVER_DEF(); + OptionCustomPtr opt1(new OptionCustom(def, Option::V6)); + OptionCustomPtr opt2(new OptionCustom(def, Option::V6)); + OptionCustomPtr opt3(new OptionCustom(def, Option::V6)); + + // Fill first option with three addresses. + OptionDefinitionPtr addr_def = + LibDHCP::getOptionDef(V6_NTP_SERVER_SPACE, NTP_SUBOPTION_SRV_ADDR); + ASSERT_TRUE(addr_def); + OptionCustomPtr addr1(new OptionCustom(*addr_def, Option::V6)); + OptionCustomPtr addr2(new OptionCustom(*addr_def, Option::V6)); + OptionCustomPtr addr3(new OptionCustom(*addr_def, Option::V6)); + EXPECT_NO_THROW(addr1->writeAddress(IOAddress("2001:db8::abcd"))); + opt1->addOption(addr1); + EXPECT_NO_THROW(addr2->writeAddress(IOAddress("2001:db8::bcde"))); + opt1->addOption(addr2); + EXPECT_NO_THROW(addr3->writeAddress(IOAddress("2001:db8::cdef"))); + opt1->addOption(addr3); + col.insert(make_pair(D6O_NTP_SERVER, opt1)); + + // Leave second option empty. + col.insert(make_pair(D6O_NTP_SERVER, opt2)); + + // Fill third option with a FQDN. + OptionDefinitionPtr fqdn_def = + LibDHCP::getOptionDef(V6_NTP_SERVER_SPACE, NTP_SUBOPTION_SRV_FQDN); + ASSERT_TRUE(fqdn_def); + OptionCustomPtr fqdn(new OptionCustom(*fqdn_def, Option::V6)); + EXPECT_NO_THROW(fqdn->writeFqdn("foo.bar.")); + opt3->addOption(fqdn); + col.insert(make_pair(D6O_NTP_SERVER, opt3)); + + // Insert another option. + OptionPtr opts(new OptionString(Option::V6, D6O_BOOTFILE_URL, "foobar")); + col.insert(make_pair(D6O_BOOTFILE_URL, opts)); + + // Split them: expect 5 options (opt1 -> 3, opt2 -> 0, opt3 -> 1 and opts). + ASSERT_EQ(4, col.size()); + ASSERT_NO_THROW(LibDHCP::splitNtpServerOptions6(col)); + EXPECT_EQ(5, col.size()); +} + } // namespace