// 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>
}
}
+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
// 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>
// 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>()});
}
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
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()) {
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_
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");
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
#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
/// 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
/// 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 {
/// @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.
/// @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.
///
/// 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.
/// 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
/// 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.
///
/// 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<
>
> 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<
>
> 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.
///
/// 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.
/// 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.
#include <config.h>
#include <dhcpsrv/address_range.h>
-
+#include <boost/scoped_ptr.hpp>
#include <gtest/gtest.h>
using namespace isc;
// 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);
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
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) {
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;
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.
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) {
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;
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"));
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) {
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