]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#1415] Support for PD in free lease container
authorMarcin Siodelski <marcin@isc.org>
Mon, 14 Sep 2020 17:46:06 +0000 (19:46 +0200)
committerMarcin Siodelski <marcin@isc.org>
Wed, 16 Sep 2020 14:39:12 +0000 (14:39 +0000)
src/lib/dhcpsrv/address_range.cc
src/lib/dhcpsrv/free_lease_queue.cc
src/lib/dhcpsrv/free_lease_queue.h
src/lib/dhcpsrv/tests/address_range_unittest.cc
src/lib/dhcpsrv/tests/free_lease_queue_unittest.cc

index 592d4073ee8ea27659806d3ba0b0b991d759af14..88e4792bd8b1e0725f4504a45bdb80c357d39ad7 100644 (file)
@@ -5,6 +5,7 @@
 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 #include <config.h>
+#include <asiolink/addr_utilities.h>
 #include <asiolink/io_address.h>
 #include <dhcpsrv/address_range.h>
 #include <exceptions/exceptions.h>
@@ -27,5 +28,42 @@ 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) {
+    if (!start_.isV6()) {
+        isc_throw(BadValue, "IPv6 prefix required for prefix delegation range but "
+                  << start_ << " was specified");
+    }
+    if (delegated_length_ < 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)) {
+        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_);
+    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) {
+    if (!start_.isV6() || !end_.isV6()) {
+        isc_throw(BadValue, "IPv6 prefix required for prefix delegation range but "
+                  << start_ << ":" << end_ << " was specified");
+    }
+    // The start must be lower or equal the end.
+    if (end_ < start_) {
+        isc_throw(BadValue, "invalid address range boundaries " << start_ << ":" << end_);
+    }
+    if (delegated_length_ > 128) {
+        isc_throw(BadValue, "delegated length " << static_cast<int>(delegated_length_)
+                  << " must not be greater than 128");
+    }
+}
 } // end of namespace isc::dhcp
 } // end of namespace isc
index 084f637c8ce259d7ca6d30384f5568059a6b7457..2e55128c0fbcdc10eac385c10dc88d90da3290b7 100644 (file)
@@ -5,13 +5,12 @@
 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 #include <config.h>
+#include <asiolink/addr_utilities.h>
 #include <dhcpsrv/free_lease_queue.h>
-#include <exceptions/exceptions.h>
 
 #include <boost/make_shared.hpp>
 
 #include <iostream>
-#include <iterator>
 #include <tuple>
 #include <utility>
 
