Browse Source

[3711] addrInRange, prefixesInRange implemented.

Tomek Mrugalski 10 years ago
parent
commit
0bdd2911c3

+ 92 - 0
src/lib/dhcpsrv/addr_utilities.cc

@@ -15,6 +15,8 @@
 #include <dhcpsrv/addr_utilities.h>
 #include <exceptions/exceptions.h>
 
+#include <vector>
+#include <limits>
 #include <string.h>
 
 using namespace isc;
@@ -205,5 +207,95 @@ isc::asiolink::IOAddress getNetmask4(uint8_t len) {
     return (IOAddress(x));
 }
 
+uint64_t
+addrsInRange(const isc::asiolink::IOAddress& min,
+             const isc::asiolink::IOAddress& max) {
+    if (min.isV4() != max.isV4()) {
+        isc_throw(BadValue, "Both addresses have to be the same family");
+    }
+
+    if (min.isV4()) {
+        // Let's explicitly cast last_ and first_ (IOAddress). This conversion is
+        // automatic, but let's explicitly cast it show that we moved to integer
+        // domain and addresses are now substractable.
+        uint64_t max_numeric = uint32_t(max);
+        uint64_t min_numeric = uint32_t(min);
+
+        // We can simply subtract the values. We need to increase the result
+        // by one, as both min and max are included in the range. So even if
+        // min == max, there's one address.
+        return (max_numeric - min_numeric + 1);
+    } else {
+
+        // Calculating the difference in v6 is more involved. Let's subtract
+        // one from the other. By subtracting min from max, we move the
+        // [a, b] range to the [0, (b-a)] range. We don't care about the beginning
+        // of the new range (it's always zero). The upper bound now specifies
+        // the number of addresses minus one.
+        IOAddress count = IOAddress::subtract(max, min);
+
+        // There's one very special case. Someone is trying to check how many
+        // IPv6 addresses are in IPv6 address space. He called this method
+        // with ::, ffff:ffff:ffff:fffff:ffff:ffff:ffff:ffff. The diff is also
+        // all 1s. Had we increased it by one, the address would flip to all 0s.
+        // This will not happen in a real world. Apparently, unit-tests are
+        // sometimes nastier then a real world.
+        static IOAddress max6("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff");
+        if (count == max6) {
+            return (std::numeric_limits<uint64_t>::max());
+        }
+
+        // Increase it by one (a..a range still contains one address, even though
+        // a subtracted from a is zero).
+        count = IOAddress::increaseAddress(count);
+
+        // We don't have uint128, so for anything greater than 2^64, we'll just
+        // assume numeric_limits<uint64_t>::max. Let's do it the manual way.
+        std::vector<uint8_t> bin = count.toBytes();
+
+        // If any of the most significant 64 bits is set, we have more than
+        // 2^64 addresses and can't represent it even on uint64_t.
+        for (int i = 0 ; i < 8; i++) {
+            if (bin[i]) {
+                return (std::numeric_limits<uint64_t>::max());
+            }
+        }
+
+        // Ok, we're good. The pool is sanely sized. It may be huge, but at least
+        // that's something we can represent on uint64_t.
+        uint64_t numeric = 0;
+        for (int i = 8; i < 16; i++) {
+            numeric <<= 8;
+            numeric += bin[i];
+        }
+
+        return (numeric);
+    }
+}
+
+uint64_t prefixesInRange(uint8_t pool_len, uint8_t delegated_len) {
+    if (delegated_len < pool_len) {
+        return (0);
+    }
+
+    uint64_t count = delegated_len - pool_len;
+
+    if (count == 0) {
+        // If we want to delegate /64 out of /64 pool, we have only
+        // one prefix.
+        return (1);
+    } else if (count >= 64) {
+        // If the difference is greater than or equal 64, e.g. we want to
+        // delegate /96 out of /16 pool, the number is bigger than we can
+        // express, so we'll stick with maximum value of uint64_t.
+        return (std::numeric_limits<uint64_t>::max());
+    } else {
+        // Now count specifies the exponent (e.g. if the difference between the
+        // delegated and pool length is 4, we have 16 prefixes), so we need
+        // to calculate 2^(count - 1)
+        return (((uint64_t)2) << (count - 1));
+    }
+}
+
 };
 };

+ 27 - 1
src/lib/dhcpsrv/addr_utilities.h

@@ -52,11 +52,37 @@ isc::asiolink::IOAddress firstAddrInPrefix(const isc::asiolink::IOAddress& prefi
 isc::asiolink::IOAddress lastAddrInPrefix(const isc::asiolink::IOAddress& prefix,
                                            uint8_t len);
 
-/// @brief generates an IPv4 netmask of specified length
+/// @brief Generates an IPv4 netmask of specified length
 /// @throw BadValue if len is greater than 32
 /// @return netmask
 isc::asiolink::IOAddress getNetmask4(uint8_t len);
 
+
+/// @brief Returns number of available addresses in the specified range (min - max).
+///
+/// Note that for min equal max case, the number of addresses is one.
+///
+/// @throw BadValue if min and max are not of the same family (both must be
+///        either IPv4 or IPv6) or if max < min.
+///
+/// @param min the first address in range
+/// @param max the last address in range
+/// @return number of addresses in range
+uint64_t addrsInRange(const isc::asiolink::IOAddress& min,
+                      const isc::asiolink::IOAddress& max);
+
+/// @brief Returns number of available IPv6 prefixes in the specified prefix.
+///
+/// Note that if the answer is bigger than uint64_t can hold, it will return
+/// the max value of uint64_t type.
+///
+/// Example: prefixesInRange(48, 64) returns 65536, as there are /48 pool
+/// can be split into 65536 prefixes, each /64 large.
+///
+/// @param min the first address in range
+/// @param max the last address in range
+/// @return number of addresses in range
+uint64_t prefixesInRange(uint8_t pool_len, uint8_t delegated_len);
 };
 };
 

