Browse Source

[master] Merge branch 'trac3562'

Conflicts:
	src/lib/dhcpsrv/srv_config.cc
	src/lib/dhcpsrv/srv_config.h
Marcin Siodelski 10 years ago
parent
commit
1ba5ec3b78

+ 5 - 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
@@ -61,6 +63,8 @@ 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 += host_reservation_parser.cc host_reservation_parser.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

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

@@ -0,0 +1,224 @@
+// 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.
+    ///
+    /// Specifying both hardware address and DUID is allowed for this method
+    /// and results in returning all objects that are associated with hardware
+    /// address OR duid. For example: if one host is associated with the
+    /// specified hardware address and another host is associated with the
+    /// specified DUID, two hosts will be returned.
+    ///
+    /// @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.
+    ///
+    /// Specifying both hardware address and DUID is allowed for this method
+    /// and results in returning all objects that are associated with hardware
+    /// address OR duid. For example: if one host is associated with the
+    /// specified hardware address and another host is associated with the
+    /// specified DUID, two hosts will be returned.
+    ///
+    /// @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

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

@@ -0,0 +1,194 @@
+// 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) {
+    // 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");
+    }
+    /// @todo This may need further sanity checks. 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(), hwaddr, duid)) {
+        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(), duid, hwaddr)) {
+        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

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

@@ -0,0 +1,256 @@
+// 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 <boost/shared_ptr.hpp>
+#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 and matching
+    /// specified identifiers.
+    ///
+    /// @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 and matching
+    /// specified identifiers.
+    ///
+    /// @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 and matching
+    /// the specified identifiers.
+    ///
+    /// @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 and matching the
+    /// specified identifiers.
+    ///
+    /// @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_;
+
+};
+
+/// @name Pointers to the @c CfgHosts objects.
+//@{
+/// @brief Non-const pointer.
+typedef boost::shared_ptr<CfgHosts> CfgHostsPtr;
+
+/// @brief Const pointer.
+typedef boost::shared_ptr<const CfgHosts> ConstCfgHostsPtr;
+
+//@}
+
+}
+}
+
+#endif // CFG_HOSTS_H

+ 90 - 8
src/lib/dhcpsrv/host.cc

@@ -15,36 +15,57 @@
 #include <dhcpsrv/host.h>
 #include <util/strutil.h>
 #include <exceptions/exceptions.h>
