From: Marcin Siodelski Date: Tue, 15 Sep 2020 09:56:34 +0000 (+0200) Subject: [#1415] Permutation of delegated prefixes X-Git-Tag: Kea-1.9.0~112 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=03330c8a2ec9e9632f2302c7bf500465bd166d27;p=thirdparty%2Fkea.git [#1415] Permutation of delegated prefixes Extended IPRangePermutation to shuffle delegated prefixes. --- diff --git a/src/lib/dhcpsrv/ip_range.cc b/src/lib/dhcpsrv/ip_range.cc index 39d409241e..5d32ef4e30 100644 --- a/src/lib/dhcpsrv/ip_range.cc +++ b/src/lib/dhcpsrv/ip_range.cc @@ -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(delegated_length_) << " must not be lower than prefix length " << static_cast(length)); } - if ((length > 128) || (delegated_length_ > 128)) { + if ((prefix_length_ > 128) || (delegated_length_ > 128)) { isc_throw(BadValue, "delegated length " << static_cast(delegated_length_) << " and prefix length " << static_cast(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(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"); diff --git a/src/lib/dhcpsrv/ip_range.h b/src/lib/dhcpsrv/ip_range.h index a25f7d7219..dd40fc1ecd 100644 --- a/src/lib/dhcpsrv/ip_range.h +++ b/src/lib/dhcpsrv/ip_range.h @@ -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_; diff --git a/src/lib/dhcpsrv/ip_range_permutation.cc b/src/lib/dhcpsrv/ip_range_permutation.cc index b5111a6544..89bf400b76 100644 --- a/src/lib/dhcpsrv/ip_range_permutation.cc +++ b/src/lib/dhcpsrv/ip_range_permutation.cc @@ -8,25 +8,33 @@ #include #include +#include + 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(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 diff --git a/src/lib/dhcpsrv/ip_range_permutation.h b/src/lib/dhcpsrv/ip_range_permutation.h index 77d2984a3c..5eaa6b9d93 100644 --- a/src/lib/dhcpsrv/ip_range_permutation.h +++ b/src/lib/dhcpsrv/ip_range_permutation.h @@ -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 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 IPRangePermutationPtr; } // end of namespace isc::dhcp } // end of namespace isc -#endif // ADDRESS_RANGE_PERMUTATION_H +#endif // IP_RANGE_PERMUTATION_H diff --git a/src/lib/dhcpsrv/tests/ip_range_permutation_unittest.cc b/src/lib/dhcpsrv/tests/ip_range_permutation_unittest.cc index 248dd4c41f..a5b059a094 100644 --- a/src/lib/dhcpsrv/tests/ip_range_permutation_unittest.cc +++ b/src/lib/dhcpsrv/tests/ip_range_permutation_unittest.cc @@ -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 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 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