Browse Source

[master] Merge branch 'trac4303'

Marcin Siodelski 9 years ago
parent
commit
942808400e

+ 65 - 2
src/bin/dhcp4/dhcp4_srv.cc

@@ -22,6 +22,7 @@
 #include <dhcpsrv/addr_utilities.h>
 #include <dhcpsrv/addr_utilities.h>
 #include <dhcpsrv/callout_handle_store.h>
 #include <dhcpsrv/callout_handle_store.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/cfg_host_operations.h>
 #include <dhcpsrv/cfg_subnets4.h>
 #include <dhcpsrv/cfg_subnets4.h>
 #include <dhcpsrv/lease_mgr.h>
 #include <dhcpsrv/lease_mgr.h>
 #include <dhcpsrv/lease_mgr_factory.h>
 #include <dhcpsrv/lease_mgr_factory.h>
@@ -137,9 +138,17 @@ Dhcpv4Exchange::Dhcpv4Exchange(const AllocEnginePtr& alloc_engine,
                 .arg(query->getLabel())
                 .arg(query->getLabel())
                 .arg(subnet->getID());
                 .arg(subnet->getID());
         }
         }
+
+        // Find static reservations if not disabled for our subnet.
+        if (subnet->getHostReservationMode() != Subnet::HR_DISABLED) {
+            // Before we can check for static reservations, we need to prepare a set
+            // of identifiers to be used for this.
+            setHostIdentifiers();
+
+            // Check for static reservations.
+            alloc_engine->findReservation(*context_);
+        }
     }
     }
-    // Check for static reservations.
-    alloc_engine->findReservation(*context_);
 };
 };
 
 
 void
 void
@@ -232,6 +241,60 @@ Dhcpv4Exchange::copyDefaultOptions() {
     }
     }
 }
 }
 
 