+#include <sstream>
 
 namespace isc {
 namespace dhcp {
 
-IPv6Resrv::IPv6Resrv(const asiolink::IOAddress& prefix,
+IPv6Resrv::IPv6Resrv(const Type& type,
+                     const asiolink::IOAddress& prefix,
                      const uint8_t prefix_len)
-    : prefix_(asiolink::IOAddress("::")), prefix_len_(128) {
+    : type_(type), prefix_(asiolink::IOAddress("::")), prefix_len_(128) {
     // Validate and set the actual values.
-    set(prefix, prefix_len);
+    set(type, prefix, prefix_len);
 }
 
 void
-IPv6Resrv::set(const asiolink::IOAddress& prefix, const uint8_t prefix_len) {
+IPv6Resrv::set(const Type& type, const asiolink::IOAddress& prefix,
+               const uint8_t prefix_len) {
     if (!prefix.isV6() || prefix.isV6Multicast()) {
         isc_throw(isc::BadValue, "invalid prefix '" << prefix
-                  << " for new IPv6 reservation");
+                  << "' for new IPv6 reservation");
 
     } else if (prefix_len > 128) {
         isc_throw(isc::BadValue, "invalid prefix length '"
                   << static_cast<int>(prefix_len)
                   << "' for new IPv6 reservation");
+
+    } else if ((type == TYPE_NA) && (prefix_len != 128)) {
+        isc_throw(isc::BadValue, "invalid prefix length '"
+                  << static_cast<int>(prefix_len)
+                  << "' for reserved IPv6 address, expected 128");
     }
 
+    type_ = type;
     prefix_ = prefix;
     prefix_len_ = prefix_len;
 }
 
+std::string
+IPv6Resrv::toText() const {
+    std::ostringstream s;
+    s << prefix_;
+    // For PD, append prefix length.
+    if (getType() == TYPE_PD) {
+        s << "/" << static_cast<int>(prefix_len_);
+    }
+    return (s.str());
+}
+
 bool
 IPv6Resrv::operator==(const IPv6Resrv& other) const {
-    return (prefix_ == other.prefix_ &&
+    return (type_ == other.type_ &&
+            prefix_ == other.prefix_ &&
             prefix_len_ == other.prefix_len_);
 }
 
@@ -61,12 +82,16 @@ Host::Host(const uint8_t* identifier, const size_t identifier_len,
            const std::string& dhcp4_client_classes,
            const std::string& dhcp6_client_classes)
     : hw_address_(), duid_(), ipv4_subnet_id_(ipv4_subnet_id),
-      ipv6_subnet_id_(ipv6_subnet_id), ipv4_reservation_(ipv4_reservation),
+      ipv6_subnet_id_(ipv6_subnet_id),
+      ipv4_reservation_(asiolink::IOAddress("0.0.0.0")),
        hostname_(hostname), dhcp4_client_classes_(dhcp4_client_classes),
        dhcp6_client_classes_(dhcp6_client_classes) {
 
     // Initialize HWAddr or DUID
     setIdentifier(identifier, identifier_len, identifier_type);
+
+    // Validate and set IPv4 address reservation.
+    setIPv4Reservation(ipv4_reservation);
 }
 
 Host::Host(const std::string& identifier, const std::string& identifier_name,
@@ -76,12 +101,37 @@ Host::Host(const std::string& identifier, const std::string& identifier_name,
            const std::string& dhcp4_client_classes,
            const std::string& dhcp6_client_classes)
     : hw_address_(), duid_(), ipv4_subnet_id_(ipv4_subnet_id),
-      ipv6_subnet_id_(ipv6_subnet_id), ipv4_reservation_(ipv4_reservation),
+      ipv6_subnet_id_(ipv6_subnet_id),
+      ipv4_reservation_(asiolink::IOAddress("0.0.0.0")),
       hostname_(hostname), dhcp4_client_classes_(dhcp4_client_classes),
       dhcp6_client_classes_(dhcp6_client_classes) {
 
     // Initialize HWAddr or DUID
     setIdentifier(identifier, identifier_name);
+
+    // Validate and set IPv4 address reservation.
+    setIPv4Reservation(ipv4_reservation);
+}
+
+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
@@ -118,7 +168,23 @@ Host::setIdentifier(const std::string& identifier, const std::string& name) {
 }
 
 void
+Host::setIPv4Reservation(const asiolink::IOAddress& address) {
+    if (!address.isV4()) {
+        isc_throw(isc::BadValue, "address '" << address << "' is not a valid"
+                  " IPv4 address");
+    }
+    ipv4_reservation_ = address;
+}
+
+
+void
 Host::addReservation(const IPv6Resrv& reservation) {
+    // Check if it is not duplicating existing reservation.
+    if (hasReservation(reservation)) {
+        isc_throw(isc::InvalidOperation, "failed on attempt to add a duplicated"
+                  " host reservation for " << reservation.toText());
+    }
+    // Add it.
     ipv6_reservations_.insert(IPv6ResrvTuple(reservation.getType(),
                                              reservation));
 }
@@ -128,6 +194,22 @@ Host::getIPv6Reservations(const IPv6Resrv::Type& type) const {
     return (ipv6_reservations_.equal_range(type));
 }
 
+bool
+Host::hasReservation(const IPv6Resrv& reservation) const {
+    IPv6ResrvRange reservations = getIPv6Reservations(reservation.getType());
+    if (std::distance(reservations.first, reservations.second) > 0) {
+        for (IPv6ResrvIterator it = reservations.first;
+             it != reservations.second; ++it) {
+            if (it->second == reservation) {
+                return (true);
+            }
+        }
+    }
+
+    // No matching reservations found.
+    return (false);
+}
+
 void
 Host::addClientClassInternal(ClientClasses& classes,
                              const std::string& class_name) {

+ 44 - 7
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>
@@ -54,12 +55,14 @@ public:
     /// of 128 is used. This value indicates that the reservation is made
     /// for an IPv6 address.
     ///
+    /// @param type Reservation type: NA or PD.
     /// @param prefix Address or prefix to be reserved.
     /// @param prefix_len Prefix length.
     ///
     /// @throw isc::BadValue if prefix is not IPv6 prefix, is a
     /// multicast address or the prefix length is greater than 128.
-    IPv6Resrv(const asiolink::IOAddress& prefix,
+    IPv6Resrv(const Type& type,
+              const asiolink::IOAddress& prefix,
               const uint8_t prefix_len = 128);
 
     /// @brief Returns prefix for the reservation.
@@ -78,17 +81,22 @@ public:
     ///
     /// @return NA for prefix length equal to 128, PD otherwise.
     Type getType() const {
-        return (prefix_len_ == 128 ? TYPE_NA : TYPE_PD);
+        return (type_);
     }
 
     /// @brief Sets a new prefix and prefix length.
     ///
+    /// @param type Reservation type: NA or PD.
     /// @param prefix New prefix.
     /// @param prefix_len New prefix length.
     ///
     /// @throw isc::BadValue if prefix is not IPv6 prefix, is a
     /// multicast address or the prefix length is greater than 128.
-    void set(const asiolink::IOAddress& prefix, const uint8_t prefix_len);
+    void set(const Type& type, const asiolink::IOAddress& prefix,
+             const uint8_t prefix_len);
+
+    /// @brief Returns information about the reservation in the textual format.
+    std::string toText() const;
 
     /// @brief Equality operator.
     ///
@@ -101,6 +109,8 @@ public:
     bool operator!=(const IPv6Resrv& other) const;
 
 private:
+
+    Type type_;                  ///< Reservation type.
     asiolink::IOAddress prefix_; ///< Prefix
     uint8_t prefix_len_;         ///< Prefix length.
 
@@ -133,7 +143,10 @@ typedef std::pair<IPv6ResrvIterator, IPv6ResrvIterator> IPv6ResrvRange;
 /// interfaces. For the MAC address based reservations, each interface on a
 /// network device maps to a single @c Host object as each @c Host object
 /// contains at most one MAC address. So, it is possible that a single
-/// device is associated with multiple distinct @c Host objects.
+/// device is associated with multiple distinct @c Host objects if the
+/// device has multiple interfaces. Under normal circumstances, a non-mobile
+/// dual stack device using one interface should be represented by a single
+/// @c Host object.
 ///
 /// A DHCPv6 DUID is common for all interfaces on a device. Therefore, for
 /// DUID based reservations a @c Host object may represent a network device with
@@ -282,6 +295,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.
@@ -311,9 +328,9 @@ public:
     /// The new reservation removes a previous reservation.
     ///
     /// @param address Address to be reserved for the client.
-    void setIPv4Reservation(const asiolink::IOAddress& address) {
-        ipv4_reservation_ = address;
-    }
+    ///
+    /// @throw isc::BadValue if the provided address is not an IPv4 address.
+    void setIPv4Reservation(const asiolink::IOAddress& address);
 
     /// @brief Returns reserved IPv4 address.
     ///
@@ -335,6 +352,14 @@ public:
     /// the specified type.
     IPv6ResrvRange getIPv6Reservations(const IPv6Resrv::Type& type) const;
 
+    /// @brief Checks if specified IPv6 reservation exists for the host.
+    ///
+    /// @param reservation A reservation to be checked for the host.
+    ///
+    /// @return true if the reservation already exists for the host, false
+    /// otherwise.
+    bool hasReservation(const IPv6Resrv& reservation) const;
+
     /// @brief Sets new hostname.
     ///
     /// @param hostname New hostname.
@@ -408,6 +433,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;
+
 }
 }
 

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

@@ -0,0 +1,84 @@
+// 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.
+///
+/// This index allows for searching for @c Host objects using an
+/// identifier + identifier type tuple.
+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

+ 200 - 0
src/lib/dhcpsrv/host_reservation_parser.cc

@@ -0,0 +1,200 @@
+// 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 <asiolink/io_address.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/host_reservation_parser.h>
+#include <boost/foreach.hpp>
+#include <boost/lexical_cast.hpp>
+#include <string>
+
+using namespace isc::asiolink;
+using namespace isc::data;
+
+namespace isc {
+namespace dhcp {
+
+HostReservationParser::HostReservationParser(const SubnetID& subnet_id)
+    : DhcpConfigParser(), subnet_id_(subnet_id) {
+}
+
+void
+HostReservationParser::build(isc::data::ConstElementPtr reservation_data) {
+    std::string identifier;
+    std::string identifier_name;
+    std::string hostname;
+
+    // Gather those parameters that are common for both IPv4 and IPv6
+    // reservations.
+    BOOST_FOREACH(ConfigPair element, reservation_data->mapValue()) {
+        try {
+            if (element.first == "hw-address" || element.first == "duid") {
+                if (!identifier_name.empty()) {
+                    isc_throw(DhcpConfigError, "the 'hw-address' and 'duid'"
+                              " parameters are mutually exclusive");
+                }
+                identifier = element.second->stringValue();
+                identifier_name = element.first;
+
+            } else if (element.first == "hostname") {
+                hostname = element.second->stringValue();
+
+            }
+        } catch (const std::exception& ex) {
+            // Append line number where the error occurred.
+            isc_throw(DhcpConfigError, ex.what() << " ("
+                      << element.second->getPosition() << ")");
+        }
+    }
+
+    try {
+        // hw-address or duid is a must.
+        if (identifier_name.empty()) {
+            isc_throw(DhcpConfigError, "'hw-address' or 'duid' is a requirement"
+                      " parameter for host reservation");
+        }
+
+        // Create a host object from the basic parameters we already parsed.
+        host_.reset(new Host(identifier, identifier_name, SubnetID(0),
+                             SubnetID(0), IOAddress("0.0.0.0"), hostname));
+
+    } catch (const std::exception& ex) {
+        // Append line number where the error occurred.
+        isc_throw(DhcpConfigError, ex.what() << " ("
+                  << reservation_data->getPosition() << ")");
+    }
+}
+
+void
+HostReservationParser::addHost(isc::data::ConstElementPtr reservation_data) {
+    try {
+        CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host_);
+
+    } catch (const std::exception& ex) {
+        // Append line number to the exception string.
+        isc_throw(DhcpConfigError, ex.what() << " ("
+                  << reservation_data->getPosition() << ")");
+    }
+}
+
+HostReservationParser4::HostReservationParser4(const SubnetID& subnet_id)
+    : HostReservationParser(subnet_id) {
+}
+
+void
+HostReservationParser4::build(isc::data::ConstElementPtr reservation_data) {
+    HostReservationParser::build(reservation_data);
+
+    host_->setIPv4SubnetID(subnet_id_);
+
+    BOOST_FOREACH(ConfigPair element, reservation_data->mapValue()) {
+        try {
+            if (element.first == "ip-address") {
+                host_->setIPv4Reservation(IOAddress(element.second->
+                                                    stringValue()));
+            }
+        }
+        catch (const std::exception& ex) {
+        // Append line number where the error occurred.
+        isc_throw(DhcpConfigError, ex.what() << " ("
+                  << reservation_data->getPosition() << ")");
+        }
+    }
+
+    addHost(reservation_data);
+}
+
+HostReservationParser6::HostReservationParser6(const SubnetID& subnet_id)
+    : HostReservationParser(subnet_id) {
+}
+
+void
+HostReservationParser6::build(isc::data::ConstElementPtr reservation_data) {
+    HostReservationParser::build(reservation_data);
+
+    host_->setIPv6SubnetID(subnet_id_);
+
+    BOOST_FOREACH(ConfigPair element, reservation_data->mapValue()) {
+        if (element.first == "ip-addresses" || element.first == "prefixes") {
+            BOOST_FOREACH(ConstElementPtr prefix_element,
+                          element.second->listValue()) {
+                try {
+                    // For the IPv6 address the prefix length is 128 and the
+                    // value specified in the list is a reserved address.
+                    IPv6Resrv::Type resrv_type = IPv6Resrv::TYPE_NA;
+                    std::string prefix = prefix_element->stringValue();
+                    uint8_t prefix_len = 128;
+
+                    // If we're dealing with prefixes, instead of addresses,
+                    // we will have to extract the prefix length from the value
+                    // specified in the following format: 2001:db8:2000::/64.
+                    if (element.first == "prefixes") {
+                        // The slash is mandatory for prefixes. If there is no
+                        // slash, return an error.
+                        size_t len_pos  = prefix.find('/');
+                        if (len_pos == std::string::npos) {
+                            isc_throw(DhcpConfigError, "prefix reservation"
+                                      " requires prefix length be specified"
+                                      " in '" << prefix << "'");
+
+                        // If there is nothing after the slash, we should also
+                        // report an error.
+                        } else if (len_pos >= prefix.length() - 1) {
+                            isc_throw(DhcpConfigError, "prefix '" <<  prefix
+                                      << "' requires length after '/'");
+
+                        }
+
+                        // Convert the prefix length from the string to the
+                        // number. Note, that we don't use the uint8_t type
+                        // as the lexical cast would expect a chracter, e.g.
+                        // 'a', instead of prefix length, e.g. '64'.
+                        try {
+                            prefix_len = boost::lexical_cast<
+                                unsigned int>(prefix.substr(len_pos + 1));
+
+                        } catch (const boost::bad_lexical_cast&) {
+                            isc_throw(DhcpConfigError, "prefix length value '"
+                                      << prefix.substr(len_pos + 1)
+                                      << "' is invalid");
+                        }
+
+                        // Remove the  slash character and the prefix length
+                        // from the parsed value.
+                        prefix.erase(len_pos);
+
+                        // Finally, set the reservation type.
+                        resrv_type = IPv6Resrv::TYPE_PD;
+                    }
+
+                    // Create a reservation for an address or prefix.
+                    host_->addReservation(IPv6Resrv(resrv_type,
+                                                    IOAddress(prefix),
+                                                    prefix_len));
+
+                } catch (const std::exception& ex) {
+                    // Append line number where the error occurred.
+                    isc_throw(DhcpConfigError, ex.what() << " ("
+                              << prefix_element->getPosition() << ")");
+                }
+            }
+        }
+    }
+
+    // This may fail, but the addHost function will handle this on its own.
+    addHost(reservation_data);
+}
+
+} // end of namespace isc::dhcp
+} // end of namespace isc

+ 110 - 0
src/lib/dhcpsrv/host_reservation_parser.h

@@ -0,0 +1,110 @@
+// 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_RESERVATION_PARSER_H
+#define HOST_RESERVATION_PARSER_H
+
+#include <cc/data.h>
+#include <dhcpsrv/dhcp_config_parser.h>
+#include <dhcpsrv/host.h>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Parser for a single host reservation entry.
+class HostReservationParser : public DhcpConfigParser {
+public:
+
+    /// @brief Constructor.
+    ///
+    /// @param subnet_id Identifier of the subnet that the host is
+    /// connected to.
+    HostReservationParser(const SubnetID& subnet_id);
+
+    /// @brief Parses a single entry for host reservation.
+    ///
+    /// @param reservation_data Data element holding map with a host
+    /// reservation configuration.
+    ///
+    /// @throw DhcpConfigError If the configuration is invalid.
+    virtual void build(isc::data::ConstElementPtr reservation_data);
+
+    /// @brief Commit, unused.
+    virtual void commit() { }
+
+protected:
+
+    /// @brief Inserts @c host_ object to the staging configuration.
+    ///
+    /// This method should be called by derived classes to insert the fully
+    /// parsed host reservation configuration to the @c CfgMgr.
+    ///
+    /// @param reservation_data Data element holding host reservation. It
+    /// used by this method to append the line number to the error string.
+    ///
+    /// @throw DhcpConfigError When operation to add a configured host fails.
+    void addHost(isc::data::ConstElementPtr reservation_data);
+
+    /// @brief Identifier of the subnet that the host is connected to.
+    SubnetID subnet_id_;
+
+    /// @brief Holds a pointer to @c Host object representing a parsed
+    /// host reservation configuration.
+    HostPtr host_;
+
+};
+
+/// @brief Parser for a single host reservation for DHCPv4.
+class HostReservationParser4 : public HostReservationParser {
+public:
+
+    /// @brief Constructor.
+    ///
+    /// @param subnet_id Identifier of the subnet that the host is
+    /// connected to.
+    HostReservationParser4(const SubnetID& subnet_id);
+
+    /// @brief Parses a single host reservation for DHCPv4.
+    ///
+    /// @param reservation_data Data element holding map with a host
+    /// reservation configuration.
+    ///
+    /// @throw DhcpConfigError If the configuration is invalid.
+    virtual void build(isc::data::ConstElementPtr reservation_data);
+};
+
+/// @brief Parser for a single host reservation for DHCPv6.
+class HostReservationParser6 : public HostReservationParser {
+public:
+
+    /// @brief Constructor.
+    ///
+    /// @param subnet_id Identifier of the subnet that the host is
+    /// connected to.
+    HostReservationParser6(const SubnetID& subnet_id);
+
+    /// @brief Parses a single host reservation for DHCPv6.
+    ///
+    /// @param reservation_data Data element holding map with a host
+    /// reservation configuration.
+    ///
+    /// @throw DhcpConfigError If the configuration is invalid.
+    virtual void build(isc::data::ConstElementPtr reservation_data);
+};
+
+
+}
+} // end of namespace isc
+
+#endif // HOST_RESERVATION_PARSER_H

+ 2 - 2
src/lib/dhcpsrv/srv_config.cc

@@ -27,13 +27,13 @@ namespace dhcp {
 SrvConfig::SrvConfig()
     : sequence_(0), cfg_option_def_(new CfgOptionDef()),
       cfg_option_(new CfgOption()), cfg_subnets4_(new CfgSubnets4()),
-      cfg_subnets6_(new CfgSubnets6()) {
+      cfg_subnets6_(new CfgSubnets6()), cfg_hosts_(new CfgHosts()) {
 }
 
 SrvConfig::SrvConfig(const uint32_t sequence)
     : sequence_(sequence), cfg_option_def_(new CfgOptionDef()),
       cfg_option_(new CfgOption()), cfg_subnets4_(new CfgSubnets4()),
-      cfg_subnets6_(new CfgSubnets6()) {
+      cfg_subnets6_(new CfgSubnets6()), cfg_hosts_(new CfgHosts()) {
 }
 
 std::string

+ 23 - 0
src/lib/dhcpsrv/srv_config.h

@@ -15,6 +15,7 @@
 #ifndef DHCPSRV_CONFIG_H
 #define DHCPSRV_CONFIG_H
 
+#include <dhcpsrv/cfg_hosts.h>
 #include <dhcpsrv/cfg_iface.h>
 #include <dhcpsrv/cfg_option.h>
 #include <dhcpsrv/cfg_option_def.h>
@@ -218,6 +219,22 @@ public:
         return (cfg_subnets6_);
     }
 
+    /// @brief Returns pointer to the non-const objects representing host
+    /// reservations for different IPv4 and IPv6 subnets.
+    ///
+    /// @return Pointer to the non-const object holding host reservations.
+    CfgHostsPtr getCfgHosts() {
+        return (cfg_hosts_);
+    }
+
+    /// @brief Returns pointer to the const objects representing host
+    /// reservations for different IPv4 and IPv6 subnets.
+    ///
+    /// @return Pointer to the const object holding host reservations.
+    ConstCfgHostsPtr getCfgHosts() const {
+        return (cfg_hosts_);
+    }
+
     //@}
 
     /// @brief Copies the currnet configuration to a new configuration.
@@ -325,6 +342,12 @@ private:
     /// @brief Pointer to subnets configuration for IPv4.
     CfgSubnets6Ptr cfg_subnets6_;
 
+    /// @brief Pointer to the configuration for hosts reservation.
+    ///
+    /// This object holds a list of @c Host objects representing host
+    /// reservations for different IPv4 and IPv6 subnets.
+    CfgHostsPtr cfg_hosts_;
+
 };
 
 /// @name Pointers to the @c SrvConfig object.

+ 2 - 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
@@ -68,6 +69,7 @@ libdhcpsrv_unittests_SOURCES += d2_udp_unittest.cc
 libdhcpsrv_unittests_SOURCES += daemon_unittest.cc
 libdhcpsrv_unittests_SOURCES += dbaccess_parser_unittest.cc
 libdhcpsrv_unittests_SOURCES += host_unittest.cc
+libdhcpsrv_unittests_SOURCES += host_reservation_parser_unittest.cc
 libdhcpsrv_unittests_SOURCES += lease_file_io.cc lease_file_io.h
 libdhcpsrv_unittests_SOURCES += lease_unittest.cc
 libdhcpsrv_unittests_SOURCES += lease_mgr_factory_unittest.cc

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

@@ -0,0 +1,396 @@
+// 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.
+        HostPtr host = HostPtr(new Host(hwaddrs_[i]->toText(false),
+                                        "hw-address",
+                                        SubnetID(10), SubnetID(1 + i % 2),
+                                        IOAddress("0.0.0.0")));
+        host->addReservation(IPv6Resrv(IPv6Resrv::TYPE_NA,
+                                       increase(IOAddress("2001:db8:1::1"),
+                                                i)));
+        cfg.add(host);
+
+        // Add host identified by DUID.
+        host = HostPtr(new Host(duids_[i]->toText(), "duid",
+                                SubnetID(10), SubnetID(1 + i % 2),
+                                IOAddress("0.0.0.0")));
+        host->addReservation(IPv6Resrv(IPv6Resrv::TYPE_NA,
+                                       increase(IOAddress("2001:db8:2::1"),
+                                                i)));
+        cfg.add(host);
+    }
+
+    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());
+        IPv6ResrvRange reservations =
+            host->getIPv6Reservations(IPv6Resrv::TYPE_NA);
+        ASSERT_EQ(1, std::distance(reservations.first, reservations.second));
+        EXPECT_EQ(increase(IOAddress("2001:db8:1::1"), i),
+                  reservations.first->second.getPrefix());
+
+        // 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());
+        reservations = host->getIPv6Reservations(IPv6Resrv::TYPE_NA);
+        ASSERT_EQ(1, std::distance(reservations.first, reservations.second));
+        EXPECT_EQ(increase(IOAddress("2001:db8:2::1"), i),
+                  reservations.first->second.getPrefix());
+    }
+
+    // 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

+ 354 - 0
src/lib/dhcpsrv/tests/host_reservation_parser_unittest.cc

@@ -0,0 +1,354 @@
+// 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 <cc/data.h>
+#include <dhcp/duid.h>
+#include <dhcp/hwaddr.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/host.h>
+#include <dhcpsrv/host_reservation_parser.h>
+#include <dhcpsrv/testutils/config_result_check.h>
+#include <gtest/gtest.h>
+#include <iterator>
+#include <string>
+
+using namespace isc::asiolink;
+using namespace isc::data;
+using namespace isc::dhcp;
+
+namespace {
+
+/// @brief Test fixture class for @c HostReservationParser.
+class HostReservationParserTest : public ::testing::Test {
+protected:
+
+    /// @brief Setup for each test.
+    ///
+    /// Clears the configuration in the @c CfgMgr.
+    virtual void SetUp();
+
+    /// @brief Cleans up after each test.
+    ///
+    /// Clears the configuration in the @c CfgMgr.
+    virtual void TearDown();
+
+    /// @brief Checks if the reservation is in the range of reservations.
+    ///
+    /// @param resrv Reservation to be searched for.
+    /// @param range Range of reservations returned by the @c Host object
+    /// in which the reservation will be searched.
+    bool
+    reservationExists(const IPv6Resrv& resrv, const IPv6ResrvRange& range) {
+        for (IPv6ResrvIterator it = range.first; it != range.second;
+             ++it) {
+            if (resrv == it->second) {
+                return (true);
+            }
+        }
+        return (false);
+    }
+
+    void
+    expectFailure(const HostReservationParser& parser,
+                  const std::string& config) const;
+
+    /// @brief HW Address object used by tests.
+    HWAddrPtr hwaddr_;
+
+    /// @brief DUID object used by tests.
+    DuidPtr duid_;
+
+};
+
+void
+HostReservationParserTest::SetUp() {
+    CfgMgr::instance().clear();
+    // Initialize HW address used by tests.
+    const uint8_t hwaddr_data[] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 };
+    hwaddr_ = HWAddrPtr(new HWAddr(hwaddr_data, sizeof(hwaddr_data),
+                                   HTYPE_ETHER));
+
+    // Initialize DUID used by tests.
+    const uint8_t duid_data[] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+                                  0x08, 0x09, 0x0A };
+    duid_ = DuidPtr(new DUID(duid_data, sizeof(duid_data)));
+}
+
+void
+HostReservationParserTest::TearDown() {
+    CfgMgr::instance().clear();
+}
+
+// This test verfies that the parser can parse the reservation entry for
+// which hw-address is a host identifier.
+TEST_F(HostReservationParserTest, dhcp4HWaddr) {
+    std::string config = "{ \"hw-address\": \"01:02:03:04:05:06\","
+        "\"ip-address\": \"192.0.2.134\","
+        "\"hostname\": \"foo.example.com\" }";
+
+    ElementPtr config_element = Element::fromJSON(config);
+
+    HostReservationParser4 parser(SubnetID(1));
+    ASSERT_NO_THROW(parser.build(config_element));
+
+    CfgHostsPtr cfg_hosts = CfgMgr::instance().getStagingCfg()->getCfgHosts();
+    HostCollection hosts;
+    ASSERT_NO_THROW(hosts = cfg_hosts->getAll(hwaddr_, DuidPtr()));
+
+    ASSERT_EQ(1, hosts.size());
+
+    EXPECT_EQ(1, hosts[0]->getIPv4SubnetID());
+    EXPECT_EQ(0, hosts[0]->getIPv6SubnetID());
+    EXPECT_EQ("192.0.2.134", hosts[0]->getIPv4Reservation().toText());
+    EXPECT_EQ("foo.example.com", hosts[0]->getHostname());
+}
+
+// This test verfies that the parser can parse the reservation entry for
+// which DUID is a host identifier.
+TEST_F(HostReservationParserTest, dhcp4DUID) {
+    std::string config = "{ \"duid\": \"01:02:03:04:05:06:07:08:09:0A\","
+        "\"ip-address\": \"192.0.2.112\","
+        "\"hostname\": \"\" }";
+
+    ElementPtr config_element = Element::fromJSON(config);
+
+    HostReservationParser4 parser(SubnetID(10));
+    ASSERT_NO_THROW(parser.build(config_element));
+
+    CfgHostsPtr cfg_hosts = CfgMgr::instance().getStagingCfg()->getCfgHosts();
+    HostCollection hosts;
+    ASSERT_NO_THROW(hosts = cfg_hosts->getAll(HWAddrPtr(), duid_));
+
+    ASSERT_EQ(1, hosts.size());
+
+    EXPECT_EQ(10, hosts[0]->getIPv4SubnetID());
+    EXPECT_EQ(0, hosts[0]->getIPv6SubnetID());
+    EXPECT_EQ("192.0.2.112", hosts[0]->getIPv4Reservation().toText());
+    EXPECT_TRUE(hosts[0]->getHostname().empty());
+}
+
+// This test verifies that the configuration parser for host reservations
+// throws an exception when IPv6 address is specified for IPv4 address
+// reservation.
+TEST_F(HostReservationParserTest, dhcp4IPv6Address) {
+    std::string config = "{ \"hw-address\": \"01:02:03:04:05:06\","
+        "\"ip-address\": \"2001:db8:1::1\" }";
+
+    ElementPtr config_element = Element::fromJSON(config);
+
+    HostReservationParser4 parser(SubnetID(10));
+    EXPECT_THROW(parser.build(config_element), DhcpConfigError);
+}
+
+// This test verifies that the configuration parser for host reservations
+// throws an exception when no HW address nor DUID is specified.
+TEST_F(HostReservationParserTest, noIdentifier) {
+    std::string config = "{ \"ip-address\": \"192.0.2.112\","
+        "\"hostname\": \"\" }";
+
+    ElementPtr config_element = Element::fromJSON(config);
+
+    HostReservationParser4 parser(SubnetID(10));
+    EXPECT_THROW(parser.build(config_element), DhcpConfigError);
+}
+
+// This test verifies that the configuration parser for host reservations
+// throws an exception when invalid IP address is specified.
+TEST_F(HostReservationParserTest, malformedAddress) {
+    std::string config = "{ \"hw-address\": \"01:02:03:04:05:06\","
+        "\"ip-address\": \"192.0.2.bogus\" }";
+
+    ElementPtr config_element = Element::fromJSON(config);
+
+    HostReservationParser4 parser(SubnetID(10));
+    EXPECT_THROW(parser.build(config_element), DhcpConfigError);
+}
+
+// This test verfies that the parser can parse the IPv6 reservation entry for
+// which hw-address is a host identifier.
+TEST_F(HostReservationParserTest, dhcp6HWaddr) {
+    std::string config = "{ \"hw-address\": \"01:02:03:04:05:06\","
+        "\"ip-addresses\": [ \"2001:db8:1::1\", \"2001:db8:1::2\" ],"
+        "\"prefixes\": [ \"2001:db8:2000:0101::/64\", "
+        "\"2001:db8:2000:0102::/64\" ],"
+        "\"hostname\": \"foo.example.com\" }";
+
+    ElementPtr config_element = Element::fromJSON(config);
+
+    HostReservationParser6 parser(SubnetID(10));
+    ASSERT_NO_THROW(parser.build(config_element));
+
+    CfgHostsPtr cfg_hosts = CfgMgr::instance().getStagingCfg()->getCfgHosts();
+    HostCollection hosts;
+    ASSERT_NO_THROW(hosts = cfg_hosts->getAll(hwaddr_, DuidPtr()));
+
+    ASSERT_EQ(1, hosts.size());
+
+    EXPECT_EQ(0, hosts[0]->getIPv4SubnetID());
+    EXPECT_EQ(10, hosts[0]->getIPv6SubnetID());
+    EXPECT_EQ("foo.example.com", hosts[0]->getHostname());
+
+    IPv6ResrvRange addresses = hosts[0]->
+        getIPv6Reservations(IPv6Resrv::TYPE_NA);
+    ASSERT_EQ(2, std::distance(addresses.first, addresses.second));
+
+    EXPECT_TRUE(reservationExists(IPv6Resrv(IPv6Resrv::TYPE_NA,
+                                            IOAddress("2001:db8:1::1")),
+                                  addresses));
+    EXPECT_TRUE(reservationExists(IPv6Resrv(IPv6Resrv::TYPE_NA,
+                                            IOAddress("2001:db8:1::2")),
+                                  addresses));
+
+    IPv6ResrvRange prefixes = hosts[0]->getIPv6Reservations(IPv6Resrv::TYPE_PD);
+    ASSERT_EQ(2, std::distance(prefixes.first, prefixes.second));
+    EXPECT_TRUE(reservationExists(IPv6Resrv(IPv6Resrv::TYPE_PD,
+                                            IOAddress("2001:db8:2000:0101::"),
+                                            64),
+                                  prefixes));
+    EXPECT_TRUE(reservationExists(IPv6Resrv(IPv6Resrv::TYPE_PD,
+                                            IOAddress("2001:db8:2000:0102::"),
+                                            64),
+                                  prefixes));
+
+}
+
+// This test verfies that the parser can parse the IPv6 reservation entry for
+// which DUID is a host identifier.
+TEST_F(HostReservationParserTest, dhcp6DUID) {
+    std::string config = "{ \"duid\": \"01:02:03:04:05:06:07:08:09:0A\","
+        "\"ip-addresses\": [ \"2001:db8:1::100\", \"2001:db8:1::200\" ],"
+        "\"prefixes\": [ ],"
+        "\"hostname\": \"foo.example.com\" }";
+
+    ElementPtr config_element = Element::fromJSON(config);
+
+    HostReservationParser6 parser(SubnetID(12));
+    ASSERT_NO_THROW(parser.build(config_element));
+
+    CfgHostsPtr cfg_hosts = CfgMgr::instance().getStagingCfg()->getCfgHosts();
+    HostCollection hosts;
+    ASSERT_NO_THROW(hosts = cfg_hosts->getAll(HWAddrPtr(), duid_));
+
+    ASSERT_EQ(1, hosts.size());
+
+    EXPECT_EQ(0, hosts[0]->getIPv4SubnetID());
+    EXPECT_EQ(12, hosts[0]->getIPv6SubnetID());
+    EXPECT_EQ("foo.example.com", hosts[0]->getHostname());
+
+    IPv6ResrvRange addresses = hosts[0]->
+        getIPv6Reservations(IPv6Resrv::TYPE_NA);
+    ASSERT_EQ(2, std::distance(addresses.first, addresses.second));
+
+    EXPECT_TRUE(reservationExists(IPv6Resrv(IPv6Resrv::TYPE_NA,
+                                            IOAddress("2001:db8:1::100")),
+                                  addresses));
+    EXPECT_TRUE(reservationExists(IPv6Resrv(IPv6Resrv::TYPE_NA,
+                                            IOAddress("2001:db8:1::200")),
+                                  addresses));
+
+    IPv6ResrvRange prefixes = hosts[0]->getIPv6Reservations(IPv6Resrv::TYPE_PD);
+    ASSERT_EQ(0, std::distance(prefixes.first, prefixes.second));
+}
+
+// This test verifies that the configuration parser throws an exception
+// when IPv4 address is specified for IPv6 reservation.
+TEST_F(HostReservationParserTest, dhcp6IPv4Address) {
+    std::string config = "{ \"duid\": \"01:02:03:04:05:06:07:08:09:0A\","
+        "\"ip-addresses\": [ \"192.0.2.3\", \"2001:db8:1::200\" ],"
+        "\"prefixes\": [ ] }";
+
+    ElementPtr config_element = Element::fromJSON(config);
+
+    HostReservationParser6 parser(SubnetID(12));
+    EXPECT_THROW(parser.build(config_element), DhcpConfigError);
+}
+
+// This test verifies that the configuration parser throws an exception
+// when empty address has been specified.
+TEST_F(HostReservationParserTest, dhcp6NullAddress) {
+    std::string config = "{ \"duid\": \"01:02:03:04:05:06:07:08:09:0A\","
+        "\"ip-addresses\": [ \"\" ],"
+        "\"prefixes\": [ ] }";
+
+    ElementPtr config_element = Element::fromJSON(config);
+
+    HostReservationParser6 parser(SubnetID(12));
+    EXPECT_THROW(parser.build(config_element), DhcpConfigError);
+}
+
+// This test verifies that the configuration parser throws an exception
+// when invalid prefix length is specified.
+TEST_F(HostReservationParserTest, dhcp6InvalidPrefixLength) {
+    std::string config = "{ \"duid\": \"01:02:03:04:05:06:07:08:09:0A\","
+        "\"prefixes\": [ \"2001:db8:1::/abc\" ] }";
+
+    ElementPtr config_element = Element::fromJSON(config);
+
+    HostReservationParser6 parser(SubnetID(12));
+    EXPECT_THROW(parser.build(config_element), DhcpConfigError);
+}
+
+// This test verifies that the configuration parser throws an exception
+// when empty prefix is specified.
+TEST_F(HostReservationParserTest, dhcp6NullPrefix) {
+    std::string config = "{ \"duid\": \"01:02:03:04:05:06:07:08:09:0A\","
+        "\"prefixes\": [ \"/64\" ] }";
+
+    ElementPtr config_element = Element::fromJSON(config);
+
+    HostReservationParser6 parser(SubnetID(12));
+    EXPECT_THROW(parser.build(config_element), DhcpConfigError);
+}
+
+// This test verifies that the configuration parser throws an exception
+// when only slash is specified for the prefix..
+TEST_F(HostReservationParserTest, dhcp6NullPrefix2) {
+    std::string config = "{ \"duid\": \"01:02:03:04:05:06:07:08:09:0A\","
+        "\"prefixes\": [ \"/\" ] }";
+
+    ElementPtr config_element = Element::fromJSON(config);
+
+    HostReservationParser6 parser(SubnetID(12));
+    EXPECT_THROW(parser.build(config_element), DhcpConfigError);
+}
+
+// This test verifies that the configuration parser throws an exception
+// when the same address is reserved twice.
+TEST_F(HostReservationParserTest, dhcp6DuplicatedAddress) {
+    std::string config = "{ \"duid\": \"01:02:03:04:05:06:07:08:09:0A\","
+        "\"ip-addresses\": [ \"2001:db8:1::1\", \"2001:db8:1::1\" ] }";
+
+    ElementPtr config_element = Element::fromJSON(config);
+
+    HostReservationParser6 parser(SubnetID(12));
+    EXPECT_THROW(parser.build(config_element), DhcpConfigError);
+}
+
+// This test verifies that the configuration parser throws an exception
+// when the same prefix is reserved twice.
+TEST_F(HostReservationParserTest, dhcp6DuplicatedPrefix) {
+    std::string config = "{ \"duid\": \"01:02:03:04:05:06:07:08:09:0A\","
+        "\"prefixes\": [ \"2001:db8:0101::/64\", \"2001:db8:0101::/64\" ] }";
+
+    ElementPtr config_element = Element::fromJSON(config);
+
+    HostReservationParser6 parser(SubnetID(12));
+    EXPECT_THROW(parser.build(config_element), DhcpConfigError);
+}
+
+
+} // end of anonymous namespace

