Browse Source

[3562] Implemented storage class for Hosts.

Marcin Siodelski 10 years ago
parent
commit
1b6096afc8

+ 4 - 1
src/lib/dhcpsrv/Makefile.am

@@ -44,7 +44,9 @@ lib_LTLIBRARIES = libkea-dhcpsrv.la
 libkea_dhcpsrv_la_SOURCES  =
 libkea_dhcpsrv_la_SOURCES += addr_utilities.cc addr_utilities.h
 libkea_dhcpsrv_la_SOURCES += alloc_engine.cc alloc_engine.h
-libkea_dhcpsrv_la_SOURCES += callout_handle_store.h
+libkea_dhcpsrv_la_SOURCES += base_host_data_source.h
+libkea_dhcpsrv_la_SOURCES += callout_handle_store.
+libkea_dhcpsrv_la_SOURCES += cfg_hosts.cc cfg_hosts.h
 libkea_dhcpsrv_la_SOURCES += cfg_iface.cc cfg_iface.h
 libkea_dhcpsrv_la_SOURCES += cfg_option.cc cfg_option.h
 libkea_dhcpsrv_la_SOURCES += cfg_option_def.cc cfg_option_def.h
@@ -59,6 +61,7 @@ libkea_dhcpsrv_la_SOURCES += dhcpsrv_log.cc dhcpsrv_log.h
 libkea_dhcpsrv_la_SOURCES += dhcp_config_parser.h
 libkea_dhcpsrv_la_SOURCES += dhcp_parsers.cc dhcp_parsers.h
 libkea_dhcpsrv_la_SOURCES += host.cc host.h
+libkea_dhcpsrv_la_SOURCES += host_container.h
 libkea_dhcpsrv_la_SOURCES += key_from_key.h
 libkea_dhcpsrv_la_SOURCES += lease.cc lease.h
 libkea_dhcpsrv_la_SOURCES += lease_mgr.cc lease_mgr.h

+ 212 - 0
src/lib/dhcpsrv/base_host_data_source.h

@@ -0,0 +1,212 @@
+// Copyright (C) 2014 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 BASE_HOST_DATA_SOURCE_H
+#define BASE_HOST_DATA_SOURCE_H
+
+#include <asiolink/io_address.h>
+#include <dhcp/duid.h>
+#include <dhcp/hwaddr.h>
+#include <dhcpsrv/host.h>
+#include <exceptions/exceptions.h>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Exception thrown when the duplicate @c Host object is detected.
+class DuplicateHost : public Exception {
+public:
+    DuplicateHost(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
+/// @brief Base interface for the classes implementing simple data source
+/// for host reservations.
+///
+/// This abstract class defines an interface for the classes implementing
+/// basic data source for host reservations. This interface allows for
+/// adding new reservations (represented by @c Host objects) and retrieving
+/// these reservations using various parameters such as HW address or DUID,
+/// subnet identifier (either IPv4 or IPv6) or reserved IP address.
+///
+/// This interface DOES NOT specify the methods to manage existing
+/// host reservations such as to remove one IPv6 reservation but leave
+/// other reservations. It also lacks the methods used for preparing
+/// the data to be added to the SQL database: commit, rollback etc.
+/// Such methods are declared in other interfaces.
+class BaseHostDataSource {
+public:
+
+    /// @brief Default destructor implementation.
+    virtual ~BaseHostDataSource() {
+    }
+
+    /// @brief Return all hosts for the specified HW address or DUID.
+    ///
+    /// This method returns all @c Host objects which represent reservations
+    /// for the specified HW address or DUID. Note, that this method may
+    /// return multiple reservations because a particular client may have
+    /// reservations in multiple subnets and the same client may be identified
+    /// by HW address or DUID. The server is unable to verify that the specific
+    /// DUID and HW address belong to the same client, until the client sends
+    /// a DHCP message.
+    ///
+    /// @param hwaddr HW address of the client or NULL if no HW address
+    /// available.
+    /// @param duid client id or NULL if not available, e.g. DHCPv4 client case.
+    ///
+    /// @return Collection of const @c Host objects.
+    virtual ConstHostCollection
+    getAll(const HWAddrPtr& hwaddr, const DuidPtr& duid = DuidPtr()) const = 0;
+
+    /// @brief Non-const version of the @c getAll const method.
+    ///
+    /// @param hwaddr HW address of the client or NULL if no HW address
+    /// available.
+    /// @param duid client id or NULL if not available, e.g. DHCPv4 client case.
+    ///
+    /// @return Collection of non-const @c Host objects.
+    virtual HostCollection
+    getAll(const HWAddrPtr& hwaddr, const DuidPtr& duid = DuidPtr()) = 0;
+
+    /// @brief Returns a collection of hosts using the specified IPv4 address.
+    ///
+    /// This method may return multiple @c Host objects if they are connected
+    /// to different subnets.
+    ///
+    /// @param address IPv4 address for which the @c Host object is searched.
+    ///
+    /// @return Collection of const @c Host objects.
+    virtual ConstHostCollection
+    getAll4(const asiolink::IOAddress& address) const = 0;
+
+    /// @brief Returns a collection of hosts using the specified IPv4 address.
+    ///
+    /// This method may return multiple @c Host objects if they are connected
+    /// to different subnets.
+    ///
+    /// @param address IPv4 address for which the @c Host object is searched.
+    ///
+    /// @return Collection of @c Host objects.
+    virtual HostCollection
+    getAll4(const asiolink::IOAddress& address) = 0;
+
+    /// @brief Returns a host connected to the IPv4 subnet.
+    ///
+    /// Implementations of this method should guard against the case when
+    /// mutliple instances of the @c Host are present, e.g. when two
+    /// @c Host objects are found, one for the DUID, another one for the
+    /// HW address. In such case, an implementation of this method
+    /// should throw an exception.
+    ///
+    /// @param subnet_id Subnet identifier.
+    /// @param hwaddr HW address of the client or NULL if no HW address
+    /// available.
+    /// @param duid client id or NULL if not available.
+    ///
+    /// @return Const @c Host object using a specified HW address or DUID.
+    virtual ConstHostPtr
+    get4(const SubnetID& subnet_id, const HWAddrPtr& hwaddr,
+         const DuidPtr& duid = DuidPtr()) const = 0;
+
+    /// @brief Returns a host connected to the IPv4 subnet.
+    ///
+    /// Implementations of this method should guard against the case when
+    /// mutliple instances of the @c Host are present, e.g. when two
+    /// @c Host objects are found, one for the DUID, another one for the
+    /// HW address. In such case, an implementation of this method
+    /// should throw an exception.
+    ///
+    /// @param subnet_id Subnet identifier.
+    /// @param hwaddr HW address of the client or NULL if no HW address
+    /// available.
+    /// @param duid client id or NULL if not available.
+    ///
+    /// @return Non-const @c Host object using a specified HW address or DUID.
+    virtual HostPtr
+    get4(const SubnetID& subnet_id, const HWAddrPtr& hwaddr,
+         const DuidPtr& duid = DuidPtr()) = 0;
+
+    /// @brief Returns a host connected to the IPv6 subnet.
+    ///
+    /// Implementations of this method should guard against the case when
+    /// mutliple instances of the @c Host are present, e.g. when two
+    /// @c Host objects are found, one for the DUID, another one for the
+    /// HW address. In such case, an implementation of this method
+    /// should throw an exception.
+    ///
+    /// @param subnet_id Subnet identifier.
+    /// @param hwaddr HW address of the client or NULL if no HW address
+    /// available.
+    /// @param duid DUID or NULL if not available.
+    ///
+    /// @return Const @c Host object using a specified HW address or DUID.
+    virtual ConstHostPtr
+    get6(const SubnetID& subnet_id, const DuidPtr& duid,
+         const HWAddrPtr& hwaddr = HWAddrPtr()) const = 0;
+
+    /// @brief Returns a host connected to the IPv6 subnet.
+    ///
+    /// Implementations of this method should guard against the case when
+    /// mutliple instances of the @c Host are present, e.g. when two
+    /// @c Host objects are found, one for the DUID, another one for the
+    /// HW address. In such case, an implementation of this method
+    /// should throw an exception.
+    ///
+    /// @param subnet_id Subnet identifier.
+    /// @param hwaddr HW address of the client or NULL if no HW address
+    /// available.
+    /// @param duid DUID or NULL if not available.
+    ///
+    /// @return Non-const @c Host object using a specified HW address or DUID.
+    virtual HostPtr
+    get6(const SubnetID& subnet_id, const DuidPtr& duid,
+         const HWAddrPtr& hwaddr = HWAddrPtr()) = 0;
+
+    /// @brief Returns a host using the specified IPv6 prefix.
+    ///
+    /// @param prefix IPv6 prefix for which the @c Host object is searched.
+    /// @param prefix_len IPv6 prefix length.
+    ///
+    /// @return Const @c Host object using a specified HW address or DUID.
+    virtual ConstHostPtr
+    get6(const asiolink::IOAddress& prefix, const uint8_t prefix_len) const = 0;
+
+    /// @brief Returns a host using the specified IPv6 prefix.
+    ///
+    /// @param prefix IPv6 prefix for which the @c Host object is searched.
+    /// @param prefix_len IPv6 prefix length.
+    ///
+    /// @return Non-const @c Host object using a specified HW address or DUID.
+    virtual HostPtr
+    get6(const asiolink::IOAddress& prefix, const uint8_t prefix_len) = 0;
+
+    /// @brief Adds a new host to the collection.
+    ///
+    /// The implementations of this method should guard against duplicate
+    /// reservations for the same host, where possible. For example, when the
+    /// reservation for the same HW address and subnet id is added twice, the
+    /// implementation should throw an exception. Note, that usually it is
+    /// impossible to guard against adding duplicated host, where one instance
+    /// is identified by HW address, another one by DUID.
+    ///
+    /// @param host Pointer to the new @c Host object being added.
+    virtual void add(const HostPtr& host) = 0;
+
+};
+
+}
+}
+
+#endif // BASE_HOST_DATA_SOURCE_H