+ 71 - 0
src/lib/dhcpsrv/tests/addr_utilities_unittest.cc

@@ -200,4 +200,75 @@ TEST(AddrUtilitiesTest, getNetmask4) {
     EXPECT_THROW(getNetmask4(33), isc::BadValue);
 }
 
+// Checks if the calculation for IPv4 addresses in range are correct.
+TEST(AddrUtilitiesTest, addrsInRange4) {
+
+    // Let's start with something simple
+    EXPECT_EQ(1, addrsInRange(IOAddress("192.0.2.0"), IOAddress("192.0.2.0")));
+    EXPECT_EQ(10, addrsInRange(IOAddress("192.0.2.10"), IOAddress("192.0.2.19")));
+    EXPECT_EQ(256, addrsInRange(IOAddress("192.0.2.0"), IOAddress("192.0.2.255")));
+    EXPECT_EQ(65536, addrsInRange(IOAddress("192.0.0.0"), IOAddress("192.0.255.255")));
+    EXPECT_EQ(16777216, addrsInRange(IOAddress("10.0.0.0"), IOAddress("10.255.255.255")));
+
+    // Let's check if the network boundaries are crossed correctly.
+    EXPECT_EQ(3, addrsInRange(IOAddress("10.0.0.255"), IOAddress("10.0.1.1")));
+
+    // Let's go a bit overboard with this! How many addresses are there in
+    // IPv4 address space? That's a slightly tricku question, as the answer
+    // cannot be stored in uint32_t.
+    EXPECT_EQ(uint64_t(std::numeric_limits<uint32_t>::max()) + 1,
+              addrsInRange(IOAddress("0.0.0.0"), IOAddress("255.255.255.255")));
+}
+
+// Checks if the calculation for IPv6 addresses in range are correct.
+TEST(AddrUtilitiesTest, addrsInRange6) {
+
+    // Let's start with something simple
+    EXPECT_EQ(1, addrsInRange(IOAddress("::"), IOAddress("::")));
+    EXPECT_EQ(16, addrsInRange(IOAddress("fe80::1"), IOAddress("fe80::10")));
+    EXPECT_EQ(65536, addrsInRange(IOAddress("fe80::"), IOAddress("fe80::ffff")));
+    EXPECT_EQ(uint64_t(std::numeric_limits<uint32_t>::max()) + 1,
+              addrsInRange(IOAddress("fe80::"), IOAddress("fe80::ffff:ffff")));
+
+    // There's 2^80 addresses between those. Due to uint64_t limits, this method is
+    // capped at 2^64 -1.
+    EXPECT_EQ(std::numeric_limits<uint64_t>::max(),
+              addrsInRange(IOAddress("2001:db8:1::"), IOAddress("2001:db8:2::")));
+
+    // Let's check if the network boundaries are crossed correctly.
+    EXPECT_EQ(3, addrsInRange(IOAddress("2001:db8::ffff"), IOAddress("2001:db8::1:1")));
+
+    // Let's go a bit overboard with this! How many addresses are there in
+    // IPv6 address space? That's a really tricky question, as the answer
+    // wouldn't fit even in uint128_t (if we had it). This method is capped
+    // at max value of uint64_t.
+    EXPECT_EQ(std::numeric_limits<uint64_t>::max(), addrsInRange(IOAddress("::"),
+              IOAddress("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")));
+}
+
+// Checks if prefixInRange returns valid number of prefixes in specified range.
+TEST(AddrUtilitiesTest, prefixesInRange) {
+
+    // How many /64 prefixes are in /64 pool?
+    EXPECT_EQ(1, prefixesInRange(64, 64));
+
+    // How many /63 prefixes are in /64 pool?
+    EXPECT_EQ(2, prefixesInRange(63, 64));
+
+    // How many /64 prefixes are in /48 pool?
+    EXPECT_EQ(65536, prefixesInRange(48, 64));
+
+    // How many /127 prefixes are in /64 pool?
+    EXPECT_EQ(uint64_t(9223372036854775808u), prefixesInRange(64, 127));
+
+    // How many /128 prefixes are in /64 pool?
+    EXPECT_EQ(std::numeric_limits<uint64_t>::max(),
+              prefixesInRange(64, 128));
+
+    // Let's go overboard again. How many IPv6 addresses are there?
+    EXPECT_EQ(std::numeric_limits<uint64_t>::max(),
+              prefixesInRange(0, 128));
+
+}
+
 }; // end of anonymous namespace