Browse Source

[master] Merge branch 'trac3688'

Conflicts:
	src/bin/dhcp4/dhcp4_srv.h
Marcin Siodelski 10 years ago
parent
commit
b5c50e2aff

+ 59 - 4
doc/guide/dhcp4-srv.xml

@@ -2124,10 +2124,65 @@ temporarily override a list of interface names and listen on all interfaces.
 
     <section id="reservation4-hostname">
       <title>Reserving a hostname</title>
-      <!-- @todo: replace this with the actual text once #3688 is implemented -->
-      <para>Reserving a hostname is currently not supported. It is possible
-      to specify that information in the configuration file, but that data
-      is not used by the server engine yet.</para>
+      <para>When the reservation for the client includes the <command>hostname
+      </command>, the server will assign this hostname to the client and send
+      it back in the Client FQDN or Hostname option, depending on which of them
+      the client has sent to the server. The reserved hostname always takes
+      precedence over the hostname supplied by the client or the autogenerated
+      (from the IPv4 address) hostname.</para>
+
+      <para>The server qualifies the reserved hostname with the value
+      of the <command>qualifying-suffix</command> parameter. For example, the
+      following subnet configuration:
+<screen>
+    {
+        "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",
+                 "hostname": "alice-laptop"
+               }
+            ]
+         }],
+        "dhcp-ddns": {
+            "enable-updates": true,
+            "qualifying-suffix": "example.isc.org."
+        }
+    }
+</screen>
+      will result in assigning the "alice-laptop.example.isc.org." hostname to the
+      client using the MAC address "aa:bb:cc:dd:ee:ff". If the <command>qualifying-suffix
+      </command> is not specified, the default (empty) value will be used, and
+      in this case the value specified as a <command>hostname</command> will
+      be treated as fully qualified name. Thus, by leaving the
+      <command>qualifying-suffix</command> empty it is possible to qualify
+      hostnames for the different clients with different domain names:
+<screen>
+    {
+        "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",
+                 "hostname": "alice-laptop.isc.org."
+               },
+               {
+                 "hw-address": "12:34:56:78:99:AA",
+                 "hostname": "mark-desktop.example.org."
+               }
+
+            ]
+         }],
+        "dhcp-ddns": {
+            "enable-updates": true,
+        }
+    }
+</screen>
+
+      </para>
     </section>
 
     <section id="reservation4-options">

+ 12 - 4
src/bin/dhcp4/dhcp4_messages.mes

@@ -31,10 +31,6 @@ to establish a session with the Kea control channel.
 This debug message informs that incoming packet has been assigned to specified
 class or classes. This is a normal behavior and indicates successful operation.
 
-% DHCP4_CLASS_PROCESSING_FAILED client class specific processing failed
-This debug message means that the server processing that is unique for each
-client class has reported a failure. The response packet will not be sent.
-
 % DHCP4_CLIENT_NAME_PROC_FAIL failed to process the fqdn or hostname sent by a client: %1
 This debug message is issued when the DHCP server was unable to process the
 FQDN or Hostname option sent by a client. This is likely because the client's
@@ -78,6 +74,10 @@ change is committed by the administrator.
 A debug message indicating that the DHCPv4 server has received an
 updated configuration from the Kea configuration system.
 
+% DHCP4_DISCOVER_CLASS_PROCESSING_FAILED client class specific processing failed for DHCPDISCOVER
+This debug message means that the server processing that is unique for each
+client class has reported a failure. The response packet will not be sent.
+
 % DHCP4_DDNS_REQUEST_SEND_FAILED failed sending a request to kea-dhcp-ddns, error: %1,  ncr: %2
 This error message indicates that DHCP4 server attempted to send a DDNS
 update request to the DHCP-DDNS server.  This is most likely a configuration or
@@ -148,6 +148,10 @@ point, the setting of the flag instructs the server not to choose a
 subnet, an action that severely limits further processing; the server
 will be only able to offer global options - no addresses will be assigned.
 
+% DHCP4_INFORM_CLASS_PROCESSING_FAILED client class specific processing failed for DHCPINFORM
+This debug message means that the server processing that is unique for each
+client class has reported a failure. The response packet will not be sent.
+
 % DHCP4_INIT_FAIL failed to initialize Kea server: %1
 The server has failed to initialize. This may be because the configuration
 was not successful, or it encountered any other critical error on startup.
@@ -349,6 +353,10 @@ a different hardware address. One possible reason for using different
 hardware address is that a cloned virtual machine was not updated and
 both clones use the same client-id.
 
+% DHCP4_REQUEST_CLASS_PROCESSING_FAILED client class specific processing failed for DHCPREQUEST
+This debug message means that the server processing that is unique for each
+client class has reported a failure. The response packet will not be sent.
+
 % DHCP4_RESPONSE_DATA responding with packet type %1, data is <%2>
 A debug message listing the data returned to the client.
 

File diff suppressed because it is too large
+ 373 - 287
src/bin/dhcp4/dhcp4_srv.cc


+ 148 - 74
src/bin/dhcp4/dhcp4_srv.h

@@ -43,6 +43,99 @@ public:
         isc::Exception(file, line, what) { };
 };
 
+/// @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.
+    /// @param subnet Pointer to the subnet to which the client belongs.
+    Dhcpv4Exchange(const AllocEnginePtr& alloc_engine, const Pkt4Ptr& query,
+                   const Subnet4Ptr& subnet);
+
+    /// @brief Initializes the instance of the response message.
+    ///
+    /// The type of the response depends on the type of the query message.
+    /// For the DHCPDISCOVER the DHCPOFFER is created. For the DHCPREQUEST
+    /// and DHCPINFORM the DHCPACK is created. For the DHCPRELEASE the
+    /// response is not initialized.
+    void initResponse();
+
+    /// @brief Returns the pointer to the query from the client.
+    Pkt4Ptr getQuery() const {
+        return (query_);
+    }
+
+    /// @brief Returns the pointer to the server's response.
+    ///
+    /// The returned pointer is NULL if the query type is DHCPRELEASE or DHCPDECLINE.
+    Pkt4Ptr getResponse() const {
+        return (resp_);
+    }
+
+    /// @brief Removes the response message by resetting the pointer to NULL.
+    void deleteResponse() {
+        resp_.reset();
+    }
+
+    /// @brief Returns the copy of the context for the Allocation engine.
+    AllocEngine::ClientContext4Ptr getContext() const {
+        return (context_);
+    }
+
+private:
+
+    /// @brief Copies default parameters from client's to server's message
+    ///
+    /// Some fields are copied from client's message into server's response,
+    /// e.g. client HW address, number of hops, transaction-id etc.
+    ///
+    /// @warning This message is called internally by @c initResponse and
+    /// thus it doesn't check if the resp_ value has been initialized. The
+    /// calling method is responsible for making sure that @c resp_ is
+    /// not NULL.
+    void copyDefaultFields();
+
+    /// @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 Pointer to the DHCPv4 message to be sent to the client.
+    Pkt4Ptr resp_;
+    /// @brief Context for use with allocation engine.
+    AllocEngine::ClientContext4Ptr context_;
+};
+
+/// @brief Type representing the pointer to the @c Dhcpv4Exchange.
+typedef boost::shared_ptr<Dhcpv4Exchange> Dhcpv4ExchangePtr;
+
+
 /// @brief DHCPv4 server service.
 ///
 /// This singleton class represents DHCPv4 server. It contains all
@@ -269,15 +362,15 @@ protected:
     bool acceptServerId(const Pkt4Ptr& pkt) const;
     //@}
 
-    /// @brief verifies if specified packet meets RFC requirements
+    /// @brief Verifies if specified packet meets RFC requirements
     ///
     /// Checks if mandatory option is really there, that forbidden option
     /// is not there, and that client-id or server-id appears only once.
     ///
-    /// @param pkt packet to be checked
+    /// @param query Pointer to the client's message.
     /// @param serverid expectation regarding server-id option
     /// @throw RFCViolation if any issues are detected
