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">
     <section id="reservation4-hostname">
       <title>Reserving a hostname</title>
       <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>
 
 
     <section id="reservation4-options">
     <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
 This debug message informs that incoming packet has been assigned to specified
 class or classes. This is a normal behavior and indicates successful operation.
 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
 % 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
 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
 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
 A debug message indicating that the DHCPv4 server has received an
 updated configuration from the Kea configuration system.
 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
 % 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
 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
 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
 subnet, an action that severely limits further processing; the server
 will be only able to offer global options - no addresses will be assigned.
 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
 % DHCP4_INIT_FAIL failed to initialize Kea server: %1
 The server has failed to initialize. This may be because the configuration
 The server has failed to initialize. This may be because the configuration
 was not successful, or it encountered any other critical error on startup.
 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
 hardware address is that a cloned virtual machine was not updated and
 both clones use the same client-id.
 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>
 % DHCP4_RESPONSE_DATA responding with packet type %1, data is <%2>
 A debug message listing the data returned to the client.
 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) { };
         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.
 /// @brief DHCPv4 server service.
 ///
 ///
 /// This singleton class represents DHCPv4 server. It contains all
 /// This singleton class represents DHCPv4 server. It contains all
@@ -269,15 +362,15 @@ protected:
     bool acceptServerId(const Pkt4Ptr& pkt) const;
     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
     /// Checks if mandatory option is really there, that forbidden option
     /// is not there, and that client-id or server-id appears only once.
     /// 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
     /// @param serverid expectation regarding server-id option
     /// @throw RFCViolation if any issues are detected
     /// @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.
     /// @brief Processes incoming DISCOVER and returns response.
     ///
     ///
@@ -322,23 +415,14 @@ protected:
     /// @param inform message received from client
     /// @param inform message received from client
     Pkt4Ptr processInform(Pkt4Ptr& inform);
     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.
     /// @brief Appends options requested by client.
     ///
     ///
     /// This method assigns options that were requested by client
     /// This method assigns options that were requested by client
     /// (sent in PRL) or are enforced by server.
     /// (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.
     /// @brief Appends requested vendor options as requested by client.
     ///
     ///
@@ -348,9 +432,9 @@ protected:
     /// options, each with unique vendor-id). Vendor options are requested
     /// options, each with unique vendor-id). Vendor options are requested
     /// using separate options within their respective vendor-option spaces.
     /// 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
     /// @brief Assigns a lease and appends corresponding options
     ///
     ///
@@ -358,28 +442,27 @@ protected:
     /// client and assigning it. Options corresponding to the lease
     /// client and assigning it. Options corresponding to the lease
     /// are added to specific message.
     /// 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.
     /// @brief Append basic options if they are not present.
     ///
     ///
     /// This function adds the following basic options if they
     /// 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,
     /// - Subnet Mask,
     /// - Router,
     /// - Router,
     /// - Name Server,
     /// - Name Server,
     /// - Domain Name.
     /// - 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.
     /// @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
     /// This function does not throw. It simply logs the debug message if the
     /// processing of the FQDN or Hostname failed.
     /// 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
     /// @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
     /// the FQDN option to be sent back to the client in the server's
     /// response.
     /// 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.
     /// @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
     /// prepare the Hostname option to be sent back to the client in the
     /// server's response.
     /// 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:
 protected:
 
 
@@ -499,17 +579,6 @@ protected:
     /// @param reply server's response (ACK or NAK)
     /// @param reply server's response (ACK or NAK)
     void renewLease(const Pkt4Ptr& renew, Pkt4Ptr& reply);
     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.
     /// @brief Adds server identifier option to the server's response.
     ///
     ///
     /// This method adds a server identifier to the DHCPv4 message. It expects
     /// 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
     /// @note This method is static because it is not dependent on the class
     /// state.
     /// 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.
     /// @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
     /// @note This method is static because it is not dependent on the class
     /// state.
     /// 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.
     /// @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
     /// @note This method is static because it is not dependent on the class
     /// state.
     /// 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
     /// @brief converts server-id to text
     /// Converts content of server-id option to a text representation, e.g.
     /// 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.
     /// @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)
     /// @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
     /// indicates if shutdown is in progress. Setting it to true will
     /// initiate server shutdown procedure.
     /// initiate server shutdown procedure.
@@ -661,12 +731,21 @@ protected:
 
 
     /// @brief Performs packet processing specific to a class
     /// @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)
     /// @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:
 private:
 
 
@@ -676,11 +755,6 @@ private:
     /// @return Option that contains netmask information
     /// @return Option that contains netmask information
     static OptionPtr getNetmaskOption(const Subnet4Ptr& subnet);
     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.
     uint16_t port_;  ///< UDP port number on which server listens.
     bool use_bcast_; ///< Should broadcast be enabled on sockets (if true).
     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")),
     ciaddr_(IOAddress("0.0.0.0")),
     curr_transid_(0),
     curr_transid_(0),
     dest_addr_("255.255.255.255"),
     dest_addr_("255.255.255.255"),
+    fqdn_(),
     hwaddr_(generateHWAddr()),
     hwaddr_(generateHWAddr()),
     iface_name_("eth0"),
     iface_name_("eth0"),
     relay_addr_("192.0.2.2"),
     relay_addr_("192.0.2.2"),