+ 190 - 0
src/lib/dhcpsrv/cfg_hosts.cc

@@ -0,0 +1,190 @@
+// Copyright (C) 2014 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.
+
+#include <dhcpsrv/cfg_hosts.h>
+#include <exceptions/exceptions.h>
+
+using namespace isc::asiolink;
+
+namespace isc {
+namespace dhcp {
+
+ConstHostCollection
+CfgHosts::getAll(const HWAddrPtr& hwaddr, const DuidPtr& duid) const {
+    ConstHostCollection collection;
+    getAllInternal<ConstHostCollection>(hwaddr, duid, collection);
+    return (collection);
+}
+
+HostCollection
+CfgHosts::getAll(const HWAddrPtr& hwaddr, const DuidPtr& duid) {
+    HostCollection collection;
+    getAllInternal<HostCollection>(hwaddr, duid, collection);
+    return (collection);
+}
+
+ConstHostCollection
+CfgHosts::getAll4(const IOAddress&) const {
+    isc_throw(isc::NotImplemented, "getAll4(address) const is not implemented");
+}
+
+HostCollection
+CfgHosts::getAll4(const IOAddress&) {
+    isc_throw(isc::NotImplemented, "getAll4(address) is not implemented");
+}
+
+template<typename Storage>
+void
+CfgHosts::getAllInternal(const std::vector<uint8_t>& identifier,
+                         const Host::IdentifierType& identifier_type,
+                         Storage& storage) const {
+    // Search for the Host using the identifier and identifier type as a
+    // composite key.
+    const HostContainerIndex0& idx = hosts_.get<0>();
+    std::pair<HostContainerIndex0::iterator, HostContainerIndex0::iterator> r =
+        idx.equal_range(boost::make_tuple(identifier, identifier_type));
+    // Append each Host object to the storage.
+    for (HostContainerIndex0::iterator host = r.first; host != r.second;
+         ++host) {
+        storage.push_back(*host);
+    }
+}
+
+template<typename Storage>
+void
+CfgHosts::getAllInternal(const HWAddrPtr& hwaddr, const DuidPtr& duid,
+                         Storage& storage) const {
+    // Get hosts using HW address.
+    if (hwaddr) {
+        getAllInternal<Storage>(hwaddr->hwaddr_, Host::IDENT_HWADDR, storage);
+    }
+    // Get hosts using DUID.
+    if (duid) {
+        getAllInternal<Storage>(duid->getDuid(), Host::IDENT_DUID, storage);
+    }
+}
+
+ConstHostPtr
+CfgHosts::get4(const SubnetID& subnet_id, const HWAddrPtr& hwaddr,
+               const DuidPtr& duid) const {
+    // The false value indicates that it is an IPv4 subnet.
+    return (getHostInternal(subnet_id, false, hwaddr, duid));
+}
+
+HostPtr
+CfgHosts::get4(const SubnetID& subnet_id, const HWAddrPtr& hwaddr,
+               const DuidPtr& duid) {
+    // The false value indicates that it is an IPv4 subnet.
+    return (getHostInternal(subnet_id, false, hwaddr, duid));
+}
+
+ConstHostPtr
+CfgHosts::get6(const SubnetID& subnet_id, const DuidPtr& duid,
+               const HWAddrPtr& hwaddr) const {
+    // The true value indicates that it is an IPv6 subnet.
+    return (getHostInternal(subnet_id, true, hwaddr, duid));
+}
+
+HostPtr
+CfgHosts::get6(const SubnetID& subnet_id, const DuidPtr& duid,
+               const HWAddrPtr& hwaddr) {
+    // The true value indicates that it is an IPv6 subnet.
+    return (getHostInternal(subnet_id, true, hwaddr, duid));
+}
+
+ConstHostPtr
+CfgHosts::get6(const IOAddress&, const uint8_t) const {
+    isc_throw(isc::NotImplemented,
+              "get6(prefix, len) const is not implemented");
+}
+
+HostPtr
+CfgHosts::get6(const IOAddress&, const uint8_t) {
+    isc_throw(isc::NotImplemented, "get6(prefix, len) is not implemented");
+}
+
+HostPtr
+CfgHosts::getHostInternal(const SubnetID& subnet_id, const bool subnet6,
+                          const HWAddrPtr& hwaddr, const DuidPtr& duid) const {
+    // 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.
+    HostCollection hosts;
+    getAllInternal<HostCollection>(hwaddr, duid, hosts);
+
+    HostPtr host;
+    // Iterate over the returned hosts and select those for which the
+    // subnet id matches.
+    for (HostCollection::const_iterator host_it = hosts.begin();
+         host_it != hosts.end(); ++host_it) {
+        // Check if this is IPv4 subnet or IPv6 subnet.
+        SubnetID host_subnet_id = subnet6 ? (*host_it)->getIPv6SubnetID() :
+            (*host_it)->getIPv4SubnetID();
+
+        if (subnet_id == host_subnet_id) {
+            // If this is the first occurrence of the host for this subnet,
+            // remember it. But, if we find that this is second @c Host object
+            // for the same client, it is a misconfiguration. Most likely,
+            // the administrator has specified one reservation for a HW
+            // address and another one for the DUID, which gives an ambiguous
+            // result, and we don't know which reservation we should choose.
+            // Therefore, throw an exception.
+            if (!host) {
+                host = *host_it;
+
+            } else {
+                isc_throw(DuplicateHost,  "more than one reservation found"
+                          " for the host belonging to the subnet with id '"
+                          << subnet_id << "' and using the HW address '"
+                          << (hwaddr ? hwaddr->toText(false) : "(null)")
+                          << "' and DUID '"
+                          << (duid ? duid->toText() : "(null)")
+                          << "'");
+            }
+        }
+    }
+    return (host);
+}
+
+
+void
+CfgHosts::add(const HostPtr& host) {
+    /// @todo This may need to be sanity-checked. For example, a duplicate
+    /// should be rejected.
+    HWAddrPtr hwaddr = host->getHWAddress();
+    DuidPtr duid = host->getDuid();
+    // Check for duplicates for the specified IPv4 subnet.
+    if (get4(host->getIPv4SubnetID(), host->getHWAddress(), host->getDuid())) {
+        isc_throw(DuplicateHost, "failed to add new host using the HW"
+                  " address '" << (hwaddr ? hwaddr->toText(false) : "(null)")
+                  << " and DUID '" << (duid ? duid->toText() : "(null)")
+                  << "' to the IPv4 subnet id '" << host->getIPv4SubnetID()
+                  << "' as this host has already been added");
+
+    // Checek for duplicates for the specified IPv6 subnet.
+    } else if (get6(host->getIPv6SubnetID(), host->getDuid(),
+                    host->getHWAddress())) {
+        isc_throw(DuplicateHost, "failed to add new host using the HW"
+                  " address '" << (hwaddr ? hwaddr->toText(false) : "(null)")
+                  << " and DUID '" << (duid ? duid->toText() : "(null)")
+                  << "' to the IPv6 subnet id '" << host->getIPv6SubnetID()
+                  << "' as this host has already been added");
+    }
+
+    // This is a new instance - add it.
+    hosts_.insert(host);
+}
+
+} // end of namespace isc::dhcp
+} // end of namespace isc

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

