]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#1415] Permutation of delegated prefixes
authorMarcin Siodelski <marcin@isc.org>
Tue, 15 Sep 2020 09:56:34 +0000 (11:56 +0200)
committerMarcin Siodelski <marcin@isc.org>
Wed, 16 Sep 2020 14:39:12 +0000 (14:39 +0000)
Extended IPRangePermutation to shuffle delegated prefixes.

src/lib/dhcpsrv/ip_range.cc
src/lib/dhcpsrv/ip_range.h
src/lib/dhcpsrv/ip_range_permutation.cc
src/lib/dhcpsrv/ip_range_permutation.h
src/lib/dhcpsrv/tests/ip_range_permutation_unittest.cc

index 39d409241ee501be319b84795f2e110030ec17e5..5d32ef4e3010a87740cd5a2289457c0b2933f91f 100644 (file)
@@ -29,28 +29,29 @@ AddressRange::AddressRange(const IOAddress& start, const IOAddress& end)
 }
 
 PrefixRange::PrefixRange(const asiolink::IOAddress& prefix, const uint8_t length, const uint8_t delegated)
-    : start_(prefix), end_(IOAddress::IPV6_ZERO_ADDRESS()), delegated_length_(delegated) {
+    : start_(prefix), end_(IOAddress::IPV6_ZERO_ADDRESS()), prefix_length_(length),
+      delegated_length_(delegated) {
     if (!start_.isV6()) {
         isc_throw(BadValue, "IPv6 prefix required for prefix delegation range but "
                   << start_ << " was specified");
     }
-    if (delegated_length_ < length) {
+    if (delegated_length_ < prefix_length_) {
         isc_throw(BadValue, "delegated length " << static_cast<int>(delegated_length_)
                   << " must not be lower than prefix length " << static_cast<int>(length));
     }
-    if ((length > 128) || (delegated_length_ > 128)) {
+    if ((prefix_length_ > 128) || (delegated_length_ > 128)) {
         isc_throw(BadValue, "delegated length " << static_cast<int>(delegated_length_)
                   << " and prefix length " << static_cast<int>(length)
                   << " must not be greater than 128");
     }
-    auto prefixes_num = prefixesInRange(length, delegated_length_);
+    auto prefixes_num = prefixesInRange(prefix_length_, delegated_length_);
     uint64_t addrs_in_prefix = static_cast<uint64_t>(1) << (128 - delegated_length_);
     end_ = offsetAddress(prefix, (prefixes_num - 1) * addrs_in_prefix);
 }
 
 PrefixRange::PrefixRange(const asiolink::IOAddress& start, const asiolink::IOAddress& end,
                          const uint8_t delegated)
-    : start_(start), end_(end), delegated_length_(delegated) {
+    : start_(start), end_(end), prefix_length_(0), delegated_length_(delegated) {
     if (!start_.isV6() || !end_.isV6()) {
         isc_throw(BadValue, "IPv6 prefix required for prefix delegation range but "
                   << start_ << ":" << end_ << " was specified");
index a25f7d72196106b8299d6a8de4fd4b53dc2b3a0c..dd40fc1ecd8814c380995e80942a1bc4d285f060 100644 (file)
@@ -35,6 +35,8 @@ struct PrefixRange {
     /// IP address denoting the first address within the last prefix
     /// in the prefix range.
     asiolink::IOAddress end_;
+    /// Prefix length.
+    uint8_t prefix_length_;
     /// Delegated prefix length.
     uint8_t delegated_length_;
 
index b5111a6544ecfcd1e287fbc8ed877b811dc6a7a0..89bf400b7657d94aabd2a27b785e278b46bbb613 100644 (file)
@@ -8,25 +8,33 @@
 #include <asiolink/addr_utilities.h>
 #include <dhcpsrv/ip_range_permutation.h>
 
+#include <iostream>
+
 using namespace isc::asiolink;
 
 namespace isc {
 namespace dhcp {
 
 IPRangePermutation::IPRangePermutation(const IPRangePermutation::Range& range)
-    : range_(range), cursor_(addrsInRange(range_.start_, range_.end_) - 1),
+    : range_start_(range.start_), step_(1), cursor_(addrsInRange(range_start_, range.end_) - 1),
       state_(), done_(false), generator_() {
     std::random_device rd;
     generator_.seed(rd());
 }
 
+IPRangePermutation::IPRangePermutation(const IPRangePermutation::PrefixRange& range)
+    : range_start_(range.start_), step_(static_cast<uint64_t>(1) << (128 - range.delegated_length_)),
+      cursor_(prefixesInRange(range.prefix_length_, range.delegated_length_) - 1),
+      state_(), done_(false), generator_() {
+}
+
 IOAddress
 IPRangePermutation::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());
+        return (range_start_.isV4() ? IOAddress::IPV4_ZERO_ADDRESS() : IOAddress::IPV6_ZERO_ADDRESS());
     }
 
     // If there is one address left, return this address.
@@ -60,7 +68,7 @@ IPRangePermutation::next(bool& done) {
         // 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);
+        next_loc_address = offsetAddress(range_start_, next_loc * step_);
     }
 
     // Let's get the address at cursor position in the same way.
@@ -69,7 +77,7 @@ IPRangePermutation::next(bool& done) {
     if (cursor_existing != state_.end()) {
         cursor_address = cursor_existing->second;
     } else {
-        cursor_address = offsetAddress(range_.start_, cursor_);
+        cursor_address = offsetAddress(range_start_, cursor_ * step_);
     }
 
     // Now we swap them.... in fact we don't swap because as an optimization
index 77d2984a3cfe80767f831df14e853733daf67c20..5eaa6b9d9358a19643e8c25211a933a52653f8ac 100644 (file)
@@ -20,24 +20,24 @@ namespace dhcp {
 
 /// @brief Random IP address/prefix 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.
+/// This class is used to shuffle IP addresses or delegated prefixes within
+/// the specified 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:
+/// The class assumes that initially the IP addresses or delegated prefixes
+/// 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
@@ -57,58 +57,73 @@ namespace dhcp {
 /// 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.
+/// This algorithm guarantees that all IP addresses or delegated prefixes
+/// beloging to the given  range are returned and no duplicates are returned.
+/// The addresses or delegated prefixes are returned in a random order.
 class IPRangePermutation {
 public:
 
     /// Address range.
     typedef AddressRange Range;
 
-    /// @brief Constructor.
+    /// Prefix range.
+    typedef PrefixRange PrefixRange;
+
+    /// @brief Constructor for address ranges.
     ///
     /// @param range address range for which the permutation will be generated.
     IPRangePermutation(const Range& range);
 
-    /// @brief Checks if the address range has been exhausted.
+    /// @brief Constructor for prefix ranges.
+    ///
+    /// @param range range of delegated prefixes for which the permutation will
+    /// be generated.
+    IPRangePermutation(const PrefixRange& range);
+
+    /// @brief Checks if the range has been exhausted.
     ///
-    /// @return false if the algorithm went over all addresses in the
-    /// range, true otherwise.
+    /// @return false if the algorithm went over all addresses or prefixes in
+    /// the range, true otherwise.
     bool exhausted() const {
         return (done_);
     }
 
-    /// @brief Returns next random address from the permutation.
+    /// @brief Returns next random address or prefix 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.
+    /// This method returns all addresses or prefixes belonging to the specified
+    /// range in random order. For the first number of calls equal to the size of
+    /// the 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.
+    /// or prefixes can be returned for this permutation.
+    /// @return next available IP address or prefix. It returns IPv4 zero or IPv6
+    /// zero address after this method walked over all available IP addresses or
+    /// prefixes in the range.
     asiolink::IOAddress next(bool& done);
 
 private:
 
-    /// Address range used in this permutation and specified in the
-    /// constructor.
-    Range range_;
+    /// Beginning of the range.
+    asiolink::IOAddress range_start_;
+
+    /// Distance between two neighboring addresses or delegated prefixes,
+    /// i.e. 1 for address range and delegated prefix size for delegated
+    /// prefixes.
+    uint64_t step_;
 
-    /// 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.
+    /// Keeps the position of the next address or prefix to be swapped with
+    /// a randomly picked address or prefix from the range of 0..cursor-1. The
+    /// cursor value is decreased every time a new IP address or prefix
+    /// is returned.
     uint64_t cursor_;
 
     /// Keeps the current permutation state. The state associates the
-    /// swapped IP addresses with their positions in the permutation.
+    /// swapped IP addresses or delegated prefixes with their positions in
+    /// the permutation.
     std::map<uint64_t, asiolink::IOAddress> state_;
 
-    /// Indicates if the addresses are exhausted.
+    /// Indicates if the addresses or delegated prefixes are exhausted.
     bool done_;
 
     /// Random generator.
@@ -121,4 +136,4 @@ typedef boost::shared_ptr<IPRangePermutation> IPRangePermutationPtr;
 } // end of namespace isc::dhcp
 } // end of namespace isc
 
-#endif // ADDRESS_RANGE_PERMUTATION_H
+#endif // IP_RANGE_PERMUTATION_H
index 248dd4c41ffbda5532c56ffbdd155b8614521d16..a5b059a094e199cccebd2d3803956aa15afe858e 100644 (file)
@@ -64,11 +64,11 @@ TEST(IPRangePermutationTest, ipv4) {
     EXPECT_TRUE(addrs.begin()->isV4Zero());
 }
 
-// This test verifies that a permutation of IPv4 address range can
+// This test verifies that a permutation of IPv6 address range can
 // be generated.
 TEST(IPRangePermutationTest, ipv6) {
     IPRangePermutation::Range range(IOAddress("2001:db8:1::1:fea0"),
-                                         IOAddress("2001:db8:1::2:abcd"));
+                                    IOAddress("2001:db8:1::2:abcd"));
     IPRangePermutation perm(range);
 
     std::set<IOAddress> addrs;
@@ -92,4 +92,32 @@ TEST(IPRangePermutationTest, ipv6) {
     EXPECT_TRUE(addrs.begin()->isV6Zero());
 }
 
+// This test verifies that a permutation of delegated prefixes can be
+// generated.
+TEST(IPRangePermutationTest, pd) {
+    IPRangePermutation::PrefixRange range(IOAddress("3000::"), 112, 120);
+    IPRangePermutation perm(range);
+
+    std::set<IOAddress> addrs;
+    bool done = false;
+    for (auto i = 0; i < 257; ++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 257 unique addresses, including the zero address.
+    EXPECT_EQ(257, addrs.size());
+    EXPECT_TRUE(addrs.begin()->isV6Zero());
+}
+
 } // end of anonymous namespace