summaryrefslogtreecommitdiffstats
path: root/src/bin/dhcp4/tests/client_handler_unittest.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/bin/dhcp4/tests/client_handler_unittest.cc')
-rw-r--r--src/bin/dhcp4/tests/client_handler_unittest.cc893
1 files changed, 893 insertions, 0 deletions
diff --git a/src/bin/dhcp4/tests/client_handler_unittest.cc b/src/bin/dhcp4/tests/client_handler_unittest.cc
new file mode 100644
index 0000000..50b3ba5
--- /dev/null
+++ b/src/bin/dhcp4/tests/client_handler_unittest.cc
@@ -0,0 +1,893 @@
+// Copyright (C) 2020-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+#include <dhcp/option.h>
+#include <dhcp4/client_handler.h>
+#include <dhcp4/tests/dhcp4_test_utils.h>
+#include <stats/stats_mgr.h>
+#include <util/multi_threading_mgr.h>
+#include <unistd.h>
+
+using namespace isc;
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+using namespace isc::stats;
+using namespace isc::util;
+
+namespace {
+
+/// @brief Test fixture class for testing client handler.
+class ClientHandleTest : public ::testing::Test {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// Creates the pkt4-receive-drop statistic.
+ ClientHandleTest() : called1_(false), called2_(false), called3_(false) {
+ MultiThreadingMgr::instance().apply(false, 0, 0);
+ StatsMgr::instance().setValue("pkt4-receive-drop", static_cast<int64_t>(0));
+ }
+
+ /// @brief Destructor.
+ ///
+ /// Removes statistics.
+ ~ClientHandleTest() {
+ MultiThreadingMgr::instance().apply(false, 0, 0);
+ StatsMgr::instance().removeAll();
+ }
+
+ /// @brief Generates a client-id option.
+ ///
+ /// (from dhcp4_test_utils.h)
+ ///
+ /// @return A random client-id option.
+ OptionPtr generateClientId(uint8_t base = 100) {
+ const size_t len = 32;
+ OptionBuffer duid(len);
+ for (size_t i = 0; i < len; ++i) {
+ duid[i] = base + i;
+ }
+ return (OptionPtr(new Option(Option::V4, DHO_DHCP_CLIENT_IDENTIFIER, duid)));
+ }
+
+ /// @brief Generates a hardware address.
+ ///
+ /// (from dhcp4_test_utils.h)
+ ///
+ /// @return A random hardware address.
+ HWAddrPtr generateHWAddr(uint8_t base = 50) {
+ const uint16_t hw_type = 6;
+ const size_t len = 6;
+ OptionBuffer mac(len);
+ for (size_t i = 0; i < len; ++i) {
+ mac[i] = base + i;
+ }
+ return (HWAddrPtr(new HWAddr(mac, hw_type)));
+ }
+
+ /// @brief Check statistics.
+ ///
+ /// @param bumped True if pkt4-receive-drop should have been bumped by one,
+ /// false otherwise.
+ void checkStat(bool bumped) {
+ ObservationPtr obs = StatsMgr::instance().getObservation("pkt4-receive-drop");
+ ASSERT_TRUE(obs);
+ if (bumped) {
+ EXPECT_EQ(1, obs->getInteger().first);
+ } else {
+ EXPECT_EQ(0, obs->getInteger().first);
+ }
+ }
+
+ /// @brief Waits for pending continuations.
+ void waitForThreads() {
+ MultiThreadingMgr::instance().getThreadPool().wait(3);
+ }
+
+ /// @brief Set called1_ to true.
+ void setCalled1() {
+ called1_ = true;
+ }
+
+ /// @brief Set called2_ to true.
+ void setCalled2() {
+ called2_ = true;
+ }
+
+ /// @brief Set called3_ to true.
+ void setCalled3() {
+ called3_ = true;
+ }
+
+ /// @brief The called flag number 1.
+ bool called1_;
+
+ /// @brief The called flag number 2.
+ bool called2_;
+
+ /// @brief The called flag number 3.
+ bool called3_;
+};
+
+// Verifies behavior with empty block.
+TEST_F(ClientHandleTest, empty) {
+ try {
+ // Get a client handler.
+ ClientHandler client_handler;
+ } catch (const std::exception& ex) {
+ ADD_FAILURE() << "unexpected exception: " << ex.what();
+ }
+ checkStat(false);
+}
+
+// Verifies behavior with one query.
+TEST_F(ClientHandleTest, oneQuery) {
+ // Get a query.
+ Pkt4Ptr dis(new Pkt4(DHCPDISCOVER, 1234));
+ dis->addOption(generateClientId());
+ dis->setHWAddr(generateHWAddr());
+
+ try {
+ // Get a client handler.
+ ClientHandler client_handler;
+
+ // Try to lock it.
+ bool duplicate = false;
+ EXPECT_NO_THROW(duplicate = !client_handler.tryLock(dis));
+
+ // Should return false (no duplicate).
+ EXPECT_FALSE(duplicate);
+ } catch (const std::exception& ex) {
+ ADD_FAILURE() << "unexpected exception: " << ex.what();
+ }
+ checkStat(false);
+}
+
+// Verifies behavior with two queries for the same client (id).
+TEST_F(ClientHandleTest, sharedQueriesById) {
+ // Get two queries.
+ Pkt4Ptr dis(new Pkt4(DHCPDISCOVER, 1234));
+ Pkt4Ptr req(new Pkt4(DHCPREQUEST, 2345));
+ OptionPtr client_id = generateClientId();
+ // Same client ID: same client.
+ dis->addOption(client_id);
+ req->addOption(client_id);
+ dis->setHWAddr(generateHWAddr());
+ req->setHWAddr(generateHWAddr(55));
+
+ try {
+ // Get a client handler.
+ ClientHandler client_handler;
+
+ // Try to lock it with the discover.
+ bool duplicate = false;
+ EXPECT_NO_THROW(duplicate = !client_handler.tryLock(dis));
+
+ // Should return false (no duplicate).
+ EXPECT_FALSE(duplicate);
+
+ // Get a second client handler.
+ ClientHandler client_handler2;
+
+ // Try to lock it with a request.
+ EXPECT_NO_THROW(duplicate = !client_handler2.tryLock(req));
+
+ // Should return true (race with the duplicate).
+ EXPECT_TRUE(duplicate);
+ } catch (const std::exception& ex) {
+ ADD_FAILURE() << "unexpected exception: " << ex.what();
+ }
+ checkStat(true);
+}
+
+// Verifies behavior with two queries for the same client (hw).
+TEST_F(ClientHandleTest, sharedQueriesByHWAddr) {
+ // Get two queries.
+ Pkt4Ptr dis(new Pkt4(DHCPDISCOVER, 1234));
+ Pkt4Ptr req(new Pkt4(DHCPREQUEST, 2345));
+ dis->addOption(generateClientId());
+ req->addOption(generateClientId(111));
+ HWAddrPtr hwaddr = generateHWAddr();
+ // Same hardware address: same client.
+ dis->setHWAddr(hwaddr);
+ req->setHWAddr(hwaddr);
+
+ try {
+ // Get a client handler.
+ ClientHandler client_handler;
+
+ // Try to lock it with the discover.
+ bool duplicate = false;
+ EXPECT_NO_THROW(duplicate = !client_handler.tryLock(dis));
+
+ // Should return false (no duplicate).
+ EXPECT_FALSE(duplicate);
+
+ // Get a second client handler.
+ ClientHandler client_handler2;
+
+ // Try to lock it with a request.
+ EXPECT_NO_THROW(duplicate = !client_handler2.tryLock(req));
+
+ // Should return true (race with the duplicate).
+ EXPECT_TRUE(duplicate);
+ } catch (const std::exception& ex) {
+ ADD_FAILURE() << "unexpected exception: " << ex.what();
+ }
+ checkStat(true);
+}
+
+// Verifies behavior with two queries for the same client (hw only).
+TEST_F(ClientHandleTest, sharedQueriesByHWAddrOnly) {
+ // Get two queries.
+ Pkt4Ptr dis(new Pkt4(DHCPDISCOVER, 1234));
+ Pkt4Ptr req(new Pkt4(DHCPREQUEST, 2345));
+ // No client ID.
+ HWAddrPtr hwaddr = generateHWAddr();
+ // Same hardware address: same client.
+ dis->setHWAddr(hwaddr);
+ req->setHWAddr(hwaddr);
+
+ try {
+ // Get a client handler.
+ ClientHandler client_handler;
+
+ // Try to lock it with the discover.
+ bool duplicate = false;
+ EXPECT_NO_THROW(duplicate = !client_handler.tryLock(dis));
+
+ // Should return false (no duplicate).
+ EXPECT_FALSE(duplicate);
+
+ // Get a second client handler.
+ ClientHandler client_handler2;
+
+ // Try to lock it with a request.
+ EXPECT_NO_THROW(duplicate = !client_handler2.tryLock(req));
+
+ // Should return true (race with the duplicate).
+ EXPECT_TRUE(duplicate);
+ } catch (const std::exception& ex) {
+ ADD_FAILURE() << "unexpected exception: " << ex.what();
+ }
+ checkStat(true);
+}
+
+// Verifies behavior with a sequence of two queries.
+TEST_F(ClientHandleTest, sequence) {
+ // Get two queries.
+ Pkt4Ptr dis(new Pkt4(DHCPDISCOVER, 1234));
+ Pkt4Ptr req(new Pkt4(DHCPREQUEST, 2345));
+ OptionPtr client_id = generateClientId();
+ // Same client ID: same client.
+ dis->addOption(client_id);
+ req->addOption(client_id);
+ HWAddrPtr hwaddr = generateHWAddr();
+ // Same hardware address: same client.
+ dis->setHWAddr(hwaddr);
+ req->setHWAddr(hwaddr);
+
+ try {
+ // Get a client handler.
+ ClientHandler client_handler;
+
+ // Try to lock it with the discover.
+ bool duplicate = false;
+ EXPECT_NO_THROW(duplicate = !client_handler.tryLock(dis));
+
+ // Should return false (no duplicate).
+ EXPECT_FALSE(duplicate);
+ } catch (const std::exception& ex) {
+ ADD_FAILURE() << "unexpected exception: " << ex.what();
+ }
+
+ // As it is a different block the lock was released.
+
+ try {
+ // Get a second client handler.
+ ClientHandler client_handler2;
+
+ // Try to lock it with a request.
+ bool duplicate = false;
+ EXPECT_NO_THROW(duplicate = !client_handler2.tryLock(req));
+
+ // Should return false (no duplicate).
+ EXPECT_FALSE(duplicate);
+ } catch (const std::exception& ex) {
+ ADD_FAILURE() << "unexpected exception: " << ex.what();
+ }
+ checkStat(false);
+}
+
+// Verifies behavior with different clients.
+TEST_F(ClientHandleTest, notSharedQueries) {
+ // Get two queries.
+ Pkt4Ptr dis(new Pkt4(DHCPDISCOVER, 1234));
+ Pkt4Ptr req(new Pkt4(DHCPREQUEST, 2345));
+ OptionPtr client_id = generateClientId();
+ OptionPtr client_id2 = generateClientId(111);
+ HWAddrPtr hwaddr = generateHWAddr();
+ HWAddrPtr hwaddr1 = generateHWAddr(55);
+ // Different client ID and hardware address: different client.
+ dis->addOption(client_id);
+ req->addOption(client_id2);
+ dis->setHWAddr(hwaddr);
+ req->setHWAddr(hwaddr1);
+
+ try {
+ // Get a client handler.
+ ClientHandler client_handler;
+
+ // Try to lock it with the discover.
+ bool duplicate = false;
+ EXPECT_NO_THROW(duplicate = !client_handler.tryLock(dis));
+
+ // Should return false (no duplicate).
+ EXPECT_FALSE(duplicate);
+
+ // Get a second client handler.
+ ClientHandler client_handler2;
+
+ // Try to lock it with a request.
+ EXPECT_NO_THROW(duplicate = !client_handler2.tryLock(req));
+
+ // Should return false (no duplicate).
+ EXPECT_FALSE(duplicate);
+ } catch (const std::exception& ex) {
+ ADD_FAILURE() << "unexpected exception: " << ex.what();
+ }
+ checkStat(false);
+}
+
+// Verifies behavior without client ID nor hardware address.
+TEST_F(ClientHandleTest, noClientIdHWAddr) {
+ // Get two queries.
+ Pkt4Ptr dis(new Pkt4(DHCPDISCOVER, 1234));
+ Pkt4Ptr req(new Pkt4(DHCPREQUEST, 2345));
+ // No client id nor hardware address: nothing to recognize the client.
+
+ try {
+ // Get a client handler.
+ ClientHandler client_handler;
+
+ // Try to lock it with the discover.
+ bool duplicate = false;
+ EXPECT_NO_THROW(duplicate = !client_handler.tryLock(dis));
+
+ // Should return false (no duplicate).
+ EXPECT_FALSE(duplicate);
+
+ // Get a second client handler.
+ ClientHandler client_handler2;
+
+ // Try to lock it with a request.
+ EXPECT_NO_THROW(duplicate = !client_handler2.tryLock(req));
+
+ // Should return false (no duplicate).
+ EXPECT_FALSE(duplicate);
+ } catch (const std::exception& ex) {
+ ADD_FAILURE() << "unexpected exception: " << ex.what();
+ }
+ checkStat(false);
+}
+
+// Verifies the query is required.
+TEST_F(ClientHandleTest, noQuery) {
+ Pkt4Ptr no_pkt;
+
+ try {
+ // Get a client handler.
+ ClientHandler client_handler;
+
+ EXPECT_THROW(client_handler.tryLock(no_pkt), InvalidParameter);
+ } catch (const std::exception& ex) {
+ ADD_FAILURE() << "unexpected exception: " << ex.what();
+ }
+}
+
+// Verifies that double tryLock call fails (id only).
+TEST_F(ClientHandleTest, doubleTryLockById) {
+ // Get a query.
+ Pkt4Ptr dis(new Pkt4(DHCPDISCOVER, 1234));
+ dis->addOption(generateClientId());
+
+ try {
+ // Get a client handler.
+ ClientHandler client_handler;
+
+ // Try to lock it.
+ bool duplicate = false;
+ EXPECT_NO_THROW(duplicate = !client_handler.tryLock(dis));
+
+ // Should return false (no duplicate).
+ EXPECT_FALSE(duplicate);
+
+ // Try to lock a second time.
+ EXPECT_THROW(client_handler.tryLock(dis), Unexpected);
+ } catch (const std::exception& ex) {
+ ADD_FAILURE() << "unexpected exception: " << ex.what();
+ }
+}
+
+// Verifies that double tryLock call fails (hw only).
+TEST_F(ClientHandleTest, doubleTryLockByHWAddr) {
+ // Get a query without a client ID.
+ Pkt4Ptr dis(new Pkt4(DHCPDISCOVER, 1234));
+ dis->setHWAddr(generateHWAddr());
+
+ try {
+ // Get a client handler.
+ ClientHandler client_handler;
+
+ // Try to lock it.
+ bool duplicate = false;
+ EXPECT_NO_THROW(duplicate = !client_handler.tryLock(dis));
+
+ // Should return false (no duplicate).
+ EXPECT_FALSE(duplicate);
+
+ // Try to lock a second time.
+ EXPECT_THROW(client_handler.tryLock(dis), Unexpected);
+ } catch (const std::exception& ex) {
+ ADD_FAILURE() << "unexpected exception: " << ex.what();
+ }
+}
+
+// Cannot verifies that empty client ID fails because getClientId() handles
+// this condition and replaces it by no client ID.
+
+// Verifies behavior with two queries for the same client and
+// multi-threading, by client id option version.
+TEST_F(ClientHandleTest, serializeTwoQueriesById) {
+ // Get two queries.
+ Pkt4Ptr dis(new Pkt4(DHCPDISCOVER, 1234));
+ Pkt4Ptr req(new Pkt4(DHCPREQUEST, 2345));
+ OptionPtr client_id = generateClientId();
+ // Same client ID: same client.
+ dis->addOption(client_id);
+ req->addOption(client_id);
+ dis->setHWAddr(generateHWAddr());
+ req->setHWAddr(generateHWAddr(55));
+
+ // Start multi-threading.
+ EXPECT_NO_THROW(MultiThreadingMgr::instance().apply(true, 1, 0));
+
+ try {
+ // Get a client handler.
+ ClientHandler client_handler;
+
+ // Create a continuation.
+ ContinuationPtr cont1 =
+ makeContinuation(std::bind(&ClientHandleTest::setCalled1, this));
+
+ // Try to lock it with the discover.
+ bool duplicate = false;
+ EXPECT_NO_THROW(duplicate = !client_handler.tryLock(dis, cont1));
+
+ // Should return false (no duplicate).
+ EXPECT_FALSE(duplicate);
+
+ // Get a second client handler.
+ ClientHandler client_handler2;
+
+ // Create a continuation.
+ ContinuationPtr cont2 =
+ makeContinuation(std::bind(&ClientHandleTest::setCalled2, this));
+
+ // Try to lock it with a request.
+ EXPECT_NO_THROW(duplicate = !client_handler2.tryLock(req, cont2));
+
+ // Should return true (race with the duplicate).
+ EXPECT_TRUE(duplicate);
+ } catch (const std::exception& ex) {
+ ADD_FAILURE() << "unexpected exception: " << ex.what();
+ }
+
+ // Give the second continuation a chance.
+ waitForThreads();
+
+ // Force multi-threading to stop;
+ MultiThreadingCriticalSection cs;
+
+ checkStat(false);
+ EXPECT_FALSE(called1_);
+ EXPECT_TRUE(called2_);
+}
+
+// Verifies behavior with two queries for the same client and
+// multi-threading, by hardware address version.
+TEST_F(ClientHandleTest, serializeTwoQueriesByHWAddr) {
+ // Get two queries.
+ Pkt4Ptr dis(new Pkt4(DHCPDISCOVER, 1234));
+ Pkt4Ptr req(new Pkt4(DHCPREQUEST, 2345));
+ dis->addOption(generateClientId());
+ req->addOption(generateClientId(111));
+ HWAddrPtr hwaddr = generateHWAddr();
+ // Same hardware address: same client.
+ dis->setHWAddr(hwaddr);
+ req->setHWAddr(hwaddr);
+
+ // Start multi-threading.
+ EXPECT_NO_THROW(MultiThreadingMgr::instance().apply(true, 1, 0));
+
+ try {
+ // Get a client handler.
+ ClientHandler client_handler;
+
+ // Create a continuation.
+ ContinuationPtr cont1 =
+ makeContinuation(std::bind(&ClientHandleTest::setCalled1, this));
+
+ // Try to lock it with the discover.
+ bool duplicate = false;
+ EXPECT_NO_THROW(duplicate = !client_handler.tryLock(dis, cont1));
+
+ // Should return false (no duplicate).
+ EXPECT_FALSE(duplicate);
+
+ // Get a second client handler.
+ ClientHandler client_handler2;
+
+ // Create a continuation.
+ ContinuationPtr cont2 =
+ makeContinuation(std::bind(&ClientHandleTest::setCalled2, this));
+
+ // Try to lock it with a request.
+ EXPECT_NO_THROW(duplicate = !client_handler2.tryLock(req, cont2));
+
+ // Should return true (race with the duplicate).
+ EXPECT_TRUE(duplicate);
+ } catch (const std::exception& ex) {
+ ADD_FAILURE() << "unexpected exception: " << ex.what();
+ }
+
+ // Give the second continuation a chance.
+ waitForThreads();
+
+ // Force multi-threading to stop;
+ MultiThreadingCriticalSection cs;
+
+ checkStat(false);
+ EXPECT_FALSE(called1_);
+ EXPECT_TRUE(called2_);
+}
+
+// Verifies behavior with two queries for the same client and multi-threading.
+// Continuations are required for serialization. By client id option version.
+TEST_F(ClientHandleTest, serializeNoContById) {
+ // Get two queries.
+ Pkt4Ptr dis(new Pkt4(DHCPDISCOVER, 1234));
+ Pkt4Ptr req(new Pkt4(DHCPREQUEST, 2345));
+ OptionPtr client_id = generateClientId();
+ // Same client ID: same client.
+ dis->addOption(client_id);
+ req->addOption(client_id);
+ dis->setHWAddr(generateHWAddr());
+ req->setHWAddr(generateHWAddr(55));
+
+ // Start multi-threading.
+ EXPECT_NO_THROW(MultiThreadingMgr::instance().apply(true, 1, 0));
+
+ try {
+ // Get a client handler.
+ ClientHandler client_handler;
+
+ // Try to lock it with the discover.
+ bool duplicate = false;
+ EXPECT_NO_THROW(duplicate = !client_handler.tryLock(dis));
+
+ // Should return false (no duplicate).
+ EXPECT_FALSE(duplicate);
+
+ // Get a second client handler.
+ ClientHandler client_handler2;
+
+ // Try to lock it with a request.
+ EXPECT_NO_THROW(duplicate = !client_handler2.tryLock(req));
+
+ // Should return true (race with the duplicate).
+ EXPECT_TRUE(duplicate);
+ } catch (const std::exception& ex) {
+ ADD_FAILURE() << "unexpected exception: " << ex.what();
+ }
+
+ // Give the second continuation a chance even there is none...
+ waitForThreads();
+
+ // Force multi-threading to stop;
+ MultiThreadingCriticalSection cs;
+
+ checkStat(true);
+}
+
+// Verifies behavior with two queries for the same client and multi-threading.
+// Continuations are required for serialization. By hardware address version.
+TEST_F(ClientHandleTest, serializeNoContByHWAddr) {
+ // Get two queries.
+ Pkt4Ptr dis(new Pkt4(DHCPDISCOVER, 1234));
+ Pkt4Ptr req(new Pkt4(DHCPREQUEST, 2345));
+ dis->addOption(generateClientId());
+ req->addOption(generateClientId(111));
+ HWAddrPtr hwaddr = generateHWAddr();
+ // Same hardware address: same client.
+ dis->setHWAddr(hwaddr);
+ req->setHWAddr(hwaddr);
+
+ // Start multi-threading.
+ EXPECT_NO_THROW(MultiThreadingMgr::instance().apply(true, 1, 0));
+
+ try {
+ // Get a client handler.
+ ClientHandler client_handler;
+
+ // Try to lock it with the discover.
+ bool duplicate = false;
+ EXPECT_NO_THROW(duplicate = !client_handler.tryLock(dis));
+
+ // Should return false (no duplicate).
+ EXPECT_FALSE(duplicate);
+
+ // Get a second client handler.
+ ClientHandler client_handler2;
+
+ // Try to lock it with a request.
+ EXPECT_NO_THROW(duplicate = !client_handler2.tryLock(req));
+
+ // Should return true (race with the duplicate).
+ EXPECT_TRUE(duplicate);
+ } catch (const std::exception& ex) {
+ ADD_FAILURE() << "unexpected exception: " << ex.what();
+ }
+
+ // Give the second continuation a chance even there is none...
+ waitForThreads();
+
+ // Force multi-threading to stop;
+ MultiThreadingCriticalSection cs;
+
+ checkStat(true);
+}
+
+// Verifies behavior with three queries for the same client and
+// multi-threading: currently we accept only two queries,
+// a third one replaces second so we get the first (oldest) query and
+// the last (newest) query when the client is busy.
+// By client id option version.
+TEST_F(ClientHandleTest, serializeThreeQueriesById) {
+ // Get two queries.
+ Pkt4Ptr dis(new Pkt4(DHCPDISCOVER, 1234));
+ Pkt4Ptr req(new Pkt4(DHCPREQUEST, 2345));
+ Pkt4Ptr rel(new Pkt4(DHCPRELEASE, 3456));
+ OptionPtr client_id = generateClientId();
+ // Same client ID: same client.
+ dis->addOption(client_id);
+ req->addOption(client_id);
+ rel->addOption(client_id);
+ dis->setHWAddr(generateHWAddr());
+ req->setHWAddr(generateHWAddr(55));
+ rel->setHWAddr(generateHWAddr(66));
+
+ // Start multi-threading.
+ EXPECT_NO_THROW(MultiThreadingMgr::instance().apply(true, 1, 0));
+
+ try {
+ // Get a client handler.
+ ClientHandler client_handler;
+
+ // Create a continuation.
+ ContinuationPtr cont1 =
+ makeContinuation(std::bind(&ClientHandleTest::setCalled1, this));
+
+ // Try to lock it with the discover.
+ bool duplicate = false;
+ EXPECT_NO_THROW(duplicate = !client_handler.tryLock(dis, cont1));
+
+ // Should return false (no duplicate).
+ EXPECT_FALSE(duplicate);
+
+ // Get a second client handler.
+ ClientHandler client_handler2;
+
+ // Create a continuation.
+ ContinuationPtr cont2 =
+ makeContinuation(std::bind(&ClientHandleTest::setCalled2, this));
+
+ // Try to lock it with a request.
+ EXPECT_NO_THROW(duplicate = !client_handler2.tryLock(req, cont2));
+
+ // Should return true (race with the duplicate).
+ EXPECT_TRUE(duplicate);
+
+ // Get a third client handler.
+ ClientHandler client_handler3;
+
+ // Create a continuation.
+ ContinuationPtr cont3 =
+ makeContinuation(std::bind(&ClientHandleTest::setCalled3, this));
+
+ // Try to lock it with a release.
+ EXPECT_NO_THROW(duplicate = !client_handler3.tryLock(rel, cont3));
+
+ // Should return true (race with the duplicate).
+ EXPECT_TRUE(duplicate);
+ } catch (const std::exception& ex) {
+ ADD_FAILURE() << "unexpected exception: " << ex.what();
+ }
+
+ // Give the second continuation a chance.
+ waitForThreads();
+
+ // Force multi-threading to stop;
+ MultiThreadingCriticalSection cs;
+
+ checkStat(true);
+ EXPECT_FALSE(called1_);
+ EXPECT_FALSE(called2_);
+ EXPECT_TRUE(called3_);
+}
+
+// Verifies behavior with three queries for the same client and
+// multi-threading: currently we accept only two queries,
+// a third one replaces second so we get the first (oldest) query and
+// the last (newest) query when the client is busy.
+// By hardware address version.
+TEST_F(ClientHandleTest, serializeThreeQueriesHWAddr) {
+ // Get two queries.
+ Pkt4Ptr dis(new Pkt4(DHCPDISCOVER, 1234));
+ Pkt4Ptr req(new Pkt4(DHCPREQUEST, 2345));
+ Pkt4Ptr rel(new Pkt4(DHCPRELEASE, 3456));
+ dis->addOption(generateClientId());
+ req->addOption(generateClientId(111));
+ rel->addOption(generateClientId(99));
+ HWAddrPtr hwaddr = generateHWAddr();
+ // Same hardware address: same client.
+ dis->setHWAddr(hwaddr);
+ req->setHWAddr(hwaddr);
+ rel->setHWAddr(hwaddr);
+
+ // Start multi-threading.
+ EXPECT_NO_THROW(MultiThreadingMgr::instance().apply(true, 1, 0));
+
+ try {
+ // Get a client handler.
+ ClientHandler client_handler;
+
+ // Create a continuation.
+ ContinuationPtr cont1 =
+ makeContinuation(std::bind(&ClientHandleTest::setCalled1, this));
+
+ // Try to lock it with the discover.
+ bool duplicate = false;
+ EXPECT_NO_THROW(duplicate = !client_handler.tryLock(dis, cont1));
+
+ // Should return false (no duplicate).
+ EXPECT_FALSE(duplicate);
+
+ // Get a second client handler.
+ ClientHandler client_handler2;
+
+ // Create a continuation.
+ ContinuationPtr cont2 =
+ makeContinuation(std::bind(&ClientHandleTest::setCalled2, this));
+
+ // Try to lock it with a request.
+ EXPECT_NO_THROW(duplicate = !client_handler2.tryLock(req, cont2));
+
+ // Should return true (race with the duplicate).
+ EXPECT_TRUE(duplicate);
+
+ // Get a third client handler.
+ ClientHandler client_handler3;
+
+ // Create a continuation.
+ ContinuationPtr cont3 =
+ makeContinuation(std::bind(&ClientHandleTest::setCalled3, this));
+
+ // Try to lock it with a release.
+ EXPECT_NO_THROW(duplicate = !client_handler3.tryLock(rel, cont3));
+
+ // Should return true (race with the duplicate).
+ EXPECT_TRUE(duplicate);
+ } catch (const std::exception& ex) {
+ ADD_FAILURE() << "unexpected exception: " << ex.what();
+ }
+
+ // Give the second continuation a chance.
+ waitForThreads();
+
+ // Force multi-threading to stop;
+ MultiThreadingCriticalSection cs;
+
+ checkStat(true);
+ EXPECT_FALSE(called1_);
+ EXPECT_FALSE(called2_);
+ EXPECT_TRUE(called3_);
+}
+
+// Verifies behavior with three queries for the same client and
+// multi-threading: currently we accept only two queries,
+// a third one replaces second so we get the first (oldest) query and
+// the last (newest) query when the client is busy.
+// Mixed version (hardware address then client id option duplicates).
+// Note the system is transitive because further races are detected
+// when serialized packet processing is performed.
+TEST_F(ClientHandleTest, serializeThreeQueriesMixed) {
+ // Get two queries.
+ Pkt4Ptr dis(new Pkt4(DHCPDISCOVER, 1234));
+ Pkt4Ptr req(new Pkt4(DHCPREQUEST, 2345));
+ Pkt4Ptr rel(new Pkt4(DHCPRELEASE, 3456));
+ HWAddrPtr hwaddr = generateHWAddr();
+ // Same hardware address: same client for discover and request.
+ dis->setHWAddr(hwaddr);
+ req->setHWAddr(hwaddr);
+ rel->setHWAddr(generateHWAddr(55));
+ OptionPtr client_id = generateClientId();
+ // Same client ID: same client for discover and release.
+ dis->addOption(client_id);
+ req->addOption(generateClientId(111));
+ rel->addOption(client_id);
+
+ // Start multi-threading.
+ EXPECT_NO_THROW(MultiThreadingMgr::instance().apply(true, 1, 0));
+
+ try {
+ // Get a client handler.
+ ClientHandler client_handler;
+
+ // Create a continuation.
+ ContinuationPtr cont1 =
+ makeContinuation(std::bind(&ClientHandleTest::setCalled1, this));
+
+ // Try to lock it with the discover.
+ bool duplicate = false;
+ EXPECT_NO_THROW(duplicate = !client_handler.tryLock(dis, cont1));
+
+ // Should return false (no duplicate).
+ EXPECT_FALSE(duplicate);
+
+ // Get a second client handler.
+ ClientHandler client_handler2;
+
+ // Create a continuation.
+ ContinuationPtr cont2 =
+ makeContinuation(std::bind(&ClientHandleTest::setCalled2, this));
+
+ // Try to lock it with a request.
+ EXPECT_NO_THROW(duplicate = !client_handler2.tryLock(req, cont2));
+
+ // Should return true (race with the duplicate).
+ EXPECT_TRUE(duplicate);
+
+ // Get a third client handler.
+ ClientHandler client_handler3;
+
+ // Create a continuation.
+ ContinuationPtr cont3 =
+ makeContinuation(std::bind(&ClientHandleTest::setCalled3, this));
+
+ // Try to lock it with a release.
+ EXPECT_NO_THROW(duplicate = !client_handler3.tryLock(rel, cont3));
+
+ // Should return true (race with the duplicate).
+ EXPECT_TRUE(duplicate);
+ } catch (const std::exception& ex) {
+ ADD_FAILURE() << "unexpected exception: " << ex.what();
+ }
+
+ // Give the second continuation a chance.
+ waitForThreads();
+
+ // Force multi-threading to stop;
+ MultiThreadingCriticalSection cs;
+
+ checkStat(true);
+ EXPECT_FALSE(called1_);
+ EXPECT_FALSE(called2_);
+ EXPECT_TRUE(called3_);
+}
+
+} // end of anonymous namespace