Kea 3.1.5
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
253FreeLeaseQueueAllocator::initAfterConfigureInternal() {
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 case Lease::TYPE_TA:
269 leases6 = LeaseMgrFactory::instance().getLeases6(subnet->getID());
270 populateFreeAddressLeases(leases6, pools);
271 break;
272 case Lease::TYPE_PD:
273 leases6 = LeaseMgrFactory::instance().getLeases6(subnet->getID());
274 populateFreePrefixDelegationLeases(leases6, pools);
275 break;
276 default:
277 ;
278 }
279 // Install the callbacks for lease add, update and delete in the interface manager.
280 // These callbacks will ensure that we have up-to-date free lease queue.
281 auto& lease_mgr = LeaseMgrFactory::instance();
282 lease_mgr.registerCallback(TrackingLeaseMgr::TRACK_ADD_LEASE, FLQ_OWNER, subnet->getID(), pool_type_,
283 std::bind(&FreeLeaseQueueAllocator::addLeaseCallback, this,
284 std::placeholders::_1));
285 lease_mgr.registerCallback(TrackingLeaseMgr::TRACK_UPDATE_LEASE, FLQ_OWNER, subnet->getID(), pool_type_,
286 std::bind(&FreeLeaseQueueAllocator::updateLeaseCallback, this,
287 std::placeholders::_1));
288 lease_mgr.registerCallback(TrackingLeaseMgr::TRACK_DELETE_LEASE, FLQ_OWNER, subnet->getID(), pool_type_,
289 std::bind(&FreeLeaseQueueAllocator::deleteLeaseCallback, this,
290 std::placeholders::_1));
291}
292
293template<typename LeaseCollectionType>
294void
295FreeLeaseQueueAllocator::populateFreeAddressLeases(const LeaseCollectionType& leases,
296 const PoolCollection& pools) {
297 auto subnet = subnet_.lock();
299 .arg(subnet->toText());
300
301 Stopwatch stopwatch;
302
303 // Let's iterate over the lease queue and index them with the
304 // unordered_set. Also, eliminate the expired leases and those
305 // in the expired-reclaimed state.
306 unordered_set<IOAddress, IOAddress::Hash> leased_addresses;
307 for (auto const& lease : leases) {
308 if ((lease->getType() == pool_type_) && (!lease->expired()) && (!lease->stateExpiredReclaimed())) {
309 leased_addresses.insert(lease->addr_);
310 }
311 }
312 // For each pool, check if the address is in the leases list.
313 size_t free_lease_count = 0;
314 for (auto const& pool : pools) {
315 // Create the pool permutation so the resulting lease queue is no
316 // particular order.
317 IPRangePermutation perm(AddressRange(pool->getFirstAddress(), pool->getLastAddress()));
318 auto pool_state = getPoolState(pool);
319 auto done = false;
320 while (!done) {
321 auto address = perm.next(done);
322 if (address.isV4Zero() || address.isV6Zero()) {
323 continue;
324 }
325 if (leased_addresses.count(address) == 0) {
326 // No lease for this address, so add it to the free leases queue.
327 pool_state->addFreeLease(address);
328 }
329 }
330 free_lease_count += pool_state->getFreeLeaseCount();
331 }
332
333 stopwatch.stop();
334
336 .arg(free_lease_count)
337 .arg(subnet->toText())
338 .arg(stopwatch.logFormatLastDuration());
339}
340
341void
342FreeLeaseQueueAllocator::populateFreePrefixDelegationLeases(const Lease6Collection& leases,
343 const PoolCollection& pools) {
344 auto subnet = subnet_.lock();
346 .arg(subnet->toText());
347
348 Stopwatch stopwatch;
349
350 // Let's iterate over the lease queue and index them with the
351 // unordered_set. Also, eliminate the expired leases and those
352 // in the expired-reclaimed state.
353 unordered_set<IOAddress, IOAddress::Hash> leased_prefixes;
354 for (auto const& lease : leases) {
355 if ((lease->getType() == Lease::TYPE_PD) && (!lease->expired()) && (!lease->stateExpiredReclaimed())) {
356 leased_prefixes.insert(lease->addr_);
357 }
358 }
359 // For each pool, check if the prefix is in the leases list.
360 size_t free_lease_count = 0;
361 for (auto const& pool : pools) {
362 auto pool6 = boost::dynamic_pointer_cast<Pool6>(pool);
363 if (!pool6) {
364 continue;
365 }
366 // Create the pool permutation so the resulting lease queue is no
367 // particular order.
368 IPRangePermutation perm(PrefixRange(pool->getFirstAddress(),
369 pool->getLastAddress(),
370 pool6->getLength()));
371 auto pool_state = getPoolState(pool);
372 auto done = false;
373 while (!done) {
374 auto prefix = perm.next(done);
375 if (prefix.isV4Zero() || prefix.isV6Zero()) {
376 continue;
377 }
378 if (leased_prefixes.count(prefix) == 0) {
379 // No lease for this prefix, so add it to the free leases queue.
380 pool_state->addFreeLease(prefix);
381 }
382 }
383 free_lease_count += pool_state->getFreeLeaseCount();
384 }
385
386 stopwatch.stop();
387
389 .arg(free_lease_count)
390 .arg(subnet->toText())
391 .arg(stopwatch.logFormatLastDuration());
392}
393
395FreeLeaseQueueAllocator::getPoolState(const PoolPtr& pool) const {
396 if (!pool->getAllocationState()) {
397 pool->setAllocationState(PoolFreeLeaseQueueAllocationState::create(pool));
398 }
399 return (boost::dynamic_pointer_cast<PoolFreeLeaseQueueAllocationState>(pool->getAllocationState()));
400}
401
403FreeLeaseQueueAllocator::getLeasePool(const LeasePtr& lease) const {
404 auto subnet = subnet_.lock();
405 if (!subnet) {
406 return (PoolPtr());
407 }
408 auto pool = subnet->getPool(pool_type_, lease->addr_, false);
409 return (pool);
410}
411
412void
413FreeLeaseQueueAllocator::addLeaseCallback(LeasePtr lease) {
414 MultiThreadingLock lock(mutex_);
415 addLeaseCallbackInternal(lease);
416}
417
418void
419FreeLeaseQueueAllocator::addLeaseCallbackInternal(LeasePtr lease) {
420 if (lease->expired()) {
421 return;
422 }
423 auto pool = getLeasePool(lease);
424 if (!pool) {
425 return;
426 }
427 getPoolState(pool)->deleteFreeLease(lease->addr_);
428}
429
430void
431FreeLeaseQueueAllocator::updateLeaseCallback(LeasePtr lease) {
432 MultiThreadingLock lock(mutex_);
433 updateLeaseCallbackInternal(lease);
434}
435
436void
437FreeLeaseQueueAllocator::updateLeaseCallbackInternal(LeasePtr lease) {
438 auto pool = getLeasePool(lease);
439 if (!pool) {
440 return;
441 }
442 auto pool_state = getPoolState(pool);
443 if (lease->stateExpiredReclaimed() || (lease->expired())) {
444 pool_state->addFreeLease(lease->addr_);
445 } else {
446 pool_state->deleteFreeLease(lease->addr_);
447 }
448}
449
450void
451FreeLeaseQueueAllocator::deleteLeaseCallback(LeasePtr lease) {
452 MultiThreadingLock lock(mutex_);
453 deleteLeaseCallbackInternal(lease);
454}
455
456void
457FreeLeaseQueueAllocator::deleteLeaseCallbackInternal(LeasePtr lease) {
458 auto pool = getLeasePool(lease);
459 if (!pool) {
460 return;
461 }
462 getPoolState(pool)->addFreeLease(lease->addr_);
463}
464
465uint64_t
466FreeLeaseQueueAllocator::getRandomNumber(uint64_t limit) {
467 // Take the short path if there is only one number to randomize from.
468 if (limit == 0) {
469 return (0);
470 }
471 std::uniform_int_distribution<uint64_t> dist(0, limit);
472 return (dist(generator_));
473}
474
475} // end of namespace isc::dhcp
476} // end of namespace isc
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_TA
the lease contains temporary IPv6 address
Definition lease.h:48
@ 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.