Browse Source

[3747] Modification to how server identifies the client for Release.

This change comes along with the new tests for DHCPRELEASE in the
separate file, and having a separate test fixture.
Marcin Siodelski 10 years ago
parent
commit
9494936373

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

@@ -349,20 +349,6 @@ removed from the database during RELEASE message processing.
 This warning message is printed when client attempts to release a lease,
 This warning message is printed when client attempts to release a lease,
 but no such lease is known to the server.
 but no such lease is known to the server.
 
 
-% DHCP4_RELEASE_FAIL_WRONG_CLIENT_ID client (client-id %2) tried to release address %1, but it belongs to client (client-id %3)
-This warning message indicates that client tried to release an address
-that belongs to a different client. This should not happen in normal
-circumstances and may indicate a misconfiguration of the client.  However,
-since the client releasing the address will stop using it anyway, there
-is a good chance that the situation will correct itself.
-
-% DHCP4_RELEASE_FAIL_WRONG_HWADDR client (client-id %2) tried to release address %1, but sent from a wrong hardware address (%3)
-This warning message indicates that client tried to release an address
-that does belong to it, but the lease information was associated with
-a different hardware address. One possible reason for using different
-hardware address is that a cloned virtual machine was not updated and
-both clones use the same client-id.
-
 % DHCP4_REQUEST_CLASS_PROCESSING_FAILED client class specific processing failed for DHCPREQUEST
 % 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
 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.
 client class has reported a failure. The response packet will not be sent.

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

@@ -1579,26 +1579,9 @@ Dhcpv4Srv::processRelease(Pkt4Ptr& release) {
             return;
             return;
         }
         }
 
 
-        // Does the hardware address match? We don't want one client releasing
-        // second client's leases. Note that we're comparing the hardware
-        // addresses only, not hardware types or sources of the hardware
-        // addresses. Thus we don't use HWAddr::equals().
-        if (lease->hwaddr_->hwaddr_ != release->getHWAddr()->hwaddr_) {
-            /// @todo: Print hwaddr from lease as part of ticket #2589
-            LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL, DHCP4_RELEASE_FAIL_WRONG_HWADDR)
-                .arg(release->getCiaddr().toText())
-                .arg(client_id ? client_id->toText() : "(no client-id)")
-                .arg(release->getHWAddr()->toText());
-            return;
-        }
-
-        // Does the lease have client-id info? If it has, then check it with what
-        // the client sent us.
-        if (lease->client_id_ && client_id && *lease->client_id_ != *client_id) {
-            LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL, DHCP4_RELEASE_FAIL_WRONG_CLIENT_ID)
-                .arg(release->getCiaddr().toText())
-                .arg(client_id->toText())
-                .arg(lease->client_id_->toText());
+        if (!lease->belongsToClient(release->getHWAddr(), client_id)) {
+            /// @todo add debug message here when we merge the code with the
+            /// changes from #3806.
             return;
             return;
         }
         }
 
 

+ 4 - 2
src/bin/dhcp4/dhcp4_srv.h

@@ -397,7 +397,7 @@ protected:
     /// @return ACK or NAK message
     /// @return ACK or NAK message
     Pkt4Ptr processRequest(Pkt4Ptr& request);
     Pkt4Ptr processRequest(Pkt4Ptr& request);
 
 
-    /// @brief Stub function that will handle incoming RELEASE messages.
+    /// @brief Processes incoming DHCPRELEASE messages.
     ///
     ///
     /// In DHCPv4, server does not respond to RELEASE messages, therefore
     /// In DHCPv4, server does not respond to RELEASE messages, therefore
     /// this function does not return anything.
     /// this function does not return anything.
@@ -410,9 +410,11 @@ protected:
     /// @param decline message received from client
     /// @param decline message received from client
     void processDecline(Pkt4Ptr& decline);
     void processDecline(Pkt4Ptr& decline);
 
 
-    /// @brief Stub function that will handle incoming INFORM messages.
+    /// @brief Processes incoming DHCPINFORM messages.
     ///
     ///
     /// @param inform message received from client
     /// @param inform message received from client
+    ///
+    /// @return DHCPACK to be sent to the client.
     Pkt4Ptr processInform(Pkt4Ptr& inform);
     Pkt4Ptr processInform(Pkt4Ptr& inform);
 
 
     /// @brief Appends options requested by client.
     /// @brief Appends options requested by client.

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

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

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

