Browse Source

[3171] AllocEngine work: increasePrefix() added, prefix_len passed around

Tomek Mrugalski 11 years ago
parent
commit
e295438305

+ 78 - 11
src/lib/dhcpsrv/alloc_engine.cc

@@ -58,7 +58,7 @@ AllocEngine::IterativeAllocator::IterativeAllocator(Lease::Type lease_type)
 }
 
 isc::asiolink::IOAddress
-AllocEngine::IterativeAllocator::increaseAddress(const isc::asiolink::IOAddress& addr) {
+AllocEngine::IterativeAllocator::increaseAddress(const isc::asiolink::IOAddress& addr) const {
     // Get a buffer holding an address.
     const std::vector<uint8_t>& vec = addr.toBytes();
     // Get the address length.
@@ -85,6 +85,52 @@ AllocEngine::IterativeAllocator::increaseAddress(const isc::asiolink::IOAddress&
     return (IOAddress::fromBytes(addr.getFamily(), packed));
 }
 
+isc::asiolink::IOAddress
+AllocEngine::IterativeAllocator::increasePrefix(const isc::asiolink::IOAddress& prefix,
+                                                uint8_t prefix_len) const {
+    if (!prefix.isV6()) {
+        isc_throw(BadValue, "Prefix operations are for IPv6 only");
+    }
+
+    // Get a buffer holding an address.
+    const std::vector<uint8_t>& vec = prefix.toBytes();
+
+    if (prefix_len < 1 || prefix_len > 128) {
+        isc_throw(BadValue, "Cannot increase prefix: invalid prefix length: "
+                  << prefix_len);
+    }
+
+    // Explanation what happens here: http://www.youtube.com/watch?v=NFQCYpIHLNQ
+    uint8_t n_bytes = (prefix_len - 1)/8;
+    uint8_t n_bits = 8 - (prefix_len - n_bytes*8);
+    uint8_t mask = 1 << n_bits;
+
+    uint8_t packed[V6ADDRESS_LEN];
+
+    // Copy the address. It must be V6, but we already checked that.
+    std::memcpy(packed, &vec[0], V6ADDRESS_LEN);
+
+    // Increase last byte that is in prefix
+    if (packed[n_bytes] + uint16_t(mask) < 256u) {
+        packed[n_bytes] += mask;
+        return (IOAddress::fromBytes(AF_INET6, packed));
+    }
+
+    // Overflow (done on uint8_t, but the sum is greater than 255)
+    packed[n_bytes] += mask;
+
+    // Start increasing the least significant byte
+    for (int i = n_bytes - 1; i >= 0; --i) {
+        ++packed[i];
+        // If we haven't overflowed (0xff->0x0) the next byte, then we are done
+        if (packed[i] != 0) {
+            break;
+        }
+    }
+
+    return (IOAddress::fromBytes(AF_INET6, packed));
+}
+
 
 isc::asiolink::IOAddress
 AllocEngine::IterativeAllocator::pickAddress(const SubnetPtr& subnet,
@@ -261,8 +307,10 @@ AllocEngine::allocateAddress6(const Subnet6Ptr& subnet,
         }
 
         // check if the hint is in pool and is available
-        if (subnet->inPool(hint)) {
+        // This is equivalent of subnet->inPool(hint), but returns the pool
+        Pool6Ptr pool = boost::dynamic_pointer_cast<Pool6>(subnet->getPool(type, hint, false));
 
+        if (pool) {
             /// @todo: We support only one hint for now
             Lease6Ptr lease = LeaseMgrFactory::instance().getLease6(type, hint);
             if (!lease) {
@@ -270,10 +318,9 @@ AllocEngine::allocateAddress6(const Subnet6Ptr& subnet,
                 /// implemented
 
                 // the hint is valid and not currently used, let's create a lease for it
-                /// @todo: We support only one lease per ia for now
-                lease = createLease6(subnet, duid, iaid, hint, type, fwd_dns_update,
-                                     rev_dns_update, hostname, callout_handle,
-                                     fake_allocation);
+                lease = createLease6(subnet, duid, iaid, hint, pool->getLength(),
+                                     type, fwd_dns_update, rev_dns_update,
+                                     hostname, callout_handle, fake_allocation);
 
                 // It can happen that the lease allocation failed (we could have lost
                 // the race condition. That means that the hint is lo longer usable and
@@ -288,6 +335,7 @@ AllocEngine::allocateAddress6(const Subnet6Ptr& subnet,
                 if (lease->expired()) {
                     /// We found a lease and it is expired, so we can reuse it
                     lease = reuseExpiredLease(lease, subnet, duid, iaid,
+                                              pool->getLength(),
                                               fwd_dns_update, rev_dns_update,
                                               hostname, callout_handle,
                                               fake_allocation);
@@ -324,13 +372,24 @@ AllocEngine::allocateAddress6(const Subnet6Ptr& subnet,
             /// @todo: check if the address is reserved once we have host support
             /// implemented
 
+            // The first step is to find out prefix length. It is 128 for
+            // non-PD leases.
+            uint8_t prefix_len = 128;
+            if (type == Lease::TYPE_PD) {
+                Pool6Ptr pool = boost::dynamic_pointer_cast<Pool6>(
+                    subnet->getPool(type, candidate, false));
+                prefix_len = pool->getLength();
+            }
+
             Lease6Ptr existing = LeaseMgrFactory::instance().getLease6(type,
                                  candidate);
             if (!existing) {
+
                 // there's no existing lease for selected candidate, so it is
                 // free. Let's allocate it.
+
                 Lease6Ptr lease = createLease6(subnet, duid, iaid, candidate,
-                                               type, fwd_dns_update,
+                                               prefix_len, type, fwd_dns_update,
                                                rev_dns_update, hostname,
                                                callout_handle, fake_allocation);
                 if (lease) {
@@ -345,9 +404,9 @@ AllocEngine::allocateAddress6(const Subnet6Ptr& subnet,
             } else {
                 if (existing->expired()) {
                     existing = reuseExpiredLease(existing, subnet, duid, iaid,
-                                                 fwd_dns_update, rev_dns_update,
-                                                 hostname, callout_handle,
-                                                 fake_allocation);
+                                                 prefix_len, fwd_dns_update,
+                                                 rev_dns_update, hostname,
+                                                 callout_handle, fake_allocation);
                     Lease6Collection collection;
                     collection.push_back(existing);
                     return (collection);
@@ -621,6 +680,7 @@ Lease6Ptr AllocEngine::reuseExpiredLease(Lease6Ptr& expired,
                                          const Subnet6Ptr& subnet,
                                          const DuidPtr& duid,
                                          uint32_t iaid,
+                                         uint8_t prefix_len,
                                          const bool fwd_dns_update,
                                          const bool rev_dns_update,
                                          const std::string& hostname,
@@ -631,6 +691,10 @@ Lease6Ptr AllocEngine::reuseExpiredLease(Lease6Ptr& expired,
         isc_throw(BadValue, "Attempt to recycle lease that is still valid");
     }
 
+    if (expired->type_ != Lease::TYPE_PD) {
+        prefix_len = 128; // non-PD lease types must be always /128
+    }
+
     // address, lease type and prefixlen (0) stay the same
     expired->iaid_ = iaid;
     expired->duid_ = duid;
@@ -644,6 +708,7 @@ Lease6Ptr AllocEngine::reuseExpiredLease(Lease6Ptr& expired,
     expired->hostname_ = hostname;
     expired->fqdn_fwd_ = fwd_dns_update;
     expired->fqdn_rev_ = rev_dns_update;
+    expired->prefixlen_ = prefix_len;
 
     /// @todo: log here that the lease was reused (there's ticket #2524 for
     /// logging in libdhcpsrv)
@@ -779,6 +844,7 @@ Lease6Ptr AllocEngine::createLease6(const Subnet6Ptr& subnet,
                                     const DuidPtr& duid,
                                     uint32_t iaid,
                                     const IOAddress& addr,
+                                    uint8_t prefix_len,
                                     Lease::Type type,
                                     const bool fwd_dns_update,
                                     const bool rev_dns_update,
@@ -788,7 +854,8 @@ Lease6Ptr AllocEngine::createLease6(const Subnet6Ptr& subnet,
 
     Lease6Ptr lease(new Lease6(type, addr, duid, iaid,
                                subnet->getPreferred(), subnet->getValid(),
-                               subnet->getT1(), subnet->getT2(), subnet->getID()));
+                               subnet->getT1(), subnet->getT2(), subnet->getID(),
+                               prefix_len));
 
     lease->fqdn_fwd_ = fwd_dns_update;
     lease->fqdn_rev_ = rev_dns_update;

+ 25 - 6
src/lib/dhcpsrv/alloc_engine.h

@@ -124,13 +124,30 @@ protected:
             pickAddress(const SubnetPtr& subnet,
                         const DuidPtr& duid,
                         const isc::asiolink::IOAddress& hint);
-    private:
+    protected:
 
-        /// @brief returns an address by one
+        /// @brief returns an address increased by one
+        ///
+        /// This method works for both IPv4 and IPv6 addresses.
+        ///
         /// @param addr address to be increased
         /// @return address increased by one
-        isc::asiolink::IOAddress increaseAddress(const isc::asiolink::IOAddress& addr);
+        isc::asiolink::IOAddress
+        increaseAddress(const isc::asiolink::IOAddress& addr) const;
 
+        /// @brief returns the next prefix
+        ///
+        /// This method works for IPv6 addresses only. It increase
+        /// specified prefix by a given prefix_len. For example, 2001:db8::
+        /// increased by prefix length /32 will become 2001:db9::. This method
+        /// is used to iterate over IPv6 prefix pools
+        ///
+        /// @param prefix prefix to be increased
+        /// @param prefix_len length of the prefix to be increased
+        /// @return result prefix
+        isc::asiolink::IOAddress
+        increasePrefix(const isc::asiolink::IOAddress& prefix,
+                       uint8_t prefix_len) const;
     };
 
     /// @brief Address/prefix allocator that gets an address based on a hash
@@ -381,6 +398,7 @@ private:
     /// @param iaid IAID from the IA_NA container the client sent to us
     /// @param addr an address that was selected and is confirmed to be
     ///        available
+    /// @param prefix_len lenght of the prefix (for PD only)
     /// @param type lease type (IA, TA or PD)
     /// @param fwd_dns_update A boolean value which indicates that server takes
     ///        responsibility for the forward DNS Update for this lease
@@ -398,8 +416,8 @@ private:
     ///         became unavailable)
     Lease6Ptr createLease6(const Subnet6Ptr& subnet, const DuidPtr& duid,
                            uint32_t iaid, const isc::asiolink::IOAddress& addr,
-                           Lease::Type type, const bool fwd_dns_update,
-                           const bool rev_dns_update,
+                           uint8_t prefix_len, Lease::Type type,
+                           const bool fwd_dns_update, const bool rev_dns_update,
                            const std::string& hostname,
                            const isc::hooks::CalloutHandlePtr& callout_handle,
                            bool fake_allocation = false);
@@ -445,6 +463,7 @@ private:
     /// @param subnet subnet the lease is allocated from
     /// @param duid client's DUID
     /// @param iaid IAID from the IA_NA container the client sent to us
+    /// @param prefix_len prefix length (for PD leases)
     /// @param fwd_dns_update A boolean value which indicates that server takes
     ///        responsibility for the forward DNS Update for this lease
     ///        (if true).
@@ -460,7 +479,7 @@ private:
     /// @throw BadValue if trying to recycle lease that is still valid
     Lease6Ptr reuseExpiredLease(Lease6Ptr& expired, const Subnet6Ptr& subnet,
                                 const DuidPtr& duid, uint32_t iaid,
-                                const bool fwd_dns_update,
+                                uint8_t prefix_len, const bool fwd_dns_update,
                                 const bool rev_dns_update,
                                 const std::string& hostname,
                                 const isc::hooks::CalloutHandlePtr& callout_handle,

+ 1 - 1
src/lib/dhcpsrv/lease.h

@@ -289,7 +289,7 @@ struct Lease6 : public Lease {
     /// @param prefixlen An address prefix length.
     Lease6(Type type, const isc::asiolink::IOAddress& addr, DuidPtr duid,
            uint32_t iaid, uint32_t preferred, uint32_t valid, uint32_t t1,
-           uint32_t t2, SubnetID subnet_id, uint8_t prefixlen = 0);
+           uint32_t t2, SubnetID subnet_id, uint8_t prefixlen = 128);
 
     /// @brief Constructor, including FQDN data.
     ///

+ 3 - 2
src/lib/dhcpsrv/subnet.cc

@@ -168,7 +168,8 @@ const PoolCollection& Subnet::getPools(Lease::Type type) const {
     }
 }
 
-PoolPtr Subnet::getPool(Lease::Type type, isc::asiolink::IOAddress hint) {
+PoolPtr Subnet::getPool(Lease::Type type, isc::asiolink::IOAddress hint,
+                        bool anypool /* true */) {
     // check if the type is valid (and throw if it isn't)
     checkType(type);
 
@@ -195,7 +196,7 @@ PoolPtr Subnet::getPool(Lease::Type type, isc::asiolink::IOAddress hint) {
          pool != pools->end(); ++pool) {
 
         // if we won't find anything better, then let's just use the first pool
-        if (!candidate) {
+        if (anypool && !candidate) {
             candidate = *pool;
         }
 

+ 8 - 1
src/lib/dhcpsrv/subnet.h

@@ -277,10 +277,17 @@ public:
     /// If there is no pool that the address belongs to (hint is invalid), other
     /// pool of specified type will be returned.
     ///
+    /// With anypool set to true, this is means give me a pool, preferably
+    /// the one that addr belongs to. With anypool set to false, it means
+    /// give me a pool that addr belongs to (or NULL if here is no such pool)
+    ///
     /// @param type pool type that the pool is looked for
     /// @param addr address that the returned pool should cover (optional)
+    /// @param anypool other pool may be returned as well, not only the one
+    ///        that addr belongs to
     /// @return found pool (or NULL)
-    PoolPtr getPool(Lease::Type type, isc::asiolink::IOAddress addr);
+    PoolPtr getPool(Lease::Type type, isc::asiolink::IOAddress addr,
+                    bool anypool = true);
 
     /// @brief Returns a pool without any address specified
     ///

+ 95 - 2
src/lib/dhcpsrv/tests/alloc_engine_unittest.cc

@@ -65,6 +65,16 @@ public:
     using AllocEngine::Allocator;
     using AllocEngine::IterativeAllocator;
     using AllocEngine::getAllocator;
+
+    class NakedIterativeAllocator: public AllocEngine::IterativeAllocator {
+    public:
+        NakedIterativeAllocator(Lease::Type type)
+            :IterativeAllocator(type) {
+        }
+
+        using AllocEngine::IterativeAllocator::increaseAddress;
+        using AllocEngine::IterativeAllocator::increasePrefix;
+    };
 };
 
 /// @brief Used in Allocation Engine tests for IPv6
@@ -125,12 +135,30 @@ public:
         EXPECT_EQ(subnet_->getPreferred(), lease->preferred_lft_);
         EXPECT_EQ(subnet_->getT1(), lease->t1_);
         EXPECT_EQ(subnet_->getT2(), lease->t2_);
-        EXPECT_EQ(0, lease->prefixlen_); // this is IA_NA, not IA_PD
+        EXPECT_EQ(128, lease->prefixlen_); // this is IA_NA, not IA_PD
         EXPECT_TRUE(false == lease->fqdn_fwd_);
         EXPECT_TRUE(false == lease->fqdn_rev_);
         EXPECT_TRUE(*lease->duid_ == *duid_);
         // @todo: check cltt
-     }
+    }
+
+    /// @brief checks if specified address is increased properly
+    /// @param alloc IterativeAllocator that is tested
+    /// @param input address to be increased
+    /// @param exp_output expected address after increase
+    void
+    checkAddrIncrease(NakedAllocEngine::NakedIterativeAllocator& alloc,
+                      std::string input, std::string exp_output) {
+        EXPECT_EQ(exp_output, alloc.increaseAddress(IOAddress(input)).toText());
+    }
+
+    void
+    checkPrefixIncrease(NakedAllocEngine::NakedIterativeAllocator& alloc,
+                        std::string input, uint8_t prefix_len,
+                        std::string exp_output) {
+        EXPECT_EQ(exp_output, alloc.increasePrefix(IOAddress(input), prefix_len)
+                  .toText());
+    }
 
     virtual ~AllocEngine6Test() {
         factory_.destroy();
@@ -423,6 +451,71 @@ TEST_F(AllocEngine6Test, IterativeAllocator) {
     }
 }
 
+// This test verifies that the allocator iterates over addresses properly
+TEST_F(AllocEngine6Test, IterativeAllocatorAddrStep) {
+    NakedAllocEngine::NakedIterativeAllocator alloc(Lease::TYPE_NA);
+
+    // Let's pick the first address
+    IOAddress addr1 = alloc.pickAddress(subnet_, duid_, IOAddress("2001:db8:1::10"));
+
+    // Check that we can indeed pick the first address from the pool
+    EXPECT_EQ("2001:db8:1::10", addr1.toText());
+
+    // Check that addresses can be increased properly
+    checkAddrIncrease(alloc, "2001:db8::9", "2001:db8::a");
+    checkAddrIncrease(alloc, "2001:db8::f", "2001:db8::10");
+    checkAddrIncrease(alloc, "2001:db8::10", "2001:db8::11");
+    checkAddrIncrease(alloc, "2001:db8::ff", "2001:db8::100");
+    checkAddrIncrease(alloc, "2001:db8::ffff", "2001:db8::1:0");
+    checkAddrIncrease(alloc, "::", "::1");
+    checkAddrIncrease(alloc, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", "::");
+
+    // Check that prefixes can be increased properly
+
+    // For /128 prefix, increasePrefix should work the same as addressIncrease
+    checkPrefixIncrease(alloc, "2001:db8::9", 128, "2001:db8::a");
+    checkPrefixIncrease(alloc, "2001:db8::f", 128, "2001:db8::10");
+    checkPrefixIncrease(alloc, "2001:db8::10", 128, "2001:db8::11");
+    checkPrefixIncrease(alloc, "2001:db8::ff", 128, "2001:db8::100");
+    checkPrefixIncrease(alloc, "2001:db8::ffff", 128, "2001:db8::1:0");
+    checkPrefixIncrease(alloc, "::", 128, "::1");
+    checkPrefixIncrease(alloc, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", 128, "::");
+
+    // Check that /64 prefixes can be generated
+    checkPrefixIncrease(alloc, "2001:db8::", 64, "2001:db8:0:1::");
+
+    // Check that prefix length not divisible by 8 are working
+    checkPrefixIncrease(alloc, "2001:db8::", 128, "2001:db8::1");
+    checkPrefixIncrease(alloc, "2001:db8::", 127, "2001:db8::2");
+    checkPrefixIncrease(alloc, "2001:db8::", 126, "2001:db8::4");
+    checkPrefixIncrease(alloc, "2001:db8::", 125, "2001:db8::8");
+    checkPrefixIncrease(alloc, "2001:db8::", 124, "2001:db8::10");
+    checkPrefixIncrease(alloc, "2001:db8::", 123, "2001:db8::20");
+    checkPrefixIncrease(alloc, "2001:db8::", 122, "2001:db8::40");
+    checkPrefixIncrease(alloc, "2001:db8::", 121, "2001:db8::80");
+    checkPrefixIncrease(alloc, "2001:db8::", 120, "2001:db8::100");
+
+    // These are not really useful cases, because there are bits set
+    // int the last (128 - prefix_len) bits. Nevertheless, it shows
+    // that the algorithm is working even in such cases
+    checkPrefixIncrease(alloc, "2001:db8::1", 128, "2001:db8::2");
+    checkPrefixIncrease(alloc, "2001:db8::1", 127, "2001:db8::3");
+    checkPrefixIncrease(alloc, "2001:db8::1", 126, "2001:db8::5");
+    checkPrefixIncrease(alloc, "2001:db8::1", 125, "2001:db8::9");
+    checkPrefixIncrease(alloc, "2001:db8::1", 124, "2001:db8::11");
+    checkPrefixIncrease(alloc, "2001:db8::1", 123, "2001:db8::21");
+    checkPrefixIncrease(alloc, "2001:db8::1", 122, "2001:db8::41");
+    checkPrefixIncrease(alloc, "2001:db8::1", 121, "2001:db8::81");
+    checkPrefixIncrease(alloc, "2001:db8::1", 120, "2001:db8::101");
+
+    // Let's try out couple real life scenarios
+    checkPrefixIncrease(alloc, "2001:db8:1:abcd::", 64, "2001:db8:1:abce::");
+    checkPrefixIncrease(alloc, "2001:db8:1:abcd::", 60, "2001:db8:1:abdd::");
+    checkPrefixIncrease(alloc, "2001:db8:1:abcd::", 56, "2001:db8:1:accd::");
+    checkPrefixIncrease(alloc, "2001:db8:1:abcd::", 52, "2001:db8:1:bbcd::");
+}
+
+
 
 // This test verifies that the iterative allocator really walks over all addresses
 // in all pools in specified subnet. It also must not pick the same address twice