summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorMarcin Siodelski <marcin@isc.org>2022-10-13 08:03:20 +0200
committerMarcin Siodelski <marcin@isc.org>2022-11-21 08:52:02 +0100
commitc599f02fe0f0c0bb92c58f99d97c0e610f794845 (patch)
tree2096849c877c46581eacf959de60163604b63a91 /src
parent[#2606] update documentation (diff)
downloadkea-c599f02fe0f0c0bb92c58f99d97c0e610f794845.tar.xz
kea-c599f02fe0f0c0bb92c58f99d97c0e610f794845.zip
[#2348] Moved allocators outside the engine
Diffstat (limited to 'src')
-rw-r--r--src/lib/dhcpsrv/Makefile.am4
-rw-r--r--src/lib/dhcpsrv/alloc_engine.cc262
-rw-r--r--src/lib/dhcpsrv/alloc_engine.h216
-rw-r--r--src/lib/dhcpsrv/allocator.h115
-rw-r--r--src/lib/dhcpsrv/iterative_allocator.cc216
-rw-r--r--src/lib/dhcpsrv/iterative_allocator.h79
-rw-r--r--src/lib/dhcpsrv/tests/Makefile.am1
-rw-r--r--src/lib/dhcpsrv/tests/alloc_engine4_unittest.cc98
-rw-r--r--src/lib/dhcpsrv/tests/alloc_engine6_unittest.cc29
-rw-r--r--src/lib/dhcpsrv/tests/alloc_engine_utils.h161
-rw-r--r--src/lib/dhcpsrv/tests/iterative_allocator_unittest.cc666
11 files changed, 1182 insertions, 665 deletions
diff --git a/src/lib/dhcpsrv/Makefile.am b/src/lib/dhcpsrv/Makefile.am
index 21e6dfb548..da0622c726 100644
--- a/src/lib/dhcpsrv/Makefile.am
+++ b/src/lib/dhcpsrv/Makefile.am
@@ -64,6 +64,7 @@ libkea_dhcpsrv_la_SOURCES =
libkea_dhcpsrv_la_SOURCES += alloc_engine.cc alloc_engine.h
libkea_dhcpsrv_la_SOURCES += alloc_engine_log.cc alloc_engine_log.h
libkea_dhcpsrv_la_SOURCES += alloc_engine_messages.h alloc_engine_messages.cc
+libkea_dhcpsrv_la_SOURCES += allocator.h
libkea_dhcpsrv_la_SOURCES += base_host_data_source.h
libkea_dhcpsrv_la_SOURCES += cache_host_data_source.h
libkea_dhcpsrv_la_SOURCES += callout_handle_store.h
@@ -114,6 +115,7 @@ libkea_dhcpsrv_la_SOURCES += hosts_log.cc hosts_log.h
libkea_dhcpsrv_la_SOURCES += hosts_messages.h hosts_messages.cc
libkea_dhcpsrv_la_SOURCES += ip_range.h ip_range.cc
libkea_dhcpsrv_la_SOURCES += ip_range_permutation.h ip_range_permutation.cc
+libkea_dhcpsrv_la_SOURCES += iterative_allocator.cc iterative_allocator.h
libkea_dhcpsrv_la_SOURCES += key_from_key.h
libkea_dhcpsrv_la_SOURCES += lease.cc lease.h
libkea_dhcpsrv_la_SOURCES += lease_file_loader.h
@@ -292,6 +294,7 @@ libkea_dhcpsrv_include_HEADERS = \
alloc_engine.h \
alloc_engine_log.h \
alloc_engine_messages.h \
+ allocator.h \
base_host_data_source.h \
cache_host_data_source.h \
callout_handle_store.h \
@@ -341,6 +344,7 @@ libkea_dhcpsrv_include_HEADERS = \
hosts_log.h \
ip_range.h \
ip_range_permutation.h \
+ iterative_allocator.h \
key_from_key.h \
lease.h \
lease_file_loader.h \
diff --git a/src/lib/dhcpsrv/alloc_engine.cc b/src/lib/dhcpsrv/alloc_engine.cc
index 1da842187d..327345e38e 100644
--- a/src/lib/dhcpsrv/alloc_engine.cc
+++ b/src/lib/dhcpsrv/alloc_engine.cc
@@ -17,6 +17,7 @@
#include <dhcpsrv/dhcpsrv_log.h>
#include <dhcpsrv/host_mgr.h>
#include <dhcpsrv/host.h>
+#include <dhcpsrv/iterative_allocator.h>
#include <dhcpsrv/lease_mgr_factory.h>
#include <dhcpsrv/ncr_generator.h>
#include <dhcpsrv/network.h>
@@ -34,7 +35,6 @@
#include <boost/make_shared.hpp>
#include <algorithm>
-#include <cstring>
#include <limits>
#include <sstream>
#include <stdint.h>
@@ -90,230 +90,7 @@ AllocEngineHooks Hooks;
namespace isc {
namespace dhcp {
-AllocEngine::IterativeAllocator::IterativeAllocator(Lease::Type lease_type)
- : Allocator(lease_type) {
-}
-
-isc::asiolink::IOAddress
-AllocEngine::IterativeAllocator::increasePrefix(const isc::asiolink::IOAddress& prefix,
- const uint8_t prefix_len) {
- if (!prefix.isV6()) {
- isc_throw(BadValue, "Prefix operations are for IPv6 only (attempted to "
- "increase prefix " << prefix << ")");
- }
-
- // Get a buffer holding an address.
- const std::vector<uint8_t>& vec = prefix.toBytes();
-
- if (prefix_len < 1 || prefix_len > 128) {
- isc_throw(BadValue, "Cannot increase prefix: invalid prefix length: "
- << prefix_len);
- }
-
- uint8_t n_bytes = (prefix_len - 1)/8;
- uint8_t n_bits = 8 - (prefix_len - n_bytes*8);
- uint8_t mask = 1 << n_bits;
-
- // Explanation: n_bytes specifies number of full bytes that are in-prefix.
- // They can also be used as an offset for the first byte that is not in
- // prefix. n_bits specifies number of bits on the last byte that is
- // (often partially) in prefix. For example for a /125 prefix, the values
- // are 15 and 3, respectively. Mask is a bitmask that has the least
- // significant bit from the prefix set.
-
- uint8_t packed[V6ADDRESS_LEN];
-
- // Copy the address. It must be V6, but we already checked that.
- std::memcpy(packed, &vec[0], V6ADDRESS_LEN);
-
- // Can we safely increase only the last byte in prefix without overflow?
- if (packed[n_bytes] + uint16_t(mask) < 256u) {
- packed[n_bytes] += mask;
- return (IOAddress::fromBytes(AF_INET6, packed));
- }
-
- // Overflow (done on uint8_t, but the sum is greater than 255)
- packed[n_bytes] += mask;
-
- // Deal with the overflow. Start increasing the least significant byte
- for (int i = n_bytes - 1; i >= 0; --i) {
- ++packed[i];
- // If we haven't overflowed (0xff->0x0) the next byte, then we are done
- if (packed[i] != 0) {
- break;
- }
- }
-
- return (IOAddress::fromBytes(AF_INET6, packed));
-}
-
-isc::asiolink::IOAddress
-AllocEngine::IterativeAllocator::increaseAddress(const isc::asiolink::IOAddress& address,
- bool prefix,
- const uint8_t prefix_len) {
- if (!prefix) {
- return (IOAddress::increase(address));
- } else {
- return (increasePrefix(address, prefix_len));
- }
-}
-
-isc::asiolink::IOAddress
-AllocEngine::IterativeAllocator::pickAddressInternal(const SubnetPtr& subnet,
- const ClientClasses& client_classes,
- const DuidPtr&,
- const IOAddress&) {
- // Is this prefix allocation?
- bool prefix = pool_type_ == Lease::TYPE_PD;
- uint8_t prefix_len = 0;
-
- // Let's get the last allocated address. It is usually set correctly,
- // but there are times when it won't be (like after removing a pool or
- // perhaps restarting the server).
- IOAddress last = subnet->getLastAllocated(pool_type_);
- bool valid = true;
- bool retrying = false;
-
- const PoolCollection& pools = subnet->getPools(pool_type_);
-
- if (pools.empty()) {
- isc_throw(AllocFailed, "No pools defined in selected subnet");
- }
-
- // first we need to find a pool the last address belongs to.
- PoolCollection::const_iterator it;
- PoolCollection::const_iterator first = pools.end();
- PoolPtr first_pool;
- for (it = pools.begin(); it != pools.end(); ++it) {
- if (!(*it)->clientSupported(client_classes)) {
- continue;
- }
- if (first == pools.end()) {
- first = it;
- }
- if ((*it)->inRange(last)) {
- break;
- }
- }
-
- // Caller checked this cannot happen
- if (first == pools.end()) {
- isc_throw(AllocFailed, "No allowed pools defined in selected subnet");
- }
-
- // last one was bogus for one of several reasons:
- // - we just booted up and that's the first address we're allocating
- // - a subnet was removed or other reconfiguration just completed
- // - perhaps allocation algorithm was changed
- // - last pool does not allow this client
- if (it == pools.end()) {
- it = first;
- }
-
- for (;;) {
- // Trying next pool
- if (retrying) {
- for (; it != pools.end(); ++it) {
- if ((*it)->clientSupported(client_classes)) {
- break;
- }
- }
- if (it == pools.end()) {
- // Really out of luck today. That was the last pool.
- break;
- }
- }
-
- last = (*it)->getLastAllocated();
- valid = (*it)->isLastAllocatedValid();
- if (!valid && (last == (*it)->getFirstAddress())) {
- // Pool was (re)initialized
- (*it)->setLastAllocated(last);
- subnet->setLastAllocated(pool_type_, last);
- return (last);
- }
- // still can be bogus
- if (valid && !(*it)->inRange(last)) {
- valid = false;
- (*it)->resetLastAllocated();
- (*it)->setLastAllocated((*it)->getFirstAddress());
- }
-
- if (valid) {
- // Ok, we have a pool that the last address belonged to, let's use it.
- if (prefix) {
- Pool6Ptr pool6 = boost::dynamic_pointer_cast<Pool6>(*it);
-
- if (!pool6) {
- // Something is gravely wrong here
- isc_throw(Unexpected, "Wrong type of pool: "
- << (*it)->toText()
- << " is not Pool6");
- }
- // Get the prefix length
- prefix_len = pool6->getLength();
- }
-
- IOAddress next = increaseAddress(last, prefix, prefix_len);
- if ((*it)->inRange(next)) {
- // the next one is in the pool as well, so we haven't hit
- // pool boundary yet
- (*it)->setLastAllocated(next);
- subnet->setLastAllocated(pool_type_, next);
- return (next);
- }
-
- valid = false;
- (*it)->resetLastAllocated();
- }
- // We hit pool boundary, let's try to jump to the next pool and try again
- ++it;
- retrying = true;
- }
-
- // Let's rewind to the beginning.
- for (it = first; it != pools.end(); ++it) {
- if ((*it)->clientSupported(client_classes)) {
- (*it)->setLastAllocated((*it)->getFirstAddress());
- (*it)->resetLastAllocated();
- }
- }
-
- // ok to access first element directly. We checked that pools is non-empty
- last = (*first)->getLastAllocated();
- (*first)->setLastAllocated(last);
- subnet->setLastAllocated(pool_type_, last);
- return (last);
-}
-
-AllocEngine::HashedAllocator::HashedAllocator(Lease::Type lease_type)
- : Allocator(lease_type) {
- isc_throw(NotImplemented, "Hashed allocator is not implemented");
-}
-
-isc::asiolink::IOAddress
-AllocEngine::HashedAllocator::pickAddressInternal(const SubnetPtr&,
- const ClientClasses&,
- const DuidPtr&,
- const IOAddress&) {
- isc_throw(NotImplemented, "Hashed allocator is not implemented");
-}
-
-AllocEngine::RandomAllocator::RandomAllocator(Lease::Type lease_type)
- : Allocator(lease_type) {
- isc_throw(NotImplemented, "Random allocator is not implemented");
-}
-
-isc::asiolink::IOAddress
-AllocEngine::RandomAllocator::pickAddressInternal(const SubnetPtr&,
- const ClientClasses&,
- const DuidPtr&,
- const IOAddress&) {
- isc_throw(NotImplemented, "Random allocator is not implemented");
-}
-
-AllocEngine::AllocEngine(AllocType engine_type, uint64_t attempts,
- bool ipv6)
+AllocEngine::AllocEngine(AllocType, uint64_t attempts, bool ipv6)
: attempts_(attempts), incomplete_v4_reclamations_(0),
incomplete_v6_reclamations_(0) {
@@ -321,39 +98,13 @@ AllocEngine::AllocEngine(AllocType engine_type, uint64_t attempts,
Lease::Type basic_type = ipv6 ? Lease::TYPE_NA : Lease::TYPE_V4;
// Initialize normal address allocators
- switch (engine_type) {
- case ALLOC_ITERATIVE:
- allocators_[basic_type] = AllocatorPtr(new IterativeAllocator(basic_type));
- break;
- case ALLOC_HASHED:
- allocators_[basic_type] = AllocatorPtr(new HashedAllocator(basic_type));
- break;
- case ALLOC_RANDOM:
- allocators_[basic_type] = AllocatorPtr(new RandomAllocator(basic_type));
- break;
- default:
- isc_throw(BadValue, "Invalid/unsupported allocation algorithm");
- }
+ allocators_[basic_type] = AllocatorPtr(new IterativeAllocator(basic_type));
// If this is IPv6 allocation engine, initialize also temporary addrs
// and prefixes
if (ipv6) {
- switch (engine_type) {
- case ALLOC_ITERATIVE:
- allocators_[Lease::TYPE_TA] = AllocatorPtr(new IterativeAllocator(Lease::TYPE_TA));
- allocators_[Lease::TYPE_PD] = AllocatorPtr(new IterativeAllocator(Lease::TYPE_PD));
- break;
- case ALLOC_HASHED:
- allocators_[Lease::TYPE_TA] = AllocatorPtr(new HashedAllocator(Lease::TYPE_TA));
- allocators_[Lease::TYPE_PD] = AllocatorPtr(new HashedAllocator(Lease::TYPE_PD));
- break;
- case ALLOC_RANDOM:
- allocators_[Lease::TYPE_TA] = AllocatorPtr(new RandomAllocator(Lease::TYPE_TA));
- allocators_[Lease::TYPE_PD] = AllocatorPtr(new RandomAllocator(Lease::TYPE_PD));
- break;
- default:
- isc_throw(BadValue, "Invalid/unsupported allocation algorithm");
- }
+ allocators_[Lease::TYPE_TA] = AllocatorPtr(new IterativeAllocator(Lease::TYPE_TA));
+ allocators_[Lease::TYPE_PD] = AllocatorPtr(new IterativeAllocator(Lease::TYPE_PD));
}
// Register hook points
@@ -361,7 +112,8 @@ AllocEngine::AllocEngine(AllocType engine_type, uint64_t attempts,
hook_index_lease6_select_ = Hooks.hook_index_lease6_select_;
}
-AllocEngine::AllocatorPtr AllocEngine::getAllocator(Lease::Type type) {
+AllocatorPtr
+AllocEngine::getAllocator(Lease::Type type) {
std::map<Lease::Type, AllocatorPtr>::const_iterator alloc = allocators_.find(type);
if (alloc == allocators_.end()) {
diff --git a/src/lib/dhcpsrv/alloc_engine.h b/src/lib/dhcpsrv/alloc_engine.h
index 07257145a1..c1ac2c53e0 100644
--- a/src/lib/dhcpsrv/alloc_engine.h
+++ b/src/lib/dhcpsrv/alloc_engine.h
@@ -16,6 +16,7 @@
#include <dhcp/option6_ia.h>
#include <dhcp/option6_iaaddr.h>
#include <dhcp/option6_iaprefix.h>
+#include <dhcpsrv/allocator.h>
#include <dhcpsrv/d2_client_cfg.h>
#include <dhcpsrv/host.h>
#include <dhcpsrv/subnet.h>
@@ -38,20 +39,6 @@
namespace isc {
namespace dhcp {
-/// An exception that is thrown when allocation module fails (e.g. due to
-/// lack of available addresses)
-class AllocFailed : public isc::Exception {
-public:
-
- /// @brief Constructor
- ///
- /// @param file name of the file, where exception occurred
- /// @param line line of the file, where exception occurred
- /// @param what text description of the issue that caused exception
- AllocFailed(const char* file, size_t line, const char* what)
- : isc::Exception(file, line, what) {}
-};
-
/// @brief DHCPv4 and DHCPv6 allocation engine
///
/// This class represents a DHCP allocation engine. It is responsible
@@ -61,207 +48,6 @@ public:
/// @todo: Does not handle out of leases well
/// @todo: Does not handle out of allocation attempts well
class AllocEngine : public boost::noncopyable {
-protected:
-
- /// @brief Base class for all address/prefix allocation algorithms
- ///
- /// This is an abstract class that should not be used directly, but rather
- /// specialized implementations should be used instead.
- class Allocator {
- public:
-
- /// @brief Picks one address out of available pools in a given subnet
- ///
- /// This method returns one address from the available pools in the
- /// specified subnet. It should not check if the address is used or
- /// reserved - AllocEngine will check that and will call pickAddress
- /// again if necessary. The number of times this method is called will
- /// increase as the number of available leases will decrease.
- ///
- /// This method can also be used to pick a prefix. We should not rename
- /// it to pickLease(), because at this early stage there is no concept
- /// of a lease yet. Here it is a matter of selecting one address or
- /// prefix from the defined pool, without going into details who it is
- /// for or who uses it. I thought that pickAddress() is less confusing
- /// than pickResource(), because nobody would immediately know what the
- /// resource means in this context.
- ///
- /// Pools which are not allowed for client classes are skipped.
- ///
- /// @param subnet next address will be returned from pool of that subnet
- /// @param client_classes list of classes client belongs to
- /// @param duid Client's DUID
- /// @param hint Client's hint
- ///
- /// @return the next address
- virtual isc::asiolink::IOAddress
- pickAddress(const SubnetPtr& subnet,
- const ClientClasses& client_classes,
- const DuidPtr& duid,
- const isc::asiolink::IOAddress& hint) {
- if (isc::util::MultiThreadingMgr::instance().getMode()) {
- std::lock_guard<std::mutex> lock(mutex_);
- return pickAddressInternal(subnet, client_classes, duid, hint);
- } else {
- return pickAddressInternal(subnet, client_classes, duid, hint);
- }
- }
-
- /// @brief Default constructor
- ///
- /// Specifies which type of leases this allocator will assign
- /// @param pool_type specifies pool type (addresses, temp. addr or prefixes)
- Allocator(Lease::Type pool_type) : pool_type_(pool_type) {
- }
-
- /// @brief Virtual destructor
- virtual ~Allocator() {
- }
-
- private:
- virtual isc::asiolink::IOAddress
- pickAddressInternal(const SubnetPtr& subnet,
- const ClientClasses& client_classes,
- const DuidPtr& duid,
- const isc::asiolink::IOAddress& hint) = 0;
-
- protected:
-
- /// @brief Defines pool type allocation
- Lease::Type pool_type_;
-
- private:
-
- /// @brief The mutex to protect the allocated lease
- std::mutex mutex_;
- };
-
- /// defines a pointer to allocator
- typedef boost::shared_ptr<Allocator> AllocatorPtr;
-
- /// @brief Address/prefix allocator that iterates over all addresses
- ///
- /// This class implements an iterative algorithm that returns all addresses in
- /// a pool iteratively, one after another. Once the last address is reached,
- /// it starts allocating from the beginning of the first pool (i.e. it loops
- /// over).
- class IterativeAllocator : public Allocator {
- public:
-
- /// @brief Default constructor
- ///
- /// Does not do anything
- /// @param type - specifies allocation type
- IterativeAllocator(Lease::Type type);
-
- private:
-
- /// @brief Returns the next address from pools in a subnet
- ///
- /// @param subnet next address will be returned from pool of that subnet
- /// @param client_classes list of classes client belongs to
- /// @param duid Client's DUID (ignored)
- /// @param hint Client's hint (ignored)
- ///
- /// @return the next address
- virtual isc::asiolink::IOAddress
- pickAddressInternal(const SubnetPtr& subnet,
- const ClientClasses& client_classes,
- const DuidPtr& duid,
- const isc::asiolink::IOAddress& hint);
-
- protected:
-
- /// @brief Returns the next prefix
- ///
- /// This method works for IPv6 addresses only. It increases the
- /// specified prefix by a given prefix_len. For example, 2001:db8::
- /// increased by prefix length /32 will become 2001:db9::. This method
- /// is used to iterate over IPv6 prefix pools
- ///
- /// @param prefix prefix to be increased
- /// @param prefix_len length of the prefix to be increased
- ///
- /// @return result prefix
- static isc::asiolink::IOAddress
- increasePrefix(const isc::asiolink::IOAddress& prefix,
- const uint8_t prefix_len);
-
- /// @brief Returns the next address or prefix
- ///
- /// This method works for IPv4 addresses, IPv6 addresses and
- /// IPv6 prefixes.
- ///
- /// @param address address or prefix to be increased
- /// @param prefix true when the previous argument is a prefix
- /// @param prefix_len length of the prefix
- ///
- /// @return result address or prefix
- static isc::asiolink::IOAddress
- increaseAddress(const isc::asiolink::IOAddress& address,
- bool prefix, const uint8_t prefix_len);
- };
-
- /// @brief Address/prefix allocator that gets an address based on a hash
- ///
- /// @todo: This is a skeleton class for now and is missing an implementation.
- class HashedAllocator : public Allocator {
- public:
-
- /// @brief Default constructor (does nothing)
- ///
- /// @param type - specifies allocation type
- HashedAllocator(Lease::Type type);
-
- private:
-
- /// @brief Returns an address based on hash calculated from client's DUID.
- ///
- /// @todo: Implement this method
- ///
- /// @param subnet an address will be picked from pool of that subnet
- /// @param client_classes list of classes client belongs to
- /// @param duid Client's DUID
- /// @param hint a hint (last address that was picked)
- ///
- /// @return selected address
- virtual isc::asiolink::IOAddress
- pickAddressInternal(const SubnetPtr& subnet,
- const ClientClasses& client_classes,
- const DuidPtr& duid,
- const isc::asiolink::IOAddress& hint);
- };
-
- /// @brief Random allocator that picks address randomly
- ///
- /// @todo: This is a skeleton class for now and is missing an implementation.
- class RandomAllocator : public Allocator {
- public:
-
- /// @brief Default constructor (does nothing)
- ///
- /// @param type - specifies allocation type
- RandomAllocator(Lease::Type type);
-
- private:
-
- /// @brief Returns a random address from pool of specified subnet
- ///
- /// @todo: Implement this method
- ///
- /// @param subnet an address will be picked from pool of that subnet
- /// @param client_classes list of classes client belongs to
- /// @param duid Client's DUID (ignored)
- /// @param hint the last address that was picked (ignored)
- ///
- /// @return a random address from the pool
- virtual isc::asiolink::IOAddress
- pickAddressInternal(const SubnetPtr& subnet,
- const ClientClasses& client_classes,
- const DuidPtr& duid,
- const isc::asiolink::IOAddress& hint);
- };
-
public:
/// @brief Specifies allocation type
diff --git a/src/lib/dhcpsrv/allocator.h b/src/lib/dhcpsrv/allocator.h
new file mode 100644
index 0000000000..c073a849db
--- /dev/null
+++ b/src/lib/dhcpsrv/allocator.h
@@ -0,0 +1,115 @@
+// Copyright (C) 2022 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/.
+
+#ifndef ALLOCATOR_H
+#define ALLOCATOR_H
+
+#include <asiolink/io_address.h>
+#include <dhcp/classify.h>
+#include <dhcp/duid.h>
+#include <dhcpsrv/subnet.h>
+#include <exceptions/exceptions.h>
+#include <util/multi_threading_mgr.h>
+#include <boost/shared_ptr.hpp>
+#include <mutex>
+
+namespace isc {
+namespace dhcp {
+
+/// An exception that is thrown when allocation module fails (e.g. due to
+/// lack of available addresses)
+class AllocFailed : public Exception {
+public:
+
+ /// @brief Constructor
+ ///
+ /// @param file name of the file, where exception occurred
+ /// @param line line of the file, where exception occurred
+ /// @param what text description of the issue that caused exception
+ AllocFailed(const char* file, size_t line, const char* what)
+ : Exception(file, line, what) {}
+};
+
+/// @brief Base class for all address/prefix allocation algorithms.
+///
+/// This is an abstract class that should not be used directly, but rather
+/// specialized implementations should be used instead.
+class Allocator {
+public:
+
+ /// @brief Picks a address or a delegated prefix
+ ///
+ /// This method returns one address from the available pools in the
+ /// specified subnet. It should not check if the address is used or
+ /// reserved - AllocEngine will check that and will call pickAddress
+ /// again if necessary. The number of times this method is called will
+ /// increase as the number of available leases will decrease.
+ ///
+ /// This method can also be used to pick a prefix. We should not rename
+ /// it to pickLease(), because at this early stage there is no concept
+ /// of a lease yet. Here it is a matter of selecting one address or
+ /// prefix from the defined pool, without going into details who it is
+ /// for or who uses it. I thought that pickAddress() is less confusing
+ /// than pickResource(), because nobody would immediately know what the
+ /// resource means in this context.
+ ///
+ /// Pools which are not allowed for client classes are skipped.
+ ///
+ /// @param subnet next address will be returned from pool of that subnet
+ /// @param client_classes list of classes client belongs to
+ /// @param duid Client's DUID
+ /// @param hint Client's hint
+ ///
+ /// @return the next address.
+ virtual isc::asiolink::IOAddress
+ pickAddress(const SubnetPtr& subnet,
+ const ClientClasses& client_classes,
+ const DuidPtr& duid,
+ const asiolink::IOAddress& hint) {
+ if (util::MultiThreadingMgr::instance().getMode()) {
+ std::lock_guard<std::mutex> lock(mutex_);
+ return pickAddressInternal(subnet, client_classes, duid, hint);
+ } else {
+ return pickAddressInternal(subnet, client_classes, duid, hint);
+ }
+ }
+
+ /// @brief Default constructor
+ ///
+ /// Specifies which type of leases this allocator will assign
+ /// @param pool_type specifies pool type (addresses, temp. addr or prefixes)
+ Allocator(Lease::Type pool_type) : pool_type_(pool_type) {
+ }
+
+ /// @brief Virtual destructor
+ virtual ~Allocator() {
+ }
+
+ private:
+ virtual isc::asiolink::IOAddress
+ pickAddressInternal(const SubnetPtr& subnet,
+ const ClientClasses& client_classes,
+ const DuidPtr& duid,
+ const isc::asiolink::IOAddress& hint) = 0;
+
+ protected:
+
+ /// @brief Defines pool type allocation
+ Lease::Type pool_type_;
+
+ private:
+
+ /// @brief The mutex to protect the allocated lease
+ std::mutex mutex_;
+};
+
+/// defines a pointer to allocator
+typedef boost::shared_ptr<Allocator> AllocatorPtr;
+
+} // end of namespace isc::dhcp
+} // end of namespace isc
+
+#endif // ALLOCATOR_H \ No newline at end of file
diff --git a/src/lib/dhcpsrv/iterative_allocator.cc b/src/lib/dhcpsrv/iterative_allocator.cc
new file mode 100644
index 0000000000..52cf8cbc4d
--- /dev/null
+++ b/src/lib/dhcpsrv/iterative_allocator.cc
@@ -0,0 +1,216 @@
+// Copyright (C) 2022 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 <dhcpsrv/iterative_allocator.h>
+#include <exceptions/exceptions.h>
+#include <cstring>
+
+using namespace isc::asiolink;
+using namespace std;
+
+namespace isc {
+namespace dhcp {
+
+IterativeAllocator::IterativeAllocator(Lease::Type lease_type)
+ : Allocator(lease_type) {
+}
+
+isc::asiolink::IOAddress
+IterativeAllocator::increasePrefix(const IOAddress& prefix,
+ const uint8_t prefix_len) {
+ if (!prefix.isV6()) {
+ isc_throw(BadValue, "Prefix operations are for IPv6 only (attempted to "
+ "increase prefix " << prefix << ")");
+ }
+
+ // Get a buffer holding an address.
+ const std::vector<uint8_t>& vec = prefix.toBytes();
+
+ if (prefix_len < 1 || prefix_len > 128) {
+ isc_throw(BadValue, "Cannot increase prefix: invalid prefix length: "
+ << prefix_len);
+ }
+
+ uint8_t n_bytes = (prefix_len - 1)/8;
+ uint8_t n_bits = 8 - (prefix_len - n_bytes*8);
+ uint8_t mask = 1 << n_bits;
+
+ // Explanation: n_bytes specifies number of full bytes that are in-prefix.
+ // They can also be used as an offset for the first byte that is not in
+ // prefix. n_bits specifies number of bits on the last byte that is
+ // (often partially) in prefix. For example for a /125 prefix, the values
+ // are 15 and 3, respectively. Mask is a bitmask that has the least
+ // significant bit from the prefix set.
+
+ uint8_t packed[V6ADDRESS_LEN];
+
+ // Copy the address. It must be V6, but we already checked that.
+ memcpy(packed, &vec[0], V6ADDRESS_LEN);
+
+ // Can we safely increase only the last byte in prefix without overflow?
+ if (packed[n_bytes] + uint16_t(mask) < 256u) {
+ packed[n_bytes] += mask;
+ return (IOAddress::fromBytes(AF_INET6, packed));
+ }
+
+ // Overflow (done on uint8_t, but the sum is greater than 255)
+ packed[n_bytes] += mask;
+
+ // Deal with the overflow. Start increasing the least significant byte
+ for (int i = n_bytes - 1; i >= 0; --i) {
+ ++packed[i];
+ // If we haven't overflowed (0xff->0x0) the next byte, then we are done
+ if (packed[i] != 0) {
+ break;
+ }
+ }
+
+ return (IOAddress::fromBytes(AF_INET6, packed));
+}
+
+IOAddress
+IterativeAllocator::increaseAddress(const IOAddress& address,
+ bool prefix,
+ const uint8_t prefix_len) {
+ if (!prefix) {
+ return (IOAddress::increase(address));
+ } else {
+ return (increasePrefix(address, prefix_len));
+ }
+}
+
+IOAddress
+IterativeAllocator::pickAddressInternal(const SubnetPtr& subnet,
+ const ClientClasses& client_classes,
+ const DuidPtr&,
+ const IOAddress&) {
+ // Is this prefix allocation?
+ bool prefix = pool_type_ == Lease::TYPE_PD;
+ uint8_t prefix_len = 0;
+
+ // Let's get the last allocated address. It is usually set correctly,
+ // but there are times when it won't be (like after removing a pool or
+ // perhaps restarting the server).
+ IOAddress last = subnet->getLastAllocated(pool_type_);
+ bool valid = true;
+ bool retrying = false;
+
+ const PoolCollection& pools = subnet->getPools(pool_type_);
+
+ if (pools.empty()) {
+ isc_throw(AllocFailed, "No pools defined in selected subnet");
+ }
+
+ // first we need to find a pool the last address belongs to.
+ PoolCollection::const_iterator it;
+ PoolCollection::const_iterator first = pools.end();
+ PoolPtr first_pool;
+ for (it = pools.begin(); it != pools.end(); ++it) {
+ if (!(*it)->clientSupported(client_classes)) {
+ continue;
+ }
+ if (first == pools.end()) {
+ first = it;
+ }
+ if ((*it)->inRange(last)) {
+ break;
+ }
+ }
+
+ // Caller checked this cannot happen
+ if (first == pools.end()) {
+ isc_throw(AllocFailed, "No allowed pools defined in selected subnet");
+ }
+
+ // last one was bogus for one of several reasons:
+ // - we just booted up and that's the first address we're allocating
+ // - a subnet was removed or other reconfiguration just completed
+ // - perhaps allocation algorithm was changed
+ // - last pool does not allow this client
+ if (it == pools.end()) {
+ it = first;
+ }
+
+ for (;;) {
+ // Trying next pool
+ if (retrying) {
+ for (; it != pools.end(); ++it) {
+ if ((*it)->clientSupported(client_classes)) {
+ break;
+ }
+ }
+ if (it == pools.end()) {
+ // Really out of luck today. That was the last pool.
+ break;
+ }
+ }
+
+ last = (*it)->getLastAllocated();
+ valid = (*it)->isLastAllocatedValid();
+ if (!valid && (last == (*it)->getFirstAddress())) {
+ // Pool was (re)initialized
+ (*it)->setLastAllocated(last);
+ subnet->setLastAllocated(pool_type_, last);
+ return (last);
+ }
+ // still can be bogus
+ if (valid && !(*it)->inRange(last)) {
+ valid = false;
+ (*it)->resetLastAllocated();
+ (*it)->setLastAllocated((*it)->getFirstAddress());
+ }
+
+ if (valid) {
+ // Ok, we have a pool that the last address belonged to, let's use it.
+ if (prefix) {
+ Pool6Ptr pool6 = boost::dynamic_pointer_cast<Pool6>(*it);
+
+ if (!pool6) {
+ // Something is gravely wrong here
+ isc_throw(Unexpected, "Wrong type of pool: "
+ << (*it)->toText()
+ << " is not Pool6");
+ }
+ // Get the prefix length
+ prefix_len = pool6->getLength();
+ }
+
+ IOAddress next = increaseAddress(last, prefix, prefix_len);
+ if ((*it)->inRange(next)) {
+ // the next one is in the pool as well, so we haven't hit
+ // pool boundary yet
+ (*it)->setLastAllocated(next);
+ subnet->setLastAllocated(pool_type_, next);
+ return (next);
+ }
+
+ valid = false;
+ (*it)->resetLastAllocated();
+ }
+ // We hit pool boundary, let's try to jump to the next pool and try again
+ ++it;
+ retrying = true;
+ }
+
+ // Let's rewind to the beginning.
+ for (it = first; it != pools.end(); ++it) {
+ if ((*it)->clientSupported(client_classes)) {
+ (*it)->setLastAllocated((*it)->getFirstAddress());
+ (*it)->resetLastAllocated();
+ }
+ }
+
+ // ok to access first element directly. We checked that pools is non-empty
+ last = (*first)->getLastAllocated();
+ (*first)->setLastAllocated(last);
+ subnet->setLastAllocated(pool_type_, last);
+ return (last);
+}
+
+} // end of namespace isc::dhcp
+} // end of namespace isc \ No newline at end of file
diff --git a/src/lib/dhcpsrv/iterative_allocator.h b/src/lib/dhcpsrv/iterative_allocator.h
new file mode 100644
index 0000000000..1450fe7edc
--- /dev/null
+++ b/src/lib/dhcpsrv/iterative_allocator.h
@@ -0,0 +1,79 @@
+// Copyright (C) 2022 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/.
+
+#ifndef ITERATIVE_ALLOCATOR_H
+#define ITERATIVE_ALLOCATOR_H
+
+#include <dhcpsrv/allocator.h>
+#include <dhcpsrv/lease.h>
+
+#include <cstdint>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Address/prefix allocator that iterates over all addresses
+///
+/// This class implements an iterative algorithm that returns all addresses in
+/// a pool iteratively, one after another. Once the last address is reached,
+/// it starts allocating from the beginning of the first pool (i.e. it loops
+/// over).
+class IterativeAllocator : public Allocator {
+public:
+ /// @brief Default constructor
+ ///
+ /// Does not do anything
+ /// @param type - specifies allocation type
+ IterativeAllocator(Lease::Type type);
+
+private:
+ /// @brief Returns the next address from pools in a subnet
+ ///
+ /// @param subnet next address will be returned from pool of that subnet
+ /// @param client_classes list of classes client belongs to
+ /// @param duid Client's DUID (ignored)
+ /// @param hint Client's hint (ignored)
+ ///
+ /// @return the next address
+ virtual asiolink::IOAddress pickAddressInternal(const SubnetPtr& subnet,
+ const ClientClasses& client_classes,
+ const DuidPtr& duid,
+ const asiolink::IOAddress& hint);
+
+protected:
+ /// @brief Returns the next prefix
+ ///
+ /// This method works for IPv6 addresses only. It increases the
+ /// specified prefix by a given prefix_len. For example, 2001:db8::
+ /// increased by prefix length /32 will become 2001:db9::. This method
+ /// is used to iterate over IPv6 prefix pools
+ ///
+ /// @param prefix prefix to be increased
+ /// @param prefix_len length of the prefix to be increased
+ ///
+ /// @return result prefix
+ static asiolink::IOAddress increasePrefix(const asiolink::IOAddress& prefix,
+ const uint8_t prefix_len);
+
+ /// @brief Returns the next address or prefix
+ ///
+ /// This method works for IPv4 addresses, IPv6 addresses and
+ /// IPv6 prefixes.
+ ///
+ /// @param address address or prefix to be increased
+ /// @param prefix true when the previous argument is a prefix
+ /// @param prefix_len length of the prefix
+ ///
+ /// @return result address or prefix
+ static asiolink::IOAddress increaseAddress(const asiolink::IOAddress& address,
+ bool prefix,
+ const uint8_t prefix_len);
+};
+
+} // namespace dhcp
+} // end of namespace isc
+
+#endif // ITERATIVE_ALLOCATOR_H \ No newline at end of file
diff --git a/src/lib/dhcpsrv/tests/Makefile.am b/src/lib/dhcpsrv/tests/Makefile.am
index c9523eb9b8..17d8ba2a49 100644
--- a/src/lib/dhcpsrv/tests/Makefile.am
+++ b/src/lib/dhcpsrv/tests/Makefile.am
@@ -99,6 +99,7 @@ libdhcpsrv_unittests_SOURCES += host_reservations_list_parser_unittest.cc
libdhcpsrv_unittests_SOURCES += ifaces_config_parser_unittest.cc
libdhcpsrv_unittests_SOURCES += ip_range_unittest.cc
libdhcpsrv_unittests_SOURCES += ip_range_permutation_unittest.cc
+libdhcpsrv_unittests_SOURCES += iterative_allocator_unittest.cc
libdhcpsrv_unittests_SOURCES += lease_file_loader_unittest.cc
libdhcpsrv_unittests_SOURCES += lease_unittest.cc
libdhcpsrv_unittests_SOURCES += lease_mgr_factory_unittest.cc
diff --git a/src/lib/dhcpsrv/tests/alloc_engine4_unittest.cc b/src/lib/dhcpsrv/tests/alloc_engine4_unittest.cc
index 83235a9f9f..31e38bf66e 100644
--- a/src/lib/dhcpsrv/tests/alloc_engine4_unittest.cc
+++ b/src/lib/dhcpsrv/tests/alloc_engine4_unittest.cc
@@ -44,12 +44,6 @@ namespace test {
TEST_F(AllocEngine4Test, constructor) {
boost::scoped_ptr<AllocEngine> x;
- // Hashed and random allocators are not supported yet
- ASSERT_THROW(x.reset(new AllocEngine(AllocEngine::ALLOC_HASHED, 5, false)),
- NotImplemented);
- ASSERT_THROW(x.reset(new AllocEngine(AllocEngine::ALLOC_RANDOM, 5, false)),
- NotImplemented);
-
// Create V4 (ipv6=false) Allocation Engine that will try at most
// 100 attempts to pick up a lease
ASSERT_NO_THROW(x.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100,
@@ -851,98 +845,6 @@ TEST_F(AllocEngine4Test, bootpRenew4) {
EXPECT_EQ(infinity_lft, lease2->valid_lft_);
}
-// This test verifies that the allocator picks addresses that belong to the
-// pool
-TEST_F(AllocEngine4Test, IterativeAllocator) {
- boost::scoped_ptr<NakedAllocEngine::Allocator>
- alloc(new NakedAllocEngine::IterativeAllocator(Lease::TYPE_V4));
-
- for (int i = 0; i < 1000; ++i) {
- IOAddress candidate = alloc->pickAddress(subnet_, cc_, clientid_,
- IOAddress("0.0.0.0"));
- EXPECT_TRUE(subnet_->inPool(Lease::TYPE_V4, candidate));
- }
-}
-
-// This test verifies that the allocator picks addresses that belong to the
-// pool using classification
-TEST_F(AllocEngine4Test, IterativeAllocator_class) {
- boost::scoped_ptr<NakedAllocEngine::Allocator>
- alloc(new NakedAllocEngine::IterativeAllocator(Lease::TYPE_V4));
-
- // Restrict pool_ to the foo class. Add a second pool with bar class.
- pool_->allowClientClass("foo");
- Pool4Ptr pool(new Pool4(IOAddress("192.0.2.200"),
- IOAddress("192.0.2.209")));
- pool->allowClientClass("bar");
- subnet_->addPool(pool);
-
- // Clients are in bar
- cc_.insert("bar");
-
- for (int i = 0; i < 1000; ++i) {
- IOAddress candidate = alloc->pickAddress(subnet_, cc_, clientid_,
- IOAddress("0.0.0.0"));
- EXPECT_TRUE(subnet_->inPool(Lease::TYPE_V4, candidate));
- EXPECT_TRUE(subnet_->inPool(Lease::TYPE_V4, candidate, cc_));
- }
-}
-
-// This test verifies that the iterative allocator really walks over all addresses
-// in all pools in specified subnet. It also must not pick the same address twice
-// unless it runs out of pool space and must start over.
-TEST_F(AllocEngine4Test, IterativeAllocator_manyPools4) {
- NakedAllocEngine::IterativeAllocator alloc(Lease::TYPE_V4);
-
- // Let's start from 2, as there is 2001:db8:1::10 - 2001:db8:1::20 pool already.
- for (int i = 2; i < 10; ++i) {
- stringstream min, max;
-
- min << "192.0.2." << i * 10 + 1;
- max << "192.0.2." << i * 10 + 9;
-
- Pool4Ptr pool(new Pool4(IOAddress(min.str()),
- IOAddress(max.str())));
- // cout << "Adding pool: " << min.str() << "-" << max.str() << endl;
- subnet_->addPool(pool);
- }
-
- int total = 10 + 8 * 9; // first pool (.100 - .109) has 10 addresses in it,
- // there are 8 extra pools with 9 addresses in each.
-
- // Let's keep picked addresses here and check their uniqueness.
- std::set<IOAddress> generated_addrs;
- int cnt = 0;
- while (++cnt) {
- IOAddress candidate = alloc.pickAddress(subnet_, cc_, clientid_, IOAddress("0.0.0.0"));
- EXPECT_TRUE(subnet_->inPool(Lease::TYPE_V4, candidate));
-
- // One way to easily verify that the iterative allocator really works is
- // to uncomment the following line and observe its output that it
- // covers all defined subnets.
- // cout << candidate.toText() << endl;
-
- if (generated_addrs.find(candidate) == generated_addrs.end()) {
- // We haven't had this
- generated_addrs.insert(candidate);
- } else {
- // We have seen this address before. That should mean that we
- // iterated over all addresses.
- if (generated_addrs.size() == total) {
- // We have exactly the number of address in all pools
- break;
- }
- ADD_FAILURE() << "Too many or not enough unique addresses generated.";
- break;
- }
-
- if ( cnt>total ) {
- ADD_FAILURE() << "Too many unique addresses generated.";
- break;
- }
- }
-}
-
// This test checks if really small pools are working
TEST_F(AllocEngine4Test, smallPool4) {
boost::scoped_ptr<AllocEngine> engine;
diff --git a/src/lib/dhcpsrv/tests/alloc_engine6_unittest.cc b/src/lib/dhcpsrv/tests/alloc_engine6_unittest.cc
index e07193bc15..f381c4fc12 100644
--- a/src/lib/dhcpsrv/tests/alloc_engine6_unittest.cc
+++ b/src/lib/dhcpsrv/tests/alloc_engine6_unittest.cc
@@ -10,6 +10,7 @@
#include <dhcpsrv/host_mgr.h>
#include <dhcpsrv/parsers/client_class_def_parser.h>
#include <dhcpsrv/tests/alloc_engine_utils.h>
+#include <dhcpsrv/allocator.h>
#include <dhcpsrv/testutils/test_utils.h>
#include <eval/eval_context.h>
#include <stats/stats_mgr.h>
@@ -58,10 +59,6 @@ TEST(ClientContext6Test, addAllocatedResource) {
TEST_F(AllocEngine6Test, constructor) {
boost::scoped_ptr<AllocEngine> x;
- // Hashed and random allocators are not supported yet
- ASSERT_THROW(x.reset(new AllocEngine(AllocEngine::ALLOC_HASHED, 5)), NotImplemented);
- ASSERT_THROW(x.reset(new AllocEngine(AllocEngine::ALLOC_RANDOM, 5)), NotImplemented);
-
ASSERT_NO_THROW(x.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100, true)));
// Check that allocator for normal addresses is created
@@ -301,8 +298,7 @@ TEST_F(AllocEngine6Test, allocateAddress6Nulls) {
// This test verifies that the allocator picks addresses that belong to the
// pool
TEST_F(AllocEngine6Test, IterativeAllocator) {
- boost::scoped_ptr<NakedAllocEngine::Allocator>
- alloc(new NakedAllocEngine::IterativeAllocator(Lease::TYPE_NA));
+ boost::scoped_ptr<Allocator> alloc(new NakedIterativeAllocator(Lease::TYPE_NA));
for (int i = 0; i < 1000; ++i) {
IOAddress candidate = alloc->pickAddress(subnet_, cc_,
@@ -314,8 +310,7 @@ TEST_F(AllocEngine6Test, IterativeAllocator) {
// This test verifies that the allocator picks addresses that belong to the
// pool using classification
TEST_F(AllocEngine6Test, IterativeAllocator_class) {
- boost::scoped_ptr<NakedAllocEngine::Allocator>
- alloc(new NakedAllocEngine::IterativeAllocator(Lease::TYPE_NA));
+ boost::scoped_ptr<Allocator> alloc(new NakedIterativeAllocator(Lease::TYPE_NA));
// Restrict pool_ to the foo class. Add a second pool with bar class.
pool_->allowClientClass("foo");
@@ -336,7 +331,7 @@ TEST_F(AllocEngine6Test, IterativeAllocator_class) {
}
TEST_F(AllocEngine6Test, IterativeAllocatorAddrStep) {
- NakedAllocEngine::NakedIterativeAllocator alloc(Lease::TYPE_NA);
+ NakedIterativeAllocator alloc(Lease::TYPE_NA);
subnet_->delPools(Lease::TYPE_NA); // Get rid of default pool
@@ -381,7 +376,7 @@ TEST_F(AllocEngine6Test, IterativeAllocatorAddrStep) {
}
TEST_F(AllocEngine6Test, IterativeAllocatorAddrStepInClass) {
- NakedAllocEngine::NakedIterativeAllocator alloc(Lease::TYPE_NA);
+ NakedIterativeAllocator alloc(Lease::TYPE_NA);
subnet_->delPools(Lease::TYPE_NA); // Get rid of default pool
@@ -432,7 +427,7 @@ TEST_F(AllocEngine6Test, IterativeAllocatorAddrStepInClass) {
}
TEST_F(AllocEngine6Test, IterativeAllocatorAddrStepOutClass) {
- NakedAllocEngine::NakedIterativeAllocator alloc(Lease::TYPE_NA);
+ NakedIterativeAllocator alloc(Lease::TYPE_NA);
subnet_->delPools(Lease::TYPE_NA); // Get rid of default pool
@@ -477,7 +472,7 @@ TEST_F(AllocEngine6Test, IterativeAllocatorAddrStepOutClass) {
}
TEST_F(AllocEngine6Test, IterativeAllocatorPrefixStep) {
- NakedAllocEngine::NakedIterativeAllocator alloc(Lease::TYPE_PD);
+ NakedIterativeAllocator alloc(Lease::TYPE_PD);
subnet_.reset(new Subnet6(IOAddress("2001:db8::"), 32, 1, 2, 3, 4));
@@ -554,7 +549,7 @@ TEST_F(AllocEngine6Test, IterativeAllocatorPrefixStep) {
}
TEST_F(AllocEngine6Test, IterativeAllocatorPrefixStepInClass) {
- NakedAllocEngine::NakedIterativeAllocator alloc(Lease::TYPE_PD);
+ NakedIterativeAllocator alloc(Lease::TYPE_PD);
subnet_.reset(new Subnet6(IOAddress("2001:db8::"), 32, 1, 2, 3, 4));
@@ -637,7 +632,7 @@ TEST_F(AllocEngine6Test, IterativeAllocatorPrefixStepInClass) {
}
TEST_F(AllocEngine6Test, IterativeAllocatorPrefixStepOutClass) {
- NakedAllocEngine::NakedIterativeAllocator alloc(Lease::TYPE_PD);
+ NakedIterativeAllocator alloc(Lease::TYPE_PD);
subnet_.reset(new Subnet6(IOAddress("2001:db8::"), 32, 1, 2, 3, 4));
@@ -715,7 +710,7 @@ TEST_F(AllocEngine6Test, IterativeAllocatorPrefixStepOutClass) {
// This test verifies that the iterative allocator can step over addresses
TEST_F(AllocEngine6Test, IterativeAllocatorAddressIncrease) {
- NakedAllocEngine::NakedIterativeAllocator alloc(Lease::TYPE_NA);
+ NakedIterativeAllocator alloc(Lease::TYPE_NA);
// Let's pick the first address
IOAddress addr1 = alloc.pickAddress(subnet_, cc_, duid_, IOAddress("2001:db8:1::10"));
@@ -735,7 +730,7 @@ TEST_F(AllocEngine6Test, IterativeAllocatorAddressIncrease) {
// This test verifies that the allocator can step over prefixes
TEST_F(AllocEngine6Test, IterativeAllocatorPrefixIncrease) {
- NakedAllocEngine::NakedIterativeAllocator alloc(Lease::TYPE_PD);
+ NakedIterativeAllocator alloc(Lease::TYPE_PD);
// For /128 prefix, increasePrefix should work the same as addressIncrease
checkPrefixIncrease(alloc, "2001:db8::9", 128, "2001:db8::a");
@@ -787,7 +782,7 @@ TEST_F(AllocEngine6Test, IterativeAllocatorPrefixIncrease) {
// in all pools in specified subnet. It also must not pick the same address twice
// unless it runs out of pool space and must start over.
TEST_F(AllocEngine6Test, IterativeAllocator_manyPools6) {
- NakedAllocEngine::IterativeAllocator alloc(Lease::TYPE_NA);
+ NakedIterativeAllocator alloc(Lease::TYPE_NA);
// let's start from 2, as there is 2001:db8:1::10 - 2001:db8:1::20 pool already.
for (int i = 2; i < 10; ++i) {
diff --git a/src/lib/dhcpsrv/tests/alloc_engine_utils.h b/src/lib/dhcpsrv/tests/alloc_engine_utils.h
index be8152419e..b94e1cf183 100644
--- a/src/lib/dhcpsrv/tests/alloc_engine_utils.h
+++ b/src/lib/dhcpsrv/tests/alloc_engine_utils.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2015-2021 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015-2022 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
@@ -7,11 +7,13 @@
#ifndef LIBDHCPSRV_ALLOC_ENGINE_UTILS_H
#define LIBDHCPSRV_ALLOC_ENGINE_UTILS_H
-#include <dhcpsrv/lease_mgr.h>
-#include <dhcpsrv/lease_mgr_factory.h>
+#include <asiolink/io_address.h>
#include <dhcpsrv/alloc_engine.h>
#include <dhcpsrv/cfgmgr.h>
-#include <asiolink/io_address.h>
+#include <dhcpsrv/iterative_allocator.h>
+#include <dhcpsrv/lease_mgr.h>
+#include <dhcpsrv/lease_mgr_factory.h>
+
#include <gtest/gtest.h>
#include <vector>
@@ -31,7 +33,6 @@ namespace test {
/// alloc_engine6_unittest.cc - all unit-tests dedicated to IPv6
/// alloc_engine_hooks_unittest.cc - all unit-tests dedicated to hooks
-
/// @brief Test that statistic manager holds a given value.
///
/// This function may be used in many allocation tests and there's no
@@ -43,7 +44,8 @@ namespace test {
///
/// @return true if the statistic manager holds a particular value,
/// false otherwise.
-bool testStatistics(const std::string& stat_name, const int64_t exp_value,
+bool testStatistics(const std::string& stat_name,
+ const int64_t exp_value,
const SubnetID subnet_id = SUBNET_ID_UNUSED);
/// @brief Get a value held by statistic manager.
@@ -54,42 +56,35 @@ bool testStatistics(const std::string& stat_name, const int64_t exp_value,
/// @param stat_name Statistic name.
/// @param subnet_id subnet_id of the desired subnet, if not zero.
/// @return the value held by the statistic manager or zero.
-int64_t getStatistics(const std::string& stat_name,
- const SubnetID subnet_id = SUBNET_ID_UNUSED);
+int64_t getStatistics(const std::string& stat_name, const SubnetID subnet_id = SUBNET_ID_UNUSED);
+
+/// @brief IterativeAllocator with internal methods exposed
+class NakedIterativeAllocator : public IterativeAllocator {
+public:
+ /// @brief constructor
+ /// @param type pool types that will be iterated through
+ NakedIterativeAllocator(Lease::Type type) : IterativeAllocator(type) {
+ }
+
+ using IterativeAllocator::increaseAddress;
+ using IterativeAllocator::increasePrefix;
+};
/// @brief Allocation engine with some internal methods exposed
class NakedAllocEngine : public AllocEngine {
public:
-
/// @brief the sole constructor
/// @param engine_type specifies engine type (e.g. iterative)
/// @param attempts number of lease selection attempts before giving up
/// @param ipv6 specifies if the engine is IPv6 or IPv4
- NakedAllocEngine(AllocEngine::AllocType engine_type,
- unsigned int attempts, bool ipv6 = true)
- :AllocEngine(engine_type, attempts, ipv6) {
+ NakedAllocEngine(AllocEngine::AllocType engine_type, unsigned int attempts, bool ipv6 = true)
+ : AllocEngine(engine_type, attempts, ipv6) {
}
// Expose internal classes for testing purposes
- using AllocEngine::Allocator;
- using AllocEngine::IterativeAllocator;
using AllocEngine::getAllocator;
using AllocEngine::updateLease4ExtendedInfo;
- /// @brief IterativeAllocator with internal methods exposed
- class NakedIterativeAllocator: public AllocEngine::IterativeAllocator {
- public:
-
- /// @brief constructor
- /// @param type pool types that will be iterated through
- NakedIterativeAllocator(Lease::Type type)
- :IterativeAllocator(type) {
- }
-
- using AllocEngine::IterativeAllocator::increaseAddress;
- using AllocEngine::IterativeAllocator::increasePrefix;
- };
-
/// @brief Wrapper method for invoking AllocEngine4::updateLease4ExtendedInfo().
/// @param lease lease to update
/// @param ctx current packet processing context
@@ -141,11 +136,10 @@ public:
const asiolink::IOAddress& pool_start,
const asiolink::IOAddress& pool_end,
const asiolink::IOAddress& pd_pool_prefix =
- asiolink::IOAddress::IPV6_ZERO_ADDRESS(),
+ asiolink::IOAddress::IPV6_ZERO_ADDRESS(),
const uint8_t pd_pool_length = 0,
const uint8_t pd_delegated_length = 0);
-
/// @brief Initializes FQDN data for a test.
///
/// The initialized values are used by the test fixture class members to
@@ -198,8 +192,10 @@ public:
/// @param exp_pd_len expected prefix length
/// @param expected_in_subnet whether the lease is expected to be in subnet
/// @param expected_in_pool whether the lease is expected to be in dynamic
- void checkLease6(const DuidPtr& duid, const Lease6Ptr& lease,
- Lease::Type exp_type, uint8_t exp_pd_len = 128,
+ void checkLease6(const DuidPtr& duid,
+ const Lease6Ptr& lease,
+ Lease::Type exp_type,
+ uint8_t exp_pd_len = 128,
bool expected_in_subnet = true,
bool expected_in_pool = true) {
@@ -263,9 +259,9 @@ public:
/// @param alloc IterativeAllocator that is tested
/// @param input address to be increased
/// @param exp_output expected address after increase
- void
- checkAddrIncrease(NakedAllocEngine::NakedIterativeAllocator& alloc,
- std::string input, std::string exp_output) {
+ void checkAddrIncrease(NakedIterativeAllocator& alloc,
+ std::string input,
+ std::string exp_output) {
EXPECT_EQ(exp_output, alloc.increaseAddress(asiolink::IOAddress(input),
false, 0).toText());
}
@@ -278,12 +274,13 @@ public:
/// @param input IPv6 prefix (as a string)
/// @param prefix_len prefix len
/// @param exp_output expected output (string)
- void
- checkPrefixIncrease(NakedAllocEngine::NakedIterativeAllocator& alloc,
- std::string input, uint8_t prefix_len,
- std::string exp_output) {
- EXPECT_EQ(exp_output, alloc.increasePrefix(asiolink::IOAddress(input),
- prefix_len).toText());
+ void checkPrefixIncrease(NakedIterativeAllocator& alloc,
+ std::string input,
+ uint8_t prefix_len,
+ std::string exp_output) {
+ EXPECT_EQ(exp_output,
+ alloc.increasePrefix(asiolink::IOAddress(input),
+ prefix_len).toText());
}
/// @brief Checks if the simple allocation can succeed
@@ -297,7 +294,8 @@ public:
/// @return allocated lease (or NULL)
Lease6Ptr simpleAlloc6Test(const Pool6Ptr& pool,
const asiolink::IOAddress& hint,
- bool fake, bool in_pool = true);
+ bool fake,
+ bool in_pool = true);
/// @brief Checks if the simple allocation can succeed with lifetimes.
///
@@ -312,8 +310,10 @@ public:
/// @return allocated lease (or NULL)
Lease6Ptr simpleAlloc6Test(const Pool6Ptr& pool,
const asiolink::IOAddress& hint,
- uint32_t preferred, uint32_t valid,
- uint32_t exp_preferred, uint32_t exp_valid);
+ uint32_t preferred,
+ uint32_t valid,
+ uint32_t exp_preferred,
+ uint32_t exp_valid);
/// @brief Checks if the simple allocation can succeed for custom DUID.
///
@@ -325,10 +325,11 @@ public:
/// @param fake true - this is fake allocation (SOLICIT)
/// @param in_pool specifies whether the lease is expected to be in pool
/// @return allocated lease (or NULL)
- Lease6Ptr simpleAlloc6Test(const Pool6Ptr& pool, const DuidPtr& duid,
+ Lease6Ptr simpleAlloc6Test(const Pool6Ptr& pool,
+ const DuidPtr& duid,
const asiolink::IOAddress& hint,
- bool fake, bool in_pool = true);
-
+ bool fake,
+ bool in_pool = true);
/// @brief Checks if the allocation can succeed.
///
@@ -341,8 +342,10 @@ public:
/// @param fake true - this is fake allocation (SOLICIT)
/// @param in_pool specifies whether the lease is expected to be in pool
/// @return allocated lease(s) (may be empty)
- Lease6Collection allocateTest(AllocEngine& engine, const Pool6Ptr& pool,
- const asiolink::IOAddress& hint, bool fake,
+ Lease6Collection allocateTest(AllocEngine& engine,
+ const Pool6Ptr& pool,
+ const asiolink::IOAddress& hint,
+ bool fake,
bool in_pool = true);
/// @brief Checks if the allocation can be renewed.
@@ -355,7 +358,8 @@ public:
/// @param hints address to be used as a hint
/// @param in_pool specifies whether the lease is expected to be in pool
/// @return allocated lease(s) (may be empty)
- Lease6Collection renewTest(AllocEngine& engine, const Pool6Ptr& pool,
+ Lease6Collection renewTest(AllocEngine& engine,
+ const Pool6Ptr& pool,
AllocEngine::HintContainer& hints,
bool in_pool = true);
@@ -369,7 +373,8 @@ public:
/// allocation by some other user)
/// @param requested address requested by the client
/// @param expected_pd_len expected PD len (128 for addresses)
- void allocWithUsedHintTest(Lease::Type type, asiolink::IOAddress used_addr,
+ void allocWithUsedHintTest(Lease::Type type,
+ asiolink::IOAddress used_addr,
asiolink::IOAddress requested,
uint8_t expected_pd_len);
@@ -426,12 +431,12 @@ public:
/// @param addr specifies reserved address or prefix
/// @param prefix_len prefix length (should be 128 for addresses)
/// @return created Host object.
- HostPtr
- createHost6(bool add_to_host_mgr, IPv6Resrv::Type type,
- const asiolink::IOAddress& addr, uint8_t prefix_len) {
- HostPtr host(new Host(&duid_->getDuid()[0], duid_->getDuid().size(),
- Host::IDENT_DUID, SUBNET_ID_UNUSED, subnet_->getID(),
- asiolink::IOAddress("0.0.0.0")));
+ HostPtr createHost6(bool add_to_host_mgr,
+ IPv6Resrv::Type type,
+ const asiolink::IOAddress& addr,
+ uint8_t prefix_len) {
+ HostPtr host(new Host(&duid_->getDuid()[0], duid_->getDuid().size(), Host::IDENT_DUID,
+ SUBNET_ID_UNUSED, subnet_->getID(), asiolink::IOAddress("0.0.0.0")));
IPv6Resrv resv(type, addr, prefix_len);
host->addReservation(resv);
@@ -450,8 +455,7 @@ public:
/// such as subnets.
///
/// @param host host reservation to add
- void
- addHost(HostPtr& host) {
+ void addHost(HostPtr& host) {
SrvConfigPtr cfg = boost::const_pointer_cast<SrvConfig>(CfgMgr::instance().getCurrentCfg());
cfg->getCfgHosts()->add(host);
}
@@ -464,10 +468,11 @@ public:
/// @param addr specifies reserved address or prefix
/// @param prefix_len prefix length (should be 128 for addresses)
/// @return created Host object.
- HostPtr
- createHost6HWAddr(bool add_to_host_mgr, IPv6Resrv::Type type,
- HWAddrPtr& hwaddr, const asiolink::IOAddress& addr,
- uint8_t prefix_len);
+ HostPtr createHost6HWAddr(bool add_to_host_mgr,
+ IPv6Resrv::Type type,
+ HWAddrPtr& hwaddr,
+ const asiolink::IOAddress& addr,
+ uint8_t prefix_len);
/// @brief Utility function that decrements cltt of a persisted lease
///
@@ -478,13 +483,11 @@ public:
///
/// @param[in][out] lease pointer reference to the lease to modify. Upon
/// return it will point to the newly updated lease.
- void
- rollbackPersistedCltt(Lease6Ptr& lease) {
+ void rollbackPersistedCltt(Lease6Ptr& lease) {
ASSERT_TRUE(lease) << "rollbackPersistedCltt lease is empty";
// Fetch it, so we can update it.
- Lease6Ptr from_mgr = LeaseMgrFactory::instance().getLease6(lease->type_,
- lease->addr_);
+ Lease6Ptr from_mgr = LeaseMgrFactory::instance().getLease6(lease->type_, lease->addr_);
ASSERT_TRUE(from_mgr) << "rollbackPersistedCltt: lease not found?";
// Decrement cltt then update it in the manager.
@@ -552,11 +555,9 @@ public:
}
if (lease->client_id_ && !clientid_) {
ADD_FAILURE() << "Lease4 has a client-id, while it should have none.";
- } else
- if (!lease->client_id_ && clientid_) {
+ } else if (!lease->client_id_ && clientid_) {
ADD_FAILURE() << "Lease4 has no client-id, but it was expected to have one.";
- } else
- if (lease->client_id_ && clientid_) {
+ } else if (lease->client_id_ && clientid_) {
EXPECT_TRUE(*lease->client_id_ == *clientid_);
}
EXPECT_TRUE(*lease->hwaddr_ == *hwaddr_);
@@ -610,16 +611,16 @@ public:
factory_.destroy();
}
- ClientIdPtr clientid_; ///< Client-identifier (value used in tests)
- ClientIdPtr clientid2_; ///< Alternative client-identifier.
- HWAddrPtr hwaddr_; ///< Hardware address (value used in tests)
- HWAddrPtr hwaddr2_; ///< Alternative hardware address.
- Subnet4Ptr subnet_; ///< Subnet4 (used in tests)
- Pool4Ptr pool_; ///< Pool belonging to subnet_
- LeaseMgrFactory factory_; ///< Pointer to LeaseMgr factory
+ ClientIdPtr clientid_; ///< Client-identifier (value used in tests)
+ ClientIdPtr clientid2_; ///< Alternative client-identifier.
+ HWAddrPtr hwaddr_; ///< Hardware address (value used in tests)
+ HWAddrPtr hwaddr2_; ///< Alternative hardware address.
+ Subnet4Ptr subnet_; ///< Subnet4 (used in tests)
+ Pool4Ptr pool_; ///< Pool belonging to subnet_
+ LeaseMgrFactory factory_; ///< Pointer to LeaseMgr factory
AllocEngine::ClientContext4 ctx_; ///< Context information passed to various
- ClientClasses cc_; ///< Client classes
- ///< allocation engine functions.
+ ClientClasses cc_; ///< Client classes
+ ///< allocation engine functions.
};
} // namespace test
diff --git a/src/lib/dhcpsrv/tests/iterative_allocator_unittest.cc b/src/lib/dhcpsrv/tests/iterative_allocator_unittest.cc
new file mode 100644
index 0000000000..1dda363fcc
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/iterative_allocator_unittest.cc
@@ -0,0 +1,666 @@
+// Copyright (C) 2022 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/io_address.h>
+#include <dhcpsrv/iterative_allocator.h>
+#include <dhcpsrv/tests/alloc_engine_utils.h>
+#include <gtest/gtest.h>
+#include <sstream>
+
+using namespace isc::asiolink;
+using namespace isc::dhcp;
+using namespace std;
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+using IterativeAllocatorTest4 = AllocEngine4Test;
+
+// This test verifies that the allocator picks addresses that belong to the
+// pool
+TEST_F(IterativeAllocatorTest4, basic) {
+ boost::scoped_ptr<Allocator> alloc(new IterativeAllocator(Lease::TYPE_V4));
+
+ for (int i = 0; i < 1000; ++i) {
+ IOAddress candidate = alloc->pickAddress(subnet_, cc_, clientid_,
+ IOAddress("0.0.0.0"));
+ EXPECT_TRUE(subnet_->inPool(Lease::TYPE_V4, candidate));
+ }
+}
+
+// This test verifies that the allocator picks addresses that belong to the
+// pool using classification
+TEST_F(IterativeAllocatorTest4, clientClass) {
+ boost::scoped_ptr<Allocator> alloc(new IterativeAllocator(Lease::TYPE_V4));
+
+ // Restrict pool_ to the foo class. Add a second pool with bar class.
+ pool_->allowClientClass("foo");
+ Pool4Ptr pool(new Pool4(IOAddress("192.0.2.200"),
+ IOAddress("192.0.2.209")));
+ pool->allowClientClass("bar");
+ subnet_->addPool(pool);
+
+ // Clients are in bar
+ cc_.insert("bar");
+
+ for (int i = 0; i < 1000; ++i) {
+ IOAddress candidate = alloc->pickAddress(subnet_, cc_, clientid_,
+ IOAddress("0.0.0.0"));
+ EXPECT_TRUE(subnet_->inPool(Lease::TYPE_V4, candidate));
+ EXPECT_TRUE(subnet_->inPool(Lease::TYPE_V4, candidate, cc_));
+ }
+}
+
+// This test verifies that the iterative allocator really walks over all addresses
+// in all pools in specified subnet. It also must not pick the same address twice
+// unless it runs out of pool space and must start over.
+TEST_F(IterativeAllocatorTest4, manyPools) {
+ IterativeAllocator alloc(Lease::TYPE_V4);
+
+ // Let's start from 2, as there is 2001:db8:1::10 - 2001:db8:1::20 pool already.
+ for (int i = 2; i < 10; ++i) {
+ stringstream min, max;
+
+ min << "192.0.2." << i * 10 + 1;
+ max << "192.0.2." << i * 10 + 9;
+
+ Pool4Ptr pool(new Pool4(IOAddress(min.str()),
+ IOAddress(max.str())));
+ // cout << "Adding pool: " << min.str() << "-" << max.str() << endl;
+ subnet_->addPool(pool);
+ }
+
+ int total = 10 + 8 * 9; // first pool (.100 - .109) has 10 addresses in it,
+ // there are 8 extra pools with 9 addresses in each.
+
+ // Let's keep picked addresses here and check their uniqueness.
+ std::set<IOAddress> generated_addrs;
+ int cnt = 0;
+ while (++cnt) {
+ IOAddress candidate = alloc.pickAddress(subnet_, cc_, clientid_, IOAddress("0.0.0.0"));
+ EXPECT_TRUE(subnet_->inPool(Lease::TYPE_V4, candidate));
+
+ // One way to easily verify that the iterative allocator really works is
+ // to uncomment the following line and observe its output that it
+ // covers all defined subnets.
+ // cout << candidate.toText() << endl;
+
+ if (generated_addrs.find(candidate) == generated_addrs.end()) {
+ // We haven't had this
+ generated_addrs.insert(candidate);
+ } else {
+ // We have seen this address before. That should mean that we
+ // iterated over all addresses.
+ if (generated_addrs.size() == total) {
+ // We have exactly the number of address in all pools
+ break;
+ }
+ ADD_FAILURE() << "Too many or not enough unique addresses generated.";
+ break;
+ }
+
+ if ( cnt>total ) {
+ ADD_FAILURE() << "Too many unique addresses generated.";
+ break;
+ }
+ }
+}
+
+using IterativeAllocatorTest6 = AllocEngine6Test;
+
+// This test verifies that the allocator picks addresses that belong to the
+// pool
+TEST_F(IterativeAllocatorTest6, basic) {
+ boost::scoped_ptr<Allocator> alloc(new NakedIterativeAllocator(Lease::TYPE_NA));
+
+ for (int i = 0; i < 1000; ++i) {
+ IOAddress candidate = alloc->pickAddress(subnet_, cc_,
+ duid_, IOAddress("::"));
+ EXPECT_TRUE(subnet_->inPool(Lease::TYPE_NA, candidate));
+ }
+}
+
+// This test verifies that the allocator picks addresses that belong to the
+// pool using classification
+TEST_F(IterativeAllocatorTest6, clientClass) {
+ boost::scoped_ptr<Allocator> alloc(new NakedIterativeAllocator(Lease::TYPE_NA));
+
+ // Restrict pool_ to the foo class. Add a second pool with bar class.
+ pool_->allowClientClass("foo");
+ Pool6Ptr pool(new Pool6(Lease::TYPE_NA, IOAddress("2001:db8:1::100"),
+ IOAddress("2001:db8:1::109")));
+ pool->allowClientClass("bar");
+ subnet_->addPool(pool);
+
+ // Clients are in bar
+ cc_.insert("bar");
+
+ for (int i = 0; i < 1000; ++i) {
+ IOAddress candidate = alloc->pickAddress(subnet_, cc_,
+ duid_, IOAddress("::"));
+ EXPECT_TRUE(subnet_->inPool(Lease::TYPE_NA, candidate));
+ EXPECT_TRUE(subnet_->inPool(Lease::TYPE_NA, candidate, cc_));
+ }
+}
+
+// This test verifies that the allocator walks over the addresses in the
+// non-contiguous pools.
+TEST_F(IterativeAllocatorTest6, addrStep) {
+ NakedIterativeAllocator alloc(Lease::TYPE_NA);
+
+ subnet_->delPools(Lease::TYPE_NA); // Get rid of default pool
+
+ Pool6Ptr pool1(new Pool6(Lease::TYPE_NA, IOAddress("2001:db8:1::1"),
+ IOAddress("2001:db8:1::5")));
+ Pool6Ptr pool2(new Pool6(Lease::TYPE_NA, IOAddress("2001:db8:1::100"),
+ IOAddress("2001:db8:1::100")));
+ Pool6Ptr pool3(new Pool6(Lease::TYPE_NA, IOAddress("2001:db8:1::105"),
+ IOAddress("2001:db8:1::106")));
+ subnet_->addPool(pool1);
+ subnet_->addPool(pool2);
+ subnet_->addPool(pool3);
+
+ // Let's check the first pool (5 addresses here)
+ EXPECT_EQ("2001:db8:1::1",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:1::2",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:1::3",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:1::4",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:1::5",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+
+ // The second pool is easy - only one address here
+ EXPECT_EQ("2001:db8:1::100",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+
+ // This is the third and last pool, with 2 addresses in it
+ EXPECT_EQ("2001:db8:1::105",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:1::106",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+
+ // We iterated over all addresses and reached to the end of the last pool.
+ // Let's wrap around and start from the beginning
+ EXPECT_EQ("2001:db8:1::1",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:1::2",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+}
+
+// This test verifies that the allocator walks over the addresses in the
+// non-contiguous pools when pools contain class guards.
+TEST_F(IterativeAllocatorTest6, addrStepInClass) {
+ NakedIterativeAllocator alloc(Lease::TYPE_NA);
+
+ subnet_->delPools(Lease::TYPE_NA); // Get rid of default pool
+
+ Pool6Ptr pool1(new Pool6(Lease::TYPE_NA, IOAddress("2001:db8:1::1"),
+ IOAddress("2001:db8:1::5")));
+ Pool6Ptr pool2(new Pool6(Lease::TYPE_NA, IOAddress("2001:db8:1::100"),
+ IOAddress("2001:db8:1::100")));
+ Pool6Ptr pool3(new Pool6(Lease::TYPE_NA, IOAddress("2001:db8:1::105"),
+ IOAddress("2001:db8:1::106")));
+ // Set pool1 and pool3 but not pool2 in foo class
+ pool1->allowClientClass("foo");
+ pool3->allowClientClass("foo");
+ subnet_->addPool(pool1);
+ subnet_->addPool(pool2);
+ subnet_->addPool(pool3);
+
+ // Clients are in foo
+ cc_.insert("foo");
+
+ // Let's check the first pool (5 addresses here)
+ EXPECT_EQ("2001:db8:1::1",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:1::2",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:1::3",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:1::4",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:1::5",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+
+ // The second pool is easy - only one address here
+ EXPECT_EQ("2001:db8:1::100",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+
+ // This is the third and last pool, with 2 addresses in it
+ EXPECT_EQ("2001:db8:1::105",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:1::106",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+
+ // We iterated over all addresses and reached to the end of the last pool.
+ // Let's wrap around and start from the beginning
+ EXPECT_EQ("2001:db8:1::1",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:1::2",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+}
+
+// This test verifies that the allocator omits pools with non-matching class guards.
+TEST_F(IterativeAllocatorTest6, addrStepOutClass) {
+ NakedIterativeAllocator alloc(Lease::TYPE_NA);
+
+ subnet_->delPools(Lease::TYPE_NA); // Get rid of default pool
+
+ Pool6Ptr pool1(new Pool6(Lease::TYPE_NA, IOAddress("2001:db8:1::1"),
+ IOAddress("2001:db8:1::5")));
+ Pool6Ptr pool2(new Pool6(Lease::TYPE_NA, IOAddress("2001:db8:1::100"),
+ IOAddress("2001:db8:1::100")));
+ Pool6Ptr pool3(new Pool6(Lease::TYPE_NA, IOAddress("2001:db8:1::105"),
+ IOAddress("2001:db8:1::106")));
+ // Set pool2 in foo
+ pool2->allowClientClass("foo");
+ subnet_->addPool(pool1);
+ subnet_->addPool(pool2);
+ subnet_->addPool(pool3);
+
+ // Let's check the first pool (5 addresses here)
+ EXPECT_EQ("2001:db8:1::1",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:1::2",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:1::3",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:1::4",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:1::5",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+
+ // The second pool is skipped
+
+ // This is the third and last pool, with 2 addresses in it
+ EXPECT_EQ("2001:db8:1::105",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:1::106",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+
+ // We iterated over all addresses and reached to the end of the last pool.
+ // Let's wrap around and start from the beginning
+ EXPECT_EQ("2001:db8:1::1",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:1::2",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+}
+
+// This test verifies that the allocator picks delegated prefixes from several
+// pools.
+TEST_F(IterativeAllocatorTest6, prefixStep) {
+ NakedIterativeAllocator alloc(Lease::TYPE_PD);
+
+ subnet_.reset(new Subnet6(IOAddress("2001:db8::"), 32, 1, 2, 3, 4));
+
+ Pool6Ptr pool1(new Pool6(Lease::TYPE_PD, IOAddress("2001:db8::"), 56, 60));
+ Pool6Ptr pool2(new Pool6(Lease::TYPE_PD, IOAddress("2001:db8:1::"), 48, 48));
+ Pool6Ptr pool3(new Pool6(Lease::TYPE_PD, IOAddress("2001:db8:2::"), 56, 64));
+ subnet_->addPool(pool1);
+ subnet_->addPool(pool2);
+ subnet_->addPool(pool3);
+
+ // We have a 2001:db8::/48 subnet that has 3 pools defined in it:
+ // 2001:db8::/56 split into /60 prefixes (16 leases) (or 2001:db8:0:X0::)
+ // 2001:db8:1::/48 split into a single /48 prefix (just 1 lease)
+ // 2001:db8:2::/56 split into /64 prefixes (256 leases) (or 2001:db8:2:XX::)
+
+ // First pool check (Let's check over all 16 leases)
+ EXPECT_EQ("2001:db8::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:0:10::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:0:20::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:0:30::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:0:40::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:0:50::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:0:60::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:0:70::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:0:80::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:0:90::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:0:a0::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:0:b0::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:0:c0::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:0:d0::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:0:e0::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:0:f0::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+
+ // Second pool (just one lease here)
+ EXPECT_EQ("2001:db8:1::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+
+ // Third pool (256 leases, let's check first and last explicitly and the
+ // rest over in a pool
+ EXPECT_EQ("2001:db8:2::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ for (int i = 1; i < 255; i++) {
+ stringstream exp;
+ exp << "2001:db8:2:" << hex << i << dec << "::";
+ EXPECT_EQ(exp.str(),
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+
+ }
+ EXPECT_EQ("2001:db8:2:ff::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+
+ // Ok, we've iterated over all prefixes in all pools. We now wrap around.
+ // We're looping over now (iterating over first pool again)
+ EXPECT_EQ("2001:db8::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:0:10::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+}
+
+// This test verifies that the allocator picks delegated prefixes from the pools
+// with class guards.
+TEST_F(IterativeAllocatorTest6, prefixStepInClass) {
+ NakedIterativeAllocator alloc(Lease::TYPE_PD);
+
+ subnet_.reset(new Subnet6(IOAddress("2001:db8::"), 32, 1, 2, 3, 4));
+
+ Pool6Ptr pool1(new Pool6(Lease::TYPE_PD, IOAddress("2001:db8::"), 56, 60));
+ Pool6Ptr pool2(new Pool6(Lease::TYPE_PD, IOAddress("2001:db8:1::"), 48, 48));
+ Pool6Ptr pool3(new Pool6(Lease::TYPE_PD, IOAddress("2001:db8:2::"), 56, 64));
+ // Set pool1 and pool3 but not pool2 in foo class
+ pool1->allowClientClass("foo");
+ pool3->allowClientClass("foo");
+ subnet_->addPool(pool1);
+ subnet_->addPool(pool2);
+ subnet_->addPool(pool3);
+
+ // Clients are in foo
+ cc_.insert("foo");
+
+ // We have a 2001:db8::/48 subnet that has 3 pools defined in it:
+ // 2001:db8::/56 split into /60 prefixes (16 leases) (or 2001:db8:0:X0::)
+ // 2001:db8:1::/48 split into a single /48 prefix (just 1 lease)
+ // 2001:db8:2::/56 split into /64 prefixes (256 leases) (or 2001:db8:2:XX::)
+
+ // First pool check (Let's check over all 16 leases)
+ EXPECT_EQ("2001:db8::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:0:10::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:0:20::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:0:30::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:0:40::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:0:50::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:0:60::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:0:70::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:0:80::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:0:90::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:0:a0::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:0:b0::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:0:c0::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:0:d0::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:0:e0::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:0:f0::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+
+ // Second pool (just one lease here)
+ EXPECT_EQ("2001:db8:1::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+
+ // Third pool (256 leases, let's check first and last explicitly and the
+ // rest over in a pool
+ EXPECT_EQ("2001:db8:2::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ for (int i = 1; i < 255; i++) {
+ stringstream exp;
+ exp << "2001:db8:2:" << hex << i << dec << "::";
+ EXPECT_EQ(exp.str(),
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+
+ }
+ EXPECT_EQ("2001:db8:2:ff::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+
+ // Ok, we've iterated over all prefixes in all pools. We now wrap around.
+ // We're looping over now (iterating over first pool again)
+ EXPECT_EQ("2001:db8::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:0:10::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+}
+
+// This test verifies that the allocator omits pools with non-matching client classes.
+TEST_F(IterativeAllocatorTest6, prefixStepOutClass) {
+ NakedIterativeAllocator alloc(Lease::TYPE_PD);
+
+ subnet_.reset(new Subnet6(IOAddress("2001:db8::"), 32, 1, 2, 3, 4));
+
+ Pool6Ptr pool1(new Pool6(Lease::TYPE_PD, IOAddress("2001:db8::"), 56, 60));
+ Pool6Ptr pool2(new Pool6(Lease::TYPE_PD, IOAddress("2001:db8:1::"), 48, 48));
+ Pool6Ptr pool3(new Pool6(Lease::TYPE_PD, IOAddress("2001:db8:2::"), 56, 64));
+ // Set pool2 in foo
+ pool2->allowClientClass("foo");
+ subnet_->addPool(pool1);
+ subnet_->addPool(pool2);
+ subnet_->addPool(pool3);
+
+ // We have a 2001:db8::/48 subnet that has 3 pools defined in it:
+ // 2001:db8::/56 split into /60 prefixes (16 leases) (or 2001:db8:0:X0::)
+ // 2001:db8:1::/48 split into a single /48 prefix (just 1 lease)
+ // 2001:db8:2::/56 split into /64 prefixes (256 leases) (or 2001:db8:2:XX::)
+
+ // First pool check (Let's check over all 16 leases)
+ EXPECT_EQ("2001:db8::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:0:10::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:0:20::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:0:30::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:0:40::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:0:50::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:0:60::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:0:70::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:0:80::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:0:90::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:0:a0::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:0:b0::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:0:c0::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:0:d0::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:0:e0::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:0:f0::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+
+ // The second pool is skipped
+
+ // Third pool (256 leases, let's check first and last explicitly and the
+ // rest over in a pool
+ EXPECT_EQ("2001:db8:2::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ for (int i = 1; i < 255; i++) {
+ stringstream exp;
+ exp << "2001:db8:2:" << hex << i << dec << "::";
+ EXPECT_EQ(exp.str(),
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+
+ }
+ EXPECT_EQ("2001:db8:2:ff::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+
+ // Ok, we've iterated over all prefixes in all pools. We now wrap around.
+ // We're looping over now (iterating over first pool again)
+ EXPECT_EQ("2001:db8::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:0:10::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+}
+
+// This test verifies that the iterative allocator can step over addresses.
+TEST_F(IterativeAllocatorTest6, addressIncrease) {
+ NakedIterativeAllocator alloc(Lease::TYPE_NA);
+
+ // Let's pick the first address
+ IOAddress addr1 = alloc.pickAddress(subnet_, cc_, duid_, IOAddress("2001:db8:1::10"));
+
+ // Check that we can indeed pick the first address from the pool
+ EXPECT_EQ("2001:db8:1::10", addr1.toText());
+
+ // Check that addresses can be increased properly
+ checkAddrIncrease(alloc, "2001:db8::9", "2001:db8::a");
+ checkAddrIncrease(alloc, "2001:db8::f", "2001:db8::10");
+ checkAddrIncrease(alloc, "2001:db8::10", "2001:db8::11");
+ checkAddrIncrease(alloc, "2001:db8::ff", "2001:db8::100");
+ checkAddrIncrease(alloc, "2001:db8::ffff", "2001:db8::1:0");
+ checkAddrIncrease(alloc, "::", "::1");
+ checkAddrIncrease(alloc, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", "::");
+}
+
+// This test verifies that the allocator can step over prefixes.
+TEST_F(IterativeAllocatorTest6, prefixIncrease) {
+ NakedIterativeAllocator alloc(Lease::TYPE_PD);
+
+ // For /128 prefix, increasePrefix should work the same as addressIncrease
+ checkPrefixIncrease(alloc, "2001:db8::9", 128, "2001:db8::a");
+ checkPrefixIncrease(alloc, "2001:db8::f", 128, "2001:db8::10");
+ checkPrefixIncrease(alloc, "2001:db8::10", 128, "2001:db8::11");
+ checkPrefixIncrease(alloc, "2001:db8::ff", 128, "2001:db8::100");
+ checkPrefixIncrease(alloc, "2001:db8::ffff", 128, "2001:db8::1:0");
+ checkPrefixIncrease(alloc, "::", 128, "::1");
+ checkPrefixIncrease(alloc, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", 128, "::");
+
+ // Check that /64 prefixes can be generated
+ checkPrefixIncrease(alloc, "2001:db8::", 64, "2001:db8:0:1::");
+
+ // Check that prefix length not divisible by 8 are working
+ checkPrefixIncrease(alloc, "2001:db8::", 128, "2001:db8::1");
+ checkPrefixIncrease(alloc, "2001:db8::", 127, "2001:db8::2");
+ checkPrefixIncrease(alloc, "2001:db8::", 126, "2001:db8::4");
+ checkPrefixIncrease(alloc, "2001:db8::", 125, "2001:db8::8");
+ checkPrefixIncrease(alloc, "2001:db8::", 124, "2001:db8::10");
+ checkPrefixIncrease(alloc, "2001:db8::", 123, "2001:db8::20");
+ checkPrefixIncrease(alloc, "2001:db8::", 122, "2001:db8::40");
+ checkPrefixIncrease(alloc, "2001:db8::", 121, "2001:db8::80");
+ checkPrefixIncrease(alloc, "2001:db8::", 120, "2001:db8::100");
+
+ // These are not really useful cases, because there are bits set
+ // int the last (128 - prefix_len) bits. Nevertheless, it shows
+ // that the algorithm is working even in such cases
+ checkPrefixIncrease(alloc, "2001:db8::1", 128, "2001:db8::2");
+ checkPrefixIncrease(alloc, "2001:db8::1", 127, "2001:db8::3");
+ checkPrefixIncrease(alloc, "2001:db8::1", 126, "2001:db8::5");
+ checkPrefixIncrease(alloc, "2001:db8::1", 125, "2001:db8::9");
+ checkPrefixIncrease(alloc, "2001:db8::1", 124, "2001:db8::11");
+ checkPrefixIncrease(alloc, "2001:db8::1", 123, "2001:db8::21");
+ checkPrefixIncrease(alloc, "2001:db8::1", 122, "2001:db8::41");
+ checkPrefixIncrease(alloc, "2001:db8::1", 121, "2001:db8::81");
+ checkPrefixIncrease(alloc, "2001:db8::1", 120, "2001:db8::101");
+
+ // Let's try out couple real life scenarios
+ checkPrefixIncrease(alloc, "2001:db8:1:abcd::", 64, "2001:db8:1:abce::");
+ checkPrefixIncrease(alloc, "2001:db8:1:abcd::", 60, "2001:db8:1:abdd::");
+ checkPrefixIncrease(alloc, "2001:db8:1:abcd::", 56, "2001:db8:1:accd::");
+ checkPrefixIncrease(alloc, "2001:db8:1:abcd::", 52, "2001:db8:1:bbcd::");
+
+ // And now let's try something over the top
+ checkPrefixIncrease(alloc, "::", 1, "8000::");
+}
+
+// This test verifies that the iterative allocator really walks over all addresses
+// in all pools in specified subnet. It also must not pick the same address twice
+// unless it runs out of pool space and must start over.
+TEST_F(IterativeAllocatorTest6, manyPools) {
+ NakedIterativeAllocator alloc(Lease::TYPE_NA);
+
+ // let's start from 2, as there is 2001:db8:1::10 - 2001:db8:1::20 pool already.
+ for (int i = 2; i < 10; ++i) {
+ stringstream min, max;
+
+ min << "2001:db8:1::" << hex << i*16 + 1;
+ max << "2001:db8:1::" << hex << i*16 + 9;
+
+ Pool6Ptr pool(new Pool6(Lease::TYPE_NA, IOAddress(min.str()),
+ IOAddress(max.str())));
+ subnet_->addPool(pool);
+ }
+
+ int total = 17 + 8 * 9; // First pool (::10 - ::20) has 17 addresses in it,
+ // there are 8 extra pools with 9 addresses in each.
+
+ // Let's keep picked addresses here and check their uniqueness.
+ std::set<IOAddress> generated_addrs;
+ int cnt = 0;
+ while (++cnt) {
+ IOAddress candidate = alloc.pickAddress(subnet_, cc_,
+ duid_, IOAddress("::"));
+ EXPECT_TRUE(subnet_->inPool(Lease::TYPE_NA, candidate));
+
+ // One way to easily verify that the iterative allocator really works is
+ // to uncomment the following line and observe its output that it
+ // covers all defined pools.
+ // cout << candidate.toText() << endl;
+
+ if (generated_addrs.find(candidate) == generated_addrs.end()) {
+ // We haven't had this.
+ generated_addrs.insert(candidate);
+ } else {
+ // We have seen this address before. That should mean that we
+ // iterated over all addresses.
+ if (generated_addrs.size() == total) {
+ // We have exactly the number of address in all pools.
+ break;
+ }
+ ADD_FAILURE() << "Too many or not enough unique addresses generated.";
+ break;
+ }
+
+ if ( cnt>total ) {
+ ADD_FAILURE() << "Too many unique addresses generated.";
+ break;
+ }
+ }
+}
+
+} // end of namespace isc::dhcp::test
+} // end of namespace isc::dhcp
+} // end of namespace isc \ No newline at end of file