@@ -254,6 +254,25 @@ Dhcp4Client::doInform(const bool set_ciaddr) {
 }
 }
 
 
 void
 void
+Dhcp4Client::doRelease() {
+    if (config_.lease_.addr_ == IOAddress::IPV4_ZERO_ADDRESS()) {
+        isc_throw(Dhcp4ClientError, "failed to send the release"
+                  " message because client doesn't have a lease");
+    }
+    context_.query_ = createMsg(DHCPRELEASE);
+    // Set ciaddr to the address which we want to release.
+    context_.query_->setCiaddr(config_.lease_.addr_);
+    // Include client identifier.
+    appendClientId();
+
+    // 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);
 
 

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

@@ -166,6 +166,12 @@ public:
     /// an error occurs.
     /// an error occurs.
     void doInform(const bool set_ciaddr = true);
     void doInform(const bool set_ciaddr = true);
 
 
+    /// @brief Sends DHCPRELEASE Message to the server.
+    ///
+    /// This method simulates sending the DHCPRELEASE message to the server.
+    /// The released lease is removed from the client's configuration.
+    void doRelease();
+
     /// @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

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

@@ -943,169 +943,6 @@ TEST_F(Dhcpv4SrvTest, sanityCheck) {
                  RFCViolation);
                  RFCViolation);
 }
 }
 
 