+void
+Dhcpv4Exchange::setHostIdentifiers() {
+    const ConstCfgHostOperationsPtr cfg =
+        CfgMgr::instance().getCurrentCfg()->getCfgHostOperations4();
+    // Collect host identifiers. The identifiers are stored in order of preference.
+    // The server will use them in that order to search for host reservations.
+    BOOST_FOREACH(const Host::IdentifierType& id_type,
+                  cfg->getIdentifierTypes()) {
+        switch (id_type) {
+        case Host::IDENT_HWADDR:
+            if (context_->hwaddr_ && !context_->hwaddr_->hwaddr_.empty()) {
+                context_->addHostIdentifier(id_type, context_->hwaddr_->hwaddr_);
+            }
+            break;
+
+        case Host::IDENT_DUID:
+            if (context_->clientid_) {
+                const std::vector<uint8_t>& vec = context_->clientid_->getDuid();
+                if (!vec.empty()) {
+                    // Client identifier type = DUID? Client identifier holding a DUID
+                    // comprises Type (1 byte), IAID (4 bytes), followed by the actual
+                    // DUID. Thus, the minimal length is 6.
+                    if ((vec[0] == CLIENT_ID_OPTION_TYPE_DUID) && (vec.size() > 5)) {
+                        // Extract DUID, skip IAID.
+                        context_->addHostIdentifier(id_type,
+                                                    std::vector<uint8_t>(vec.begin() + 5,
+                                                                         vec.end()));
+                    }
+                    /// @todo Add support for other client identifiers (see #4317).
+                }
+            }
+            break;
+
+        case Host::IDENT_CIRCUIT_ID:
+            {
+                OptionPtr rai = query_->getOption(DHO_DHCP_AGENT_OPTIONS);
+                if (rai) {
+                    OptionPtr circuit_id_opt = rai->getOption(RAI_OPTION_AGENT_CIRCUIT_ID);
+                    if (circuit_id_opt) {
+                        const OptionBuffer& circuit_id_vec = circuit_id_opt->getData();
+                        if (!circuit_id_vec.empty()) {
+                            context_->addHostIdentifier(id_type, circuit_id_vec);
+                        }
+                    }
+                }
+            }
+            break;
+
+        default:
+            ;
+        }
+    }
+}
+
 const std::string Dhcpv4Srv::VENDOR_CLASS_PREFIX("VENDOR_CLASS_");
 const std::string Dhcpv4Srv::VENDOR_CLASS_PREFIX("VENDOR_CLASS_");
 
 
 Dhcpv4Srv::Dhcpv4Srv(uint16_t port, const bool use_bcast,
 Dhcpv4Srv::Dhcpv4Srv(uint16_t port, const bool use_bcast,

+ 8 - 0
src/bin/dhcp4/dhcp4_srv.h

@@ -137,6 +137,14 @@ private:
     /// not NULL.
     /// not NULL.
     void copyDefaultOptions();
     void copyDefaultOptions();
 
 
+    /// @brief Set host identifiers within a context.
+    ///
+    /// This method sets an ordered list of host identifier types and
+    /// values which the server should use to find host reservations.
+    /// The order of the set is determined by the configuration parameter,
+    /// host-reservation-identifiers
+    void setHostIdentifiers();
+
     /// @brief Pointer to the allocation engine used by the server.
     /// @brief Pointer to the allocation engine used by the server.
     AllocEnginePtr alloc_engine_;
     AllocEnginePtr alloc_engine_;
     /// @brief Pointer to the DHCPv4 message sent by the client.
     /// @brief Pointer to the DHCPv4 message sent by the client.

+ 2 - 0
src/bin/dhcp4/json_config_parser.cc

@@ -448,6 +448,8 @@ DhcpConfigParser* createGlobalDhcp4ConfigParser(const std::string& config_id,
         parser = new ExpirationConfigParser();
         parser = new ExpirationConfigParser();
     } else if (config_id.compare("client-classes") == 0) {
     } else if (config_id.compare("client-classes") == 0) {
         parser = new ClientClassDefListParser(config_id, globalContext());
         parser = new ClientClassDefListParser(config_id, globalContext());
+    } else if (config_id.compare("host-reservation-identifiers") == 0) {
+        parser = new HostReservationIdsParser4();
     } else {
     } else {
         isc_throw(DhcpConfigError,
         isc_throw(DhcpConfigError,
                 "unsupported global configuration parameter: "
                 "unsupported global configuration parameter: "

+ 14 - 3
src/bin/dhcp4/tests/dhcp4_client.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2016 Internet Systems Consortium, Inc. ("ISC")
 //
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -49,7 +49,8 @@ Dhcp4Client::Dhcp4Client(const Dhcp4Client::State& state) :
     server_facing_relay_addr_("10.0.0.2"),
     server_facing_relay_addr_("10.0.0.2"),
     srv_(boost::shared_ptr<NakedDhcpv4Srv>(new NakedDhcpv4Srv(0))),
     srv_(boost::shared_ptr<NakedDhcpv4Srv>(new NakedDhcpv4Srv(0))),
     state_(state),
     state_(state),
-    use_relay_(false) {
+    use_relay_(false),
+    circuit_id_() {
 }
 }
 
 
 Dhcp4Client::Dhcp4Client(boost::shared_ptr<NakedDhcpv4Srv> srv,
 Dhcp4Client::Dhcp4Client(boost::shared_ptr<NakedDhcpv4Srv> srv,
@@ -67,7 +68,8 @@ Dhcp4Client::Dhcp4Client(boost::shared_ptr<NakedDhcpv4Srv> srv,
     server_facing_relay_addr_("10.0.0.2"),
     server_facing_relay_addr_("10.0.0.2"),
     srv_(srv),
     srv_(srv),
     state_(state),
     state_(state),
-    use_relay_(false) {
+    use_relay_(false),
+    circuit_id_() {
 }
 }
 
 
 void
 void
@@ -468,6 +470,15 @@ Dhcp4Client::sendMsg(const Pkt4Ptr& msg) {
         msg->setHops(1);
         msg->setHops(1);
         msg->setGiaddr(relay_addr_);
         msg->setGiaddr(relay_addr_);
         msg->setLocalAddr(server_facing_relay_addr_);
         msg->setLocalAddr(server_facing_relay_addr_);
+        // Insert RAI
+        OptionPtr rai(new Option(Option::V4, DHO_DHCP_AGENT_OPTIONS));
+        // Insert circuit id, if specified.
+        if (!circuit_id_.empty()) {
+            rai->addOption(OptionPtr(new Option(Option::V4, RAI_OPTION_AGENT_CIRCUIT_ID,
+                                                OptionBuffer(circuit_id_.begin(),
+                                                             circuit_id_.end()))));
+        }
+        msg->addOption(rai);
     }
     }
     // Repack the message to simulate wire-data parsing.
     // Repack the message to simulate wire-data parsing.
     msg->pack();
     msg->pack();

+ 13 - 1
src/bin/dhcp4/tests/dhcp4_client.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2016 Internet Systems Consortium, Inc. ("ISC")
 //
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -284,6 +284,14 @@ public:
                         const uint8_t option2 = 0,
                         const uint8_t option2 = 0,
                         const uint8_t option3 = 0);
                         const uint8_t option3 = 0);
 
 
+    /// @brief Sets circuit-id value to be included in the circuit-id
+    /// sub option of the RAI option.
+    ///
+    /// @param circuit_id New circuit-id value.
+    void setCircuitId(const std::string& circuit_id) {
+        circuit_id_ = circuit_id;
+    }
+
     /// @brief Sets destination address for the messages being sent by the
     /// @brief Sets destination address for the messages being sent by the
     /// client.
     /// client.
     ///
     ///
@@ -468,6 +476,10 @@ private:
     /// @brief Enable relaying messages to the server.
     /// @brief Enable relaying messages to the server.
     bool use_relay_;
     bool use_relay_;
 
 
+    /// @brief Specifies value to be inserted into circuit-id sub option
+    /// of the RAI option.
+    std::string circuit_id_;
+
     /// @brief Extra options the client will send.
     /// @brief Extra options the client will send.
     OptionCollection extra_options_;
     OptionCollection extra_options_;
 };
 };

+ 105 - 1
src/bin/dhcp4/tests/dora_unittest.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2016 Internet Systems Consortium, Inc. ("ISC")
 //
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -57,6 +57,11 @@ namespace {
 ///   - 1 pool: 10.0.0.10-10.0.0.100
 ///   - 1 pool: 10.0.0.10-10.0.0.100
 ///   - match-client-id flag is set to false, thus the server
 ///   - match-client-id flag is set to false, thus the server
 ///     uses HW address for lease lookup, rather than client id
 ///     uses HW address for lease lookup, rather than client id
+///
+/// - Configuration 4:
+///   - The same as configuration 2, but using different values in
+///     'host-reservation-identifiers'
+///
 const char* DORA_CONFIGS[] = {
 const char* DORA_CONFIGS[] = {
 // Configuration 0
 // Configuration 0
     "{ \"interfaces-config\": {"
     "{ \"interfaces-config\": {"
@@ -125,6 +130,14 @@ const char* DORA_CONFIGS[] = {
         "       {"
         "       {"
         "         \"hw-address\": \"aa:bb:cc:dd:ee:ff\","
         "         \"hw-address\": \"aa:bb:cc:dd:ee:ff\","
         "         \"ip-address\": \"10.0.0.7\""
         "         \"ip-address\": \"10.0.0.7\""
+        "       },"
+        "       {"
+        "         \"duid\": \"01:02:03:04:05\","
+        "         \"ip-address\": \"10.0.0.8\""
+        "       },"
+        "       {"
+        "         \"circuit-id\": \"'charter950'\","
+        "         \"ip-address\": \"10.0.0.9\""
         "       }"
         "       }"
         "    ]"
         "    ]"
         "} ]"
         "} ]"
@@ -146,6 +159,32 @@ const char* DORA_CONFIGS[] = {
         "    } ]"
         "    } ]"
         " } ]"
         " } ]"
     "}",
     "}",
+
+// Configuration 4
+    "{ \"interfaces-config\": {"
+        "      \"interfaces\": [ \"*\" ]"
+        "},"
+        "\"host-reservation-identifiers\": [ \"circuit-id\", \"hw-address\" ],"
+        "\"valid-lifetime\": 600,"
+        "\"subnet4\": [ { "
+        "    \"subnet\": \"10.0.0.0/24\", "
+        "    \"pools\": [ { \"pool\": \"10.0.0.10-10.0.0.100\" } ],"
+        "    \"reservations\": [ "
+        "       {"
+        "         \"hw-address\": \"aa:bb:cc:dd:ee:ff\","
+        "         \"ip-address\": \"10.0.0.7\""
+        "       },"
+        "       {"
+        "         \"duid\": \"01:02:03:04:05\","
+        "         \"ip-address\": \"10.0.0.8\""
+        "       },"
+        "       {"
+        "         \"circuit-id\": \"'charter950'\","
+        "         \"ip-address\": \"10.0.0.9\""
+        "       }"
+        "    ]"
+        "} ]"
+    "}"
 };
 };
 
 
 /// @brief Test fixture class for testing 4-way (DORA) exchanges.
 /// @brief Test fixture class for testing 4-way (DORA) exchanges.
@@ -688,6 +727,71 @@ TEST_F(DORATest, reservation) {
     ASSERT_TRUE(subnet->inPool(Lease::TYPE_V4, clientB.config_.lease_.addr_));
     ASSERT_TRUE(subnet->inPool(Lease::TYPE_V4, clientB.config_.lease_.addr_));
 }
 }
 
 
+// This test checks that it is possible to make a reservation by
+// circuit-id inserted by the relay agent.
+TEST_F(DORATest, reservationByCircuitId) {
+    Dhcp4Client client(Dhcp4Client::SELECTING);
+    // Use relay agent so as the circuit-id can be inserted.
+    client.useRelay(true, IOAddress("10.0.0.1"), IOAddress("10.0.0.2"));
+    // Specify circuit-id.
+    client.setCircuitId("charter950");
+
+    // Configure DHCP server.
+    configure(DORA_CONFIGS[2], *client.getServer());
+    // Client A performs 4-way exchange and should obtain a reserved
+    // address.
+    ASSERT_NO_THROW(client.doDORA(boost::shared_ptr<
+                                  IOAddress>(new IOAddress("0.0.0.0"))));
+    // Make sure that the server responded.
+    ASSERT_TRUE(client.getContext().response_);
+    Pkt4Ptr resp = client.getContext().response_;
+    // Make sure that the server has responded with DHCPACK.
+    ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+    // Make sure that the client has got the lease for the reserved address.
+    ASSERT_EQ("10.0.0.9", client.config_.lease_.addr_.toText());
+}
+
+// This test verifies that order in which host identifiers are used to
+// retrieve host reservations can be controlled.
+TEST_F(DORATest, hostIdentifiersOrder) {
+    Dhcp4Client client(Dhcp4Client::SELECTING);
+    client.setHWAddress("aa:bb:cc:dd:ee:ff");
+    // Use relay agent so as the circuit-id can be inserted.
+    client.useRelay(true, IOAddress("10.0.0.1"), IOAddress("10.0.0.2"));
+    // Specify circuit-id.
+    client.setCircuitId("charter950");
+
+    // Configure DHCP server.
+    configure(DORA_CONFIGS[2], *client.getServer());
+    // Perform 4-way exchange to obtain reserved address.
+    // The client has in fact two reserved addresses, but the one assigned
+    // should be by hw-address.
+    ASSERT_NO_THROW(client.doDORA(boost::shared_ptr<
+                                  IOAddress>(new IOAddress("0.0.0.0"))));
+    // Make sure that the server responded.
+    ASSERT_TRUE(client.getContext().response_);
+    Pkt4Ptr resp = client.getContext().response_;
+    // Make sure that the server has responded with DHCPACK.
+    ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+    // Make sure that the client has got the lease for the reserved address.
+    ASSERT_EQ("10.0.0.7", client.config_.lease_.addr_.toText());
+
+    // Reconfigure the server to change the preference order of the
+    // host identifiers. The 'circuit-id' should now take precedence over
+    // the hw-address.
+    configure(DORA_CONFIGS[4], *client.getServer());
+    ASSERT_NO_THROW(client.doDORA(boost::shared_ptr<
+                                  IOAddress>(new IOAddress("0.0.0.0"))));
+    // Make sure that the server responded.
+    ASSERT_TRUE(client.getContext().response_);
+    resp = client.getContext().response_;
+    // Make sure that the server has responded with DHCPACK.
+    ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+    // Make sure that the client has got the lease for the reserved address.
+    ASSERT_EQ("10.0.0.9", client.config_.lease_.addr_.toText());
+
+}
+
 // This test checks that setting the match-client-id value to false causes
 // This test checks that setting the match-client-id value to false causes
 // the server to ignore changing client identifier when the client is
 // the server to ignore changing client identifier when the client is
 // using consistent HW address.
 // using consistent HW address.

+ 35 - 2
src/bin/dhcp6/dhcp6_srv.cc

@@ -28,6 +28,7 @@
 #include <dhcp6/dhcp6_log.h>
 #include <dhcp6/dhcp6_log.h>
 #include <dhcp6/dhcp6_srv.h>
 #include <dhcp6/dhcp6_srv.h>
 #include <dhcpsrv/callout_handle_store.h>
 #include <dhcpsrv/callout_handle_store.h>
+#include <dhcpsrv/cfg_host_operations.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/lease_mgr.h>
 #include <dhcpsrv/lease_mgr.h>
 #include <dhcpsrv/lease_mgr_factory.h>
 #include <dhcpsrv/lease_mgr_factory.h>
@@ -276,10 +277,40 @@ AllocEngine::ClientContext6
 Dhcpv6Srv::createContext(const Pkt6Ptr& pkt) {
 Dhcpv6Srv::createContext(const Pkt6Ptr& pkt) {
     AllocEngine::ClientContext6 ctx;
     AllocEngine::ClientContext6 ctx;
     ctx.subnet_ = selectSubnet(pkt);
     ctx.subnet_ = selectSubnet(pkt);
+    ctx.query_ = pkt;
     ctx.duid_ = pkt->getClientId();
     ctx.duid_ = pkt->getClientId();
     ctx.hwaddr_ = getMAC(pkt);
     ctx.hwaddr_ = getMAC(pkt);
-    ctx.query_ = pkt;
-    alloc_engine_->findReservation(ctx);
+
+    // Collect host identifiers if host reservations enabled. The identifiers
+    // are stored in order of preference. The server will use them in that
+    // order to search for host reservations.
+    if (ctx.subnet_ &&
+        (ctx.subnet_->getHostReservationMode() != Subnet::HR_DISABLED)) {
+        const ConstCfgHostOperationsPtr cfg =
+            CfgMgr::instance().getCurrentCfg()->getCfgHostOperations6();
+        BOOST_FOREACH(const Host::IdentifierType& id_type,
+                      cfg->getIdentifierTypes()) {
+            switch (id_type) {
+            case Host::IDENT_DUID:
+                if (ctx.duid_) {
+                    ctx.addHostIdentifier(id_type, ctx.duid_->getDuid());
+                }
+                break;
+
+            case Host::IDENT_HWADDR:
+                if (ctx.hwaddr_) {
+                    ctx.addHostIdentifier(id_type, ctx.hwaddr_->hwaddr_);
+                }
+                break;
+
+            default:
+                ;
+            }
+        }
+
+        // Find host reservations using specified identifiers.
+        alloc_engine_->findReservation(ctx);
+    }
 
 
     return (ctx);
     return (ctx);
 }
 }
@@ -1321,6 +1352,7 @@ Dhcpv6Srv::assignIA_NA(const Pkt6Ptr& query, const Pkt6Ptr& answer,
     ctx.hwaddr_ = orig_ctx.hwaddr_;
     ctx.hwaddr_ = orig_ctx.hwaddr_;
     ctx.host_ = orig_ctx.host_;
     ctx.host_ = orig_ctx.host_;
     ctx.query_ = orig_ctx.query_;
     ctx.query_ = orig_ctx.query_;
+    ctx.host_identifiers_ = orig_ctx.host_identifiers_;
 
 
     Lease6Collection leases = alloc_engine_->allocateLeases6(ctx);
     Lease6Collection leases = alloc_engine_->allocateLeases6(ctx);
 
 
@@ -1442,6 +1474,7 @@ Dhcpv6Srv::assignIA_PD(const Pkt6Ptr& query, const Pkt6Ptr& answer,
     ctx.hwaddr_ = orig_ctx.hwaddr_;
     ctx.hwaddr_ = orig_ctx.hwaddr_;
     ctx.host_ = orig_ctx.host_;
     ctx.host_ = orig_ctx.host_;
     ctx.query_ = orig_ctx.query_;
     ctx.query_ = orig_ctx.query_;
+    ctx.host_identifiers_ = orig_ctx.host_identifiers_;
 
 
     Lease6Collection leases = alloc_engine_->allocateLeases6(ctx);
     Lease6Collection leases = alloc_engine_->allocateLeases6(ctx);
 
 

+ 2 - 0
src/bin/dhcp6/json_config_parser.cc

@@ -702,6 +702,8 @@ DhcpConfigParser* createGlobal6DhcpConfigParser(const std::string& config_id,
         parser = new ClientClassDefListParser(config_id, globalContext());
         parser = new ClientClassDefListParser(config_id, globalContext());
     } else if (config_id.compare("server-id") == 0) {
     } else if (config_id.compare("server-id") == 0) {
         parser = new DUIDConfigParser();
         parser = new DUIDConfigParser();
+    } else if (config_id.compare("host-reservation-identifiers") == 0) {
+        parser = new HostReservationIdsParser6();
     } else {
     } else {
         isc_throw(DhcpConfigError,
         isc_throw(DhcpConfigError,
                 "unsupported global configuration parameter: "
                 "unsupported global configuration parameter: "

+ 8 - 1
src/bin/dhcp6/tests/dhcp6_client.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2016 Internet Systems Consortium, Inc. ("ISC")
 //
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -412,6 +412,13 @@ public:
         iface_name_ = iface_name;
         iface_name_ = iface_name;
     }
     }
 
 
+    /// @brief Sets link local address used by the client.
+    ///
+    /// @param link_local New link local address.
+    void setLinkLocal(const asiolink::IOAddress& link_local) {
+        link_local_ = link_local;
+    }
+
     /// @brief Set an address hint to be sent to a server.
     /// @brief Set an address hint to be sent to a server.
     ///
     ///
     /// @param pref_lft Preferred lifetime.
     /// @param pref_lft Preferred lifetime.

+ 131 - 1
src/bin/dhcp6/tests/host_unittest.cc

@@ -1,15 +1,17 @@
-// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015-2016 Internet Systems Consortium, Inc. ("ISC")
 //
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
 // License, v. 2.0. If a copy of the MPL was not distributed with this
 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 
 #include <config.h>
 #include <config.h>
+#include <asiolink/io_address.h>
 #include <dhcp/tests/iface_mgr_test_config.h>
 #include <dhcp/tests/iface_mgr_test_config.h>
 #include <dhcp6/tests/dhcp6_test_utils.h>
 #include <dhcp6/tests/dhcp6_test_utils.h>
 #include <dhcp6/tests/dhcp6_client.h>
 #include <dhcp6/tests/dhcp6_client.h>
 
 
 using namespace isc;
 using namespace isc;
+using namespace isc::asiolink;
 using namespace isc::dhcp;
 using namespace isc::dhcp;
 using namespace isc::dhcp::test;
 using namespace isc::dhcp::test;
 
 
@@ -19,6 +21,11 @@ namespace {
 ///
 ///
 /// - Configuration 0:
 /// - Configuration 0:
 ///   Single subnet with two reservations, one with a hostname, one without
 ///   Single subnet with two reservations, one with a hostname, one without
+/// - Configuration 1:
+///   Multiple reservations using different host identifiers.
+/// - Configuration 2:
+///   Same as configuration 1 but 'host-reservation-identifiers' specified
+///   in non-default order.
 const char* CONFIGS[] = {
 const char* CONFIGS[] = {
     // Configuration 0:
     // Configuration 0:
     "{ "
     "{ "
@@ -45,6 +52,61 @@ const char* CONFIGS[] = {
         "        \"ip-addresses\": [ \"2001:db8:1:1::babf\" ]"
         "        \"ip-addresses\": [ \"2001:db8:1:1::babf\" ]"
         "    } ]"
         "    } ]"
         " } ]"
         " } ]"
+    "}",
+
+    // Configuration 1:
+    "{ "
+        "\"interfaces-config\": {"
+        "  \"interfaces\": [ \"*\" ]"
+        "},"
+        "\"valid-lifetime\": 4000, "
+        "\"preferred-lifetime\": 3000,"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"mac-sources\": [ \"ipv6-link-local\" ], "
+        "\"subnet6\": [ "
+        " { "
+        "    \"subnet\": \"2001:db8:1::/48\", "
+        "    \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
+        "    \"interface\" : \"eth0\" , "
+        "    \"reservations\": ["
+        "    {"
+        "        \"hw-address\": \"38:60:77:d5:ff:ee\","
+        "        \"ip-addresses\": [ \"2001:db8:1::1\" ]"
+        "    },"
+        "    {"
+        "        \"duid\": \"01:02:03:05\","
+        "        \"ip-addresses\": [ \"2001:db8:1::2\" ]"
+        "    } ]"
+        " } ]"
+    "}",
+
+    // Configuration 2:
+    "{ "
+        "\"interfaces-config\": {"
+        "  \"interfaces\": [ \"*\" ]"
+        "},"
+        "\"host-reservation-identifiers\": [ \"duid\", \"hw-address\" ],"
+        "\"valid-lifetime\": 4000, "
+        "\"preferred-lifetime\": 3000,"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"mac-sources\": [ \"ipv6-link-local\" ], "
+        "\"subnet6\": [ "
+        " { "
+        "    \"subnet\": \"2001:db8:1::/48\", "
+        "    \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
+        "    \"interface\" : \"eth0\" , "
+        "    \"reservations\": ["
+        "    {"
+        "        \"hw-address\": \"38:60:77:d5:ff:ee\","
+        "        \"ip-addresses\": [ \"2001:db8:1::1\" ]"
+        "    },"
+        "    {"
+        "        \"duid\": \"01:02:03:05\","
+        "        \"ip-addresses\": [ \"2001:db8:1::2\" ]"
+        "    } ]"
+        " } ]"
     "}"
     "}"
 };
 };
 
 
@@ -59,6 +121,39 @@ public:
           iface_mgr_test_config_(true) {
           iface_mgr_test_config_(true) {
     }
     }
 
 
+    /// @brief Verifies that the reservation is retrieved by the server
+    /// using one of the host identifiers.
+    ///
+    /// @param client Reference to a client to be used in the test.
+    /// The client should be preconfigured to insert a specific identifier
+    /// into the message, e.g. DUID, HW address etc.
+    /// @param config_index Index of the configuration to use in the CONFIGS
+    /// table.
+    /// @param exp_ip_address Expected IPv6 address in the returned
+    /// reservation.
+    void testReservationByIdentifier(Dhcp6Client& client,
+                                     const unsigned int config_index,
+                                     const std::string exp_ip_address) {
+        configure(CONFIGS[config_index], *client.getServer());
+
+        const Subnet6Collection* subnets = CfgMgr::instance().getCurrentCfg()->
+            getCfgSubnets6()->getAll();
+        ASSERT_EQ(1, subnets->size());
+
+        // Configure client to request IA_NA and append IA_NA option
+        //  to the client's message.
+        client.useNA();
+        ASSERT_NO_THROW(client.useHint(100, 200, 64, "2001:db8:1:1::dead:beef"));
+
+        // Perform 4-way exchange.
+        ASSERT_NO_THROW(client.doSARR());
+
+        // Verify that the client we got the reserved address
+        ASSERT_EQ(1, client.getLeaseNum());
+        Lease6 lease_client = client.getLease(0);
+        EXPECT_EQ(exp_ip_address, lease_client.addr_.toText());
+    }
+
     /// @brief Interface Manager's fake configuration control.
     /// @brief Interface Manager's fake configuration control.
     IfaceMgrTestConfig iface_mgr_test_config_;
     IfaceMgrTestConfig iface_mgr_test_config_;
 };
 };
@@ -246,4 +341,39 @@ TEST_F(HostTest, sarrAndRebind) {
     EXPECT_EQ("alice", lease_server2->hostname_);
     EXPECT_EQ("alice", lease_server2->hostname_);
 }
 }
 
 
+// This test verfies that the host reservation by DUID is found by the
+// server.
+TEST_F(HostTest, reservationByDUID) {
+    Dhcp6Client client;
+    // Set DUID matching the one used to create host reservations.
+    client.setDUID("01:02:03:05");
+    // Run the actual test.
+    testReservationByIdentifier(client, 1, "2001:db8:1::2");
+}
+
+// This test verfies that the host reservation by HW address is found
+// by the server.
+TEST_F(HostTest, reservationByHWAddress) {
+    Dhcp6Client client;
+    // Set link local address for the client which the server will
+    // use to decode the HW address as 38:60:77:d5:ff:ee. This
+    // decoded address will be used to search for host reservations.
+    client.setLinkLocal(IOAddress("fe80::3a60:77ff:fed5:ffee"));
+    // Run the actual test.
+    testReservationByIdentifier(client, 1, "2001:db8:1::1");
+}
+
+// This test verifies that order in which host identifiers are used to
+// retrieve host reservations can be controlled.
+TEST_F(HostTest, hostIdentifiersOrder) {
+    Dhcp6Client client;
+    // Set DUID matching the one used to create host reservations.
+    client.setDUID("01:02:03:05");
+    // Set link local address for the client which the server will
+    // use to decode the HW address as 38:60:77:d5:ff:ee. This
+    // decoded address will be used to search for host reservations.
+    client.setLinkLocal(IOAddress("fe80::3a60:77ff:fed5:ffee"));
+    testReservationByIdentifier(client, 2, "2001:db8:1::2");
+}
+
 } // end of anonymous namespace
 } // end of anonymous namespace

+ 3 - 0
src/lib/dhcp/dhcp4.h

@@ -283,6 +283,9 @@ static const uint16_t RAI_OPTION_VIRTUAL_SUBNET_SELECT_CTRL = 152; //RFC6607
 
 
 #endif
 #endif
 
 
+/* Client identifier types */
+static const uint8_t CLIENT_ID_OPTION_TYPE_DUID = 255;
+
 } // end of isc::dhcp namespace
 } // end of isc::dhcp namespace
 } // end of isc namespace
 } // end of isc namespace
 
 

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

@@ -90,6 +90,7 @@ libkea_dhcpsrv_la_SOURCES += cfg_duid.cc cfg_duid.h
 libkea_dhcpsrv_la_SOURCES += cfg_hosts.cc cfg_hosts.h
 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_iface.cc cfg_iface.h
 libkea_dhcpsrv_la_SOURCES += cfg_expiration.cc cfg_expiration.h
 libkea_dhcpsrv_la_SOURCES += cfg_expiration.cc cfg_expiration.h
+libkea_dhcpsrv_la_SOURCES += cfg_host_operations.cc cfg_host_operations.h
 libkea_dhcpsrv_la_SOURCES += cfg_option.cc cfg_option.h
 libkea_dhcpsrv_la_SOURCES += cfg_option.cc cfg_option.h
 libkea_dhcpsrv_la_SOURCES += cfg_option_def.cc cfg_option_def.h
 libkea_dhcpsrv_la_SOURCES += cfg_option_def.cc cfg_option_def.h
 libkea_dhcpsrv_la_SOURCES += cfg_rsoo.cc cfg_rsoo.h
 libkea_dhcpsrv_la_SOURCES += cfg_rsoo.cc cfg_rsoo.h

+ 47 - 37
src/lib/dhcpsrv/alloc_engine.cc

@@ -12,6 +12,7 @@
 #include <dhcp_ddns/ncr_msg.h>
 #include <dhcp_ddns/ncr_msg.h>
 #include <dhcpsrv/alloc_engine.h>
 #include <dhcpsrv/alloc_engine.h>
 #include <dhcpsrv/alloc_engine_log.h>
 #include <dhcpsrv/alloc_engine_log.h>
+#include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/dhcpsrv_log.h>
 #include <dhcpsrv/dhcpsrv_log.h>
 #include <dhcpsrv/host_mgr.h>
 #include <dhcpsrv/host_mgr.h>
 #include <dhcpsrv/host.h>
 #include <dhcpsrv/host.h>
@@ -27,6 +28,7 @@
 
 
 #include <boost/foreach.hpp>
 #include <boost/foreach.hpp>
 
 
+#include <algorithm>
 #include <cstring>
 #include <cstring>
 #include <sstream>
 #include <sstream>
 #include <limits>
 #include <limits>
@@ -301,6 +303,29 @@ AllocEngine::AllocatorPtr AllocEngine::getAllocator(Lease::Type type) {
     return (alloc->second);
     return (alloc->second);
 }
 }
 
 
