From: Marcin Siodelski Date: Fri, 11 Sep 2020 15:05:38 +0000 (+0200) Subject: [#1415] Implmented address range permutation X-Git-Tag: Kea-1.9.0~117 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=8c54ebbeffa2390b45c7adac9444ec3e41e92c8e;p=thirdparty%2Fkea.git [#1415] Implmented address range permutation --- diff --git a/src/lib/dhcpsrv/Makefile.am b/src/lib/dhcpsrv/Makefile.am index d6bac2f073..181a64a606 100644 --- a/src/lib/dhcpsrv/Makefile.am +++ b/src/lib/dhcpsrv/Makefile.am @@ -64,6 +64,7 @@ CLEANFILES += *.csv lib_LTLIBRARIES = libkea-dhcpsrv.la libkea_dhcpsrv_la_SOURCES = 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_log.cc alloc_engine_log.h 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. libkea_dhcpsrv_includedir = $(pkgincludedir)/dhcpsrv libkea_dhcpsrv_include_HEADERS = \ + address_range.h \ + address_range_permutation.h \ alloc_engine.h \ alloc_engine_log.h \ alloc_engine_messages.h \ @@ -342,6 +345,7 @@ libkea_dhcpsrv_include_HEADERS = \ db_type.h \ dhcp4o6_ipc.h \ dhcpsrv_log.h \ + free_lease_queue.h \ host.h \ host_container.h \ host_data_source_factory.h \ diff --git a/src/lib/dhcpsrv/address_range_permutation.cc b/src/lib/dhcpsrv/address_range_permutation.cc new file mode 100644 index 0000000000..7c3d78bb29 --- /dev/null +++ b/src/lib/dhcpsrv/address_range_permutation.cc @@ -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 +#include +#include + +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 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 diff --git a/src/lib/dhcpsrv/address_range_permutation.h b/src/lib/dhcpsrv/address_range_permutation.h new file mode 100644 index 0000000000..af77153916 --- /dev/null +++ b/src/lib/dhcpsrv/address_range_permutation.h @@ -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 +#include + +#include +#include + +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 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 diff --git a/src/lib/dhcpsrv/tests/Makefile.am b/src/lib/dhcpsrv/tests/Makefile.am index c16af4d10e..359967ab59 100644 --- a/src/lib/dhcpsrv/tests/Makefile.am +++ b/src/lib/dhcpsrv/tests/Makefile.am @@ -58,6 +58,7 @@ TESTS += libdhcpsrv_unittests libdhcpsrv_unittests_SOURCES = run_unittests.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_expiration_unittest.cc libdhcpsrv_unittests_SOURCES += alloc_engine_hooks_unittest.cc diff --git a/src/lib/dhcpsrv/tests/address_range_permutation_unittest.cc b/src/lib/dhcpsrv/tests/address_range_permutation_unittest.cc new file mode 100644 index 0000000000..2cd010fbee --- /dev/null +++ b/src/lib/dhcpsrv/tests/address_range_permutation_unittest.cc @@ -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 +#include + +#include + +#include + +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 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 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