-// This test verifies that incoming (positive) RELEASE can be handled properly.
-// As there is no REPLY in DHCPv4, the only thing to verify here is that
-// the lease is indeed removed from the database.
-TEST_F(Dhcpv4SrvTest, ReleaseBasic) {
-    boost::scoped_ptr<NakedDhcpv4Srv> srv;
-    ASSERT_NO_THROW(srv.reset(new NakedDhcpv4Srv(0)));
-
-    const IOAddress addr("192.0.2.106");
-    const uint32_t temp_t1 = 50;
-    const uint32_t temp_t2 = 75;
-    const uint32_t temp_valid = 100;
-    const time_t temp_timestamp = time(NULL) - 10;
-
-    // Generate client-id also duid_
-    OptionPtr clientid = generateClientId();
-
-    // Check that the address we are about to use is indeed in pool
-    ASSERT_TRUE(subnet_->inPool(Lease::TYPE_V4, addr));
-
-    // Let's create a lease and put it in the LeaseMgr
-    uint8_t hwaddr_data[] = { 0, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe};
-    HWAddrPtr hwaddr(new HWAddr(hwaddr_data, sizeof(hwaddr_data), HTYPE_ETHER));
-    Lease4Ptr used(new Lease4(addr, hwaddr,
-                              &client_id_->getDuid()[0], client_id_->getDuid().size(),
-                              temp_valid, temp_t1, temp_t2, temp_timestamp,
-                              subnet_->getID()));
-    ASSERT_TRUE(LeaseMgrFactory::instance().addLease(used));
-
-    // Check that the lease is really in the database
-    Lease4Ptr l = LeaseMgrFactory::instance().getLease4(addr);
-    ASSERT_TRUE(l);
-
-    // Let's create a RELEASE
-    // Generate client-id also duid_
-    Pkt4Ptr rel = Pkt4Ptr(new Pkt4(DHCPRELEASE, 1234));
-    rel->setRemoteAddr(addr);
-    rel->setCiaddr(addr);
-    rel->addOption(clientid);
-    rel->addOption(srv->getServerID());
-    rel->setHWAddr(hwaddr);
-    rel->setIface("eth0");
-
-    // Pass it to the server and hope for a REPLY
-    // Note: this is no response to RELEASE in DHCPv4
-    EXPECT_NO_THROW(srv->processRelease(rel));
-
-    // The lease should be gone from LeaseMgr
-    l = LeaseMgrFactory::instance().getLease4(addr);
-    EXPECT_FALSE(l);
-
-    // Try to get the lease by hardware address
-    Lease4Collection leases = LeaseMgrFactory::instance().getLease4(*hwaddr);
-    EXPECT_EQ(leases.size(), 0);
-
-    // Try to get it by hw/subnet_id combination
-    l = LeaseMgrFactory::instance().getLease4(*hwaddr, subnet_->getID());
-    EXPECT_FALSE(l);
-
-    // Try by client-id
-    leases = LeaseMgrFactory::instance().getLease4(*client_id_);
-    EXPECT_EQ(leases.size(), 0);
-
-    // Try by client-id/subnet-id
-    l = LeaseMgrFactory::instance().getLease4(*client_id_, subnet_->getID());
-    EXPECT_FALSE(l);
-
-    // Ok, the lease is *really* not there.
-}
-
-// This test verifies that incoming (invalid) RELEASE can be handled properly.
-//
-// This test checks 3 scenarios:
-// 1. there is no such lease at all
-// 2. there is such a lease, but it is assigned to a different IAID
-// 3. there is such a lease, but it belongs to a different client
-TEST_F(Dhcpv4SrvTest, ReleaseReject) {
-    boost::scoped_ptr<NakedDhcpv4Srv> srv;
-    ASSERT_NO_THROW(srv.reset(new NakedDhcpv4Srv(0)));
-
-    const IOAddress addr("192.0.2.106");
-    const uint32_t t1 = 50;
-    const uint32_t t2 = 75;
-    const uint32_t valid = 100;
-    const time_t timestamp = time(NULL) - 10;
-
-    // Let's create a lease and put it in the LeaseMgr
-    uint8_t bogus_mac_addr[] = { 0, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe};
-    HWAddrPtr bogus_hw(new HWAddr(bogus_mac_addr, sizeof(bogus_mac_addr), HTYPE_ETHER));
-    OptionPtr bogus_clientid = generateClientId(7); // different length
-
-    // Generate client-id also duid_
-    OptionPtr clientid = generateClientId();
-
-    // Check that the address we are about to use is indeed in pool
-    ASSERT_TRUE(subnet_->inPool(Lease::TYPE_V4, addr));
-
-    // Let's create a RELEASE
-    // Generate client-id also duid_
-    Pkt4Ptr rel = Pkt4Ptr(new Pkt4(DHCPRELEASE, 1234));
-    rel->setRemoteAddr(addr);
-    rel->setCiaddr(addr);
-    rel->addOption(clientid);
-    rel->addOption(srv->getServerID());
-    rel->setHWAddr(bogus_hw);
-    rel->setIface("eth0");
-
-    // Case 1: No lease known to server
-    SCOPED_TRACE("CASE 1: Lease is not known to the server");
-
-    // There is nothing to check here. The lease is not there and server does
-    // not send anything back. This case is enumerated here just for keeping
-    // parity with similar test in DHCPv6.
-    EXPECT_NO_THROW(srv->processRelease(rel));
-
-    // CASE 2: Lease is known and belongs to this client, but to a different hardware
-    SCOPED_TRACE("CASE 2: Lease is known and belongs to this client, but uses different HW addr");
-
-    // Let's create a lease and put it in the LeaseMgr
-    uint8_t mac_addr[] = { 0, 0x1, 0x2, 0x3, 0x4, 0x5};
-    HWAddrPtr hw(new HWAddr(mac_addr, sizeof(mac_addr), HTYPE_ETHER));
-    Lease4Ptr used(new Lease4(addr, hw,
-                              &client_id_->getDuid()[0], client_id_->getDuid().size(),
-                              valid, t1, t2, timestamp, subnet_->getID()));
-    ASSERT_TRUE(LeaseMgrFactory::instance().addLease(used));
-    // Check that the lease is really in the database
-    Lease4Ptr l = LeaseMgrFactory::instance().getLease4(addr);
-    ASSERT_TRUE(l);
-
-    rel->setHWAddr(bogus_hw);
-
-    EXPECT_NO_THROW(srv->processRelease(rel));
-
-    // Check that the lease was not removed (due to hardware address mis-match)
-    l = LeaseMgrFactory::instance().getLease4(addr);
-    ASSERT_TRUE(l);
-
-    // CASE 3: Lease belongs to a client with different client-id
-    SCOPED_TRACE("CASE 3: Lease belongs to a client with different client-id");
-
-    rel->setHWAddr(hw); // proper HW address this time
-    rel->delOption(DHO_DHCP_CLIENT_IDENTIFIER);
-    rel->addOption(bogus_clientid); // but invalid client-id
-
-    OptionPtr x = rel->getOption(DHO_DHCP_CLIENT_IDENTIFIER);
-
-    EXPECT_NO_THROW(srv->processRelease(rel));
-
-    // Check that the lease is still there
-    l = LeaseMgrFactory::instance().getLease4(addr);
-    ASSERT_TRUE(l);
-
-    // Final sanity check. Verify that with valid hw and client-id release is successful
-    rel->delOption(DHO_DHCP_CLIENT_IDENTIFIER);
-    rel->addOption(clientid);
-
-    // It should work this time
-    EXPECT_NO_THROW(srv->processRelease(rel));
-
-    // Check that the lease is not there
-    l = LeaseMgrFactory::instance().getLease4(addr);
-    EXPECT_FALSE(l);
-}
-
 // Checks if received relay agent info option is echoed back to the client
 // Checks if received relay agent info option is echoed back to the client
 TEST_F(Dhcpv4SrvTest, relayAgentInfoEcho) {
 TEST_F(Dhcpv4SrvTest, relayAgentInfoEcho) {
     IfaceMgrTestConfig test_config(true);
     IfaceMgrTestConfig test_config(true);

+ 334 - 0
src/bin/dhcp4/tests/release_unittest.cc

@@ -0,0 +1,334 @@
+// 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 <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 Release tests.
+///
+/// - Configuration 0:
+///   - Used for testing Release 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* RELEASE_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\""
+        "    } ]"
+        " } ]"
+    "}",
+
+// Configuration 1
+    "{ \"interfaces-config\": {"
+        "      \"interfaces\": [ \"*\" ]"
+        "},"
+
+        "\"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\""
+        "    } ]"
+        " } ]"
+    "}",
+
+// Configuration 2
+    "{ \"interfaces-config\": {"
+        "      \"interfaces\": [ \"*\" ]"
+        "},"
+        "\"valid-lifetime\": 600,"
+        "\"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\","
+        "         \"ip-address\": \"10.0.0.7\""
+        "       }"
+        "    ]"
+        "} ]"
+    "}"
+};
+
+/// @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 ReleaseTest : public Dhcpv4SrvTest {
+public:
+
+    /// @brief Constructor.
+    ///
+    /// Sets up fake interfaces.
+    ReleaseTest()
+        : Dhcpv4SrvTest(),
+          iface_mgr_test_config_(true) {
+        IfaceMgr::instance().openSockets4();
+    }
+
+    /// @brief Performs 4-way exchange to obtain new lease.
+    ///
+    /// @param client Client to be used to obtain a lease.
+    void acquireLease(Dhcp4Client& client);
+
+    /// @brief Test successful release for different identifiers.
+    ///
+    /// @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 release the lease.
+    /// @param client_id_2 Client id to be used to release the lease.
+    void successfulRelease(const std::string& hw_address_1,
+                           const std::string& client_id_1,
+                           const std::string& hw_address_2,
+                           const std::string& client_id_2);
+
+    /// @brief Test unsuccessful release for different identifiers.
+    ///
+    /// @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 release the lease.
+    /// @param client_id_2 Client id to be used to release the lease.
+    void unsuccessfulRelease(const std::string& hw_address_1,
+                             const std::string& client_id_1,
+                             const std::string& hw_address_2,
+                             const std::string& client_id_2);
+
+    /// @brief Interface Manager's fake configuration control.
+    IfaceMgrTestConfig iface_mgr_test_config_;
+
+};
+
+void
+ReleaseTest::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
+ReleaseTest::successfulRelease(const std::string& hw_address_1,
+                               const std::string& client_id_1,
+                               const std::string& hw_address_2,
+                               const std::string& client_id_2) {
+    Dhcp4Client client(Dhcp4Client::SELECTING);
+    // Configure DHCP server.
+    configure(RELEASE_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);
+
+    // Remember the acquired address.
+    IOAddress leased_address = client.config_.lease_.addr_;
+
+    // Explicitly set the client id for DHCPRELEASE.
+    client.includeClientId(client_id_2);
+    // Explicitly set the HW address for DHCPRELEASE.
+    client.setHWAddress(hw_address_2);
+
+    // Send the release and make sure that the lease is removed from the
+    // server.
+    ASSERT_NO_THROW(client.doRelease());
+    Lease4Ptr lease = LeaseMgrFactory::instance().getLease4(leased_address);
+    ASSERT_FALSE(lease);
+}
+
+void
+ReleaseTest::unsuccessfulRelease(const std::string& hw_address_1,
+                                 const std::string& client_id_1,
+                                 const std::string& hw_address_2,
+                                 const std::string& client_id_2) {
+    Dhcp4Client client(Dhcp4Client::SELECTING);
+    // Configure DHCP server.
+    configure(RELEASE_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);
+
+    // Remember the acquired address.
+    IOAddress leased_address = client.config_.lease_.addr_;
+
+    // Explicitly set the client id for DHCPRELEASE.
+    client.includeClientId(client_id_2);
+    // Explicitly set the HW address for DHCPRELEASE.
+    client.setHWAddress(hw_address_2);
+
+    // Send DHCPRELEASE and make sure it was unsuccessful, i.e. the lease
+    // remains in the database.
+    ASSERT_NO_THROW(client.doRelease());
+    Lease4Ptr lease = LeaseMgrFactory::instance().getLease4(leased_address);
+    ASSERT_TRUE(lease);
+}
+
+
+// This test checks that the client can acquire and release the lease.
+TEST_F(ReleaseTest, releaseNoIdentifierChange) {
+    successfulRelease("01:02:03:04:05:06", "12:14",
+                      "01:02:03:04:05:06", "12:14");
+}
+
+// This test verifies the release correctness in the following case:
+// - Client acquires new lease using HW address only
+// - Client sends the DHCPRELEASE with valid HW address and without
+//   client identifier.
+// - The server successfully releases the lease.
+TEST_F(ReleaseTest, releaseHWAddressOnly) {
+    successfulRelease("01:02:03:04:05:06", "",
+                      "01:02:03:04:05:06", "");
+}
+
+// This test verifies the release correctness in the following case:
+// - Client acquires new lease using the client identifier and HW address
+// - Client sends the DHCPRELEASE with valid HW address but with no
+//   client identifier.
+// - The server successfully releases the lease.
+TEST_F(ReleaseTest, releaseNoClientId) {
+    successfulRelease("01:02:03:04:05:06", "12:14",
+                      "01:02:03:04:05:06", "");
+}
+
+// This test verifies the release correctness in the following case:
+// - Client acquires new lease using HW address
+// - Client sends the DHCPRELEASE with valid HW address and some
+//   client identifier.
+// - The server identifies the lease using HW address and releases
+//   this lease.
+TEST_F(ReleaseTest, releaseNoClientId2) {
+    successfulRelease("01:02:03:04:05:06", "",
+                      "01:02:03:04:05:06", "12:14");
+}
+
+// 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 DHCPRELEASE with the valid HW address but with invalid
+//   client identifier.
+// - The server should not remove the lease.
+TEST_F(ReleaseTest, releaseNonMatchingClientId) {
+    unsuccessfulRelease("01:02:03:04:05:06", "12:14",
+                        "01:02:03:04:05:06", "12:15:16");
+}
+
+// This test checks the server's behavior in the following case:
+// - Client acquires new lease using client identifier and HW address
+// - Client sends the DHCPRELEASE with the same client identifier but
+//   different HW address.
+// - The server uses client identifier to find the client's lease and
+//   releases it.
+TEST_F(ReleaseTest, releaseNonMatchingHWAddress) {
+    successfulRelease("01:02:03:04:05:06", "12:14",
+                      "06:06:06:06:06:06", "12:14");
+}
+
+// This test checks the server's behavior in the following case:
+// - Client acquires new lease.
+// - Client sends DHCPRELEASE with the ciaddr set to a different
+//   address than it has acquired from the server.
+// - Server determines that the client is trying to release a
+//   wrong address and will refuse to release.
+TEST_F(ReleaseTest, releaseNonMatchingIPAddress) {
+    Dhcp4Client client(Dhcp4Client::SELECTING);
+    // Configure DHCP server.
+    configure(RELEASE_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 release a different address
+    // than it has obtained from the server.
+    client.config_.lease_.addr_ = IOAddress(static_cast<uint32_t>(leased_address) + 1);
+
+    // Send DHCPRELEASE and make sure it was unsuccessful, i.e. the lease
+    // remains in the database.
+    ASSERT_NO_THROW(client.doRelease());
+    Lease4Ptr lease = LeaseMgrFactory::instance().getLease4(leased_address);
+    ASSERT_TRUE(lease);
+}
+
+} // end of anonymous namespace