From 069c6ec34e3a73ea6fd2db3f38eeaa63f5f2ae33 Mon Sep 17 00:00:00 2001 From: Francis Dupont Date: Wed, 13 Aug 2025 00:32:06 +0200 Subject: [PATCH] [#226] Finished v4 code --- src/bin/dhcp4/tests/flq_unittest.cc | 403 ++++++++++++++++++ src/bin/dhcp4/tests/meson.build | 1 + .../dhcp4/tests/simple_parser4_unittest.cc | 6 +- src/lib/dhcpsrv/alloc_engine.cc | 5 +- 4 files changed, 410 insertions(+), 5 deletions(-) create mode 100644 src/bin/dhcp4/tests/flq_unittest.cc diff --git a/src/bin/dhcp4/tests/flq_unittest.cc b/src/bin/dhcp4/tests/flq_unittest.cc new file mode 100644 index 0000000000..48730258d0 --- /dev/null +++ b/src/bin/dhcp4/tests/flq_unittest.cc @@ -0,0 +1,403 @@ +// Copyright (C) 2025 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 +#include + +using namespace isc; +using namespace isc::asiolink; +using namespace isc::dhcp; +using namespace isc::dhcp::test; + +namespace { + +/// @brief JSON configuration +std::string FLQ_CONFIG = + "{ \"interfaces-config\": {" + " \"interfaces\": [ \"*\" ]" + "}," + "\"min-valid-lifetime\": 100," + "\"valid-lifetime\": 200," + "\"max-valid-lifetime\": 300," + "\"adaptive-lease-time-threshold\": .5," + "\"cache-threshold\": 0.," + "\"subnet4\": [ { " + " \"subnet\": \"10.0.0.0/24\", " + " \"id\": 1," + " \"pools\": [ { \"pool\": \"10.0.0.11-10.0.0.14\" } ]," + " \"allocator\": \"flq\"" + " } ]" + "}"; + +/// @brief Test fixture class for testing FLQ / adaptive-lease-time-threshold. +class FLQTest : public Dhcpv4SrvTest { +public: + /// @brief Constructor. + /// + /// Sets up fake interfaces. + FLQTest() : Dhcpv4SrvTest(), iface_mgr_test_config_(true) { + } + + /// @brief Creates a DHCPv4 lease for an address and MAC address. + /// + /// @param address Lease address. + /// @param hw_address_seed a seed from which the hardware address is generated. + /// @return Created lease pointer. + Lease4Ptr + createLease4(const IOAddress& address, uint64_t hw_address_seed) const { + vector hw_address_vec(sizeof(hw_address_seed)); + for (unsigned i = 0; i < sizeof(hw_address_seed); ++i) { + hw_address_vec[i] = (hw_address_seed >> i) & 0xFF; + } + HWAddrPtr hw_address(new HWAddr(hw_address_vec, HTYPE_ETHER)); + Lease4Ptr lease(new Lease4(address, hw_address, ClientIdPtr(), + 600, time(0), 1)); + return (lease); + } + + /// @brief Interface Manager's fake configuration control. + IfaceMgrTestConfig iface_mgr_test_config_; +}; + +// Test allocation with empty pool. +TEST_F(FLQTest, empty) { + Dhcp4Client client(srv_, Dhcp4Client::SELECTING); + + // Configure DHCP server. + configure(FLQ_CONFIG, *client.getServer()); + + // Perform 4-way exchange with the server requesting the last address. + boost::shared_ptr hint(new IOAddress("10.0.0.14")); + ASSERT_NO_THROW(client.doDORA(hint)); + + // Make sure that the server responded. + Pkt4Ptr resp = client.getContext().response_; + ASSERT_TRUE(resp); + + // Make sure that the server has responded with DHCPACK. + ASSERT_EQ(DHCPACK, static_cast(resp->getType())); + + // Make sure that the client has got the requested address. + EXPECT_EQ(*hint, resp->getYiaddr()); + + // Valid lifetime should be the valid-lifetime parameter value (200). + OptionUint32Ptr opt = boost::dynamic_pointer_cast< + OptionUint32>(resp->getOption(DHO_DHCP_LEASE_TIME)); + ASSERT_TRUE(opt); + EXPECT_EQ(200, opt->getValue()); +} + +// Test allocation with almost full pool. +TEST_F(FLQTest, last) { + Dhcp4Client client(srv_, Dhcp4Client::SELECTING); + + // Configure DHCP server. + configure(FLQ_CONFIG, *client.getServer()); + + // Create leases for the first addresses. + auto& lease_mgr = LeaseMgrFactory::instance(); + auto lease1 = createLease4(IOAddress("10.0.0.11"), 1); + EXPECT_TRUE(lease_mgr.addLease(lease1)); + auto lease2 = createLease4(IOAddress("10.0.0.12"), 2); + EXPECT_TRUE(lease_mgr.addLease(lease2)); + auto lease3 = createLease4(IOAddress("10.0.0.13"), 3); + EXPECT_TRUE(lease_mgr.addLease(lease3)); + + // Perform 4-way exchange with the server. + ASSERT_NO_THROW(client.doDORA()); + + // Make sure that the server responded. + Pkt4Ptr resp = client.getContext().response_; + ASSERT_TRUE(resp); + + // Make sure that the server has responded with DHCPACK. + ASSERT_EQ(DHCPACK, static_cast(resp->getType())); + + // Make sure that the client has got the last address. + EXPECT_EQ("10.0.0.14", resp->getYiaddr().toText()); + + // Valid lifetime should be the min-valid-lifetime parameter value (100). + OptionUint32Ptr opt = boost::dynamic_pointer_cast< + OptionUint32>(resp->getOption(DHO_DHCP_LEASE_TIME)); + ASSERT_TRUE(opt); + EXPECT_EQ(100, opt->getValue()); +} + +// Test allocation with an expired lease. +TEST_F(FLQTest, expired) { + Dhcp4Client client(srv_, Dhcp4Client::SELECTING); + + // Configure DHCP server. + configure(FLQ_CONFIG, *client.getServer()); + + // Create leases for the first addresses. + auto& lease_mgr = LeaseMgrFactory::instance(); + auto lease1 = createLease4(IOAddress("10.0.0.11"), 1); + EXPECT_TRUE(lease_mgr.addLease(lease1)); + auto lease2 = createLease4(IOAddress("10.0.0.12"), 2); + EXPECT_TRUE(lease_mgr.addLease(lease2)); + auto lease3 = createLease4(IOAddress("10.0.0.13"), 3); + EXPECT_TRUE(lease_mgr.addLease(lease3)); + auto lease4 = createLease4(IOAddress("10.0.0.14"), 4); + // Expired lease. + lease4->cltt_ -= 1000; + lease4->current_cltt_ -= 1000; + ASSERT_TRUE(lease4->expired()); + EXPECT_TRUE(lease_mgr.addLease(lease4)); + + // Perform 4-way exchange with the server. + ASSERT_NO_THROW(client.doDORA()); + + // Make sure that the server responded. + Pkt4Ptr resp = client.getContext().response_; + ASSERT_TRUE(resp); + + // Make sure that the server has responded with DHCPACK. + ASSERT_EQ(DHCPACK, static_cast(resp->getType())); + + // Make sure that the client has got the last address. + EXPECT_EQ("10.0.0.14", resp->getYiaddr().toText()); + + // Valid lifetime should be the min-valid-lifetime parameter value (100). + OptionUint32Ptr opt = boost::dynamic_pointer_cast< + OptionUint32>(resp->getOption(DHO_DHCP_LEASE_TIME)); + ASSERT_TRUE(opt); + EXPECT_EQ(100, opt->getValue()); +} + +// Test allocation with a reclaimed lease. +TEST_F(FLQTest, reclaimed) { + Dhcp4Client client(srv_, Dhcp4Client::SELECTING); + + // Configure DHCP server. + configure(FLQ_CONFIG, *client.getServer()); + + // Create leases for the first addresses. + auto& lease_mgr = LeaseMgrFactory::instance(); + auto lease1 = createLease4(IOAddress("10.0.0.11"), 1); + EXPECT_TRUE(lease_mgr.addLease(lease1)); + auto lease2 = createLease4(IOAddress("10.0.0.12"), 2); + EXPECT_TRUE(lease_mgr.addLease(lease2)); + auto lease3 = createLease4(IOAddress("10.0.0.13"), 3); + EXPECT_TRUE(lease_mgr.addLease(lease3)); + auto lease4 = createLease4(IOAddress("10.0.0.14"), 4); + // Reclaimed lease. + lease4->cltt_ -= 1000; + lease4->current_cltt_ -= 1000; + lease4->state_ = Lease::STATE_EXPIRED_RECLAIMED; + EXPECT_TRUE(lease_mgr.addLease(lease4)); + + // Perform 4-way exchange with the server. + ASSERT_NO_THROW(client.doDORA()); + + // Make sure that the server responded. + Pkt4Ptr resp = client.getContext().response_; + ASSERT_TRUE(resp); + + // Make sure that the server has responded with DHCPACK. + ASSERT_EQ(DHCPACK, static_cast(resp->getType())); + + // Make sure that the client has got the last address. + EXPECT_EQ("10.0.0.14", resp->getYiaddr().toText()); + + // Valid lifetime should be the min-valid-lifetime parameter value (100). + OptionUint32Ptr opt = boost::dynamic_pointer_cast< + OptionUint32>(resp->getOption(DHO_DHCP_LEASE_TIME)); + ASSERT_TRUE(opt); + EXPECT_EQ(100, opt->getValue()); +} + +// Test renewal with almost empty pool. +TEST_F(FLQTest, renew) { + Dhcp4Client client(srv_, Dhcp4Client::SELECTING); + + // Configure DHCP server. + configure(FLQ_CONFIG, *client.getServer()); + + // Obtain a lease from the server using the 4-way exchange. + boost::shared_ptr hint(new IOAddress("10.0.0.14")); + ASSERT_NO_THROW(client.doDORA(hint)); + + // Make sure that the server responded. + Pkt4Ptr resp = client.getContext().response_; + ASSERT_TRUE(resp); + + // Make sure that the server has responded with DHCPACK. + ASSERT_EQ(DHCPACK, static_cast(resp->getType())); + + // Make sure that the client has got the requested address. + EXPECT_EQ(*hint, resp->getYiaddr()); + + // Valid lifetime should be the valid-lifetime parameter value (200). + OptionUint32Ptr opt = boost::dynamic_pointer_cast< + OptionUint32>(resp->getOption(DHO_DHCP_LEASE_TIME)); + ASSERT_TRUE(opt); + EXPECT_EQ(200, opt->getValue()); + + // Age the lease. + auto& lease_mgr = LeaseMgrFactory::instance(); + Lease4Ptr lease = lease_mgr.getLease4(*hint); + ASSERT_TRUE(lease); + lease->cltt_ -= 150; + lease->current_cltt_ -= 150; + EXPECT_NO_THROW(lease_mgr.updateLease4(lease)); + + // Let's transition the client to Renewing state. + client.setState(Dhcp4Client::RENEWING); + + // Set the unicast destination address to indicate that it is a renewal. + client.setDestAddress(IOAddress("10.0.0.1")); + ASSERT_NO_THROW(client.doRequest()); + + // Make sure that renewal was ACKed. + resp = client.getContext().response_; + ASSERT_TRUE(resp); + ASSERT_EQ(DHCPACK, static_cast(resp->getType())); + + // Make sure that the client renewed the requested address. + EXPECT_EQ(*hint, resp->getYiaddr()); + + // Valid lifetime should be the valid-lifetime parameter value (200). + opt = boost::dynamic_pointer_cast(resp->getOption(DHO_DHCP_LEASE_TIME)); + ASSERT_TRUE(opt); + EXPECT_EQ(200, opt->getValue()); +} + +// Test renewal with full pool. +TEST_F(FLQTest, renewFull) { + Dhcp4Client client(srv_, Dhcp4Client::SELECTING); + + // Configure DHCP server. + configure(FLQ_CONFIG, *client.getServer()); + + // Obtain a lease from the server using the 4-way exchange. + boost::shared_ptr hint(new IOAddress("10.0.0.14")); + ASSERT_NO_THROW(client.doDORA(hint)); + + // Make sure that the server responded. + Pkt4Ptr resp = client.getContext().response_; + ASSERT_TRUE(resp); + + // Make sure that the server has responded with DHCPACK. + ASSERT_EQ(DHCPACK, static_cast(resp->getType())); + + // Make sure that the client has got the requested address. + EXPECT_EQ(*hint, resp->getYiaddr()); + + // Valid lifetime should be the valid-lifetime parameter value (200). + OptionUint32Ptr opt = boost::dynamic_pointer_cast< + OptionUint32>(resp->getOption(DHO_DHCP_LEASE_TIME)); + ASSERT_TRUE(opt); + EXPECT_EQ(200, opt->getValue()); + + // Age the lease. + auto& lease_mgr = LeaseMgrFactory::instance(); + Lease4Ptr lease = lease_mgr.getLease4(*hint); + ASSERT_TRUE(lease); + lease->cltt_ -= 150; + lease->current_cltt_ -= 150; + EXPECT_NO_THROW(lease_mgr.updateLease4(lease)); + + // Create leases for the first addresses. + auto lease1 = createLease4(IOAddress("10.0.0.11"), 1); + EXPECT_TRUE(lease_mgr.addLease(lease1)); + auto lease2 = createLease4(IOAddress("10.0.0.12"), 2); + EXPECT_TRUE(lease_mgr.addLease(lease2)); + auto lease3 = createLease4(IOAddress("10.0.0.13"), 3); + EXPECT_TRUE(lease_mgr.addLease(lease3)); + + // Let's transition the client to Renewing state. + client.setState(Dhcp4Client::RENEWING); + + // Set the unicast destination address to indicate that it is a renewal. + client.setDestAddress(IOAddress("10.0.0.1")); + ASSERT_NO_THROW(client.doRequest()); + + // Make sure that renewal was ACKed. + resp = client.getContext().response_; + ASSERT_TRUE(resp); + ASSERT_EQ(DHCPACK, static_cast(resp->getType())); + + // Make sure that the client renewed the requested address. + EXPECT_EQ(*hint, resp->getYiaddr()); + + // Valid lifetime should be the min-valid-lifetime parameter value (100). + opt = boost::dynamic_pointer_cast(resp->getOption(DHO_DHCP_LEASE_TIME)); + ASSERT_TRUE(opt); + EXPECT_EQ(100, opt->getValue()); +} + +// Test renewal with full pool but remaining lifetime greater than minimal. +TEST_F(FLQTest, renewRemaining) { + Dhcp4Client client(srv_, Dhcp4Client::SELECTING); + + // Configure DHCP server. + configure(FLQ_CONFIG, *client.getServer()); + + // Obtain a lease from the server using the 4-way exchange. + boost::shared_ptr hint(new IOAddress("10.0.0.14")); + ASSERT_NO_THROW(client.doDORA(hint)); + + // Make sure that the server responded. + Pkt4Ptr resp = client.getContext().response_; + ASSERT_TRUE(resp); + + // Make sure that the server has responded with DHCPACK. + ASSERT_EQ(DHCPACK, static_cast(resp->getType())); + + // Make sure that the client has got the requested address. + EXPECT_EQ(*hint, resp->getYiaddr()); + + // Valid lifetime should be the valid-lifetime parameter value (200). + OptionUint32Ptr opt = boost::dynamic_pointer_cast< + OptionUint32>(resp->getOption(DHO_DHCP_LEASE_TIME)); + ASSERT_TRUE(opt); + EXPECT_EQ(200, opt->getValue()); + + // Age the lease but only by 50 seconds. + auto& lease_mgr = LeaseMgrFactory::instance(); + Lease4Ptr lease = lease_mgr.getLease4(*hint); + ASSERT_TRUE(lease); + lease->cltt_ -= 50; + lease->current_cltt_ -= 50; + EXPECT_NO_THROW(lease_mgr.updateLease4(lease)); + + // Create leases for the first addresses. + auto lease1 = createLease4(IOAddress("10.0.0.11"), 1); + EXPECT_TRUE(lease_mgr.addLease(lease1)); + auto lease2 = createLease4(IOAddress("10.0.0.12"), 2); + EXPECT_TRUE(lease_mgr.addLease(lease2)); + auto lease3 = createLease4(IOAddress("10.0.0.13"), 3); + EXPECT_TRUE(lease_mgr.addLease(lease3)); + + // Let's transition the client to Renewing state. + client.setState(Dhcp4Client::RENEWING); + + // Set the unicast destination address to indicate that it is a renewal. + client.setDestAddress(IOAddress("10.0.0.1")); + ASSERT_NO_THROW(client.doRequest()); + + // Make sure that renewal was ACKed. + resp = client.getContext().response_; + ASSERT_TRUE(resp); + ASSERT_EQ(DHCPACK, static_cast(resp->getType())); + + // Make sure that the client renewed the requested address. + EXPECT_EQ(*hint, resp->getYiaddr()); + + // Valid lifetime should be the remaining lifetime so ~150 seconds. + opt = boost::dynamic_pointer_cast(resp->getOption(DHO_DHCP_LEASE_TIME)); + ASSERT_TRUE(opt); + EXPECT_NEAR(150, opt->getValue(), 2); +} + +} diff --git a/src/bin/dhcp4/tests/meson.build b/src/bin/dhcp4/tests/meson.build index d7657f94d4..7cc8121fcd 100644 --- a/src/bin/dhcp4/tests/meson.build +++ b/src/bin/dhcp4/tests/meson.build @@ -126,6 +126,7 @@ kea_dhcp4_tests = executable( 'dhcp4to6_ipc_unittest.cc', 'direct_client_unittest.cc', 'dora_unittest.cc', + 'flq_unittest.cc', 'fqdn_unittest.cc', 'get_config_unittest.cc', 'hooks_unittest.cc', diff --git a/src/bin/dhcp4/tests/simple_parser4_unittest.cc b/src/bin/dhcp4/tests/simple_parser4_unittest.cc index 3a661ba79a..421217f0fd 100644 --- a/src/bin/dhcp4/tests/simple_parser4_unittest.cc +++ b/src/bin/dhcp4/tests/simple_parser4_unittest.cc @@ -210,6 +210,6 @@ TEST_F(SimpleParser4Test, optionDefDefaults4) { checkBoolValue(def, "array", false); } -}; -}; -}; +} +} +} diff --git a/src/lib/dhcpsrv/alloc_engine.cc b/src/lib/dhcpsrv/alloc_engine.cc index 775759c391..abd56db5ce 100644 --- a/src/lib/dhcpsrv/alloc_engine.cc +++ b/src/lib/dhcpsrv/alloc_engine.cc @@ -4475,7 +4475,6 @@ AllocEngine::createLease4(const ClientContext4& ctx, const IOAddress& addr, } } if (use_min) { - // Log getMinValidLft(ctx, valid_lft); } else { valid_lft = getValidLft(ctx); @@ -5178,6 +5177,8 @@ AllocEngine::updateLease4Information(const Lease4Ptr& lease, changed = true; lease->client_id_ = ClientIdPtr(); } + uint32_t remain_lft(0); + getRemaining(lease, remain_lft); lease->cltt_ = time(0); // Get the context appropriate valid lifetime. @@ -5193,7 +5194,7 @@ AllocEngine::updateLease4Information(const Lease4Ptr& lease, } } if (use_min) { - // Log + lease->valid_lft_ = remain_lft; getMinValidLft(ctx, lease->valid_lft_); } else { lease->valid_lft_ = getValidLft(ctx);