+ 83 - 37
src/lib/dhcpsrv/tests/host_unittest.cc

@@ -27,7 +27,7 @@ namespace {
 // This test verifies that it is possible to create IPv6 address
 // reservation.
 TEST(IPv6ResrvTest, constructorAddress) {
-    IPv6Resrv resrv(IOAddress("2001:db8:1::cafe"));
+    IPv6Resrv resrv(IPv6Resrv::TYPE_NA, IOAddress("2001:db8:1::cafe"));
     EXPECT_EQ("2001:db8:1::cafe", resrv.getPrefix().toText());
     EXPECT_EQ(128, resrv.getPrefixLen());
     EXPECT_EQ(IPv6Resrv::TYPE_NA, resrv.getType());
@@ -36,74 +36,96 @@ TEST(IPv6ResrvTest, constructorAddress) {
 // This test verifies that it is possible to create IPv6 prefix
 // reservation.
 TEST(IPv6ResrvTest, constructorPrefix) {
-    IPv6Resrv resrv(IOAddress("2001:db8:1::"), 64);
+    IPv6Resrv resrv(IPv6Resrv::TYPE_PD, IOAddress("2001:db8:1::"), 64);
     EXPECT_EQ("2001:db8:1::", resrv.getPrefix().toText());
     EXPECT_EQ(64, resrv.getPrefixLen());
     EXPECT_EQ(IPv6Resrv::TYPE_PD, resrv.getType());
 }
 
+// This test verifies that the toText() function prints correctly.
+TEST(IPv6ResrvTest, toText) {
+    IPv6Resrv resrv_prefix(IPv6Resrv::TYPE_PD, IOAddress("2001:db8:1::"), 64);
+    EXPECT_EQ("2001:db8:1::/64", resrv_prefix.toText());
+
+    IPv6Resrv resrv_address(IPv6Resrv::TYPE_NA, IOAddress("2001:db8:111::23"));
+    EXPECT_EQ("2001:db8:111::23", resrv_address.toText());
+}
+
 // This test verifies that invalid prefix is rejected.
 TEST(IPv6ResrvTest, constructorInvalidPrefix) {
     // IPv4 address is invalid for IPv6 reservation.
-    EXPECT_THROW(IPv6Resrv(IOAddress("10.0.0.1"), 128), isc::BadValue);
+    EXPECT_THROW(IPv6Resrv(IPv6Resrv::TYPE_NA, IOAddress("10.0.0.1"), 128),
+                 isc::BadValue);
     // Multicast address is invalid for IPv6 reservation.
-    EXPECT_THROW(IPv6Resrv(IOAddress("ff02:1::2"), 128), isc::BadValue);
+    EXPECT_THROW(IPv6Resrv(IPv6Resrv::TYPE_NA, IOAddress("ff02:1::2"), 128),
+                 isc::BadValue);
 }
 
 // This test verifies that invalid prefix length is rejected.
 TEST(IPv6ResrvTest, constructiorInvalidPrefixLength) {
-    ASSERT_NO_THROW(IPv6Resrv(IOAddress("2001:db8:1::"), 128));
-    EXPECT_THROW(IPv6Resrv(IOAddress("2001:db8:1::"), 129), isc::BadValue);
-    EXPECT_THROW(IPv6Resrv(IOAddress("2001:db8:1::"), 244), isc::BadValue);
+    ASSERT_NO_THROW(IPv6Resrv(IPv6Resrv::TYPE_PD, IOAddress("2001:db8:1::"),
+                              128));
+    EXPECT_THROW(IPv6Resrv(IPv6Resrv::TYPE_PD, IOAddress("2001:db8:1::"), 129),
+                 isc::BadValue);
+    EXPECT_THROW(IPv6Resrv(IPv6Resrv::TYPE_PD, IOAddress("2001:db8:1::"), 244),
+                 isc::BadValue);
+    EXPECT_THROW(IPv6Resrv(IPv6Resrv::TYPE_NA, IOAddress("2001:db8:1::"), 64),
+                 isc::BadValue);
 }
 
 // This test verifies that it is possible to modify prefix and its
 // length in an existing reservation.
 TEST(IPv6ResrvTest, setPrefix) {
     // Create a reservation using an address and prefix length 128.
-    IPv6Resrv resrv(IOAddress("2001:db8:1::1"));
+    IPv6Resrv resrv(IPv6Resrv::TYPE_NA, IOAddress("2001:db8:1::1"));
     ASSERT_EQ("2001:db8:1::1", resrv.getPrefix().toText());
     ASSERT_EQ(128, resrv.getPrefixLen());
     ASSERT_EQ(IPv6Resrv::TYPE_NA, resrv.getType());
 
     // Modify the reservation to use a prefix having a length of 48.
-    ASSERT_NO_THROW(resrv.set(IOAddress("2001:db8::"), 48));
+    ASSERT_NO_THROW(resrv.set(IPv6Resrv::TYPE_PD, IOAddress("2001:db8::"), 48));
     EXPECT_EQ("2001:db8::", resrv.getPrefix().toText());
     EXPECT_EQ(48, resrv.getPrefixLen());
     EXPECT_EQ(IPv6Resrv::TYPE_PD, resrv.getType());
 
     // IPv4 address is invalid for IPv6 reservation.
-    EXPECT_THROW(resrv.set(IOAddress("10.0.0.1"), 128), isc::BadValue);
+    EXPECT_THROW(resrv.set(IPv6Resrv::TYPE_NA, IOAddress("10.0.0.1"), 128),
+                 isc::BadValue);
     // IPv6 multicast address is invalid for IPv6 reservation.
-    EXPECT_THROW(resrv.set(IOAddress("ff02::1:2"), 128), isc::BadValue);
+    EXPECT_THROW(resrv.set(IPv6Resrv::TYPE_NA, IOAddress("ff02::1:2"), 128),
+                 isc::BadValue);
     // Prefix length greater than 128 is invalid.
-    EXPECT_THROW(resrv.set(IOAddress("2001:db8:1::"), 129), isc::BadValue);
+    EXPECT_THROW(resrv.set(IPv6Resrv::TYPE_PD, IOAddress("2001:db8:1::"), 129),
+                 isc::BadValue);
 }
 
 // This test checks that the equality operators work fine.
 TEST(IPv6ResrvTest, equal) {
-    EXPECT_TRUE(IPv6Resrv(IOAddress("2001:db8::"), 64) ==
-                IPv6Resrv(IOAddress("2001:db8::"), 64));
-
-    EXPECT_FALSE(IPv6Resrv(IOAddress("2001:db8::"), 64) !=
-                IPv6Resrv(IOAddress("2001:db8::"), 64));
+    EXPECT_TRUE(IPv6Resrv(IPv6Resrv::TYPE_PD, IOAddress("2001:db8::"), 64) ==
+                IPv6Resrv(IPv6Resrv::TYPE_PD, IOAddress("2001:db8::"), 64));
 
+    EXPECT_FALSE(IPv6Resrv(IPv6Resrv::TYPE_PD, IOAddress("2001:db8::"), 64) !=
+                 IPv6Resrv(IPv6Resrv::TYPE_PD, IOAddress("2001:db8::"), 64));
 
-    EXPECT_TRUE(IPv6Resrv(IOAddress("2001:db8::1")) ==
-                IPv6Resrv(IOAddress("2001:db8::1")));
-    EXPECT_FALSE(IPv6Resrv(IOAddress("2001:db8::1")) !=
-                 IPv6Resrv(IOAddress("2001:db8::1")));
+    EXPECT_TRUE(IPv6Resrv(IPv6Resrv::TYPE_NA, IOAddress("2001:db8::1")) ==
+                IPv6Resrv(IPv6Resrv::TYPE_NA, IOAddress("2001:db8::1")));
+    EXPECT_FALSE(IPv6Resrv(IPv6Resrv::TYPE_NA, IOAddress("2001:db8::1")) !=
+                 IPv6Resrv(IPv6Resrv::TYPE_NA, IOAddress("2001:db8::1")));
 
+    EXPECT_FALSE(IPv6Resrv(IPv6Resrv::TYPE_NA, IOAddress("2001:db8::1")) ==
+                 IPv6Resrv(IPv6Resrv::TYPE_NA, IOAddress("2001:db8::2")));
+    EXPECT_TRUE(IPv6Resrv(IPv6Resrv::TYPE_NA, IOAddress("2001:db8::1")) !=
+                 IPv6Resrv(IPv6Resrv::TYPE_NA, IOAddress("2001:db8::2")));
 
-    EXPECT_FALSE(IPv6Resrv(IOAddress("2001:db8::1")) ==
-                 IPv6Resrv(IOAddress("2001:db8::2")));
-    EXPECT_TRUE(IPv6Resrv(IOAddress("2001:db8::1")) !=
-                 IPv6Resrv(IOAddress("2001:db8::2")));
+    EXPECT_FALSE(IPv6Resrv(IPv6Resrv::TYPE_PD, IOAddress("2001:db8::"), 64) ==
+                 IPv6Resrv(IPv6Resrv::TYPE_PD, IOAddress("2001:db8::"), 48));
+    EXPECT_TRUE(IPv6Resrv(IPv6Resrv::TYPE_PD, IOAddress("2001:db8::"), 64) !=
+                IPv6Resrv(IPv6Resrv::TYPE_PD, IOAddress("2001:db8::"), 48));
 
-    EXPECT_FALSE(IPv6Resrv(IOAddress("2001:db8::"), 64) ==
-                 IPv6Resrv(IOAddress("2001:db8::"), 48));
-    EXPECT_TRUE(IPv6Resrv(IOAddress("2001:db8::"), 64) !=
-                 IPv6Resrv(IOAddress("2001:db8::"), 48));
+    EXPECT_FALSE(IPv6Resrv(IPv6Resrv::TYPE_NA, IOAddress("2001:db8::1"), 128) ==
+                 IPv6Resrv(IPv6Resrv::TYPE_PD, IOAddress("2001:db8::1"), 128));
+    EXPECT_TRUE(IPv6Resrv(IPv6Resrv::TYPE_NA, IOAddress("2001:db8::1"), 128) !=
+                IPv6Resrv(IPv6Resrv::TYPE_PD, IOAddress("2001:db8::1"), 128));
 
 }
 
@@ -333,27 +355,47 @@ TEST(HostTest, addReservations) {
 
     // Add 4 reservations: 2 for NAs, 2 for PDs.
     ASSERT_NO_THROW(
-        host->addReservation(IPv6Resrv(IOAddress("2001:db8:1::cafe")));
-        host->addReservation(IPv6Resrv(IOAddress("2001:db8:1:1::"), 64));
-        host->addReservation(IPv6Resrv(IOAddress("2001:db8:1:2::"), 64));
-        host->addReservation(IPv6Resrv(IOAddress("2001:db8:1::1")));
+        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")));
     );
 
+    // Check that reservations exist.
+    EXPECT_TRUE(host->hasReservation(IPv6Resrv(IPv6Resrv::TYPE_NA,
+                                               IOAddress("2001:db8:1::cafe"))));
+    EXPECT_TRUE(host->hasReservation(IPv6Resrv(IPv6Resrv::TYPE_PD,
+                                               IOAddress("2001:db8:1:1::"),
+                                               64)));
+    EXPECT_TRUE(host->hasReservation(IPv6Resrv(IPv6Resrv::TYPE_PD,
+                                               IOAddress("2001:db8:1:2::"),
+                                               64)));
+    EXPECT_TRUE(host->hasReservation(IPv6Resrv(IPv6Resrv::TYPE_NA,
+                                               IOAddress("2001:db8:1::1"))));
+
     // Get only NA reservations.
     IPv6ResrvRange addresses = host->getIPv6Reservations(IPv6Resrv::TYPE_NA);
     ASSERT_EQ(2, std::distance(addresses.first, addresses.second));
-    EXPECT_TRUE(reservationExists(IPv6Resrv(IOAddress("2001:db8:1::cafe")),
+    EXPECT_TRUE(reservationExists(IPv6Resrv(IPv6Resrv::TYPE_NA,
+                                            IOAddress("2001:db8:1::cafe")),
                                   addresses));
-    EXPECT_TRUE(reservationExists(IPv6Resrv(IOAddress("2001:db8:1::1")),
+    EXPECT_TRUE(reservationExists(IPv6Resrv(IPv6Resrv::TYPE_NA,
+                                            IOAddress("2001:db8:1::1")),
                                   addresses));
 
 
     // Get only PD reservations.
     IPv6ResrvRange prefixes = host->getIPv6Reservations(IPv6Resrv::TYPE_PD);
     ASSERT_EQ(2, std::distance(prefixes.first, prefixes.second));
-    EXPECT_TRUE(reservationExists(IPv6Resrv(IOAddress("2001:db8:1:1::"), 64),
+    EXPECT_TRUE(reservationExists(IPv6Resrv(IPv6Resrv::TYPE_PD,
+                                            IOAddress("2001:db8:1:1::"), 64),
                                   prefixes));
-    EXPECT_TRUE(reservationExists(IPv6Resrv(IOAddress("2001:db8:1:2::"), 64),
+    EXPECT_TRUE(reservationExists(IPv6Resrv(IPv6Resrv::TYPE_PD,
+                                            IOAddress("2001:db8:1:2::"), 64),
                                   prefixes));
 }
 
@@ -380,6 +422,10 @@ TEST(HostTest, setValues) {
     EXPECT_EQ(234, host->getIPv6SubnetID());
     EXPECT_EQ("10.0.0.1", host->getIPv4Reservation().toText());
     EXPECT_EQ("other-host.example.org", host->getHostname());
+
+    // An IPv6 address can't be used for IPv4 reservations.
+    EXPECT_THROW(host->setIPv4Reservation(IOAddress("2001:db8:1::1")),
+                 isc::BadValue);
 }
 
 // Test that Host constructors initialize client classes from string.