Kea 3.1.9
flq_allocator.cc
Go to the documentation of this file.
1// Copyright (C) 2023-2025 Internet Systems Consortium, Inc. ("ISC")
2//
3// This Source Code Form is subject to the terms of the Mozilla Public
4// License, v. 2.0. If a copy of the MPL was not distributed with this
5// file, You can obtain one at http://mozilla.org/MPL/2.0/.
6
7#include <config.h>
8
10#include <dhcpsrv/dhcpsrv_log.h>
14#include <dhcpsrv/subnet.h>
15#include <util/stopwatch.h>
16#include <limits>
17#include <unordered_set>
18
19using namespace isc::asiolink;
20using namespace isc::util;
21using namespace std;
22
23namespace {
26const string FLQ_OWNER = "flq";
27}
28
29namespace isc {
30namespace dhcp {
31
33 : Allocator(type, subnet), generator_() {
34 random_device rd;
35 generator_.seed(rd());
36}
37
39FreeLeaseQueueAllocator::pickAddressInternal(const ClientClasses& client_classes,
41 const IOAddress&) {
42 auto subnet = subnet_.lock();
43 auto const& pools = subnet->getPools(pool_type_);
44 if (pools.empty()) {
45 // No pools, no allocation.
47 }
48 // Let's first iterate over the pools and identify the ones that
49 // meet client class criteria and are not exhausted.
50 std::vector<uint64_t> available;
51 for (unsigned i = 0; i < pools.size(); ++i) {
52 // Check if the pool is allowed for the client's classes.
53 if (pools[i]->clientSupported(client_classes)) {
54 // Get or create the pool state.
55 auto pool_state = getPoolState(pools[i]);
56 if (!pool_state->exhausted()) {
57 // There are still available addresses in this pool.
58 available.push_back(i);
59 }
60 }
61 }
62 if (available.empty()) {
63 // No pool meets the client class criteria or all are exhausted.
65 }
66 // Get a random pool from the available ones.
67 auto const& pool = pools[available[getRandomNumber(available.size() - 1)]];
68
69 // Get or create the pool state.
70 auto pool_state = getPoolState(pool);
71
72 // The pool should still offer some leases.
73 auto free_lease = pool_state->offerFreeLease();
74 // It shouldn't happen, but let's be safe.
75 if (!free_lease.isV4Zero() && !free_lease.isV6Zero()) {
76 return (free_lease);
77 }
78 // No address available.
80}
81
83FreeLeaseQueueAllocator::pickPrefixInternal(const ClientClasses& client_classes,
84 Pool6Ptr& pool6,
86 PrefixLenMatchType prefix_length_match,
87 const IOAddress&,
88 uint8_t hint_prefix_length) {
89 auto subnet = subnet_.lock();
90 auto const& pools = subnet->getPools(pool_type_);
91 if (pools.empty()) {
92 // No pool, no allocation.
94 }
95 // Let's first iterate over the pools and identify the ones that
96 // meet client class criteria and are not exhausted.
97 std::vector<uint64_t> available;
98 for (unsigned i = 0; i < pools.size(); ++i) {
99 // Check if the pool is allowed for the client's classes.
100 if (pools[i]->clientSupported(client_classes)) {
101 if (!Allocator::isValidPrefixPool(prefix_length_match, pools[i],
102 hint_prefix_length)) {
103 continue;
104 }
105 // Get or create the pool state.
106 auto pool_state = getPoolState(pools[i]);
107 if (!pool_state->exhausted()) {
108 // There are still available prefixes in this pool.
109 available.push_back(i);
110 }
111 }
112 }
113 if (available.empty()) {
114 // No pool meets the client class criteria or all are exhausted.
116 }
117 // Get a random pool from the available ones.
118 auto const& pool = pools[available[getRandomNumber(available.size() - 1)]];
119 pool6 = boost::dynamic_pointer_cast<Pool6>(pool);
120 if (!pool6) {
121 // Something is gravely wrong here
122 isc_throw(Unexpected, "Wrong type of pool: "
123 << (pool)->toText()
124 << " is not Pool6");
125 }
126 // Get or create the pool state.
127 auto pool_state = getPoolState(pool);
128 // The pool should still offer some leases.
129 auto free_lease = pool_state->offerFreeLease();
130 // It shouldn't happen, but let's be safe.
131 if (!free_lease.isV6Zero()) {
132 return (free_lease);
133 }
134 // No prefix available.
136}
137
138double
140 const ClientClasses& client_classes) {
142 return (getOccupancyRateInternal(addr, client_classes));
143}
144
145double
146FreeLeaseQueueAllocator::getOccupancyRateInternal(const IOAddress& addr,
147 const ClientClasses& client_classes) {
148 // Sanity.
149 if (!addr.isV4()) {
150 return (0.);
151 }
152 auto subnet = subnet_.lock();
153 uint128_t total(0);
154 uint128_t busy(0);
155 bool found(false);
156
157 for (auto const& pool : subnet->getPools(Lease::TYPE_V4)) {
158 if (!pool->clientSupported(client_classes)) {
159 continue;
160 }
161 uint128_t capacity = pool->getCapacity();
162 total += capacity;
163 if (total >= std::numeric_limits<uint64_t>::max()) {
164 return (0.);
165 }
166 auto pool_state = boost::dynamic_pointer_cast<PoolFreeLeaseQueueAllocationState>(pool->getAllocationState());
167 if (!pool_state) {
168 continue;
169 }
170 uint128_t free_cnt = pool_state->getFreeLeaseCount();
171 if (!found && pool->inRange(addr)) {
172 found = true;
173 if ((free_cnt > 0) && pool_state->isFreeLease(addr)) {
174 --free_cnt;
175 }
176 }
177 if (free_cnt > capacity) {
178 free_cnt = capacity;
179 }
180 busy += capacity - free_cnt;
181 }
182 if (!found) {
183 return (0.);
184 }
185 // Should not happen...
186 if (total == 0) {
187 return (0.);
188 }
189 return (static_cast<double>(busy) / static_cast<double>(total));
190}
191
192double
194 const uint8_t plen,
195 const ClientClasses& client_classes) {
197 return (getOccupancyRateInternal(pref, plen, client_classes));
198}
199
200double
201FreeLeaseQueueAllocator::getOccupancyRateInternal(const IOAddress& pref,
202 const uint8_t plen,
203 const ClientClasses& client_classes) {
204 // Sanity.
205 if (!pref.isV6()) {
206 return (0.);
207 }
208 auto subnet = subnet_.lock();
209 uint128_t total(0);
210 uint128_t busy(0);
211 bool found(false);
212
213 for (auto const& pool : subnet->getPools(Lease::TYPE_PD)) {
214 if (!pool->clientSupported(client_classes)) {
215 continue;
216 }
217 auto const& pool6 = boost::dynamic_pointer_cast<Pool6>(pool);
218 if (!pool6 || (pool6->getLength() > plen)) {
219 continue;
220 }
221 uint128_t capacity = pool->getCapacity();
222 total += capacity;
223 if (total >= std::numeric_limits<uint64_t>::max()) {
224 return (0.);
225 }
226 auto pool_state = boost::dynamic_pointer_cast<PoolFreeLeaseQueueAllocationState>(pool->getAllocationState());
227 if (!pool_state) {
228 continue;
229 }
230 uint128_t free_cnt = pool_state->getFreeLeaseCount();
231 if (!found && pool->inRange(pref)) {
232 found = true;
233 if ((free_cnt > 0) && pool_state->isFreeLease(pref)) {
234 --free_cnt;
235 }
236 }
237 if (free_cnt > capacity) {
238 free_cnt = capacity;
239 }
240 busy += capacity - free_cnt;
241 }
242 if (!found) {
243 return (0.);
244 }
245 // Should not happen...
246 if (total == 0) {
247 return (0.);
248 }
249 return (static_cast<double>(busy) / static_cast<double>(total));
250}
251
252void
254 auto subnet = subnet_.lock();
255 auto const& pools = subnet->getPools(pool_type_);
256 if (pools.empty()) {
257 // If there are no pools there is nothing to do.
258 return;
259 }
260 Lease4Collection leases4;
261 Lease6Collection leases6;
262 switch (pool_type_) {
263 case Lease::TYPE_V4:
264 leases4 = LeaseMgrFactory::instance().getLeases4(subnet->getID());
265 populateFreeAddressLeases(leases4, pools);
266 break;
267 case Lease::TYPE_NA:
268 leases6 = LeaseMgrFactory::instance().getLeases6(subnet->getID());
269 populateFreeAddressLeases(leases6, pools);
270 break;
271 case Lease::TYPE_PD:
272 leases6 = LeaseMgrFactory::instance().getLeases6(subnet->getID());
273 populateFreePrefixDelegationLeases(leases6, pools);
274 break;
275 default:
276 ;
277 }
278 // Install the callbacks for lease add, update and delete in the interface manager.
279 // These callbacks will ensure that we have up-to-date free lease queue.
280 auto& lease_mgr = LeaseMgrFactory::instance();
281 lease_mgr.registerCallback(TrackingLeaseMgr::TRACK_ADD_LEASE, FLQ_OWNER, subnet->getID(), pool_type_,
282 std::bind(&FreeLeaseQueueAllocator::addLeaseCallback, this,
283 std::placeholders::_1));
284 lease_mgr.registerCallback(TrackingLeaseMgr::TRACK_UPDATE_LEASE, FLQ_OWNER, subnet->getID(), pool_type_,
285 std::bind(&FreeLeaseQueueAllocator::updateLeaseCallback, this,
286 std::placeholders::_1));
287 lease_mgr.registerCallback(TrackingLeaseMgr::TRACK_DELETE_LEASE, FLQ_OWNER, subnet->getID(), pool_type_,
288 std::bind(&FreeLeaseQueueAllocator::deleteLeaseCallback, this,
289 std::placeholders::_1));
290}
291
292template<typename LeaseCollectionType>
293void
294FreeLeaseQueueAllocator::populateFreeAddressLeases(const LeaseCollectionType& leases,
295 const PoolCollection& pools) {
296 auto subnet = subnet_.lock();
298 .arg(subnet->toText());
299
300 Stopwatch stopwatch;
301
302 // Let's iterate over the lease queue and index them with the
303 // unordered_set. Also, eliminate the expired leases and those
304 // in the expired-reclaimed state.
305 unordered_set<IOAddress, IOAddress::Hash> leased_addresses;
306 for (auto const& lease : leases) {
307 if ((lease->getType() == pool_type_) && (!lease->expired()) && (!lease->stateExpiredReclaimed())) {
308 leased_addresses.insert(lease->addr_);
309 }
310 }
311 // For each pool, check if the address is in the leases list.
312 size_t free_lease_count = 0;
313 for (auto const& pool : pools) {
314 // Create the pool permutation so the resulting lease queue is no
315 // particular order.
316 IPRangePermutation perm(AddressRange(pool->getFirstAddress(), pool->getLastAddress()));
317 auto pool_state = getPoolState(pool);
318 auto done = false;
319 while (!done) {
320 auto address = perm.next(done);
321 if (address.isV4Zero() || address.isV6Zero()) {
322 continue;
323 }
324 if (leased_addresses.count(address) == 0) {
325 // No lease for this address, so add it to the free leases queue.
326 pool_state->addFreeLease(address);
327 }
328 }
329 free_lease_count += pool_state->getFreeLeaseCount();
330 }
331
332 stopwatch.stop();
333
335 .arg(free_lease_count)
336 .arg(subnet->toText())
337 .arg(stopwatch.logFormatLastDuration());
338}
339
340void
341FreeLeaseQueueAllocator::populateFreePrefixDelegationLeases(const Lease6Collection& leases,
342 const PoolCollection& pools) {
343 auto subnet = subnet_.lock();
345 .arg(subnet->toText());
346
347 Stopwatch stopwatch;
348
349 // Let's iterate over the lease queue and index them with the
350 // unordered_set. Also, eliminate the expired leases and those
351 // in the expired-reclaimed state.
352 unordered_set<IOAddress, IOAddress::Hash> leased_prefixes;
353 for (auto const& lease : leases) {
354 if ((lease->getType() == Lease::TYPE_PD) && (!lease->expired()) && (!lease->stateExpiredReclaimed())) {
355 leased_prefixes.insert(lease->addr_);
356 }
357 }
358 // For each pool, check if the prefix is in the leases list.
359 size_t free_lease_count = 0;
360 for (auto const& pool : pools) {
361 auto pool6 = boost::dynamic_pointer_cast<Pool6>(pool);
362 if (!pool6) {
363 continue;
364 }
365 // Create the pool permutation so the resulting lease queue is no
366 // particular order.
367 IPRangePermutation perm(PrefixRange(pool->getFirstAddress(),
368 pool->getLastAddress(),
369 pool6->getLength()));
370 auto pool_state = getPoolState(pool);
371 auto done = false;
372 while (!done) {
373 auto prefix = perm.next(done);
374 if (prefix.isV4Zero() || prefix.isV6Zero()) {
375 continue;
376 }
377 if (leased_prefixes.count(prefix) == 0) {
378 // No lease for this prefix, so add it to the free leases queue.
379 pool_state->addFreeLease(prefix);
380 }
381 }
382 free_lease_count += pool_state->getFreeLeaseCount();
383 }
384
385 stopwatch.stop();
386
388 .arg(free_lease_count)
389 .arg(subnet->toText())
390 .arg(stopwatch.logFormatLastDuration());
391}
392
394FreeLeaseQueueAllocator::getPoolState(const PoolPtr& pool) const {
395 if (!pool->getAllocationState()) {
396 pool->setAllocationState(PoolFreeLeaseQueueAllocationState::create(pool));
397 }
398 return (boost::dynamic_pointer_cast<PoolFreeLeaseQueueAllocationState>(pool->getAllocationState()));
399}
400
402FreeLeaseQueueAllocator::getLeasePool(const LeasePtr& lease) const {
403 auto subnet = subnet_.lock();
404 if (!subnet) {
405 return (PoolPtr());
406 }
407 auto pool = subnet->getPool(pool_type_, lease->addr_, false);
408 return (pool);
409}
410
411void
412FreeLeaseQueueAllocator::addLeaseCallback(LeasePtr lease) {
413 MultiThreadingLock lock(mutex_);
414 addLeaseCallbackInternal(lease);
415}
416
417void
418FreeLeaseQueueAllocator::addLeaseCallbackInternal(LeasePtr lease) {
419 if (lease->expired()) {
420 return;
421 }
422 auto pool = getLeasePool(lease);
423 if (!pool) {
424 return;
425 }
426 getPoolState(pool)->deleteFreeLease(lease->addr_);
427}
428
429void
430FreeLeaseQueueAllocator::updateLeaseCallback(LeasePtr lease) {
431 MultiThreadingLock lock(mutex_);
432 updateLeaseCallbackInternal(lease);
433}
434
435void
436FreeLeaseQueueAllocator::updateLeaseCallbackInternal(LeasePtr lease) {
437 auto pool = getLeasePool(lease);
438 if (!pool) {
439 return;
440 }
441 auto pool_state = getPoolState(pool);
442 if (lease->stateExpiredReclaimed() || (lease->expired())) {
443 pool_state->addFreeLease(lease->addr_);
444 } else {
445 pool_state->deleteFreeLease(lease->addr_);
446 }
447}
448
449void
450FreeLeaseQueueAllocator::deleteLeaseCallback(LeasePtr lease) {
451 MultiThreadingLock lock(mutex_);
452 deleteLeaseCallbackInternal(lease);
453}
454
455void
456FreeLeaseQueueAllocator::deleteLeaseCallbackInternal(LeasePtr lease) {
457 auto pool = getLeasePool(lease);
458 if (!pool) {
459 return;
460 }
461 getPoolState(pool)->addFreeLease(lease->addr_);
462}
463
464uint64_t
465FreeLeaseQueueAllocator::getRandomNumber(uint64_t limit) {
466 // Take the short path if there is only one number to randomize from.
467 if (limit == 0) {
468 return (0);
469 }
470 std::uniform_int_distribution<uint64_t> dist(0, limit);
471 return (dist(generator_));
472}
473
474} // end of namespace isc::dhcp
475} // end of namespace isc
virtual void initAfterConfigureInternal()
Allocator-specific initialization function.
Definition allocator.h:202
Lease::Type pool_type_
Defines pool type allocation.
Definition allocator.h:256
std::mutex mutex_
The mutex to protect the allocated lease.
Definition allocator.h:265
Allocator(Lease::Type type, const WeakSubnetPtr &subnet)
Constructor.
Definition allocator.cc:17
WeakSubnetPtr subnet_
Weak pointer to the subnet owning the allocator.
Definition allocator.h:262
static bool isValidPrefixPool(Allocator::PrefixLenMatchType prefix_length_match, PoolPtr pool, uint8_t hint_prefix_length)
Check if the pool matches the selection criteria relative to the provided hint prefix length.
Definition allocator.cc:38
Container for storing client class names.
Definition classify.h:110
virtual double getOccupancyRate(const asiolink::IOAddress &addr, const ClientClasses &client_classes)
Returns the occupancy rate (v4 addresses).
FreeLeaseQueueAllocator(Lease::Type type, const WeakSubnetPtr &subnet)
Constructor.
static TrackingLeaseMgr & instance()
Return current lease manager.
virtual Lease6Collection getLeases6(Lease::Type type, const DUID &duid, uint32_t iaid) const =0
Returns existing IPv6 leases for a given DUID+IA combination.
virtual Lease4Collection getLeases4(SubnetID subnet_id) const =0
Returns all IPv4 leases for the particular subnet identifier.
static PoolFreeLeaseQueueAllocationStatePtr create(const PoolPtr &pool)
Factory function creating the state instance from a pool.
void stop()
Stops the stopwatch.
Definition stopwatch.cc:34
std::string logFormatLastDuration() const
Returns the last measured duration in the format directly usable in log messages.
Definition stopwatch.cc:74
#define isc_throw(type, stream)
A shortcut macro to insert known values into exception arguments.
#define LOG_INFO(LOGGER, MESSAGE)
Macro to conveniently test info output and log it.
Definition macros.h:20
isc::log::Logger dhcpsrv_logger("dhcpsrv")
DHCP server library Logger.
Definition dhcpsrv_log.h:56
boost::shared_ptr< PoolFreeLeaseQueueAllocationState > PoolFreeLeaseQueueAllocationStatePtr
Type of the pointer to the PoolFreeLeaseQueueAllocationState.
const isc::log::MessageID DHCPSRV_CFGMGR_FLQ_POPULATE_FREE_PREFIX_LEASES
boost::weak_ptr< Subnet > WeakSubnetPtr
Weak pointer to the Subnet.
Definition allocator.h:32
const isc::log::MessageID DHCPSRV_CFGMGR_FLQ_POPULATE_FREE_ADDRESS_LEASES_DONE
std::vector< Lease6Ptr > Lease6Collection
A collection of IPv6 leases.
Definition lease.h:693
const isc::log::MessageID DHCPSRV_CFGMGR_FLQ_POPULATE_FREE_PREFIX_LEASES_DONE
std::vector< PoolPtr > PoolCollection
a container for either IPv4 or IPv6 Pools
Definition pool.h:729
boost::shared_ptr< IdentifierBaseType > IdentifierBaseTypePtr
Shared pointer to a IdentifierType.
Definition duid.h:34
boost::shared_ptr< Pool > PoolPtr
a pointer to either IPv4 or IPv6 Pool
Definition pool.h:726
boost::shared_ptr< Lease > LeasePtr
Pointer to the lease object.
Definition lease.h:25
std::vector< Lease4Ptr > Lease4Collection
A collection of IPv4 leases.
Definition lease.h:520
const isc::log::MessageID DHCPSRV_CFGMGR_FLQ_POPULATE_FREE_ADDRESS_LEASES
boost::shared_ptr< Pool6 > Pool6Ptr
a pointer an IPv6 Pool
Definition pool.h:536
boost::multiprecision::checked_uint128_t uint128_t
Definition bigints.h:21
Defines the logger used by the top-level component of kea-lfc.
Type
Type of lease or pool.
Definition lease.h:46
@ TYPE_PD
the lease contains IPv6 prefix (for prefix delegation)
Definition lease.h:49
@ TYPE_V4
IPv4 lease.
Definition lease.h:50
@ TYPE_NA
the lease contains non-temporary IPv6 address
Definition lease.h:47
RAII lock object to protect the code in the same scope with a mutex.