@@ -150,6 +151,8 @@ Dhcp4Client::doDiscover(const boost::shared_ptr<IOAddress>& requested_addr) {
     context_.query_ = createMsg(DHCPDISCOVER);
     context_.query_ = createMsg(DHCPDISCOVER);
     // Request options if any.
     // Request options if any.
     includePRL();
     includePRL();
+    // Include FQDN or Hostname.
+    includeName();
     if (requested_addr) {
     if (requested_addr) {
         addRequestedAddress(*requested_addr);
         addRequestedAddress(*requested_addr);
     }
     }
@@ -239,6 +242,8 @@ Dhcp4Client::doRequest() {
 
 
     // Request options if any.
     // Request options if any.
     includePRL();
     includePRL();
+    // Include FQDN or Hostname.
+    includeName();
     // Send the message to the server.
     // Send the message to the server.
     sendMsg(context_.query_);
     sendMsg(context_.query_);
     // Expect response.
     // Expect response.
@@ -250,6 +255,33 @@ Dhcp4Client::doRequest() {
 }
 }
 
 
 void
 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() {
 Dhcp4Client::includePRL() {
     if (!context_.query_) {
     if (!context_.query_) {
         isc_throw(Dhcp4ClientError, "pointer to the query must not be NULL"
         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_);
         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).
     /// @brief Modifies the client's HW address (adds one to it).
     ///
     ///
     /// The HW address should be modified to test negative scenarios when the
     /// The HW address should be modified to test negative scenarios when the
@@ -345,6 +360,13 @@ private:
     /// @return An instance of the message created.
     /// @return An instance of the message created.
     Pkt4Ptr createMsg(const uint8_t msg_type);
     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.
     /// @brief Include PRL Option in the query message.
     ///
     ///
     /// This function creates the instance of the PRL (Parameter Request List)
     /// This function creates the instance of the PRL (Parameter Request List)
@@ -376,6 +398,12 @@ private:
     /// @brief Currently used destination address.
     /// @brief Currently used destination address.
     asiolink::IOAddress dest_addr_;
     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.
     /// @brief Current hardware address of the client.
     HWAddrPtr hwaddr_;
     HWAddrPtr hwaddr_;
 
 
@@ -406,4 +434,4 @@ private:
 } // end of namespace isc::dhcp
 } // end of namespace isc::dhcp
 } // end of namespace isc
 } // 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->setIface("eth1");
     req->setIndex(1);
     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"));
     resp->setYiaddr(IOAddress("192.0.1.100"));
     // Clear the remote address.
     // Clear the remote address.
     resp->setRemoteAddr(IOAddress("0.0.0.0"));
     resp->setRemoteAddr(IOAddress("0.0.0.0"));