+template<typename ContextType>
+void
+AllocEngine::findReservationInternal(ContextType& ctx,
+                                     const AllocEngine::HostGetFunc& host_get) {
+    ctx.host_.reset();
+
+    // We can only search for the reservation if a subnet has been selected.
+    if (ctx.subnet_) {
+        // Iterate over configured identifiers in the order of preference
+        // and try to use each of them to search for the reservations.
+        BOOST_FOREACH(const IdentifierPair& id_pair, ctx.host_identifiers_) {
+            // Attempt to find a host using a specified identifier.
+            ctx.host_ = host_get(ctx.subnet_->getID(), id_pair.first,
+                                 &id_pair.second[0], id_pair.second.size());
+            // If we found matching host, return.
+            if (ctx.host_) {
+                return;
+            }
+        }
+    }
+}
+
+
 // ##########################################################################
 // ##########################################################################
 // #    DHCPv6 lease allocation code starts here.
 // #    DHCPv6 lease allocation code starts here.
 // ##########################################################################
 // ##########################################################################
@@ -309,7 +334,7 @@ AllocEngine::ClientContext6::ClientContext6()
     : subnet_(), duid_(), iaid_(0), type_(Lease::TYPE_NA), hwaddr_(),
     : subnet_(), duid_(), iaid_(0), type_(Lease::TYPE_NA), hwaddr_(),
       hints_(), fwd_dns_update_(false), rev_dns_update_(false), hostname_(""),
       hints_(), fwd_dns_update_(false), rev_dns_update_(false), hostname_(""),
       callout_handle_(), fake_allocation_(false), old_leases_(), host_(),
       callout_handle_(), fake_allocation_(false), old_leases_(), host_(),
