summaryrefslogtreecommitdiffstats
path: root/src/lib/dhcpsrv/free_lease_queue.h
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/dhcpsrv/free_lease_queue.h')
-rw-r--r--src/lib/dhcpsrv/free_lease_queue.h412
1 files changed, 412 insertions, 0 deletions
diff --git a/src/lib/dhcpsrv/free_lease_queue.h b/src/lib/dhcpsrv/free_lease_queue.h
new file mode 100644
index 0000000..5996511
--- /dev/null
+++ b/src/lib/dhcpsrv/free_lease_queue.h
@@ -0,0 +1,412 @@
+// Copyright (C) 2020-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef FREE_LEASE_QUEUE_H
+#define FREE_LEASE_QUEUE_H
+
+#include <asiolink/io_address.h>
+#include <dhcpsrv/ip_range.h>
+#include <exceptions/exceptions.h>
+
+#include <boost/multi_index_container.hpp>
+#include <boost/multi_index/hashed_index.hpp>
+#include <boost/multi_index/identity.hpp>
+#include <boost/multi_index/indexed_by.hpp>
+#include <boost/multi_index/member.hpp>
+#include <boost/multi_index/ordered_index.hpp>
+#include <boost/multi_index/random_access_index.hpp>
+#include <boost/multi_index/sequenced_index.hpp>
+#include <boost/shared_ptr.hpp>
+
+#include <iterator>
+#include <map>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Queue holding free leases for various address and prefix ranges.
+///
+/// Free leases can be stored in this queue 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 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 the queue.
+///
+/// This effectively moves the free lease lookup to the configuration phase.
+/// When the server is going to allocate a new lease, it will make an
+/// appropriate query to this queue to get the next available lease for the
+/// given pool. If the server decides to use this lease, it is removed from
+/// this queue. Conversely, when the lease expires (and is reclaimed) or is
+/// released it is returned to this queue so it can be offered to a requesting
+/// client at some later time.
+///
+/// The queue with free leases is optimized for two use cases. Firstly, it is
+/// optimized to get the next available address or prefix efficiently. In order
+/// to the get the next available lease the allocation engine can 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. The @c next function puts the returned lease
+/// at the end of the queue. If the lease is rejected by the allocation engine
+/// for any reason, e.g. conflict with existing host reservations, the allocation
+/// engine simply calls @c next function again. It 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. If the
+/// allocation engine decides to allocate the returned lease, it must call
+/// @c FreeLeaseQueue::use to remove this lease from the queue.
+///
+/// If the @c FreeLeaseQueue::pop method is used instead of @c next to get
+/// the next available lease the returned lease is removed from the queue.
+/// In that case, the allocation engine must not call @c FreeLeaseQueue::use
+/// function when this lease is allocated. However, if the allocation engine
+/// rejects this lease the @c FreeLeaseQueue::append must be called to return
+/// the lease to the queue.
+///
+/// The second use case for which this queue is optimized is the lease
+/// reclamation. This is the process by which expired leases are again made
+/// available for allocation. The leases 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 queue associates free leases with
+/// certain ranges it is important that this container can efficiently identify
+/// the range. Once the range is identified, the reclaimed lease is appended to
+/// the end of the queue for that range.
+///
+/// @todo Methods of this class should be called in thread safe context. Otherwise
+/// they should be made thread safe.
+class FreeLeaseQueue {
+public:
+
+ /// @brief Constructor.
+ FreeLeaseQueue();
+
+ /// @brief Adds new address range to the queue.
+ ///
+ /// 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.
+ void addRange(const AddressRange& range);
+
+ /// @brief Adds new address range to the queue.
+ ///
+ /// This variant of the method accepts the address range as a pair of
+ /// start/end arguments.
+ ///
+ /// @param start beginning of the new address range.
+ /// @param end end of the new address range.
+ /// @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 queue.
+ ///
+ /// 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 queue.
+ ///
+ /// 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 queue.
+ ///
+ /// It removes all free leases associated with the removed address 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 (ranges_.get<1>().erase(range.start_) > 0);
+ }
+
+ /// @brief Appends an address to 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 address range for that address.
+ ///
+ /// @param address address to be appended to the range queue.
+ /// @return true if the range was found and the address was appended,
+ /// false otherwise.
+ bool append(const asiolink::IOAddress& address);
+
+ /// @brief Appends a delegated prefix to 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.
+ /// @param delegated_length delegated prefix length.
+ /// @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 to the end of the queue for a range.
+ ///
+ /// This method is typically called upon server startup or reconfiguration.
+ /// For each address belonging to the pool for which there is no lease
+ /// this method appends free address at the end of the queue for the
+ /// specified range.
+ ///
+ /// @param range specifies the address range to which the given address
+ /// belongs.
+ /// @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 AddressRange& range, const asiolink::IOAddress& address);
+
+ /// @brief Appends a prefix to 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 to 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
+ /// method taking the @c Range structure as an argument. The range is
+ /// identified by the @c range_index which designates the range index
+ /// in the queue returned by the @c getRangeIndex method. Use
+ /// this method variant to add many addresses to the same range.
+ ///
+ /// @param range_index address range index returned by @c getRangeIndex.
+ /// @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 range from which the free address should be removed.
+ /// @param address address to remove.
+ /// @return true if the address was found and successfully removed,
+ /// false otherwise.
+ /// @throw BadValue if the range does not exist.
+ bool use(const AddressRange& range, const asiolink::IOAddress& address);
+
+ /// @brief Removes the specified delegated prefix from the free prefixes.
+ ///
+ /// This method should be called upon successful lease allocation for
+ /// that delegated prefix.
+ ///
+ /// @param range range from which the free prefix should be removed.
+ /// @param prefix prefix to remove.
+ /// @return true if the prefix was found and successfully removed,
+ /// false otherwise.
+ /// @throw BadValue if the range does not exist.
+ bool use(const PrefixRange& range, const asiolink::IOAddress& prefix);
+
+ /// @brief Returns next free address or delegated prefix in the range.
+ ///
+ /// 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.
+ /// @tparam RangeType type of the range, i.e. @c AddressRange 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 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 AddressRange 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.
+ ///
+ /// The range index designates the position of the range within the queue.
+ /// Searching for a range using the index is faster than searching by the
+ /// 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 AddressRange or PrefixRange.
+ /// @return range index.
+ /// @throw BadValue if the range does not exist.
+ template<typename RangeType>
+ uint64_t getRangeIndex(const RangeType& range) const {
+ auto cont = ranges_.get<1>().find(range.start_);
+ if (cont == ranges_.get<1>().end()) {
+ isc_throw(BadValue, "container for the specified range " << range.start_
+ << ":" << range.end_ << " does not exist");
+ }
+ return (std::distance(ranges_.get<2>().begin(), ranges_.project<2>(cont)));
+ }
+
+private:
+
+ /// @brief Queue holding free leases for a range.
+ ///
+ /// 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<
+ boost::multi_index::ordered_unique<
+ boost::multi_index::identity<asiolink::IOAddress>
+ >,
+ boost::multi_index::sequenced<>
+ >
+ > Leases;
+
+ /// Pointer to the queue of free leases for a range.
+ typedef boost::shared_ptr<Leases> LeasesPtr;
+
+ /// @brief Helper structure associating a range with the queue of
+ /// free leases.
+ struct RangeDescriptor {
+ /// Range start.
+ asiolink::IOAddress range_start_;
+ /// Range end.
+ asiolink::IOAddress range_end_;
+ /// Delegated length (used in prefix delegation).
+ uint8_t delegated_length_;
+ /// Queue holding free addresses for the range.
+ LeasesPtr leases_;
+ };
+
+ /// @brief Collection (container) of containers for various ranges.
+ ///
+ /// 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<
+ RangeDescriptor,
+ boost::multi_index::indexed_by<
+ boost::multi_index::ordered_unique<
+ boost::multi_index::member<RangeDescriptor, asiolink::IOAddress,
+ &RangeDescriptor::range_start_>
+ >,
+ boost::multi_index::hashed_unique<
+ boost::multi_index::member<RangeDescriptor, asiolink::IOAddress,
+ &RangeDescriptor::range_start_>
+ >,
+ boost::multi_index::random_access<>
+ >
+ > Ranges;
+
+ /// @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 AddressRange 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 queue 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.
+ LeasesPtr getLeases(const AddressRange& range) const;
+
+ /// @brief Returns queue 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.
+ LeasesPtr getLeases(const PrefixRange& range) const;
+
+ /// @brief Returns descriptor for a given range index.
+ ///
+ /// The returned descriptor includes the range boundaries and also the
+ /// pointer to the queue with free leases for the range.
+ ///
+ /// @param range_index index of the range which descriptor should be
+ /// returned.
+ /// @return Range descriptor if found.
+ /// @throw BadValue if the range with the given index does not exist.
+ RangeDescriptor getRangeDescriptor(const uint64_t range_index) const;
+
+ /// @brief This is internal implementation of the @c next and @c pop
+ /// methods.
+ ///
+ /// @param range range for which next address is to be returned.
+ /// @param push boolean flag indicating if the value should be appended
+ /// to the end of the queue upon return (if true) or removed from the
+ /// queue (if false).
+ /// @return Next free address in that range.
+ /// @throw BadValue if the range does not exist.
+ template<typename RangeType>
+ asiolink::IOAddress popNextInternal(const RangeType& range, const bool push) {
+ auto cont = getLeases(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.
+ Ranges ranges_;
+};
+
+} // end of namespace isc::dhcp
+} // end of namespace isc
+
+#endif // FREE_LEASE_QUEUE_H