@@ -97,7 +97,7 @@ TEST_F(Dhcpv4SrvTest, adjustIfaceDataRelay) {
     resp->setHops(req->getHops());
     resp->setHops(req->getHops());
 
 
     // This function never throws.
     // 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.
     // Now the destination address should be relay's address.
     EXPECT_EQ("192.0.1.1", resp->getRemoteAddr().toText());
     EXPECT_EQ("192.0.1.1", resp->getRemoteAddr().toText());
@@ -123,7 +123,7 @@ TEST_F(Dhcpv4SrvTest, adjustIfaceDataRelay) {
     // Clear remote address.
     // Clear remote address.
     resp->setRemoteAddr(IOAddress("0.0.0.0"));
     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.
     // Response should be sent back to the relay address.
     EXPECT_EQ("192.0.1.50", resp->getRemoteAddr().toText());
     EXPECT_EQ("192.0.1.50", resp->getRemoteAddr().toText());
@@ -163,8 +163,10 @@ TEST_F(Dhcpv4SrvTest, adjustIfaceDataRenew) {
     req->setIface("eth1");
     req->setIface("eth1");
     req->setIndex(1);
     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
     // Let's extend the lease for the client in such a way that
     // it will actually get different address. The response
     // it will actually get different address. The response
     // should not be sent to this address but rather to ciaddr
     // 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.
     // Copy hops value from the query.
     resp->setHops(req->getHops());
     resp->setHops(req->getHops());
 
 
-    ASSERT_NO_THROW(NakedDhcpv4Srv::adjustIfaceData(req, resp));
+    ASSERT_NO_THROW(NakedDhcpv4Srv::adjustIfaceData(ex));
 
 
     // Check that server responds to ciaddr
     // Check that server responds to ciaddr
     EXPECT_EQ("192.0.1.15", resp->getRemoteAddr().toText());
     EXPECT_EQ("192.0.1.15", resp->getRemoteAddr().toText());
@@ -225,8 +227,9 @@ TEST_F(Dhcpv4SrvTest, adjustIfaceDataSelect) {
     req->setIface("eth1");
     req->setIface("eth1");
     req->setIndex(1);
     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.
     // Assign some new address for this client.
     resp->setYiaddr(IOAddress("192.0.1.13"));
     resp->setYiaddr(IOAddress("192.0.1.13"));
     // Clear the remote address.
     // 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
     // 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,
     // carried in yiaddr. In order to send this address to the client,
     // server must broadcast its response.
     // 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
     // Check that the response is sent to broadcast address as the
     // server doesn't have capability to respond directly.
     // 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
     // Now we expect that the server will send its response to the
     // address assigned for the client.
     // 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());
     EXPECT_EQ("192.0.1.13", resp->getRemoteAddr().toText());
 }
 }
@@ -308,15 +311,17 @@ TEST_F(Dhcpv4SrvTest, adjustIfaceDataBroadcast) {
     // Let's set the broadcast flag.
     // Let's set the broadcast flag.
     req->setFlags(Pkt4::FLAG_BROADCAST_MASK);
     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.
     // Assign some new address for this client.
     resp->setYiaddr(IOAddress("192.0.1.13"));
     resp->setYiaddr(IOAddress("192.0.1.13"));
 
 
     // Clear the remote address.
     // Clear the remote address.
     resp->setRemoteAddr(IOAddress("0.0.0.0"));
     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
     // Server must respond to broadcast address when client desired that
     // by setting the broadcast flag in its request.
     // 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
 // This test verifies that exception is thrown of the invalid combination
 // of giaddr and hops is specified in a client's message.
 // of giaddr and hops is specified in a client's message.
 TEST_F(Dhcpv4SrvTest, adjustIfaceDataInvalid) {
 TEST_F(Dhcpv4SrvTest, adjustIfaceDataInvalid) {
+    IfaceMgrTestConfig test_config(true);
+    IfaceMgr::instance().openSockets4();
+
     boost::shared_ptr<Pkt4> req(new Pkt4(DHCPDISCOVER, 1234));
     boost::shared_ptr<Pkt4> req(new Pkt4(DHCPDISCOVER, 1234));
 
 
     // The hops and giaddr values are used to determine if the client's
     // 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->setIface("eth0");
     req->setIndex(1);
     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.
     // Assign some new address for this client.
     resp->setYiaddr(IOAddress("192.0.2.13"));
     resp->setYiaddr(IOAddress("192.0.2.13"));
 
 
     // Clear the remote address.
     // Clear the remote address.
     resp->setRemoteAddr(IOAddress("0.0.0.0"));
     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
 // This test verifies that the server identifier option is appended to
 // a specified DHCPv4 message and the server identifier is correct.
 // a specified DHCPv4 message and the server identifier is correct.
 TEST_F(Dhcpv4SrvTest, appendServerID) {
 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
     // Set a local address. It is required by the function under test
     // to create the Server Identifier option.
     // to create the Server Identifier option.
     response->setLocalAddr(IOAddress("192.0.3.1"));
     response->setLocalAddr(IOAddress("192.0.3.1"));
 
 
     // Append the Server Identifier.
     // Append the Server Identifier.
-    ASSERT_NO_THROW(NakedDhcpv4Srv::appendServerID(response));
+    ASSERT_NO_THROW(NakedDhcpv4Srv::appendServerID(ex));
 
 
     // Make sure that the option has been added.
     // Make sure that the option has been added.
     OptionPtr opt = response->getOption(DHO_DHCP_SERVER_IDENTIFIER);
     OptionPtr opt = response->getOption(DHO_DHCP_SERVER_IDENTIFIER);
@@ -783,10 +796,10 @@ TEST_F(Dhcpv4SrvTest, requestEchoClientId) {
     checkClientId(ack, clientid);
     checkClientId(ack, clientid);
 
 
     CfgMgr::instance().echoClientId(false);
     CfgMgr::instance().echoClientId(false);
-    ack = srv.processDiscover(dis);
+    ack = srv.processRequest(dis);
 
 
     // Check if we get response at all
     // Check if we get response at all
-    checkResponse(ack, DHCPOFFER, 1234);
+    checkResponse(ack, DHCPACK, 1234);
     checkClientId(ack, clientid);
     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
 }; // 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::selectSubnet;
     using Dhcpv4Srv::VENDOR_CLASS_PREFIX;
     using Dhcpv4Srv::VENDOR_CLASS_PREFIX;
     using Dhcpv4Srv::shutdown_;
     using Dhcpv4Srv::shutdown_;
+    using Dhcpv4Srv::alloc_engine_;
 };
 };
 
 
 class Dhcpv4SrvTest : public ::testing::Test {
 class Dhcpv4SrvTest : public ::testing::Test {
@@ -390,6 +391,9 @@ public:
     void configure(const std::string& config, NakedDhcpv4Srv& srv,
     void configure(const std::string& config, NakedDhcpv4Srv& srv,
                    const bool commit = true);
                    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.
     /// @brief This function cleans up after the test.
     virtual void TearDown();
     virtual void TearDown();
 
 

+ 307 - 11
src/bin/dhcp4/tests/fqdn_unittest.cc

@@ -17,9 +17,12 @@
 #include <dhcp/option4_client_fqdn.h>
 #include <dhcp/option4_client_fqdn.h>
 #include <dhcp/option_int_array.h>
 #include <dhcp/option_int_array.h>
 #include <dhcp/tests/iface_mgr_test_config.h>
 #include <dhcp/tests/iface_mgr_test_config.h>
+#include <dhcp4/tests/dhcp4_client.h>
 #include <dhcp4/tests/dhcp4_test_utils.h>
 #include <dhcp4/tests/dhcp4_test_utils.h>
 #include <dhcp_ddns/ncr_msg.h>
 #include <dhcp_ddns/ncr_msg.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/lease_mgr.h>
+#include <dhcpsrv/lease_mgr_factory.h>
 
 
 #include <gtest/gtest.h>
 #include <gtest/gtest.h>
 #include <boost/scoped_ptr.hpp>
 #include <boost/scoped_ptr.hpp>
@@ -32,20 +35,89 @@ using namespace isc::dhcp_ddns;
 
 
 namespace {
 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 {
 class NameDhcpv4SrvTest : public Dhcpv4SrvTest {
 public:
 public:
     // Reference to D2ClientMgr singleton
     // Reference to D2ClientMgr singleton
     D2ClientMgr& d2_mgr_;
     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.
     // Bit Constants for turning on and off DDNS configuration options.
     static const uint16_t ALWAYS_INCLUDE_FQDN = 1;
     static const uint16_t ALWAYS_INCLUDE_FQDN = 1;
     static const uint16_t OVERRIDE_NO_UPDATE = 2;
     static const uint16_t OVERRIDE_NO_UPDATE = 2;
     static const uint16_t OVERRIDE_CLIENT_UPDATE = 4;
     static const uint16_t OVERRIDE_CLIENT_UPDATE = 4;
     static const uint16_t REPLACE_CLIENT_NAME = 8;
     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);
         srv_ = new NakedDhcpv4Srv(0);
+        IfaceMgr::instance().openSockets4();
         // Config DDNS to be enabled, all controls off
         // Config DDNS to be enabled, all controls off
         enableD2();
         enableD2();
     }
     }
@@ -235,12 +307,13 @@ public:
             answer.reset(new Pkt4(DHCPACK, 1234));
             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);
         ASSERT_TRUE(fqdn);
 
 
-        checkFqdnFlags(answer, exp_flags);
+        checkFqdnFlags(ex.getResponse(), exp_flags);
 
 
         EXPECT_EQ(exp_domain_name, fqdn->getDomainName());
         EXPECT_EQ(exp_domain_name, fqdn->getDomainName());
         EXPECT_EQ(exp_domain_type, fqdn->getDomainNameType());
         EXPECT_EQ(exp_domain_type, fqdn->getDomainNameType());
@@ -284,9 +357,10 @@ public:
             answer.reset(new Pkt4(DHCPACK, 1234));
             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);
         return (hostname);
 
 
     }
     }
@@ -381,10 +455,6 @@ public:
             }
             }
         }
         }
     }
     }
