Browse Source

[master] Merge branch 'trac3320'

Marcin Siodelski 10 years ago
parent
commit
ad411a177a

+ 5 - 0
src/bin/dhcp4/dhcp4_messages.mes

@@ -147,6 +147,11 @@ point, the setting of the flag instructs the server not to choose a
 subnet, an action that severely limits further processing; the server
 will be only able to offer global options - no addresses will be assigned.
 
+% DHCP4_INVALID_ADDRESS_INIT_REBOOT invalid address %1 requested by INIT-REBOOT client (id: %2, hwaddr: %3)
+This debug message is issued when the client being in the INIT-REBOOT state
+requested an address which is not assigned to him. The server will respond
+to this client with DHCPNAK.
+
 % DHCP4_LEASE_ADVERT lease %1 advertised (client client-id %2, hwaddr %3)
 This debug message indicates that the server successfully advertised
 a lease. It is up to the client to choose one server out of othe advertised

+ 36 - 3
src/bin/dhcp4/dhcp4_srv.cc

@@ -940,9 +940,23 @@ Dhcpv4Srv::assignLease(const Pkt4Ptr& question, Pkt4Ptr& answer) {
     if (opt) {
         client_id = ClientIdPtr(new ClientId(opt->getData()));
     }
+
     // client-id is not mandatory in DHCPv4
 
-    IOAddress hint = question->getYiaddr();
+    // Get the server identifier. It will be used to determine the state
+    // of the client.
+    OptionCustomPtr opt_serverid = boost::dynamic_pointer_cast<
+        OptionCustom>(question->getOption(DHO_DHCP_SERVER_IDENTIFIER));
+
+    // Try to get the Requested IP Address option and use the address as a hint
+    // for the allocation engine. If the server doesn't already have a lease
+    // for this client it will try to allocate the one requested.
+    OptionCustomPtr opt_requested_address = boost::dynamic_pointer_cast<
+        OptionCustom>(question->getOption(DHO_DHCP_REQUESTED_ADDRESS));
+    IOAddress hint("0.0.0.0");
+    if (opt_requested_address) {
+        hint = opt_requested_address->readAddress();
+    }
 
     HWAddrPtr hwaddr = question->getHWAddr();
 
@@ -954,6 +968,25 @@ Dhcpv4Srv::assignLease(const Pkt4Ptr& question, Pkt4Ptr& answer) {
     // allocation.
     bool fake_allocation = (question->getType() == DHCPDISCOVER);
 
+    // If there is no server id and there is a Requested IP Address option
+    // the client is in the INIT-REBOOT state in which the server has to
+    // determine whether the client's notion of the address has to be verified.
+    if (!fake_allocation && !opt_serverid && opt_requested_address) {
+        Lease4Ptr lease = LeaseMgrFactory::instance().getLease4(hint);
+        if (!lease) {
+            LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL,
+                      DHCP4_INVALID_ADDRESS_INIT_REBOOT)
+                .arg(hint.toText())
+                .arg(client_id ? client_id->toText():"(no client-id)")
+                .arg(hwaddr ? hwaddr->toText():"(no hwaddr info)");
+
+            answer->setType(DHCPNAK);
+            answer->setYiaddr(IOAddress("0.0.0.0"));
+            return;
+        }
+    }
+
+
     CalloutHandlePtr callout_handle = getCalloutHandle(question);
 
     std::string hostname;
@@ -993,8 +1026,8 @@ Dhcpv4Srv::assignLease(const Pkt4Ptr& question, Pkt4Ptr& answer) {
     /// @todo pass the actual FQDN data.
     Lease4Ptr old_lease;
     Lease4Ptr lease = alloc_engine_->allocateLease4(subnet, client_id, hwaddr,
-                                                      hint, fqdn_fwd, fqdn_rev,
-                                                      hostname,
+                                                    hint, fqdn_fwd, fqdn_rev,
+                                                    hostname,
                                                     fake_allocation,
                                                     callout_handle,
                                                     old_lease);

+ 1 - 0
src/bin/dhcp4/tests/Makefile.am

@@ -90,6 +90,7 @@ dhcp4_unittests_SOURCES += fqdn_unittest.cc
 dhcp4_unittests_SOURCES += marker_file.cc
 dhcp4_unittests_SOURCES += dhcp4_client.cc dhcp4_client.h
 dhcp4_unittests_SOURCES += inform_unittest.cc
+dhcp4_unittests_SOURCES += dora_unittest.cc
 
 if CONFIG_BACKEND_BUNDY
 # For Bundy backend, we only need to run the usual tests. There are no

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

@@ -21,6 +21,8 @@
 #include <boost/pointer_cast.hpp>
 #include <cstdlib>
 
+using namespace isc::asiolink;
+
 namespace isc {
 namespace dhcp {
 namespace test {
@@ -38,9 +40,10 @@ Dhcp4Client::Configuration::reset() {
     log_servers_.clear();
     quotes_servers_.clear();
     serverid_ = asiolink::IOAddress("0.0.0.0");
+    lease_ = Lease4();
 }
 
-Dhcp4Client::Dhcp4Client() :
+Dhcp4Client::Dhcp4Client(const Dhcp4Client::State& state) :
     config_(),
     curr_transid_(0),
     dest_addr_("255.255.255.255"),
@@ -49,10 +52,12 @@ Dhcp4Client::Dhcp4Client() :
     requested_options_(),
     server_facing_relay_addr_("10.0.0.2"),
     srv_(boost::shared_ptr<NakedDhcpv4Srv>(new NakedDhcpv4Srv(0))),
+    state_(state),
     use_relay_(false) {
 }
 
-Dhcp4Client::Dhcp4Client(boost::shared_ptr<NakedDhcpv4Srv>& srv) :
+Dhcp4Client::Dhcp4Client(boost::shared_ptr<NakedDhcpv4Srv>& srv,
+                         const Dhcp4Client::State& state) :
     config_(),
     curr_transid_(0),
     dest_addr_("255.255.255.255"),
@@ -61,10 +66,20 @@ Dhcp4Client::Dhcp4Client(boost::shared_ptr<NakedDhcpv4Srv>& srv) :
     requested_options_(),
     server_facing_relay_addr_("10.0.0.2"),
     srv_(srv),
+    state_(state),
     use_relay_(false) {
 }
 
 void
+Dhcp4Client::addRequestedAddress(const asiolink::IOAddress& addr) {
+    if (context_.query_) {
+        Option4AddrLstPtr opt(new Option4AddrLst(DHO_DHCP_REQUESTED_ADDRESS,
+                                                 addr));
+        context_.query_->addOption(opt);
+    }
+}
+
+void
 Dhcp4Client::applyConfiguration() {
     Pkt4Ptr resp = context_.response_;
     if (!resp) {
@@ -104,7 +119,12 @@ Dhcp4Client::applyConfiguration() {
         config_.serverid_ = opt_serverid->readAddress();
     }
 
-    /// @todo Other possible configuration, e.g. lease.
+    /// @todo Set the valid lifetime, t1, t2 etc.
+    config_.lease_ = Lease4(IOAddress(context_.response_->getYiaddr()),
+                            &context_.response_->getHWAddr()->hwaddr_[0],
+                            context_.response_->getHWAddr()->hwaddr_.size(),
+                            0, 0, 0, 0, 0, time(NULL), 0, false, false,
+                            "");
 }
 
 void
@@ -124,20 +144,32 @@ Dhcp4Client::createMsg(const uint8_t msg_type) {
 }
 
 void
+Dhcp4Client::doDiscover(const boost::shared_ptr<IOAddress>& requested_addr) {
+    context_.query_ = createMsg(DHCPDISCOVER);
+    // Request options if any.
+    includePRL();
+    if (requested_addr) {
+        addRequestedAddress(*requested_addr);
+    }
+    // Send the message to the server.
+    sendMsg(context_.query_);
+    // Expect response.
+    context_.response_ = receiveOneMsg();
+}
+
+void
+Dhcp4Client::doDORA(const boost::shared_ptr<IOAddress>& requested_addr) {
+    doDiscover(requested_addr);
+    if (context_.response_ && (context_.response_->getType() == DHCPOFFER)) {
+        doRequest();
+    }
+}
+
+void
 Dhcp4Client::doInform(const bool set_ciaddr) {
     context_.query_ = createMsg(DHCPINFORM);
     // Request options if any.
-    if (!requested_options_.empty()) {
-        // Include Parameter Request List if at least one option code
-        // has been specified to be requested.
-        OptionUint8ArrayPtr prl(new OptionUint8Array(Option::V4,
-                                  DHO_DHCP_PARAMETER_REQUEST_LIST));
-        for (std::set<uint8_t>::const_iterator opt = requested_options_.begin();
-             opt != requested_options_.end(); ++opt) {
-            prl->addValue(*opt);
-        }
-        context_.query_->addOption(prl);
-    }
+    includePRL();
     // The client sending a DHCPINFORM message has an IP address obtained
     // by some other means, e.g. static configuration. The lease which we
     // are using here is most likely set by the createLease method.
@@ -159,6 +191,75 @@ Dhcp4Client::doInform(const bool set_ciaddr) {
     }
 }
 
+void
+Dhcp4Client::doRequest() {
+    context_.query_ = createMsg(DHCPREQUEST);
+
+    // Set ciaddr.
+    if ((state_ == SELECTING) || (state_ == INIT_REBOOT)) {
+        context_.query_->setCiaddr(IOAddress("0.0.0.0"));
+    } else {
+        context_.query_->setCiaddr(IOAddress(config_.lease_.addr_));
+    }
+
+    // Requested IP address.
+    if (state_ == SELECTING) {
+        if (context_.response_ &&
+            (context_.response_->getType() == DHCPOFFER) &&
+            (context_.response_->getYiaddr() != IOAddress("0.0.0.0"))) {
+            addRequestedAddress(context_.response_->getYiaddr());
+        } else {
+            isc_throw(Dhcp4ClientError, "error sending the DHCPREQUEST because"
+                      " the received DHCPOFFER message was invalid");
+        }
+    } else if (state_ == INIT_REBOOT) {
+        addRequestedAddress(config_.lease_.addr_);
+    }
+
+    // Server identifier.
+    if (state_ == SELECTING) {
+        if (context_.response_) {
+            OptionPtr opt_serverid =
+                context_.response_->getOption(DHO_DHCP_SERVER_IDENTIFIER);
+            if (!opt_serverid) {
+                isc_throw(Dhcp4ClientError, "missing server identifier in the"
+                          " server's response");
+            }
+            context_.query_->addOption(opt_serverid);
+        }
+    }
+
+    // Request options if any.
+    includePRL();
+    // Send the message to the server.
+    sendMsg(context_.query_);
+    // Expect response.
+    context_.response_ = receiveOneMsg();
+    // If the server has responded, store the configuration received.
+    if (context_.response_) {
+        applyConfiguration();
+    }
+}
+
+void
+Dhcp4Client::includePRL() {
+    if (!context_.query_) {
+        isc_throw(Dhcp4ClientError, "pointer to the query must not be NULL"
+                  " when adding option codes to the PRL option");
+
+    } else if (!requested_options_.empty()) {
+        // Include Parameter Request List if at least one option code
+        // has been specified to be requested.
+        OptionUint8ArrayPtr prl(new OptionUint8Array(Option::V4,
+                                  DHO_DHCP_PARAMETER_REQUEST_LIST));
+        for (std::set<uint8_t>::const_iterator opt = requested_options_.begin();
+             opt != requested_options_.end(); ++opt) {
+            prl->addValue(*opt);
+        }
+        context_.query_->addOption(prl);
+    }
+}
+
 HWAddrPtr
 Dhcp4Client::generateHWAddr(const uint8_t htype) const {
     if (htype != HTYPE_ETHER) {

+ 99 - 2
src/bin/dhcp4/tests/dhcp4_client.h

@@ -27,6 +27,13 @@ namespace isc {
 namespace dhcp {
 namespace test {
 
+/// @brief General error emitted by the DHCP4 test client.
+class Dhcp4ClientError : public isc::Exception {
+public:
+    Dhcp4ClientError(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
 /// @brief DHCPv4 client used for unit testing.
 ///
 /// This class implements a DHCPv4 "client" which interoperates with the
@@ -45,6 +52,14 @@ namespace test {
 class Dhcp4Client : public boost::noncopyable {
 public:
 
+    /// @brief States of the DHCP client.
+    enum State {
+        SELECTING,
+        INIT_REBOOT,
+        RENEWING,
+        REBINDING
+    };
+
     /// @brief Holds the DHCPv4 messages taking part in transaction between
     /// the client and the server.
     struct Context {
@@ -79,12 +94,16 @@ public:
     };
 
     /// @brief Creates a new client.
-    Dhcp4Client();
+    ///
+    /// @param Initial client's state.
+    Dhcp4Client(const State& state = SELECTING);
 
     /// @brief Creates a new client that communicates with a specified server.
     ///
     /// @param srv An instance of the DHCPv4 server to be used.
-    Dhcp4Client(boost::shared_ptr<NakedDhcpv4Srv>& srv);
+    /// @param state Initial client's state.
+    Dhcp4Client(boost::shared_ptr<NakedDhcpv4Srv>& srv,
+                const State& state = SELECTING);
 
     /// @brief Creates a lease for the client using the specified address
     /// and valid lifetime.
@@ -101,6 +120,31 @@ public:
     /// @param valid_lft Valid lifetime.
     void createLease(const asiolink::IOAddress& addr, const uint32_t valid_lft);
 
+    /// @brief Sends DHCPDISCOVER message to the server and receives response.
+    ///
+    /// The message being sent to the server includes Parameter Request List
+    /// option if any options to be requested have been specified using the
+    /// @c requestOptions or @c requestOption methods.
+    ///
+    /// The configuration returned by the server in the DHCPOFFER message is
+    /// NOT stored in the client configuration: @c config_.
+    ///
+    /// @param requested_addr A pointer to the IP Address to be sent in the
+    /// Requested IP Address option or NULL if the option should not be
+    /// included.
+    void doDiscover(const boost::shared_ptr<asiolink::IOAddress>&
+                    requested_addr = boost::shared_ptr<asiolink::IOAddress>());
+
+    /// @brief Perform 4-way exchange with a server.
+    ///
+    /// This method calls @c doDiscover and @c doRequest to perform the 4-way
+    /// exchange with the server.
+    ///
+    /// @param requested_addr A pointer to the address to be requested using the
+    /// Requested IP Address option.
+    void doDORA(const boost::shared_ptr<asiolink::IOAddress>&
+                requested_addr = boost::shared_ptr<asiolink::IOAddress>());
+
     /// @brief Sends DHCPINFORM message to the server and receives response.
     ///
     /// This function simulates sending the DHCPINFORM message to the server
@@ -121,6 +165,32 @@ public:
     /// an error occurs.
     void doInform(const bool set_ciaddr = true);
 
+    /// @brief Sends DHCPREQUEST Message to the server and receives a response.
+    ///
+    /// This method simulates sending the DHCPREQUEST message to the server and
+    /// receiving a response. The DHCPREQUEST message can be used by the client
+    /// being in various states:
+    /// - SELECTING - client is trying to obtain a new lease and it has selected
+    /// the server using the DHCPDISCOVER.
+    /// - INIT-REBOOT - client cached an address it was previously using and is
+    /// now trying to verify if this address is still valid.
+    /// - RENEW - client's renewal timer has passed and the client is trying to
+    /// extend the lifetime of the lease.
+    /// - REBIND - client's rebind timer has passed and the client is trying to
+    /// extend the lifetime of the lease from any server.
+    ///
+    /// Depending on the state that the client is in, different combinations of
+    /// - ciaddr
+    /// - Requested IP Address option
+    /// - server identifier
+    /// are used (as per RFC2131, section 4.3.2). Therefore, the unit tests
+    /// must setthe appropriate state of the client prior to calling this
+    /// method using the @c setState function.
+    ///
+    /// When the server returns the DHCPACK the configuration carried in the
+    /// DHCPACK message is applied and can be obtained from the @c config_.
+    void doRequest();
+
     /// @brief Generates a hardware address used by the client.
     ///
     /// It assigns random values to the bytes of the hardware address.
@@ -195,6 +265,16 @@ public:
         dest_addr_ = dest_addr;
     }
 
+    /// @brief Sets client state.
+    ///
+    /// Depending on the current state the client's behavior is different
+    /// when sending Request messages as per RFC2131, section 4.3.2.
+    ///
+    /// @param state New client's state.
+    void setState(const State& state) {
+        state_ = state;
+    }
+
     /// @brief Simulate sending messages through a relay.
     ///
     /// @param use Parameter which 'true' value indicates that client should
@@ -216,6 +296,12 @@ public:
 
 private:
 
+    /// @brief Creates and adds Requested IP Address option to the client's
+    /// query.
+    ///
+    /// @param addr Address to be added in the Requested IP Address option.
+    void addRequestedAddress(const asiolink::IOAddress& addr);
+
     /// @brief Stores configuration received from the server.
     ///
     /// This methods stores the configuration obtained from the DHCP server
@@ -236,6 +322,14 @@ private:
     /// @return An instance of the message created.
     Pkt4Ptr createMsg(const uint8_t msg_type);
 
+    /// @brief Include PRL Option in the query message.
+    ///
+    /// This function creates the instance of the PRL (Parameter Request List)
+    /// option and adds option codes from the @c requested_options_ to it.
+    /// It later adds the PRL option to the @c context_.query_ message
+    /// if it is non-NULL.
+    void includePRL();
+
     /// @brief Simulates reception of the message from the server.
     ///
     /// @return Received message.
@@ -274,6 +368,9 @@ private:
     /// @brief Pointer to the server that the client is communicating with.
     boost::shared_ptr<NakedDhcpv4Srv> srv_;
 
+    /// @brief Current state of the client.
+    State state_;
+
     /// @brief Enable relaying messages to the server.
     bool use_relay_;
 };

+ 0 - 239
src/bin/dhcp4/tests/dhcp4_srv_unittest.cc

@@ -596,92 +596,6 @@ TEST_F(Dhcpv4SrvTest, DiscoverNoTimers) {
 //
 // constructed very simple DISCOVER message with:
 // - client-id option
-// - address set to specific value as hint
-//
-// expected returned OFFER message:
-// - copy of client-id
-// - server-id
-// - offered address
-TEST_F(Dhcpv4SrvTest, DiscoverHint) {
-    IfaceMgrTestConfig test_config(true);
-    IfaceMgr::instance().openSockets4();
-
-    boost::scoped_ptr<NakedDhcpv4Srv> srv;
-    ASSERT_NO_THROW(srv.reset(new NakedDhcpv4Srv(0)));
-    IOAddress hint("192.0.2.107");
-
-    Pkt4Ptr dis = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 1234));
-    dis->setRemoteAddr(IOAddress("192.0.2.1"));
-    OptionPtr clientid = generateClientId();
-    dis->addOption(clientid);
-    dis->setYiaddr(hint);
-    dis->setIface("eth1");
-
-    // Pass it to the server and get an offer
-    Pkt4Ptr offer = srv->processDiscover(dis);
-
-    // Check if we get response at all
-    checkResponse(offer, DHCPOFFER, 1234);
-
-    // Check that address was returned from proper range, that its lease
-    // lifetime is correct, that T1 and T2 are returned properly
-    checkAddressParams(offer, subnet_, true, true);
-
-    EXPECT_EQ(offer->getYiaddr(), hint);
-
-    // Check identifiers
-    checkServerId(offer, srv->getServerID());
-    checkClientId(offer, clientid);
-}
-
-
-// This test verifies that incoming DISCOVER can be handled properly, that an
-// OFFER is generated, that the response has an address and that address
-// really belongs to the configured pool.
-//
-// constructed very simple DISCOVER message with:
-// - address set to specific value as hint
-//
-// expected returned OFFER message:
-// - copy of client-id
-// - server-id
-// - offered address
-TEST_F(Dhcpv4SrvTest, DiscoverNoClientId) {
-    IfaceMgrTestConfig test_config(true);
-    IfaceMgr::instance().openSockets4();
-
-    boost::scoped_ptr<NakedDhcpv4Srv> srv;
-    ASSERT_NO_THROW(srv.reset(new NakedDhcpv4Srv(0)));
-    IOAddress hint("192.0.2.107");
-
-    Pkt4Ptr dis = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 1234));
-    dis->setRemoteAddr(IOAddress("192.0.2.1"));
-    dis->setYiaddr(hint);
-    dis->setHWAddr(generateHWAddr(6));
-    dis->setIface("eth1");
-
-    // Pass it to the server and get an offer
-    Pkt4Ptr offer = srv->processDiscover(dis);
-
-    // Check if we get response at all
-    checkResponse(offer, DHCPOFFER, 1234);
-
-    // Check that address was returned from proper range, that its lease
-    // lifetime is correct, that T1 and T2 are returned properly
-    checkAddressParams(offer, subnet_, true, true);
-
-    EXPECT_EQ(offer->getYiaddr(), hint);
-
-    // Check identifiers
-    checkServerId(offer, srv->getServerID());
-}
-
-// This test verifies that incoming DISCOVER can be handled properly, that an
-// OFFER is generated, that the response has an address and that address
-// really belongs to the configured pool.
-//
-// constructed very simple DISCOVER message with:
-// - client-id option
 // - address set to specific value as hint, but that hint is invalid
 //
 // expected returned OFFER message:
@@ -825,58 +739,6 @@ TEST_F(Dhcpv4SrvTest, discoverEchoClientId) {
     checkClientId(offer, clientid);
 }
 
-// This test verifies that incoming REQUEST can be handled properly, that an
-// ACK is generated, that the response has an address and that address
-// really belongs to the configured pool.
-//
-// constructed a single REQUEST message with:
-// - client-id option
-// - hwaddr information
-// - requested address (that the client received in DISCOVER/OFFER exchange)
-//
-// expected returned ACK message:
-// - copy of client-id
-// - server-id
-// - assigned address
-//
-// Test verifies that the lease is actually in the database.
-TEST_F(Dhcpv4SrvTest, RequestBasic) {
-    IfaceMgrTestConfig test_config(true);
-    IfaceMgr::instance().openSockets4();
-
-    boost::scoped_ptr<NakedDhcpv4Srv> srv;
-    ASSERT_NO_THROW(srv.reset(new NakedDhcpv4Srv(0)));
-
-    IOAddress hint("192.0.2.107");
-    Pkt4Ptr req = Pkt4Ptr(new Pkt4(DHCPREQUEST, 1234));
-    req->setRemoteAddr(IOAddress("192.0.2.1"));
-    OptionPtr clientid = generateClientId();
-    req->addOption(clientid);
-    req->setYiaddr(hint);
-    req->setIface("eth1");
-
-    // Pass it to the server and get an advertise
-    Pkt4Ptr ack = srv->processRequest(req);
-
-    // Check if we get response at all
-    checkResponse(ack, DHCPACK, 1234);
-    EXPECT_EQ(hint, ack->getYiaddr());
-
-    // Check that address was returned from proper range, that its lease
-    // lifetime is correct, that T1 and T2 are returned properly
-    checkAddressParams(ack, subnet_, true, true);
-
-    // Check identifiers
-    checkServerId(ack, srv->getServerID());
-    checkClientId(ack, clientid);
-
-    // Check that the lease is really in the database
-    Lease4Ptr l = checkLease(ack, clientid, req->getHWAddr(), hint);
-
-    ASSERT_TRUE(l);
-    LeaseMgrFactory::instance().deleteLease(l->addr_);
-}
-
 // Check that option 58 and 59 are not included if they are not specified.
 TEST_F(Dhcpv4SrvTest, RequestNoTimers) {
     IfaceMgrTestConfig test_config(true);
@@ -916,107 +778,6 @@ TEST_F(Dhcpv4SrvTest, RequestNoTimers) {
     checkClientId(ack, clientid);
 }
 
-// This test verifies that incoming REQUEST can be handled properly, that an
-// ACK is generated, that the response has an address and that address
-// really belongs to the configured pool.
-//
-// constructed 3 REQUEST messages with:
-// - client-id option (differs between messages)
-// - hwaddr information (differs between messages)
-//
-// expected returned ACK message:
-// - copy of client-id
-// - server-id
-// - assigned address (different for each client)
-TEST_F(Dhcpv4SrvTest, ManyRequests) {
-    IfaceMgrTestConfig test_config(true);
-    IfaceMgr::instance().openSockets4();
-
-    boost::scoped_ptr<NakedDhcpv4Srv> srv;
-    ASSERT_NO_THROW(srv.reset(new NakedDhcpv4Srv(0)));
-
-    const IOAddress req_addr1("192.0.2.105");
-    const IOAddress req_addr2("192.0.2.101");
-    const IOAddress req_addr3("192.0.2.109");
-    const IOAddress relay("192.0.2.1");
-
-    Pkt4Ptr req1 = Pkt4Ptr(new Pkt4(DHCPOFFER, 1234));
-    Pkt4Ptr req2 = Pkt4Ptr(new Pkt4(DHCPOFFER, 2345));
-    Pkt4Ptr req3 = Pkt4Ptr(new Pkt4(DHCPOFFER, 3456));
-
-    req1->setRemoteAddr(relay);
-    req2->setRemoteAddr(relay);
-    req3->setRemoteAddr(relay);
-
-    // Assign interfaces
-    req1->setIface("eth1");
-    req2->setIface("eth1");
-    req3->setIface("eth1");
-
-    req1->setYiaddr(req_addr1);
-    req2->setYiaddr(req_addr2);
-    req3->setYiaddr(req_addr3);
-
-    req1->setHWAddr(generateHWAddr(6));
-    req2->setHWAddr(generateHWAddr(7));
-    req3->setHWAddr(generateHWAddr(8));
-
-    // Different client-id sizes
-    OptionPtr clientid1 = generateClientId(4); // length 4
-    OptionPtr clientid2 = generateClientId(5); // length 5
-    OptionPtr clientid3 = generateClientId(6); // length 6
-
-    req1->addOption(clientid1);
-    req2->addOption(clientid2);
-    req3->addOption(clientid3);
-
-    // Pass it to the server and get an advertise
-    Pkt4Ptr ack1 = srv->processRequest(req1);
-    Pkt4Ptr ack2 = srv->processRequest(req2);
-    Pkt4Ptr ack3 = srv->processRequest(req3);
-
-    // Check if we get response at all
-    checkResponse(ack1, DHCPACK, 1234);
-    checkResponse(ack2, DHCPACK, 2345);
-    checkResponse(ack3, DHCPACK, 3456);
-
-    IOAddress addr1 = ack1->getYiaddr();
-    IOAddress addr2 = ack2->getYiaddr();
-    IOAddress addr3 = ack3->getYiaddr();
-
-    // Check that every client received the address it requested
-    EXPECT_EQ(req_addr1, addr1);
-    EXPECT_EQ(req_addr2, addr2);
-    EXPECT_EQ(req_addr3, addr3);
-
-    // Check that the assigned address is indeed from the configured pool
-    checkAddressParams(ack1, subnet_, true, true);
-    checkAddressParams(ack2, subnet_, true, true);
-    checkAddressParams(ack3, subnet_, true, true);
-
-    // Check DUIDs
-    checkServerId(ack1, srv->getServerID());
-    checkServerId(ack2, srv->getServerID());
-    checkServerId(ack3, srv->getServerID());
-    checkClientId(ack1, clientid1);
-    checkClientId(ack2, clientid2);
-    checkClientId(ack3, clientid3);
-
-    // Check that leases are in the database
-    Lease4Ptr l = checkLease(ack1, clientid1, req1->getHWAddr(), addr1);
-    EXPECT_TRUE(l);
-    l = checkLease(ack2, clientid2, req2->getHWAddr(), addr2);
-    l = checkLease(ack3, clientid3, req3->getHWAddr(), addr3);
-
-    // Finally check that the addresses offered are different
-    EXPECT_NE(addr1, addr2);
-    EXPECT_NE(addr2, addr3);
-    EXPECT_NE(addr3, addr1);
-    cout << "Offered address to client1=" << addr1 << endl;
-    cout << "Offered address to client2=" << addr2 << endl;
-    cout << "Offered address to client3=" << addr3 << endl;
-}
-
 // Checks whether echoing back client-id is controllable
 TEST_F(Dhcpv4SrvTest, requestEchoClientId) {
     IfaceMgrTestConfig test_config(true);

+ 352 - 0
src/bin/dhcp4/tests/dora_unittest.cc

@@ -0,0 +1,352 @@
+// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+#include <asiolink/io_address.h>
+#include <cc/data.h>
+#include <dhcp/dhcp4.h>
+#include <dhcp/tests/iface_mgr_test_config.h>
+#include <dhcp4/tests/dhcp4_test_utils.h>
+#include <dhcp4/tests/dhcp4_client.h>
+#include <boost/shared_ptr.hpp>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::data;
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+
+namespace {
+
+/// @brief Set of JSON configurations used throughout the DORA tests.
+///
+/// - Configuration 0:
+///   - Used for testing direct traffic
+///   - 1 subnet: 10.0.0.0/24
+///   - 1 pool: 10.0.0.10-10.0.0.100
+///   - Router option present: 10.0.0.200 and 10.0.0.201
+///   - Domain Name Server option present: 10.0.0.202, 10.0.0.203.
+///   - Log Servers option present: 192.0.2.200 and 192.0.2.201
+///   - Quotes Servers option present: 192.0.2.202, 192.0.2.203.
+///
+/// - Configuration 1:
+///   - Use for testing relayed messages
+///   - 1 subnet: 192.0.2.0/24
+///   - Router option present: 192.0.2.200 and 192.0.2.201
+///   - Domain Name Server option present: 192.0.2.202, 192.0.2.203.
+///   - Log Servers option present: 192.0.2.200 and 192.0.2.201
+///   - Quotes Servers option present: 192.0.2.202, 192.0.2.203.
+const char* DORA_CONFIGS[] = {
+// Configuration 0
+    "{ \"interfaces\": [ \"all\" ],"
+        "\"valid-lifetime\": 600,"
+        "\"subnet4\": [ { "
+        "    \"subnet\": \"10.0.0.0/24\", "
+        "    \"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\""
+        "    },"
+        "    {"
+        "        \"name\": \"domain-name-servers\","
+        "        \"code\": 6,"
+        "        \"data\": \"10.0.0.202,10.0.0.203\","
+        "        \"csv-format\": true,"
+        "        \"space\": \"dhcp4\""
+        "    },"
+        "    {"
+        "        \"name\": \"log-servers\","
+        "        \"code\": 7,"
+        "        \"data\": \"10.0.0.200,10.0.0.201\","
+        "        \"csv-format\": true,"
+        "        \"space\": \"dhcp4\""
+        "    },"
+        "    {"
+        "        \"name\": \"cookie-servers\","
+        "        \"code\": 8,"
+        "        \"data\": \"10.0.0.202,10.0.0.203\","
+        "        \"csv-format\": true,"
+        "        \"space\": \"dhcp4\""
+        "    } ]"
+        " } ]"
+    "}",
+
+// Configuration 1
+    "{ \"interfaces\": [ \"all\" ],"
+        "\"valid-lifetime\": 600,"
+        "\"subnet4\": [ { "
+        "    \"subnet\": \"192.0.2.0/24\", "
+        "    \"option-data\": [ {"
+        "        \"name\": \"routers\","
+        "        \"code\": 3,"
+        "        \"data\": \"192.0.2.200,192.0.2.201\","
+        "        \"csv-format\": true,"
+        "        \"space\": \"dhcp4\""
+        "    },"
+        "    {"
+        "        \"name\": \"domain-name-servers\","
+        "        \"code\": 6,"
+        "        \"data\": \"192.0.2.202,192.0.2.203\","
+        "        \"csv-format\": true,"
+        "        \"space\": \"dhcp4\""
+        "    },"
+        "    {"
+        "        \"name\": \"log-servers\","
+        "        \"code\": 7,"
+        "        \"data\": \"10.0.0.200,10.0.0.201\","
+        "        \"csv-format\": true,"
+        "        \"space\": \"dhcp4\""
+        "    },"
+        "    {"
+        "        \"name\": \"cookie-servers\","
+        "        \"code\": 8,"
+        "        \"data\": \"10.0.0.202,10.0.0.203\","
+        "        \"csv-format\": true,"
+        "        \"space\": \"dhcp4\""
+        "    } ]"
+        " } ]"
+    "}"
+};
+
+/// @brief Test fixture class for testing 4-way (DORA) exchanges.
+///
+/// @todo Currently there is a limit number of test cases covered here.
+/// In the future it is planned that the tests from the
+/// dhcp4_srv_unittest.cc will be migrated here and will use the
+/// @c Dhcp4Client class.
+class DORATest : public Dhcpv4SrvTest {
+public:
+
+    /// @brief Constructor.
+    ///
+    /// Sets up fake interfaces.
+    DORATest()
+        : Dhcpv4SrvTest(),
+          iface_mgr_test_config_(true) {
+        IfaceMgr::instance().openSockets4();
+    }
+
+    /// @brief Interface Manager's fake configuration control.
+    IfaceMgrTestConfig iface_mgr_test_config_;
+
+};
+
+/// This test verifies that the client in the SELECTING state can get
+/// an address when it doesn't request any specific address in the
+/// DHCPDISCOVER message.
+TEST_F(DORATest, selectingDoNotRequestAddress) {
+    Dhcp4Client client(Dhcp4Client::SELECTING);
+    // Configure DHCP server.
+    configure(DORA_CONFIGS[0], *client.getServer());
+
+    // Perform 4-way exchange with the server but to not request any
+    // specific address in the DHCPDISCOVER message.
+    ASSERT_NO_THROW(client.doDORA());
+
+    // Make sure that the server responded.
+    ASSERT_TRUE(client.getContext().response_);
+    Pkt4Ptr resp = client.getContext().response_;
+    // Make sure that the server has responded with DHCPACK.
+    ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+    // Response must not be relayed.
+    EXPECT_FALSE(resp->isRelayed());
+    // Make sure that the server id is present.
+    EXPECT_EQ("10.0.0.1", client.config_.serverid_.toText());
+    // Make sure that the client has got the lease with the requested address.
+    ASSERT_NE(client.config_.lease_.addr_.toText(), "0.0.0.0");
+}
+
+/// This test verifies that multiple clients may use the DHCPv4 server
+/// and obtain unique leases.
+TEST_F(DORATest, selectingMultipleClients) {
+    Dhcp4Client client(Dhcp4Client::SELECTING);
+    // Configure DHCP server.
+    configure(DORA_CONFIGS[0], *client.getServer());
+
+    // Get the first lease.
+    ASSERT_NO_THROW(client.doDORA());
+
+    // Make sure that the server responded.
+    Pkt4Ptr resp = client.getContext().response_;
+    ASSERT_TRUE(resp);
+    ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+    // Store the lease.
+    Lease4 lease1 = client.config_.lease_;
+
+    // Get the lease for a different client.
+    client.modifyHWAddr();
+    ASSERT_NO_THROW(client.doDORA());
+    // Make sure that the server responded.
+    resp = client.getContext().response_;
+    ASSERT_TRUE(resp);
+    ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+    // Store the lease.
+    Lease4 lease2 = client.config_.lease_;
+
+    // Get the lease for a different client.
+    client.modifyHWAddr();
+    ASSERT_NO_THROW(client.doDORA());
+    // Make sure that the server responded.
+    resp = client.getContext().response_;
+    ASSERT_TRUE(resp);
+    ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+    // Store the lease.
+    Lease4 lease3 = client.config_.lease_;
+
+    // Make sure that unique addresses have been assigned.
+    EXPECT_NE(lease1.addr_, lease2.addr_);
+    EXPECT_NE(lease2.addr_, lease3.addr_);
+    EXPECT_NE(lease1.addr_, lease3.addr_);
+}
+
+// This test verifies that the client in a SELECTING state can request
+// a specific address and that this address will be assigned when
+// available. It also tests that if the client requests an address which
+// is in use the client will get a different address.
+TEST_F(DORATest, selectingRequestAddress) {
+    Dhcp4Client client(Dhcp4Client::SELECTING);
+    // Configure DHCP server.
+    configure(DORA_CONFIGS[0], *client.getServer());
+
+    // Perform 4-way exchange with the server.
+    ASSERT_NO_THROW(client.doDORA(boost::shared_ptr<
+                                  IOAddress>(new IOAddress("10.0.0.50"))));
+
+    // Make sure that the server responded.
+    ASSERT_TRUE(client.getContext().response_);
+    Pkt4Ptr resp = client.getContext().response_;
+    // Make sure that the server has responded with DHCPACK.
+    ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+    // Response must not be relayed.
+    EXPECT_FALSE(resp->isRelayed());
+    // Make sure that the server id is present.
+    EXPECT_EQ("10.0.0.1", client.config_.serverid_.toText());
+    // Make sure that the client has got the lease with the requested address.
+    ASSERT_EQ("10.0.0.50", client.config_.lease_.addr_.toText());
+
+    // Simulate different client requesting the same address.
+    client.modifyHWAddr();
+    ASSERT_NO_THROW(client.doDORA(boost::shared_ptr<
+                                  IOAddress>(new IOAddress("10.0.0.50"))));
+    resp = client.getContext().response_;
+    // Make sure that the server responded.
+    ASSERT_TRUE(resp);
+    // Make sure that the server has responded with DHCPACK.
+    ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+    // Response must not be relayed.
+    EXPECT_FALSE(resp->isRelayed());
+    // Make sure that the server id is present.
+    EXPECT_EQ("10.0.0.1", client.config_.serverid_.toText());
+    // Make sure that the client has got some address.
+    EXPECT_NE(client.config_.lease_.addr_.toText(), "0.0.0.0");
+    // Make sure that the client has got a different address than requested
+    // as the requested one is already in use.
+    EXPECT_NE(client.config_.lease_.addr_.toText(), "10.0.0.50");
+}
+
+// This test verifies that the client will get the address that it has
+// been allocated when the client requests a different address.
+TEST_F(DORATest, selectingRequestNonMatchingAddress) {
+    Dhcp4Client client(Dhcp4Client::SELECTING);
+    // Configure DHCP server.
+    configure(DORA_CONFIGS[0], *client.getServer());
+
+    // Perform 4-way exchange with the server.
+    ASSERT_NO_THROW(client.doDORA(boost::shared_ptr<
+                                  IOAddress>(new IOAddress("10.0.0.50"))));
+
+    // Make sure that the server responded.
+    ASSERT_TRUE(client.getContext().response_);
+    Pkt4Ptr resp = client.getContext().response_;
+    // Make sure that the server has responded with DHCPACK.
+    ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+    // Response must not be relayed.
+    EXPECT_FALSE(resp->isRelayed());
+    // Make sure that the server id is present.
+    EXPECT_EQ("10.0.0.1", client.config_.serverid_.toText());
+    // Make sure that the client has got the lease with the requested address.
+    ASSERT_EQ("10.0.0.50", client.config_.lease_.addr_.toText());
+
+    // Let's request a different address. The server should respond with
+    // the one that the client already has allocated.
+    ASSERT_NO_THROW(client.doDORA(boost::shared_ptr<
+                                  IOAddress>(new IOAddress("10.0.0.80"))));
+    // Make sure that the server responded.
+    ASSERT_TRUE(client.getContext().response_);
+    resp = client.getContext().response_;
+    // Make sure that the server has responded with DHCPACK.
+    ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+    // Response must not be relayed.
+    EXPECT_FALSE(resp->isRelayed());
+    // Make sure that the server id is present.
+    EXPECT_EQ("10.0.0.1", client.config_.serverid_.toText());
+    // Make sure that the client has got the lease with the address that
+    // the client has recorded in the lease database.
+    EXPECT_EQ("10.0.0.50", client.config_.lease_.addr_.toText());
+}
+
+// Test that the client in the INIT-REBOOT state can request the IP
+// address it has and the address is returned. Also, check that if
+// if the client requests in valid address the server sends a DHCPNAK.
+TEST_F(DORATest, InitRebootRequest) {
+    Dhcp4Client client(Dhcp4Client::SELECTING);
+    // Configure DHCP server.
+    configure(DORA_CONFIGS[0], *client.getServer());
+    // Obtain a lease from the server using the 4-way exchange.
+    ASSERT_NO_THROW(client.doDORA(boost::shared_ptr<
+                                  IOAddress>(new IOAddress("10.0.0.50"))));
+    // Make sure that the server responded.
+    ASSERT_TRUE(client.getContext().response_);
+    Pkt4Ptr resp = client.getContext().response_;
+    // Make sure that the server has responded with DHCPACK.
+    ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+    // Response must not be relayed.
+    EXPECT_FALSE(resp->isRelayed());
+    // Make sure that the server id is present.
+    EXPECT_EQ("10.0.0.1", client.config_.serverid_.toText());
+    // Make sure that the client has got the lease with the requested address.
+    ASSERT_EQ("10.0.0.50", client.config_.lease_.addr_.toText());
+
+    // Client has a lease in the database. Let's transition the client
+    // to the INIT_REBOOT state so as the client can request the cached
+    // lease using the DHCPREQUEST message.
+    client.setState(Dhcp4Client::INIT_REBOOT);
+    ASSERT_NO_THROW(client.doRequest());
+
+    // Make sure that the server responded.
+    ASSERT_TRUE(client.getContext().response_);
+    resp = client.getContext().response_;
+    // Make sure that the server has responded with DHCPACK.
+    ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+    // Response must not be relayed.
+    EXPECT_FALSE(resp->isRelayed());
+    // Make sure that the server id is present.
+    EXPECT_EQ("10.0.0.1", client.config_.serverid_.toText());
+    // Make sure that the client has got the lease with the requested address.
+    ASSERT_EQ("10.0.0.50", client.config_.lease_.addr_.toText());
+
+    // Try to request a different address than the client has. The server
+    // should respond with DHCPNAK.
+    client.config_.lease_.addr_ = IOAddress("10.0.0.30");
+    ASSERT_NO_THROW(client.doRequest());
+    // Make sure that the server responded.
+    ASSERT_TRUE(client.getContext().response_);
+    resp = client.getContext().response_;
+    EXPECT_EQ(DHCPNAK, static_cast<int>(resp->getType()));
+}
+
+} // end of anonymous namespace

+ 1 - 1
src/bin/dhcp4/tests/inform_unittest.cc

@@ -28,7 +28,7 @@ using namespace isc::dhcp::test;
 
 namespace {
 
-/// @brief Set of JSON configurations used throughout the Rebind tests.
+/// @brief Set of JSON configurations used throughout the Inform tests.
 ///
 /// - Configuration 0:
 ///   - Used for testing direct traffic