-    static void sanityCheck(const Pkt4Ptr& pkt, RequirementLevel serverid);
+    static void sanityCheck(const Pkt4Ptr& query, RequirementLevel serverid);
 
     /// @brief Processes incoming DISCOVER and returns response.
     ///
@@ -322,23 +415,14 @@ protected:
     /// @param inform message received from client
     Pkt4Ptr processInform(Pkt4Ptr& inform);
 
-    /// @brief Copies default parameters from client's to server's message
-    ///
-    /// Some fields are copied from client's message into server's response,
-    /// e.g. client HW address, number of hops, transaction-id etc.
-    ///
-    /// @param question any message sent by client
-    /// @param answer any message server is going to send as response
-    void copyDefaultFields(const Pkt4Ptr& question, Pkt4Ptr& answer);
-
     /// @brief Appends options requested by client.
     ///
     /// This method assigns options that were requested by client
     /// (sent in PRL) or are enforced by server.
     ///
-    /// @param question DISCOVER or REQUEST message from a client.
-    /// @param msg outgoing message (options will be added here)
-    void appendRequestedOptions(const Pkt4Ptr& question, Pkt4Ptr& msg);
+    /// @param ex The exchange holding both the client's message and the
+    /// server's response.
+    void appendRequestedOptions(Dhcpv4Exchange& ex);
 
     /// @brief Appends requested vendor options as requested by client.
     ///
@@ -348,9 +432,9 @@ protected:
     /// options, each with unique vendor-id). Vendor options are requested
     /// using separate options within their respective vendor-option spaces.
     ///
-    /// @param question DISCOVER or REQUEST message from a client.
-    /// @param answer outgoing message (options will be added here)
-    void appendRequestedVendorOptions(const Pkt4Ptr& question, Pkt4Ptr& answer);
+    /// @param ex The exchange holding both the client's message and the
+    /// server's response.
+    void appendRequestedVendorOptions(Dhcpv4Exchange& ex);
 
     /// @brief Assigns a lease and appends corresponding options
     ///
@@ -358,28 +442,27 @@ protected:
     /// client and assigning it. Options corresponding to the lease
     /// are added to specific message.
     ///
-    /// @param question DISCOVER or REQUEST message from client
-
-    /// @param answer OFFER or ACK/NAK message (lease options will be
-    /// added here)
-    /// 
-    /// This method may reset the @c answer shared pointer to indicate
-    /// that the response should not be sent to the client. The caller
-    /// must check if the @c answer is null after calling this method.
-    void assignLease(const Pkt4Ptr& question, Pkt4Ptr& answer);
+    /// This method may reset the pointer to the response in the @c ex object
+    /// to indicate that the response should not be sent to the client.
+    /// The caller must check if the response is is null after calling
+    /// this method.
+    ///
+    /// The response type in the @c ex object may be set to DHCPACK or DHCPNAK.
+    ///
+    /// @param ex DHCPv4 exchange holding the client's message to be checked.
+    void assignLease(Dhcpv4Exchange& ex);
 
     /// @brief Append basic options if they are not present.
     ///
     /// This function adds the following basic options if they
-    /// are not yet added to the message:
+    /// are not yet added to the response message:
     /// - Subnet Mask,
     /// - Router,
     /// - Name Server,
     /// - Domain Name.
     ///
-    /// @param question DISCOVER or REQUEST message from a client.
-    /// @param msg the message to add options to.
-    void appendBasicOptions(const Pkt4Ptr& question, Pkt4Ptr& msg);
+    /// @param ex DHCPv4 exchange holding the client's message to be checked.
+    void appendBasicOptions(Dhcpv4Exchange& ex);
 
     /// @brief Processes Client FQDN and Hostname Options sent by a client.
     ///
@@ -416,9 +499,9 @@ protected:
     /// This function does not throw. It simply logs the debug message if the
     /// processing of the FQDN or Hostname failed.
     ///
-    /// @param query A DISCOVER or REQUEST message from a client.
-    /// @param [out] answer A response message to be sent to a client.
-    void processClientName(const Pkt4Ptr& query, Pkt4Ptr& answer);
+    /// @param ex The exchange holding both the client's message and the
+    /// server's response.
+    void processClientName(Dhcpv4Exchange& ex);
 
     /// @brief this is a prefix added to the contend of vendor-class option
     ///
@@ -437,10 +520,9 @@ private:
     /// the FQDN option to be sent back to the client in the server's
     /// response.
     ///
-    /// @param fqdn An DHCPv4 Client FQDN %Option sent by a client.
-    /// @param [out] answer A response message to be sent to a client.
-    void processClientFqdnOption(const Option4ClientFqdnPtr& fqdn,
-                                 Pkt4Ptr& answer);
+    /// @param ex The exchange holding both the client's message and the
+    /// server's response.
+    void processClientFqdnOption(Dhcpv4Exchange& ex);
 
     /// @brief Process Hostname %Option sent by a client.
     ///
@@ -450,11 +532,9 @@ private:
     /// prepare the Hostname option to be sent back to the client in the
     /// server's response.
     ///
-    /// @param opt_hostname An @c OptionString object encapsulating the Hostname
-    /// %Option.
-    /// @param [out] answer A response message to be sent to a client.
-    void processHostnameOption(const OptionStringPtr& opt_hostname,
-                               Pkt4Ptr& answer);
+    /// @param ex The exchange holding both the client's message and the
+    /// server's response.
+    void processHostnameOption(Dhcpv4Exchange& ex);
 
 protected:
 
@@ -499,17 +579,6 @@ protected:
     /// @param reply server's response (ACK or NAK)
     void renewLease(const Pkt4Ptr& renew, Pkt4Ptr& reply);
 
-    /// @brief Appends default options to a message
-    ///
-    /// Currently it is only a Message Type option. This function does not add
-    /// the Server Identifier option as this option must be added using
-    /// @c Dhcpv4Srv::appendServerID.
-    ///
-    ///
-    /// @param msg message object (options will be added to it)
-    /// @param msg_type specifies message type
-    void appendDefaultOptions(Pkt4Ptr& msg, uint8_t msg_type);
-
     /// @brief Adds server identifier option to the server's response.
     ///
     /// This method adds a server identifier to the DHCPv4 message. It expects
@@ -527,9 +596,9 @@ protected:
     /// @note This method is static because it is not dependent on the class
     /// state.
     ///
-    /// @param [out] response DHCPv4 message to which the server identifier
-    /// option should be added.
-    static void appendServerID(const Pkt4Ptr& response);
+    /// @param ex The exchange holding both the client's message and the
+    /// server's response.
+    static void appendServerID(Dhcpv4Exchange& ex);
 
     /// @brief Set IP/UDP and interface parameters for the DHCPv4 response.
     ///
@@ -561,7 +630,10 @@ protected:
     ///
     /// @note This method is static because it is not dependent on the class
     /// state.
-    static void adjustIfaceData(const Pkt4Ptr& query, const Pkt4Ptr& response);
+    ///
+    /// @param ex The exchange holding both the client's message and the
+    /// server's response.
+    static void adjustIfaceData(Dhcpv4Exchange& ex);
 
     /// @brief Sets remote addresses for outgoing packet.
     ///
@@ -579,11 +651,9 @@ protected:
     /// @note This method is static because it is not dependent on the class
     /// state.
     ///
-    /// @param question instance of a packet received by a server.
-    /// @param [out] response response packet which addresses are to be
-    /// adjusted.
-    static void adjustRemoteAddr(const Pkt4Ptr& question,
-                                 const Pkt4Ptr& response);
+    /// @param ex The exchange holding both the client's message and the
+    /// server's response.
+    static void adjustRemoteAddr(Dhcpv4Exchange& ex);
 
     /// @brief converts server-id to text
     /// Converts content of server-id option to a text representation, e.g.