-
-
-    NakedDhcpv4Srv* srv_;
-
 };
 };
 
 
 // Test that the exception is thrown if lease pointer specified as the argument
 // 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));
     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
 } // 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);
     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
 } // end of anonymous namespace
 
 
 namespace isc {
 namespace isc {
@@ -1228,7 +1248,7 @@ AllocEngine::ClientContext4::ClientContext4()
       old_lease_(), host_(), conflicting_lease_() {
       old_lease_(), host_(), conflicting_lease_() {
 }
 }
 
 
-AllocEngine::ClientContext4::ClientContext4(const SubnetPtr& subnet,
+AllocEngine::ClientContext4::ClientContext4(const Subnet4Ptr& subnet,
                                             const ClientIdPtr& clientid,
                                             const ClientIdPtr& clientid,
                                             const HWAddrPtr& hwaddr,
                                             const HWAddrPtr& hwaddr,
                                             const asiolink::IOAddress& requested_addr,
                                             const asiolink::IOAddress& requested_addr,
@@ -1284,8 +1304,6 @@ AllocEngine::allocateLease4(ClientContext4& ctx) {
             isc_throw(BadValue, "HWAddr must be defined");
             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);
         new_lease = ctx.fake_allocation_ ? discoverLease4(ctx) : requestLease4(ctx);
         if (!new_lease) {
         if (!new_lease) {
             // Unable to allocate an address, return an empty lease.
             // Unable to allocate an address, return an empty lease.
@@ -1300,6 +1318,26 @@ AllocEngine::allocateLease4(ClientContext4& ctx) {
     return (new_lease);
     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
 Lease4Ptr
 AllocEngine::discoverLease4(AllocEngine::ClientContext4& ctx) {
 AllocEngine::discoverLease4(AllocEngine::ClientContext4& ctx) {
     // Obtain the sole instance of the LeaseMgr.
     // 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
     // Check if there is a reservation for the client. If there is, we want to
     // assign the reserved address, rather than any other one.
     // 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
         // 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.
         // 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
         // Otherwise the address that the client has is the one for which it
@@ -1418,7 +1456,7 @@ AllocEngine::requestLease4(AllocEngine::ClientContext4& ctx) {
             return (Lease4Ptr());
             return (Lease4Ptr());
         }
         }
 
 
-    } else if (ctx.host_) {
+    } else if (hasAddressReservation(ctx)) {
         // The client hasn't specified an address to allocate, so the
         // The client hasn't specified an address to allocate, so the
         // allocation engine needs to find an appropriate address.
         // allocation engine needs to find an appropriate address.
         // If there is a reservation for the client, let's try to
         // 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 it is possible that the client was offered this different
         // address because the reserved address is in use. We will have to
         // address because the reserved address is in use. We will have to
         // check if the address is in use.
         // 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());
             existing = LeaseMgrFactory::instance().getLease4(ctx.host_->getIPv4Reservation());
             // If the reserved address is not in use, i.e. the lease doesn't
             // 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
             // 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
         // 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
         // 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.
         // 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_)) {
             !ctx.subnet_->inPool(Lease4::TYPE_V4, ctx.requested_address_)) {
             return (Lease4Ptr());
             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.
     /// new information doesn't modify the API of the allocation engine.
     struct ClientContext4 {
     struct ClientContext4 {
         /// @brief Subnet selected for the client by the server.
         /// @brief Subnet selected for the client by the server.
-        SubnetPtr subnet_;
+        Subnet4Ptr subnet_;
 
 
         /// @brief Client identifier from the DHCP message.
         /// @brief Client identifier from the DHCP message.
         ClientIdPtr clientid_;
         ClientIdPtr clientid_;
@@ -748,7 +748,7 @@ public:
         /// @param fake_allocation Is this real i.e. REQUEST (false)
         /// @param fake_allocation Is this real i.e. REQUEST (false)
         ///      or just picking an address for DISCOVER that is not really
         ///      or just picking an address for DISCOVER that is not really
         ///      allocated (true)
         ///      allocated (true)
-        ClientContext4(const SubnetPtr& subnet, const ClientIdPtr& clientid,
+        ClientContext4(const Subnet4Ptr& subnet, const ClientIdPtr& clientid,
                        const HWAddrPtr& hwaddr,
                        const HWAddrPtr& hwaddr,
                        const asiolink::IOAddress& requested_addr,
                        const asiolink::IOAddress& requested_addr,
                        const bool fwd_dns_update, const bool rev_dns_update,
                        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.
     /// @brief Returns IPv4 lease.
     ///
     ///
     /// This method finds a lease for a client using the following algorithm:
     /// 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)
     /// - @ref ClientContext4::fake_allocation_ Is this real i.e. REQUEST (false)
     ///      or just picking an address for DISCOVER that is not really
     ///      or just picking an address for DISCOVER that is not really
     ///      allocated (true)
     ///      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).
     /// - @ref ClientContext4::callout_handle_ A callout handle (used in hooks).
     ///      A lease callouts will be executed if this parameter is passed.
     ///      A lease callouts will be executed if this parameter is passed.
     /// - @ref ClientContext4::old_lease_ [out] Holds the pointer to a previous
     /// - @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).
     /// @return Allocated IPv4 lease (or NULL if allocation failed).
     Lease4Ptr allocateLease4(ClientContext4& ctx);
     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:
 private:
 
 
     /// @brief Offers the lease.
     /// @brief Offers the lease.
@@ -1069,6 +1084,9 @@ private:
                                  ClientContext4& ctx) const;
                                  ClientContext4& ctx) const;
 };
 };
 
 
+/// @brief A pointer to the @c AllocEngine object.
+typedef boost::shared_ptr<AllocEngine> AllocEnginePtr;
+
 }; // namespace isc::dhcp
 }; // namespace isc::dhcp
 }; // namespace isc
 }; // 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)
            const std::string& dhcp6_client_classes)
     : hw_address_(), duid_(), ipv4_subnet_id_(ipv4_subnet_id),
     : hw_address_(), duid_(), ipv4_subnet_id_(ipv4_subnet_id),
       ipv6_subnet_id_(ipv6_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
     // Initialize HWAddr or DUID
     setIdentifier(identifier, identifier_len, identifier_type);
     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,
 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)
            const std::string& dhcp6_client_classes)
     : hw_address_(), duid_(), ipv4_subnet_id_(ipv4_subnet_id),
     : hw_address_(), duid_(), ipv4_subnet_id_(ipv4_subnet_id),
       ipv6_subnet_id_(ipv6_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),
       hostname_(hostname), dhcp4_client_classes_(dhcp4_client_classes),
       dhcp6_client_classes_(dhcp6_client_classes) {
       dhcp6_client_classes_(dhcp6_client_classes) {
 
 
     // Initialize HWAddr or DUID
     // Initialize HWAddr or DUID
     setIdentifier(identifier, identifier_name);
     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>&
 const std::vector<uint8_t>&
@@ -172,10 +176,17 @@ Host::setIPv4Reservation(const asiolink::IOAddress& address) {
     if (!address.isV4()) {
     if (!address.isV4()) {
         isc_throw(isc::BadValue, "address '" << address << "' is not a valid"
         isc_throw(isc::BadValue, "address '" << address << "' is not a valid"
                   " IPv4 address");
                   " IPv4 address");
+    } else if (address.isV4Zero() || address.isV4Bcast()) {
+        isc_throw(isc::BadValue, "must not make reservation for the '"
+                  << address << "' address");
     }
     }
     ipv4_reservation_ = address;
     ipv4_reservation_ = address;
 }
 }
 
 
+void
+Host::removeIPv4Reservation() {
+    ipv4_reservation_ = asiolink::IOAddress::IPV4_ZERO_ADDRESS();
+}
 
 
 void
 void
 Host::addReservation(const IPv6Resrv& reservation) {
 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.
     /// @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);
     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.
     /// @brief Returns reserved IPv4 address.
     ///
     ///
     /// @return IPv4 address or 0.0.0.0 if no IPv4 reservation specified.
     /// @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);
     ASSERT_TRUE(engine);
 
 
     // Allocations without subnet are not allowed
     // 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,
                                     IOAddress("0.0.0.0"), false, false,
                                     "", false);
                                     "", false);
     Lease4Ptr lease = engine->allocateLease4(ctx1);
     Lease4Ptr lease = engine->allocateLease4(ctx1);
@@ -598,6 +598,7 @@ TEST_F(AllocEngine4Test, reservedAddressNoHint) {
     AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_,
     AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_,
                                     IOAddress("0.0.0.0"), false, false,
                                     IOAddress("0.0.0.0"), false, false,
                                     "", false);
                                     "", false);
+    AllocEngine::findReservation(ctx);
     Lease4Ptr lease = engine.allocateLease4(ctx);
     Lease4Ptr lease = engine.allocateLease4(ctx);
 
 
     ASSERT_TRUE(lease);
     ASSERT_TRUE(lease);
@@ -634,6 +635,7 @@ TEST_F(AllocEngine4Test,reservedAddressNoHintFakeAllocation) {
     AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_,
     AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_,
                                     IOAddress("0.0.0.0"), false, false,
                                     IOAddress("0.0.0.0"), false, false,
                                     "", true);
                                     "", true);
