Browse Source

[master] Merge branch 'trac3981'

Conflicts:
	src/bin/dhcp4/tests/Makefile.am
Tomek Mrugalski 9 years ago
parent
commit
c14a63c0d3

+ 21 - 0
doc/guide/dhcp4-srv.xml

@@ -2718,6 +2718,27 @@ It is merely echoed by the server
 
 
     </section>
     </section>
 
 
+    <section id="dhcp4-decline">
+      <title>Duplicate Addresses (DHCPDECLINE support)</title>
+      <note>
+        <para>@todo: Full text will be added as part of #3990.</para>
+      </note>
+
+      <para>
+        The server does not decrease assigned-addresses statistics
+        when DHCPDECLINE is received and processed successfully. While
+        technically a declined address is no longer assigned, the primary usage
+        of the assigned-addresses statistic is to monitor pool utilization. Most
+        people would forget to include declined-addresses in the calculation,
+        and simply do assigned-addresses/total-addresses. This would have a bias
+        towards under-representing pool utilization. As this has a
+        potential for major issues, we decided not to decrease assigned
+        addresses immediately after receiving DHCPDECLINE, but to do
+        it later when we recover the address back to the available pool.
+      </para>
+
+    </section>
+
     <section id="dhcp4-stats">
     <section id="dhcp4-stats">
       <title>Statistics in DHCPv4 server</title>
       <title>Statistics in DHCPv4 server</title>
       <note>
       <note>

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

@@ -177,6 +177,29 @@ This message is printed when DHCPv4 server disables an interface from being
 used to receive DHCPv4 traffic. Sockets on this interface will not be opened
 used to receive DHCPv4 traffic. Sockets on this interface will not be opened
 by the Interface Manager until interface is enabled.
 by the Interface Manager until interface is enabled.
 
 
+% DHCP4_DECLINE_LEASE Received DHCPDECLINE for addr %1 from client %2. The lease will be unavailable for %3 seconds.
+This informational message is printed when a client received an address, but
+discovered that it is being used by some other device and notified the server by
+sending a DHCPDECLINE message. The server checked that this address really was
+leased to the client and marked this address as unusable for a certain
+amount of time. This message may indicate a misconfiguration in a network,
+as there is either a buggy client or more likely a device that is using an
+address that it is not supposed to. The server will fully recover from this
+situation, but if the underlying problem of a misconfigured or rogue device
+is not solved, this address may be declined again in the future.
+
+% DHCP4_DECLINE_LEASE_NOT_FOUND Received DHCPDECLINE for addr %1 from client %2, but no such lease found.
+This warning message indicates that a client reported that his address was
+detected as a duplicate (i.e. another device in the network is using this address).
+However, the server does not have a record for this address. This may indicate
+a client's error or a server's purged database.
+
+% DHCP4_DECLINE_LEASE_MISMATCH Received DHCPDECLINE for addr %1 from client %2, but the data doesn't match: received hwaddr: %3, lease hwaddr: %4, received client-id: %5, lease client-id: %6
+This informational message means that a client attempted to report his address
+as declined (i.e. used by unknown entity). The server has information about
+a lease for that address, but the client's hardware address or client identifier
+does not match the server's stored information. The client's request will be ignored.
+
 % DHCP4_DHCID_COMPUTE_ERROR failed to compute the DHCID for lease: %1, reason: %2
 % DHCP4_DHCID_COMPUTE_ERROR failed to compute the DHCID for lease: %1, reason: %2
 This error message is logged when the attempt to compute DHCID for a specified
 This error message is logged when the attempt to compute DHCID for a specified
 lease has failed. The lease details and reason for failure is logged in the
 lease has failed. The lease details and reason for failure is logged in the

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

@@ -1831,8 +1831,120 @@ Dhcpv4Srv::processRelease(Pkt4Ptr& release) {
 }
 }
 
 
 void
 void