-      query_(), ia_rsp_() {
+      query_(), ia_rsp_(), host_identifiers_() {
 }
 }
 
 
 AllocEngine::ClientContext6::ClientContext6(const Subnet6Ptr& subnet, const DuidPtr& duid,
 AllocEngine::ClientContext6::ClientContext6(const Subnet6Ptr& subnet, const DuidPtr& duid,
@@ -322,7 +347,7 @@ AllocEngine::ClientContext6::ClientContext6(const Subnet6Ptr& subnet, const Duid
     subnet_(subnet), duid_(duid), iaid_(iaid), type_(type), hwaddr_(),
     subnet_(subnet), duid_(duid), iaid_(iaid), type_(type), hwaddr_(),
     hints_(), fwd_dns_update_(fwd_dns), rev_dns_update_(rev_dns),
     hints_(), fwd_dns_update_(fwd_dns), rev_dns_update_(rev_dns),
     hostname_(hostname), fake_allocation_(fake_allocation),
     hostname_(hostname), fake_allocation_(fake_allocation),
-    old_leases_(), host_(), query_(), ia_rsp_() {
+    old_leases_(), host_(), query_(), ia_rsp_(), host_identifiers_() {
 
 
     static asiolink::IOAddress any("::");
     static asiolink::IOAddress any("::");
 
 
@@ -331,27 +356,18 @@ AllocEngine::ClientContext6::ClientContext6(const Subnet6Ptr& subnet, const Duid
     }
     }
     // callout_handle, host pointers initiated to NULL by their
     // callout_handle, host pointers initiated to NULL by their
     // respective constructors.
     // respective constructors.
-}
-
 
 
-void AllocEngine::findReservation(ClientContext6& ctx) const {
-    if (!ctx.subnet_ || !ctx.duid_) {
-        return;
+    // Initialize host identifiers.
+    if (duid) {
+        addHostIdentifier(Host::IDENT_DUID, duid->getDuid());
     }
     }
+}
 
 
-    // Check which host reservation mode is supported in this subnet.
-    Subnet::HRMode hr_mode = ctx.subnet_->getHostReservationMode();
-
-    // Check if there's a host reservation for this client. Attempt to get
-    // host info only if reservations are not disabled.
-    if (hr_mode != Subnet::HR_DISABLED) {
 
 
-        ctx.host_ = HostMgr::instance().get6(ctx.subnet_->getID(), ctx.duid_,
-                                             ctx.hwaddr_);
-        } else {
-        // Let's explicitly set it to NULL if reservations are disabled.
-        ctx.host_.reset();
-    }
+void AllocEngine::findReservation(ClientContext6& ctx) {
+    findReservationInternal(ctx, boost::bind(&HostMgr::get6,
+                                             &HostMgr::instance(),
+                                             _1, _2, _3, _4));
 }
 }
 
 
 Lease6Collection
 Lease6Collection
@@ -2049,7 +2065,8 @@ AllocEngine::ClientContext4::ClientContext4()
       requested_address_(IOAddress::IPV4_ZERO_ADDRESS()),
       requested_address_(IOAddress::IPV4_ZERO_ADDRESS()),
       fwd_dns_update_(false), rev_dns_update_(false),
       fwd_dns_update_(false), rev_dns_update_(false),
       hostname_(""), callout_handle_(), fake_allocation_(false),
       hostname_(""), callout_handle_(), fake_allocation_(false),
-      old_lease_(), host_(), conflicting_lease_(), query_() {
+      old_lease_(), host_(), conflicting_lease_(), query_(),
+      host_identifiers_() {
 }
 }
 
 
 AllocEngine::ClientContext4::ClientContext4(const Subnet4Ptr& subnet,
 AllocEngine::ClientContext4::ClientContext4(const Subnet4Ptr& subnet,
@@ -2064,7 +2081,13 @@ AllocEngine::ClientContext4::ClientContext4(const Subnet4Ptr& subnet,
       requested_address_(requested_addr),
       requested_address_(requested_addr),
       fwd_dns_update_(fwd_dns_update), rev_dns_update_(rev_dns_update),
       fwd_dns_update_(fwd_dns_update), rev_dns_update_(rev_dns_update),
       hostname_(hostname), callout_handle_(),
       hostname_(hostname), callout_handle_(),
-      fake_allocation_(fake_allocation), old_lease_(), host_() {
+      fake_allocation_(fake_allocation), old_lease_(), host_(),
+      host_identifiers_() {
+
+    // Initialize host identifiers.
+    if (hwaddr) {
+        addHostIdentifier(Host::IDENT_HWADDR, hwaddr->hwaddr_);
+    }
 }
 }
 
 
 Lease4Ptr
 Lease4Ptr
@@ -2099,22 +2122,9 @@ AllocEngine::allocateLease4(ClientContext4& ctx) {
 
 
 void
 void
 AllocEngine::findReservation(ClientContext4& ctx) {
 AllocEngine::findReservation(ClientContext4& ctx) {
-    ctx.host_.reset();
-
-    // We can only search for the reservation if a subnet has been selected.
-    if (ctx.subnet_) {
-        // Check which host reservation mode is supported in this subnet.
-        Subnet::HRMode hr_mode = ctx.subnet_->getHostReservationMode();
-
-        // Check if there is a host reseravtion for this client. Attempt to
-        // get host information
-        if (hr_mode != Subnet::HR_DISABLED) {
-            // This method should handle the case when there is neither hwaddr
-            // nor clientid_ available and simply return NULL.
-            ctx.host_ = HostMgr::instance().get4(ctx.subnet_->getID(), ctx.hwaddr_,
-                                                 ctx.clientid_);
-        }
-    }
+    findReservationInternal(ctx, boost::bind(&HostMgr::get4,
+                                             &HostMgr::instance(),
+                                             _1, _2, _3, _4));
 }
 }
 
 
 Lease4Ptr
 Lease4Ptr

+ 57 - 2
src/lib/dhcpsrv/alloc_engine.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2016 Internet Systems Consortium, Inc. ("ISC")
 //
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -22,7 +22,9 @@
 #include <boost/shared_ptr.hpp>
 #include <boost/shared_ptr.hpp>
 #include <boost/noncopyable.hpp>
 #include <boost/noncopyable.hpp>
 
 
+#include <list>
 #include <map>
 #include <map>
+#include <utility>
 
 
 namespace isc {
 namespace isc {
 namespace dhcp {
 namespace dhcp {
@@ -251,6 +253,12 @@ public:
     /// @brief Container for client's hints.
     /// @brief Container for client's hints.
     typedef std::vector<HintType> HintContainer;
     typedef std::vector<HintType> HintContainer;
 
 
+    /// @brief A tuple holding host identifier type and value.
+    typedef std::pair<Host::IdentifierType, std::vector<uint8_t> > IdentifierPair;
+
+    /// @brief Map holding values to be used as host identifiers.
+    typedef std::list<IdentifierPair> IdentifierList;
+
     /// @brief Context information for the DHCPv6 leases allocation.
     /// @brief Context information for the DHCPv6 leases allocation.
     ///
     ///
     /// This structure holds a set of information provided by the DHCPv6
     /// This structure holds a set of information provided by the DHCPv6
@@ -350,6 +358,20 @@ public:
         /// @brief A pointer to the IA_NA/IA_PD option to be sent in response
         /// @brief A pointer to the IA_NA/IA_PD option to be sent in response
         Option6IAPtr ia_rsp_;
         Option6IAPtr ia_rsp_;
 
 
+        /// @brief A list holding host identifiers extracted from a message
+        /// received by the server.
+        IdentifierList host_identifiers_;
+
+        /// @brief Conveniece function adding host identifier into
+        /// @ref host_identifiers_ list.
+        ///
+        /// @param id_type Identifier type.
+        /// @param identifier Identifier value.
+        void addHostIdentifier(const Host::IdentifierType& id_type,
+                               const std::vector<uint8_t>& identifier) {
+            host_identifiers_.push_back(IdentifierPair(id_type, identifier));
+        }
+
         /// @brief Default constructor.
         /// @brief Default constructor.
         ClientContext6();
         ClientContext6();
 
 
@@ -606,10 +628,29 @@ public:
     /// Attempts to find appropriate host reservation in HostMgr. If found, it
     /// Attempts to find appropriate host reservation in HostMgr. If found, it
     /// will be set in ctx.host_.
     /// will be set in ctx.host_.
     /// @param ctx Client context that contains all necessary information.
     /// @param ctx Client context that contains all necessary information.
-    void findReservation(ClientContext6& ctx) const;
+    static void findReservation(ClientContext6& ctx);
 
 
 private:
 private:
 
 
+    /// @brief Type of the function used by @ref findReservationInternal to
+    /// retrieve reservations by subnet identifier and host identifier.
+    typedef boost::function<ConstHostPtr(const SubnetID&,
+                                         const Host::IdentifierType&,
+                                         const uint8_t*, const size_t)> HostGetFunc;
+
+    /// @brief Common function for searching host reservations.
+    ///
+    /// This is a common function called by variants of @ref findReservation
+    /// functions.
+    ///
+    /// @param ctx Reference to a @ref ClientContext6 or @ref ClientContext4.
+    /// @param host_get Pointer to the @ref HostMgr functions to be used
+    /// to retrieve reservation by subnet identifier and host identifier.
+    /// @tparam ContextType Either @ref ClientContext6 or @ref ClientContext4.
+    template<typename ContextType>
+    static void findReservationInternal(ContextType& ctx,
+                                        const HostGetFunc& host_get);
+
     /// @brief creates a lease and inserts it in LeaseMgr if necessary
     /// @brief creates a lease and inserts it in LeaseMgr if necessary
     ///
     ///
     /// Creates a lease based on specified parameters and tries to insert it
     /// Creates a lease based on specified parameters and tries to insert it
@@ -971,6 +1012,20 @@ public:
         /// transaction identification information.
         /// transaction identification information.
         Pkt4Ptr query_;
         Pkt4Ptr query_;
 
 
+        /// @brief A list holding host identifiers extracted from a message
+        /// received by the server.
+        IdentifierList host_identifiers_;
+
+        /// @brief Conveniece function adding host identifier into
+        /// @ref host_identifiers_ list.
+        ///
+        /// @param id_type Identifier type.
+        /// @param identifier Identifier value.
+        void addHostIdentifier(const Host::IdentifierType& id_type,
+                               const std::vector<uint8_t>& identifier) {
+            host_identifiers_.push_back(IdentifierPair(id_type, identifier));
+        }
+
         /// @brief Default constructor.
         /// @brief Default constructor.
         ClientContext4();
         ClientContext4();
 
 

+ 52 - 0
src/lib/dhcpsrv/cfg_host_operations.cc

@@ -0,0 +1,52 @@
+// Copyright (C) 2016 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <exceptions/exceptions.h>
+#include <dhcpsrv/cfg_host_operations.h>
+#include <algorithm>
+
+namespace isc {
+namespace dhcp {
+
+CfgHostOperations::CfgHostOperations()
+    : identifier_types_() {
+}
+
+CfgHostOperationsPtr
+CfgHostOperations::createConfig4() {
+    CfgHostOperationsPtr cfg(new CfgHostOperations());
+    cfg->addIdentifierType("hw-address");
+    cfg->addIdentifierType("duid");
+    cfg->addIdentifierType("circuit-id");
+    return (cfg);
+}
+
+CfgHostOperationsPtr
+CfgHostOperations::createConfig6() {
+    CfgHostOperationsPtr cfg(new CfgHostOperations());
+    cfg->addIdentifierType("hw-address");
+    cfg->addIdentifierType("duid");
+    return (cfg);
+}
+
+void
+CfgHostOperations::addIdentifierType(const std::string& identifier_name) {
+    Host::IdentifierType identifier_type = Host::getIdentifierType(identifier_name);
+    if (std::find(identifier_types_.begin(), identifier_types_.end(),
+                  identifier_type) != identifier_types_.end()) {
+        isc_throw(isc::BadValue, "duplicate host identifier '"
+                  << identifier_name << "'");
+    }
+    identifier_types_.push_back(identifier_type);
+}
+
+void
+CfgHostOperations::clearIdentifierTypes() {
+    identifier_types_.clear();
+}
+
+}
+}

+ 91 - 0
src/lib/dhcpsrv/cfg_host_operations.h

@@ -0,0 +1,91 @@
+// Copyright (C) 2016 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef CFG_HOST_OPERATIONS_H
+#define CFG_HOST_OPERATIONS_H
+
+#include <dhcpsrv/host.h>
+#include <boost/shared_ptr.hpp>
+#include <list>
+#include <string>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Forward declaration of the @ref CfgHostOperations.
+class CfgHostOperations;
+
+/// @name Pointers to the @ref CfgHostOperations objects.
+//@{
+/// @brief Pointer to the Non-const object.
+typedef boost::shared_ptr<CfgHostOperations> CfgHostOperationsPtr;
+
+/// @brief Pointer to the const object.
+typedef boost::shared_ptr<const CfgHostOperations>
+ConstCfgHostOperationsPtr;
+
+//@}
+
+/// @brief Represents global configuration for host reservations.
+///
+/// This class represents server configuration pertaining to host
+/// reservations.
+///
+/// Currently it only holds the ordered list of host identifiers
+/// to be used to search for reservations for a particular host.
+/// An administrator selects which identifiers the server should
+/// use and in which order to search for host reservations to
+/// optimize performance of the server.
+class CfgHostOperations {
+public:
+
+    /// @brief Type of the container holding ordered list of identifiers.
+    typedef std::list<Host::IdentifierType> IdentifierTypes;
+
+    /// @brief Constructor.
+    ///
+    /// The default confguration:
+    /// - no identifiers selected for host reservations searches.
+    CfgHostOperations();
+
+    /// @name Factory functions for creating default configurations.
+    //@{
+    /// @brief Factory function for DHCPv4.
+    static CfgHostOperationsPtr createConfig4();
+
+    /// @brief Factory function for DHCPv6.
+    static CfgHostOperationsPtr createConfig6();
+    //@}
+
+    /// @brief Adds new identifier type to a collection of identifiers
+    /// to be used by the server to search for host reservations.
+    ///
+    /// @param identifier_type Name of the identifier to be added. It
+    /// must be one of the names supported by the @ref Host::getIdentifierType
+    /// function.
+    void addIdentifierType(const std::string& identifier_name);
+
+    /// @brief Returns const reference to ordered collection of identifiers
+    /// to be used by the server to search for host reservations.
+    const IdentifierTypes& getIdentifierTypes() const {
+        return (identifier_types_);
+    }
+
+    /// @brief Removes existing identifier types.
+    void clearIdentifierTypes();
+
+private:
+
+    /// @brief Holds ordered collection of identifiers to be used by the
+    /// server to search for host reservations for a client.
+    IdentifierTypes identifier_types_;
+
+};
+
+}
+}
+
+#endif // CFG_HOST_OPERATIONS_H

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

@@ -183,6 +183,10 @@ public:
         IDENT_CIRCUIT_ID
         IDENT_CIRCUIT_ID
     };
     };
 
 
+    /// @brief Constant pointing to the last identifier of the
+    /// @ref IdentifierType enumeration.
+    static const IdentifierType LAST_IDENTIFIER_TYPE = IDENT_CIRCUIT_ID;
+
     /// @brief Constructor.
     /// @brief Constructor.
     ///
     ///
     /// Creates a @c Host object using an identifier in a binary format. This
     /// Creates a @c Host object using an identifier in a binary format. This

+ 80 - 2
src/lib/dhcpsrv/parsers/host_reservation_parser.cc

@@ -11,6 +11,7 @@
 #include <dhcpsrv/parsers/host_reservation_parser.h>
 #include <dhcpsrv/parsers/host_reservation_parser.h>
 #include <boost/foreach.hpp>
 #include <boost/foreach.hpp>
 #include <boost/lexical_cast.hpp>
 #include <boost/lexical_cast.hpp>
+#include <algorithm>
 #include <sys/socket.h>
 #include <sys/socket.h>
 #include <sstream>
 #include <sstream>
 #include <string>
 #include <string>
@@ -37,8 +38,8 @@ getSupportedParams4(const bool identifiers_only = false) {
     // If this is first execution of this function, we need
     // If this is first execution of this function, we need
     // to initialize the set.
     // to initialize the set.
     if (identifiers_set.empty()) {
     if (identifiers_set.empty()) {
-        identifiers_set.insert("duid");
         identifiers_set.insert("hw-address");
         identifiers_set.insert("hw-address");
+        identifiers_set.insert("duid");
         identifiers_set.insert("circuit-id");
         identifiers_set.insert("circuit-id");
     }
     }
     // Copy identifiers and add all other parameters.
     // Copy identifiers and add all other parameters.
@@ -68,8 +69,8 @@ getSupportedParams6(const bool identifiers_only = false) {
     // If this is first execution of this function, we need
     // If this is first execution of this function, we need
     // to initialize the set.
     // to initialize the set.
     if (identifiers_set.empty()) {
     if (identifiers_set.empty()) {
-        identifiers_set.insert("duid");
         identifiers_set.insert("hw-address");
         identifiers_set.insert("hw-address");
+        identifiers_set.insert("duid");
     }
     }
     // Copy identifiers and add all other parameters.
     // Copy identifiers and add all other parameters.
     if (params_set.empty()) {
     if (params_set.empty()) {
@@ -317,5 +318,82 @@ HostReservationParser6::getSupportedParameters(const bool identifiers_only) cons
     return (getSupportedParams6(identifiers_only));
     return (getSupportedParams6(identifiers_only));
 }
 }
 
 
+HostReservationIdsParser::HostReservationIdsParser()
+    : staging_cfg_() {
+}
+
+void
+HostReservationIdsParser::build(isc::data::ConstElementPtr ids_list) {
+    // Remove existing identifier types.
+    staging_cfg_->clearIdentifierTypes();
+
+    BOOST_FOREACH(ConstElementPtr element, ids_list->listValue()) {
+        std::string id_name = element->stringValue();
+        try {
+            if (id_name != "auto") {
+                if (!isSupportedIdentifier(id_name)) {
+                    isc_throw(isc::BadValue, "unsupported identifier '"
+                              << id_name << "'");
+                }
+                staging_cfg_->addIdentifierType(id_name);
+
+            } else {
+                // 'auto' is mutually exclusive with other values. If there
+                // are any values in the configuration already it means that
+                // some other values have already been specified.
+                if (!staging_cfg_->getIdentifierTypes().empty()) {
+                    isc_throw(isc::BadValue, "if 'auto' keyword is used,"
+                              " no other values can be specified within '"
+                              "host-reservation-identifiers' list");
+                }
+                // Iterate over all identifier types and for those supported
+                // in a given context (DHCPv4 or DHCPv6) add the identifier type
+                // to the configuration.
+                for (unsigned int i = 0;
+                     i <= static_cast<unsigned int>(Host::LAST_IDENTIFIER_TYPE);
+                     ++i) {
+                    std::string supported_id_name =
+                        Host::getIdentifierName(static_cast<Host::IdentifierType>(i));
+                    if (isSupportedIdentifier(supported_id_name)) {
+                        staging_cfg_->addIdentifierType(supported_id_name);
+                    }
+                }
+            }
+
+        } catch (const std::exception& ex) {
+            // Append line number where the error occurred.
+            isc_throw(DhcpConfigError, ex.what() << " ("
+                      << element->getPosition() << ")");
+        }
+    }
+
+    // The parsed list must not be empty.
+    if (staging_cfg_->getIdentifierTypes().empty()) {
+        isc_throw(DhcpConfigError, "'host-reservation-identifiers' list must not"
+                  " be empty (" << ids_list->getPosition() << ")");
+    }
+
+}
+
+HostReservationIdsParser4::HostReservationIdsParser4()
+    : HostReservationIdsParser() {
+    staging_cfg_ = CfgMgr::instance().getStagingCfg()->getCfgHostOperations4();
+}
+
+bool
+HostReservationIdsParser4::isSupportedIdentifier(const std::string& id_name) const {
+    return (getSupportedParams4(true).count(id_name) > 0);
+}
+
+HostReservationIdsParser6::HostReservationIdsParser6()
+    : HostReservationIdsParser() {
+    staging_cfg_ = CfgMgr::instance().getStagingCfg()->getCfgHostOperations6();
+}
+
+bool
+HostReservationIdsParser6::isSupportedIdentifier(const std::string& id_name) const {
+    return (getSupportedParams6(true).count(id_name) > 0);
+}
+
 } // end of namespace isc::dhcp
 } // end of namespace isc::dhcp
 } // end of namespace isc
 } // end of namespace isc

+ 81 - 0
src/lib/dhcpsrv/parsers/host_reservation_parser.h

@@ -146,6 +146,87 @@ protected:
 
 
 };
 };
 
 
+/// @brief Parser for a list of host identifiers.
+///
+/// This is a parent parser class for parsing "host-reservation-identifiers"
+/// global configuration parmeter. The DHCPv4 and DHCPv6 specific parsers
+/// derive from this class.
+class HostReservationIdsParser : public DhcpConfigParser {
+public:
+
+    /// @brief Constructor.
+    HostReservationIdsParser();
+
+    /// @brief Parses a list of host identifiers.
+    ///
+    /// @param ids_list Data element pointing to an ordered list of host
+    /// identifier names.
+    ///
+    /// @throw DhcpConfigError If specified configuration is invalid.
+    virtual void build(isc::data::ConstElementPtr ids_list);
+
+    /// @brief Commit, unused.
+    virtual void commit() { }
+
+protected:
+
+    /// @brief Checks if specified identifier name is supported in the
+    /// context of the parser.
+    ///
+    /// This is abstract method which must be implemented in the derived
+    /// parser classes for DHCPv4 and DHCPv6.
+    ///
+    /// @param id_name Identifier name.
+    /// @return true if the specified identifier is supported, false
+    /// otherwise.
+    virtual bool isSupportedIdentifier(const std::string& id_name) const = 0;
+
+    /// @brief Pointer to the object holding configuration.
+    CfgHostOperationsPtr staging_cfg_;
+
+};
+
+/// @brief Parser for a list of host identifiers for DHCPv4.
+class HostReservationIdsParser4 : public HostReservationIdsParser {
+public:
+
+    /// @brief Constructor.
+    ///
+    /// Initializes staging configuration pointer to the one used for DHCPv4
+    /// configuration.
+    HostReservationIdsParser4();
+
+protected:
+
+    /// @brief Checks if specified identifier name is supported for DHCPv4.
+    ///
+    /// @param id_name Identifier name.
+    /// @return true if the specified identifier is supported, false
+    /// otherwise.
+    virtual bool isSupportedIdentifier(const std::string& id_name) const;
+
+};
+
+/// @brief Parser for a list of host identifiers for DHCPv6.
+class HostReservationIdsParser6 : public HostReservationIdsParser {
+public:
+
+    /// @brief Constructor.
+    ///
+    /// Initializes staging configuration pointer to the one used for DHCPv6
+    /// configuration.
+    HostReservationIdsParser6();
+
+protected:
+
+    /// @brief Checks if specified identifier name is supported for DHCPv6.
+    ///
+    /// @param id_name Identifier name.
+    /// @return true if the specified identifier is supported, false
+    /// otherwise.
+    virtual bool isSupportedIdentifier(const std::string& id_name) const;
+};
+
 
 
 }
 }
 } // end of namespace isc
 } // end of namespace isc