+    AllocEngine::findReservation(ctx);
     Lease4Ptr lease = engine.allocateLease4(ctx);
     Lease4Ptr lease = engine.allocateLease4(ctx);
 
 
     ASSERT_TRUE(lease);
     ASSERT_TRUE(lease);
@@ -671,6 +673,7 @@ TEST_F(AllocEngine4Test, reservedAddressHint) {
     AllocEngine::ClientContext4 ctx1(subnet_, clientid_, hwaddr_,
     AllocEngine::ClientContext4 ctx1(subnet_, clientid_, hwaddr_,
                                     IOAddress("192.0.2.234"), false, false,
                                     IOAddress("192.0.2.234"), false, false,
                                     "", false);
                                     "", false);
+    AllocEngine::findReservation(ctx1);
     Lease4Ptr lease = engine.allocateLease4(ctx1);
     Lease4Ptr lease = engine.allocateLease4(ctx1);
 
 
     // The client requested a different address than reserved, so
     // The client requested a different address than reserved, so
@@ -683,6 +686,7 @@ TEST_F(AllocEngine4Test, reservedAddressHint) {
     AllocEngine::ClientContext4 ctx2(subnet_, clientid_, hwaddr_,
     AllocEngine::ClientContext4 ctx2(subnet_, clientid_, hwaddr_,
                                     IOAddress("192.0.2.123"), false, false,
                                     IOAddress("192.0.2.123"), false, false,
                                     "", false);
                                     "", false);
+    AllocEngine::findReservation(ctx2);
     lease = engine.allocateLease4(ctx2);
     lease = engine.allocateLease4(ctx2);
     ASSERT_TRUE(lease);
     ASSERT_TRUE(lease);
     EXPECT_EQ("192.0.2.123", lease->addr_.toText());
     EXPECT_EQ("192.0.2.123", lease->addr_.toText());
@@ -718,6 +722,7 @@ TEST_F(AllocEngine4Test, reservedAddressHintFakeAllocation) {
     AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_,
     AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_,
                                     IOAddress("192.0.2.234"), false, false,
                                     IOAddress("192.0.2.234"), false, false,
                                     "", true);
                                     "", true);
+    AllocEngine::findReservation(ctx);
     Lease4Ptr lease = engine.allocateLease4(ctx);
     Lease4Ptr lease = engine.allocateLease4(ctx);
 
 
     ASSERT_TRUE(lease);
     ASSERT_TRUE(lease);
@@ -761,6 +766,7 @@ TEST_F(AllocEngine4Test, reservedAddressExistingLease) {
     AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_,
     AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_,
                                     IOAddress("192.0.2.123"), false, false,
                                     IOAddress("192.0.2.123"), false, false,
                                     "", false);
                                     "", false);