-Dhcpv4Srv::processDecline(Pkt4Ptr&) {
-    /// @todo Implement this (also see ticket #3116)
+Dhcpv4Srv::processDecline(Pkt4Ptr& decline) {
+
+    // Server-id is mandatory in DHCPDECLINE (see table 5, RFC2131)
+    /// @todo Uncomment this (see ticket #3116)
+    // sanityCheck(decline, MANDATORY);
+
+    // Client is supposed to specify the address being declined in
+    // Requested IP address option, but must not set its ciaddr.
+    // (again, see table 5 in RFC2131).
+
+    OptionCustomPtr opt_requested_address = boost::dynamic_pointer_cast<
+        OptionCustom>(decline->getOption(DHO_DHCP_REQUESTED_ADDRESS));
+    if (!opt_requested_address) {
+
+        isc_throw(RFCViolation, "Mandatory 'Requested IP address' option missing"
+                  "in DHCPDECLINE sent from " << decline->getLabel());
+    }
+    IOAddress addr(opt_requested_address->readAddress());
+
+    // We could also extract client's address from ciaddr, but that's clearly
+    // against RFC2131.
+
+    // Now we need to check whether this address really belongs to the client
+    // that attempts to decline it.
+    const Lease4Ptr lease = LeaseMgrFactory::instance().getLease4(addr);
+
+    if (!lease) {
+        // Client tried to decline an address, but we don't have a lease for
+        // that address. Let's ignore it.
+        //
+        // We could assume that we're recovering from a mishandled migration
+        // to a new server and mark the address as declined, but the window of
+        // opportunity for that to be useful is small and the attack vector
+        // would be pretty severe.
+        LOG_WARN(dhcp4_logger, DHCP4_DECLINE_LEASE_NOT_FOUND)
+            .arg(addr.toText()).arg(decline->getLabel());
+        return;
+    }
+
+    // Get client-id, if available.
+    OptionPtr opt_clientid = decline->getOption(DHO_DHCP_CLIENT_IDENTIFIER);
+    ClientIdPtr client_id;
+    if (opt_clientid) {
+        client_id.reset(new ClientId(opt_clientid->getData()));
+    }
+
+    // Check if the client attempted to decline a lease it doesn't own.
+    if (!lease->belongsToClient(decline->getHWAddr(), client_id)) {
+
+        // Get printable hardware addresses
+        string client_hw = decline->getHWAddr() ?
+            decline->getHWAddr()->toText(false) : "(none)";
+        string lease_hw = lease->hwaddr_ ?
+            lease->hwaddr_->toText(false) : "(none)";
+
+        // Get printable client-ids
+        string client_id_txt = client_id ? client_id->toText() : "(none)";
+        string lease_id_txt = lease->client_id_ ?
+            lease->client_id_->toText() : "(none)";
+
+        // Print the warning and we're done here.
+        LOG_WARN(dhcp4_logger, DHCP4_DECLINE_LEASE_MISMATCH)
+            .arg(addr.toText()).arg(decline->getLabel())
+            .arg(client_hw).arg(lease_hw).arg(client_id_txt).arg(lease_id_txt);
+
+        return;
+    }
+
+    // Ok, all is good. The client is reporting its own address. Let's
+    // process it.
+    declineLease(lease, decline->getLabel());
+}
+
+void
+Dhcpv4Srv::declineLease(const Lease4Ptr& lease, const std::string& descr) {
+
+    // Clean up DDNS, if needed.
+    if (CfgMgr::instance().ddnsEnabled()) {
+        // Remove existing DNS entries for the lease, if any.
+        // queueNameChangeRequest will do the necessary checks and will
+        // skip the update, if not needed.
+        queueNameChangeRequest(isc::dhcp_ddns::CHG_REMOVE, lease);
+    }
+
+    // Bump up the statistics.
+
+    // Per subnet declined addresses counter.
+    StatsMgr::instance().addValue(
+        StatsMgr::generateName("subnet", lease->subnet_id_, "declined-addresses"),
+        static_cast<int64_t>(1));
+
+    // Global declined addresses counter.
+    StatsMgr::instance().addValue("declined-addresses", static_cast<int64_t>(1));
+
+    // We do not want to decrease the assigned-addresses at this time. While
+    // technically a declined address is no longer allocated, the primary usage
+    // of the assigned-addresses statistic is to monitor pool utilization. Most
+    // people would forget to include declined-addresses in the calculation,
+    // and simply do assigned-addresses/total-addresses. This would have a bias
+    // towards under-representing pool utilization, if we decreased allocated
+    // immediately after receiving DHCPDECLINE, rather than later when we recover
+    // the address.
+
+    // @todo: Call hooks.
+
+    // We need to disassociate the lease from the client. Once we move a lease
+    // to declined state, it is no longer associated with the client in any
+    // way.
+    lease->decline(CfgMgr::instance().getCurrentCfg()->getDeclinePeriod());
+
+    LeaseMgrFactory::instance().updateLease4(lease);
+
+    LOG_INFO(dhcp4_logger, DHCP4_DECLINE_LEASE).arg(lease->addr_.toText())
+        .arg(descr).arg(lease->valid_lft_);
 }
 }
 
 
 Pkt4Ptr
 Pkt4Ptr

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