@@ -617,9 +687,9 @@ protected:
 
     /// @brief Selects a subnet for a given client's packet.
     ///
-    /// @param question client's message
+    /// @param query client's message
     /// @return selected subnet (or NULL if no suitable subnet was found)
-    isc::dhcp::Subnet4Ptr selectSubnet(const Pkt4Ptr& question) const;
+    isc::dhcp::Subnet4Ptr selectSubnet(const Pkt4Ptr& query) const;
 
     /// indicates if shutdown is in progress. Setting it to true will
     /// initiate server shutdown procedure.
@@ -661,12 +731,21 @@ protected:
 
     /// @brief Performs packet processing specific to a class
     ///
-    /// This processing is a likely candidate to be pushed into hooks.
+    /// If the selected subnet, query or response in the @c ex object is NULL
+    /// this method returns immediately and returns true.
     ///
-    /// @param query incoming client's packet
-    /// @param rsp server's response
+    /// @note This processing is a likely candidate to be pushed into hooks.
+    ///
+    /// @param ex The exchange holding both the client's message and the
+    /// server's response.
     /// @return true if successful, false otherwise (will prevent sending response)
-    bool classSpecificProcessing(const Pkt4Ptr& query, const Pkt4Ptr& rsp);
+    bool classSpecificProcessing(const Dhcpv4Exchange& ex);
+
+    /// @brief Allocation Engine.
+    /// Pointer to the allocation engine that we are currently using
+    /// It must be a pointer, because we will support changing engines
+    /// during normal operation (e.g. to use different allocators)
+    boost::shared_ptr<AllocEngine> alloc_engine_;
 
 private:
 
@@ -676,11 +755,6 @@ private:
     /// @return Option that contains netmask information
     static OptionPtr getNetmaskOption(const Subnet4Ptr& subnet);
 
-    /// @brief Allocation Engine.
-    /// Pointer to the allocation engine that we are currently using
-    /// It must be a pointer, because we will support changing engines
-    /// during normal operation (e.g. to use different allocators)
-    boost::shared_ptr<AllocEngine> alloc_engine_;
 
     uint16_t port_;  ///< UDP port number on which server listens.
     bool use_bcast_; ///< Should broadcast be enabled on sockets (if true).

+ 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

+ 36 - 23
src/bin/dhcp4/tests/dhcp4_srv_unittest.cc

