mirror of
https://gitlab.isc.org/isc-projects/kea
synced 2025-09-01 06:25:34 +00:00
[#1415] Implmented address range permutation
This commit is contained in:
@@ -64,6 +64,7 @@ CLEANFILES += *.csv
|
|||||||
lib_LTLIBRARIES = libkea-dhcpsrv.la
|
lib_LTLIBRARIES = libkea-dhcpsrv.la
|
||||||
libkea_dhcpsrv_la_SOURCES =
|
libkea_dhcpsrv_la_SOURCES =
|
||||||
libkea_dhcpsrv_la_SOURCES += address_range.h address_range.cc
|
libkea_dhcpsrv_la_SOURCES += address_range.h address_range.cc
|
||||||
|
libkea_dhcpsrv_la_SOURCES += address_range_permutation.h address_range_permutation.cc
|
||||||
libkea_dhcpsrv_la_SOURCES += alloc_engine.cc alloc_engine.h
|
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_log.cc alloc_engine_log.h
|
||||||
libkea_dhcpsrv_la_SOURCES += alloc_engine_messages.h alloc_engine_messages.cc
|
libkea_dhcpsrv_la_SOURCES += alloc_engine_messages.h alloc_engine_messages.cc
|
||||||
@@ -299,6 +300,8 @@ EXTRA_DIST += database_backends.dox libdhcpsrv.dox
|
|||||||
# Specify the headers for copying into the installation directory tree.
|
# Specify the headers for copying into the installation directory tree.
|
||||||
libkea_dhcpsrv_includedir = $(pkgincludedir)/dhcpsrv
|
libkea_dhcpsrv_includedir = $(pkgincludedir)/dhcpsrv
|
||||||
libkea_dhcpsrv_include_HEADERS = \
|
libkea_dhcpsrv_include_HEADERS = \
|
||||||
|
address_range.h \
|
||||||
|
address_range_permutation.h \
|
||||||
alloc_engine.h \
|
alloc_engine.h \
|
||||||
alloc_engine_log.h \
|
alloc_engine_log.h \
|
||||||
alloc_engine_messages.h \
|
alloc_engine_messages.h \
|
||||||
@@ -342,6 +345,7 @@ libkea_dhcpsrv_include_HEADERS = \
|
|||||||
db_type.h \
|
db_type.h \
|
||||||
dhcp4o6_ipc.h \
|
dhcp4o6_ipc.h \
|
||||||
dhcpsrv_log.h \
|
dhcpsrv_log.h \
|
||||||
|
free_lease_queue.h \
|
||||||
host.h \
|
host.h \
|
||||||
host_container.h \
|
host_container.h \
|
||||||
host_data_source_factory.h \
|
host_data_source_factory.h \
|
||||||
|
93
src/lib/dhcpsrv/address_range_permutation.cc
Normal file
93
src/lib/dhcpsrv/address_range_permutation.cc
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
// Copyright (C) 2020 Internet Systems Consortium, Inc. ("ISC")
|
||||||
|
//
|
||||||
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
#include <config.h>
|
||||||
|
#include <asiolink/addr_utilities.h>
|
||||||
|
#include <dhcpsrv/address_range_permutation.h>
|
||||||
|
|
||||||
|
using namespace isc::asiolink;
|
||||||
|
|
||||||
|
namespace isc {
|
||||||
|
namespace dhcp {
|
||||||
|
|
||||||
|
AddressRangePermutation::AddressRangePermutation(const AddressRangePermutation::Range& range)
|
||||||
|
: range_(range), cursor_(addrsInRange(range_.start_, range_.end_) - 1),
|
||||||
|
state_(), done_(false), generator_() {
|
||||||
|
std::random_device rd;
|
||||||
|
generator_.seed(rd());
|
||||||
|
}
|
||||||
|
|
||||||
|
IOAddress
|
||||||
|
AddressRangePermutation::next(bool& done) {
|
||||||
|
// If we're done iterating over the pool let's return zero address and
|
||||||
|
// set the user supplied done flag to true.
|
||||||
|
if (done_) {
|
||||||
|
done = true;
|
||||||
|
return (range_.start_.isV4() ? IOAddress::IPV4_ZERO_ADDRESS() : IOAddress::IPV6_ZERO_ADDRESS());
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there is one address left, return this address.
|
||||||
|
if (cursor_ == 0) {
|
||||||
|
done = done_ = true;
|
||||||
|
return (state_.at(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
// We're not done.
|
||||||
|
done = false;
|
||||||
|
|
||||||
|
// The cursor indicates where we're in the range starting from its end. The
|
||||||
|
// addresses between the cursor and the end of the range have been already
|
||||||
|
// returned by this function. Therefore we focus on the remaining cursor-1
|
||||||
|
// addresses. Let's get random address from this sub-range.
|
||||||
|
std::uniform_int_distribution<int> dist(0, cursor_ - 1);
|
||||||
|
auto next_loc = dist(generator_);
|
||||||
|
|
||||||
|
IOAddress next_loc_address = IOAddress::IPV4_ZERO_ADDRESS();
|
||||||
|
|
||||||
|
// Check if whether this address exists in our map or not. If it exists
|
||||||
|
// it means it was swapped with some other address in previous calls to
|
||||||
|
// this function.
|
||||||
|
auto next_loc_existing = state_.find(next_loc);
|
||||||
|
if (next_loc_existing != state_.end()) {
|
||||||
|
// Address exists, so let's record it.
|
||||||
|
next_loc_address = next_loc_existing->second;
|
||||||
|
} else {
|
||||||
|
// Address does not exist on this position. We infer this address from
|
||||||
|
// its position by advancing the range start by position. For example,
|
||||||
|
// if the range is 192.0.2.1-192.0.2.10 and the picked random position is
|
||||||
|
// 5, the address we get is 192.0.2.6. This random address will be later
|
||||||
|
// returned to the caller.
|
||||||
|
next_loc_address = offsetAddress(range_.start_, next_loc);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Let's get the address at cursor position in the same way.
|
||||||
|
IOAddress cursor_address = IOAddress::IPV4_ZERO_ADDRESS();
|
||||||
|
auto cursor_existing = state_.find(cursor_);
|
||||||
|
if (cursor_existing != state_.end()) {
|
||||||
|
cursor_address = cursor_existing->second;
|
||||||
|
} else {
|
||||||
|
cursor_address = offsetAddress(range_.start_, cursor_);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now we swap them.... in fact we don't swap because as an optimization
|
||||||
|
// we don't record the addresses we returned by this function. We merely
|
||||||
|
// replace the address at random position with the address from cursor
|
||||||
|
// position. This address will be returned in the future if we get back
|
||||||
|
// to this position as a result of randomization.
|
||||||
|
if (next_loc_existing == state_.end()) {
|
||||||
|
state_.insert(std::make_pair(next_loc, cursor_address));
|
||||||
|
} else {
|
||||||
|
state_.at(next_loc) = cursor_address;
|
||||||
|
}
|
||||||
|
// Move the cursor one position backwards.
|
||||||
|
--cursor_;
|
||||||
|
|
||||||
|
// Return the address from the random position.
|
||||||
|
return (next_loc_address);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // end of namespace isc::dhcp
|
||||||
|
} // end of namespace isc
|
119
src/lib/dhcpsrv/address_range_permutation.h
Normal file
119
src/lib/dhcpsrv/address_range_permutation.h
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
// Copyright (C) 2020 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 ADDRESS_RANGE_PERMUTATION_H
|
||||||
|
#define ADDRESS_RANGE_PERMUTATION_H
|
||||||
|
|
||||||
|
#include <asiolink/io_address.h>
|
||||||
|
#include <dhcpsrv/address_range.h>
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
#include <random>
|
||||||
|
|
||||||
|
namespace isc {
|
||||||
|
namespace dhcp {
|
||||||
|
|
||||||
|
/// @brief Random IP address permutation based on Fisher-Yates shuffle.
|
||||||
|
///
|
||||||
|
/// This class is used to shuffle IP addresses within the specified address
|
||||||
|
/// range. It is following the Fisher-Yates shuffle algorithm described in
|
||||||
|
/// https://en.wikipedia.org/wiki/Fisher–Yates_shuffle.
|
||||||
|
///
|
||||||
|
/// The original algorithm is modified to keep the minimal information about
|
||||||
|
/// the current state of the permutation and relies on the caller to collect
|
||||||
|
/// and store the next available value. In other words, the generated and
|
||||||
|
/// already returned random values are not stored by this class.
|
||||||
|
///
|
||||||
|
/// The class assumes that initially the IP addresses in the specified range
|
||||||
|
/// are in increasing order. Suppose we're dealing with the following address
|
||||||
|
/// range: 192.0.2.1-192.0.2.5. Therefore our addresses are initially ordered
|
||||||
|
/// like this: a[0]=192.0.2.1, a[1]=192.0.2.2 ..., a[4]=192.0.2.5. The
|
||||||
|
/// algorithm starts from the end of that range, i.e. i=4, so a[i]=192.0.2.5.
|
||||||
|
/// A random value from the range of [0..i-1] is picked, i.e. a value from the
|
||||||
|
/// range of [0..3]. Let's say it is 1. This value initially corresponds to the
|
||||||
|
/// address a[1]=192.0.2.2. In the original algorithm the value of a[1] is
|
||||||
|
/// swapped with a[4], yelding the following partial permutation:
|
||||||
|
/// 192.0.2.1, 192.0.2.5, 192.0.2.3, 192.0.2.4, 192.0.2.2. In our case, we simply
|
||||||
|
/// return the value of 192.0.2.2 to the caller and remember that
|
||||||
|
/// a[1]=192.0.2.5. At this point we don't store the values of a[0], a[2] and
|
||||||
|
/// a[3] because the corresponding IP addresses can be calculated from the
|
||||||
|
/// range start and their index in the permutation. The value of a[1] must be
|
||||||
|
/// stored because it has been swapped with a[4] and can't be calculated from
|
||||||
|
/// the position index.
|
||||||
|
///
|
||||||
|
/// In the next step, the current index i (cursor value) is decreased by one.
|
||||||
|
/// It now has the value of 3. Again, a random index is picked from the range
|
||||||
|
/// of [0..3]. Note that it can be the same or different index than selected
|
||||||
|
/// in the previous step. Let's assume it is 0. This corresponds to the address
|
||||||
|
/// of 192.0.2.1. This address will be returned to the caller. The value of
|
||||||
|
/// a[3]=192.0.2.4 is moved to a[0]. This yelds the following permutation:
|
||||||
|
/// 192.0.2.4, 192.0.2.5, 192.0.2.3, 192.0.2.1, 192.0.2.2. However, we only
|
||||||
|
/// remember a[0] and a[1]. The a[3] can be still computed from the range
|
||||||
|
/// start and the position. The other two have been already returned to the
|
||||||
|
/// caller so we forget them.
|
||||||
|
///
|
||||||
|
/// This algorithm guarantees that all IP addresses beloging to the given
|
||||||
|
/// address range are returned and no duplicates are returned. The addresses
|
||||||
|
/// are returned in a random order.
|
||||||
|
class AddressRangePermutation {
|
||||||
|
public:
|
||||||
|
|
||||||
|
/// Address range.
|
||||||
|
typedef AddressRange Range;
|
||||||
|
|
||||||
|
/// @brief Constructor.
|
||||||
|
///
|
||||||
|
/// @param range address range for which the permutation will be generated.
|
||||||
|
AddressRangePermutation(const Range& range);
|
||||||
|
|
||||||
|
/// @brief Checks if the address range has been exhausted.
|
||||||
|
///
|
||||||
|
/// @return false if the algorithm went over all addresses in the
|
||||||
|
/// range, true otherwise.
|
||||||
|
bool exhausted() const {
|
||||||
|
return (done_);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @brief Returns next random address from the permutation.
|
||||||
|
///
|
||||||
|
/// This method will returns all addresses belonging to the specified
|
||||||
|
/// address range in random order. For the first number of calls equal
|
||||||
|
/// to the size of the address range it guarantees to return a non-zero
|
||||||
|
/// IP address from that range without duplicates.
|
||||||
|
///
|
||||||
|
/// @param [out] done this parameter is set to true if no more addresses
|
||||||
|
/// can be returned for this permutation.
|
||||||
|
/// @return next available IP address. It returns IPv4 zero or IPv6 zero
|
||||||
|
/// address after this method walked over all available IP addresses in
|
||||||
|
/// the range.
|
||||||
|
asiolink::IOAddress next(bool& done);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
/// Address range used in this permutation and specified in the
|
||||||
|
/// constructor.
|
||||||
|
Range range_;
|
||||||
|
|
||||||
|
/// Keeps the possition of the next address to be swapped with a
|
||||||
|
/// randomly picked address from the range of 0..cursor-1. The
|
||||||
|
/// cursor value is decreased every time a new IP address is returned.
|
||||||
|
uint64_t cursor_;
|
||||||
|
|
||||||
|
/// Keeps the current permutation state. The state associates the
|
||||||
|
/// swapped IP addresses with their positions in the permutation.
|
||||||
|
std::map<uint64_t, asiolink::IOAddress> state_;
|
||||||
|
|
||||||
|
/// Indicates if the addresses are exhausted.
|
||||||
|
bool done_;
|
||||||
|
|
||||||
|
/// Random generator.
|
||||||
|
std::mt19937 generator_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // end of namespace isc::dhcp
|
||||||
|
} // end of namespace isc
|
||||||
|
|
||||||
|
#endif // ADDRESS_RANGE_PERMUTATION_H
|
@@ -58,6 +58,7 @@ TESTS += libdhcpsrv_unittests
|
|||||||
|
|
||||||
libdhcpsrv_unittests_SOURCES = run_unittests.cc
|
libdhcpsrv_unittests_SOURCES = run_unittests.cc
|
||||||
libdhcpsrv_unittests_SOURCES += address_range_unittest.cc
|
libdhcpsrv_unittests_SOURCES += address_range_unittest.cc
|
||||||
|
libdhcpsrv_unittests_SOURCES += address_range_permutation_unittest.cc
|
||||||
libdhcpsrv_unittests_SOURCES += alloc_engine_utils.cc alloc_engine_utils.h
|
libdhcpsrv_unittests_SOURCES += alloc_engine_utils.cc alloc_engine_utils.h
|
||||||
libdhcpsrv_unittests_SOURCES += alloc_engine_expiration_unittest.cc
|
libdhcpsrv_unittests_SOURCES += alloc_engine_expiration_unittest.cc
|
||||||
libdhcpsrv_unittests_SOURCES += alloc_engine_hooks_unittest.cc
|
libdhcpsrv_unittests_SOURCES += alloc_engine_hooks_unittest.cc
|
||||||
|
95
src/lib/dhcpsrv/tests/address_range_permutation_unittest.cc
Normal file
95
src/lib/dhcpsrv/tests/address_range_permutation_unittest.cc
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
// Copyright (C) 2020 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/address_range_permutation.h>
|
||||||
|
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
#include <set>
|
||||||
|
|
||||||
|
using namespace isc;
|
||||||
|
using namespace isc::asiolink;
|
||||||
|
using namespace isc::dhcp;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
// This test verifies that the object can be successfully constructed for
|
||||||
|
// both IPv4 and IPv6 address range.
|
||||||
|
TEST(AddressRangePermutationTest, constructor) {
|
||||||
|
ASSERT_NO_THROW({
|
||||||
|
AddressRangePermutation::Range range(IOAddress("192.0.2.10"), IOAddress("192.0.2.100"));
|
||||||
|
AddressRangePermutation perm(range);
|
||||||
|
});
|
||||||
|
ASSERT_NO_THROW({
|
||||||
|
AddressRangePermutation::Range range(IOAddress("3000::"), IOAddress("3000::10"));
|
||||||
|
AddressRangePermutation perm(range);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// This test verifies that a permutation of IPv4 address range can
|
||||||
|
// be generated.
|
||||||
|
TEST(AddressRangePermutationTest, ipv4) {
|
||||||
|
// Create address range with 91 addresses.
|
||||||
|
AddressRangePermutation::Range range(IOAddress("192.0.2.10"), IOAddress("192.0.2.100"));
|
||||||
|
AddressRangePermutation perm(range);
|
||||||
|
|
||||||
|
// This set will record unique IP addresses generated.
|
||||||
|
std::set<IOAddress> addrs;
|
||||||
|
bool done = false;
|
||||||
|
|
||||||
|
// Call the next() function 95 tims. The first 91 calls should return non-zero
|
||||||
|
// IP addresses.
|
||||||
|
for (auto i = 0; i < 95; ++i) {
|
||||||
|
auto next = perm.next(done);
|
||||||
|
if (!next.isV4Zero()) {
|
||||||
|
// Make sure the returned address is within the range.
|
||||||
|
EXPECT_LE(range.start_, next);
|
||||||
|
EXPECT_LE(next, range.end_);
|
||||||
|
} else {
|
||||||
|
// The IPv4 zero address marks the end of the permutation. In this case
|
||||||
|
// the done flag should be set.
|
||||||
|
EXPECT_TRUE(done);
|
||||||
|
EXPECT_TRUE(perm.exhausted());
|
||||||
|
}
|
||||||
|
// Insert the address returned to the set.
|
||||||
|
addrs.insert(next);
|
||||||
|
}
|
||||||
|
|
||||||
|
// We should have recorded 92 unique addresses, including the zero address.
|
||||||
|
EXPECT_EQ(92, addrs.size());
|
||||||
|
EXPECT_TRUE(addrs.begin()->isV4Zero());
|
||||||
|
}
|
||||||
|
|
||||||
|
// This test verifies that a permutation of IPv4 address range can
|
||||||
|
// be generated.
|
||||||
|
TEST(AddressRangePermutationTest, ipv6) {
|
||||||
|
AddressRangePermutation::Range range(IOAddress("2001:db8:1::1:fea0"),
|
||||||
|
IOAddress("2001:db8:1::2:abcd"));
|
||||||
|
AddressRangePermutation perm(range);
|
||||||
|
|
||||||
|
std::set<IOAddress> addrs;
|
||||||
|
bool done = false;
|
||||||
|
for (auto i = 0; i < 44335; ++i) {
|
||||||
|
auto next = perm.next(done);
|
||||||
|
if (!next.isV6Zero()) {
|
||||||
|
// The IPv6 zero address marks the end of the permutation. In this case
|
||||||
|
// the done flag should be set.
|
||||||
|
EXPECT_LE(range.start_, next);
|
||||||
|
EXPECT_LE(next, range.end_);
|
||||||
|
} else {
|
||||||
|
EXPECT_TRUE(done);
|
||||||
|
EXPECT_TRUE(perm.exhausted());
|
||||||
|
}
|
||||||
|
// Insert the address returned to the set.
|
||||||
|
addrs.insert(next);
|
||||||
|
}
|
||||||
|
// We should have recorded 44335 unique addresses, including the zero address.
|
||||||
|
EXPECT_EQ(44335, addrs.size());
|
||||||
|
EXPECT_TRUE(addrs.begin()->isV6Zero());
|
||||||
|
}
|
||||||
|
|
||||||
|
} // end of anonymous namespace
|
Reference in New Issue
Block a user