@@ -402,7 +402,11 @@ protected:
     /// @param release message received from client
     /// @param release message received from client
     void processRelease(Pkt4Ptr& release);
     void processRelease(Pkt4Ptr& release);
 
 
-    /// @brief Stub function that will handle incoming DHCPDECLINE messages.
+    /// @brief Process incoming DHCPDECLINE messages.
+    ///
+    /// This method processes incoming DHCPDECLINE. In particular, it extracts
+    /// Requested IP Address option, checks that the address really belongs to
+    /// the client and if it does, calls @ref declineLease.
     ///
     ///
     /// @param decline message received from client
     /// @param decline message received from client
     void processDecline(Pkt4Ptr& decline);
     void processDecline(Pkt4Ptr& decline);
@@ -535,6 +539,20 @@ private:
     /// server's response.
     /// server's response.
     void processHostnameOption(Dhcpv4Exchange& ex);
     void processHostnameOption(Dhcpv4Exchange& ex);
 
 
+    /// @brief Marks lease as declined.
+    ///
+    /// This method moves a lease to declined state with all the steps involved:
+    /// - trigger DNS removal (if necessary)
+    /// - disassociate the client information
+    /// - update lease in the database (switch to DECLINED state)
+    /// - increase necessary statistics
+    /// - call appropriate hook (@todo)
+    ///
+    /// @param lease lease to be declined
+    /// @param descr textual description of the client (will be used for logging)
+    void
+    declineLease(const Lease4Ptr& lease, const std::string& descr);
+
 protected:
 protected:
 
 
     /// @brief Creates NameChangeRequests which correspond to the lease
     /// @brief Creates NameChangeRequests which correspond to the lease

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

@@ -89,7 +89,7 @@ dhcp4_unittests_SOURCES += inform_unittest.cc
 dhcp4_unittests_SOURCES += dora_unittest.cc
 dhcp4_unittests_SOURCES += dora_unittest.cc
 dhcp4_unittests_SOURCES += release_unittest.cc
 dhcp4_unittests_SOURCES += release_unittest.cc
 dhcp4_unittests_SOURCES += out_of_range_unittest.cc
 dhcp4_unittests_SOURCES += out_of_range_unittest.cc
-
+dhcp4_unittests_SOURCES += decline_unittest.cc
 dhcp4_unittests_SOURCES += kea_controller_unittest.cc
 dhcp4_unittests_SOURCES += kea_controller_unittest.cc
 
 
 nodist_dhcp4_unittests_SOURCES = marker_file.h test_libraries.h
 nodist_dhcp4_unittests_SOURCES = marker_file.h test_libraries.h

+ 313 - 0
src/bin/dhcp4/tests/decline_unittest.cc