+ 4 - 0
src/lib/dhcpsrv/srv_config.cc

@@ -25,6 +25,8 @@ SrvConfig::SrvConfig()
       cfg_hosts_(new CfgHosts()), cfg_rsoo_(new CfgRSOO()),
       cfg_hosts_(new CfgHosts()), cfg_rsoo_(new CfgRSOO()),
       cfg_expiration_(new CfgExpiration()), cfg_duid_(new CfgDUID()),
       cfg_expiration_(new CfgExpiration()), cfg_duid_(new CfgDUID()),
       cfg_db_access_(new CfgDbAccess()),
       cfg_db_access_(new CfgDbAccess()),
+      cfg_host_operations4_(CfgHostOperations::createConfig4()),
+      cfg_host_operations6_(CfgHostOperations::createConfig6()),
       class_dictionary_(new ClientClassDictionary()),
       class_dictionary_(new ClientClassDictionary()),
       decline_timer_(0) {
       decline_timer_(0) {
 }
 }
@@ -36,6 +38,8 @@ SrvConfig::SrvConfig(const uint32_t sequence)
       cfg_hosts_(new CfgHosts()), cfg_rsoo_(new CfgRSOO()),
       cfg_hosts_(new CfgHosts()), cfg_rsoo_(new CfgRSOO()),
       cfg_expiration_(new CfgExpiration()), cfg_duid_(new CfgDUID()),
       cfg_expiration_(new CfgExpiration()), cfg_duid_(new CfgDUID()),
       cfg_db_access_(new CfgDbAccess()),
       cfg_db_access_(new CfgDbAccess()),