@@ -0,0 +1,241 @@
+// Copyright (C) 2014 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 CFG_HOSTS_H
+#define CFG_HOSTS_H
+
+#include <asiolink/io_address.h>
+#include <dhcp/duid.h>
+#include <dhcp/hwaddr.h>
+#include <dhcpsrv/base_host_data_source.h>
+#include <dhcpsrv/host.h>
+#include <dhcpsrv/host_container.h>
+#include <dhcpsrv/subnet_id.h>
+#include <vector>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Represents the host reservations specified in the configuration file.
+///
+/// This class holds a collection of the host reservations (@c Host objects)
+/// which can be retrieved using different search criteria.
+///
+/// In the typical case the reservations are searched using the client's MAC
+/// address of DUID and a subnet that the client is connected to. The
+/// reservations can be also retrieved using other parameters, such as reserved
+/// IP address.
+///
+/// The reservations are added to this object by the configuration parsers,
+/// when the new configuration is applied for the server. The reservations
+/// are retrieved by the @c HostMgr class when the server is allocating or
+/// renewing an address or prefix for the particular client.
+class CfgHosts : public BaseHostDataSource {
+public:
+
+    /// @brief Return all hosts for the specified HW address or DUID.
+    ///
+    /// This method returns all @c Host objects which represent reservations
+    /// for the specified HW address or DUID. Note, that this method may
+    /// return multiple reservations because a particular client may have
+    /// reservations in multiple subnets and the same client may be identified
+    /// by HW address or DUID. The server is unable to verify that the specific
+    /// DUID and HW address belong to the same client, until the client sends
+    /// a DHCP message.
+    ///
+    /// @param hwaddr HW address of the client or NULL if no HW address
+    /// available.
+    /// @param duid client id or NULL if not available, e.g. DHCPv4 client case.
+    ///
+    /// @return Collection of const @c Host objects.
+    virtual ConstHostCollection
+    getAll(const HWAddrPtr& hwaddr, const DuidPtr& duid = DuidPtr()) const;
+
+    /// @brief Non-const version of the @c getAll const method.
+    ///
+    /// @param hwaddr HW address of the client or NULL if no HW address
+    /// available.
+    /// @param duid client id or NULL if not available, e.g. DHCPv4 client case.
+    ///
+    /// @return Collection of non-const @c Host objects.
+    virtual HostCollection
+    getAll(const HWAddrPtr& hwaddr, const DuidPtr& duid = DuidPtr());
+
+    /// @brief Returns a collection of hosts using the specified IPv4 address.
+    ///
+    /// This method may return multiple @c Host objects if they are connected
+    /// to different subnets.
+    ///
+    /// @param address IPv4 address for which the @c Host object is searched.
+    ///
+	/// @throw isc::NotImplemented.
+    virtual ConstHostCollection
+    getAll4(const asiolink::IOAddress& address) const;
+
+    /// @brief Returns a collection of hosts using the specified IPv4 address.
+    ///
+    /// This method may return multiple @c Host objects if they are connected
+    /// to different subnets.
+    ///
+    /// @param address IPv4 address for which the @c Host object is searched.
+    ///
+	/// @throw isc::NotImplemented
+    virtual HostCollection
+    getAll4(const asiolink::IOAddress& address);
+
+    /// @brief Returns a host connected to the IPv4 subnet.
+    ///
+    /// @param subnet_id Subnet identifier.
+    /// @param hwaddr HW address of the client or NULL if no HW address
+    /// available.
+    /// @param duid client id or NULL if not available.
+    ///
+    /// @return Const @c Host object using a specified HW address or DUID.
+    /// @throw isc::dhcp::DuplicateHost if more than one candidate host has
+    /// been found.
+    virtual ConstHostPtr
+    get4(const SubnetID& subnet_id, const HWAddrPtr& hwaddr,
+         const DuidPtr& duid = DuidPtr()) const;
+
+    /// @brief Returns a host connected to the IPv4 subnet.
+    ///
+    /// @param subnet_id Subnet identifier.
+    /// @param hwaddr HW address of the client or NULL if no HW address
+    /// available.
+    /// @param duid client id or NULL if not available.
+    ///
+    /// @return Non-const @c Host object using a specified HW address or DUID.
+    /// @throw isc::dhcp::DuplicateHost if more than one candidate host has
+    /// been found.
+    virtual HostPtr
+    get4(const SubnetID& subnet_id, const HWAddrPtr& hwaddr,
+         const DuidPtr& duid = DuidPtr());
+
+    /// @brief Returns a host connected to the IPv6 subnet.
+    ///
+    /// @param subnet_id Subnet identifier.
+    /// @param hwaddr HW address of the client or NULL if no HW address
+    /// available.
+    /// @param duid DUID or NULL if not available.
+    ///
+    /// @return Const @c Host object using a specified HW address or DUID.
+    /// @throw isc::dhcp::DuplicateHost if more than one candidate host has
+    /// been found.
+    virtual ConstHostPtr
+    get6(const SubnetID& subnet_id, const DuidPtr& duid,
+         const HWAddrPtr& hwaddr = HWAddrPtr()) const;
+
+    /// @brief Returns a host connected to the IPv6 subnet.
+    ///
+    /// @param subnet_id Subnet identifier.
+    /// @param hwaddr HW address of the client or NULL if no HW address
+    /// available.
+    /// @param duid DUID or NULL if not available.
+    ///
+    /// @return Non-const @c Host object using a specified HW address or DUID.
+    /// @throw isc::dhcp::DuplicateHost if more than one candidate host has
+    /// been found.
+    virtual HostPtr
+    get6(const SubnetID& subnet_id, const DuidPtr& duid,
+         const HWAddrPtr& hwaddr = HWAddrPtr());
+
+    /// @brief Returns a host using the specified IPv6 prefix.
+    ///
+    /// @param prefix IPv6 prefix for which the @c Host object is searched.
+    /// @param prefix_len IPv6 prefix length.
+    ///
+    /// @throw isc::NotImplemented.
+    virtual ConstHostPtr
+    get6(const asiolink::IOAddress& prefix, const uint8_t prefix_len) const;
+
+    /// @brief Returns a host using the specified IPv6 prefix.
+    ///
+    /// @param prefix IPv6 prefix for which the @c Host object is searched.
+    /// @param prefix_len IPv6 prefix length.
+    ///
+    /// @throw isc::NotImplemented.
+    virtual HostPtr
+    get6(const asiolink::IOAddress& prefix, const uint8_t prefix_len);
+
+
+    /// @brief Adds a new host to the collection.
+    ///
+    /// @param host Pointer to the new @c Host object being added.
+    ///
+    /// @throw DuplicateHost If a host for a particular HW address or DUID
+    /// has already been added to the IPv4 or IPv6 subnet.
+    virtual void add(const HostPtr& host);
+
+private:
+
+    /// @brief Returns @c Host objects for the specific identifier and type.
+    ///
+    /// This private method is called by the @c CfgHosts::getAllInternal
+    /// method which finds the @c Host objects using HW address or DUID.
+    /// The retrieved objects are appended to the @c storage container.
+    ///
+    /// @param identifier Binary representation of the HW addres or DUID (or
+    /// other future identifier).
+    /// @param identifier_type The type of the supplied identifier.
+    /// @param [out] storage Container to which the retreived objects are
+    /// appended.
+    /// @tparam One of the @c ConstHostCollection of @c HostCollection.
+    template<typename Storage>
+    void getAllInternal(const std::vector<uint8_t>& identifier,
+                        const Host::IdentifierType& identifier_type,
+                        Storage& storage) const;
+
+    /// @brief Returns @c Host objects for the specified HW address or DUID.
+    ///
+    /// This private method is called by the @c CfgHosts::getAll methods to
+    /// retrieve the @c Host objects using HW address or DUID. The retrieved
+    /// objects are appended to the @c storage container.
+    ///
+    /// @param hwaddr HW address identifying a host.
+    /// @param duid DUID identifying a host.
+    /// @param [out] storage Container to which the retrieved objects are
+    /// appended.
+    /// @tparam One of the @c ConstHostCollection or @c HostCollection.
+    template<typename Storage>
+    void getAllInternal(const HWAddrPtr& hwaddr, const DuidPtr& duid,
+                        Storage& storage) const;
+
+    /// @brief Returns @c Host object connected to a subnet.
+    ///
+    /// This private method returns a pointer to the @c Host object identified
+    /// by the HW address or DUID and connected to an IPv4 or IPv6 subnet.
+    ///
+    /// @param subnet_id IPv4 or IPv6 subnet identifier.
+    /// @param subnet6 A boolean flag which indicates if the subnet identifier
+    /// points to a IPv4 (if false) or IPv6 subnet (if true).
+    /// @param hwaddr HW address identifying a host.
+    /// @param duid DUID identifying a host.
+    ///
+    /// @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.
+    HostPtr getHostInternal(const SubnetID& subnet_id,
+                            const bool subnet6,
+                            const HWAddrPtr& hwaddr,
+                            const DuidPtr& duid) const;
+
+    /// @brief Multi-index container holding @c Host objects.
+    HostContainer hosts_;
+
+};
+
+}
+}
+
+#endif // CFG_HOSTS_H

