Skip to content

Commit 03330c8

Browse files
committed
[#1415] Permutation of delegated prefixes
Extended IPRangePermutation to shuffle delegated prefixes.
1 parent b14cbc3 commit 03330c8

File tree

5 files changed

+102
-48
lines changed

5 files changed

+102
-48
lines changed

src/lib/dhcpsrv/ip_range.cc

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,28 +29,29 @@ AddressRange::AddressRange(const IOAddress& start, const IOAddress& end)
2929
}
3030

3131
PrefixRange::PrefixRange(const asiolink::IOAddress& prefix, const uint8_t length, const uint8_t delegated)
32-
: start_(prefix), end_(IOAddress::IPV6_ZERO_ADDRESS()), delegated_length_(delegated) {
32+
: start_(prefix), end_(IOAddress::IPV6_ZERO_ADDRESS()), prefix_length_(length),
33+
delegated_length_(delegated) {
3334
if (!start_.isV6()) {
3435
isc_throw(BadValue, "IPv6 prefix required for prefix delegation range but "
3536
<< start_ << " was specified");
3637
}
37-
if (delegated_length_ < length) {
38+
if (delegated_length_ < prefix_length_) {
3839
isc_throw(BadValue, "delegated length " << static_cast<int>(delegated_length_)
3940
<< " must not be lower than prefix length " << static_cast<int>(length));
4041
}
41-
if ((length > 128) || (delegated_length_ > 128)) {
42+
if ((prefix_length_ > 128) || (delegated_length_ > 128)) {
4243
isc_throw(BadValue, "delegated length " << static_cast<int>(delegated_length_)
4344
<< " and prefix length " << static_cast<int>(length)
4445
<< " must not be greater than 128");
4546
}
46-
auto prefixes_num = prefixesInRange(length, delegated_length_);
47+
auto prefixes_num = prefixesInRange(prefix_length_, delegated_length_);
4748
uint64_t addrs_in_prefix = static_cast<uint64_t>(1) << (128 - delegated_length_);
4849
end_ = offsetAddress(prefix, (prefixes_num - 1) * addrs_in_prefix);
4950
}
5051

5152
PrefixRange::PrefixRange(const asiolink::IOAddress& start, const asiolink::IOAddress& end,
5253
const uint8_t delegated)
53-
: start_(start), end_(end), delegated_length_(delegated) {
54+
: start_(start), end_(end), prefix_length_(0), delegated_length_(delegated) {
5455
if (!start_.isV6() || !end_.isV6()) {
5556
isc_throw(BadValue, "IPv6 prefix required for prefix delegation range but "
5657
<< start_ << ":" << end_ << " was specified");

src/lib/dhcpsrv/ip_range.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ struct PrefixRange {
3535
/// IP address denoting the first address within the last prefix
3636
/// in the prefix range.
3737
asiolink::IOAddress end_;
38+
/// Prefix length.
39+
uint8_t prefix_length_;
3840
/// Delegated prefix length.
3941
uint8_t delegated_length_;
4042

src/lib/dhcpsrv/ip_range_permutation.cc

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,25 +8,33 @@
88
#include <asiolink/addr_utilities.h>
99
#include <dhcpsrv/ip_range_permutation.h>
1010

11+
#include <iostream>
12+
1113
using namespace isc::asiolink;
1214

1315
namespace isc {
1416
namespace dhcp {
1517

1618
IPRangePermutation::IPRangePermutation(const IPRangePermutation::Range& range)
17-
: range_(range), cursor_(addrsInRange(range_.start_, range_.end_) - 1),
19+
: range_start_(range.start_), step_(1), cursor_(addrsInRange(range_start_, range.end_) - 1),
1820
state_(), done_(false), generator_() {
1921
std::random_device rd;
2022
generator_.seed(rd());
2123
}
2224

25+
IPRangePermutation::IPRangePermutation(const IPRangePermutation::PrefixRange& range)
26+
: range_start_(range.start_), step_(static_cast<uint64_t>(1) << (128 - range.delegated_length_)),
27+
cursor_(prefixesInRange(range.prefix_length_, range.delegated_length_) - 1),
28+
state_(), done_(false), generator_() {
29+
}
30+
2331
IOAddress
2432
IPRangePermutation::next(bool& done) {
2533
// If we're done iterating over the pool let's return zero address and
2634
// set the user supplied done flag to true.
2735
if (done_) {
2836
done = true;
29-
return (range_.start_.isV4() ? IOAddress::IPV4_ZERO_ADDRESS() : IOAddress::IPV6_ZERO_ADDRESS());
37+
return (range_start_.isV4() ? IOAddress::IPV4_ZERO_ADDRESS() : IOAddress::IPV6_ZERO_ADDRESS());
3038
}
3139

3240
// If there is one address left, return this address.
@@ -60,7 +68,7 @@ IPRangePermutation::next(bool& done) {
6068
// if the range is 192.0.2.1-192.0.2.10 and the picked random position is
6169
// 5, the address we get is 192.0.2.6. This random address will be later
6270
// returned to the caller.
63-
next_loc_address = offsetAddress(range_.start_, next_loc);
71+
next_loc_address = offsetAddress(range_start_, next_loc * step_);
6472
}
6573

6674
// Let's get the address at cursor position in the same way.
@@ -69,7 +77,7 @@ IPRangePermutation::next(bool& done) {
6977
if (cursor_existing != state_.end()) {
7078
cursor_address = cursor_existing->second;
7179
} else {
72-
cursor_address = offsetAddress(range_.start_, cursor_);
80+
cursor_address = offsetAddress(range_start_, cursor_ * step_);
7381
}
7482

7583
// Now we swap them.... in fact we don't swap because as an optimization

src/lib/dhcpsrv/ip_range_permutation.h

Lines changed: 52 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -20,24 +20,24 @@ namespace dhcp {
2020

2121
/// @brief Random IP address/prefix permutation based on Fisher-Yates shuffle.
2222
///
23-
/// This class is used to shuffle IP addresses within the specified address
24-
/// range. It is following the Fisher-Yates shuffle algorithm described in
25-
/// https://en.wikipedia.org/wiki/Fisher–Yates_shuffle.
23+
/// This class is used to shuffle IP addresses or delegated prefixes within
24+
/// the specified range. It is following the Fisher-Yates shuffle algorithm
25+
/// described in https://en.wikipedia.org/wiki/Fisher–Yates_shuffle.
2626
///
2727
/// The original algorithm is modified to keep the minimal information about
2828
/// the current state of the permutation and relies on the caller to collect
2929
/// and store the next available value. In other words, the generated and
3030
/// already returned random values are not stored by this class.
3131
///
32-
/// The class assumes that initially the IP addresses in the specified range
33-
/// are in increasing order. Suppose we're dealing with the following address
34-
/// range: 192.0.2.1-192.0.2.5. Therefore our addresses are initially ordered
35-
/// like this: a[0]=192.0.2.1, a[1]=192.0.2.2 ..., a[4]=192.0.2.5. The
36-
/// algorithm starts from the end of that range, i.e. i=4, so a[i]=192.0.2.5.
37-
/// A random value from the range of [0..i-1] is picked, i.e. a value from the
38-
/// range of [0..3]. Let's say it is 1. This value initially corresponds to the
39-
/// address a[1]=192.0.2.2. In the original algorithm the value of a[1] is
40-
/// swapped with a[4], yelding the following partial permutation:
32+
/// The class assumes that initially the IP addresses or delegated prefixes
33+
/// in the specified range are in increasing order. Suppose we're dealing with
34+
/// the following address range: 192.0.2.1-192.0.2.5. Therefore our addresses
35+
/// are initially ordered like this: a[0]=192.0.2.1, a[1]=192.0.2.2 ...,
36+
/// a[4]=192.0.2.5. The algorithm starts from the end of that range, i.e. i=4,
37+
/// so a[i]=192.0.2.5. A random value from the range of [0..i-1] is picked,
38+
/// i.e. a value from the range of [0..3]. Let's say it is 1. This value initially
39+
/// corresponds to the address a[1]=192.0.2.2. In the original algorithm the
40+
/// value of a[1] is swapped with a[4], yelding the following partial permutation:
4141
/// 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
4242
/// return the value of 192.0.2.2 to the caller and remember that
4343
/// 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 {
5757
/// start and the position. The other two have been already returned to the
5858
/// caller so we forget them.
5959
///
60-
/// This algorithm guarantees that all IP addresses beloging to the given
61-
/// address range are returned and no duplicates are returned. The addresses
62-
/// are returned in a random order.
60+
/// This algorithm guarantees that all IP addresses or delegated prefixes
61+
/// beloging to the given range are returned and no duplicates are returned.
62+
/// The addresses or delegated prefixes are returned in a random order.
6363
class IPRangePermutation {
6464
public:
6565

6666
/// Address range.
6767
typedef AddressRange Range;
6868

69-
/// @brief Constructor.
69+
/// Prefix range.
70+
typedef PrefixRange PrefixRange;
71+
72+
/// @brief Constructor for address ranges.
7073
///
7174
/// @param range address range for which the permutation will be generated.
7275
IPRangePermutation(const Range& range);
7376

74-
/// @brief Checks if the address range has been exhausted.
77+
/// @brief Constructor for prefix ranges.
78+
///
79+
/// @param range range of delegated prefixes for which the permutation will
80+
/// be generated.
81+
IPRangePermutation(const PrefixRange& range);
82+
83+
/// @brief Checks if the range has been exhausted.
7584
///
76-
/// @return false if the algorithm went over all addresses in the
77-
/// range, true otherwise.
85+
/// @return false if the algorithm went over all addresses or prefixes in
86+
/// the range, true otherwise.
7887
bool exhausted() const {
7988
return (done_);
8089
}
8190

82-
/// @brief Returns next random address from the permutation.
91+
/// @brief Returns next random address or prefix from the permutation.
8392
///
84-
/// This method will returns all addresses belonging to the specified
85-
/// address range in random order. For the first number of calls equal
86-
/// to the size of the address range it guarantees to return a non-zero
87-
/// IP address from that range without duplicates.
93+
/// This method returns all addresses or prefixes belonging to the specified
94+
/// range in random order. For the first number of calls equal to the size of
95+
/// the range it guarantees to return a non-zero IP address from that range
96+
/// without duplicates.
8897
///
8998
/// @param [out] done this parameter is set to true if no more addresses
90-
/// can be returned for this permutation.
91-
/// @return next available IP address. It returns IPv4 zero or IPv6 zero
92-
/// address after this method walked over all available IP addresses in
93-
/// the range.
99+
/// or prefixes can be returned for this permutation.
100+
/// @return next available IP address or prefix. It returns IPv4 zero or IPv6
101+
/// zero address after this method walked over all available IP addresses or
102+
/// prefixes in the range.
94103
asiolink::IOAddress next(bool& done);
95104

96105
private:
97106

98-
/// Address range used in this permutation and specified in the
99-
/// constructor.
100-
Range range_;
107+
/// Beginning of the range.
108+
asiolink::IOAddress range_start_;
109+
110+
/// Distance between two neighboring addresses or delegated prefixes,
111+
/// i.e. 1 for address range and delegated prefix size for delegated
112+
/// prefixes.
113+
uint64_t step_;
101114

102-
/// Keeps the possition of the next address to be swapped with a
103-
/// randomly picked address from the range of 0..cursor-1. The
104-
/// cursor value is decreased every time a new IP address is returned.
115+
/// Keeps the position of the next address or prefix to be swapped with
116+
/// a randomly picked address or prefix from the range of 0..cursor-1. The
117+
/// cursor value is decreased every time a new IP address or prefix
118+
/// is returned.
105119
uint64_t cursor_;
106120

107121
/// Keeps the current permutation state. The state associates the
108-
/// swapped IP addresses with their positions in the permutation.
122+
/// swapped IP addresses or delegated prefixes with their positions in
123+
/// the permutation.
109124
std::map<uint64_t, asiolink::IOAddress> state_;
110125

111-
/// Indicates if the addresses are exhausted.
126+
/// Indicates if the addresses or delegated prefixes are exhausted.
112127
bool done_;
113128

114129
/// Random generator.
@@ -121,4 +136,4 @@ typedef boost::shared_ptr<IPRangePermutation> IPRangePermutationPtr;
121136
} // end of namespace isc::dhcp
122137
} // end of namespace isc
123138

124-
#endif // ADDRESS_RANGE_PERMUTATION_H
139+
#endif // IP_RANGE_PERMUTATION_H

src/lib/dhcpsrv/tests/ip_range_permutation_unittest.cc

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,11 +64,11 @@ TEST(IPRangePermutationTest, ipv4) {
6464
EXPECT_TRUE(addrs.begin()->isV4Zero());
6565
}
6666

67-
// This test verifies that a permutation of IPv4 address range can
67+
// This test verifies that a permutation of IPv6 address range can
6868
// be generated.
6969
TEST(IPRangePermutationTest, ipv6) {
7070
IPRangePermutation::Range range(IOAddress("2001:db8:1::1:fea0"),
71-
IOAddress("2001:db8:1::2:abcd"));
71+
IOAddress("2001:db8:1::2:abcd"));
7272
IPRangePermutation perm(range);
7373

7474
std::set<IOAddress> addrs;
@@ -92,4 +92,32 @@ TEST(IPRangePermutationTest, ipv6) {
9292
EXPECT_TRUE(addrs.begin()->isV6Zero());
9393
}
9494

95+
// This test verifies that a permutation of delegated prefixes can be
96+
// generated.
97+
TEST(IPRangePermutationTest, pd) {
98+
IPRangePermutation::PrefixRange range(IOAddress("3000::"), 112, 120);
99+
IPRangePermutation perm(range);
100+
101+
std::set<IOAddress> addrs;
102+
bool done = false;
103+
for (auto i = 0; i < 257; ++i) {
104+
auto next = perm.next(done);
105+
if (!next.isV6Zero()) {
106+
// The IPv6 zero address marks the end of the permutation. In this case
107+
// the done flag should be set.
108+
EXPECT_LE(range.start_, next);
109+
EXPECT_LE(next, range.end_);
110+
} else {
111+
EXPECT_TRUE(done);
112+
EXPECT_TRUE(perm.exhausted());
113+
}
114+
// Insert the address returned to the set.
115+
addrs.insert(next);
116+
}
117+
118+
// We should have recorded 257 unique addresses, including the zero address.
119+
EXPECT_EQ(257, addrs.size());
120+
EXPECT_TRUE(addrs.begin()->isV6Zero());
121+
}
122+
95123
} // end of anonymous namespace

0 commit comments

Comments
 (0)