+      cfg_host_operations4_(CfgHostOperations::createConfig4()),
+      cfg_host_operations6_(CfgHostOperations::createConfig6()),
       class_dictionary_(new ClientClassDictionary()),
       class_dictionary_(new ClientClassDictionary()),
       decline_timer_(0) {
       decline_timer_(0) {
 }
 }

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

@@ -10,6 +10,7 @@
 #include <dhcpsrv/cfg_db_access.h>
 #include <dhcpsrv/cfg_db_access.h>
 #include <dhcpsrv/cfg_duid.h>
 #include <dhcpsrv/cfg_duid.h>
 #include <dhcpsrv/cfg_expiration.h>
 #include <dhcpsrv/cfg_expiration.h>
+#include <dhcpsrv/cfg_host_operations.h>
 #include <dhcpsrv/cfg_hosts.h>
 #include <dhcpsrv/cfg_hosts.h>
 #include <dhcpsrv/cfg_iface.h>
 #include <dhcpsrv/cfg_iface.h>
 #include <dhcpsrv/cfg_option.h>
 #include <dhcpsrv/cfg_option.h>
@@ -291,6 +292,30 @@ public:
         return (cfg_db_access_);
         return (cfg_db_access_);
     }
     }
 
 
+    /// @brief Returns pointer to the object holding general configuration
+    /// for host reservations in DHCPv4.
+    CfgHostOperationsPtr getCfgHostOperations4() {
+        return (cfg_host_operations4_);
+    }
+
+    /// @brief Returns const pointer to the object holding general
+    /// configuration for host reservations in DHCPv4
+    ConstCfgHostOperationsPtr getCfgHostOperations4() const {
+        return (cfg_host_operations4_);
+    }
+
+    /// @brief Returns pointer to the object holding general configuration
+    /// for host reservations in DHCPv6.
+    CfgHostOperationsPtr getCfgHostOperations6() {
+        return (cfg_host_operations6_);
+    }
+
+    /// @brief Returns const pointer to the object holding general
+    /// configuration for host reservations in DHCPv6
+    ConstCfgHostOperationsPtr getCfgHostOperations6() const {
+        return (cfg_host_operations6_);
+    }
+
     //@}
     //@}
 
 
     /// @brief Returns non-const reference to an array that stores
     /// @brief Returns non-const reference to an array that stores