@@ -0,0 +1,313 @@
+// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// 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 <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/subnet_id.h>
+#include <dhcp4/tests/dhcp4_test_utils.h>
+#include <dhcp4/tests/dhcp4_client.h>
+#include <stats/stats_mgr.h>
+#include <boost/shared_ptr.hpp>
+#include <sstream>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::data;
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+using namespace isc::stats;
+
+namespace {
+
+/// @brief Set of JSON configurations used throughout the Decline tests.
+///
+/// - Configuration 0:
+///   - Used for testing Decline message processing
+///   - 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
+const char* DECLINE_CONFIGS[] = {
+// Configuration 0
+    "{ \"interfaces-config\": {"
+        "      \"interfaces\": [ \"*\" ]"
+        "},"
+        "\"valid-lifetime\": 600,"
+        "\"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\""
+        "    } ]"
+        " } ]"
+    "}"
+};
+
+/// @brief Test fixture class for testing DHCPDECLINE message handling.
+///
+/// @todo This class is very similar to ReleaseTest. Maybe we could
+/// merge those two classes one day and use derived classes?
+class DeclineTest : public Dhcpv4SrvTest {
+public:
+
+    enum ExpectedResult {
+        SHOULD_PASS, // pass = accept decline, move lease to declined state.
+        SHOULD_FAIL  // fail = reject the decline
+    };
+
+    /// @brief Constructor.
+    ///
+    /// Sets up fake interfaces.
+    DeclineTest()
+        : Dhcpv4SrvTest(),
+          iface_mgr_test_config_(true) {
+        IfaceMgr::instance().openSockets4();
+    }
+
+    /// @brief Performs 4-way exchange to obtain new lease.
+    ///
+    /// This is used as a preparatory step for Decline operation.
+    ///
+    /// @param client Client to be used to obtain a lease.
+    void acquireLease(Dhcp4Client& client);
+
+    /// @brief Tests if the acquired lease is or is not declined.
+    ///
+    /// @param hw_address_1 HW Address to be used to acquire the lease.
+    /// @param client_id_1 Client id to be used to acquire the lease.
+    /// @param hw_address_2 HW Address to be used to decline the lease.
+    /// @param client_id_2 Client id to be used to decline the lease.
+    /// @param expected_result SHOULD_PASS if the lease is expected to
+    /// be successfully declined, or SHOULD_FAIL if the lease is expected
+    /// to not be declined.
+    void acquireAndDecline(const std::string& hw_address_1,
+                           const std::string& client_id_1,
+                           const std::string& hw_address_2,
+                           const std::string& client_id_2,
+                           ExpectedResult expected_result);
+
+    /// @brief Interface Manager's fake configuration control.
+    IfaceMgrTestConfig iface_mgr_test_config_;
+
+};
+
+void
+DeclineTest::acquireLease(Dhcp4Client& client) {
+    // 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");
+    Lease4Ptr lease = LeaseMgrFactory::instance().getLease4(client.config_.lease_.addr_);
+    ASSERT_TRUE(lease);
+}
+
+void
+DeclineTest::acquireAndDecline(const std::string& hw_address_1,
+                               const std::string& client_id_1,
+                               const std::string& hw_address_2,
+                               const std::string& client_id_2,
+                               ExpectedResult expected_result) {
+
+    // Let's get the subnet-id and generate statistics name out of it.
+    const Subnet4Collection* subnets =
+        CfgMgr::instance().getCurrentCfg()->getCfgSubnets4()->getAll();
+    ASSERT_EQ(1, subnets->size());
+    std::stringstream name;
+    name << "subnet[" << subnets->at(0)->getID() << "].declined-addresses";
+
+    // Set the subnet specific statistic explicitly to zero.
+    isc::stats::StatsMgr::instance().setValue(name.str(), static_cast<int64_t>(0));
+
+    // Set this global statistic explicitly to zero.
+    isc::stats::StatsMgr::instance().setValue("declined-addresses",
+                                              static_cast<int64_t>(0));
+
+    // Ok, do the normal lease aquisition.
+    CfgMgr::instance().clear();
+    Dhcp4Client client(Dhcp4Client::SELECTING);
+    // Configure DHCP server.
+    configure(DECLINE_CONFIGS[0], *client.getServer());
+    // Explicitly set the client id.
+    client.includeClientId(client_id_1);
+    // Explicitly set the HW address.
+    client.setHWAddress(hw_address_1);
+    // Perform 4-way exchange to obtain a new lease.
+    acquireLease(client);
+
+    // Check the declined-addresses (subnet) statistic before the Decline operation.
+    ObservationPtr declined_cnt = StatsMgr::instance().getObservation(name.str());
+    ASSERT_TRUE(declined_cnt);
+    uint64_t before = declined_cnt->getInteger().first;
+
+    // Check the global declined-addresses statistic before the Decline.
+    ObservationPtr declined_global = StatsMgr::instance()
+        .getObservation("declined-addresses");
+    ASSERT_TRUE(declined_global);
+    uint64_t before_global = declined_cnt->getInteger().first;
+
+    // Remember the acquired address.
+    IOAddress declined_address = client.config_.lease_.addr_;
+
+    // Explicitly set the client id for DHCPDECLINE.
+    client.includeClientId(client_id_2);
+    // Explicitly set the HW address for DHCPDECLINE.
+    client.setHWAddress(hw_address_2);
+
+    // Send the decline and make sure that the lease is removed from the
+    // server.
+    ASSERT_NO_THROW(client.doDecline());
+    Lease4Ptr lease = LeaseMgrFactory::instance().getLease4(declined_address);
+
+    declined_cnt = StatsMgr::instance().getObservation(name.str());
+    ASSERT_TRUE(declined_cnt);
+    uint64_t after = declined_cnt->getInteger().first;
+
+    declined_global = StatsMgr::instance().getObservation("declined-addresses");
+    ASSERT_TRUE(declined_global);
+    uint64_t after_global = declined_global->getInteger().first;
+
+    ASSERT_TRUE(lease);
+    // We check if the decline process was successful by checking if the
+    // lease is in the database and what is its state.
+    if (expected_result == SHOULD_PASS) {
+        EXPECT_EQ(Lease::STATE_DECLINED, lease->state_);
+
+        // The decline succeded, so the declined-addresses statistic should
+        // be increased by one
+        EXPECT_EQ(after, before + 1);
+
+        EXPECT_EQ(after_global, before_global + 1);
+    } else {
+        // the decline was supposed, to be rejected.
+        EXPECT_EQ(Lease::STATE_DEFAULT, lease->state_);
+
+        // The decline failed, so the declined-addresses should be the same
+        // as before
+        EXPECT_EQ(before, after);
+        EXPECT_EQ(before_global, after_global);
+    }
+}
+
+// This test checks that the client can acquire and decline the lease.
+TEST_F(DeclineTest, declineNoIdentifierChange) {
+    acquireAndDecline("01:02:03:04:05:06", "12:14",
+                      "01:02:03:04:05:06", "12:14",
+                      SHOULD_PASS);
+}
+
+// This test verifies the decline correctness in the following case:
+// - Client acquires new lease using HW address only
+// - Client sends the DHCPDECLINE with valid HW address and without
+//   client identifier.
+// - The server successfully declines the lease.
+TEST_F(DeclineTest, declineHWAddressOnly) {
+    acquireAndDecline("01:02:03:04:05:06", "",
+                      "01:02:03:04:05:06", "",
+                      SHOULD_PASS);
+}
+
+// This test verifies the decline correctness in the following case:
+// - Client acquires new lease using the client identifier and HW address
+// - Client sends the DHCPDECLINE with valid HW address but with no
+//   client identifier.
+// - The server successfully declines the lease.
+TEST_F(DeclineTest, declineNoClientId) {
+    acquireAndDecline("01:02:03:04:05:06", "12:14",
+                      "01:02:03:04:05:06", "",
+                      SHOULD_PASS);
+}
+
+// This test verifies the decline correctness in the following case:
+// - Client acquires new lease using HW address
+// - Client sends the DHCPDECLINE with valid HW address and some
+//   client identifier.
+// - The server identifies the lease using HW address and declines
+//   this lease.
+TEST_F(DeclineTest, declineNoClientId2) {
+    acquireAndDecline("01:02:03:04:05:06", "",
+                      "01:02:03:04:05:06", "12:14",
+                      SHOULD_PASS);
+}
+
+// This test checks the server's behavior in the following case:
+// - Client acquires new lease using the client identifier and HW address
+// - Client sends the DHCPDECLINE with the valid HW address but with invalid
+//   client identifier.
+// - The server should not remove the lease.
+TEST_F(DeclineTest, declineNonMatchingClientId) {
+    acquireAndDecline("01:02:03:04:05:06", "12:14",
+                      "01:02:03:04:05:06", "12:15:16",
+                      SHOULD_FAIL);
+}
+
+// This test checks the server's behavior in the following case:
+// - Client acquires new lease using client identifier and HW address
+// - Client sends the DHCPDECLINE with the same client identifier but
+//   different HW address.
+// - The server uses client identifier to find the client's lease and
+//   declines it.
+TEST_F(DeclineTest, declineNonMatchingHWAddress) {
+    acquireAndDecline("01:02:03:04:05:06", "12:14",
+                      "06:06:06:06:06:06", "12:14",
+                      SHOULD_PASS);
+}
+
+// This test checks the server's behavior in the following case:
+// - Client acquires new lease (address A).
+// - Client sends DHCPDECLINE with the requested IP address set to a different
+//   address B than it has acquired from the server.
+// - Server determines that the client is trying to decline a
+//   wrong address and will refuse to decline.
+TEST_F(DeclineTest, declineNonMatchingIPAddress) {
+    Dhcp4Client client(Dhcp4Client::SELECTING);
+    // Configure DHCP server.
+    configure(DECLINE_CONFIGS[0], *client.getServer());
+    // Perform 4-way exchange to obtain a new lease.
+    acquireLease(client);
+
+    // Remember the acquired address.
+    IOAddress leased_address = client.config_.lease_.addr_;
+
+    // Modify the client's address to force it to decline a different address
+    // than it has obtained from the server.
+    client.config_.lease_.addr_ = IOAddress(static_cast<uint32_t>(leased_address) + 1);
+
+    // Send DHCPDECLINE and make sure it was unsuccessful, i.e. the lease
+    // remains in the database.
+    ASSERT_NO_THROW(client.doDecline());
+    Lease4Ptr lease = LeaseMgrFactory::instance().getLease4(leased_address);
+    ASSERT_TRUE(lease);
+    EXPECT_EQ(Lease::STATE_DEFAULT, lease->state_);
+}
+
+} // end of anonymous namespace

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

