Browse Source

[master] Merge branch 'trac3699'

Marcin Siodelski 10 years ago
parent
commit
75b75c89db

+ 12 - 0
doc/guide/logging.xml

@@ -171,6 +171,12 @@
             libraries will be logged using this logger.</simpara>
           </listitem>
           <listitem>
+            <simpara><command>kea-dhcp4.hosts</command> - this logger is used
+            within the libdhcpsrv and it logs messages related to the management
+            of the DHCPv4 host reservations, i.e. retrieval of the resevations
+            and adding new reservations.</simpara>
+          </listitem>
+          <listitem>
             <simpara><command>kea-dhcp6</command> - this is the root logger for
             the DHCPv6 server. All components used by the DHCPv6 server inherit
             the settings from this logger if there is no specialized logger
@@ -193,6 +199,12 @@
             libraries will be logged using this logger.</simpara>
           </listitem>
           <listitem>
+            <simpara><command>kea-dhcp6.hosts</command> - this logger is used
+            within the libdhcpsrv and it logs messages related to the management
+            of the DHCPv6 host reservations, i.e. retrieval of the resevations
+            and adding new reservations.</simpara>
+          </listitem>
+          <listitem>
             <simpara><command>kea-dhcp-ddns</command> - this is the root logger for
             the kea-dhcp-ddns deamon. All components used by this deamon inherit
             the settings from this logger if there is no specialized logger

+ 2 - 0
src/lib/dhcpsrv/.gitignore

@@ -1,3 +1,5 @@
 /dhcpsrv_messages.cc
 /dhcpsrv_messages.h
+/hosts_messages.cc
+/hosts_messages.h
 /s-messages

+ 14 - 7
src/lib/dhcpsrv/Makefile.am

@@ -35,19 +35,22 @@ EXTRA_DIST += parsers/host_reservation_parser.h
 EXTRA_DIST += parsers/host_reservations_list_parser.h
 
 # Define rule to build logging source files from message file
-dhcpsrv_messages.h dhcpsrv_messages.cc: s-messages
+dhcpsrv_messages.h dhcpsrv_messages.cc hosts_messages.h hosts_messages.cc: s-messages
 
-s-messages: dhcpsrv_messages.mes
+s-messages: dhcpsrv_messages.mes hosts_messages.mes
 	$(top_builddir)/src/lib/log/compiler/message $(top_srcdir)/src/lib/dhcpsrv/dhcpsrv_messages.mes
 	touch $@
+	$(top_builddir)/src/lib/log/compiler/message $(top_srcdir)/src/lib/dhcpsrv/hosts_messages.mes
+	touch $@
 
-# Tell Automake that the dhcpsrv_messages.{cc,h} source files are created in the
-# build process, so it must create these before doing anything else.  Although
-# they are a dependency of the library (so will be created from the message file
-# anyway), there is no guarantee as to exactly _when_ in the build they will be
+# Tell Automake that the {dhcpsrv,hosts}_messages.{cc,h} source files are created
+# in the build process, so it must create these before doing anything else.
+# Although they are a dependency of the library (so will be created from the message
+# file anyway), there is no guarantee as to exactly _when_ in the build they will be
 # created.  As the .h file is included in other sources file (so must be
 # present when they are compiled), the safest option is to create it first.
 BUILT_SOURCES = dhcpsrv_messages.h dhcpsrv_messages.cc
+BUILT_SOURCES += hosts_messages.h hosts_messages.cc
 
 # Some versions of GCC warn about some versions of Boost regarding
 # missing initializer for members in its posix_time.
@@ -56,7 +59,8 @@ BUILT_SOURCES = dhcpsrv_messages.h dhcpsrv_messages.cc
 AM_CXXFLAGS += $(WARNING_NO_MISSING_FIELD_INITIALIZERS_CFLAG)
 
 # Make sure the generated files are deleted in a "clean" operation
-CLEANFILES = *.gcno *.gcda dhcpsrv_messages.h dhcpsrv_messages.cc s-messages
+CLEANFILES = *.gcno *.gcda dhcpsrv_messages.h dhcpsrv_messages.cc
+CLEANFILES += hosts_messages.h hosts_messages.cc s-messages
 # Remove CSV files created by the CSVLeaseFile6 and CSVLeaseFile4 unit tests.
 CLEANFILES += *.csv
 
@@ -84,6 +88,7 @@ libkea_dhcpsrv_la_SOURCES += dhcpsrv_log.cc dhcpsrv_log.h
 libkea_dhcpsrv_la_SOURCES += host.cc host.h
 libkea_dhcpsrv_la_SOURCES += host_container.h
 libkea_dhcpsrv_la_SOURCES += host_mgr.cc host_mgr.h
+libkea_dhcpsrv_la_SOURCES += hosts_log.cc hosts_log.h
 libkea_dhcpsrv_la_SOURCES += key_from_key.h
 libkea_dhcpsrv_la_SOURCES += lease.cc lease.h
 libkea_dhcpsrv_la_SOURCES += lease_file_loader.h
@@ -125,6 +130,7 @@ libkea_dhcpsrv_la_SOURCES += parsers/ifaces_config_parser.h
 
 
 nodist_libkea_dhcpsrv_la_SOURCES = dhcpsrv_messages.h dhcpsrv_messages.cc
+nodist_libkea_dhcpsrv_la_SOURCES += hosts_messages.h hosts_messages.cc
 
 libkea_dhcpsrv_la_CXXFLAGS = $(AM_CXXFLAGS)
 libkea_dhcpsrv_la_CPPFLAGS = $(AM_CPPFLAGS) $(LOG4CPLUS_INCLUDES)
@@ -154,6 +160,7 @@ endif
 
 # The message file should be in the distribution
 EXTRA_DIST += dhcpsrv_messages.mes
+EXTRA_DIST += hosts_messages.mes
 
 # Distribute backend documentation
 # Database schema creation script moved to src/bin/admin

+ 165 - 22
src/lib/dhcpsrv/cfg_hosts.cc