@@ -502,6 +527,14 @@ private:
     /// connection parameters.
     /// connection parameters.
     CfgDbAccessPtr cfg_db_access_;
     CfgDbAccessPtr cfg_db_access_;
 
 
+    /// @brief Pointer to the general configuration for host reservations in
+    /// DHCPv4.
+    CfgHostOperationsPtr cfg_host_operations4_;
+
+    /// @brief Pointer to the general configuration for host reservations in
+    /// DHCPv6.
+    CfgHostOperationsPtr cfg_host_operations6_;
+
     /// @brief Pointer to the control-socket information
     /// @brief Pointer to the control-socket information
     isc::data::ConstElementPtr control_socket_;
     isc::data::ConstElementPtr control_socket_;
 
 

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

@@ -68,6 +68,7 @@ libdhcpsrv_unittests_SOURCES += callout_handle_store_unittest.cc
 libdhcpsrv_unittests_SOURCES += cfg_db_access_unittest.cc
 libdhcpsrv_unittests_SOURCES += cfg_db_access_unittest.cc
 libdhcpsrv_unittests_SOURCES += cfg_duid_unittest.cc
 libdhcpsrv_unittests_SOURCES += cfg_duid_unittest.cc
 libdhcpsrv_unittests_SOURCES += cfg_expiration_unittest.cc
 libdhcpsrv_unittests_SOURCES += cfg_expiration_unittest.cc
+libdhcpsrv_unittests_SOURCES += cfg_host_operations_unittest.cc
 libdhcpsrv_unittests_SOURCES += cfg_hosts_unittest.cc
 libdhcpsrv_unittests_SOURCES += cfg_hosts_unittest.cc
 libdhcpsrv_unittests_SOURCES += cfg_iface_unittest.cc
 libdhcpsrv_unittests_SOURCES += cfg_iface_unittest.cc
 libdhcpsrv_unittests_SOURCES += cfg_mac_source_unittest.cc
 libdhcpsrv_unittests_SOURCES += cfg_mac_source_unittest.cc

+ 6 - 4
src/lib/dhcpsrv/tests/alloc_engine4_unittest.cc

@@ -1649,6 +1649,8 @@ TEST_F(AllocEngine4Test, findReservation) {
     AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_,
     AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_,
                                     IOAddress("0.0.0.0"), false, false,
                                     IOAddress("0.0.0.0"), false, false,
                                     "", false);
                                     "", false);
+    ctx.addHostIdentifier(Host::IDENT_HWADDR, hwaddr_->hwaddr_);
+    ctx.addHostIdentifier(Host::IDENT_DUID, clientid_->getDuid());
 
 
     // There is no reservation in the database so no host should be
     // There is no reservation in the database so no host should be
     // retruned.
     // retruned.
@@ -1667,12 +1669,12 @@ TEST_F(AllocEngine4Test, findReservation) {
     EXPECT_TRUE(ctx.host_);
     EXPECT_TRUE(ctx.host_);
     EXPECT_EQ(ctx.host_->getIPv4Reservation(), host->getIPv4Reservation());
     EXPECT_EQ(ctx.host_->getIPv4Reservation(), host->getIPv4Reservation());
 
 
-    // If the host reservation mode for the subnet is disabled, the
-    // host should not be returned, even though it exists in the
-    // host database.
+    // Regardless of the host reservation mode, the host should be
+    // always returned when findReservation() is called.
     subnet_->setHostReservationMode(Subnet::HR_DISABLED);
     subnet_->setHostReservationMode(Subnet::HR_DISABLED);
     ASSERT_NO_THROW(engine.findReservation(ctx));
     ASSERT_NO_THROW(engine.findReservation(ctx));
-    EXPECT_FALSE(ctx.host_);
+    EXPECT_TRUE(ctx.host_);
+    EXPECT_EQ(ctx.host_->getIPv4Reservation(), host->getIPv4Reservation());
 
 
     // Check the third possible reservation mode.
     // Check the third possible reservation mode.
     subnet_->setHostReservationMode(Subnet::HR_OUT_OF_POOL);
     subnet_->setHostReservationMode(Subnet::HR_OUT_OF_POOL);

+ 2 - 1
src/lib/dhcpsrv/tests/alloc_engine_utils.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2016 Internet Systems Consortium, Inc. ("ISC")
 //
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -270,6 +270,7 @@ AllocEngine6Test::simpleAlloc6Test(const Pool6Ptr& pool, const IOAddress& hint,
     AllocEngine::ClientContext6 ctx(subnet_, duid_, iaid_, hint, type,
     AllocEngine::ClientContext6 ctx(subnet_, duid_, iaid_, hint, type,
                                     false, false, "", fake);
                                     false, false, "", fake);
     ctx.hwaddr_ = hwaddr_;
     ctx.hwaddr_ = hwaddr_;
+    ctx.addHostIdentifier(Host::IDENT_HWADDR, hwaddr_->hwaddr_);
     ctx.query_.reset(new Pkt6(fake ? DHCPV6_SOLICIT : DHCPV6_REQUEST, 1234));
     ctx.query_.reset(new Pkt6(fake ? DHCPV6_SOLICIT : DHCPV6_REQUEST, 1234));
 
 
     findReservation(*engine, ctx);
     findReservation(*engine, ctx);

+ 104 - 0
src/lib/dhcpsrv/tests/cfg_host_operations_unittest.cc

@@ -0,0 +1,104 @@
+// Copyright (C) 2016 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+#include <dhcp/dhcp6.h>
+#include <dhcpsrv/cfg_host_operations.h>
+#include <dhcpsrv/host.h>
+#include <gtest/gtest.h>
+#include <algorithm>
+#include <iterator>
+
+using namespace isc;
+using namespace isc::dhcp;
+
+namespace {
+
+/// @brief Checks if specified identifier is present.
+///
+/// @param cfg Object holding current configuration.
+/// @param id Identifier type which presence should be checked.
+/// @return true if specified identifier is present.
+bool
+identifierPresent(const CfgHostOperations& cfg, const Host::IdentifierType& id) {
+    CfgHostOperations::IdentifierTypes types = cfg.getIdentifierTypes();
+    return (std::find(types.begin(), types.end(), id) != types.end());
+}
+
+/// @brief Checks if specified identifier is at specified position.
+///
+/// @param cfg Object holding current configuration.
+/// @param id Identifier type which presence should be checked.
+/// @param pos Position at which the identifier is expected on the list.
+/// @return true if specified identifier exists at specified position.
+bool
+identifierAtPosition(const CfgHostOperations& cfg, const Host::IdentifierType& id,
+                     const size_t pos) {
+    CfgHostOperations::IdentifierTypes types = cfg.getIdentifierTypes();
+    if (types.size() > pos) {
+        CfgHostOperations::IdentifierTypes::const_iterator type = types.begin();
+        std::advance(type, pos);
+        return (*type == id);
+    }
+    return (false);
+}
+
+// This test checks that the list of identifiers is initially
+// empty.
+TEST(CfgHostOperationsTest, defaults) {
+    CfgHostOperations cfg;
+    EXPECT_TRUE(cfg.getIdentifierTypes().empty());
+}
+
+// This test verifies that identifier types can be added into an
+// ordered collection and then removed.
+TEST(CfgHostOperationsTest, addIdentifier) {
+    CfgHostOperations cfg;
+
+    // Only HW address added.
+    ASSERT_NO_THROW(cfg.addIdentifierType("hw-address"));
+    EXPECT_TRUE(identifierAtPosition(cfg, Host::IDENT_HWADDR, 0));
+    EXPECT_FALSE(identifierPresent(cfg, Host::IDENT_DUID));
+    EXPECT_FALSE(identifierPresent(cfg, Host::IDENT_CIRCUIT_ID));
+
+    // HW address and DUID should be present.
+    ASSERT_NO_THROW(cfg.addIdentifierType("duid"));
+    EXPECT_TRUE(identifierAtPosition(cfg, Host::IDENT_HWADDR, 0));
+    EXPECT_TRUE(identifierAtPosition(cfg, Host::IDENT_DUID, 1));
+    EXPECT_FALSE(identifierPresent(cfg, Host::IDENT_CIRCUIT_ID));
+
+    // All three identifiers should be present now.
+    ASSERT_NO_THROW(cfg.addIdentifierType("circuit-id"));
+    EXPECT_TRUE(identifierAtPosition(cfg, Host::IDENT_HWADDR, 0));
+    EXPECT_TRUE(identifierAtPosition(cfg, Host::IDENT_DUID, 1));
+    EXPECT_TRUE(identifierAtPosition(cfg, Host::IDENT_CIRCUIT_ID, 2));
+
+    // Let's clear and make sure no identifiers are present.
+    ASSERT_NO_THROW(cfg.clearIdentifierTypes());
+    EXPECT_TRUE(cfg.getIdentifierTypes().empty());
+}
+
+// This test verfies that the default DHCPv4 configuration is created
+// as expected.
+TEST(CfgHostOperationsTest, createConfig4) {
+    CfgHostOperationsPtr cfg = CfgHostOperations::createConfig4();
+
+    EXPECT_TRUE(identifierAtPosition(*cfg, Host::IDENT_HWADDR, 0));
+    EXPECT_TRUE(identifierAtPosition(*cfg, Host::IDENT_DUID, 1));
+    EXPECT_TRUE(identifierAtPosition(*cfg, Host::IDENT_CIRCUIT_ID, 2));
+}
+
+// This test verfies that the default DHCPv6 configuration is created
+// as expected.
+TEST(CfgHostOperationsTest, createConfig6) {
+    CfgHostOperationsPtr cfg = CfgHostOperations::createConfig6();
+
+    EXPECT_TRUE(identifierAtPosition(*cfg, Host::IDENT_HWADDR, 0));
+    EXPECT_TRUE(identifierAtPosition(*cfg, Host::IDENT_DUID, 1));
+    EXPECT_FALSE(identifierPresent(*cfg, Host::IDENT_CIRCUIT_ID));
+}
+
+} // end of anonymous namespace

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

@@ -783,5 +783,175 @@ TEST_F(HostReservationParserTest, mutuallyExclusiveIdentifiers6) {
     }
     }
 }
 }
 
 