@@ -102,6 +102,13 @@ Dhcp4Client::appendClientId() {
 }
 }
 
 
 void
 void
+Dhcp4Client::appendServerId() {
+    OptionPtr opt(new Option4AddrLst(DHO_DHCP_SERVER_IDENTIFIER,
+                                     config_.serverid_));
+    context_.query_->addOption(opt);
+}
+
+void
 Dhcp4Client::appendName() {
 Dhcp4Client::appendName() {
     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"
@@ -281,6 +288,34 @@ Dhcp4Client::doRelease() {
 }
 }
 
 
 void
 void
+Dhcp4Client::doDecline() {
+    if (config_.lease_.addr_ == IOAddress::IPV4_ZERO_ADDRESS()) {
+        isc_throw(Dhcp4ClientError, "failed to send the decline"
+                  " message because client doesn't have a lease");
+    }
+
+    context_.query_ = createMsg(DHCPDECLINE);
+
+    // Set ciaddr to 0.
+    context_.query_->setCiaddr(IOAddress("0.0.0.0"));
+
+    // Include Requested IP Address Option
+    addRequestedAddress(config_.lease_.addr_);
+
+    // Include client identifier.
+    appendClientId();
+
+    // Incluer server identifier.
+    appendServerId();
+
+    // Remove configuration.
+    config_.reset();
+
+    // Send the message to the server.
+    sendMsg(context_.query_);
+}
+
+void
 Dhcp4Client::doRequest() {
 Dhcp4Client::doRequest() {
     context_.query_ = createMsg(DHCPREQUEST);
     context_.query_ = createMsg(DHCPREQUEST);
 
 

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

@@ -172,6 +172,13 @@ public:
     /// The released lease is removed from the client's configuration.
     /// The released lease is removed from the client's configuration.
     void doRelease();
     void doRelease();
 
 
+
+    /// @brief Sends DHCPDECLINE Message to the server.
+    ///
+    /// This method simulates sending the DHCPDECLINE message to the server.
+    /// The released lease is removed from the client's configuration.
+    void doDecline();
+
     /// @brief Sends DHCPREQUEST Message to the server and receives a response.
     /// @brief Sends DHCPREQUEST Message to the server and receives a response.
     ///
     ///
     /// This method simulates sending the DHCPREQUEST message to the server and
     /// This method simulates sending the DHCPREQUEST message to the server and
@@ -383,6 +390,12 @@ private:
     /// option in the client's message to the server.
     /// option in the client's message to the server.
     void appendClientId();
     void appendClientId();
 
 
+    /// @brief Includes the Server Identifier option in the client's message.
+    ///
+    /// This function creates an instance of the Server Identifier option.
+    /// It uses whatever information is stored in config_.serverid_.
+    void appendServerId();
+
     /// @brief Includes FQDN or Hostname option in the client's message.
     /// @brief Includes FQDN or Hostname option in the client's message.
     ///
     ///
     /// This method checks if @c fqdn_ or @c hostname_ is specified and
     /// This method checks if @c fqdn_ or @c hostname_ is specified and

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

@@ -450,14 +450,6 @@ TEST_F(Dhcpv4SrvTest, processRelease) {
     EXPECT_NO_THROW(srv.processRelease(pkt));
     EXPECT_NO_THROW(srv.processRelease(pkt));
 }
 }
 
 
-TEST_F(Dhcpv4SrvTest, processDecline) {
-    NakedDhcpv4Srv srv;
-    Pkt4Ptr pkt(new Pkt4(DHCPDECLINE, 1234));
-
-    // Should not throw
-    EXPECT_NO_THROW(srv.processDecline(pkt));
-}
-
 // This test verifies that incoming DISCOVER can be handled properly, that an
 // This test verifies that incoming DISCOVER can be handled properly, that an
 // OFFER is generated, that the response has an address and that address
 // OFFER is generated, that the response has an address and that address
 // really belongs to the configured pool.
 // really belongs to the configured pool.

+ 19 - 0
src/lib/dhcpsrv/lease.cc

@@ -179,6 +179,20 @@ Lease4::belongsToClient(const HWAddrPtr& hw_address,
     return (false);
     return (false);
 }
 }
 
 
+void
+Lease4::decline(uint32_t probation_period) {
+    hwaddr_.reset(new HWAddr());
+    client_id_.reset();
+    t1_ = 0;
+    t2_ = 0;
+    cltt_ = time(NULL);
+    hostname_ = string("");
+    fqdn_fwd_ = false;
+    fqdn_rev_ = false;
+    state_ = STATE_DECLINED;
+    valid_lft_ = probation_period;
+}
+
 Lease4&
 Lease4&
 Lease4::operator=(const Lease4& other) {
 Lease4::operator=(const Lease4& other) {
     if (this != &other) {
     if (this != &other) {
@@ -261,6 +275,11 @@ Lease6::getDuidVector() const {
     return (duid_->getDuid());
     return (duid_->getDuid());
 }
 }
 
 
+void
+Lease6::decline(uint32_t ) {
+    /// @todo: implement this
+}
+
 std::string
 std::string
 Lease6::toText() const {
 Lease6::toText() const {
     ostringstream stream;
     ostringstream stream;

+ 26 - 0
src/lib/dhcpsrv/lease.h

@@ -200,6 +200,18 @@ struct Lease {
     /// The lease expiration time is a sum of a client last transmission time
     /// The lease expiration time is a sum of a client last transmission time
     /// and valid lifetime.
     /// and valid lifetime.
     int64_t getExpirationTime() const;
     int64_t getExpirationTime() const;
+
+    /// @brief Sets lease to DECLINED state.
+    ///
+    /// All client identifying parameters will be stripped off (HWaddr,
+    /// client_id, hostname), timers set to 0 (t1, t2), cltt will be set
+    /// to current time and valid_lft to parameter specified as probation
+    /// period. Note that This method only sets fields in the structure.
+    /// It is caller's responsibility to clean up DDNS, bump up stats,
+    /// log, call hooks ets.
+    ///
+    /// @param probation_period lease lifetime will be set to this value
+    virtual void decline(uint32_t probation_period) = 0;
 };
 };
 
 
 /// @brief Structure that holds a lease for IPv4 address
 /// @brief Structure that holds a lease for IPv4 address
@@ -386,6 +398,13 @@ struct Lease4 : public Lease {
     /// @return Textual represenation of lease data
     /// @return Textual represenation of lease data
     virtual std::string toText() const;
     virtual std::string toText() const;
 
 
+    /// @brief Sets IPv4 lease to declined state.
+    ///
+    /// See @ref Lease::decline for detailed description.
+    ///
+    /// @param probation_period valid lifetime will be set to this value
+    void decline(uint32_t probation_period);
+
     /// @todo: Add DHCPv4 failover related fields here
     /// @todo: Add DHCPv4 failover related fields here
 };
 };
 
 
@@ -495,6 +514,13 @@ struct Lease6 : public Lease {
     /// @return A reference to a vector holding a DUID.
     /// @return A reference to a vector holding a DUID.
     const std::vector<uint8_t>& getDuidVector() const;
     const std::vector<uint8_t>& getDuidVector() const;
 
 
+    /// @brief Sets IPv6 lease to declined state.
+    ///
+    /// See @ref Lease::decline for detailed description.
+    ///
+    /// @param probation_period valid lifetime will be set to this value
+    void decline(uint32_t probation_period);
+
     /// @brief Compare two leases for equality
     /// @brief Compare two leases for equality
     ///
     ///
     /// @param other lease6 object with which to compare
     /// @param other lease6 object with which to compare

+ 37 - 3
src/lib/dhcpsrv/tests/lease_unittest.cc

@@ -415,7 +415,7 @@ TEST_F(Lease4Test, toText) {
     const time_t current_time = 12345678;
     const time_t current_time = 12345678;
     Lease4 lease(IOAddress("192.0.2.3"), hwaddr_, clientid_, 3600, 123,
     Lease4 lease(IOAddress("192.0.2.3"), hwaddr_, clientid_, 3600, 123,
                  456, current_time, 789);
                  456, current_time, 789);
-    
+
     std::stringstream expected;
     std::stringstream expected;
     expected << "Address:       192.0.2.3\n"
     expected << "Address:       192.0.2.3\n"
              << "Valid life:    3600\n"
              << "Valid life:    3600\n"
@@ -445,6 +445,35 @@ TEST_F(Lease4Test, toText) {
     EXPECT_EQ(expected.str(), lease.toText());
     EXPECT_EQ(expected.str(), lease.toText());
 }
 }
 
 
+// Verify that decline() method properly clears up specific fields.
+TEST_F(Lease4Test, decline) {
+
+    const time_t current_time = 12345678;
+    Lease4 lease(IOAddress("192.0.2.3"), hwaddr_, clientid_, 3600, 123,
+                 456, current_time, 789);
+    lease.hostname_="foo.example.org";
+    lease.fqdn_fwd_ = true;
+    lease.fqdn_rev_ = true;
+
+    time_t now = time(NULL);
+
+    // Move lease to declined state and set its valid-lifetime to 123 seconds
+    lease.decline(123);
+    ASSERT_TRUE(lease.hwaddr_);
+    EXPECT_EQ("", lease.hwaddr_->toText(false));
+    EXPECT_FALSE(lease.client_id_);
+    EXPECT_EQ(0, lease.t1_);
+    EXPECT_EQ(0, lease.t2_);
+
+    EXPECT_TRUE(now <= lease.cltt_);
+    EXPECT_TRUE(lease.cltt_ <= now + 1);
+    EXPECT_EQ("", lease.hostname_);
+    EXPECT_FALSE(lease.fqdn_fwd_);
+    EXPECT_FALSE(lease.fqdn_rev_);
+    EXPECT_EQ(Lease::STATE_DECLINED, lease.state_);
+    EXPECT_EQ(123, lease.valid_lft_);
+}
+
 // Verify that the lease states are correctly returned in the textual format.
 // Verify that the lease states are correctly returned in the textual format.
 TEST_F(Lease4Test, stateToText) {
 TEST_F(Lease4Test, stateToText) {
     EXPECT_EQ("default", Lease4::statesToText(Lease::STATE_DEFAULT));
     EXPECT_EQ("default", Lease4::statesToText(Lease::STATE_DEFAULT));
@@ -741,6 +770,11 @@ TEST(Lease6Test, getDuidVector) {
     EXPECT_TRUE(returned_vec == duid_vec);
     EXPECT_TRUE(returned_vec == duid_vec);
 }
 }
 
 
+// Verify that decline() method properly clears up specific fields.
+TEST_F(Lease6Test, decline) {
+    /// @todo (see ticket 3981)
+}
+
 // Verify the behavior of the function which checks FQDN data for equality.
 // Verify the behavior of the function which checks FQDN data for equality.
 TEST(Lease6Test, hasIdenticalFqdn) {
 TEST(Lease6Test, hasIdenticalFqdn) {
     Lease6 lease = createLease6("myhost.example.com.", true, true);
     Lease6 lease = createLease6("myhost.example.com.", true, true);
@@ -765,12 +799,12 @@ TEST(Lease6Test, toText) {
 
 
     uint8_t llt[] = {0, 1, 2, 3, 4, 5, 6, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf};
     uint8_t llt[] = {0, 1, 2, 3, 4, 5, 6, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf};
     DuidPtr duid(new DUID(llt, sizeof(llt)));
     DuidPtr duid(new DUID(llt, sizeof(llt)));
-    
+
     Lease6 lease(Lease::TYPE_NA, IOAddress("2001:db8::1"), duid, 123456,
     Lease6 lease(Lease::TYPE_NA, IOAddress("2001:db8::1"), duid, 123456,
                  400, 800, 100, 200, 5678, hwaddr, 128);
                  400, 800, 100, 200, 5678, hwaddr, 128);
     lease.cltt_ = 12345678;
     lease.cltt_ = 12345678;
     lease.state_ = Lease::STATE_DECLINED;
     lease.state_ = Lease::STATE_DECLINED;
-    
+
     std::stringstream expected;
     std::stringstream expected;
     expected << "Type:          IA_NA(" << static_cast<int>(Lease::TYPE_NA) << ")\n"
     expected << "Type:          IA_NA(" << static_cast<int>(Lease::TYPE_NA) << ")\n"
              << "Address:       2001:db8::1\n"
              << "Address:       2001:db8::1\n"