+ 21 - 0
src/lib/dhcpsrv/host.cc

@@ -84,6 +84,27 @@ Host::Host(const std::string& identifier, const std::string& identifier_name,
     setIdentifier(identifier, identifier_name);
 }
 
+const std::vector<uint8_t>&
+Host::getIdentifier() const {
+    if (hw_address_) {
+        return (hw_address_->hwaddr_);
+
+    } else if (duid_) {
+        return (duid_->getDuid());
+
+    }
+    static std::vector<uint8_t> empty_vector;
+    return (empty_vector);
+}
+
+Host::IdentifierType
+Host::getIdentifierType() const {
+    if (hw_address_) {
+        return (IDENT_HWADDR);
+    }
+    return (IDENT_DUID);
+}
+
 void
 Host::setIdentifier(const uint8_t* identifier, const size_t len,
                     const IdentifierType& type) {

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

@@ -20,6 +20,7 @@
 #include <dhcp/duid.h>
 #include <dhcp/hwaddr.h>
 #include <dhcpsrv/subnet_id.h>
+#include <boost/shared_ptr.hpp>
 #include <list>
 #include <map>
 #include <string>
@@ -282,6 +283,10 @@ public:
         return (duid_);
     }
 
+    const std::vector<uint8_t>& getIdentifier() const;
+
+    IdentifierType getIdentifierType() const;
+
     /// @brief Sets new IPv4 subnet identifier.
     ///
     /// @param ipv4_subnet_id New subnet identifier.