+/// @brief Test fixture class for @ref HostReservationIdsParser.
+class HostReservationIdsParserTest : public ::testing::Test {
+public:
+
+    /// @brief Constructor.
+    ///
+    /// Clears current configuration.
+    HostReservationIdsParserTest() {
+        CfgMgr::instance().clear();
+    }
+
+    /// @brief Destructor.
+    ///
+    /// Clears current configuration.
+    virtual ~HostReservationIdsParserTest() {
+        CfgMgr::instance().clear();
+    }
+
+    /// @brief Test verifies that invalid configuration causes an error.
+    ///
+    /// @param config Configuration string.
+    /// @tparam ParserType @ref HostReservationIdsParser4 or
+    /// @ref HostReservationIdsParser6
+    template<typename ParserType>
+    void testInvalidConfig(const std::string& config) const {
+        ElementPtr config_element = Element::fromJSON(config);
+        ParserType parser;
+        EXPECT_THROW(parser.build(config_element), DhcpConfigError);
+    }
+
+};
+
+// Test that list of supported DHCPv4 identifiers list is correctly
+// parsed.
+TEST_F(HostReservationIdsParserTest, dhcp4Identifiers) {
+    std::string config = "[ \"circuit-id\", \"duid\", \"hw-address\" ]";
+
+    ElementPtr config_element = Element::fromJSON(config);
+
+    HostReservationIdsParser4 parser;
+    ASSERT_NO_THROW(parser.build(config_element));
+
+    ConstCfgHostOperationsPtr cfg = CfgMgr::instance().getStagingCfg()->
+        getCfgHostOperations4();
+    const CfgHostOperations::IdentifierTypes& ids = cfg->getIdentifierTypes();
+    ASSERT_EQ(3, ids.size());
+
+    CfgHostOperations::IdentifierTypes::const_iterator id = ids.begin();
+    EXPECT_EQ(*id++, Host::IDENT_CIRCUIT_ID);
+    EXPECT_EQ(*id++, Host::IDENT_DUID);
+    EXPECT_EQ(*id++, Host::IDENT_HWADDR);
+}
+
+// Test that list of supported DHCPv6 identifiers list is correctly
+// parsed.
+TEST_F(HostReservationIdsParserTest, dhcp6Identifiers) {
+    std::string config = "[ \"duid\", \"hw-address\" ]";
+
+    ElementPtr config_element = Element::fromJSON(config);
+
+    HostReservationIdsParser6 parser;
+    ASSERT_NO_THROW(parser.build(config_element));
+
+    ConstCfgHostOperationsPtr cfg = CfgMgr::instance().getStagingCfg()->
+        getCfgHostOperations6();
+    const CfgHostOperations::IdentifierTypes& ids = cfg->getIdentifierTypes();
+    ASSERT_EQ(2, ids.size());
+
+    CfgHostOperations::IdentifierTypes::const_iterator id = ids.begin();
+    EXPECT_EQ(*id++, Host::IDENT_DUID);
+    EXPECT_EQ(*id++, Host::IDENT_HWADDR);
+}
+
+// Test that invalid DHCPv4 identifier causes error.
+TEST_F(HostReservationIdsParserTest, dhcp4InvalidIdentifier) {
+    // Create configuration including unsupported identifier.
+    std::string config = "[ \"unsupported-id\" ]";
+    testInvalidConfig<HostReservationIdsParser4>(config);
+}
+
+// Test that invalid DHCPv6 identifier causes error.
+TEST_F(HostReservationIdsParserTest, dhcp6InvalidIdentifier) {
+    // Create configuration including unsupported identifier for DHCPv6.
+    // The circuit-id is only supported in DHCPv4.
+    std::string config = "[ \"circuit-id\" ]";
+    testInvalidConfig<HostReservationIdsParser6>(config);
+}
+
+// Check that all supported identifiers are used when 'auto' keyword
+// is specified for DHCPv4 case.
+TEST_F(HostReservationIdsParserTest, dhcp4AutoIdentifiers) {
+    std::string config = "[ \"auto\" ]";
+    ElementPtr config_element = Element::fromJSON(config);
+
+    HostReservationIdsParser4 parser;
+    ASSERT_NO_THROW(parser.build(config_element));
+
+    ConstCfgHostOperationsPtr cfg = CfgMgr::instance().getStagingCfg()->
+        getCfgHostOperations4();
+    const CfgHostOperations::IdentifierTypes& ids = cfg->getIdentifierTypes();
+    ASSERT_EQ(3, ids.size());
+
+    CfgHostOperations::IdentifierTypes::const_iterator id = ids.begin();
+    EXPECT_EQ(*id++, Host::IDENT_HWADDR);
+    EXPECT_EQ(*id++, Host::IDENT_DUID);
+    EXPECT_EQ(*id++, Host::IDENT_CIRCUIT_ID);
+}
+
+// This test verifies that use of "auto" together with an explicit
+// identifier causes an error. "auto" is placed before the explicit
+// identifier.
+TEST_F(HostReservationIdsParserTest, dhcp4AutoBeforeIdentifier) {
+    std::string config = "[ \"auto\", \"duid\" ]";
+    testInvalidConfig<HostReservationIdsParser4>(config);
+}
+
+// This test verifies that use of "auto" together with an explicit
+// identifier causes an error. "auto" is placed after the explicit
+// identifier.
+TEST_F(HostReservationIdsParserTest, dhcp4AutoAfterIdentifier) {
+    std::string config = "[ \"duid\", \"auto\" ]";
+    testInvalidConfig<HostReservationIdsParser4>(config);
+}
+
+// Test that empty list of identifier types is not allowed.
+TEST_F(HostReservationIdsParserTest, dhcp4EmptyList) {
+    std::string config = "[ ]";
+    testInvalidConfig<HostReservationIdsParser4>(config);
+}
+
+// Check that all supported identifiers are used when 'auto' keyword
+// is specified for DHCPv6 case.
+TEST_F(HostReservationIdsParserTest, dhcp6AutoIdentifiers) {
+    std::string config = "[ \"auto\" ]";
+    ElementPtr config_element = Element::fromJSON(config);
+
+    HostReservationIdsParser6 parser;
+    ASSERT_NO_THROW(parser.build(config_element));
+
+    ConstCfgHostOperationsPtr cfg = CfgMgr::instance().getStagingCfg()->
+        getCfgHostOperations6();
+    const CfgHostOperations::IdentifierTypes& ids = cfg->getIdentifierTypes();
+    ASSERT_EQ(2, ids.size());
+
+    CfgHostOperations::IdentifierTypes::const_iterator id = ids.begin();
+    EXPECT_EQ(*id++, Host::IDENT_HWADDR);
+    EXPECT_EQ(*id++, Host::IDENT_DUID);
+}
+
+// This test verifies that use of "auto" together with an explicit
+// identifier causes an error. "auto" is placed before the explicit
+// identifier.
+TEST_F(HostReservationIdsParserTest, dhcp6AutoBeforeIdentifier) {
+    std::string config = "[ \"auto\", \"duid\" ]";
+    testInvalidConfig<HostReservationIdsParser6>(config);
+}
+
+// This test verifies that use of "auto" together with an explicit
+// identifier causes an error. "auto" is placed after the explicit
+// identifier.
+TEST_F(HostReservationIdsParserTest, dhcp6AutoAfterIdentifier) {
+    std::string config = "[ \"duid\", \"auto\" ]";
+    testInvalidConfig<HostReservationIdsParser6>(config);
+}
+
+// Test that empty list of identifier types is not allowed.
+TEST_F(HostReservationIdsParserTest, dhcp6EmptyList) {
+    std::string config = "[ ]";
+    testInvalidConfig<HostReservationIdsParser6>(config);
+}
 
 
 } // end of anonymous namespace
 } // end of anonymous namespace