summaryrefslogtreecommitdiffstats
path: root/src/lib/dhcpsrv/flq_allocator.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/dhcpsrv/flq_allocator.cc')
-rw-r--r--src/lib/dhcpsrv/flq_allocator.cc361
1 files changed, 361 insertions, 0 deletions
diff --git a/src/lib/dhcpsrv/flq_allocator.cc b/src/lib/dhcpsrv/flq_allocator.cc
new file mode 100644
index 0000000..8da982f
--- /dev/null
+++ b/src/lib/dhcpsrv/flq_allocator.cc
@@ -0,0 +1,361 @@
+// Copyright (C) 2023 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 <asiolink/addr_utilities.h>
+#include <dhcpsrv/dhcpsrv_log.h>
+#include <dhcpsrv/flq_allocator.h>
+#include <dhcpsrv/ip_range_permutation.h>
+#include <dhcpsrv/lease_mgr_factory.h>
+#include <dhcpsrv/subnet.h>
+#include <util/stopwatch.h>
+#include <unordered_set>
+
+using namespace isc::asiolink;
+using namespace isc::util;
+using namespace std;
+
+namespace {
+/// @brief An owner string used in the callbacks installed in
+/// the lease manager.
+const string FLQ_OWNER = "flq";
+}
+
+namespace isc {
+namespace dhcp {
+
+FreeLeaseQueueAllocator::FreeLeaseQueueAllocator(Lease::Type type, const WeakSubnetPtr& subnet)
+ : Allocator(type, subnet), generator_() {
+ random_device rd;
+ generator_.seed(rd());
+}
+
+IOAddress
+FreeLeaseQueueAllocator::pickAddressInternal(const ClientClasses& client_classes,
+ const IdentifierBaseTypePtr&,
+ const IOAddress&) {
+ auto subnet = subnet_.lock();
+ auto const& pools = subnet->getPools(pool_type_);
+ if (pools.empty()) {
+ // No pools, no allocation.
+ return (pool_type_ == Lease::TYPE_V4 ? IOAddress::IPV4_ZERO_ADDRESS() : IOAddress::IPV6_ZERO_ADDRESS());
+ }
+ // Let's first iterate over the pools and identify the ones that
+ // meet client class criteria and are not exhausted.
+ std::vector<uint64_t> available;
+ for (auto i = 0; i < pools.size(); ++i) {
+ // Check if the pool is allowed for the client's classes.
+ if (pools[i]->clientSupported(client_classes)) {
+ // Get or create the pool state.
+ auto pool_state = getPoolState(pools[i]);
+ if (!pool_state->exhausted()) {
+ // There are still available addresses in this pool.
+ available.push_back(i);
+ }
+ }
+ }
+ if (available.empty()) {
+ // No pool meets the client class criteria or all are exhausted.
+ return (pool_type_ == Lease::TYPE_V4 ? IOAddress::IPV4_ZERO_ADDRESS() : IOAddress::IPV6_ZERO_ADDRESS());
+ }
+ // Get a random pool from the available ones.
+ auto const& pool = pools[available[getRandomNumber(available.size() - 1)]];
+
+ // Get or create the pool state.
+ auto pool_state = getPoolState(pool);
+
+ // The pool should still offer some leases.
+ auto free_lease = pool_state->offerFreeLease();
+ // It shouldn't happen, but let's be safe.
+ if (!free_lease.isV4Zero() && !free_lease.isV6Zero()) {
+ return (free_lease);
+ }
+ // No address available.
+ return (pool_type_ == Lease::TYPE_V4 ? IOAddress::IPV4_ZERO_ADDRESS() : IOAddress::IPV6_ZERO_ADDRESS());
+}
+
+IOAddress
+FreeLeaseQueueAllocator::pickPrefixInternal(const ClientClasses& client_classes,
+ Pool6Ptr& pool6,
+ const IdentifierBaseTypePtr&,
+ PrefixLenMatchType prefix_length_match,
+ const IOAddress&,
+ uint8_t hint_prefix_length) {
+ auto subnet = subnet_.lock();
+ auto const& pools = subnet->getPools(pool_type_);
+ if (pools.empty()) {
+ // No pool, no allocation.
+ return (IOAddress::IPV6_ZERO_ADDRESS());
+ }
+ // Let's first iterate over the pools and identify the ones that
+ // meet client class criteria and are not exhausted.
+ std::vector<uint64_t> available;
+ for (auto i = 0; i < pools.size(); ++i) {
+ // Check if the pool is allowed for the client's classes.
+ if (pools[i]->clientSupported(client_classes)) {
+ if (!Allocator::isValidPrefixPool(prefix_length_match, pools[i],
+ hint_prefix_length)) {
+ continue;
+ }
+ // Get or create the pool state.
+ auto pool_state = getPoolState(pools[i]);
+ if (!pool_state->exhausted()) {
+ // There are still available prefixes in this pool.
+ available.push_back(i);
+ }
+ }
+ }
+ if (available.empty()) {
+ // No pool meets the client class criteria or all are exhausted.
+ return (IOAddress::IPV6_ZERO_ADDRESS());
+ }
+ // Get a random pool from the available ones.
+ auto const& pool = pools[available[getRandomNumber(available.size() - 1)]];
+ pool6 = boost::dynamic_pointer_cast<Pool6>(pool);
+ if (!pool6) {
+ // Something is gravely wrong here
+ isc_throw(Unexpected, "Wrong type of pool: "
+ << (pool)->toText()
+ << " is not Pool6");
+ }
+ // Get or create the pool state.
+ auto pool_state = getPoolState(pool);
+ // The pool should still offer some leases.
+ auto free_lease = pool_state->offerFreeLease();
+ // It shouldn't happen, but let's be safe.
+ if (!free_lease.isV6Zero()) {
+ return (free_lease);
+ }
+ // No prefix available.
+ return (IOAddress::IPV6_ZERO_ADDRESS());
+}
+
+void
+FreeLeaseQueueAllocator::initAfterConfigureInternal() {
+ auto subnet = subnet_.lock();
+ auto const& pools = subnet->getPools(pool_type_);
+ if (pools.empty()) {
+ // If there are no pools there is nothing to do.
+ return;
+ }
+ Lease4Collection leases4;
+ Lease6Collection leases6;
+ switch (pool_type_) {
+ case Lease::TYPE_V4:
+ leases4 = LeaseMgrFactory::instance().getLeases4(subnet->getID());
+ populateFreeAddressLeases(leases4, pools);
+ break;
+ case Lease::TYPE_NA:
+ case Lease::TYPE_TA:
+ leases6 = LeaseMgrFactory::instance().getLeases6(subnet->getID());
+ populateFreeAddressLeases(leases6, pools);
+ break;
+ case Lease::TYPE_PD:
+ leases6 = LeaseMgrFactory::instance().getLeases6(subnet->getID());
+ populateFreePrefixDelegationLeases(leases6, pools);
+ break;
+ default:
+ ;
+ }
+ // Install the callbacks for lease add, update and delete in the interface manager.
+ // These callbacks will ensure that we have up-to-date free lease queue.
+ auto& lease_mgr = LeaseMgrFactory::instance();
+ lease_mgr.registerCallback(TrackingLeaseMgr::TRACK_ADD_LEASE, FLQ_OWNER, subnet->getID(), pool_type_,
+ std::bind(&FreeLeaseQueueAllocator::addLeaseCallback, this,
+ std::placeholders::_1));
+ lease_mgr.registerCallback(TrackingLeaseMgr::TRACK_UPDATE_LEASE, FLQ_OWNER, subnet->getID(), pool_type_,
+ std::bind(&FreeLeaseQueueAllocator::updateLeaseCallback, this,
+ std::placeholders::_1));
+ lease_mgr.registerCallback(TrackingLeaseMgr::TRACK_DELETE_LEASE, FLQ_OWNER, subnet->getID(), pool_type_,
+ std::bind(&FreeLeaseQueueAllocator::deleteLeaseCallback, this,
+ std::placeholders::_1));
+}
+
+template<typename LeaseCollectionType>
+void
+FreeLeaseQueueAllocator::populateFreeAddressLeases(const LeaseCollectionType& leases,
+ const PoolCollection& pools) {
+ auto subnet = subnet_.lock();
+ LOG_INFO(dhcpsrv_logger, DHCPSRV_CFGMGR_FLQ_POPULATE_FREE_ADDRESS_LEASES)
+ .arg(subnet->toText());
+
+ Stopwatch stopwatch;
+
+ // Let's iterate over the lease queue and index them with the
+ // unordered_set. Also, elminate the expired leases and those
+ // in the expired-reclaimed state.
+ unordered_set<IOAddress, IOAddress::Hash> leased_addresses;
+ for (auto lease : leases) {
+ if ((lease->getType() == pool_type_) && (!lease->expired()) && (!lease->stateExpiredReclaimed())) {
+ leased_addresses.insert(lease->addr_);
+ }
+ }
+ // For each pool, check if the address is in the leases list.
+ size_t free_lease_count = 0;
+ for (auto pool : pools) {
+ // Create the pool permutation so the resulting lease queue is no
+ // particular order.
+ IPRangePermutation perm(AddressRange(pool->getFirstAddress(), pool->getLastAddress()));
+ auto pool_state = getPoolState(pool);
+ auto done = false;
+ while (!done) {
+ auto address = perm.next(done);
+ if (address.isV4Zero() || address.isV6Zero()) {
+ continue;
+ }
+ if (leased_addresses.count(address) == 0) {
+ // No lease for this address, so add it to the free leases queue.
+ pool_state->addFreeLease(address);
+ }
+ }
+ free_lease_count += pool_state->getFreeLeaseCount();
+ }
+
+ stopwatch.stop();
+
+ LOG_INFO(dhcpsrv_logger, DHCPSRV_CFGMGR_FLQ_POPULATE_FREE_ADDRESS_LEASES_DONE)
+ .arg(free_lease_count)
+ .arg(subnet->toText())
+ .arg(stopwatch.logFormatLastDuration());
+}
+
+void
+FreeLeaseQueueAllocator::populateFreePrefixDelegationLeases(const Lease6Collection& leases,
+ const PoolCollection& pools) {
+ auto subnet = subnet_.lock();
+ LOG_INFO(dhcpsrv_logger, DHCPSRV_CFGMGR_FLQ_POPULATE_FREE_PREFIX_LEASES)
+ .arg(subnet->toText());
+
+ Stopwatch stopwatch;
+
+ // Let's iterate over the lease queue and index them with the
+ // unordered_set. Also, elminate the expired leases and those
+ // in the expired-reclaimed state.
+ unordered_set<IOAddress, IOAddress::Hash> leased_prefixes;
+ for (auto lease : leases) {
+ if ((lease->getType() == Lease::TYPE_PD) && (!lease->expired()) && (!lease->stateExpiredReclaimed())) {
+ leased_prefixes.insert(lease->addr_);
+ }
+ }
+ // For each pool, check if the prefix is in the leases list.
+ size_t free_lease_count = 0;
+ for (auto pool : pools) {
+ auto pool6 = boost::dynamic_pointer_cast<Pool6>(pool);
+ if (!pool6) {
+ continue;
+ }
+ // Create the pool permutation so the resulting lease queue is no
+ // particular order.
+ IPRangePermutation perm(PrefixRange(pool->getFirstAddress(),
+ pool->getLastAddress(),
+ pool6->getLength()));
+ auto pool_state = getPoolState(pool);
+ auto done = false;
+ while (!done) {
+ auto prefix = perm.next(done);
+ if (prefix.isV4Zero() || prefix.isV6Zero()) {
+ continue;
+ }
+ if (leased_prefixes.count(prefix) == 0) {
+ // No lease for this prefix, so add it to the free leases queue.
+ pool_state->addFreeLease(prefix);
+ }
+ }
+ free_lease_count += pool_state->getFreeLeaseCount();
+ }
+
+ stopwatch.stop();
+
+ LOG_INFO(dhcpsrv_logger, DHCPSRV_CFGMGR_FLQ_POPULATE_FREE_PREFIX_LEASES_DONE)
+ .arg(free_lease_count)
+ .arg(subnet->toText())
+ .arg(stopwatch.logFormatLastDuration());
+}
+
+PoolFreeLeaseQueueAllocationStatePtr
+FreeLeaseQueueAllocator::getPoolState(const PoolPtr& pool) const {
+ if (!pool->getAllocationState()) {
+ pool->setAllocationState(PoolFreeLeaseQueueAllocationState::create(pool));
+ }
+ return (boost::dynamic_pointer_cast<PoolFreeLeaseQueueAllocationState>(pool->getAllocationState()));
+}
+
+PoolPtr
+FreeLeaseQueueAllocator::getLeasePool(const LeasePtr& lease) const {
+ auto subnet = subnet_.lock();
+ if (!subnet) {
+ return (PoolPtr());
+ }
+ auto pool = subnet->getPool(pool_type_, lease->addr_, false);
+ return (pool);
+}
+
+void
+FreeLeaseQueueAllocator::addLeaseCallback(LeasePtr lease) {
+ MultiThreadingLock lock(mutex_);
+ addLeaseCallbackInternal(lease);
+}
+
+void
+FreeLeaseQueueAllocator::addLeaseCallbackInternal(LeasePtr lease) {
+ if (lease->expired()) {
+ return;
+ }
+ auto pool = getLeasePool(lease);
+ if (!pool) {
+ return;
+ }
+ getPoolState(pool)->deleteFreeLease(lease->addr_);
+}
+
+void
+FreeLeaseQueueAllocator::updateLeaseCallback(LeasePtr lease) {
+ MultiThreadingLock lock(mutex_);
+ updateLeaseCallbackInternal(lease);
+}
+
+void
+FreeLeaseQueueAllocator::updateLeaseCallbackInternal(LeasePtr lease) {
+ auto pool = getLeasePool(lease);
+ if (!pool) {
+ return;
+ }
+ auto pool_state = getPoolState(pool);
+ if (lease->stateExpiredReclaimed() || (lease->expired())) {
+ pool_state->addFreeLease(lease->addr_);
+ } else {
+ pool_state->deleteFreeLease(lease->addr_);
+ }
+}
+
+void
+FreeLeaseQueueAllocator::deleteLeaseCallback(LeasePtr lease) {
+ MultiThreadingLock lock(mutex_);
+ deleteLeaseCallbackInternal(lease);
+}
+
+void
+FreeLeaseQueueAllocator::deleteLeaseCallbackInternal(LeasePtr lease) {
+ auto pool = getLeasePool(lease);
+ if (!pool) {
+ return;
+ }
+ getPoolState(pool)->addFreeLease(lease->addr_);
+}
+
+uint64_t
+FreeLeaseQueueAllocator::getRandomNumber(uint64_t limit) {
+ // Take the short path if there is only one number to randomize from.
+ if (limit == 0) {
+ return (0);
+ }
+ std::uniform_int_distribution<uint64_t> dist(0, limit);
+ return (dist(generator_));
+}
+
+} // end of namespace isc::dhcp
+} // end of namespace isc