From f5f56e1a1c4d9e9496fcb9d81131066a964ccd23 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sat, 13 Apr 2024 14:15:43 +0200 Subject: Adding upstream version 2.4.1. Signed-off-by: Daniel Baumann --- src/bin/dhcp6/tests/client_handler_unittest.cc | 495 +++++++++++++++++++++++++ 1 file changed, 495 insertions(+) create mode 100644 src/bin/dhcp6/tests/client_handler_unittest.cc (limited to 'src/bin/dhcp6/tests/client_handler_unittest.cc') diff --git a/src/bin/dhcp6/tests/client_handler_unittest.cc b/src/bin/dhcp6/tests/client_handler_unittest.cc new file mode 100644 index 0000000..6eed4c4 --- /dev/null +++ b/src/bin/dhcp6/tests/client_handler_unittest.cc @@ -0,0 +1,495 @@ +// 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 +#include +#include +#include +#include +#include +#include + +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 pkt6-receive-drop statistic. + ClientHandleTest() : called1_(false), called2_(false), called3_(false) { + MultiThreadingMgr::instance().apply(false, 0, 0); + StatsMgr::instance().setValue("pkt6-receive-drop", static_cast(0)); + } + + /// @brief Destructor. + /// + /// Removes statistics. + ~ClientHandleTest() { + MultiThreadingMgr::instance().apply(false, 0, 0); + StatsMgr::instance().removeAll(); + } + + /// @brief Generates a client-id option. + /// + /// (from dhcp6_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::V6, D6O_CLIENTID, duid))); + } + + /// @brief Check statistics. + /// + /// @param bumped True if pkt6-receive-drop should have been bumped by one, + /// false otherwise. + void checkStat(bool bumped) { + ObservationPtr obs = StatsMgr::instance().getObservation("pkt6-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. + Pkt6Ptr sol(new Pkt6(DHCPV6_SOLICIT, 1234)); + sol->addOption(generateClientId()); + + try { + // Get a client handler. + ClientHandler client_handler; + + // Try to lock it. + bool duplicate = false; + EXPECT_NO_THROW(duplicate = !client_handler.tryLock(sol)); + + // 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. +TEST_F(ClientHandleTest, sharedQueries) { + // Get two queries. + Pkt6Ptr sol(new Pkt6(DHCPV6_SOLICIT, 1234)); + Pkt6Ptr req(new Pkt6(DHCPV6_REQUEST, 2345)); + OptionPtr client_id = generateClientId(); + // Same client ID: same client. + sol->addOption(client_id); + req->addOption(client_id); + + try { + // Get a client handler. + ClientHandler client_handler; + + // Try to lock it with the solicit. + bool duplicate = false; + EXPECT_NO_THROW(duplicate = !client_handler.tryLock(sol)); + + // 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. + Pkt6Ptr sol(new Pkt6(DHCPV6_SOLICIT, 1234)); + Pkt6Ptr req(new Pkt6(DHCPV6_REQUEST, 2345)); + OptionPtr client_id = generateClientId(); + // Same client ID: same client. + sol->addOption(client_id); + req->addOption(client_id); + + try { + // Get a client handler. + ClientHandler client_handler; + + // Try to lock it with the solicit. + bool duplicate = false; + EXPECT_NO_THROW(duplicate = !client_handler.tryLock(sol)); + + // 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. + Pkt6Ptr sol(new Pkt6(DHCPV6_SOLICIT, 1234)); + Pkt6Ptr req(new Pkt6(DHCPV6_REQUEST, 2345)); + OptionPtr client_id = generateClientId(); + OptionPtr client_id2 = generateClientId(111); + // Different client ID: different client. + sol->addOption(client_id); + req->addOption(client_id2); + + try { + // Get a client handler. + ClientHandler client_handler; + + // Try to lock it with the solicit. + bool duplicate = false; + EXPECT_NO_THROW(duplicate = !client_handler.tryLock(sol)); + + // 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. +TEST_F(ClientHandleTest, noClientId) { + // Get two queries. + Pkt6Ptr sol(new Pkt6(DHCPV6_SOLICIT, 1234)); + Pkt6Ptr req(new Pkt6(DHCPV6_REQUEST, 2345)); + // No client id: nothing to recognize the client. + + try { + // Get a client handler. + ClientHandler client_handler; + + // Try to lock it with the solicit. + bool duplicate = false; + EXPECT_NO_THROW(duplicate = !client_handler.tryLock(sol)); + + // 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) { + Pkt6Ptr 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. +TEST_F(ClientHandleTest, doubleTryLock) { + // Get a query. + Pkt6Ptr sol(new Pkt6(DHCPV6_SOLICIT, 1234)); + sol->addOption(generateClientId()); + + try { + // Get a client handler. + ClientHandler client_handler; + + // Try to lock it. + bool duplicate = false; + EXPECT_NO_THROW(duplicate = !client_handler.tryLock(sol)); + + // Should return false (no duplicate). + EXPECT_FALSE(duplicate); + + // Try to lock a second time. + EXPECT_THROW(client_handler.tryLock(sol), 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. +TEST_F(ClientHandleTest, serializeTwoQueries) { + // Get two queries. + Pkt6Ptr sol(new Pkt6(DHCPV6_SOLICIT, 1234)); + Pkt6Ptr req(new Pkt6(DHCPV6_REQUEST, 2345)); + OptionPtr client_id = generateClientId(); + // Same client ID: same client. + sol->addOption(client_id); + req->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 solicit. + bool duplicate = false; + EXPECT_NO_THROW(duplicate = !client_handler.tryLock(sol, 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. +TEST_F(ClientHandleTest, serializeNoCont) { + // Get two queries. + Pkt6Ptr sol(new Pkt6(DHCPV6_SOLICIT, 1234)); + Pkt6Ptr req(new Pkt6(DHCPV6_REQUEST, 2345)); + OptionPtr client_id = generateClientId(); + // Same client ID: same client. + sol->addOption(client_id); + req->addOption(client_id); + + // 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 solicit. + bool duplicate = false; + EXPECT_NO_THROW(duplicate = !client_handler.tryLock(sol)); + + // 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. +TEST_F(ClientHandleTest, serializeThreeQueries) { + // Get two queries. + Pkt6Ptr sol(new Pkt6(DHCPV6_SOLICIT, 1234)); + Pkt6Ptr req(new Pkt6(DHCPV6_REQUEST, 2345)); + Pkt6Ptr ren(new Pkt6(DHCPV6_RENEW, 3456)); + OptionPtr client_id = generateClientId(); + // Same client ID: same client. + sol->addOption(client_id); + req->addOption(client_id); + ren->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 solicit. + bool duplicate = false; + EXPECT_NO_THROW(duplicate = !client_handler.tryLock(sol, 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 renew. + EXPECT_NO_THROW(duplicate = !client_handler3.tryLock(ren, 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 -- cgit v1.2.3