@@ -29,48 +28,9 @@ FreeLeaseQueue::addRange(const Range& range) {
     // If the container with ranges is empty, there are is no need for
     // doing any checks. Let's just add the new range.
     if (!containers_.empty()) {
-        // Get the next range in the container relative to the start of the new
-        // range. The upper_bound returns the range which starts after the start
-        // of the new range.
-        auto next_range = containers_.lower_bound(range.start_);
-        // Get the range the range that is before that one. It is also possible that
-        // there is no previous range in which case we default to end().
-        auto previous_range = containers_.end();
-        // If the next range is at the beginning of the container there is no
-        // previous range.
-        if (next_range != containers_.begin()) {
-            // This should work fine even if the next range is set to end(). We
-            // will get the range that is one position before end() and that
-            // should be the range that goes before the new one.
-            auto it = next_range;
-            --it;
-            previous_range = it;
-        }
-
-        // Now that we have next and previous ranges set we should check that the
-        // new range we're adding does not overlap with them.
-
-        // If the previous range exists, let's check that the start of the new
-        // range is neither within that range nor lower. Assuming that the ranges
-        // are constructed such that the end must be greater or equal the start
-        // it is sufficient to check that the start of the new range is not lower
-        // or equal the end of the previous range.
-        if ((previous_range != containers_.end()) &&
-            (range.start_ <= previous_range->range_end_)) {
-            isc_throw(BadValue, "new address range " << range.start_ << ":" << range.end_
-                      << " overlaps with the existing range");
-        }
-
-        // If the next range exists, let's check that the end of the new range
-        // is neither within that range nor higher.
-        if ((next_range != containers_.end()) &&
-            (next_range->range_start_ <= range.end_)) {
-            isc_throw(BadValue, "new address range " << range.start_ << ":" << range.end_
-                      << " overlaps with the existing range");
-        }
-    }
-
-    containers_.insert(ContainerDescriptor{range.start_, range.end_,
+        checkRangeOverlaps(range.start_, range.end_);
+    }
+    containers_.insert(ContainerDescriptor{range.start_, range.end_, 128,
                                            boost::make_shared<Container>()});
 }
 
@@ -79,9 +39,20 @@ FreeLeaseQueue::addRange(const IOAddress& start, const IOAddress& end) {
     addRange(FreeLeaseQueue::Range(start, end));
 }
 
-bool
-FreeLeaseQueue::removeRange(const Range& range) {
-    return (containers_.get<1>().erase(range.start_) > 0);
+void
+FreeLeaseQueue::addRange(const PrefixRange& range) {
+    if (!containers_.empty()) {
+        auto last_addr = offsetAddress(range.end_, range.delegated_length_ - 1);
+        checkRangeOverlaps(range.start_, last_addr);
+    }
+    containers_.insert(ContainerDescriptor{range.start_, range.end_, range.delegated_length_,
+                                           boost::make_shared<Container>()});
+}
+
+void
+FreeLeaseQueue::addRange(const asiolink::IOAddress& prefix, const uint8_t prefix_length,
+                         const uint8_t delegated_length) {
+    addRange(FreeLeaseQueue::PrefixRange(prefix, prefix_length, delegated_length));
 }
 
 bool
@@ -112,33 +83,63 @@ FreeLeaseQueue::append(const IOAddress& address) {
     return (true);
 }
 
+bool
+FreeLeaseQueue::append(const IOAddress& prefix, const uint8_t delegated_length) {
+    // If there are no ranges defined, there is nothing to do.
+    if (containers_.empty()) {
+        return (false);
+    }
+    // Find the beginning of the range which has the start address
+    // greater than the address we're appending.
+    auto lb = containers_.upper_bound(prefix);
+    // If the range we found is the first one in the container
+    // there is no range matching our prefix because all existing
+    // ranges include higher addresses.
+    if (lb == containers_.begin()) {
+        return (false);
+    }
+    --lb;
+    // Go one range back and see if our prefix is within its boundaries.
+    if ((lb->range_end_ < prefix) || (prefix < lb->range_start_) ||
+        (delegated_length != lb->delegated_length_)) {
+        return (false);
+    }
+    // Use the range we found and append the prefix to it.
+    FreeLeaseQueue::PrefixRange range(lb->range_start_, lb->range_end_, lb->delegated_length_);
+    append(range, prefix);
+
+    // Everything is fine.
+    return (true);
+}
+
 void
 FreeLeaseQueue::append(const FreeLeaseQueue::Range& range, const IOAddress& address) {
     // Make sure the address is within the range boundaries.
-    if ((address < range.start_) || (range.end_ < address)) {
-        isc_throw(BadValue, "address " << address << " is not within the range of "
-                  << range.start_ << ":" << range.end_);
-    }
+    checkRangeBoundaries(range, address);
     auto cont = getContainer(range);
     cont->insert(address);
 }
 
 void
-FreeLeaseQueue::append(const uint64_t range_index, const IOAddress& address) {
+FreeLeaseQueue::append(const uint64_t range_index, const IOAddress& ip) {
     auto desc = getContainerDescriptor(range_index);
-    if ((address < desc.range_start_) || (desc.range_end_ < address)) {
-        isc_throw(BadValue, "address " << address << " is not within the range of "
-                  << desc.range_start_ << ":" << desc.range_end_);
+    if ((ip < desc.range_start_) || (desc.range_end_ < ip)) {
+        isc_throw(BadValue, ip << " is not within the range of " << desc.range_start_
+                  << ":" << desc.range_end_);
     }
-    desc.container_->insert(address);
+    desc.container_->insert(ip);
+}
+
+void
+FreeLeaseQueue::append(const FreeLeaseQueue::PrefixRange& range, const asiolink::IOAddress& prefix) {
+    checkRangeBoundaries(range, prefix, true);
+    auto cont = getContainer(range);
+    cont->insert(prefix);
 }
 
 bool
 FreeLeaseQueue::use(const FreeLeaseQueue::Range& range, const IOAddress& address) {
-    if ((address < range.start_) || (range.end_ < address)) {
-        isc_throw(BadValue, "address " << address << " is outside of the range of "
-                  << range.start_ << ":" << range.end_);
-    }
+    checkRangeBoundaries(range, address);
     auto cont = getContainer(range);
     auto found = cont->find(address);
     if (found != cont->end()) {
@@ -148,28 +149,74 @@ FreeLeaseQueue::use(const FreeLeaseQueue::Range& range, const IOAddress& address
     return (false);
 }
 
-IOAddress
-FreeLeaseQueue::next(const FreeLeaseQueue::Range& range) {
-    return (popNextInternal(range, true));
+bool
+FreeLeaseQueue::use(const FreeLeaseQueue::PrefixRange& range, const IOAddress& prefix) {
+    checkRangeBoundaries(range, prefix, true);
+    auto cont = getContainer(range);
+    auto found = cont->find(prefix);
+    if (found != cont->end()) {
+        static_cast<void>(cont->erase(found));
+        return (true);
+    }
+    return (false);
 }
 
-IOAddress
-FreeLeaseQueue::pop(const FreeLeaseQueue::Range& range) {
-    return (popNextInternal(range, false));
+template<typename RangeType>
+void
+FreeLeaseQueue::checkRangeBoundaries(const RangeType& range, const IOAddress& ip,
+                                     const bool prefix) const {
+    if ((ip < range.start_) || (range.end_ < ip)) {
+        isc_throw(BadValue, (prefix ? "prefix " : "address ") << ip << " is not within the range of "
+                  << range.start_ << ":" << range.end_);
+    }
 }
 
-uint64_t
-FreeLeaseQueue::getRangeIndex(const FreeLeaseQueue::Range& range) const {
-    auto cont = containers_.get<1>().find(range.start_);
-    if (cont == containers_.get<1>().end()) {
-        isc_throw(BadValue, "conatiner for the specified address range " << range.start_
-                  << ":" << range.end_ << " does not exist");
+void
+FreeLeaseQueue::checkRangeOverlaps(const IOAddress& start, const IOAddress& end) const {
+    // Get the next range in the container relative to the start of the new
+    // range. The upper_bound returns the range which starts after the start
+    // of the new range.
+    auto next_range = containers_.lower_bound(start);
+    // Get the range the range that is before that one. It is also possible that
+    // there is no previous range in which case we default to end().
+    auto previous_range = containers_.end();
+    // If the next range is at the beginning of the container there is no
+    // previous range.
+    if (next_range != containers_.begin()) {
+        // This should work fine even if the next range is set to end(). We
+        // will get the range that is one position before end() and that
+        // should be the range that goes before the new one.
+        auto it = next_range;
+        --it;
+        previous_range = it;
+    }
+
+    // Now that we have next and previous ranges set we should check that the
+    // new range we're adding does not overlap with them.
+
+    // If the previous range exists, let's check that the start of the new
+    // range is neither within that range nor lower. Assuming that the ranges
+    // are constructed such that the end must be greater or equal the start
+    // it is sufficient to check that the start of the new range is not lower
+    // or equal the end of the previous range.
+    if ((previous_range != containers_.end()) &&
+        (start <= previous_range->range_end_)) {
+        isc_throw(BadValue, "new address range " << start << ":" << end
+                  << " overlaps with the existing range");
+    }
+
+    // If the next range exists, let's check that the end of the new range
+    // is neither within that range nor higher.
+    if ((next_range != containers_.end()) &&
+        (next_range->range_start_ <= end)) {
+        isc_throw(BadValue, "new address range " << start << ":" << end
+                  << " overlaps with the existing range");
     }
-    return (std::distance(containers_.get<2>().begin(), containers_.project<2>(cont)));
 }
 
+
 FreeLeaseQueue::ContainerPtr
-FreeLeaseQueue::getContainer(const FreeLeaseQueue::Range& range) {
+FreeLeaseQueue::getContainer(const FreeLeaseQueue::Range& range) const {
     auto cont = containers_.find(range.start_);
     if (cont == containers_.end()) {
         isc_throw(BadValue, "conatiner for the specified address range " << range.start_
@@ -178,8 +225,19 @@ FreeLeaseQueue::getContainer(const FreeLeaseQueue::Range& range) {
     return (cont->container_);
 }
 
+FreeLeaseQueue::ContainerPtr
+FreeLeaseQueue::getContainer(const FreeLeaseQueue::PrefixRange& range) const {
+    auto cont = containers_.find(range.start_);
+    if (cont == containers_.end()) {
+        isc_throw(BadValue, "conatiner for the specified prefix " << range.start_
+                  << " and delegated length of " << static_cast<int>(range.delegated_length_)
+                  << " does not exist");
+    }
+    return (cont->container_);
+}
+
 FreeLeaseQueue::ContainerDescriptor
-FreeLeaseQueue::getContainerDescriptor(const uint64_t range_index) {
+FreeLeaseQueue::getContainerDescriptor(const uint64_t range_index) const {
     if (containers_.get<2>().size() <= range_index) {
         isc_throw(BadValue, "conatiner for the specified range index " << range_index
                   << " does not exist");
@@ -188,21 +246,5 @@ FreeLeaseQueue::getContainerDescriptor(const uint64_t range_index) {
     return (cont);
 }
 
-IOAddress
-FreeLeaseQueue::popNextInternal(const Range& range, const bool push) {
-    auto cont = getContainer(range);
-    if (cont->empty()) {
-        return (IOAddress::IPV4_ZERO_ADDRESS());
-    }
-    auto& idx = cont->get<1>();
-    auto next = idx.front();
-    idx.pop_front();
-    if (push) {
-        idx.push_back(next);
-    }
-    return (next);
-
-}
-
 } // end of namespace isc::dhcp
 } // end of namespace isc
index 8782c28bd124f9faae44fc30cc87f7499869b9d1..445ab0818e6a3138e4bf6706b47e8588c3184b36 100644 (file)
@@ -9,6 +9,7 @@
 
 #include <asiolink/io_address.h>
 #include <dhcpsrv/address_range.h>
+#include <exceptions/exceptions.h>
 
 #include <boost/multi_index_container.hpp>
 #include <boost/multi_index/hashed_index.hpp>
 #include <boost/multi_index/sequenced_index.hpp>
 #include <boost/shared_ptr.hpp>
 
+#include <iterator>
 #include <map>
 
 namespace isc {
 namespace dhcp {
 
-/// @brief Container holding free leases for various address ranges.
+/// @brief Container holding free leases for various address and
+/// prefix ranges.
 ///
 /// Free leases can be stored in this container to provide the DHCP
 /// allocation engine with the fast lookup mechanism for available
 /// leases. This avoids costly lookup for available leases in the
 /// lease database backend when the client's packet is being processed.
 /// Instead, when the server is being configured, it iterates over
-/// the addresses in respective address pools, verifies if leases
-/// exist for these addresses and for each addresses for which it
-/// doesn't find the lease it creates an appropriate entry in this
-/// container.
+/// the addresses and/or prefixes in respective pools, verifies if leases
+/// exist for these addresses and/or prefixes and for each of them for
+/// which it doesn't find the lease it creates an appropriate entry in
+/// this container.
 ///
 /// This effectively moves the free lease lookup to the configuration
 /// phase. When the server is going to allocate a new lease, it will
@@ -47,19 +50,18 @@ namespace dhcp {
 /// requesting client at some later time.
 ///
 /// The container with free leases is optimized for two use cases.
-/// Firstly, it is optimized to get the next available address
-/// efficiently. In order to the get the next available address one
-/// should call the @c FreeLeaseQueue::next function. The range
-/// from which the address is to be returned must be specified in
-/// the call. The range corresponds to the address pool boundaries.
-/// If the address returned is rejected by the allocation engine
-/// for any reason, e.g. conflict with existing host reservations,
-/// the address goes to the end of the queue for that address
-/// range. The allocation engine may need to perform multiple
-/// calls to the @c next function until it gets the satifactionary
-/// IP address. However, it should be typically one call per
-/// allocation when no reservations are present or there is a low
-/// number of in pool reservations.
+/// Firstly, it is optimized to get the next available address or
+/// prefix efficiently. In order to the get the next available lease
+/// one should call the @c FreeLeaseQueue::next function. The range
+/// from which the lease is to be returned must be specified in
+/// the call. The range corresponds to the address or prefix pool
+/// boundaries. If the lease returned is rejected by the allocation
+/// engine for any reason, e.g. conflict with existing host reservations,
+/// the lease goes to the end of the queue for that range. The allocation
+/// engine may need to perform multiple calls to the @c next function
+/// until it gets the satifactionary lease. However, it should be
+/// typically one call per allocation when no reservations are present
+/// or there is a low number of in pool reservations.
 ///
 /// The second use case for which this container is optimized is
 /// the lease reclamation. This is the process by which expired
@@ -67,8 +69,8 @@ namespace dhcp {
 /// belonging to different pools expire at various times. When the
 /// expired leases are reclaimed the server doesn't know to which
 /// pools they belong. Since this container associates free leases
-/// with certain address ranges it is important that this container
-/// can efficiently identify the range. Once the range is identified,
+/// with certain ranges it is important that this container can
+/// efficiently identify the range. Once the range is identified,
 /// the reclaimed lease is appended at the end of the queue for that
 /// range.
 class FreeLeaseQueue {
@@ -77,12 +79,15 @@ public:
     /// @brief Structure representing address range in the @c FreeLeaseQueue.
     typedef AddressRange Range;
 
+    /// @brief Structure representing delegated prefix range in @c FreeLeaseQueue.
+    typedef PrefixRange PrefixRange;
+
     /// @brief Constructor.
     FreeLeaseQueue();
 
     /// @brief Adds new address range to the container.
     ///
-    /// The new range must not overlap with existing address ranges.
+    /// The new range must not overlap with existing ranges.
     ///
     /// @param range the new range to be added.
     /// @throw BadValue if the new range overlaps with any of the existing ranges.
@@ -98,12 +103,37 @@ public:
     /// @throw BadValue if the new range overlaps with any of the existing ranges.
     void addRange(const asiolink::IOAddress& start, const asiolink::IOAddress& end);
 
+    /// @brief Adds new delegated prefix range to the container.
+    ///
+    /// The new range must not overlap with existing ranges.
+    ///
+    /// @param range the new range to be added.
+    /// @throw BadValue of the new range overlaps with any of the existing ranges.
+    void addRange(const PrefixRange& range);
+
+    /// @brief Adds new delegated prefix range to the container.
+    ///
+    /// This variant of the method accepts the prefix range specified with three
+    /// parameters: prefix, prefix length and delegated prefix length.
+    ///
+    /// @param prefix range prefix.
+    /// @param prefix_length range prefix length.
+    /// @param delegated_length delegated prefix length.
+    /// @throw BadValue if the new range overlaps with any of the existing ranges.
+    void addRange(const asiolink::IOAddress& prefix, const uint8_t prefix_length,
+                  const uint8_t delegated_length);
+
     /// @brief Removes the range from the container.
     ///
     /// It removes all free leases associated with the removed address range.
     ///
-    /// @return true if the address range existed and was removed.
-    bool removeRange(const Range& range);
+    /// @param range range to be removed.
+    /// @tparam RangeType type of the range, i.e. @c Range or @c PrefixRange.
+    /// @return true if the range existed and was removed.
+    template<typename RangeType>
+    bool removeRange(const RangeType& range) {
+        return (containers_.get<1>().erase(range.start_) > 0);
+    }
 
     /// @brief Appends an address at the end of the queue for a range.
     ///
@@ -116,6 +146,17 @@ public:
     /// false otherwise.
     bool append(const asiolink::IOAddress& address);
 
+    /// @brief Appends a delegated prefix at the end of the queue for a range.
+    ///
+    /// This method is typically called when a lease expires and is reclaimed.
+    /// The range is not specified by the caller. The method identifies
+    /// appropriate prefix range for that prefix.
+    ///
+    /// @param prefix delegated prefix to be appended to the range queue.
+    /// @return true if the range was found and the prefix was appended,
+    /// false otherwise.
+    bool append(const asiolink::IOAddress& prefix, const uint8_t delegated_length);
+
     /// @brief Appends an address at the end of the queue for a range.
     ///
     /// This method is typically called upon server startup or reconfiguration.
@@ -130,7 +171,21 @@ public:
     /// range or if the given range does not exist.
     void append(const Range& range, const asiolink::IOAddress& address);
 
-    /// @brief Appends an address at the end of the queue for a range.
+    /// @brief Appends a prefix at the end of the queue for a range.
+    ///
+    /// This method is typically called upon server startup or reconfiguration.
+    /// For each delegated prefix belonging to the pool for which there is no
+    /// lease this method appends free delegated prefix at the end of the queue
+    /// for the specified range.
+    ///
+    /// @param range specifies the prefix range to which the given prefix
+    /// belongs.
+    /// @param prefix delegated prefix to be appended to the range queue.
+    /// @throw BadValue if the prefix does not belong to the specified
+    /// range or if the given range does not exist.
+    void append(const PrefixRange& range, const asiolink::IOAddress& prefix);
+
+    /// @brief Appends an address or prefix at the end of the queue for a range.
     ///
     /// This variant of the @c append method is called upon server startup or
     /// reconfiguration. It is considered faster than the overload of this
@@ -140,41 +195,61 @@ public:
     /// this method variant to add many addresses to the same range.
     ///
     /// @param range_index address range index returned by @c getRangeIndex.
-    /// @param address address to be appended to the range queue.
-    /// @throw BadValue if the address does not belong to the specified
-    /// range or if the given range does not exist.
-    void append(const uint64_t range_index, const asiolink::IOAddress& address);
+    /// @param ip address or prefix to be appended to the range queue.
+    /// @throw BadValue if the address or prefix does not belong to the
+    /// specified range or if the given range does not exist.
+    void append(const uint64_t range_index, const asiolink::IOAddress& ip);
 
     /// @brief Removes the specified address from the free addresses.
     ///
     /// This method should be called upon successful lease allocation for
     /// that address.
     ///
-    /// @param range from which the free address should be removed.
+    /// @param range range from which the free address should be removed.
     /// @return true if the address was found and successfully removed,
     /// false otherwise.
     /// @throw BadValue if the range does not exist.
     bool use(const Range& range, const asiolink::IOAddress& address);
 
-    /// @brief Returns next free address in the range.
+    /// @brief Removes the specified delegated prefix from the free prefixes.
     ///
-    /// This address is moved to the end of the queue for the range.
+    /// This method should be called upon successful lease allocation for
+    /// that delegated prefix.
     ///
-    /// @param range range for which next address is to be returned.
-    /// @return Next free address in that range.
+    /// @param range range from which the free prefix should be removed.
+    /// @return true if the prefix was found and successfully removed,
+    /// false otherwise.
     /// @throw BadValue if the range does not exist.
-    asiolink::IOAddress next(const Range& range);
+    bool use(const PrefixRange& range, const asiolink::IOAddress& prefix);
 
-    /// @brief Pops and returns next free address in the range.
+    /// @brief Returns next free address or delegated prefix in the range.
     ///
-    /// The address is removed from the queue. If the caller, i.e. allocation
-    /// engine decides to not use this address it should be appended to
-    /// the queue using the @c append method.
+    /// This address or delegated prefix is moved to the end of the queue
+    /// for the range.
     ///
     /// @param range range for which next address is to be returned.
-    /// @return Next free address in that range.
+    /// @tparam RangeType type of the range, i.e. @c Range or @c PrefixRange.
+    /// @return Next free address or delegated prefix in that range.
     /// @throw BadValue if the range does not exist.
-    asiolink::IOAddress pop(const Range& range);
+    template<typename RangeType>
+    asiolink::IOAddress next(const RangeType& range) {
+        return (popNextInternal(range, true));
+    }
+
+    /// @brief Pops and returns next free address or delegated prefix in the range.
+    ///
+    /// The address or delegated prefix is removed from the queue. If the caller,
+    /// i.e. allocation engine decides to not use this address or prefix it
+    /// should be appended to the queue using the @c append method.
+    ///
+    /// @param range range for which next address or prefix is to be returned.
+    /// @tparam RangeType type of the range, i.e. @c Range or @c PrefixRange.
+    /// @return Next free address or delegated prefix in that range.
+    /// @throw BadValue if the range does not exist.
+    template<typename RangeType>
+    asiolink::IOAddress pop(const RangeType& range) {
+        return (popNextInternal(range, false));
+    }
 
     /// @brief Returns range index.
     ///
@@ -183,18 +258,27 @@ public:
     /// range itself because it uses random access index.
     ///
     /// @param range range which index is to be returned.
+    /// @tparam RangeType type of the range, i.e. @c Range or PrefixRange.
     /// @return range index.
     /// @throw BadValue if the range does not exist.
-    uint64_t getRangeIndex(const Range& range) const;
+    template<typename RangeType>
+    uint64_t getRangeIndex(const RangeType& range) const {
+        auto cont = containers_.get<1>().find(range.start_);
+        if (cont == containers_.get<1>().end()) {
+            isc_throw(BadValue, "conatiner for the specified range " << range.start_
+                      << ":" << range.end_ << " does not exist");
+        }
+        return (std::distance(containers_.get<2>().begin(), containers_.project<2>(cont)));
+    }
 
 private:
 
     /// @brief Container holding free leases for a range.
     ///
-    /// This container holds free addresses for a given address range.
-    /// It contains two indexes. The first index orders free addresses
-    /// by address values. The second index is sequential and serves
-    /// as a double-ended queue from which addresses are picked.
+    /// This container holds free leases for a given range. It contains two
+    /// indexes. The first index orders free leases by address values. The
+    /// second index is sequential and serves as a double-ended queue from
+    /// which leases are picked.
     typedef boost::multi_index_container<
         isc::asiolink::IOAddress,
         boost::multi_index::indexed_by<
@@ -205,26 +289,28 @@ private:
         >
     > Container;
 
-    /// Pointer to the container of free addresses for a range.
+    /// Pointer to the container of free leases for a range.
     typedef boost::shared_ptr<Container> ContainerPtr;
 
     /// @brief Helper structure associating a range with the container of
-    /// free addresses.
+    /// free leases.
     struct ContainerDescriptor {
         /// Range start.
         asiolink::IOAddress range_start_;
         /// Range end.
         asiolink::IOAddress range_end_;
+        /// Delegated length (used in prefix delegation).
+        uint8_t delegated_length_;
         /// Container holding free addresses for the range.
         ContainerPtr container_;
     };
 
-    /// @brief Collection (container) of containers for various address ranges.
+    /// @brief Collection (container) of containers for various ranges.
     ///
-    /// This container provides two indexes for searching a given address range
-    /// along with the appropriate container holding free leases. The first index
-    /// is by the range start value. The second index is the random access index
-    /// allowing faster access once the range index is known.
+    /// This container provides two indexes for searching a given range along with
+    /// the appropriate container holding free leases. The first index is by the
+    /// range start value. The second index is the random access index allowing
+    /// faster access once the range index is known.
     typedef boost::multi_index_container<
         ContainerDescriptor,
         boost::multi_index::indexed_by<
@@ -240,12 +326,41 @@ private:
         >
     > Containers;
 
+    /// @brief Checks if the specified address or delegated prefix is within the
+    /// range.
+    ///
+    /// @param range range for which the check should be performed.
+    /// @param ip address or delegated prefix for which the check should be performed.
+    /// @param prefix boolean value indicating if the specified IP is an address or
+    /// delegated prefix - used in error logging.
+    /// @tparam RangeType type of the range used, i.e. @c Range or @c PrefixRange.
+    /// @throw BadValue of the address or delegated prefix does not belong to the range.
+    template<typename RangeType>
+    void checkRangeBoundaries(const RangeType& range, const asiolink::IOAddress& ip,
+                              const bool prefix = false) const;
+
+    /// @brief Checks if the specified address or prefix range overlaps with an
+    /// existing range.
+    ///
+    /// @param start beginning of the range.
+    /// @param end end of the range.
+    /// @throw BadValue if the specified range overlaps with an existing range.
+    void checkRangeOverlaps(const asiolink::IOAddress& start,
+                            const asiolink::IOAddress& end) const;
+
     /// @brief Returns container for a given address range.
     ///
     /// @param range range for which the container should be returned.
     /// @return Pointer to the container (if found).
     /// @throw BadValue if the specified range does not exist.
-    ContainerPtr getContainer(const Range& range);
+    ContainerPtr getContainer(const Range& range) const;
+
+    /// @brief Returns container for a given prefix range.
+    ///
+    /// @param range range for which the container should be returned.
+    /// @return Pointer to the container (if found).
+    /// @throw BadValue if the specified range does not exist.
+    ContainerPtr getContainer(const PrefixRange& range) const;
 
     /// @brief Returns container descriptor for a given range index.
     ///
@@ -256,7 +371,7 @@ private:
     /// returned.
     /// @return Range descriptor if found.
     /// @throw BadValue if the range with the given index does not exist.
-    ContainerDescriptor getContainerDescriptor(const uint64_t range_index);
+    ContainerDescriptor getContainerDescriptor(const uint64_t range_index) const;
 
     /// @brief This is internal implemenation of the @c next and @c pop
     /// methods.
@@ -267,7 +382,21 @@ private:
     /// queue (if false).
     /// @return Next free address in that range.
     /// @throw BadValue if the range does not exist.
-    asiolink::IOAddress popNextInternal(const Range& range, const bool push);
+    template<typename RangeType>
+    asiolink::IOAddress popNextInternal(const RangeType& range, const bool push) {
+        auto cont = getContainer(range);
+        if (cont->empty()) {
+            return (range.start_.isV4() ? asiolink::IOAddress::IPV4_ZERO_ADDRESS() :
+                    asiolink::IOAddress::IPV6_ZERO_ADDRESS());
+        }
+        auto& idx = cont->template get<1>();
+        auto next = idx.front();
+        idx.pop_front();
+        if (push) {
+            idx.push_back(next);
+        }
+        return (next);
+    }
 
     /// @brief Holds a collection of containers with free leases for each
     /// address range.
index e0bb0550197ffd77305ac209a84dc411d76ec1ac..1259482b567918e2796610a3ea959cc32ce5a1c8 100644 (file)
@@ -6,7 +6,7 @@
 
 #include <config.h>
 #include <dhcpsrv/address_range.h>
-
+#include <boost/scoped_ptr.hpp>
 #include <gtest/gtest.h>
 
 using namespace isc;
@@ -18,7 +18,7 @@ namespace {
 // This test verifies that an exception is thrown upon an attempt to
 // create an address range from invalid values and that no exception
 // is thrown when the values are correct.
-TEST(AddressRangeTest, constructorWithInvalidValues) {
+TEST(AddressRangeTest, constructor) {
     // The start address must be lower or equal the end address.
     EXPECT_THROW(AddressRange(IOAddress("192.0.2.100"), IOAddress("192.0.2.99")),
                  BadValue);
@@ -29,4 +29,35 @@ TEST(AddressRangeTest, constructorWithInvalidValues) {
     EXPECT_NO_THROW(AddressRange(IOAddress("192.0.2.100"), IOAddress("192.0.2.100")));
 }
 
+// This test verifies successful construction of the prefix range.
+TEST(PrefixRangeTest, constructor) {
+    boost::scoped_ptr<PrefixRange> range;
+    ASSERT_NO_THROW(range.reset(new PrefixRange(IOAddress("2001:db8:1::"), 64, 96)));
+    EXPECT_EQ("2001:db8:1::", range->start_.toText());
+    EXPECT_EQ("2001:db8:1:0:ffff:ffff::", range->end_.toText());
+
+    ASSERT_NO_THROW(range.reset(new PrefixRange(IOAddress("2001:db8:1:2::"), 80, 120)));
+    EXPECT_EQ("2001:db8:1:2::", range->start_.toText());
+    EXPECT_EQ("2001:db8:1:2:0:ffff:ffff:ff00", range->end_.toText());
+
+    ASSERT_NO_THROW(range.reset(new PrefixRange(IOAddress("2001:db8:1:2::"), 80, 127)));
+    EXPECT_EQ("2001:db8:1:2::", range->start_.toText());
+    EXPECT_EQ("2001:db8:1:2:0:ffff:ffff:fffe", range->end_.toText());
+}
+
+// This test verifies that exception is thrown upon an attempt to
+// create a prefix range from invalid values.
+TEST(PrefixRangeTest, constructorWithInvalidValues) {
+    boost::scoped_ptr<PrefixRange> range;
+    // It must be IPv6 prefix.
+    EXPECT_THROW(PrefixRange(IOAddress("192.0.2.0"), 8, 16), BadValue);
+    // Delegated length must not be lower than prefix length.
+    EXPECT_THROW(PrefixRange(IOAddress("2001:db8:1::"), 96, 64), BadValue);
+    // Lengths must not exceed 128.
+    EXPECT_THROW(PrefixRange(IOAddress("2001:db8:1::"), 200, 204), BadValue);
+    // End must not be lower than start.
+    EXPECT_THROW(PrefixRange(IOAddress("2001:db8:1:1::6:0"), IOAddress("2001:db8:1::5:0"), 112),
+                 BadValue);
+}
+
 } // end of anonymous namespace
index 7b48a7656736bab7c9f3f1946b949b2a09bfb053..01a7e883baaf67433810c91fc44034fb50190223 100644 (file)
@@ -15,22 +15,6 @@ using namespace isc::dhcp;
 
 namespace {
 
-// This test verifies that an exception is thrown upon an attempt to
-// create an address range from invalid values and that no exception
-// is thrown when the values are correct.
-TEST(FreeLeaseQueueRangeTest, constructorWithInvalidValues) {
-    FreeLeaseQueue lq;
-
-    // The start address must be lower or equal the end address.
-    EXPECT_THROW(FreeLeaseQueue::Range(IOAddress("192.0.2.100"), IOAddress("192.0.2.99")),
-                 BadValue);
-    // The start and end address must be of the same family.
-    EXPECT_THROW(FreeLeaseQueue::Range(IOAddress("192.0.2.100"), IOAddress("2001:db8:1::1")),
-                 BadValue);
-    // It is allowed to create address range with a single IP address.
-    EXPECT_NO_THROW(FreeLeaseQueue::Range(IOAddress("192.0.2.100"), IOAddress("192.0.2.100")));
-}
-
 // This test verifies that it is not allowed to add a range that overlaps with
 // any existing range.
 TEST(FreeLeaseQueueTest, addRangeOverlapping) {
@@ -143,6 +127,19 @@ TEST(FreeLeaseQueueTest, addRangeOverlapping) {
                  BadValue);
 }
 
+// This test verifies that it is not allowed to add a prefix range that overlaps with
+// any existing range.
+TEST(FreeLeaseQueueTest, addPrefixRangeOverlapping) {
+    FreeLeaseQueue lq;
+    // Add the initial range. This should succeed.
+    ASSERT_NO_THROW(lq.addRange(IOAddress("2001:db8:1::"), 64, 96));
+
+    EXPECT_THROW(lq.addRange(IOAddress("2001:db8:1:0:0:5:ffff:0"), 96, 120),
+                 BadValue);
+    EXPECT_THROW(lq.addRange(IOAddress("2001:db8:1::0"), 80, 88),
+                 BadValue);
+}
+
 // This test verifies that a range can be removed from the container.
 TEST(FreeLeaseQueueTest, removeRange) {
     FreeLeaseQueue lq;
@@ -175,6 +172,38 @@ TEST(FreeLeaseQueueTest, removeRange) {
     EXPECT_FALSE(removed);
 }
 
+// This test verifies that a prefix range can be removed from the container.
+TEST(FreeLeaseQueueTest, removePrefixRange) {
+    FreeLeaseQueue lq;
+
+    // Add two ranges.
+    FreeLeaseQueue::PrefixRange range1(IOAddress("3000::"), 64, 96);
+    FreeLeaseQueue::PrefixRange range2(IOAddress("3001::"), 64, 96);
+    ASSERT_NO_THROW(lq.addRange(range1));
+    ASSERT_NO_THROW(lq.addRange(range2));
+
+    bool removed;
+
+    // Expect true to be returned when the range is removed.
+    ASSERT_NO_THROW(removed = lq.removeRange(range1));
+    EXPECT_TRUE(removed);
+
+    // An attempt to append a prefix to the removed range should not succeed.
+    EXPECT_FALSE(lq.append(IOAddress("3000::5:0:0"), 96));
+
+    // Removing it the second time should return false to indicate that the range
+    // was no longer there.
+    ASSERT_NO_THROW(removed = lq.removeRange(range1));
+    EXPECT_FALSE(removed);
+
+    // Same for the second range.
+    ASSERT_NO_THROW(removed = lq.removeRange(range2));
+    EXPECT_TRUE(removed);
+
+    ASSERT_NO_THROW(removed = lq.removeRange(range2));
+    EXPECT_FALSE(removed);
+}
+
 // This test verifies that an attempt to use an address from outside the
 // given range throws and that an attempt to use non-existing in-range
 // address returns false.
@@ -193,6 +222,24 @@ TEST(FreeLeaseQueueTest, useInvalidAddress) {
     EXPECT_FALSE(used);
 }
 
+// This test verifies that an attempt to use a prefix from outside the
+// given range throws and that an attempt to use non-existing in-range
+// address returns false.
+TEST(FreeLeaseQueueTest, useInvalidPrefix) {
+    FreeLeaseQueue::PrefixRange range(IOAddress("2001:db8:1::"), 64, 96);
+
+    FreeLeaseQueue lq;
+    ASSERT_NO_THROW(lq.addRange(range));
+
+    // Using out of range prefix.
+    EXPECT_THROW(lq.use(range, IOAddress("2001:db8:2::")), BadValue);
+
+    // Using in-range prefix but not existing in the container.
+    bool used = false;
+    ASSERT_NO_THROW(used = lq.use(range, IOAddress("2001:db8:1::5:0:0")));
+    EXPECT_FALSE(used);
+}
+
 // Check that duplicates are eliminated when appending free address to
 // the range queue.
 TEST(FreeLeaseQueueTest, appendDuplicates) {
@@ -276,9 +323,48 @@ TEST(FreeLeaseQueueTest, next) {
     EXPECT_EQ("192.0.3.23", next.toText());
 }
 
-// This test verifies that it is possible to pop next address from the given
+// This test verifies that it is possible to pick next prefix from the given
 // range.
+TEST(FreeLeaseQueueTest, nextPrefix) {
+    FreeLeaseQueue lq;
+
+    FreeLeaseQueue::PrefixRange range1(IOAddress("2001:db8:1::"), 64, 96);
+    ASSERT_NO_THROW(lq.addRange(range1));
+
+    ASSERT_NO_THROW(lq.append(range1, IOAddress("2001:db8:1::4:0")));
+    ASSERT_NO_THROW(lq.append(range1, IOAddress("2001:db8:1::7:0")));
+    ASSERT_NO_THROW(lq.append(range1, IOAddress("2001:db8:1::3:0")));
+    ASSERT_NO_THROW(lq.append(range1, IOAddress("2001:db8:1::")));
+
+    IOAddress next = IOAddress::IPV6_ZERO_ADDRESS();
+    ASSERT_NO_THROW(next = lq.next(range1));
+    EXPECT_EQ("2001:db8:1::4:0", next.toText());
+
+    ASSERT_NO_THROW(next = lq.next(range1));
+    EXPECT_EQ("2001:db8:1::7:0", next.toText());
 
+    ASSERT_NO_THROW(next = lq.next(range1));
+    EXPECT_EQ("2001:db8:1::3:0", next.toText());
+
+    ASSERT_NO_THROW(next = lq.next(range1));
+    EXPECT_EQ("2001:db8:1::", next.toText());
+
+    ASSERT_NO_THROW(next = lq.next(range1));
+    EXPECT_EQ("2001:db8:1::4:0", next.toText());
+
+    // Use (remove) the prefix from the range.
+    bool used = false;
+    ASSERT_NO_THROW(used = lq.use(range1, IOAddress("2001:db8:1::7:0")));
+    EXPECT_TRUE(used);
+
+    // After we have removed the second prefix, the third prefix should be
+    // returned.
+    ASSERT_NO_THROW(next = lq.next(range1));
+    EXPECT_EQ("2001:db8:1::3:0", next.toText());
+}
+
+// This test verifies that it is possible to pop next address from the given
+// range.
 TEST(FreeLeaseQueueTest, pop) {
     FreeLeaseQueue lq;
 
@@ -318,6 +404,43 @@ TEST(FreeLeaseQueueTest, pop) {
     EXPECT_TRUE(next.isV4Zero());
 }
 
+// This test verifies that it is possible to pop next prefix from the given
+// range.
+TEST(FreeLeaseQueueTest, popPrefix) {
+    FreeLeaseQueue lq;
+
+    // Add the range.
+    FreeLeaseQueue::PrefixRange range1(IOAddress("2001:db8:1::"), 64, 96);
+    ASSERT_NO_THROW(lq.addRange(range1));
+
+    // Append several prefixes to that range.
+    ASSERT_NO_THROW(lq.append(range1, IOAddress("2001:db8:1::4:0")));
+    ASSERT_NO_THROW(lq.append(range1, IOAddress("2001:db8:1::7:0")));
+    ASSERT_NO_THROW(lq.append(range1, IOAddress("2001:db8:1::3:0")));
+    ASSERT_NO_THROW(lq.append(range1, IOAddress("2001:db8:1::")));
+
+    // Make sure we get retrieve them in the order in which they have
+    // been added.
+    IOAddress next = IOAddress::IPV6_ZERO_ADDRESS();
+    ASSERT_NO_THROW(next = lq.pop(range1));
+    EXPECT_EQ("2001:db8:1::4:0", next.toText());
+
+    ASSERT_NO_THROW(next = lq.pop(range1));
+    EXPECT_EQ("2001:db8:1::7:0", next.toText());
+
+    ASSERT_NO_THROW(next = lq.pop(range1));
+    EXPECT_EQ("2001:db8:1::3:0", next.toText());
+
+    ASSERT_NO_THROW(next = lq.pop(range1));
+    EXPECT_EQ("2001:db8:1::", next.toText());
+
+    // After we went over all of them they should all be gone from the
+    // container and the IPv6 zero address should be returned.
+    ASSERT_NO_THROW(next = lq.pop(range1));
+    EXPECT_TRUE(next.isV6Zero());
+}
+
+
 // Check that out of bounds address can't be appended to the range.
 TEST(FreeLeaseQueueTest, nextRangeMismatch) {
     FreeLeaseQueue::Range range(IOAddress("192.0.2.1"), IOAddress("192.0.2.255"));
@@ -370,6 +493,50 @@ TEST(FreeLeaseQueueTest, detectRange) {
     EXPECT_FALSE(lq.append(IOAddress("10.0.0.0")));
 }
 
+// Check that it is possible to return a delegated prefix to the range and
+// that the appropriate range is detected.
+TEST(FreeLeaseQueueTest, detectPrefixRange) {
+    FreeLeaseQueue lq;
+
+    // Create three ranges.
+    FreeLeaseQueue::PrefixRange range1(IOAddress("2001:db8:1::"), 64, 96);
+    FreeLeaseQueue::PrefixRange range2(IOAddress("2001:db8:2::"), 112, 120);
+    FreeLeaseQueue::PrefixRange range3(IOAddress("2001:db8:3::"), 96, 104);
+    ASSERT_NO_THROW(lq.addRange(range1));
+    ASSERT_NO_THROW(lq.addRange(range2));
+    ASSERT_NO_THROW(lq.addRange(range3));
+
+    // Append some prefixes matching the first range.
+    ASSERT_NO_THROW(lq.append(IOAddress("2001:db8:1::7:0"), 96));
+    ASSERT_NO_THROW(lq.append(IOAddress("2001:db8:1::100:0"), 96));
+    ASSERT_NO_THROW(lq.append(IOAddress("2001:db8:1::4:0"), 96));
+
+    // Make sure that these prefixes have been appended to that range.
+    IOAddress next(0);
+    ASSERT_NO_THROW(next = lq.next(range1));
+    EXPECT_EQ("2001:db8:1::7:0", next.toText());
+
+    ASSERT_NO_THROW(next = lq.next(range1));
+    EXPECT_EQ("2001:db8:1::100:0", next.toText());
+
+    ASSERT_NO_THROW(next = lq.next(range1));
+    EXPECT_EQ("2001:db8:1::4:0", next.toText());
+
+    // Append some prefixes matching the remaining two ranges.
+    ASSERT_NO_THROW(lq.append(IOAddress("2001:db8:2::10"), 120));
+    ASSERT_NO_THROW(lq.append(IOAddress("2001:db8:3::50"), 104));
+
+    ASSERT_NO_THROW(next = lq.next(range3));
+    EXPECT_EQ("2001:db8:3::50", next.toText());
+
+    ASSERT_NO_THROW(next = lq.next(range2));
+    EXPECT_EQ("2001:db8:2::10", next.toText());
+
+    // Appending out of bounds prefix should not succeed.
+    EXPECT_FALSE(lq.append(IOAddress("2001:db8:4::10"), 96));
+    EXPECT_FALSE(lq.append(IOAddress("2001:db8:2::30"), 97));
+}
+
 // This test verifies that false is returned if the specified address to be
 // appended does not belong to any of the existing ranges.
 TEST(FreeLeaseQueueTest, detectRangeFailed) {
@@ -412,4 +579,35 @@ TEST(FreeLeaseQueueTest, appendThroughRangeIndex) {
     ASSERT_THROW(lq.append(index3, IOAddress("192.0.3.34")), BadValue);
 }
 
+// This test verifies that it is possible to append delegated prefixes to the
+// selected range via random access index.
+TEST(FreeLeaseQueueTest, appendPrefixThroughRangeIndex) {
+    FreeLeaseQueue lq;
+
+    FreeLeaseQueue::PrefixRange range1(IOAddress("2001:db8:1::"), 64, 96);
+    FreeLeaseQueue::PrefixRange range2(IOAddress("2001:db8:2::"), 64, 96);
+    FreeLeaseQueue::PrefixRange range3(IOAddress("2001:db8:3::"), 64, 96);
+    ASSERT_NO_THROW(lq.addRange(range1));
+    ASSERT_NO_THROW(lq.addRange(range2));
+    ASSERT_NO_THROW(lq.addRange(range3));
+
+    uint64_t index1 = 0;
+    ASSERT_NO_THROW(index1 = lq.getRangeIndex(range1));
+    uint64_t index2 = 0;
+    ASSERT_NO_THROW(index2 = lq.getRangeIndex(range2));
+    uint64_t index3 = 0;
+    ASSERT_NO_THROW(index3 = lq.getRangeIndex(range3));
+
+    EXPECT_NE(index1, index2);
+    EXPECT_NE(index2, index3);
+    EXPECT_NE(index1, index3);
+
+    ASSERT_NO_THROW(lq.append(index1, IOAddress("2001:db8:1::5:0:0")));
+    ASSERT_NO_THROW(lq.append(index2, IOAddress("2001:db8:2::7:0:0")));
+    ASSERT_NO_THROW(lq.append(index3, IOAddress("2001:db8:3::2:0:0")));
+
+    ASSERT_THROW(lq.append(index2, IOAddress("2001:db8:3::3:0:0")), BadValue);
+    ASSERT_THROW(lq.append(index3, IOAddress("2001:db8:2::8:0:0")), BadValue);
+}
+
 } // end of anonymous namespace