+    AllocEngine::findReservation(ctx);
     Lease4Ptr allocated_lease = engine.allocateLease4(ctx);
     Lease4Ptr allocated_lease = engine.allocateLease4(ctx);
 
 
     ASSERT_TRUE(allocated_lease);
     ASSERT_TRUE(allocated_lease);
@@ -808,6 +814,7 @@ TEST_F(AllocEngine4Test, reservedAddressHijacked) {
     AllocEngine::ClientContext4 ctx1(subnet_, clientid_, hwaddr_,
     AllocEngine::ClientContext4 ctx1(subnet_, clientid_, hwaddr_,
                                     IOAddress("192.0.2.123"), false, false,
                                     IOAddress("192.0.2.123"), false, false,
                                     "", false);
                                     "", false);
+    AllocEngine::findReservation(ctx1);
     Lease4Ptr allocated_lease = engine.allocateLease4(ctx1);
     Lease4Ptr allocated_lease = engine.allocateLease4(ctx1);
     // The lease is allocated to someone else, so the allocation should not
     // The lease is allocated to someone else, so the allocation should not
     // succeed.
     // succeed.
@@ -825,6 +832,7 @@ TEST_F(AllocEngine4Test, reservedAddressHijacked) {
     AllocEngine::ClientContext4 ctx2(subnet_, clientid_, hwaddr_,
     AllocEngine::ClientContext4 ctx2(subnet_, clientid_, hwaddr_,
                                     IOAddress("0.0.0.0"), false, false,
                                     IOAddress("0.0.0.0"), false, false,
                                     "", false);
                                     "", false);
+    AllocEngine::findReservation(ctx2);
     allocated_lease = engine.allocateLease4(ctx2);
     allocated_lease = engine.allocateLease4(ctx2);
     ASSERT_FALSE(allocated_lease);
     ASSERT_FALSE(allocated_lease);
     EXPECT_FALSE(ctx2.old_lease_);
     EXPECT_FALSE(ctx2.old_lease_);
@@ -863,6 +871,7 @@ TEST_F(AllocEngine4Test, reservedAddressHijackedFakeAllocation) {
     AllocEngine::ClientContext4 ctx1(subnet_, clientid_, hwaddr_,
     AllocEngine::ClientContext4 ctx1(subnet_, clientid_, hwaddr_,
                                     IOAddress("192.0.2.123"), false, false,
                                     IOAddress("192.0.2.123"), false, false,
                                     "", true);
                                     "", true);
+    AllocEngine::findReservation(ctx1);
     Lease4Ptr allocated_lease = engine.allocateLease4(ctx1);
     Lease4Ptr allocated_lease = engine.allocateLease4(ctx1);
 
 
     // The allocation engine should return a lease but for a different address
     // 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_,
     AllocEngine::ClientContext4 ctx2(subnet_, clientid_, hwaddr_,
                                     IOAddress("0.0.0.0"), false, false,
                                     IOAddress("0.0.0.0"), false, false,
                                     "", true);
                                     "", true);
+    AllocEngine::findReservation(ctx2);
     allocated_lease = engine.allocateLease4(ctx2);
     allocated_lease = engine.allocateLease4(ctx2);
 
 
     ASSERT_TRUE(allocated_lease);
     ASSERT_TRUE(allocated_lease);
@@ -918,6 +928,7 @@ TEST_F(AllocEngine4Test, reservedAddressExistingLeaseInvalidHint) {
     AllocEngine::ClientContext4 ctx1(subnet_, clientid_, hwaddr_,
     AllocEngine::ClientContext4 ctx1(subnet_, clientid_, hwaddr_,
                                     IOAddress("192.0.2.102"), false, false,
                                     IOAddress("192.0.2.102"), false, false,
                                     "", false);
                                     "", false);
+    AllocEngine::findReservation(ctx1);
     Lease4Ptr allocated_lease = engine.allocateLease4(ctx1);
     Lease4Ptr allocated_lease = engine.allocateLease4(ctx1);
     ASSERT_FALSE(allocated_lease);
     ASSERT_FALSE(allocated_lease);
     ASSERT_FALSE(ctx1.old_lease_);
     ASSERT_FALSE(ctx1.old_lease_);
@@ -968,6 +979,7 @@ TEST_F(AllocEngine4Test, reservedAddressExistingLeaseFakeAllocation) {
     AllocEngine::ClientContext4 ctx1(subnet_, clientid_, hwaddr_,
     AllocEngine::ClientContext4 ctx1(subnet_, clientid_, hwaddr_,
                                     IOAddress("192.0.2.102"), false, false,
                                     IOAddress("192.0.2.102"), false, false,
                                     "", true);
                                     "", true);
+    AllocEngine::findReservation(ctx1);
     Lease4Ptr allocated_lease = engine.allocateLease4(ctx1);
     Lease4Ptr allocated_lease = engine.allocateLease4(ctx1);
 
 
     // Server should offer a lease for a reserved address.
     // Server should offer a lease for a reserved address.
@@ -986,6 +998,7 @@ TEST_F(AllocEngine4Test, reservedAddressExistingLeaseFakeAllocation) {
     AllocEngine::ClientContext4 ctx2(subnet_, clientid_, hwaddr_,
     AllocEngine::ClientContext4 ctx2(subnet_, clientid_, hwaddr_,
                                     IOAddress("192.0.2.101"), false, false,
                                     IOAddress("192.0.2.101"), false, false,
                                     "", true);
                                     "", true);
+    AllocEngine::findReservation(ctx2);
     allocated_lease = engine.allocateLease4(ctx2);
     allocated_lease = engine.allocateLease4(ctx2);
 
 
     // The server should offer the lease, but not for the address that
     // 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_,
     AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_,
                                     IOAddress("0.0.0.0"), false, false,
                                     IOAddress("0.0.0.0"), false, false,
                                     "", false);
                                     "", false);
+    AllocEngine::findReservation(ctx);
     Lease4Ptr allocated_lease = engine.allocateLease4(ctx);
     Lease4Ptr allocated_lease = engine.allocateLease4(ctx);
 
 
     // The reserved address should be allocated.
     // The reserved address should be allocated.
@@ -1075,6 +1089,7 @@ TEST_F(AllocEngine4Test, reservedAddressExistingLeaseNoHintFakeAllocation) {
     AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_,
     AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_,
                                     IOAddress("0.0.0.0"), false, false,
                                     IOAddress("0.0.0.0"), false, false,
                                     "", true);
                                     "", true);
+    AllocEngine::findReservation(ctx);
     Lease4Ptr allocated_lease = engine.allocateLease4(ctx);
     Lease4Ptr allocated_lease = engine.allocateLease4(ctx);
 
 
     // The server should offer the reserved address.
     // The server should offer the reserved address.
@@ -1137,6 +1152,7 @@ TEST_F(AllocEngine4Test, reservedAddressConflictResolution) {
     AllocEngine::ClientContext4 ctx1(subnet_, ClientIdPtr(), hwaddr2_,
     AllocEngine::ClientContext4 ctx1(subnet_, ClientIdPtr(), hwaddr2_,
                                     IOAddress("192.0.2.101"), false, false,
                                     IOAddress("192.0.2.101"), false, false,
                                     "", false);
                                     "", false);
+    AllocEngine::findReservation(ctx1);
     Lease4Ptr offered_lease = engine.allocateLease4(ctx1);
     Lease4Ptr offered_lease = engine.allocateLease4(ctx1);
     ASSERT_FALSE(offered_lease);
     ASSERT_FALSE(offered_lease);
 
 
@@ -1146,6 +1162,7 @@ TEST_F(AllocEngine4Test, reservedAddressConflictResolution) {
     AllocEngine::ClientContext4 ctx2(subnet_, clientid_, hwaddr_,
     AllocEngine::ClientContext4 ctx2(subnet_, clientid_, hwaddr_,
                                     IOAddress("192.0.2.101"), false, false,
                                     IOAddress("192.0.2.101"), false, false,
                                     "", false);
                                     "", false);
+    AllocEngine::findReservation(ctx2);
     ASSERT_FALSE(engine.allocateLease4(ctx2));
     ASSERT_FALSE(engine.allocateLease4(ctx2));
 
 
     ASSERT_FALSE(ctx2.old_lease_);
     ASSERT_FALSE(ctx2.old_lease_);
@@ -1156,6 +1173,7 @@ TEST_F(AllocEngine4Test, reservedAddressConflictResolution) {
     AllocEngine::ClientContext4 ctx3(subnet_, clientid_, hwaddr_,
     AllocEngine::ClientContext4 ctx3(subnet_, clientid_, hwaddr_,
                                     IOAddress("192.0.2.101"), false, false,
                                     IOAddress("192.0.2.101"), false, false,
                                     "", true);
                                     "", true);
+    AllocEngine::findReservation(ctx3);
     offered_lease = engine.allocateLease4(ctx3);
     offered_lease = engine.allocateLease4(ctx3);
     ASSERT_TRUE(offered_lease);
     ASSERT_TRUE(offered_lease);
     EXPECT_NE(offered_lease->addr_.toText(), "192.0.2.101");
     EXPECT_NE(offered_lease->addr_.toText(), "192.0.2.101");
@@ -1166,6 +1184,7 @@ TEST_F(AllocEngine4Test, reservedAddressConflictResolution) {
     AllocEngine::ClientContext4 ctx4(subnet_, clientid_, hwaddr_,
     AllocEngine::ClientContext4 ctx4(subnet_, clientid_, hwaddr_,
                                     offered_lease->addr_, false, false,
                                     offered_lease->addr_, false, false,
                                     "", false);
                                     "", false);
+    AllocEngine::findReservation(ctx4);
     Lease4Ptr allocated_lease = engine.allocateLease4(ctx4);
     Lease4Ptr allocated_lease = engine.allocateLease4(ctx4);
 
 
     ASSERT_TRUE(allocated_lease);
     ASSERT_TRUE(allocated_lease);
@@ -1176,6 +1195,7 @@ TEST_F(AllocEngine4Test, reservedAddressConflictResolution) {
     AllocEngine::ClientContext4 ctx5(subnet_, ClientIdPtr(), hwaddr2_,
     AllocEngine::ClientContext4 ctx5(subnet_, ClientIdPtr(), hwaddr2_,
                                      IOAddress("0.0.0.0"), false, false,
                                      IOAddress("0.0.0.0"), false, false,
                                     "", true);
                                     "", true);
+    AllocEngine::findReservation(ctx5);
     offered_lease = engine.allocateLease4(ctx5);
     offered_lease = engine.allocateLease4(ctx5);
 
 
     ASSERT_TRUE(offered_lease);
     ASSERT_TRUE(offered_lease);
@@ -1210,6 +1230,7 @@ TEST_F(AllocEngine4Test, reservedAddressVsDynamicPool) {
     AllocEngine::ClientContext4 ctx(subnet_, ClientIdPtr(), hwaddr_,
     AllocEngine::ClientContext4 ctx(subnet_, ClientIdPtr(), hwaddr_,
                                      IOAddress("0.0.0.0"), false, false,
                                      IOAddress("0.0.0.0"), false, false,
                                     "", false);
                                     "", false);
+    AllocEngine::findReservation(ctx);
     Lease4Ptr allocated_lease = engine.allocateLease4(ctx);
     Lease4Ptr allocated_lease = engine.allocateLease4(ctx);
 
 
     ASSERT_TRUE(allocated_lease);
     ASSERT_TRUE(allocated_lease);
@@ -1236,7 +1257,8 @@ TEST_F(AllocEngine4Test, reservedAddressHintUsedByOtherClient) {
     // Different client is requesting this address.
     // Different client is requesting this address.
     AllocEngine::ClientContext4 ctx1(subnet_, ClientIdPtr(), hwaddr_,
     AllocEngine::ClientContext4 ctx1(subnet_, ClientIdPtr(), hwaddr_,
                                      IOAddress("192.0.2.100"), false, false,
                                      IOAddress("192.0.2.100"), false, false,
-                                    "", false);
+                                     "", false);
+    AllocEngine::findReservation(ctx1);
     Lease4Ptr allocated_lease = engine.allocateLease4(ctx1);
     Lease4Ptr allocated_lease = engine.allocateLease4(ctx1);
 
 
     // The client should get no lease (DHCPNAK).
     // The client should get no lease (DHCPNAK).
@@ -1246,7 +1268,8 @@ TEST_F(AllocEngine4Test, reservedAddressHintUsedByOtherClient) {
     // if is sending a DHCPDISCOVER (fake allocation is true).
     // if is sending a DHCPDISCOVER (fake allocation is true).
     AllocEngine::ClientContext4 ctx2(subnet_, ClientIdPtr(), hwaddr_,
     AllocEngine::ClientContext4 ctx2(subnet_, ClientIdPtr(), hwaddr_,
                                      IOAddress("192.0.2.100"), false, false,
                                      IOAddress("192.0.2.100"), false, false,
-                                    "", true);
+                                     "", true);
+    AllocEngine::findReservation(ctx2);
     allocated_lease = engine.allocateLease4(ctx2);
     allocated_lease = engine.allocateLease4(ctx2);
 
 
     ASSERT_TRUE(allocated_lease);
     ASSERT_TRUE(allocated_lease);
@@ -1263,7 +1286,7 @@ TEST_F(AllocEngine4Test, reservedAddressShortPool) {
     // Create short pool with only one address.
     // Create short pool with only one address.
     initSubnet(IOAddress("192.0.2.100"), IOAddress("192.0.2.100"));
     initSubnet(IOAddress("192.0.2.100"), IOAddress("192.0.2.100"));
     // Reserve the address for a different client.
     // 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(),
                           Host::IDENT_HWADDR, subnet_->getID(),
                           SubnetID(0), IOAddress("192.0.2.100")));
                           SubnetID(0), IOAddress("192.0.2.100")));
     CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
     CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
@@ -1274,6 +1297,7 @@ TEST_F(AllocEngine4Test, reservedAddressShortPool) {
     AllocEngine::ClientContext4 ctx1(subnet_, ClientIdPtr(), hwaddr_,
     AllocEngine::ClientContext4 ctx1(subnet_, ClientIdPtr(), hwaddr_,
                                      IOAddress("0.0.0.0"), false, false,
                                      IOAddress("0.0.0.0"), false, false,
                                      "", false);
                                      "", false);
+    AllocEngine::findReservation(ctx1);
     Lease4Ptr allocated_lease = engine.allocateLease4(ctx1);
     Lease4Ptr allocated_lease = engine.allocateLease4(ctx1);
 
 
     EXPECT_FALSE(allocated_lease);
     EXPECT_FALSE(allocated_lease);
@@ -1286,12 +1310,119 @@ TEST_F(AllocEngine4Test, reservedAddressShortPool) {
     AllocEngine::ClientContext4 ctx2(subnet_, ClientIdPtr(), hwaddr_,
     AllocEngine::ClientContext4 ctx2(subnet_, ClientIdPtr(), hwaddr_,
                                      IOAddress("0.0.0.0"), false, false,
                                      IOAddress("0.0.0.0"), false, false,
                                      "", false);
                                      "", false);
+    AllocEngine::findReservation(ctx2);
     allocated_lease = engine.allocateLease4(ctx2);
     allocated_lease = engine.allocateLease4(ctx2);
 
 
     ASSERT_TRUE(allocated_lease);
     ASSERT_TRUE(allocated_lease);
     EXPECT_EQ("192.0.2.100", allocated_lease->addr_.toText());
     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 test
 }; // namespace dhcp
 }; // namespace dhcp
 }; // namespace isc
 }; // 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
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
 // 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);
     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
 // This test verfies that the parser can parse the IPv6 reservation entry for
 // which hw-address is a host identifier.
 // which hw-address is a host identifier.
 TEST_F(HostReservationParserTest, dhcp6HWaddr) {
 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("10.0.0.1", host->getIPv4Reservation().toText());
     EXPECT_EQ("other-host.example.org", host->getHostname());
     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.
     // An IPv6 address can't be used for IPv4 reservations.
     EXPECT_THROW(host->setIPv4Reservation(IOAddress("2001:db8:1::1")),
     EXPECT_THROW(host->setIPv4Reservation(IOAddress("2001:db8:1::1")),
                  isc::BadValue);
                  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.
 // Test that Host constructors initialize client classes from string.