Kea 3.1.1
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) const {
141 // Sanity.
142 if (!addr.isV4()) {
143 return (0.);
144 }
145 auto subnet = subnet_.lock();
146 uint128_t total(0);
147 uint128_t busy(0);
148 bool found(false);
149
150 for (auto const& pool : subnet->getPools(Lease::TYPE_V4)) {
151 if (!pool->clientSupported(client_classes)) {
152 continue;
153 }
154 uint128_t capacity = pool->getCapacity();
155 total += capacity;
156 if (total >= std::numeric_limits<uint64_t>::max()) {
157 return (0.);
158 }
159 auto pool_state = boost::dynamic_pointer_cast<PoolFreeLeaseQueueAllocationState>(pool->getAllocationState());
160 if (!pool_state) {
161 continue;
162 }
163 uint128_t free_cnt = pool_state->getFreeLeaseCount();
164 if (!found && pool->inRange(addr)) {
165 found = true;
166 if ((free_cnt > 0) && pool_state->isFreeLease(addr)) {
167 --free_cnt;
168 }
169 }
170 if (free_cnt > capacity) {
171 free_cnt = capacity;
172 }
173 busy += capacity - free_cnt;
174 }
175 if (!found) {
176 return (0.);
177 }
178 // Should not happen...
179 if (total == 0) {
180 return (0.);
181 }
182 return (static_cast<double>(busy) / static_cast<double>(total));
183}
184
185double
187 const uint8_t plen,
188 const ClientClasses& client_classes) const {
189 // Sanity.
190 if (!pref.isV6()) {
191 return (0.);
192 }
193 auto subnet = subnet_.lock();
194 uint128_t total(0);
195 uint128_t busy(0);
196 bool found(false);
197
198 for (auto const& pool : subnet->getPools(Lease::TYPE_PD)) {
199 if (!pool->clientSupported(client_classes)) {
200 continue;
201 }
202 auto const& pool6 = boost::dynamic_pointer_cast<Pool6>(pool);
203 if (!pool6 || (pool6->getLength() > plen)) {
204 continue;
205 }
206 uint128_t capacity = pool->getCapacity();
207 total += capacity;
208 if (total >= std::numeric_limits<uint64_t>::max()) {
209 return (0.);
210 }
211 auto pool_state = boost::dynamic_pointer_cast<PoolFreeLeaseQueueAllocationState>(pool->getAllocationState());
212 if (!pool_state) {
213 continue;
214 }
215 uint128_t free_cnt = pool_state->getFreeLeaseCount();
216 if (!found && pool->inRange(pref)) {
217 found = true;
218 if ((free_cnt > 0) && pool_state->isFreeLease(pref)) {
219 --free_cnt;
220 }
221 }
222 if (free_cnt > capacity) {
223 free_cnt = capacity;
224 }
225 busy += capacity - free_cnt;
226 }
227 if (!found) {
228 return (0.);
229 }
230 // Should not happen...
231 if (total == 0) {
232 return (0.);
233 }
234 return (static_cast<double>(busy) / static_cast<double>(total));
235}
236
237void
238FreeLeaseQueueAllocator::initAfterConfigureInternal() {
239 auto subnet = subnet_.lock();
240 auto const& pools = subnet->getPools(pool_type_);
241 if (pools.empty()) {
242 // If there are no pools there is nothing to do.
243 return;
244 }
245 Lease4Collection leases4;
246 Lease6Collection leases6;
247 switch (pool_type_) {
248 case Lease::TYPE_V4:
249 leases4 = LeaseMgrFactory::instance().getLeases4(subnet->getID());
250 populateFreeAddressLeases(leases4, pools);
251 break;
252 case Lease::TYPE_NA:
253 case Lease::TYPE_TA:
254 leases6 = LeaseMgrFactory::instance().getLeases6(subnet->getID());
255 populateFreeAddressLeases(leases6, pools);
256 break;
257 case Lease::TYPE_PD:
258 leases6 = LeaseMgrFactory::instance().getLeases6(subnet->getID());
259 populateFreePrefixDelegationLeases(leases6, pools);
260 break;
261 default:
262 ;
263 }
264 // Install the callbacks for lease add, update and delete in the interface manager.
265 // These callbacks will ensure that we have up-to-date free lease queue.
266 auto& lease_mgr = LeaseMgrFactory::instance();
267 lease_mgr.registerCallback(TrackingLeaseMgr::TRACK_ADD_LEASE, FLQ_OWNER, subnet->getID(), pool_type_,
268 std::bind(&FreeLeaseQueueAllocator::addLeaseCallback, this,
269 std::placeholders::_1));
270 lease_mgr.registerCallback(TrackingLeaseMgr::TRACK_UPDATE_LEASE, FLQ_OWNER, subnet->getID(), pool_type_,
271 std::bind(&FreeLeaseQueueAllocator::updateLeaseCallback, this,
272 std::placeholders::_1));
273 lease_mgr.registerCallback(TrackingLeaseMgr::TRACK_DELETE_LEASE, FLQ_OWNER, subnet->getID(), pool_type_,
274 std::bind(&FreeLeaseQueueAllocator::deleteLeaseCallback, this,
275 std::placeholders::_1));
276}
277
278template<typename LeaseCollectionType>
279void
280FreeLeaseQueueAllocator::populateFreeAddressLeases(const LeaseCollectionType& leases,
281 const PoolCollection& pools) {
282 auto subnet = subnet_.lock();
284 .arg(subnet->toText());
285
286 Stopwatch stopwatch;
287
288 // Let's iterate over the lease queue and index them with the
289 // unordered_set. Also, elminate the expired leases and those
290 // in the expired-reclaimed state.
291 unordered_set<IOAddress, IOAddress::Hash> leased_addresses;
292 for (auto const& lease : leases) {
293 if ((lease->getType() == pool_type_) && (!lease->expired()) && (!lease->stateExpiredReclaimed())) {
294 leased_addresses.insert(lease->addr_);
295 }
296 }
297 // For each pool, check if the address is in the leases list.
298 size_t free_lease_count = 0;
299 for (auto const& pool : pools) {
300 // Create the pool permutation so the resulting lease queue is no
301 // particular order.
302 IPRangePermutation perm(AddressRange(pool->getFirstAddress(), pool->getLastAddress()));
303 auto pool_state = getPoolState(pool);
304 auto done = false;
305 while (!done) {
306 auto address = perm.next(done);
307 if (address.isV4Zero() || address.isV6Zero()) {
308 continue;
309 }
310 if (leased_addresses.count(address) == 0) {
311 // No lease for this address, so add it to the free leases queue.
312 pool_state->addFreeLease(address);
313 }
314 }
315 free_lease_count += pool_state->getFreeLeaseCount();
316 }
317
318 stopwatch.stop();
319
321 .arg(free_lease_count)
322 .arg(subnet->toText())
323 .arg(stopwatch.logFormatLastDuration());
324}
325
326void
327FreeLeaseQueueAllocator::populateFreePrefixDelegationLeases(const Lease6Collection& leases,
328 const PoolCollection& pools) {
329 auto subnet = subnet_.lock();
331 .arg(subnet->toText());
332
333 Stopwatch stopwatch;
334
335 // Let's iterate over the lease queue and index them with the
336 // unordered_set. Also, elminate the expired leases and those
337 // in the expired-reclaimed state.
338 unordered_set<IOAddress, IOAddress::Hash> leased_prefixes;
339 for (auto const& lease : leases) {
340 if ((lease->getType() == Lease::TYPE_PD) && (!lease->expired()) && (!lease->stateExpiredReclaimed())) {
341 leased_prefixes.insert(lease->addr_);
342 }
343 }
344 // For each pool, check if the prefix is in the leases list.
345 size_t free_lease_count = 0;
346 for (auto const& pool : pools) {
347 auto pool6 = boost::dynamic_pointer_cast<Pool6>(pool);
348 if (!pool6) {
349 continue;
350 }
351 // Create the pool permutation so the resulting lease queue is no
352 // particular order.
353 IPRangePermutation perm(PrefixRange(pool->getFirstAddress(),
354 pool->getLastAddress(),
355 pool6->getLength()));
356 auto pool_state = getPoolState(pool);
357 auto done = false;
358 while (!done) {
359 auto prefix = perm.next(done);
360 if (prefix.isV4Zero() || prefix.isV6Zero()) {
361 continue;
362 }
363 if (leased_prefixes.count(prefix) == 0) {
364 // No lease for this prefix, so add it to the free leases queue.
365 pool_state->addFreeLease(prefix);
366 }
367 }
368 free_lease_count += pool_state->getFreeLeaseCount();
369 }
370
371 stopwatch.stop();
372
374 .arg(free_lease_count)
375 .arg(subnet->toText())
376 .arg(stopwatch.logFormatLastDuration());
377}
378
380FreeLeaseQueueAllocator::getPoolState(const PoolPtr& pool) const {
381 if (!pool->getAllocationState()) {
382 pool->setAllocationState(PoolFreeLeaseQueueAllocationState::create(pool));
383 }
384 return (boost::dynamic_pointer_cast<PoolFreeLeaseQueueAllocationState>(pool->getAllocationState()));
385}
386
388FreeLeaseQueueAllocator::getLeasePool(const LeasePtr& lease) const {
389 auto subnet = subnet_.lock();
390 if (!subnet) {
391 return (PoolPtr());
392 }
393 auto pool = subnet->getPool(pool_type_, lease->addr_, false);
394 return (pool);
395}
396
397void
398FreeLeaseQueueAllocator::addLeaseCallback(LeasePtr lease) {
399 MultiThreadingLock lock(mutex_);
400 addLeaseCallbackInternal(lease);
401}
402
403void
404FreeLeaseQueueAllocator::addLeaseCallbackInternal(LeasePtr lease) {
405 if (lease->expired()) {
406 return;
407 }
408 auto pool = getLeasePool(lease);
409 if (!pool) {
410 return;
411 }
412 getPoolState(pool)->deleteFreeLease(lease->addr_);
413}
414
415void
416FreeLeaseQueueAllocator::updateLeaseCallback(LeasePtr lease) {
417 MultiThreadingLock lock(mutex_);
418 updateLeaseCallbackInternal(lease);
419}
420
421void
422FreeLeaseQueueAllocator::updateLeaseCallbackInternal(LeasePtr lease) {
423 auto pool = getLeasePool(lease);
424 if (!pool) {
425 return;
426 }
427 auto pool_state = getPoolState(pool);
428 if (lease->stateExpiredReclaimed() || (lease->expired())) {
429 pool_state->addFreeLease(lease->addr_);
430 } else {
431 pool_state->deleteFreeLease(lease->addr_);
432 }
433}
434
435void
436FreeLeaseQueueAllocator::deleteLeaseCallback(LeasePtr lease) {
437 MultiThreadingLock lock(mutex_);
438 deleteLeaseCallbackInternal(lease);
439}
440
441void
442FreeLeaseQueueAllocator::deleteLeaseCallbackInternal(LeasePtr lease) {
443 auto pool = getLeasePool(lease);
444 if (!pool) {
445 return;
446 }
447 getPoolState(pool)->addFreeLease(lease->addr_);
448}
449
450uint64_t
451FreeLeaseQueueAllocator::getRandomNumber(uint64_t limit) {
452 // Take the short path if there is only one number to randomize from.
453 if (limit == 0) {
454 return (0);
455 }
456 std::uniform_int_distribution<uint64_t> dist(0, limit);
457 return (dist(generator_));
458}
459
460} // end of namespace isc::dhcp
461} // 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
FreeLeaseQueueAllocator(Lease::Type type, const WeakSubnetPtr &subnet)
Constructor.
virtual double getOccupancyRate(const asiolink::IOAddress &addr, const ClientClasses &client_classes) const
Returns the occupancy rate (v4 addresses).
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