]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#1415] Implmented address range permutation
authorMarcin Siodelski <marcin@isc.org>
Fri, 11 Sep 2020 15:05:38 +0000 (17:05 +0200)
committerMarcin Siodelski <marcin@isc.org>
Wed, 16 Sep 2020 14:39:12 +0000 (14:39 +0000)
src/lib/dhcpsrv/Makefile.am
src/lib/dhcpsrv/address_range_permutation.cc [new file with mode: 0644]
src/lib/dhcpsrv/address_range_permutation.h [new file with mode: 0644]
src/lib/dhcpsrv/tests/Makefile.am
src/lib/dhcpsrv/tests/address_range_permutation_unittest.cc [new file with mode: 0644]

index d6bac2f0739cf911ca376e6af66058402842736a..181a64a60606f8807c39bb9d6b0d5273961ecaef 100644 (file)
@@ -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 (file)
index 0000000..7c3d78b
--- /dev/null
@@ -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
diff --git a/src/lib/dhcpsrv/address_range_permutation.h b/src/lib/dhcpsrv/address_range_permutation.h
new file mode 100644 (file)
index 0000000..af77153
--- /dev/null
@@ -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
index c16af4d10edf8b0830aa70e53611f79b9baf1ebd..359967ab59eaecac0dc995c1a10555cd8c63310e 100644 (file)
@@ -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 (file)
index 0000000..2cd010f
--- /dev/null
@@ -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