@@ -408,6 +413,18 @@ private:
     ClientClasses dhcp6_client_classes_;
 };
 
+/// @brief Pointer to the @c Host object.
+typedef boost::shared_ptr<Host> HostPtr;
+
+/// @brief Const pointer to the @c Host object.
+typedef boost::shared_ptr<const Host> ConstHostPtr;
+
+/// @brief Collection of the const Host objects.
+typedef std::vector<ConstHostPtr> ConstHostCollection;
+
+/// @brief Collection of the @c Host objects.
+typedef std::vector<HostPtr> HostCollection;
+
 }
 }
 

+ 81 - 0
src/lib/dhcpsrv/host_container.h

@@ -0,0 +1,81 @@
+// Copyright (C) 2014 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 HOST_CONTAINER_H
+#define HOST_CONTAINER_H
+
+#include <dhcpsrv/host.h>
+#include <boost/multi_index_container.hpp>
+#include <boost/multi_index/composite_key.hpp>
+#include <boost/multi_index/mem_fun.hpp>
+#include <boost/multi_index/ordered_index.hpp>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Multi-index container holding host reservations.
+///
+/// This container holds a collection of @c Host objects which can be retrieved
+/// using various parameters. The typical use of this container is to search for
+/// all @c Host objects which are identified by a specified identifier, i.e.
+/// HW address or DUID.
+///
+/// @todo This container will be extended to search for @c Host objects
+/// associated with a specific IPv4 address or IPv6 prefix/length.
+///
+/// @see http://www.boost.org/doc/libs/1_56_0/libs/multi_index/doc/index.html
+typedef boost::multi_index_container<
+    // This container stores pointers to Host objects.
+    HostPtr,
+    // Start specification of indexes here.
+    boost::multi_index::indexed_by<
+        // First index is used to search for the host using one of the
+        // identifiers, i.e. HW address or DUID. The elements of this
+        // index are non-unique because there may be multiple reservations
+        // for the same host belonging to a different subnets.
+        boost::multi_index::ordered_non_unique<
+            // The index comprises actual identifier (HW address or DUID) in
+            // a binary form and a type of the identifier which indicates
+            // that it is HW address or DUID.
+            boost::multi_index::composite_key<
+                // Composite key uses members of the Host class.
+                Host,
+                // Binary identifier is retrieved from the Host class using
+                // a getIdentifier method.
+                boost::multi_index::const_mem_fun<
+                    Host, const std::vector<uint8_t>&,
+                    &Host::getIdentifier
+                >,
+                // Identifier type is retrieved from the Host class using
+                // a getIdentifierType method.
+                boost::multi_index::const_mem_fun<
+                    Host, Host::IdentifierType,
+                    &Host::getIdentifierType
+                >
+            >
+        >
+    >
+> HostContainer;
+
+/// @brief First index type in the @c HostContainer.
+typedef HostContainer::nth_index<0>::type HostContainerIndex0;
+
+/// @brief Results range returned using the @c HostContainerIndex0.
+typedef std::pair<HostContainerIndex0::iterator,
+                  HostContainerIndex0::iterator> HostContainerIndex0Range;
+
+}
+}
+
+#endif // HOST_CONTAINER_H