@@ -13,6 +13,7 @@
 // PERFORMANCE OF THIS SOFTWARE.
 
 #include <dhcpsrv/cfg_hosts.h>
+#include <dhcpsrv/hosts_log.h>
 #include <exceptions/exceptions.h>
 #include <ostream>
 
@@ -23,6 +24,8 @@ namespace dhcp {
 
 ConstHostCollection
 CfgHosts::getAll(const HWAddrPtr& hwaddr, const DuidPtr& duid) const {
+    // Do not issue logging message here because it will be logged by
+    // the getAllInternal method.
     ConstHostCollection collection;
     getAllInternal<ConstHostCollection>(hwaddr, duid, collection);
     return (collection);
@@ -30,6 +33,8 @@ CfgHosts::getAll(const HWAddrPtr& hwaddr, const DuidPtr& duid) const {
 
 HostCollection
 CfgHosts::getAll(const HWAddrPtr& hwaddr, const DuidPtr& duid) {
+    // Do not issue logging message here because it will be logged by
+    // the getAllInternal method.
     HostCollection collection;
     getAllInternal<HostCollection>(hwaddr, duid, collection);
     return (collection);
@@ -37,6 +42,8 @@ CfgHosts::getAll(const HWAddrPtr& hwaddr, const DuidPtr& duid) {
 
 ConstHostCollection
 CfgHosts::getAll4(const IOAddress& address) const {
+    // Do not issue logging message here because it will be logged by
+    // the getAllInternal4 method.
     ConstHostCollection collection;
     getAllInternal4<ConstHostCollection>(address, collection);
     return (collection);
@@ -44,6 +51,8 @@ CfgHosts::getAll4(const IOAddress& address) const {
 
 HostCollection
 CfgHosts::getAll4(const IOAddress& address) {
+    // Do not issue logging message here because it will be logged by
+    // the getAllInternal4 method.
     HostCollection collection;
     getAllInternal4<HostCollection>(address, collection);
     return (collection);
@@ -51,6 +60,8 @@ CfgHosts::getAll4(const IOAddress& address) {
 
 ConstHostCollection
 CfgHosts::getAll6(const IOAddress& address) const {
+    // Do not issue logging message here because it will be logged by
+    // the getAllInternal6 method.
     ConstHostCollection collection;
     getAllInternal6<ConstHostCollection>(address, collection);
     return (collection);
@@ -58,6 +69,8 @@ CfgHosts::getAll6(const IOAddress& address) const {
 
 HostCollection
 CfgHosts::getAll6(const IOAddress& address) {
+    // Do not issue logging message here because it will be logged by
+    // the getAllInternal6 method.
     HostCollection collection;
     getAllInternal6<HostCollection>(address, collection);
     return (collection);
@@ -68,6 +81,35 @@ void
 CfgHosts::getAllInternal(const std::vector<uint8_t>& identifier,
                          const Host::IdentifierType& identifier_type,
                          Storage& storage) const {
+    // We will need to transform the identifier into the textual format.
+    // Until we do it, we mark it as invalid.
+    std::string identifier_text = "(invalid)";
+    if (!identifier.empty()) {
+        try {
+            // Use Host object to find the textual form of the identifier.
+            // This may throw exception if the identifier is invalid.
+            Host host(&identifier[0], identifier.size(), identifier_type,
+                      SubnetID(0), SubnetID(0), IOAddress::IPV4_ZERO_ADDRESS());
+            identifier_text = host.getIdentifierAsText();
+
+        } catch (...) {
+            // Suppress exception and keep using (invalid) as an
+            // identifier. We will log that the identifier is
+            // invalid and return.
+        }
+
+    }
+    // This will log that we're invoking this function with the specified
+    // identifier. The identifier may also be marked as (invalid) if it
+    // had 0 length or its type is unsupported.
+    LOG_DEBUG(hosts_logger, HOSTS_DBG_TRACE, HOSTS_CFG_GET_ALL_IDENTIFIER)
+        .arg(identifier_text);
+
+    // Do nothing if the identifier specified is invalid.
+    if (identifier_text == "(invalid)") {
+        return;
+    }
+
     // Use the identifier and identifier type as a composite key.
     const HostContainerIndex0& idx = hosts_.get<0>();
     boost::tuple<const std::vector<uint8_t>, const Host::IdentifierType> t =
@@ -76,14 +118,27 @@ CfgHosts::getAllInternal(const std::vector<uint8_t>& identifier,
     // Append each Host object to the storage.
     for (HostContainerIndex0::iterator host = idx.lower_bound(t); host != idx.upper_bound(t);
          ++host) {
+        LOG_DEBUG(hosts_logger, HOSTS_DBG_TRACE_DETAIL_DATA,
+                  HOSTS_CFG_GET_ALL_IDENTIFIER_HOST)
+            .arg(identifier_text)
+            .arg((*host)->toText());
         storage.push_back(*host);
     }
+
+    // Log how many hosts have been found.
+    LOG_DEBUG(hosts_logger, HOSTS_DBG_RESULTS, HOSTS_CFG_GET_ALL_IDENTIFIER_COUNT)
+        .arg(identifier_text)
+        .arg(storage.size());
 }
 
 template<typename Storage>
 void
 CfgHosts::getAllInternal(const HWAddrPtr& hwaddr, const DuidPtr& duid,
                          Storage& storage) const {
+    LOG_DEBUG(hosts_logger, HOSTS_DBG_TRACE, HOSTS_CFG_GET_ALL_HWADDR_DUID)
+        .arg(hwaddr ? hwaddr->toText() : "(no-hwaddr)")
+        .arg(duid ? duid->toText() : "(no-duid)");
+
     // Get hosts using HW address.
     if (hwaddr) {
         getAllInternal<Storage>(hwaddr->hwaddr_, Host::IDENT_HWADDR, storage);
@@ -97,6 +152,9 @@ CfgHosts::getAllInternal(const HWAddrPtr& hwaddr, const DuidPtr& duid,
 template<typename Storage>
 void
 CfgHosts::getAllInternal4(const IOAddress& address, Storage& storage) const {
+    LOG_DEBUG(hosts_logger, HOSTS_DBG_TRACE, HOSTS_CFG_GET_ALL_ADDRESS4)
+        .arg(address.toText());
+
     // Must not specify address other than IPv4.
     if (!address.isV4()) {
         isc_throw(BadHostAddress, "must specify an IPv4 address when searching"
@@ -108,13 +166,24 @@ CfgHosts::getAllInternal4(const IOAddress& address, Storage& storage) const {
     // Append each Host object to the storage.
     for (HostContainerIndex1::iterator host = r.first; host != r.second;
          ++host) {
+        LOG_DEBUG(hosts_logger, HOSTS_DBG_TRACE_DETAIL_DATA,
+                  HOSTS_CFG_GET_ALL_ADDRESS4_HOST)
+            .arg(address.toText())
+            .arg((*host)->toText());
         storage.push_back(*host);
     }
+
+    LOG_DEBUG(hosts_logger, HOSTS_DBG_RESULTS, HOSTS_CFG_GET_ALL_ADDRESS4_COUNT)
+        .arg(address.toText())
+        .arg(storage.size());
 }
 
 template<typename Storage>
 void
 CfgHosts::getAllInternal6(const IOAddress& address, Storage& storage) const {
+    LOG_DEBUG(hosts_logger, HOSTS_DBG_TRACE, HOSTS_CFG_GET_ALL_ADDRESS6)
+        .arg(address.toText());
+
     // Must not specify address other than IPv6.
     if (!address.isV6()) {
         isc_throw(BadHostAddress, "must specify an IPv6 address when searching"
@@ -126,14 +195,23 @@ CfgHosts::getAllInternal6(const IOAddress& address, Storage& storage) const {
     // Append each Host object to the storage.
     for (HostContainerIndex1::iterator host = r.first; host != r.second;
          ++host) {
+        LOG_DEBUG(hosts_logger, HOSTS_DBG_TRACE_DETAIL_DATA,
+                  HOSTS_CFG_GET_ALL_ADDRESS6_HOST)
+            .arg(address.toText())
+            .arg((*host)->toText());
         storage.push_back(*host);
     }
+
+    LOG_DEBUG(hosts_logger, HOSTS_DBG_RESULTS, HOSTS_CFG_GET_ALL_ADDRESS6_COUNT)
+        .arg(address.toText())
+        .arg(storage.size());
 }
 
 
 ConstHostPtr
 CfgHosts::get4(const SubnetID& subnet_id, const HWAddrPtr& hwaddr,
                const DuidPtr& duid) const {
+    // Do not log here because getHostInternal logs.
     // The false value indicates that it is an IPv4 subnet.
     return (getHostInternal(subnet_id, false, hwaddr, duid));
 }
@@ -141,19 +219,31 @@ CfgHosts::get4(const SubnetID& subnet_id, const HWAddrPtr& hwaddr,
 HostPtr
 CfgHosts::get4(const SubnetID& subnet_id, const HWAddrPtr& hwaddr,
                const DuidPtr& duid) {
+    // Do not log here because getHostInternal logs.
     // The false value indicates that it is an IPv4 subnet.
     return (getHostInternal(subnet_id, false, hwaddr, duid));
 }
 
 ConstHostPtr
 CfgHosts::get4(const SubnetID& subnet_id, const IOAddress& address) const {
+    LOG_DEBUG(hosts_logger, HOSTS_DBG_TRACE, HOSTS_CFG_GET_ONE_SUBNET_ID_ADDRESS4)
+        .arg(subnet_id).arg(address.toText());
+
     ConstHostCollection hosts = getAll4(address);
     for (ConstHostCollection::const_iterator host = hosts.begin();
          host != hosts.end(); ++host) {
         if ((*host)->getIPv4SubnetID() == subnet_id) {
+            LOG_DEBUG(hosts_logger, HOSTS_DBG_RESULTS,
+                      HOSTS_CFG_GET_ONE_SUBNET_ID_ADDRESS4_HOST)
+                .arg(subnet_id)
+                .arg(address.toText())
+                .arg((*host)->toText());
             return (*host);
         }
     }
+
+    LOG_DEBUG(hosts_logger, HOSTS_DBG_RESULTS, HOSTS_CFG_GET_ONE_SUBNET_ID_ADDRESS4_NULL)
+        .arg(subnet_id).arg(address.toText());
     return (ConstHostPtr());
 }
 
@@ -161,6 +251,7 @@ CfgHosts::get4(const SubnetID& subnet_id, const IOAddress& address) const {
 ConstHostPtr
 CfgHosts::get6(const SubnetID& subnet_id, const DuidPtr& duid,
                const HWAddrPtr& hwaddr) const {
+    // Do not log here because getHostInternal logs.
     // The true value indicates that it is an IPv6 subnet.
     return (getHostInternal(subnet_id, true, hwaddr, duid));
 }
@@ -168,6 +259,7 @@ CfgHosts::get6(const SubnetID& subnet_id, const DuidPtr& duid,
 HostPtr
 CfgHosts::get6(const SubnetID& subnet_id, const DuidPtr& duid,
                const HWAddrPtr& hwaddr) {
+    // Do not log here because getHostInternal logs.
     // The true value indicates that it is an IPv6 subnet.
     return (getHostInternal(subnet_id, true, hwaddr, duid));
 }
@@ -186,20 +278,49 @@ CfgHosts::get6(const IOAddress&, const uint8_t) {
 ConstHostPtr
 CfgHosts::get6(const SubnetID& subnet_id,
                const asiolink::IOAddress& address) const {
-    ConstHostCollection storage;
-    getAllInternal6(subnet_id, address, storage);
+    // Do not log here because getHostInternal6 logs.
+    return (getHostInternal6<ConstHostPtr, ConstHostCollection>(subnet_id, address));
+}
 
+HostPtr
+CfgHosts::get6(const SubnetID& subnet_id,
+               const asiolink::IOAddress& address) {
+    // Do not log here because getHostInternal6 logs.
+    return (getHostInternal6<HostPtr, HostCollection>(subnet_id, address));
+}
+
+template<typename ReturnType, typename Storage>
+ReturnType
+CfgHosts::getHostInternal6(const SubnetID& subnet_id,
+                           const asiolink::IOAddress& address) const {
+    LOG_DEBUG(hosts_logger, HOSTS_DBG_TRACE, HOSTS_CFG_GET_ONE_SUBNET_ID_ADDRESS6)
+        .arg(subnet_id).arg(address.toText());
+
+    Storage storage;
+    getAllInternal6<Storage>(subnet_id, address, storage);
     switch (storage.size()) {
     case 0:
-        return (ConstHostPtr());
+        LOG_DEBUG(hosts_logger, HOSTS_DBG_RESULTS,
+                  HOSTS_CFG_GET_ONE_SUBNET_ID_ADDRESS6_NULL)
+            .arg(subnet_id)
+            .arg(address.toText());
+        return (HostPtr());
+
     case 1:
+        LOG_DEBUG(hosts_logger, HOSTS_DBG_RESULTS,
+                  HOSTS_CFG_GET_ONE_SUBNET_ID_ADDRESS6_HOST)
+            .arg(subnet_id)
+            .arg(address.toText())
+            .arg((*storage.begin())->toText());
         return (*storage.begin());
+
     default:
         isc_throw(DuplicateHost,  "more than one reservation found"
                   " for the host belonging to the subnet with id '"
                   << subnet_id << "' and using the address '"
                   << address.toText() << "'");
     }
+
 }
 
 template<typename Storage>
@@ -207,6 +328,9 @@ void
 CfgHosts::getAllInternal6(const SubnetID& subnet_id,
                           const asiolink::IOAddress& address,
                           Storage& storage) const {
+    LOG_DEBUG(hosts_logger, HOSTS_DBG_TRACE, HOSTS_CFG_GET_ALL_SUBNET_ID_ADDRESS6)
+        .arg(subnet_id).arg(address.toText());
+
     // Must not specify address other than IPv6.
     if (!address.isV6()) {
         isc_throw(BadHostAddress, "must specify an IPv6 address when searching"
@@ -223,30 +347,30 @@ CfgHosts::getAllInternal6(const SubnetID& subnet_id,
     // multiple addresses reserved, but for each (address, subnet_id) there should
     // be at most one host reserving it).
     for(HostContainer6Index1::iterator resrv = r.first; resrv != r.second; ++resrv) {
+        LOG_DEBUG(hosts_logger, HOSTS_DBG_TRACE_DETAIL_DATA,
+                  HOSTS_CFG_GET_ALL_SUBNET_ID_ADDRESS6_HOST)
+            .arg(subnet_id)
+            .arg(address.toText())
+            .arg(resrv->host_);
         storage.push_back(resrv->host_);
     }
-}
 
-HostPtr
-CfgHosts::get6(const SubnetID& subnet_id, const asiolink::IOAddress& address) {
-    HostCollection storage;
-    getAllInternal6<HostCollection>(subnet_id, address, storage);
-    switch (storage.size()) {
-    case 0:
-        return (HostPtr());
-    case 1:
-        return (*storage.begin());
-    default:
-        isc_throw(DuplicateHost,  "more than one reservation found"
-                  " for the host belonging to the subnet with id '"
-                  << subnet_id << "' and using the address '"
-                  << address.toText() << "'");
-    }
+    LOG_DEBUG(hosts_logger, HOSTS_DBG_RESULTS,
+              HOSTS_CFG_GET_ALL_SUBNET_ID_ADDRESS6_COUNT)
+        .arg(subnet_id)
+        .arg(address.toText())
+        .arg(storage.size());
 }
 
 HostPtr
 CfgHosts::getHostInternal(const SubnetID& subnet_id, const bool subnet6,
                           const HWAddrPtr& hwaddr, const DuidPtr& duid) const {
+    LOG_DEBUG(hosts_logger, HOSTS_DBG_TRACE, HOSTS_CFG_GET_ONE_SUBNET_ID_HWADDR_DUID)
+        .arg(subnet6 ? "IPv6" : "IPv4")
+        .arg(subnet_id)
+        .arg(hwaddr ? hwaddr->toText() : "(no-hwaddr)")
+        .arg(duid ? duid->toText() : "(no-duid)");
+
     // Get all hosts for the HW address and DUID. This may return multiple hosts
     // for different subnets, but the number of hosts returned should be low
     // because one host presumably doesn't show up in many subnets.
@@ -284,17 +408,38 @@ CfgHosts::getHostInternal(const SubnetID& subnet_id, const bool subnet6,
             }
         }
     }
+
+    if (host) {
+        LOG_DEBUG(hosts_logger, HOSTS_DBG_RESULTS,
+                  HOSTS_CFG_GET_ONE_SUBNET_ID_HWADDR_DUID)
+            .arg(subnet_id)
+            .arg(hwaddr ? hwaddr->toText() : "(no-hwaddr)")
+            .arg(duid ? duid->toText() : "(no-duid)")
+            .arg(host->toText());
+
+    } else {
+        LOG_DEBUG(hosts_logger, HOSTS_DBG_RESULTS,
+                  HOSTS_CFG_GET_ONE_SUBNET_ID_HWADDR_DUID_NULL)
+            .arg(subnet_id)
+            .arg(hwaddr ? hwaddr->toText() : "(no-hwaddr)")
+            .arg(duid ? duid->toText() : "(no-duid)");
+    }
+
     return (host);
 }
 
 
 void
 CfgHosts::add(const HostPtr& host) {
+    LOG_DEBUG(hosts_logger, HOSTS_DBG_TRACE, HOSTS_CFG_ADD_HOST)
+        .arg(host ? host->toText() : "(no-host)");
+
     // Sanity check that the host is non-null.
     if (!host) {
         isc_throw(BadValue, "specified host object must not be NULL when it"
                   " is added to the configuration");
     }
+
     // At least one subnet ID must be non-zero
     if (host->getIPv4SubnetID() == 0 && host->getIPv6SubnetID() == 0) {
         isc_throw(BadValue, "must not use both IPv4 and IPv6 subnet ids of"
@@ -308,7 +453,6 @@ CfgHosts::add(const HostPtr& host) {
 
 void
 CfgHosts::add4(const HostPtr& host) {
-
     /// @todo This may need further sanity checks.
     HWAddrPtr hwaddr = host->getHWAddress();
     DuidPtr duid = host->getDuid();
@@ -316,7 +460,7 @@ CfgHosts::add4(const HostPtr& host) {
     // There should be at least one resource reserved: hostname, IPv4
     // address, IPv6 address or prefix.
     if (host->getHostname().empty() &&
-        (host->getIPv4Reservation() == IOAddress("0.0.0.0")) &&
+        (host->getIPv4Reservation().isV4Zero()) &&
         (!host->hasIPv6Reservation())) {
         std::ostringstream s;
         if (hwaddr) {
@@ -357,7 +501,6 @@ CfgHosts::add4(const HostPtr& host) {
 
 void
 CfgHosts::add6(const HostPtr& host) {
-
     /// @todo This may need further sanity checks.
     HWAddrPtr hwaddr = host->getHWAddress();
     DuidPtr duid = host->getDuid();

+ 17 - 0
src/lib/dhcpsrv/cfg_hosts.h

@@ -339,6 +339,23 @@ private:
                             const HWAddrPtr& hwaddr,
                             const DuidPtr& duid) const;
 
+    /// @brief Returns the @c Host object holding reservation for the IPv6
+    /// address and connected to the specific subnet.
+    ///
+    /// This private method is called by the public @c get6 method variants.
+    ///
+    /// @param subnet_id IPv6 subnet identifier.
+    /// @param address IPv6 address.
+    /// @tparam ReturnType One of @c HostPtr or @c ConstHostPtr
+    /// @tparam One of the @c ConstHostCollection or @c HostCollection.
+    ///
+    /// @return Pointer to the found host, or NULL if no host found.
+    /// @throw isc::dhcp::DuplicateHost if method found more than one matching
+    /// @c Host object.
+    template<typename ReturnType, typename Storage>
+    ReturnType getHostInternal6(const SubnetID& subnet_id,
+                                const asiolink::IOAddress& adddress) const;
+
     /// @brief Adds a new host to the v4 collection.
     ///
     /// This is an internal method called by public @ref add.

+ 67 - 10
src/lib/dhcpsrv/host.cc

@@ -138,6 +138,24 @@ Host::getIdentifierType() const {
     return (IDENT_DUID);
 }
 
+std::string
+Host::getIdentifierAsText() const {
+    std::string txt;
+    if (hw_address_) {
+        txt = "hwaddr=" + hw_address_->toText(false);
+    } else {
+        txt = "duid=";
+        if (duid_) {
+            txt += duid_->toText();
+        } else {
+            txt += "(none)";
+        }
+    }
+
+    return (txt);
+
+}
+
 void
 Host::setIdentifier(const uint8_t* identifier, const size_t len,
                     const IdentifierType& type) {
@@ -252,20 +270,59 @@ Host::addClientClassInternal(ClientClasses& classes,
 }
 
 std::string
-Host::getIdentifierAsText() const {
-    std::string txt;
-    if (hw_address_) {
-        txt = "hwaddr=" + hw_address_->toText(false);
+Host::toText() const {
+    std::ostringstream s;
+
+    // Add HW address or DUID.
+    s << getIdentifierAsText();
+
+    // Add IPv4 subnet id if exists (non-zero).
+    if (ipv4_subnet_id_) {
+        s << " ipv4_subnet_id=" << ipv4_subnet_id_;
+    }
+
+    // Add IPv6 subnet id if exists (non-zero).
+    if (ipv6_subnet_id_) {
+        s << " ipv6_subnet_id=" << ipv6_subnet_id_;
+    }
+
+    // Add hostname.
+    s << " hostname=" << (hostname_.empty() ? "(empty)" : hostname_);
+
+    // Add IPv4 reservation.
+    s << " ipv4_reservation=" << (ipv4_reservation_.isV4Zero() ? "(no)" :
+                                  ipv4_reservation_.toText());
+
+    if (ipv6_reservations_.empty()) {
+        s << " ipv6_reservations=(none)";
+
     } else {
-        txt = "duid=";
-        if (duid_) {
-            txt += duid_->toText();
-        } else {
-            txt += "(none)";
+        // Add all IPv6 reservations.
+        for (IPv6ResrvIterator resrv = ipv6_reservations_.begin();
+             resrv != ipv6_reservations_.end(); ++resrv) {
+            s << " ipv6_reservation"
+              << std::distance(ipv6_reservations_.begin(), resrv)
+              << "=" << resrv->second.toText();
         }
     }
 
-    return (txt);
+    // Add DHCPv4 client classes.
+    for (ClientClasses::const_iterator cclass = dhcp4_client_classes_.begin();
+         cclass != dhcp4_client_classes_.end(); ++cclass) {
+        s << " dhcp4_class"
+          << std::distance(dhcp4_client_classes_.begin(), cclass)
+          << "=" << *cclass;
+    }
+
+    // Add DHCPv6 client classes.
+    for (ClientClasses::const_iterator cclass = dhcp6_client_classes_.begin();
+         cclass != dhcp6_client_classes_.end(); ++cclass) {
+        s << " dhcp6_class"
+          << std::distance(dhcp6_client_classes_.begin(), cclass)
+          << "=" << *cclass;
+    }
+
+    return (s.str());
 }
 
 } // end of namespace isc::dhcp

+ 3 - 0
src/lib/dhcpsrv/host.h

@@ -417,6 +417,9 @@ public:
         return (dhcp6_client_classes_);
     }
 
+    /// @brief Returns information about the host in the textual format.
+    std::string toText() const;
+
 private:
 
     /// @brief Adds new client class for DHCPv4 or DHCPv6.

+ 23 - 0
src/lib/dhcpsrv/host_mgr.cc

@@ -15,6 +15,7 @@
 #include <dhcpsrv/cfg_hosts.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/host_mgr.h>
+#include <dhcpsrv/hosts_log.h>
 
 namespace {
 
@@ -83,6 +84,11 @@ HostMgr::get4(const SubnetID& subnet_id, const HWAddrPtr& hwaddr,
               const DuidPtr& duid) const {
     ConstHostPtr host = getCfgHosts()->get4(subnet_id, hwaddr, duid);
     if (!host && alternate_source) {
+        LOG_DEBUG(hosts_logger, HOSTS_DBG_TRACE,
+                  HOSTS_MGR_ALTERNATE_GET4_SUBNET_ID_HWADDR_DUID)
+            .arg(subnet_id)
+            .arg(hwaddr ? hwaddr->toText() : "(no-hwaddr)")
+            .arg(duid ? duid->toText() : "(duid)");
         host = alternate_source->get4(subnet_id, hwaddr, duid);
     }
     return (host);
@@ -93,6 +99,10 @@ HostMgr::get4(const SubnetID& subnet_id,
               const asiolink::IOAddress& address) const {
     ConstHostPtr host = getCfgHosts()->get4(subnet_id, address);
     if (!host && alternate_source) {
+        LOG_DEBUG(hosts_logger, HOSTS_DBG_TRACE,
+                  HOSTS_MGR_ALTERNATE_GET4_SUBNET_ID_ADDRESS4)
+            .arg(subnet_id)
+            .arg(address.toText());
         host = alternate_source->get4(subnet_id, address);
     }
     return (host);
@@ -104,6 +114,11 @@ HostMgr::get6(const SubnetID& subnet_id, const DuidPtr& duid,
                const HWAddrPtr& hwaddr) const {
     ConstHostPtr host = getCfgHosts()->get6(subnet_id, duid, hwaddr);
     if (!host && alternate_source) {
+        LOG_DEBUG(hosts_logger, HOSTS_DBG_TRACE,
+                  HOSTS_MGR_ALTERNATE_GET6_SUBNET_ID_DUID_HWADDR)
+            .arg(subnet_id)
+            .arg(duid ? duid->toText() : "(duid)")
+            .arg(hwaddr ? hwaddr->toText() : "(no-hwaddr)");
         host = alternate_source->get6(subnet_id, duid, hwaddr);
     }
     return (host);
@@ -113,6 +128,10 @@ ConstHostPtr
 HostMgr::get6(const IOAddress& prefix, const uint8_t prefix_len) const {
     ConstHostPtr host = getCfgHosts()->get6(prefix, prefix_len);
     if (!host && alternate_source) {
+        LOG_DEBUG(hosts_logger, HOSTS_DBG_TRACE,
+                  HOSTS_MGR_ALTERNATE_GET6_PREFIX)
+            .arg(prefix.toText())
+            .arg(static_cast<int>(prefix_len));
         host = alternate_source->get6(prefix, prefix_len);
     }
     return (host);
@@ -123,6 +142,10 @@ HostMgr::get6(const SubnetID& subnet_id,
               const asiolink::IOAddress& addr) const {
     ConstHostPtr host = getCfgHosts()->get6(subnet_id, addr);
     if (!host && alternate_source) {
+        LOG_DEBUG(hosts_logger, HOSTS_DBG_TRACE,
+                  HOSTS_MGR_ALTERNATE_GET6_SUBNET_ID_ADDRESS6)
+            .arg(subnet_id)
+            .arg(addr.toText());
         host = alternate_source->get6(subnet_id, addr);
     }
     return (host);

+ 26 - 0
src/lib/dhcpsrv/hosts_log.cc

@@ -0,0 +1,26 @@
+// Copyright (C) 2015  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+/// @file Defines the logger used by the @c isc::dhcp::HostMgr
+
+#include "dhcpsrv/hosts_log.h"
+
+namespace isc {
+namespace dhcp {
+
+isc::log::Logger hosts_logger("hosts");
+
+} // namespace dhcp
+} // namespace isc
+

+ 64 - 0
src/lib/dhcpsrv/hosts_log.h

@@ -0,0 +1,64 @@
+// Copyright (C) 2015  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef HOSTS_LOG_H
+#define HOSTS_LOG_H
+
+#include <dhcpsrv/hosts_messages.h>
+#include <log/macros.h>
+
+namespace isc {
+namespace dhcp {
+
+///@{
+/// \brief Logging levels for the host reservations management.
+///
+/// Defines the levels used to output debug messages during the host
+/// reservations management, i.e. retrieving and adding host reservations.
+/// Note that higher numbers equate to more verbose(and detailed) output.
+
+/// @brief Traces normal operations
+///
+/// An example of the normal operation is the call to one of the functions
+/// which retrieve the reservations or add new reservation.
+const int HOSTS_DBG_TRACE = DBGLVL_TRACE_BASIC;
+
+/// @brief Records the results of the lookups
+///
+/// Messages logged at this level will typically contain summary of the
+/// data retrieved.
+const int HOSTS_DBG_RESULTS = DBGLVL_TRACE_BASIC_DATA;
+
+/// @brief Record detailed traces
+///
+/// Messages logged at this level will log detailed tracing information.
+const int HOSTS_DBG_TRACE_DETAIL = DBGLVL_TRACE_DETAIL;
+
+/// @brief Records detailed results of lookups.
+///
+/// Messages logged at this level will contain detailed results.
+const int HOSTS_DBG_TRACE_DETAIL_DATA = DBGLVL_TRACE_DETAIL_DATA;
+
+///@}
+
+/// @brief Logger for the @c HostMgr and the code it calls.
+///
+/// Define the logger used to log messages in @c HostMgr and the code it
+/// calls to manage host reservations.
+extern isc::log::Logger hosts_logger;
+
+} // namespace dhcp
+} // namespace isc
+
+#endif // HOSTS_LOG_H

+ 155 - 0
src/lib/dhcpsrv/hosts_messages.mes

@@ -0,0 +1,155 @@
+# Copyright (C) 2015  Internet Systems Consortium, Inc. ("ISC")
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+# PERFORMANCE OF THIS SOFTWARE.
+
+$NAMESPACE isc::dhcp
+
+% HOSTS_CFG_ADD_HOST add the host for reservations: %1
+This debug message is issued when new host (with reservations) is added to
+the server's configuration. The argument describes the host and its
+reservations in detail.
+
+% HOSTS_CFG_GET_ALL_ADDRESS4 get all hosts with reservations for IPv4 address %1
+This debug message is issued when starting to retrieve all hosts, holding the
+reservation for the specific IPv4 address, from the configuration. The
+argument specifies the IPv4 address used to search the hosts.
+
+% HOSTS_CFG_GET_ALL_ADDRESS4_HOST using address %1 found host: %2
+This debug message is issued when found host with the reservation
+for the specified IPv4 addres. The arguments specify the IPv4 address
+and the detailed description of the host found.
+
+% HOSTS_CFG_GET_ALL_ADDRESS4_COUNT using address %1, found %2 host(s)
+This debug message logs the number of hosts found using the specified
+IPv4 address. The arguments specify the IPv4 address used and the number
+of hosts found respectively.
+
+% HOSTS_CFG_GET_ALL_ADDRESS6 get all hosts with reservations for IPv6 address %1
+This debug message is issued when starting to retrieve all hosts, holding the
+reservation for the specific IPv6 address, from the configuration.
+The argument specifies the IPv6 address used to search the hosts.
+
+% HOSTS_CFG_GET_ALL_ADDRESS6_HOST using address %1 found host: %2
+This debug message is issued when found host with the reservation
+for the specified IPv6 address. The arguments specify the IPv6 address
+and the detailed description of the host found.
+
+% HOSTS_CFG_GET_ALL_ADDRESS6_COUNT using address %1, found %2 host(s)
+This debug message logs the number of hosts found using the specified
+IPv6 address. The arguments specify the IPv6 address used and the number
+of hosts found respectively.
+
+% HOSTS_CFG_GET_ALL_HWADDR_DUID get all hosts with reservations for HWADDR %1 and DUID %2
+This debug message is issued when starting to retrieve reservations for all hosts
+using specific HW address or DUID. The arguments specify the HW address and
+DUID respectively. The argument specify the HW address and DUID respectively.
+
+% HOSTS_CFG_GET_ALL_IDENTIFIER get all hosts with reservations using identifier: %1
+This debug message is issued when starting to retrieve reservations for all hosts
+identified by HW address or DUID. The argument holds both the identifier
+type and the value.
+
+% HOSTS_CFG_GET_ALL_IDENTIFIER_HOST using identifier: %1, found host: %2
+This debug message is issued when found host identified by the specific
+identifier. The arguments specify the identifier and the detailed
+description of the host found.
+
+% HOSTS_CFG_GET_ALL_IDENTIFIER_COUNT using identifier %1, found %2 host(s)
+This debug message logs the number of hosts found using the specified
+identifier. The arguments specify the identifier used and the number
+of hosts found respectively.
+
+% HOSTS_CFG_GET_ALL_SUBNET_ID_ADDRESS6 get all hosts with reservations for subnet id %1 and IPv6 address %2
+This debug message is issued when starting to retrieve all hosts connected to
+the specific subnet and having the specific IPv6 address reserved.
+The arguments specify subnet id and IPv6 address respectively.
+
+% HOSTS_CFG_GET_ALL_SUBNET_ID_ADDRESS6_HOST using subnet id %1 and address %2, found host: %3
+This debug message include the details of the host found using the
+subnet id and address. The arguments specify subnet id, address and
+found host details respectively.
+
+% HOSTS_CFG_GET_ALL_SUBNET_ID_ADDRESS6_COUNT using subnet id %1 and address %2, found %3 host(s)
+This debug message include the details of the host found using the
+subnet id and address. The arguments specify subnet id, address and
+found host details respectively.
+
+% HOSTS_CFG_GET_ONE_SUBNET_ID_ADDRESS4 get one host with reservation for subnet id %1 and IPv4 address %2
+This debug message is issued when starting to retrieve a host connected to the
+specific subnet and having the specific IPv4 address reserved. The
+arguments specify subnet id and IPv4 address respectively.
+
+% HOSTS_CFG_GET_ONE_SUBNET_ID_ADDRESS4_HOST using subnet id %1 and address %2, found host: %3
+This debug message logs the details of the host found using the
+subnet id and IPv4 address.
+
+% HOSTS_CFG_GET_ONE_SUBNET_ID_ADDRESS4_NULL host not found using subnet id %1 and address %2
+This debug message is issued when no host was found for the specified
+subnet id and IPv4 address.
+
+% HOSTS_CFG_GET_ONE_SUBNET_ID_ADDRESS6 get one host with reservation for subnet id %1 and including IPv6 address %2
+This debug message is issued when starting to retrieve a host connected to the
+specific subnet and having the specific IPv6 address reserved. The
+arguments specify subnet id and IPv6 address respectively.
+
+% HOSTS_CFG_GET_ONE_SUBNET_ID_ADDRESS6_HOST using subnet id %1 and address %2, found host: %3
+This debug message logs the details of the host found using the
+subnet id and IPv6 address.
+
+% HOSTS_CFG_GET_ONE_SUBNET_ID_ADDRESS6_NULL host not found using subnet id %1 and address %2
+This debug message is issued when no host was found using the specified
+subnet if and IPv6 address.
+
+% HOSTS_CFG_GET_ONE_SUBNET_ID_HWADDR_DUID get one host with %1 reservation for subnet id %2, HWADDR %3, DUID %4
+This debug message is issued when starting to retrieve the host holding IPv4 or
+IPv6 reservations, which is connected to the specific subnet and is
+identified by the specific HW address or DUID. The first argument
+identifies if the IPv4 or IPv6 reservation is desired.
+
+% HOSTS_CFG_GET_ONE_SUBNET_ID_HWADDR_DUID_HOST using subnet id %1, HWADDR %2 and DUID %3, found host: %4
+This debug message includes the details of the host found using the
+subnet id, HW address and/or DUID.
+
+% HOSTS_CFG_GET_ONE_SUBNET_ID_HWADDR_DUID_NULL host not found using subnet id %1, HW address %2 and DUID %3
+This debug message is issued when no host was found using the specified
+subnet id, HW address and DUID.
+
+% HOSTS_MGR_ALTERNATE_GET4_SUBNET_ID_HWADDR_DUID trying alternate source for host using subnet id %1, HWADDR %2, DUID%3
+This debug message is issued when the Host Manager doesn't find the
+host connected to the specific subnet and identified by the HW address
+or DUID, and it is starting to search for this host in the alternate
+host data source.
+
+% HOSTS_MGR_ALTERNATE_GET4_SUBNET_ID_ADDRESS4 trying alternate source for host using subnet id %1 and address %2
+This debug message is issued when the Host Manager doesn't find the
+host connected to the specific subnet and having the reservation for
+the specific IPv4 address, and it is starting to search for this host
+in the alternate host data source.
+
+% HOSTS_MGR_ALTERNATE_GET6_PREFIX trying alternate source for host using prefix %1/%2
+This debug message is issued when the Host Manager doesn't find the
+host connected to the specific subnet and having the reservation for
+the specified prefix, and it is starting to search for this host in
+the alternate host data source.
+
+% HOSTS_MGR_ALTERNATE_GET6_SUBNET_ID_ADDRESS6 trying alternate source for host using subnet id %1 and IPv6 address %2
+This debug message is issued when the Host Manager doesn't find the
+host connected to teh specific subnet and having the reservation for
+the specified IPv6 address, and it is starting to search for this
+host in the alternate host data source.
+
+% HOSTS_MGR_ALTERNATE_GET6_SUBNET_ID_DUID_HWADDR trying alternate source for host using subnet id %1, DUID %2, HWADDR %3
+This debug message is issued when the Host Manager doesn't find the
+host connected to the specific subnet and identified by the specified
+DUID or HW Address, and it is starting to search for this host in the
+alternate host data source.

+ 73 - 0
src/lib/dhcpsrv/tests/host_unittest.cc

@@ -524,4 +524,77 @@ TEST(HostTest, getIdentifierAsText) {
               host2.getIdentifierAsText());
 }
 
+// This test checks that Host object is correctly described in the
+// textual format using the toText method.
+TEST(HostTest, toText) {
+    boost::scoped_ptr<Host> host;
+    ASSERT_NO_THROW(host.reset(new Host("01:02:03:04:05:06", "hw-address",
+                                        SubnetID(1), SubnetID(2),
+                                        IOAddress("192.0.2.3"),
+                                        "myhost.example.com")));
+
+    // Add 4 reservations: 2 for NAs, 2 for PDs.
+    ASSERT_NO_THROW(
+        host->addReservation(IPv6Resrv(IPv6Resrv::TYPE_NA,
+                                       IOAddress("2001:db8:1::cafe")));
+        host->addReservation(IPv6Resrv(IPv6Resrv::TYPE_PD,
+                                       IOAddress("2001:db8:1:1::"), 64));
+        host->addReservation(IPv6Resrv(IPv6Resrv::TYPE_PD,
+                                       IOAddress("2001:db8:1:2::"), 64));
+        host->addReservation(IPv6Resrv(IPv6Resrv::TYPE_NA,
+                                       IOAddress("2001:db8:1::1")));
+    );
+
+    // Make sure that the output is correct,
+    EXPECT_EQ("hwaddr=01:02:03:04:05:06 ipv4_subnet_id=1 ipv6_subnet_id=2"
+              " hostname=myhost.example.com"
+              " ipv4_reservation=192.0.2.3"
+              " ipv6_reservation0=2001:db8:1::cafe"
+              " ipv6_reservation1=2001:db8:1::1"
+              " ipv6_reservation2=2001:db8:1:1::/64"
+              " ipv6_reservation3=2001:db8:1:2::/64",
+              host->toText());
+
+    // Reset some of the data and make sure that the output is affected.
+    host->setHostname("");
+    host->removeIPv4Reservation();
+    host->setIPv4SubnetID(0);
+
+    EXPECT_EQ("hwaddr=01:02:03:04:05:06 ipv6_subnet_id=2"
+              " hostname=(empty) ipv4_reservation=(no)"
+              " ipv6_reservation0=2001:db8:1::cafe"
+              " ipv6_reservation1=2001:db8:1::1"
+              " ipv6_reservation2=2001:db8:1:1::/64"
+              " ipv6_reservation3=2001:db8:1:2::/64",
+              host->toText());
+
+    // Create host identified by DUID, instead of HWADDR, with a very
+    // basic configuration.
+    ASSERT_NO_THROW(host.reset(new Host("11:12:13:14:15", "duid",
+                                        SubnetID(0), SubnetID(0),
+                                        IOAddress::IPV4_ZERO_ADDRESS(),
+                                        "myhost")));
+
+    EXPECT_EQ("duid=11:12:13:14:15 hostname=myhost ipv4_reservation=(no)"
+              " ipv6_reservations=(none)", host->toText());
+
+    // Add some classes.
+    host->addClientClass4("modem");
+    host->addClientClass4("router");
+
+    EXPECT_EQ("duid=11:12:13:14:15 hostname=myhost ipv4_reservation=(no)"
+              " ipv6_reservations=(none)"
+              " dhcp4_class0=modem dhcp4_class1=router",
+              host->toText());
+
+    host->addClientClass6("hub");
+    host->addClientClass6("device");
+
+    EXPECT_EQ("duid=11:12:13:14:15 hostname=myhost ipv4_reservation=(no)"
+              " ipv6_reservations=(none)"
+              " dhcp4_class0=modem dhcp4_class1=router"
+              " dhcp6_class0=device dhcp6_class1=hub",
+              host->toText());
+}
+
 } // end of anonymous namespace