@@ -86,10 +86,10 @@ TEST_F(Dhcpv4SrvTest, adjustIfaceDataRelay) {
     req->setIface("eth1");
     req->setIndex(1);
 
-    // Create a response packet. Assume that the new lease have
-    // been created and new address allocated. This address is
-    // stored in yiaddr field.
-    boost::shared_ptr<Pkt4> resp(new Pkt4(DHCPOFFER, 1234));
+    // Create the exchange using the req.
+    Dhcpv4Exchange ex = createExchange(req);
+
+    Pkt4Ptr resp = ex.getResponse();
     resp->setYiaddr(IOAddress("192.0.1.100"));
     // Clear the remote address.
     resp->setRemoteAddr(IOAddress("0.0.0.0"));
@@ -97,7 +97,7 @@ TEST_F(Dhcpv4SrvTest, adjustIfaceDataRelay) {
     resp->setHops(req->getHops());
 
     // This function never throws.
-    ASSERT_NO_THROW(NakedDhcpv4Srv::adjustIfaceData(req, resp));
+    ASSERT_NO_THROW(NakedDhcpv4Srv::adjustIfaceData(ex));
 
     // Now the destination address should be relay's address.
     EXPECT_EQ("192.0.1.1", resp->getRemoteAddr().toText());
@@ -123,7 +123,7 @@ TEST_F(Dhcpv4SrvTest, adjustIfaceDataRelay) {
     // Clear remote address.
     resp->setRemoteAddr(IOAddress("0.0.0.0"));
 
-    ASSERT_NO_THROW(NakedDhcpv4Srv::adjustIfaceData(req, resp));
+    ASSERT_NO_THROW(NakedDhcpv4Srv::adjustIfaceData(ex));
 
     // Response should be sent back to the relay address.
     EXPECT_EQ("192.0.1.50", resp->getRemoteAddr().toText());
@@ -163,8 +163,10 @@ TEST_F(Dhcpv4SrvTest, adjustIfaceDataRenew) {
     req->setIface("eth1");
     req->setIndex(1);
 
-    // Create a response.
-    boost::shared_ptr<Pkt4> resp(new Pkt4(DHCPOFFER, 1234));
+    // Create the exchange using the req.
+    Dhcpv4Exchange ex = createExchange(req);
+    Pkt4Ptr resp = ex.getResponse();
+
     // Let's extend the lease for the client in such a way that
     // it will actually get different address. The response
     // should not be sent to this address but rather to ciaddr
@@ -175,7 +177,7 @@ TEST_F(Dhcpv4SrvTest, adjustIfaceDataRenew) {
     // Copy hops value from the query.
     resp->setHops(req->getHops());
 
-    ASSERT_NO_THROW(NakedDhcpv4Srv::adjustIfaceData(req, resp));
+    ASSERT_NO_THROW(NakedDhcpv4Srv::adjustIfaceData(ex));
 
     // Check that server responds to ciaddr
     EXPECT_EQ("192.0.1.15", resp->getRemoteAddr().toText());
@@ -225,8 +227,9 @@ TEST_F(Dhcpv4SrvTest, adjustIfaceDataSelect) {
     req->setIface("eth1");
     req->setIndex(1);
 
-    // Create a response.
-    boost::shared_ptr<Pkt4> resp(new Pkt4(DHCPOFFER, 1234));
+    // Create the exchange using the req.
+    Dhcpv4Exchange ex = createExchange(req);
+    Pkt4Ptr resp = ex.getResponse();
     // Assign some new address for this client.
     resp->setYiaddr(IOAddress("192.0.1.13"));
     // Clear the remote address.
@@ -247,7 +250,7 @@ TEST_F(Dhcpv4SrvTest, adjustIfaceDataSelect) {
     // are zero and client has just got new lease, the assigned address is
     // carried in yiaddr. In order to send this address to the client,
     // server must broadcast its response.
-    ASSERT_NO_THROW(NakedDhcpv4Srv::adjustIfaceData(req, resp));
+    ASSERT_NO_THROW(NakedDhcpv4Srv::adjustIfaceData(ex));
 
     // Check that the response is sent to broadcast address as the
     // server doesn't have capability to respond directly.
@@ -276,7 +279,7 @@ TEST_F(Dhcpv4SrvTest, adjustIfaceDataSelect) {
 
     // Now we expect that the server will send its response to the
     // address assigned for the client.
-    ASSERT_NO_THROW(NakedDhcpv4Srv::adjustIfaceData(req, resp));
+    ASSERT_NO_THROW(NakedDhcpv4Srv::adjustIfaceData(ex));
 
     EXPECT_EQ("192.0.1.13", resp->getRemoteAddr().toText());
 }
@@ -308,15 +311,17 @@ TEST_F(Dhcpv4SrvTest, adjustIfaceDataBroadcast) {
     // Let's set the broadcast flag.
     req->setFlags(Pkt4::FLAG_BROADCAST_MASK);
 
-    // Create a response.
-    boost::shared_ptr<Pkt4> resp(new Pkt4(DHCPOFFER, 1234));
+    // Create the exchange using the req.
+    Dhcpv4Exchange ex = createExchange(req);
+    Pkt4Ptr resp = ex.getResponse();
+
     // Assign some new address for this client.
     resp->setYiaddr(IOAddress("192.0.1.13"));
 
     // Clear the remote address.
     resp->setRemoteAddr(IOAddress("0.0.0.0"));
 
-    ASSERT_NO_THROW(NakedDhcpv4Srv::adjustIfaceData(req, resp));
+    ASSERT_NO_THROW(NakedDhcpv4Srv::adjustIfaceData(ex));
 
     // Server must respond to broadcast address when client desired that
     // by setting the broadcast flag in its request.
@@ -340,6 +345,9 @@ TEST_F(Dhcpv4SrvTest, adjustIfaceDataBroadcast) {
 // This test verifies that exception is thrown of the invalid combination
 // of giaddr and hops is specified in a client's message.
 TEST_F(Dhcpv4SrvTest, adjustIfaceDataInvalid) {
+    IfaceMgrTestConfig test_config(true);
+    IfaceMgr::instance().openSockets4();
+
     boost::shared_ptr<Pkt4> req(new Pkt4(DHCPDISCOVER, 1234));
 
     // The hops and giaddr values are used to determine if the client's
@@ -360,27 +368,32 @@ TEST_F(Dhcpv4SrvTest, adjustIfaceDataInvalid) {
     req->setIface("eth0");
     req->setIndex(1);
 
-    // Create a response.
-    boost::shared_ptr<Pkt4> resp(new Pkt4(DHCPOFFER, 1234));
+    // Create the exchange using the req.
+    Dhcpv4Exchange ex = createExchange(req);
+    Pkt4Ptr resp = ex.getResponse();
+
     // Assign some new address for this client.
     resp->setYiaddr(IOAddress("192.0.2.13"));
 
     // Clear the remote address.
     resp->setRemoteAddr(IOAddress("0.0.0.0"));
 
-    EXPECT_THROW(NakedDhcpv4Srv::adjustIfaceData(req, resp), isc::BadValue);
+    EXPECT_THROW(NakedDhcpv4Srv::adjustIfaceData(ex), isc::BadValue);
 }
 
 // This test verifies that the server identifier option is appended to
 // a specified DHCPv4 message and the server identifier is correct.
 TEST_F(Dhcpv4SrvTest, appendServerID) {
-    Pkt4Ptr response(new Pkt4(DHCPDISCOVER, 1234));
+    Pkt4Ptr query(new Pkt4(DHCPDISCOVER, 1234));
+    Dhcpv4Exchange ex = createExchange(query);
+    Pkt4Ptr response = ex.getResponse();
+
     // Set a local address. It is required by the function under test
     // to create the Server Identifier option.
     response->setLocalAddr(IOAddress("192.0.3.1"));
 
     // Append the Server Identifier.
-    ASSERT_NO_THROW(NakedDhcpv4Srv::appendServerID(response));
+    ASSERT_NO_THROW(NakedDhcpv4Srv::appendServerID(ex));
 
     // Make sure that the option has been added.
     OptionPtr opt = response->getOption(DHO_DHCP_SERVER_IDENTIFIER);
@@ -783,10 +796,10 @@ TEST_F(Dhcpv4SrvTest, requestEchoClientId) {
     checkClientId(ack, clientid);
 
     CfgMgr::instance().echoClientId(false);
-    ack = srv.processDiscover(dis);
+    ack = srv.processRequest(dis);
 
     // Check if we get response at all
-    checkResponse(ack, DHCPOFFER, 1234);
+    checkResponse(ack, DHCPACK, 1234);
     checkClientId(ack, clientid);
 }
 

+ 4 - 0
src/bin/dhcp4/tests/dhcp4_test_utils.cc

@@ -589,6 +589,10 @@ Dhcpv4SrvTest::configure(const std::string& config, NakedDhcpv4Srv& srv,
     }
  }
 
+Dhcpv4Exchange
+Dhcpv4SrvTest::createExchange(const Pkt4Ptr& query) {
+    return (Dhcpv4Exchange(srv_.alloc_engine_, query, srv_.selectSubnet(query)));
+}
 
 
 }; // end of isc::dhcp::test namespace

+ 4 - 0
src/bin/dhcp4/tests/dhcp4_test_utils.h

@@ -205,6 +205,7 @@ public:
     using Dhcpv4Srv::selectSubnet;
     using Dhcpv4Srv::VENDOR_CLASS_PREFIX;
     using Dhcpv4Srv::shutdown_;
+    using Dhcpv4Srv::alloc_engine_;
 };
 
 class Dhcpv4SrvTest : public ::testing::Test {
@@ -390,6 +391,9 @@ public:
     void configure(const std::string& config, NakedDhcpv4Srv& srv,
                    const bool commit = true);
 
+    /// @brief Create @c Dhcpv4Exchange from client's query.
+    Dhcpv4Exchange createExchange(const Pkt4Ptr& query);
+
     /// @brief This function cleans up after the test.
     virtual void TearDown();
 

+ 307 - 11
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,89 @@ 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\","
+        "         \"hostname\":   \"unique-host.example.org\""
+        "       }"
+        "    ]"
+        " }],"
+        "\"dhcp-ddns\": {"
+            "\"enable-updates\": true,"
+            "\"qualifying-suffix\": \"\""
+        "}"
+    "}",
+    "{ \"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\","
+        "         \"hostname\":   \"foobar\""
+        "       }"
+        "    ]"
+        " }],"
+        "\"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();
     }
@@ -235,12 +307,13 @@ public:
             answer.reset(new Pkt4(DHCPACK, 1234));
 
         }
-        ASSERT_NO_THROW(srv_->processClientName(query, answer));
+        Dhcpv4Exchange ex = createExchange(query);
+        ASSERT_NO_THROW(srv_->processClientName(ex));
 
-        Option4ClientFqdnPtr fqdn = getClientFqdnOption(answer);
+        Option4ClientFqdnPtr fqdn = getClientFqdnOption(ex.getResponse());
         ASSERT_TRUE(fqdn);
 
-        checkFqdnFlags(answer, exp_flags);
+        checkFqdnFlags(ex.getResponse(), exp_flags);
 
         EXPECT_EQ(exp_domain_name, fqdn->getDomainName());
         EXPECT_EQ(exp_domain_type, fqdn->getDomainNameType());
@@ -284,9 +357,10 @@ public:
             answer.reset(new Pkt4(DHCPACK, 1234));
 
         }
-        srv_->processClientName(query, answer);
+        Dhcpv4Exchange ex = createExchange(query);
+        srv_->processClientName(ex);
 
-        OptionStringPtr hostname = getHostnameOption(answer);
+        OptionStringPtr hostname = getHostnameOption(ex.getResponse());
         return (hostname);
 
     }
@@ -381,10 +455,6 @@ public:
             }
         }
     }
-
-
-    NakedDhcpv4Srv* srv_;
-
 };
 
 // Test that the exception is thrown if lease pointer specified as the argument
@@ -1002,5 +1072,231 @@ 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());
+
+    {
+        SCOPED_TRACE("Verify the correctness of the NCR for the"
+                     "unique-host.example.org");
+
+        // 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());
+    // Make sure that DDNS is enabled.
+    ASSERT_TRUE(CfgMgr::instance().ddnsEnabled());
+    ASSERT_NO_THROW(client.getServer()->startD2());
+
+    // 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.fake-suffix.isc.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.fake-suffix.isc.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());
+
+    {
+        SCOPED_TRACE("Verify the correctness of the CHG_REMOVE NCR for the "
+                     "unique-host.example.org");
+
+        verifyNameChangeRequest(isc::dhcp_ddns::CHG_REMOVE, true, true,
+                                resp->getYiaddr().toText(),
+                                "unique-host.example.org.",
+                                "000001ACB52196C8F3BCC1DF3BA1F40BAC39BF23"
+                                "0D280858B1ED7696E174C4479E3372",
+                                time(NULL), subnet_->getValid(), true);
+    }
+
+    {
+        SCOPED_TRACE("Verify the correctness of the CHG_ADD NCR for the "
+                     "foobar.fake-suffix.isc.org");
+
+        verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true,
+                                resp->getYiaddr().toText(),
+                                "foobar.fake-suffix.isc.org.",
+                                "0000017C29B3C236344924E448E247F3FD56C7E9"
+                                "167B3397B1305FB664C160B967CE1F",
+                                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());
+    {
+        SCOPED_TRACE("Verify the correctness of the NCR for the"
+                     "unique-host.example.org");
+
+        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());
+    // Make sure that DDNS is enabled.
+    ASSERT_TRUE(CfgMgr::instance().ddnsEnabled());
+    ASSERT_NO_THROW(client.getServer()->startD2());
+
+    // 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.fake-suffix.isc.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.fake-suffix.isc.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());
+    {
+        SCOPED_TRACE("Verify the correctness of the CHG_REMOVE NCR for the "
+                     "unique-host.example.org");
+
+        verifyNameChangeRequest(isc::dhcp_ddns::CHG_REMOVE, true, true,
+                                resp->getYiaddr().toText(),
+                                "unique-host.example.org.",
+                                "000001ACB52196C8F3BCC1DF3BA1F40BAC39BF23"
+                                "0D280858B1ED7696E174C4479E3372",
+                                time(NULL), subnet_->getValid(), true);
+    }
+
+    {
+        SCOPED_TRACE("Verify the correctness of the CHG_ADD NCR for the "
+                     "foobar.fake-suffix.isc.org");
+
+        verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true,
+                                resp->getYiaddr().toText(),
+                                "foobar.fake-suffix.isc.org.",
+                                "0000017C29B3C236344924E448E247F3FD56C7E9"
+                                "167B3397B1305FB664C160B967CE1F",
+                                time(NULL), subnet_->getValid(), true);
+    }
+}
+
 
 } // end of anonymous namespace