+ 1 - 0
src/lib/dhcpsrv/tests/Makefile.am

@@ -55,6 +55,7 @@ libdhcpsrv_unittests_SOURCES  = run_unittests.cc
 libdhcpsrv_unittests_SOURCES += addr_utilities_unittest.cc
 libdhcpsrv_unittests_SOURCES += alloc_engine_unittest.cc
 libdhcpsrv_unittests_SOURCES += callout_handle_store_unittest.cc
+libdhcpsrv_unittests_SOURCES += cfg_hosts_unittest.cc
 libdhcpsrv_unittests_SOURCES += cfg_iface_unittest.cc
 libdhcpsrv_unittests_SOURCES += cfg_option_unittest.cc
 libdhcpsrv_unittests_SOURCES += cfg_option_def_unittest.cc

+ 383 - 0
src/lib/dhcpsrv/tests/cfg_hosts_unittest.cc

@@ -0,0 +1,383 @@
+// Copyright (C) 2014 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.
+
+#include <config.h>
+#include <asiolink/io_address.h>
+#include <dhcp/duid.h>
+#include <dhcp/hwaddr.h>
+#include <dhcpsrv/cfg_hosts.h>
+#include <dhcpsrv/host.h>
+#include <gtest/gtest.h>
+
+using namespace isc;
+using namespace isc::dhcp;
+using namespace isc::asiolink;
+
+namespace {
+
+/// @brief Test fixture class for testing @c CfgHost object holding
+/// host reservations specified in the configuration file.
+class CfgHostsTest : public ::testing::Test {
+public:
+
+    /// @brief Constructor.
+    ///
+    /// This constructor allocates a collection of @c HWAddr and @c DuidPtr
+    /// objects used by the unit tests.
+    ///
+    /// The allocated HW addresses use the following pattern: 01:02:0A:BB:03:XX
+    /// where XX is a number between 0 and 0x32. All of them are of the
+    /// HTYPE_ETHER type.
+    ///
+    /// The allocated DUID LLTs use the following pattern:
+    /// 01:02:03:04:05:06:07:08:09:0A:XX where the XX is a number between
+    /// 0 and 0x32.
+    CfgHostsTest();
+
+    /// @brief Increases last byte of an address.
+    ///
+    /// @param address Address to be increased.
+    IOAddress increase(const IOAddress& address, const uint8_t num) const;
+
+    /// @brief Collection of HW address objects allocated for unit tests.
+    std::vector<HWAddrPtr> hwaddrs_;
+    /// @brief Collection of DUIDs allocated for unit tests.
+    std::vector<DuidPtr> duids_;
+};
+
+CfgHostsTest::CfgHostsTest() {
+    const uint8_t mac_template[] = {
+        0x01, 0x02, 0x0A, 0xBB, 0x03, 0x00
+    };
+    for (int i = 0; i < 50; ++i) {
+        std::vector<uint8_t> vec(mac_template,
+                                 mac_template + sizeof(mac_template));
+        vec[vec.size() - 1] = i;
+        HWAddrPtr hwaddr(new HWAddr(vec, HTYPE_ETHER));
+        hwaddrs_.push_back(hwaddr);
+    }
+
+    const uint8_t duid_template[] = {
+        0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x00
+    };
+    for (int i = 0; i < 50; ++i) {
+        std::vector<uint8_t> vec(duid_template,
+                                 duid_template + sizeof(mac_template));
+        vec[vec.size() - 1] = i;
+        DuidPtr duid(new DUID(vec));
+        duids_.push_back(duid);
+    }
+}
+
+IOAddress
+CfgHostsTest::increase(const IOAddress& address, const uint8_t num) const {
+    std::vector<uint8_t> vec = address.toBytes();
+    if (!vec.empty()) {
+        vec[vec.size() - 1] += num;
+        return (IOAddress::fromBytes(address.getFamily(), &vec[0]));
+    }
+    return (address);
+}
+
+// This test checks that hosts with unique HW addresses and DUIDs can be
+// retrieved from the host configuration.
+TEST_F(CfgHostsTest, getAllNonRepeatingHosts) {
+    CfgHosts cfg;
+    // Add 25 hosts identified by HW address and 25 hosts identified by
+    // DUID. They are added to different subnets.
+    for (int i = 0; i < 25; ++i) {
+        cfg.add(HostPtr(new Host(hwaddrs_[i]->toText(false),
+                                 "hw-address",
+                                 SubnetID(i % 10), SubnetID(i % 5),
+                                 IOAddress("192.0.2.5"))));
+
+        cfg.add(HostPtr(new Host(duids_[i]->toText(), "duid",
+                                 SubnetID(i % 5), SubnetID(i % 10),
+                                 IOAddress("192.0.2.10"))));
+
+    }
+
+    // Try to retrieve each added reservation using HW address and DUID. Do it
+    // in the reverse order to make sure that the order doesn't matter.
+    for (int i = 24; i >= 0; --i) {
+        // Get host identified by HW address. The DUID is non-zero but it
+        // points to a host for which the reservation hasn't been added.
+        HostCollection hosts = cfg.getAll(hwaddrs_[i], duids_[i + 25]);
+        ASSERT_EQ(1, hosts.size());
+        EXPECT_EQ(i % 10, hosts[0]->getIPv4SubnetID());
+        EXPECT_EQ("192.0.2.5", hosts[0]->getIPv4Reservation().toText());
+
+        // Get host identified by DUID. The HW address is non-null but it
+        // points to a host for which the reservation hasn't been added.
+        hosts = cfg.getAll(hwaddrs_[i + 25], duids_[i]);
+        ASSERT_EQ(1, hosts.size());
+        EXPECT_EQ(i % 5, hosts[0]->getIPv4SubnetID());
+        EXPECT_EQ("192.0.2.10", hosts[0]->getIPv4Reservation().toText());
+    }
+
+    // Make sure that the reservations do not exist for the hardware addresses
+    // and DUIDs from the range of 25 to 49.
+    for (int i = 49; i >= 25; --i) {
+        EXPECT_TRUE(cfg.getAll(hwaddrs_[i]).empty());
+        EXPECT_TRUE(cfg.getAll(HWAddrPtr(), duids_[i]).empty());
+    }
+}
+
+// This test verifies that the host can be added to multiple subnets and
+// that the getAll message retrieves all instances of the host.
+TEST_F(CfgHostsTest, getAllRepeatingHosts) {
+    CfgHosts cfg;
+    // Add hosts.
+    for (int i = 0; i < 25; ++i) {
+        // Add two hosts, using the same HW address to two distnict subnets.
+        cfg.add(HostPtr(new Host(hwaddrs_[i]->toText(false),
+                                 "hw-address",
+                                 SubnetID(1), SubnetID(2),
+                                 IOAddress("192.0.2.5"))));
+        cfg.add(HostPtr(new Host(hwaddrs_[i]->toText(false),
+                                 "hw-address",
+                                 SubnetID(2), SubnetID(3),
+                                 IOAddress("10.0.0.5"))));
+
+        // Add two hosts, using the same DUID to two distnict subnets.
+        cfg.add(HostPtr(new Host(duids_[i]->toText(), "duid",
+                                 SubnetID(1), SubnetID(2),
+                                 IOAddress("192.0.2.10"))));
+        cfg.add(HostPtr(new Host(duids_[i]->toText(), "duid",
+                                 SubnetID(2), SubnetID(3),
+                                 IOAddress("10.0.2.10"))));
+    }
+
+    // Verify that hosts can be retrieved.
+    for (int i = 0; i < 25; ++i) {
+        // Get host by HW address. The DUID is non-null but the reservation
+        // should be returned for the HW address because there are no
+        // reservations for the DUIDs from the range of 25 to 49.
+        HostCollection hosts = cfg.getAll(hwaddrs_[i], duids_[i + 25]);
+        ASSERT_EQ(2, hosts.size());
+        EXPECT_EQ(1, hosts[0]->getIPv4SubnetID());
+        EXPECT_EQ("192.0.2.5", hosts[0]->getIPv4Reservation().toText());
+        EXPECT_EQ(2, hosts[1]->getIPv4SubnetID());
+        EXPECT_EQ("10.0.0.5", hosts[1]->getIPv4Reservation().toText());
+
+        // Get host by DUID. The HW address is non-null but the reservation
+        // should be returned for the DUID because there are no
+        // reservations for the HW addresses from the range of 25 to 49.
+        hosts = cfg.getAll(hwaddrs_[i + 25], duids_[i]);
+        ASSERT_EQ(2, hosts.size());
+        EXPECT_EQ(1, hosts[0]->getIPv4SubnetID());
+        EXPECT_EQ(2, hosts[1]->getIPv4SubnetID());
+    }
+
+    // The getAll function should return empty containers for the HW addresses
+    //  and DUIDs for which the reservations haven't been added.
+    for (int i = 25; i < 50; ++i) {
+        EXPECT_TRUE(cfg.getAll(hwaddrs_[i]).empty());
+        EXPECT_TRUE(cfg.getAll(HWAddrPtr(), duids_[i]).empty());
+    }
+}
+
+// This test checks that the reservations can be retrieved for the particular
+// host connected to the specific IPv4 subnet (by subnet id).
+TEST_F(CfgHostsTest, get4) {
+    CfgHosts cfg;
+    // Add hosts.
+    for (int i = 0; i < 25; ++i) {
+        // Add host identified by HW address.
+        cfg.add(HostPtr(new Host(hwaddrs_[i]->toText(false),
+                                 "hw-address",
+                                 SubnetID(1 + i % 2), SubnetID(13),
+                                 increase(IOAddress("192.0.2.5"), i))));
+
+        // Add host identified by DUID.
+        cfg.add(HostPtr(new Host(duids_[i]->toText(), "duid",
+                                 SubnetID(1 + i % 2), SubnetID(13),
+                                 increase(IOAddress("192.0.2.100"), i))));
+    }
+
+    for (int i = 0; i < 25; ++i) {
+        // Retrieve host by HW address. The DUID is non-null but there is no
+        // reservation made for the DUID so the reservation is returned for
+        // HW address.
+        HostPtr host = cfg.get4(SubnetID(1 + i % 2), hwaddrs_[i],
+                                duids_[i + 25]);
+        ASSERT_TRUE(host);
+        EXPECT_EQ(1 + i % 2, host->getIPv4SubnetID());
+        EXPECT_EQ(increase(IOAddress("192.0.2.5"), i),
+                  host->getIPv4Reservation());
+
+        // Retrieve host by DUID. The HW address is non-null but there is no
+        // reservation made for the HW address so the reservation is returned
+        // for the DUID.
+        host = cfg.get4(SubnetID(1 + i % 2), hwaddrs_[i + 25], duids_[i]);
+        ASSERT_TRUE(host);
+        EXPECT_EQ(1 + i % 2, host->getIPv4SubnetID());
+        EXPECT_EQ(increase(IOAddress("192.0.2.100"), i),
+                  host->getIPv4Reservation());
+
+    }
+
+    // Also check that when the get4 finds multiple Host objects that fulfil
+    // search criteria, it will throw an exception. Note that the reservation
+    // exist both for hwaddrs_[0] and duids_[0].
+    EXPECT_THROW(cfg.get4(SubnetID(1), hwaddrs_[0], duids_[0]), DuplicateHost);
+}
+
+// This test checks that the reservations can be retrieved for the particular
+// host connected to the specific IPv6 subnet (by subnet id).
+TEST_F(CfgHostsTest, get6) {
+    CfgHosts cfg;
+    // Add hosts.
+    for (int i = 0; i < 25; ++i) {
+        // Add host identified by HW address.
+        cfg.add(HostPtr(new Host(hwaddrs_[i]->toText(false),
+                                 "hw-address",
+                                 SubnetID(10), SubnetID(1 + i % 2),
+                                 increase(IOAddress("2001:db8:1::1"), i))));
+
+        // Add host identified by DUID.
+        cfg.add(HostPtr(new Host(duids_[i]->toText(), "duid",
+                                 SubnetID(10), SubnetID(1 + i % 2),
+                                 increase(IOAddress("2001:db8:2::1"), i))));
+    }
+
+    for (int i = 0; i < 25; ++i) {
+        // Retrieve host by HW address. The DUID is non-null but there is no
+        // reservation made for the DUID so the reservation is returned for
+        // HW address.
+        HostPtr host = cfg.get6(SubnetID(1 + i % 2), duids_[i + 25],
+                                hwaddrs_[i]);
+        ASSERT_TRUE(host);
+        EXPECT_EQ(1 + i % 2, host->getIPv6SubnetID());
+        EXPECT_EQ(increase(IOAddress("2001:db8:1::1"), i),
+                  host->getIPv4Reservation());
+
+        // Retrieve host by DUID. The HW address is non-null but there is no
+        // reservation made for the HW address so the reservation is returned
+        // for the DUID.
+        host = cfg.get6(SubnetID(1 + i % 2), duids_[i], hwaddrs_[i + 25]);
+        ASSERT_TRUE(host);
+        EXPECT_EQ(1 + i % 2, host->getIPv6SubnetID());
+        EXPECT_EQ(increase(IOAddress("2001:db8:2::1"), i),
+                  host->getIPv4Reservation());
+    }
+
+    // Also check that when the get6 finds multiple Host objects that fulfil
+    // search criteria, it will throw an exception. Note that the reservation
+    // exist both for hwaddrs_[0] and duids_[0].
+    EXPECT_THROW(cfg.get6(SubnetID(1), duids_[0], hwaddrs_[0]), DuplicateHost);
+}
+
+// This test verifies that it is not possible to add the same Host to the
+// same IPv4 subnet twice.
+TEST_F(CfgHostsTest, duplicatesSubnet4HWAddr) {
+    CfgHosts cfg;
+    // Add a host.
+    ASSERT_NO_THROW(cfg.add(HostPtr(new Host(hwaddrs_[0]->toText(false),
+                                             "hw-address",
+                                             SubnetID(10), SubnetID(1),
+                                             IOAddress("10.0.0.1")))));
+
+    // Try to add the host with the same HW address to the same subnet. The fact
+    // that the IP address is different here shouldn't really matter.
+    EXPECT_THROW(cfg.add(HostPtr(new Host(hwaddrs_[0]->toText(false),
+                                          "hw-address",
+                                          SubnetID(10), SubnetID(12),
+                                          IOAddress("10.0.0.10")))),
+                 isc::dhcp::DuplicateHost);
+
+    // Now try to add it to a different subnet. It should go through.
+    EXPECT_NO_THROW(cfg.add(HostPtr(new Host(hwaddrs_[0]->toText(false),
+                                             "hw-address",
+                                             SubnetID(11), SubnetID(12),
+                                             IOAddress("10.0.0.10")))));
+}
+
+// This test verifies that it is not possible to add the same Host to the
+// same IPv4 subnet twice.
+TEST_F(CfgHostsTest, duplicatesSubnet4DUID) {
+    CfgHosts cfg;
+    // Add a host.
+    ASSERT_NO_THROW(cfg.add(HostPtr(new Host(duids_[0]->toText(),
+                                             "duid",
+                                             SubnetID(10), SubnetID(1),
+                                             IOAddress("10.0.0.1")))));
+
+    // Try to add the host with the same DUID to the same subnet. The fact
+    // that the IP address is different here shouldn't really matter.
+    EXPECT_THROW(cfg.add(HostPtr(new Host(duids_[0]->toText(),
+                                          "duid",
+                                          SubnetID(10), SubnetID(12),
+                                          IOAddress("10.0.0.10")))),
+                 isc::dhcp::DuplicateHost);
+
+    // Now try to add it to a different subnet. It should go through.
+    EXPECT_NO_THROW(cfg.add(HostPtr(new Host(duids_[0]->toText(),
+                                             "duid",
+                                             SubnetID(11), SubnetID(12),
+                                             IOAddress("10.0.0.10")))));
+}
+
+// This test verifies that it is not possible to add the same Host to the
+// same IPv4 subnet twice.
+TEST_F(CfgHostsTest, duplicatesSubnet6HWAddr) {
+    CfgHosts cfg;
+    // Add a host.
+    ASSERT_NO_THROW(cfg.add(HostPtr(new Host(hwaddrs_[0]->toText(false),
+                                             "hw-address",
+                                             SubnetID(10), SubnetID(1),
+                                             IOAddress("0.0.0.0")))));
+
+    // Try to add the host with the same HW address to the same subnet. The fact
+    // that the IP address is different here shouldn't really matter.
+    EXPECT_THROW(cfg.add(HostPtr(new Host(hwaddrs_[0]->toText(false),
+                                          "hw-address",
+                                          SubnetID(11), SubnetID(1),
+                                          IOAddress("0.0.0.0")))),
+                 isc::dhcp::DuplicateHost);
+
+    // Now try to add it to a different subnet. It should go through.
+    EXPECT_NO_THROW(cfg.add(HostPtr(new Host(hwaddrs_[0]->toText(false),
+                                             "hw-address",
+                                             SubnetID(11), SubnetID(2),
+                                             IOAddress("0.0.0.0")))));
+}
+
+// This test verifies that it is not possible to add the same Host to the
+// same IPv4 subnet twice.
+TEST_F(CfgHostsTest, duplicatesSubnet6DUID) {
+    CfgHosts cfg;
+    // Add a host.
+    ASSERT_NO_THROW(cfg.add(HostPtr(new Host(duids_[0]->toText(),
+                                             "duid",
+                                             SubnetID(10), SubnetID(1),
+                                             IOAddress("0.0.0.0")))));
+
+    // Try to add the host with the same DUID to the same subnet. The fact
+    // that the IP address is different here shouldn't really matter.
+    EXPECT_THROW(cfg.add(HostPtr(new Host(duids_[0]->toText(),
+                                          "duid",
+                                          SubnetID(11), SubnetID(1),
+                                          IOAddress("0.0.0.0")))),
+                 isc::dhcp::DuplicateHost);
+
+    // Now try to add it to a different subnet. It should go through.
+    EXPECT_NO_THROW(cfg.add(HostPtr(new Host(duids_[0]->toText(),
+                                             "duid",
+                                             SubnetID(11), SubnetID(2),
+                                             IOAddress("0.0.0.0")))));
+}
+
+
+} // end of anonymous namespace