Browse Source

[3688] DHCPv4 server assignes reserved hostname to the clients.

Marcin Siodelski 10 years ago
parent
commit
6364a03c06

+ 215 - 75
src/bin/dhcp4/dhcp4_srv.cc

@@ -43,6 +43,7 @@
 #include <asio.hpp>
 #include <boost/bind.hpp>
 #include <boost/foreach.hpp>
+#include <boost/shared_ptr.hpp>
 
 #include <iomanip>
 
@@ -80,6 +81,152 @@ struct Dhcp4Hooks {
 // module is called.
 Dhcp4Hooks Hooks;
 
+namespace {
+
+/// @brief DHCPv4 message exchange.
+///
+/// This class represents the DHCPv4 message exchange. The message exchange
+/// consists of the single client message, server response to this message
+/// and the mechanisms to generate the server's response. The server creates
+/// the instance of the @c DHCPv4Exchange for each inbound message that it
+/// accepts for processing.
+///
+/// The use of the @c DHCPv4Exchange object as a central repository of
+/// information about the message exchange simplifies the API of the
+/// @c Dhcpv4Srv class.
+///
+/// Another benefit of using this class is that different methods of the
+/// @c Dhcpv4Srv may share information. For example, the constructor of this
+/// class selects the subnet and multiple methods of @c Dhcpv4Srv use this
+/// subnet, without the need to select it again.
+///
+/// @todo This is the initial version of this class. In the future a lot of
+/// code from the @c Dhcpv4Srv class will be migrated here.
+class DHCPv4Exchange {
+public:
+    /// @brief Constructor.
+    ///
+    /// The constructor selects the subnet for the query and checks for the
+    /// static host reservations for the client which has sent the message.
+    /// The information about the reservations is stored in the
+    /// @c AllocEngine::ClientContext4 object, which can be obtained by
+    /// calling the @c getContext.
+    ///
+    /// @param alloc_engine Pointer to the instance of the Allocation Engine
+    /// used by the server.
+    /// @param query Pointer to the client message.
+    DHCPv4Exchange(const AllocEnginePtr& alloc_engine, const Pkt4Ptr& query);
+
+    /// @brief Selects the subnet for the message processing.
+    ///
+    /// The pointer to the selected subnet is stored in the @c ClientContext4
+    /// structure.
+    void selectSubnet();
+
+    /// @brief Selects the subnet for the message processing.
+    ///
+    /// @todo This variant of the @c selectSubnet method is static and public so
+    /// as it may be invoked by the @c Dhcpv4Srv object. This is temporary solution
+    /// and the function will go away once the server code fully supports the use
+    /// of this class and it obtains the subnet from the context returned by the
+    /// @c getContext method.
+    ///
+    /// @param query Pointer to the client's message.
+    /// @return Pointer to the selected subnet or NULL if no suitable subnet
+    /// has been found.
+    static Subnet4Ptr selectSubnet(const Pkt4Ptr& query);
+
+    /// @brief Returns the copy of the context for the Allocation engine.
+    AllocEngine::ClientContext4 getContext() const {
+        return (context_);
+    }
+
+private:
+    /// @brief Pointer to the allocation engine used by the server.
+    AllocEnginePtr alloc_engine_;
+    /// @brief Pointer to the DHCPv4 message sent by the client.
+    Pkt4Ptr query_;
+    /// @brief Context for use with allocation engine.
+    AllocEngine::ClientContext4 context_;
+};
+
+/// @brief Type representing the pointer to the @c DHCPv4Exchange.
+typedef boost::shared_ptr<DHCPv4Exchange> DHCPv4ExchangePtr;
+
+DHCPv4Exchange::DHCPv4Exchange(const AllocEnginePtr& alloc_engine,
+                               const Pkt4Ptr& query)
+    : alloc_engine_(alloc_engine), query_(query), context_() {
+    selectSubnet();
+    // Hardware address.
+    context_.hwaddr_ = query->getHWAddr();
+    // Client Identifier
+    OptionPtr opt_clientid = query->getOption(DHO_DHCP_CLIENT_IDENTIFIER);
+    if (opt_clientid) {
+        context_.clientid_.reset(new ClientId(opt_clientid->getData()));
+    }
+    // Check for static reservations.
+    alloc_engine->findReservation(context_);
+};
+
+void
+DHCPv4Exchange::selectSubnet() {
+    context_.subnet_ = selectSubnet(query_);
+}
+
+Subnet4Ptr
+DHCPv4Exchange::selectSubnet(const Pkt4Ptr& query) {
+
+    Subnet4Ptr subnet;
+
+    SubnetSelector selector;
+    selector.ciaddr_ = query->getCiaddr();
+    selector.giaddr_ = query->getGiaddr();
+    selector.local_address_ = query->getLocalAddr();
+    selector.remote_address_ = query->getRemoteAddr();
+    selector.client_classes_ = query->classes_;
+    selector.iface_name_ = query->getIface();
+
+    CfgMgr& cfgmgr = CfgMgr::instance();
+    subnet = cfgmgr.getCurrentCfg()->getCfgSubnets4()->selectSubnet(selector);
+
+    // Let's execute all callouts registered for subnet4_select
+    if (HooksManager::calloutsPresent(Hooks.hook_index_subnet4_select_)) {
+        CalloutHandlePtr callout_handle = getCalloutHandle(query);
+
+        // We're reusing callout_handle from previous calls
+        callout_handle->deleteAllArguments();
+
+        // Set new arguments
+        callout_handle->setArgument("query4", query);
+        callout_handle->setArgument("subnet4", subnet);
+        callout_handle->setArgument("subnet4collection",
+                                    cfgmgr.getCurrentCfg()->
+                                    getCfgSubnets4()->getAll());
+
+        // Call user (and server-side) callouts
+        HooksManager::callCallouts(Hooks.hook_index_subnet4_select_,
+                                   *callout_handle);
+
+        // Callouts decided to skip this step. This means that no subnet
+        // will be selected. Packet processing will continue, but it will
+        // be severely limited (i.e. only global options will be assigned)
+        if (callout_handle->getSkip()) {
+            LOG_DEBUG(dhcp4_logger, DBG_DHCP4_HOOKS,
+                      DHCP4_HOOK_SUBNET4_SELECT_SKIP);
+            return (Subnet4Ptr());
+        }
+
+        // Use whatever subnet was specified by the callout
+        callout_handle->getArgument("subnet4", subnet);
+    }
+
+    return (subnet);
+}
+
+DHCPv4ExchangePtr exchange;
+
+};
+
 namespace isc {
 namespace dhcp {
 
@@ -137,6 +284,11 @@ Dhcpv4Srv::shutdown() {
     shutdown_ = true;
 }
 
+isc::dhcp::Subnet4Ptr
+Dhcpv4Srv::selectSubnet(const Pkt4Ptr& question) {
+    return (DHCPv4Exchange::selectSubnet(question));
+}
+
 Pkt4Ptr
 Dhcpv4Srv::receivePacket(int timeout) {
     return (IfaceMgr::instance().receive4(timeout));
@@ -150,6 +302,9 @@ Dhcpv4Srv::sendPacket(const Pkt4Ptr& packet) {
 bool
 Dhcpv4Srv::run() {
     while (!shutdown_) {
+        // Reset pointer to the current exchange.
+        exchange.reset();
+
         // client's message and server's response
         Pkt4Ptr query;
         Pkt4Ptr rsp;
@@ -592,7 +747,7 @@ Dhcpv4Srv::appendRequestedOptions(const Pkt4Ptr& question, Pkt4Ptr& msg) {
 
     // Get the subnet relevant for the client. We will need it
     // to get the options associated with it.
-    Subnet4Ptr subnet = selectSubnet(question);
+    Subnet4Ptr subnet = DHCPv4Exchange::selectSubnet(question);
     // If we can't find the subnet for the client there is no way
     // to get the options to be sent to a client. We don't log an
     // error because it will be logged by the assignLease method
@@ -629,7 +784,7 @@ Dhcpv4Srv::appendRequestedOptions(const Pkt4Ptr& question, Pkt4Ptr& msg) {
 void
 Dhcpv4Srv::appendRequestedVendorOptions(const Pkt4Ptr& question, Pkt4Ptr& answer) {
     // Get the configured subnet suitable for the incoming packet.
-    Subnet4Ptr subnet = selectSubnet(question);
+    Subnet4Ptr subnet = DHCPv4Exchange::selectSubnet(question);
     // Leave if there is no subnet matching the incoming packet.
     // There is no need to log the error message here because
     // it will be logged in the assignLease() when it fails to
@@ -696,7 +851,7 @@ Dhcpv4Srv::appendBasicOptions(const Pkt4Ptr& question, Pkt4Ptr& msg) {
         sizeof(required_options) / sizeof(required_options[0]);
 
     // Get the subnet.
-    Subnet4Ptr subnet = selectSubnet(question);
+    Subnet4Ptr subnet = DHCPv4Exchange::selectSubnet(question);
     if (!subnet) {
         return;
     }
@@ -764,10 +919,16 @@ Dhcpv4Srv::processClientFqdnOption(const Option4ClientFqdnPtr& fqdn,
     fqdn_resp->setFlag(Option4ClientFqdn::FLAG_E,
                        fqdn->getFlag(Option4ClientFqdn::FLAG_E));
 
+    if (exchange && exchange->getContext().host_ &&
+        !exchange->getContext().host_->getHostname().empty()) {
+        fqdn_resp->setDomainName(exchange->getContext().host_->getHostname(),
+                                 Option4ClientFqdn::FULL);
 
-    // Adjust the domain name based on domain name value and type sent by the
-    // client and current configuration.
-    d2_mgr.adjustDomainName<Option4ClientFqdn>(*fqdn, *fqdn_resp);
+    } else {
+        // Adjust the domain name based on domain name value and type sent by the
+        // client and current configuration.
+        d2_mgr.adjustDomainName<Option4ClientFqdn>(*fqdn, *fqdn_resp);
+    }
 
     // Add FQDN option to the response message. Note that, there may be some
     // cases when server may choose not to include the FQDN option in a
@@ -818,17 +979,23 @@ Dhcpv4Srv::processHostnameOption(const OptionStringPtr& opt_hostname,
     // By checking the number of labels present in the hostname we may infer
     // whether client has sent the fully qualified or unqualified hostname.
 
-    /// @todo We may want to reconsider whether it is appropriate for the
-    /// client to send a root domain name as a Hostname. There are
-    /// also extensions to the auto generation of the client's name,
-    /// e.g. conversion to the puny code which may be considered at some point.
-    /// For now, we just remain liberal and expect that the DNS will handle
-    /// conversion if needed and possible.
-    if ((d2_mgr.getD2ClientConfig()->getReplaceClientName()) ||
-        (label_count < 2)) {
+    // If there is a hostname reservation for this client, use it.
+    if (exchange && exchange->getContext().host_ &&
+        !exchange->getContext().host_->getHostname().empty()) {
+        opt_hostname_resp->setValue(exchange->getContext().host_->getHostname());
+
+    } else if ((d2_mgr.getD2ClientConfig()->getReplaceClientName()) ||
+               (label_count < 2)) {
         // Set to root domain to signal later on that we should replace it.
         // DHO_HOST_NAME is a string option which cannot be empty.
+        /// @todo We may want to reconsider whether it is appropriate for the
+        /// client to send a root domain name as a Hostname. There are
+        /// also extensions to the auto generation of the client's name,
+        /// e.g. conversion to the puny code which may be considered at some point.
+        /// For now, we just remain liberal and expect that the DNS will handle
+        /// conversion if needed and possible.
         opt_hostname_resp->setValue(".");
+
     } else if (label_count == 2) {
         // If there are two labels, it means that the client has specified
         // the unqualified name. We have to concatenate the unqualified name
@@ -933,7 +1100,7 @@ void
 Dhcpv4Srv::assignLease(const Pkt4Ptr& question, Pkt4Ptr& answer) {
 
     // We need to select a subnet the client is connected in.
-    Subnet4Ptr subnet = selectSubnet(question);
+    Subnet4Ptr subnet = DHCPv4Exchange::selectSubnet(question);
     if (!subnet) {
         // This particular client is out of luck today. We do not have
         // information about the subnet he is connected to. This likely means
@@ -1072,13 +1239,18 @@ Dhcpv4Srv::assignLease(const Pkt4Ptr& question, Pkt4Ptr& answer) {
         }
     }
 
-    // Use allocation engine to pick a lease for this client. Allocation engine
-    // will try to honour the hint, but it is just a hint - some other address
-    // may be used instead. If fake_allocation is set to false, the lease will
-    // be inserted into the LeaseMgr as well.
-    /// @todo pass the actual FQDN data.
-    AllocEngine::ClientContext4 ctx(subnet, client_id, hwaddr, hint, fqdn_fwd,
-                                    fqdn_rev, hostname, fake_allocation);
+    AllocEngine::ClientContext4 ctx;
+    if (exchange) {
+        ctx = exchange->getContext();
+    }
+    ctx.subnet_ = subnet;
+    ctx.clientid_ = client_id;
+    ctx.hwaddr_ = hwaddr;
+    ctx.requested_address_ = hint;
+    ctx.fwd_dns_update_ = fqdn_fwd;
+    ctx.rev_dns_update_ = fqdn_rev;
+    ctx.hostname_ = hostname;
+    ctx.fake_allocation_ = fake_allocation;
     ctx.callout_handle_ = callout_handle;
 
     Lease4Ptr lease = alloc_engine_->allocateLease4(ctx);
@@ -1342,6 +1514,9 @@ Dhcpv4Srv::getNetmaskOption(const Subnet4Ptr& subnet) {
 
 Pkt4Ptr
 Dhcpv4Srv::processDiscover(Pkt4Ptr& discover) {
+    /// @todo Move this call to run() once we reorgnize some unit tests
+    /// which directly call this method.
+    exchange.reset(new DHCPv4Exchange(alloc_engine_, discover));
 
     sanityCheck(discover, FORBIDDEN);
 
@@ -1390,6 +1565,9 @@ Dhcpv4Srv::processDiscover(Pkt4Ptr& discover) {
 
 Pkt4Ptr
 Dhcpv4Srv::processRequest(Pkt4Ptr& request) {
+    /// @todo Move this call to run() once we reorgnize some unit tests
+    /// which directly call this method.
+    exchange.reset(new DHCPv4Exchange(alloc_engine_, request));
 
     /// @todo Uncomment this (see ticket #3116)
     /// sanityCheck(request, MANDATORY);
@@ -1437,6 +1615,9 @@ Dhcpv4Srv::processRequest(Pkt4Ptr& request) {
 
 void
 Dhcpv4Srv::processRelease(Pkt4Ptr& release) {
+    /// @todo Move this call to run() once we reorgnize some unit tests
+    /// which directly call this method.
+    exchange.reset(new DHCPv4Exchange(alloc_engine_, release));
 
     /// @todo Uncomment this (see ticket #3116)
     /// sanityCheck(release, MANDATORY);
@@ -1548,12 +1729,20 @@ Dhcpv4Srv::processRelease(Pkt4Ptr& release) {
 }
 
 void
-Dhcpv4Srv::processDecline(Pkt4Ptr& /* decline */) {
+Dhcpv4Srv::processDecline(Pkt4Ptr& decline) {
+    /// @todo Move this call to run() once we reorgnize some unit tests
+    /// which directly call this method.
+    exchange.reset(new DHCPv4Exchange(alloc_engine_, decline));
+
     /// @todo Implement this (also see ticket #3116)
 }
 
 Pkt4Ptr
 Dhcpv4Srv::processInform(Pkt4Ptr& inform) {
+    /// @todo Move this call to run() once we reorgnize some unit tests
+    /// which directly call this method.
+    exchange.reset(new DHCPv4Exchange(alloc_engine_, inform));
+
     // DHCPINFORM MUST not include server identifier.
     sanityCheck(inform, FORBIDDEN);
 
@@ -1611,56 +1800,6 @@ Dhcpv4Srv::serverReceivedPacketName(uint8_t type) {
     return (UNKNOWN);
 }
 
-Subnet4Ptr
-Dhcpv4Srv::selectSubnet(const Pkt4Ptr& question) const {
-
-    Subnet4Ptr subnet;
-
-    SubnetSelector selector;
-    selector.ciaddr_ = question->getCiaddr();
-    selector.giaddr_ = question->getGiaddr();
-    selector.local_address_ = question->getLocalAddr();
-    selector.remote_address_ = question->getRemoteAddr();
-    selector.client_classes_ = question->classes_;
-    selector.iface_name_ = question->getIface();
-
-    CfgMgr& cfgmgr = CfgMgr::instance();
-    subnet = cfgmgr.getCurrentCfg()->getCfgSubnets4()->selectSubnet(selector);
-
-    // Let's execute all callouts registered for subnet4_select
-    if (HooksManager::calloutsPresent(hook_index_subnet4_select_)) {
-        CalloutHandlePtr callout_handle = getCalloutHandle(question);
-
-        // We're reusing callout_handle from previous calls
-        callout_handle->deleteAllArguments();
-
-        // Set new arguments
-        callout_handle->setArgument("query4", question);
-        callout_handle->setArgument("subnet4", subnet);
-        callout_handle->setArgument("subnet4collection",
-                                    cfgmgr.getCurrentCfg()->
-                                    getCfgSubnets4()->getAll());
-
-        // Call user (and server-side) callouts
-        HooksManager::callCallouts(hook_index_subnet4_select_,
-                                   *callout_handle);
-
-        // Callouts decided to skip this step. This means that no subnet
-        // will be selected. Packet processing will continue, but it will
-        // be severely limited (i.e. only global options will be assigned)
-        if (callout_handle->getSkip()) {
-            LOG_DEBUG(dhcp4_logger, DBG_DHCP4_HOOKS,
-                      DHCP4_HOOK_SUBNET4_SELECT_SKIP);
-            return (Subnet4Ptr());
-        }
-
-        // Use whatever subnet was specified by the callout
-        callout_handle->getArgument("subnet4", subnet);
-    }
-
-    return (subnet);
-}
-
 bool
 Dhcpv4Srv::accept(const Pkt4Ptr& query) const {
     // Check that the message type is accepted by the server. We rely on the
@@ -1725,7 +1864,8 @@ Dhcpv4Srv::acceptDirectRequest(const Pkt4Ptr& pkt) const {
         // we validate the message type prior to calling this function.
         return (false);
     }
-    return ((pkt->getLocalAddr() != IOAddress::IPV4_BCAST_ADDRESS() || selectSubnet(pkt)));
+    return ((pkt->getLocalAddr() != IOAddress::IPV4_BCAST_ADDRESS()
+             || DHCPv4Exchange::selectSubnet(pkt)));
 }
 
 bool
@@ -2012,7 +2152,7 @@ void Dhcpv4Srv::classifyPacket(const Pkt4Ptr& pkt) {
 
 bool Dhcpv4Srv::classSpecificProcessing(const Pkt4Ptr& query, const Pkt4Ptr& rsp) {
 
-    Subnet4Ptr subnet = selectSubnet(query);
+    Subnet4Ptr subnet = DHCPv4Exchange::selectSubnet(query);
     if (!subnet) {
         return (true);
     }

+ 1 - 1
src/bin/dhcp4/dhcp4_srv.h

@@ -619,7 +619,7 @@ protected:
     ///
     /// @param question client's message
     /// @return selected subnet (or NULL if no suitable subnet was found)
-    isc::dhcp::Subnet4Ptr selectSubnet(const Pkt4Ptr& question) const;
+    static isc::dhcp::Subnet4Ptr selectSubnet(const Pkt4Ptr& question);
 
     /// indicates if shutdown is in progress. Setting it to true will
     /// initiate server shutdown procedure.

+ 32 - 0
src/bin/dhcp4/tests/dhcp4_client.cc

@@ -64,6 +64,7 @@ Dhcp4Client::Dhcp4Client(boost::shared_ptr<NakedDhcpv4Srv> srv,
     ciaddr_(IOAddress("0.0.0.0")),
     curr_transid_(0),
     dest_addr_("255.255.255.255"),
+    fqdn_(),
     hwaddr_(generateHWAddr()),
     iface_name_("eth0"),
     relay_addr_("192.0.2.2"),
@@ -150,6 +151,8 @@ Dhcp4Client::doDiscover(const boost::shared_ptr<IOAddress>& requested_addr) {
     context_.query_ = createMsg(DHCPDISCOVER);
     // Request options if any.
     includePRL();
+    // Include FQDN or Hostname.
+    includeName();
     if (requested_addr) {
         addRequestedAddress(*requested_addr);
     }
@@ -239,6 +242,8 @@ Dhcp4Client::doRequest() {
 
     // Request options if any.
     includePRL();
+    // Include FQDN or Hostname.
+    includeName();
     // Send the message to the server.
     sendMsg(context_.query_);
     // Expect response.
@@ -250,6 +255,33 @@ Dhcp4Client::doRequest() {
 }
 
 void
+Dhcp4Client::includeFQDN(const uint8_t flags, const std::string& fqdn_name,
+                         Option4ClientFqdn::DomainNameType fqdn_type) {
+    fqdn_.reset(new Option4ClientFqdn(flags, Option4ClientFqdn::RCODE_CLIENT(),
+                                      fqdn_name, fqdn_type));
+}
+
+void
+Dhcp4Client::includeHostname(const std::string& name) {
+    hostname_.reset(new OptionString(Option::V4, DHO_HOST_NAME, name));
+}
+
+void
+Dhcp4Client::includeName() {
+    if (!context_.query_) {
+        isc_throw(Dhcp4ClientError, "pointer to the query must not be NULL"
+                  " when adding FQDN or Hostname option");
+    }
+
+    if (fqdn_) {
+        context_.query_->addOption(fqdn_);
+
+    } else if (hostname_) {
+        context_.query_->addOption(hostname_);
+    }
+}
+
+void
 Dhcp4Client::includePRL() {
     if (!context_.query_) {
         isc_throw(Dhcp4ClientError, "pointer to the query must not be NULL"

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

@@ -217,6 +217,21 @@ public:
         return (srv_);
     }
 
+    /// @brief Creates an instance of the Client FQDN option to be included
+    /// in the client's message.
+    ///
+    /// @param flags Flags.
+    /// @param fqdn_name Name in the textual format.
+    /// @param fqdn_type Type of the name (fully qualified or partial).
+    void includeFQDN(const uint8_t flags, const std::string& fqdn_name,
+                     Option4ClientFqdn::DomainNameType fqdn_type);
+
+    /// @brief Creates an instance of the Hostname option to be included
+    /// in the client's message.
+    ///
+    /// @param name Name to be stored in the option.
+    void includeHostname(const std::string& name);
+
     /// @brief Modifies the client's HW address (adds one to it).
     ///
     /// The HW address should be modified to test negative scenarios when the
@@ -345,6 +360,13 @@ private:
     /// @return An instance of the message created.
     Pkt4Ptr createMsg(const uint8_t msg_type);
 
+    /// @brief Includes FQDN or Hostname option in the client's message.
+    ///
+    /// This method checks if @c fqdn_ or @c hostname_ is specified and
+    /// includes it in the client's message. If both are specified, the
+    /// @c fqdn_ will be used.
+    void includeName();
+
     /// @brief Include PRL Option in the query message.
     ///
     /// This function creates the instance of the PRL (Parameter Request List)
@@ -376,6 +398,12 @@ private:
     /// @brief Currently used destination address.
     asiolink::IOAddress dest_addr_;
 
+    /// @brief FQDN requested by the client.
+    Option4ClientFqdnPtr fqdn_;
+
+    /// @brief Hostname requested by the client.
+    OptionStringPtr hostname_;
+
     /// @brief Current hardware address of the client.
     HWAddrPtr hwaddr_;
 
@@ -406,4 +434,4 @@ private:
 } // end of namespace isc::dhcp
 } // end of namespace isc
 
-#endif // DHCP4_CLIENT
+#endif // DHCP4_CLIENT_H

+ 265 - 6
src/bin/dhcp4/tests/fqdn_unittest.cc

@@ -17,9 +17,12 @@
 #include <dhcp/option4_client_fqdn.h>
 #include <dhcp/option_int_array.h>
 #include <dhcp/tests/iface_mgr_test_config.h>
+#include <dhcp4/tests/dhcp4_client.h>
 #include <dhcp4/tests/dhcp4_test_utils.h>
 #include <dhcp_ddns/ncr_msg.h>
 #include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/lease_mgr.h>
+#include <dhcpsrv/lease_mgr_factory.h>
 
 #include <gtest/gtest.h>
 #include <boost/scoped_ptr.hpp>
@@ -32,20 +35,91 @@ using namespace isc::dhcp_ddns;
 
 namespace {
 
+/// @brief Set of JSON configurations used by the FQDN tests.
+const char* CONFIGS[] = {
+    "{ \"interfaces-config\": {"
+        "      \"interfaces\": [ \"*\" ]"
+        "},"
+        "\"valid-lifetime\": 3000,"
+        "\"subnet4\": [ { "
+        "    \"subnet\": \"10.0.0.0/24\", "
+        "    \"id\": 1,"
+        "    \"pools\": [ { \"pool\": \"10.0.0.10-10.0.0.100\" } ],"
+        "    \"option-data\": [ {"
+        "        \"name\": \"routers\","
+        "        \"code\": 3,"
+        "        \"data\": \"10.0.0.200,10.0.0.201\","
+        "        \"csv-format\": true,"
+        "        \"space\": \"dhcp4\""
+        "    } ],"
+        "    \"reservations\": ["
+        "       {"
+        "         \"hw-address\": \"aa:bb:cc:dd:ee:ff\","
+        "         \"ip-address\": \"10.0.0.5\","
+        "         \"hostname\":   \"unique-host.example.org\""
+        "       }"
+        "    ]"
+        " }],"
+        "\"dhcp-ddns\": {"
+            "\"enable-updates\": true,"
+            "\"qualifying-suffix\": \"fake-suffix.isc.org.\""
+        "}"
+    "}",
+    "{ \"interfaces-config\": {"
+        "      \"interfaces\": [ \"*\" ]"
+        "},"
+        "\"valid-lifetime\": 3000,"
+        "\"subnet4\": [ { "
+        "    \"subnet\": \"10.0.0.0/24\", "
+        "    \"id\": 1,"
+        "    \"pools\": [ { \"pool\": \"10.0.0.10-10.0.0.100\" } ],"
+        "    \"option-data\": [ {"
+        "        \"name\": \"routers\","
+        "        \"code\": 3,"
+        "        \"data\": \"10.0.0.200,10.0.0.201\","
+        "        \"csv-format\": true,"
+        "        \"space\": \"dhcp4\""
+        "    } ],"
+        "    \"reservations\": ["
+        "       {"
+        "         \"hw-address\": \"aa:bb:cc:dd:ee:ff\","
+        "         \"ip-address\": \"10.0.0.5\","
+        "         \"hostname\":   \"foobar.org\""
+        "       }"
+        "    ]"
+        " }],"
+        "\"dhcp-ddns\": {"
+            "\"enable-updates\": true,"
+            "\"qualifying-suffix\": \"fake-suffix.isc.org.\""
+        "}"
+    "}"
+};
+
 class NameDhcpv4SrvTest : public Dhcpv4SrvTest {
 public:
     // Reference to D2ClientMgr singleton
     D2ClientMgr& d2_mgr_;
 
+    /// @brief Pointer to the DHCP server instance.
+    NakedDhcpv4Srv* srv_;
+
+    /// @brief Interface Manager's fake configuration control.
+    IfaceMgrTestConfig iface_mgr_test_config_;
+
     // Bit Constants for turning on and off DDNS configuration options.
     static const uint16_t ALWAYS_INCLUDE_FQDN = 1;
     static const uint16_t OVERRIDE_NO_UPDATE = 2;
     static const uint16_t OVERRIDE_CLIENT_UPDATE = 4;
     static const uint16_t REPLACE_CLIENT_NAME = 8;
 
-    NameDhcpv4SrvTest() : Dhcpv4SrvTest(),
-        d2_mgr_(CfgMgr::instance().getD2ClientMgr()) {
+    NameDhcpv4SrvTest()
+        : Dhcpv4SrvTest(),
+          d2_mgr_(CfgMgr::instance().getD2ClientMgr()),
+          srv_(NULL),
+          iface_mgr_test_config_(true)
+    {
         srv_ = new NakedDhcpv4Srv(0);
+        IfaceMgr::instance().openSockets4();
         // Config DDNS to be enabled, all controls off
         enableD2();
     }
@@ -412,10 +486,6 @@ public:
             }
         }
     }
-
-
-    NakedDhcpv4Srv* srv_;
-
 };
 
 // Test that the exception is thrown if lease pointer specified as the argument
@@ -1033,5 +1103,194 @@ TEST_F(NameDhcpv4SrvTest, processRequestReleaseUpdatesDisabled) {
     ASSERT_NO_THROW(srv_->processRelease(rel));
 }
 
+// This test verifies that the server sends the FQDN option to the client
+// with the reserved hostname.
+TEST_F(NameDhcpv4SrvTest, fqdnReservation) {
+    Dhcp4Client client(Dhcp4Client::SELECTING);
+    // Use HW address that matches the reservation entry in the configuration.
+    client.setHWAddress("aa:bb:cc:dd:ee:ff");
+    // Configure DHCP server.
+    configure(CONFIGS[0], *client.getServer());
+    // Make sure that DDNS is enabled.
+    ASSERT_TRUE(CfgMgr::instance().ddnsEnabled());
+    ASSERT_NO_THROW(client.getServer()->startD2());
+    // Include the Client FQDN option.
+    ASSERT_NO_THROW(client.includeFQDN(Option4ClientFqdn::FLAG_S | Option4ClientFqdn::FLAG_E,
+                                       "client-name", Option4ClientFqdn::PARTIAL));
+    // Send the DHCPDISCOVER.
+    ASSERT_NO_THROW(client.doDiscover());
+
+    // Make sure that the server responded.
+    Pkt4Ptr resp = client.getContext().response_;
+    ASSERT_TRUE(resp);
+    ASSERT_EQ(DHCPOFFER, static_cast<int>(resp->getType()));
+
+    // Obtain the FQDN option sent in the response and make sure that the server
+    // has used the hostname reserved for this client.
+    Option4ClientFqdnPtr fqdn;
+    fqdn = boost::dynamic_pointer_cast<Option4ClientFqdn>(resp->getOption(DHO_FQDN));
+    ASSERT_TRUE(fqdn);
+    EXPECT_EQ("unique-host.example.org.", fqdn->getDomainName());
+
+    // When receiving DHCPDISCOVER, no NCRs should be generated.
+    EXPECT_EQ(0, d2_mgr_.getQueueSize());
+
+    // Now send the DHCPREQUEST with including the FQDN option.
+    ASSERT_NO_THROW(client.doRequest());
+    resp = client.getContext().response_;
+    ASSERT_TRUE(resp);
+    ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+
+    // Once again check that the FQDN is as expected.
+    fqdn = boost::dynamic_pointer_cast<Option4ClientFqdn>(resp->getOption(DHO_FQDN));
+    ASSERT_TRUE(fqdn);
+    EXPECT_EQ("unique-host.example.org.", fqdn->getDomainName());
+
+    // Because this is a new lease, there should be one NCR which adds the
+    // new DNS entry.
+    ASSERT_EQ(1, CfgMgr::instance().getD2ClientMgr().getQueueSize());
+    verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true,
+                            resp->getYiaddr().toText(),
+                            "unique-host.example.org.",
+                            "000001ACB52196C8F3BCC1DF3BA1F40BAC39BF23"
+                            "0D280858B1ED7696E174C4479E3372",
+                            time(NULL), subnet_->getValid(), true);
+    
+    // And that this FQDN has been stored in the lease database.
+    Lease4Ptr lease = LeaseMgrFactory::instance().getLease4(client.config_.lease_.addr_);
+    ASSERT_TRUE(lease);
+    EXPECT_EQ("unique-host.example.org.", lease->hostname_);
+
+    // Reconfigure DHCP server to use a different hostname for the client.
+    configure(CONFIGS[1], *client.getServer());
+
+    // Client is in the renewing state.
+    client.setState(Dhcp4Client::RENEWING);
+    client.doRequest();
+    resp = client.getContext().response_;
+    ASSERT_TRUE(resp);
+    ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+
+    // The new FQDN should contain a different name this time.
+    fqdn = boost::dynamic_pointer_cast<Option4ClientFqdn>(resp->getOption(DHO_FQDN));
+    ASSERT_TRUE(fqdn);
+    EXPECT_EQ("foobar.org.", fqdn->getDomainName());
+
+    // And the lease in the lease database should also contain this new FQDN.
+    lease = LeaseMgrFactory::instance().getLease4(client.config_.lease_.addr_);
+    ASSERT_TRUE(lease);
+    EXPECT_EQ("foobar.org.", lease->hostname_);
+
+    // Now there should be two name NCRs. One that removes the previous entry
+    // and the one that adds a new entry for the new hostname.
+    ASSERT_EQ(2, CfgMgr::instance().getD2ClientMgr().getQueueSize());
+    verifyNameChangeRequest(isc::dhcp_ddns::CHG_REMOVE, true, true,
+                            resp->getYiaddr().toText(),
+                            "unique-host.example.org.",
+                            "000001ACB52196C8F3BCC1DF3BA1F40BAC39BF23"
+                            "0D280858B1ED7696E174C4479E3372",
+                            time(NULL), subnet_->getValid(), true);
+
+    verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true,
+                            resp->getYiaddr().toText(),
+                            "foobar.org.",
+                            "000001B722C2FB5FAFE25B99178A0BFEC05127B9"
+                            "5DC843E00941D444D53B24C2365337",
+                            time(NULL), subnet_->getValid(), true);
+}
+
+// This test verifies that the server sends the Hostname option to the client
+// with the reserved hostname.
+TEST_F(NameDhcpv4SrvTest, hostnameReservation) {
+    Dhcp4Client client(Dhcp4Client::SELECTING);
+    // Use HW address that matches the reservation entry in the configuration.
+    client.setHWAddress("aa:bb:cc:dd:ee:ff");
+    // Configure DHCP server.
+    configure(CONFIGS[0], *client.getServer());
+    // Make sure that DDNS is enabled.
+    ASSERT_TRUE(CfgMgr::instance().ddnsEnabled());
+    ASSERT_NO_THROW(client.getServer()->startD2());
+    // Include the Hostname option.
+    ASSERT_NO_THROW(client.includeHostname("client-name"));
+
+    // Send the DHCPDISCOVER
+    ASSERT_NO_THROW(client.doDiscover());
+
+    // Make sure that the server responded.
+    Pkt4Ptr resp = client.getContext().response_;
+    ASSERT_TRUE(resp);
+    ASSERT_EQ(DHCPOFFER, static_cast<int>(resp->getType()));
+
+    // Obtain the Hostname option sent in the response and make sure that the server
+    // has used the hostname reserved for this client.
+    OptionStringPtr hostname; 
+    hostname = boost::dynamic_pointer_cast<OptionString>(resp->getOption(DHO_HOST_NAME));
+    ASSERT_TRUE(hostname);
+    EXPECT_EQ("unique-host.example.org", hostname->getValue());
+
+    // Now send the DHCPREQUEST with including the Hostname option.
+    ASSERT_NO_THROW(client.doRequest());
+    resp = client.getContext().response_;
+    ASSERT_TRUE(resp);
+    ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+
+    // Once again check that the Hostname is as expected.
+    hostname = boost::dynamic_pointer_cast<OptionString>(resp->getOption(DHO_HOST_NAME));
+    ASSERT_TRUE(hostname);
+    EXPECT_EQ("unique-host.example.org", hostname->getValue());
+
+    // And that this hostname has been stored in the lease database.
+    Lease4Ptr lease = LeaseMgrFactory::instance().getLease4(client.config_.lease_.addr_);
+    ASSERT_TRUE(lease);
+    EXPECT_EQ("unique-host.example.org", lease->hostname_);
+
+    // Because this is a new lease, there should be one NCR which adds the
+    // new DNS entry.
+    ASSERT_EQ(1, CfgMgr::instance().getD2ClientMgr().getQueueSize());
+    verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true,
+                            resp->getYiaddr().toText(),
+                            "unique-host.example.org.",
+                            "000001ACB52196C8F3BCC1DF3BA1F40BAC39BF23"
+                            "0D280858B1ED7696E174C4479E3372",
+                            time(NULL), subnet_->getValid(), true);
+
+    // Reconfigure DHCP server to use a different hostname for the client.
+    configure(CONFIGS[1], *client.getServer());
+
+    // Client is in the renewing state.
+    client.setState(Dhcp4Client::RENEWING);
+    client.doRequest();
+    resp = client.getContext().response_;
+    ASSERT_TRUE(resp);
+    ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+
+    // The new hostname should be different than previously.
+    hostname = boost::dynamic_pointer_cast<OptionString>(resp->getOption(DHO_HOST_NAME));
+    ASSERT_TRUE(hostname);
+    EXPECT_EQ("foobar.org", hostname->getValue());
+
+    // And the lease in the lease database should also contain this new FQDN.
+    lease = LeaseMgrFactory::instance().getLease4(client.config_.lease_.addr_);
+    ASSERT_TRUE(lease);
+    EXPECT_EQ("foobar.org", lease->hostname_);
+
+    // Now there should be two name NCRs. One that removes the previous entry
+    // and the one that adds a new entry for the new hostname.
+    ASSERT_EQ(2, CfgMgr::instance().getD2ClientMgr().getQueueSize());
+    verifyNameChangeRequest(isc::dhcp_ddns::CHG_REMOVE, true, true,
+                            resp->getYiaddr().toText(),
+                            "unique-host.example.org.",
+                            "000001ACB52196C8F3BCC1DF3BA1F40BAC39BF23"
+                            "0D280858B1ED7696E174C4479E3372",
+                            time(NULL), subnet_->getValid(), true);
+
+    verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true,
+                            resp->getYiaddr().toText(),
+                            "foobar.org.",
+                            "000001B722C2FB5FAFE25B99178A0BFEC05127B9"
+                            "5DC843E00941D444D53B24C2365337",
+                            time(NULL), subnet_->getValid(), true);
+}
+
 
 } // end of anonymous namespace

+ 1 - 1
src/lib/dhcpsrv/alloc_engine.cc

@@ -1228,7 +1228,7 @@ AllocEngine::ClientContext4::ClientContext4()
       old_lease_(), host_(), conflicting_lease_() {
 }
 
-AllocEngine::ClientContext4::ClientContext4(const SubnetPtr& subnet,
+AllocEngine::ClientContext4::ClientContext4(const Subnet4Ptr& subnet,
                                             const ClientIdPtr& clientid,
                                             const HWAddrPtr& hwaddr,
                                             const asiolink::IOAddress& requested_addr,

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

@@ -682,7 +682,7 @@ public:
     /// new information doesn't modify the API of the allocation engine.
     struct ClientContext4 {
         /// @brief Subnet selected for the client by the server.
-        SubnetPtr subnet_;
+        Subnet4Ptr subnet_;
 
         /// @brief Client identifier from the DHCP message.
         ClientIdPtr clientid_;
@@ -748,7 +748,7 @@ public:
         /// @param fake_allocation Is this real i.e. REQUEST (false)
         ///      or just picking an address for DISCOVER that is not really
         ///      allocated (true)
-        ClientContext4(const SubnetPtr& subnet, const ClientIdPtr& clientid,
+        ClientContext4(const Subnet4Ptr& subnet, const ClientIdPtr& clientid,
                        const HWAddrPtr& hwaddr,
                        const asiolink::IOAddress& requested_addr,
                        const bool fwd_dns_update, const bool rev_dns_update,
@@ -1079,6 +1079,9 @@ private:
                                  ClientContext4& ctx) const;
 };
 
+/// @brief A pointer to the @c AllocEngine object.
+typedef boost::shared_ptr<AllocEngine> AllocEnginePtr;
+
 }; // namespace isc::dhcp
 }; // namespace isc
 

+ 1 - 1
src/lib/dhcpsrv/tests/alloc_engine4_unittest.cc

@@ -226,7 +226,7 @@ TEST_F(AllocEngine4Test, allocateLease4Nulls) {
     ASSERT_TRUE(engine);
 
     // Allocations without subnet are not allowed
-    AllocEngine::ClientContext4 ctx1(SubnetPtr(), clientid_, hwaddr_,
+    AllocEngine::ClientContext4 ctx1(Subnet4Ptr(), clientid_, hwaddr_,
                                     IOAddress("0.0.0.0"), false, false,
                                     "", false);
     Lease4Ptr lease = engine->allocateLease4(ctx1);