+ 1 - 0
src/bin/dhcp4/tests/test_config.json

@@ -0,0 +1 @@
+{}

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

@@ -1215,6 +1215,26 @@ addressReserved(const IOAddress& address, const AllocEngine::ClientContext4& ctx
     return (false);
 }
 
+/// @brief Check if the context contains the reservation for the
+/// IPv4 address.
+///
+/// This convenience function checks if the context contains the reservation
+/// for the IPv4 address. Note that some reservations may not assign a
+/// static IPv4 address to the clients, but may rather reserve a hostname.
+/// Allocation engine should check if the existing reservation is made
+/// for the IPv4 address and if it is not, allocate the address from the
+/// dynamic pool. The allocation engine uses this function to check if
+/// the reservation is made for the IPv4 address.
+///
+/// @param ctx Client context holding the data extracted from the
+/// client's message.
+///
+/// @return true if the context contains the reservation for the IPv4 address.
+bool
+hasAddressReservation(const AllocEngine::ClientContext4& ctx) {
+    return (ctx.host_ && !ctx.host_->getIPv4Reservation().isV4Zero());
+}
+
 } // end of anonymous namespace
 
 namespace isc {
@@ -1228,7 +1248,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,
@@ -1284,8 +1304,6 @@ AllocEngine::allocateLease4(ClientContext4& ctx) {
             isc_throw(BadValue, "HWAddr must be defined");
         }
 
-        ctx.host_ = HostMgr::instance().get4(ctx.subnet_->getID(), ctx.hwaddr_);
-
         new_lease = ctx.fake_allocation_ ? discoverLease4(ctx) : requestLease4(ctx);
         if (!new_lease) {
             // Unable to allocate an address, return an empty lease.
@@ -1300,6 +1318,26 @@ AllocEngine::allocateLease4(ClientContext4& ctx) {
     return (new_lease);
 }
 
+void
+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_);
+        }
+    }
+}
+
 Lease4Ptr
 AllocEngine::discoverLease4(AllocEngine::ClientContext4& ctx) {
     // Obtain the sole instance of the LeaseMgr.
@@ -1319,7 +1357,7 @@ AllocEngine::discoverLease4(AllocEngine::ClientContext4& ctx) {
 
     // Check if there is a reservation for the client. If there is, we want to
     // assign the reserved address, rather than any other one.
-    if (ctx.host_) {
+    if (hasAddressReservation(ctx)) {
         // If the client doesn't have a lease or the leased address is different
         // than the reserved one then let's try to allocate the reserved address.
         // Otherwise the address that the client has is the one for which it
@@ -1418,7 +1456,7 @@ AllocEngine::requestLease4(AllocEngine::ClientContext4& ctx) {
             return (Lease4Ptr());
         }
 
-    } else if (ctx.host_) {
+    } else if (hasAddressReservation(ctx)) {
         // The client hasn't specified an address to allocate, so the
         // allocation engine needs to find an appropriate address.
         // If there is a reservation for the client, let's try to
@@ -1442,7 +1480,8 @@ AllocEngine::requestLease4(AllocEngine::ClientContext4& ctx) {
         // address it is possible that the client was offered this different
         // address because the reserved address is in use. We will have to
         // check if the address is in use.
-        if (ctx.host_ && (ctx.host_->getIPv4Reservation() != ctx.requested_address_)) {
+        if (hasAddressReservation(ctx) &&
+            (ctx.host_->getIPv4Reservation() != ctx.requested_address_)) {
             existing = LeaseMgrFactory::instance().getLease4(ctx.host_->getIPv4Reservation());
             // If the reserved address is not in use, i.e. the lease doesn't
             // exist or is expired, and the client is requesting a different
@@ -1456,7 +1495,8 @@ AllocEngine::requestLease4(AllocEngine::ClientContext4& ctx) {
         // The use of the out-of-pool addresses is only allowed when the requested
         // address is reserved for the client. If the address is not reserved one
         // and it doesn't belong to the dynamic pool, do not allocate it.
-        if ((!ctx.host_ || (ctx.host_->getIPv4Reservation() != ctx.requested_address_)) &&
+        if ((!hasAddressReservation(ctx) ||
+             (ctx.host_->getIPv4Reservation() != ctx.requested_address_)) &&
             !ctx.subnet_->inPool(Lease4::TYPE_V4, ctx.requested_address_)) {
             return (Lease4Ptr());
         }

+ 20 - 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,
@@ -767,6 +767,9 @@ public:
 
     };
 
+    /// @brief Pointer to the @c ClientContext4.
+    typedef boost::shared_ptr<ClientContext4> ClientContext4Ptr;
+
     /// @brief Returns IPv4 lease.
     ///
     /// This method finds a lease for a client using the following algorithm:
@@ -865,6 +868,8 @@ public:
     /// - @ref ClientContext4::fake_allocation_ Is this real i.e. REQUEST (false)
     ///      or just picking an address for DISCOVER that is not really
     ///      allocated (true)
+    /// - @ref ClientContext4::host_ Pointer to the object representing the
+    //       static reservations (host reservations) for the client.
     /// - @ref ClientContext4::callout_handle_ A callout handle (used in hooks).
     ///      A lease callouts will be executed if this parameter is passed.
     /// - @ref ClientContext4::old_lease_ [out] Holds the pointer to a previous
@@ -874,6 +879,16 @@ public:
     /// @return Allocated IPv4 lease (or NULL if allocation failed).
     Lease4Ptr allocateLease4(ClientContext4& ctx);
 
+    /// @brief Attempts to find the host reservation for the client.
+    ///
+    /// This method attempts to find the host reservation for the client. If
+    /// found, it is set in the @c ctx.host_. If the host reservations are
+    /// disabled for the particular subnet or the reservation is not found
+    /// for the client, the @c ctx.host_ is set to NULL.
+    ///
+    /// @param ctx Client context holding various information about the client.
+    static void findReservation(ClientContext4& ctx);
+
 private:
 
     /// @brief Offers the lease.
@@ -1069,6 +1084,9 @@ private:
                                  ClientContext4& ctx) const;
 };
 
+/// @brief A pointer to the @c AllocEngine object.
+typedef boost::shared_ptr<AllocEngine> AllocEnginePtr;
+
 }; // namespace isc::dhcp
 }; // namespace isc
 

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

@@ -83,15 +83,17 @@ Host::Host(const uint8_t* identifier, const size_t identifier_len,
            const std::string& dhcp6_client_classes)
     : hw_address_(), duid_(), ipv4_subnet_id_(ipv4_subnet_id),
       ipv6_subnet_id_(ipv6_subnet_id),
-      ipv4_reservation_(asiolink::IOAddress("0.0.0.0")),
-       hostname_(hostname), dhcp4_client_classes_(dhcp4_client_classes),
-       dhcp6_client_classes_(dhcp6_client_classes) {
+      ipv4_reservation_(asiolink::IOAddress::IPV4_ZERO_ADDRESS()),
+      hostname_(hostname), dhcp4_client_classes_(dhcp4_client_classes),
+      dhcp6_client_classes_(dhcp6_client_classes) {
 
     // Initialize HWAddr or DUID
     setIdentifier(identifier, identifier_len, identifier_type);
 
-    // Validate and set IPv4 address reservation.
-    setIPv4Reservation(ipv4_reservation);
+    if (!ipv4_reservation.isV4Zero()) {
+        // Validate and set IPv4 address reservation.
+        setIPv4Reservation(ipv4_reservation);
+    }
 }
 
 Host::Host(const std::string& identifier, const std::string& identifier_name,
@@ -102,15 +104,17 @@ Host::Host(const std::string& identifier, const std::string& identifier_name,
            const std::string& dhcp6_client_classes)
     : hw_address_(), duid_(), ipv4_subnet_id_(ipv4_subnet_id),
       ipv6_subnet_id_(ipv6_subnet_id),
-      ipv4_reservation_(asiolink::IOAddress("0.0.0.0")),
+      ipv4_reservation_(asiolink::IOAddress::IPV4_ZERO_ADDRESS()),
       hostname_(hostname), dhcp4_client_classes_(dhcp4_client_classes),
       dhcp6_client_classes_(dhcp6_client_classes) {
 
     // Initialize HWAddr or DUID
     setIdentifier(identifier, identifier_name);
 
-    // Validate and set IPv4 address reservation.
-    setIPv4Reservation(ipv4_reservation);
+    if (!ipv4_reservation.isV4Zero()) {
+        // Validate and set IPv4 address reservation.
+        setIPv4Reservation(ipv4_reservation);
+    }
 }
 
 const std::vector<uint8_t>&
@@ -172,10 +176,17 @@ Host::setIPv4Reservation(const asiolink::IOAddress& address) {
     if (!address.isV4()) {
         isc_throw(isc::BadValue, "address '" << address << "' is not a valid"
                   " IPv4 address");
+    } else if (address.isV4Zero() || address.isV4Bcast()) {
+        isc_throw(isc::BadValue, "must not make reservation for the '"
+                  << address << "' address");
     }
     ipv4_reservation_ = address;
 }
 
+void
+Host::removeIPv4Reservation() {
+    ipv4_reservation_ = asiolink::IOAddress::IPV4_ZERO_ADDRESS();
+}
 
 void
 Host::addReservation(const IPv6Resrv& reservation) {

+ 7 - 1
src/lib/dhcpsrv/host.h

@@ -337,9 +337,15 @@ public:
     ///
     /// @param address Address to be reserved for the client.
     ///
-    /// @throw isc::BadValue if the provided address is not an IPv4 address.
+    /// @throw isc::BadValue if the provided address is not an IPv4 address,
+    /// is a 0 address or broadcast address.
     void setIPv4Reservation(const asiolink::IOAddress& address);
 
+    /// @brief Removes the IPv4 reservation.
+    ///
+    /// Sets the IPv4 reserved address to 0.
+    void removeIPv4Reservation();
+
     /// @brief Returns reserved IPv4 address.
     ///
     /// @return IPv4 address or 0.0.0.0 if no IPv4 reservation specified.

+ 135 - 4
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);
@@ -598,6 +598,7 @@ TEST_F(AllocEngine4Test, reservedAddressNoHint) {
     AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_,
                                     IOAddress("0.0.0.0"), false, false,
                                     "", false);
+    AllocEngine::findReservation(ctx);
     Lease4Ptr lease = engine.allocateLease4(ctx);
 
     ASSERT_TRUE(lease);
@@ -634,6 +635,7 @@ TEST_F(AllocEngine4Test,reservedAddressNoHintFakeAllocation) {
     AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_,
                                     IOAddress("0.0.0.0"), false, false,
                                     "", true);
+    AllocEngine::findReservation(ctx);
     Lease4Ptr lease = engine.allocateLease4(ctx);
 
     ASSERT_TRUE(lease);
@@ -671,6 +673,7 @@ TEST_F(AllocEngine4Test, reservedAddressHint) {
     AllocEngine::ClientContext4 ctx1(subnet_, clientid_, hwaddr_,
                                     IOAddress("192.0.2.234"), false, false,
                                     "", false);
+    AllocEngine::findReservation(ctx1);
     Lease4Ptr lease = engine.allocateLease4(ctx1);
 
     // The client requested a different address than reserved, so
@@ -683,6 +686,7 @@ TEST_F(AllocEngine4Test, reservedAddressHint) {
     AllocEngine::ClientContext4 ctx2(subnet_, clientid_, hwaddr_,
                                     IOAddress("192.0.2.123"), false, false,
                                     "", false);
+    AllocEngine::findReservation(ctx2);
     lease = engine.allocateLease4(ctx2);
     ASSERT_TRUE(lease);
     EXPECT_EQ("192.0.2.123", lease->addr_.toText());
@@ -718,6 +722,7 @@ TEST_F(AllocEngine4Test, reservedAddressHintFakeAllocation) {
     AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_,
                                     IOAddress("192.0.2.234"), false, false,
                                     "", true);
+    AllocEngine::findReservation(ctx);
     Lease4Ptr lease = engine.allocateLease4(ctx);
 
     ASSERT_TRUE(lease);
@@ -761,6 +766,7 @@ TEST_F(AllocEngine4Test, reservedAddressExistingLease) {
     AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_,
                                     IOAddress("192.0.2.123"), false, false,
                                     "", false);
+    AllocEngine::findReservation(ctx);
     Lease4Ptr allocated_lease = engine.allocateLease4(ctx);
 
     ASSERT_TRUE(allocated_lease);
@@ -808,6 +814,7 @@ TEST_F(AllocEngine4Test, reservedAddressHijacked) {
     AllocEngine::ClientContext4 ctx1(subnet_, clientid_, hwaddr_,
                                     IOAddress("192.0.2.123"), false, false,
                                     "", false);
+    AllocEngine::findReservation(ctx1);
     Lease4Ptr allocated_lease = engine.allocateLease4(ctx1);
     // The lease is allocated to someone else, so the allocation should not
     // succeed.
@@ -825,6 +832,7 @@ TEST_F(AllocEngine4Test, reservedAddressHijacked) {
     AllocEngine::ClientContext4 ctx2(subnet_, clientid_, hwaddr_,
                                     IOAddress("0.0.0.0"), false, false,
                                     "", false);
+    AllocEngine::findReservation(ctx2);
     allocated_lease = engine.allocateLease4(ctx2);
     ASSERT_FALSE(allocated_lease);
     EXPECT_FALSE(ctx2.old_lease_);
@@ -863,6 +871,7 @@ TEST_F(AllocEngine4Test, reservedAddressHijackedFakeAllocation) {
     AllocEngine::ClientContext4 ctx1(subnet_, clientid_, hwaddr_,
                                     IOAddress("192.0.2.123"), false, false,
                                     "", true);
+    AllocEngine::findReservation(ctx1);
     Lease4Ptr allocated_lease = engine.allocateLease4(ctx1);
 
     // The allocation engine should return a lease but for a different address
@@ -878,6 +887,7 @@ TEST_F(AllocEngine4Test, reservedAddressHijackedFakeAllocation) {
     AllocEngine::ClientContext4 ctx2(subnet_, clientid_, hwaddr_,
                                     IOAddress("0.0.0.0"), false, false,
                                     "", true);
+    AllocEngine::findReservation(ctx2);
     allocated_lease = engine.allocateLease4(ctx2);
 
     ASSERT_TRUE(allocated_lease);
@@ -918,6 +928,7 @@ TEST_F(AllocEngine4Test, reservedAddressExistingLeaseInvalidHint) {
     AllocEngine::ClientContext4 ctx1(subnet_, clientid_, hwaddr_,
                                     IOAddress("192.0.2.102"), false, false,
                                     "", false);
+    AllocEngine::findReservation(ctx1);
     Lease4Ptr allocated_lease = engine.allocateLease4(ctx1);
     ASSERT_FALSE(allocated_lease);
     ASSERT_FALSE(ctx1.old_lease_);
@@ -968,6 +979,7 @@ TEST_F(AllocEngine4Test, reservedAddressExistingLeaseFakeAllocation) {
     AllocEngine::ClientContext4 ctx1(subnet_, clientid_, hwaddr_,
                                     IOAddress("192.0.2.102"), false, false,
                                     "", true);
+    AllocEngine::findReservation(ctx1);
     Lease4Ptr allocated_lease = engine.allocateLease4(ctx1);
 
     // Server should offer a lease for a reserved address.
@@ -986,6 +998,7 @@ TEST_F(AllocEngine4Test, reservedAddressExistingLeaseFakeAllocation) {
     AllocEngine::ClientContext4 ctx2(subnet_, clientid_, hwaddr_,
                                     IOAddress("192.0.2.101"), false, false,
                                     "", true);
+    AllocEngine::findReservation(ctx2);
     allocated_lease = engine.allocateLease4(ctx2);
 
     // The server should offer the lease, but not for the address that
@@ -1026,6 +1039,7 @@ TEST_F(AllocEngine4Test, reservedAddressExistingLeaseNoHint) {
     AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_,
                                     IOAddress("0.0.0.0"), false, false,
                                     "", false);
+    AllocEngine::findReservation(ctx);
     Lease4Ptr allocated_lease = engine.allocateLease4(ctx);
 
     // The reserved address should be allocated.
@@ -1075,6 +1089,7 @@ TEST_F(AllocEngine4Test, reservedAddressExistingLeaseNoHintFakeAllocation) {
     AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_,
                                     IOAddress("0.0.0.0"), false, false,
                                     "", true);
+    AllocEngine::findReservation(ctx);
     Lease4Ptr allocated_lease = engine.allocateLease4(ctx);
 
     // The server should offer the reserved address.
@@ -1137,6 +1152,7 @@ TEST_F(AllocEngine4Test, reservedAddressConflictResolution) {
     AllocEngine::ClientContext4 ctx1(subnet_, ClientIdPtr(), hwaddr2_,
                                     IOAddress("192.0.2.101"), false, false,
                                     "", false);
+    AllocEngine::findReservation(ctx1);
     Lease4Ptr offered_lease = engine.allocateLease4(ctx1);
     ASSERT_FALSE(offered_lease);
 
@@ -1146,6 +1162,7 @@ TEST_F(AllocEngine4Test, reservedAddressConflictResolution) {
     AllocEngine::ClientContext4 ctx2(subnet_, clientid_, hwaddr_,
                                     IOAddress("192.0.2.101"), false, false,
                                     "", false);
+    AllocEngine::findReservation(ctx2);
     ASSERT_FALSE(engine.allocateLease4(ctx2));
 
     ASSERT_FALSE(ctx2.old_lease_);
@@ -1156,6 +1173,7 @@ TEST_F(AllocEngine4Test, reservedAddressConflictResolution) {
     AllocEngine::ClientContext4 ctx3(subnet_, clientid_, hwaddr_,
                                     IOAddress("192.0.2.101"), false, false,
                                     "", true);
+    AllocEngine::findReservation(ctx3);
     offered_lease = engine.allocateLease4(ctx3);
     ASSERT_TRUE(offered_lease);
     EXPECT_NE(offered_lease->addr_.toText(), "192.0.2.101");
@@ -1166,6 +1184,7 @@ TEST_F(AllocEngine4Test, reservedAddressConflictResolution) {
     AllocEngine::ClientContext4 ctx4(subnet_, clientid_, hwaddr_,
                                     offered_lease->addr_, false, false,
                                     "", false);
+    AllocEngine::findReservation(ctx4);
     Lease4Ptr allocated_lease = engine.allocateLease4(ctx4);
 
     ASSERT_TRUE(allocated_lease);
@@ -1176,6 +1195,7 @@ TEST_F(AllocEngine4Test, reservedAddressConflictResolution) {
     AllocEngine::ClientContext4 ctx5(subnet_, ClientIdPtr(), hwaddr2_,
                                      IOAddress("0.0.0.0"), false, false,
                                     "", true);
+    AllocEngine::findReservation(ctx5);
     offered_lease = engine.allocateLease4(ctx5);
 
     ASSERT_TRUE(offered_lease);
@@ -1210,6 +1230,7 @@ TEST_F(AllocEngine4Test, reservedAddressVsDynamicPool) {
     AllocEngine::ClientContext4 ctx(subnet_, ClientIdPtr(), hwaddr_,
                                      IOAddress("0.0.0.0"), false, false,
                                     "", false);
+    AllocEngine::findReservation(ctx);
     Lease4Ptr allocated_lease = engine.allocateLease4(ctx);
 
     ASSERT_TRUE(allocated_lease);
@@ -1236,7 +1257,8 @@ TEST_F(AllocEngine4Test, reservedAddressHintUsedByOtherClient) {
     // Different client is requesting this address.
     AllocEngine::ClientContext4 ctx1(subnet_, ClientIdPtr(), hwaddr_,
                                      IOAddress("192.0.2.100"), false, false,
-                                    "", false);
+                                     "", false);
+    AllocEngine::findReservation(ctx1);
     Lease4Ptr allocated_lease = engine.allocateLease4(ctx1);
 
     // The client should get no lease (DHCPNAK).
@@ -1246,7 +1268,8 @@ TEST_F(AllocEngine4Test, reservedAddressHintUsedByOtherClient) {
     // if is sending a DHCPDISCOVER (fake allocation is true).
     AllocEngine::ClientContext4 ctx2(subnet_, ClientIdPtr(), hwaddr_,
                                      IOAddress("192.0.2.100"), false, false,
-                                    "", true);
+                                     "", true);
+    AllocEngine::findReservation(ctx2);
     allocated_lease = engine.allocateLease4(ctx2);
 
     ASSERT_TRUE(allocated_lease);
@@ -1263,7 +1286,7 @@ TEST_F(AllocEngine4Test, reservedAddressShortPool) {
     // Create short pool with only one address.
     initSubnet(IOAddress("192.0.2.100"), IOAddress("192.0.2.100"));
     // Reserve the address for a different client.
-    HostPtr host(new Host(&hwaddr2_->hwaddr_[0], hwaddr_->hwaddr_.size(),
+    HostPtr host(new Host(&hwaddr2_->hwaddr_[0], hwaddr2_->hwaddr_.size(),
                           Host::IDENT_HWADDR, subnet_->getID(),
                           SubnetID(0), IOAddress("192.0.2.100")));
     CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
@@ -1274,6 +1297,7 @@ TEST_F(AllocEngine4Test, reservedAddressShortPool) {
     AllocEngine::ClientContext4 ctx1(subnet_, ClientIdPtr(), hwaddr_,
                                      IOAddress("0.0.0.0"), false, false,
                                      "", false);
+    AllocEngine::findReservation(ctx1);
     Lease4Ptr allocated_lease = engine.allocateLease4(ctx1);
 
     EXPECT_FALSE(allocated_lease);
@@ -1286,12 +1310,119 @@ TEST_F(AllocEngine4Test, reservedAddressShortPool) {
     AllocEngine::ClientContext4 ctx2(subnet_, ClientIdPtr(), hwaddr_,
                                      IOAddress("0.0.0.0"), false, false,
                                      "", false);
+    AllocEngine::findReservation(ctx2);
     allocated_lease = engine.allocateLease4(ctx2);
 
     ASSERT_TRUE(allocated_lease);
     EXPECT_EQ("192.0.2.100", allocated_lease->addr_.toText());
 }
 
+// This test checks that the AllocEngine allocates an address from the
+// dynamic pool if the client's reservation is made for a hostname but
+// not for an address.
+TEST_F(AllocEngine4Test, reservedHostname) {
+    AllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 100, false);
+
+    // Create a reservation for a hostname. Address is set to 0 which
+    // indicates that there is no reservation.
+    HostPtr host(new Host(&hwaddr_->hwaddr_[0], hwaddr_->hwaddr_.size(),
+                          Host::IDENT_HWADDR, subnet_->getID(),
+                          SubnetID(0), IOAddress::IPV4_ZERO_ADDRESS(),
+                          "foo.example.org"));
+    CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
+    CfgMgr::instance().commit();
+
+    // Try to allocate a lease.
+    AllocEngine::ClientContext4 ctx(subnet_, ClientIdPtr(), hwaddr_,
+                                    IOAddress::IOAddress("192.0.2.109"), false, false,
+                                    "foo.example.org", true);
+    AllocEngine::findReservation(ctx);
+    Lease4Ptr allocated_lease = engine.allocateLease4(ctx);
+    ASSERT_TRUE(allocated_lease);
+    ASSERT_FALSE(allocated_lease->addr_.isV4Zero());
+    ASSERT_EQ("192.0.2.109", allocated_lease->addr_.toText());
+
+    ctx.requested_address_ = allocated_lease->addr_;
+    ctx.fake_allocation_ = false;
+    allocated_lease = engine.allocateLease4(ctx);
+    ASSERT_TRUE(allocated_lease);
+    EXPECT_EQ("192.0.2.109", allocated_lease->addr_.toText());
+}
+
+// This test checks that the AllocEngine::findReservation method finds
+// and returns host reservation for the DHCPv4 client using the data from
+// the client context. If the host reservation can't be found, it sets
+// the value of NULL in the host_ field of the client context.
+TEST_F(AllocEngine4Test, findReservation) {
+    // Create the instance of the allocation engine.
+    AllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 100, false);
+
+    // Context is required to call the AllocEngine::findReservation.
+    AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_,
+                                    IOAddress("0.0.0.0"), false, false,
+                                    "", false);
+
+    // There is no reservation in the database so no host should be
+    // retruned.
+    ASSERT_NO_THROW(engine.findReservation(ctx));
+    EXPECT_FALSE(ctx.host_);
+
+    // Create a reservation for the client.
+    HostPtr host(new Host(&hwaddr_->hwaddr_[0], hwaddr_->hwaddr_.size(),
+                          Host::IDENT_HWADDR, subnet_->getID(),
+                          SubnetID(0), IOAddress("192.0.2.100")));
+    CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
+    CfgMgr::instance().commit();
+
+    // This time the reservation should be returned.
+    ASSERT_NO_THROW(engine.findReservation(ctx));
+    EXPECT_TRUE(ctx.host_);
+    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.
+    subnet_->setHostReservationMode(Subnet::HR_DISABLED);
+    ASSERT_NO_THROW(engine.findReservation(ctx));
+    EXPECT_FALSE(ctx.host_);
+
+    // Check the third possible reservation mode.
+    subnet_->setHostReservationMode(Subnet::HR_OUT_OF_POOL);
+    ASSERT_NO_THROW(engine.findReservation(ctx));
+    EXPECT_TRUE(ctx.host_);
+    EXPECT_EQ(ctx.host_->getIPv4Reservation(), host->getIPv4Reservation());
+
+    // This time use the client identifier to search for the host.
+    host.reset(new Host(&clientid_->getClientId()[0],
+                        clientid_->getClientId().size(),
+                        Host::IDENT_DUID, subnet_->getID(),
+                        SubnetID(0), IOAddress("192.0.2.101")));
+    CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
+    CfgMgr::instance().commit();
+
+    ASSERT_NO_THROW(engine.findReservation(ctx));
+    EXPECT_TRUE(ctx.host_);
+    EXPECT_EQ(ctx.host_->getIPv4Reservation(), host->getIPv4Reservation());
+
+    // Remove the subnet. Subnet id is required to find host reservations, so
+    // if it is set to NULL, no reservation should be returned
+    ctx.subnet_.reset();
+    ASSERT_NO_THROW(engine.findReservation(ctx));
+    EXPECT_FALSE(ctx.host_);
+
+    // The same if there is a mismatch of the subnet id between the reservation
+    // and the context.
+    ctx.subnet_ = subnet_;
+    host.reset(new Host(&hwaddr_->hwaddr_[0], hwaddr_->hwaddr_.size(),
+                        Host::IDENT_HWADDR, subnet_->getID() + 1,
+                        SubnetID(0), IOAddress("192.0.2.100")));
+    CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
+    CfgMgr::instance().commit();
+
+    ASSERT_NO_THROW(engine.findReservation(ctx));
+    EXPECT_FALSE(ctx.host_);
+}
+
 }; // namespace test
 }; // namespace dhcp
 }; // namespace isc

+ 25 - 1
src/lib/dhcpsrv/tests/host_reservation_parser_unittest.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -249,6 +249,30 @@ TEST_F(HostReservationParserTest, malformedAddress) {
     EXPECT_THROW(parser.build(config_element), DhcpConfigError);
 }
 
+// This test verifies that the configuration parser for host reservations
+// throws an exception when zero IP address is specified.
+TEST_F(HostReservationParserTest, zeroAddress) {
+    std::string config = "{ \"hw-address\": \"01:02:03:04:05:06\","
+        "\"ip-address\": \"0.0.0.0\" }";
+
+    ElementPtr config_element = Element::fromJSON(config);
+
+    HostReservationParser4 parser(SubnetID(10));
+    EXPECT_THROW(parser.build(config_element), DhcpConfigError);
+}
+
+// This test verifies that the configuration parser for host reservations
+// throws an exception when broadcast IP address is specified.
+TEST_F(HostReservationParserTest, bcastAddress) {
+    std::string config = "{ \"hw-address\": \"01:02:03:04:05:06\","
+        "\"ip-address\": \"255.255.255.255\" }";
+
+    ElementPtr config_element = Element::fromJSON(config);
+
+    HostReservationParser4 parser(SubnetID(10));
+    EXPECT_THROW(parser.build(config_element), DhcpConfigError);
+}
+
 // This test verfies that the parser can parse the IPv6 reservation entry for
 // which hw-address is a host identifier.
 TEST_F(HostReservationParserTest, dhcp6HWaddr) {

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

@@ -427,9 +427,20 @@ TEST(HostTest, setValues) {
     EXPECT_EQ("10.0.0.1", host->getIPv4Reservation().toText());
     EXPECT_EQ("other-host.example.org", host->getHostname());
 
+    // Remove IPv4 reservation.
+    host->removeIPv4Reservation();
+    EXPECT_EQ(IOAddress::IPV4_ZERO_ADDRESS(), host->getIPv4Reservation());
+
     // An IPv6 address can't be used for IPv4 reservations.
     EXPECT_THROW(host->setIPv4Reservation(IOAddress("2001:db8:1::1")),
                  isc::BadValue);
+    // Zero address can't be set, the removeIPv4Reservation should be
+    // used intead.
+    EXPECT_THROW(host->setIPv4Reservation(IOAddress::IPV4_ZERO_ADDRESS()),
+                 isc::BadValue);
+    // Broadcast address can't be set.
+    EXPECT_THROW(host->setIPv4Reservation(IOAddress::IPV4_BCAST_ADDRESS()),
+                 isc::BadValue);
 }
 
 // Test that Host constructors initialize client classes from string.