]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[5437] Select last used subnet for new allocations in DHCPv6.
authorMarcin Siodelski <marcin@isc.org>
Tue, 27 Feb 2018 18:52:10 +0000 (19:52 +0100)
committerMarcin Siodelski <marcin@isc.org>
Tue, 27 Feb 2018 18:52:10 +0000 (19:52 +0100)
src/lib/dhcpsrv/alloc_engine.cc
src/lib/dhcpsrv/shared_network.cc
src/lib/dhcpsrv/shared_network.h
src/lib/dhcpsrv/subnet.cc
src/lib/dhcpsrv/subnet.h
src/lib/dhcpsrv/tests/alloc_engine6_unittest.cc
src/lib/dhcpsrv/tests/shared_network_unittest.cc

index 72b4a61318f801a8b4284e829f1701921c4b2593..67c33cabf5963f2c9ba4b254b0a811df2086d778 100644 (file)
@@ -761,8 +761,6 @@ AllocEngine::allocateUnreservedLeases6(ClientContext6& ctx) {
 
     Subnet6Ptr original_subnet = ctx.subnet_;
     Subnet6Ptr subnet = ctx.subnet_;
-    SharedNetwork6Ptr network;
-    subnet->getSharedNetwork(network);
 
     Pool6Ptr pool;
 
@@ -869,7 +867,28 @@ AllocEngine::allocateUnreservedLeases6(ClientContext6& ctx) {
     }
 
     uint64_t total_attempts = 0;
-    subnet = original_subnet;
+
+    // Need to check if the subnet belongs to a shared network. If so,
+    // we might be able to find a better subnet for lease allocation,
+    // for which it is more likely that there are some leases available.
+    // If we stick to the selected subnet, we may end up walking over
+    // the entire subnet (or more subnets) to discover that the pools
+    // have been exhausted. Using a subnet from which a lease was
+    // assigned most recently is an optimization which increases
+    // the likelyhood of starting from the subnet which pools are not
+    // exhausted.
+    SharedNetwork6Ptr network;
+    original_subnet->getSharedNetwork(network);
+    if (network) {
+        // This would try to find a subnet with the same set of classes
+        // as the current subnet, but with the more recent "usage timestamp".
+        // This timestamp is only updated for the allocations made with an
+        // allocator (unreserved lease allocations), not the static
+        // allocations or requested addresses.
+        original_subnet = network->getPreferredSubnet(original_subnet, ctx.currentIA().type_);
+    }
+
+    ctx.subnet_ = subnet = original_subnet;
 
     while (subnet) {
 
index 4f2cdb6a61b74a79f8491af709cafce4bee956ec..00d757d31bf94ef58e57362db21ff45cbac972ac 100644 (file)
@@ -214,17 +214,21 @@ public:
     /// @param subnets Container holding subnets belonging to this shared
     /// network.
     /// @param selected_subnet Pointer to a currently selected subnet.
+    /// @param lease_type Type of the lease for which preferred subnet should be
+    /// returned.
     ///
     /// @return Pointer to a preferred subnet. It may be the same as @c selected_subnet
     /// if no better subnet was found.
     template<typename SubnetPtrType, typename SubnetCollectionType>
     static SubnetPtrType getPreferredSubnet(const SubnetCollectionType& subnets,
-                                            const SubnetPtrType& selected_subnet) {
+                                            const SubnetPtrType& selected_subnet,
+                                            const Lease::Type& lease_type) {
 
-        Subnet4Ptr preferred_subnet = selected_subnet;
+        auto preferred_subnet = selected_subnet;
         for (auto s = subnets.begin(); s != subnets.end(); ++s) {
             if (((*s)->getClientClasses() == selected_subnet->getClientClasses()) &&
-                ((*s)->getLastAllocatedTime() > selected_subnet->getLastAllocatedTime())) {
+                ((*s)->getLastAllocatedTime(lease_type) >
+                 selected_subnet->getLastAllocatedTime(lease_type))) {
                 preferred_subnet = (*s);
             }
         }
@@ -277,7 +281,8 @@ SharedNetwork4::getNextSubnet(const Subnet4Ptr& first_subnet,
 
 Subnet4Ptr
 SharedNetwork4::getPreferredSubnet(const Subnet4Ptr& selected_subnet) const {
-    return (Impl::getPreferredSubnet<Subnet4Ptr>(subnets_, selected_subnet));
+    return (Impl::getPreferredSubnet<Subnet4Ptr>(subnets_, selected_subnet,
+                                                 Lease::TYPE_V4));
 }
 
 ElementPtr
@@ -335,6 +340,12 @@ SharedNetwork6::getNextSubnet(const Subnet6Ptr& first_subnet,
     return (Impl::getNextSubnet(subnets_, first_subnet, current_subnet));
 }
 
+Subnet6Ptr
+SharedNetwork6::getPreferredSubnet(const Subnet6Ptr& selected_subnet,
+                                   const Lease::Type& lease_type) const {
+    return (Impl::getPreferredSubnet(subnets_, selected_subnet, lease_type));
+}
+
 ElementPtr
 SharedNetwork6::toElement() const {
     ElementPtr map = Network6::toElement();
index b2fbd8b22312671881d8c253cd4c4edbc53fe862..d9839bce0600b0eefe688c5a75b5a423974ee854 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2017-2018 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
@@ -276,6 +276,31 @@ public:
     Subnet6Ptr getNextSubnet(const Subnet6Ptr& first_subnet,
                              const SubnetID& current_subnet) const;
 
+    /// @brief Attempts to find a subnet which is more likely to include available
+    /// leases than selected subnet.
+    ///
+    /// When allocating unreserved leases from a shared network it is important to
+    /// remember from which subnet within the shared network we have been recently
+    /// handing out leases. The allocation engine can use that information to start
+    /// trying allocation of the leases from that subnet rather than from the default
+    /// subnet selected for this client. Starting from the default subnet causes a
+    /// risk of having to walk over many subnets with exhausted address pools before
+    /// getting to the subnet with available leases. This method attempts to find
+    /// such subnet by inspecting "last allocation" timestamps. The one with most
+    /// recent timestamp is selected.
+    ///
+    /// The preferred subnet must also fulfil the condition of equal client classes
+    /// with the @c selected_subnet.
+    ///
+    /// @param selected_subnet Pointer to a currently selected subnet.
+    /// @param lease_type Type of the lease for which preferred subnet should be
+    /// returned.
+    ///
+    /// @return Pointer to a preferred subnet. It may be the same as @c selected_subnet
+    /// if no better subnet was found.
+    Subnet6Ptr getPreferredSubnet(const Subnet6Ptr& selected_subnet,
+                                  const Lease::Type& lease_type) const;
+
     /// @brief Unparses shared network object.
     ///
     /// @return A pointer to unparsed shared network configuration.
index 82365240d6c39ae25b9960e4cc10c4ca3341228d..385989115424158ec11683f490e87d1e9abdd0ed 100644 (file)
@@ -57,12 +57,18 @@ Subnet::Subnet(const isc::asiolink::IOAddress& prefix, uint8_t len,
       last_allocated_ia_(lastAddrInPrefix(prefix, len)),
       last_allocated_ta_(lastAddrInPrefix(prefix, len)),
       last_allocated_pd_(lastAddrInPrefix(prefix, len)),
-      last_allocated_time_(boost::posix_time::neg_infin) {
+      last_allocated_time_() {
     if ((prefix.isV6() && len > 128) ||
         (prefix.isV4() && len > 32)) {
         isc_throw(BadValue,
                   "Invalid prefix length specified for subnet: " << len);
     }
+
+    // Initialize timestamps for each lease type to negative infinity.
+    last_allocated_time_[Lease::TYPE_V4] = boost::posix_time::neg_infin;
+    last_allocated_time_[Lease::TYPE_NA] = boost::posix_time::neg_infin;
+    last_allocated_time_[Lease::TYPE_TA] = boost::posix_time::neg_infin;
+    last_allocated_time_[Lease::TYPE_PD] = boost::posix_time::neg_infin;
 }
 
 bool
@@ -90,6 +96,19 @@ isc::asiolink::IOAddress Subnet::getLastAllocated(Lease::Type type) const {
     }
 }
 
+boost::posix_time::ptime
+Subnet::getLastAllocatedTime(const Lease::Type& lease_type) const {
+    auto t = last_allocated_time_.find(lease_type);
+    if (t != last_allocated_time_.end()) {
+        return (t->second);
+    }
+
+    // This shouldn't happen, because we have initialized the structure
+    // for all lease types.
+    return (boost::posix_time::neg_infin);
+}
+
+
 void Subnet::setLastAllocated(Lease::Type type,
                               const isc::asiolink::IOAddress& addr) {
 
@@ -112,7 +131,7 @@ void Subnet::setLastAllocated(Lease::Type type,
     }
 
     // Update the timestamp of last allocation.
-    last_allocated_time_ = boost::posix_time::microsec_clock::universal_time();
+    last_allocated_time_[type] = boost::posix_time::microsec_clock::universal_time();
 }
 
 std::string
index 6c65a17443732471a91fa18df9dd9eca2e3c47ae..cb41bb2ebdc9860c8f55a4d63edf5d630f7dc353 100644 (file)
@@ -25,6 +25,7 @@
 #include <boost/date_time/posix_time/posix_time.hpp>
 #include <boost/pointer_cast.hpp>
 #include <boost/shared_ptr.hpp>
+#include <map>
 
 namespace isc {
 namespace dhcp {
@@ -84,9 +85,14 @@ public:
 
     /// @brief Returns the timestamp when the @c setLastAllocated function
     /// was called.
-    boost::posix_time::ptime getLastAllocatedTime() const {
-        return (last_allocated_time_);
-    }
+    ///
+    /// @param lease_type Lease type for which last allocation timestamp should
+    /// be returned.
+    ///
+    /// @return Time when a lease of a specified type has been allocated from
+    /// this subnet. The negative infinity time is returned if a lease type is
+    /// not recognized (which is unlikely).
+    boost::posix_time::ptime getLastAllocatedTime(const Lease::Type& lease_type) const;
 
     /// @brief sets the last address that was tried from this pool
     ///
@@ -391,9 +397,9 @@ protected:
     /// See @ref last_allocated_ia_ for details.
     isc::asiolink::IOAddress last_allocated_pd_;
 
-    /// @brief Timestamp indicating when an address has been last allocated
-    /// from this subnet.
-    boost::posix_time::ptime last_allocated_time_;
+    /// @brief Timestamp indicating when a lease of a specified type has been
+    /// last allocated from this subnet.
+    std::map<Lease::Type, boost::posix_time::ptime> last_allocated_time_;
 
     /// @brief Name of the network interface (if connected directly)
     std::string iface_;
index e96ebbd37bbe276c9465c5846554d03179e4b9c5..52a5aac6b7fc424d3bc2cbfdfb06303373bf1644 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2015-2017 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015-2018 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
@@ -2448,6 +2448,10 @@ TEST_F(SharedNetworkAlloc6Test, solicitSharedNetworkPoolClassification) {
     // offer an address from the pool1_.
     ctx4.query_->addClass(ClientClass("cable-modem"));
 
+    // Restrict access to pool2 for this client, to make sure that the
+    // server doesn't accidentally get a lease from this pool.
+    pool2_->allowClientClass("telephone");
+
     AllocEngine::findReservation(ctx4);
     ASSERT_NO_THROW(lease = expectOneLease(engine_.allocateLeases6(ctx4)));
     ASSERT_TRUE(lease);
index 3b3c0e0003124dd7828cdf939c3500a5d7981319..c3933477a0f40a666dc8db5bb51f88ac3a6a4768 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2017-2018 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
@@ -558,6 +558,104 @@ TEST(SharedNetwork6Test, getNextSubnet) {
     }
 }
 
+// This test verifies that preferred subnet is returned based on the timestamp
+// when the subnet was last used and allowed client classes.
+TEST(SharedNetwork6Test, getPreferredSubnet) {
+    SharedNetwork6Ptr network(new SharedNetwork6("frog"));
+
+    // Create four subnets.
+    Subnet6Ptr subnet1(new Subnet6(IOAddress("2001:db8:1::"), 64, 10, 20, 30,
+                                   40, SubnetID(1)));
+    Subnet6Ptr subnet2(new Subnet6(IOAddress("3000::"), 16, 10, 20, 30, 40,
+                                   SubnetID(2)));
+    Subnet6Ptr subnet3(new Subnet6(IOAddress("2001:db8:2::"), 64, 10, 20, 30,
+                                   40, SubnetID(3)));
+    Subnet6Ptr subnet4(new Subnet6(IOAddress("3000:1::"), 64, 10, 20, 30,
+                                   40, SubnetID(4)));
+
+
+    // Associate first two subnets with classes.
+    subnet1->allowClientClass("class1");
+    subnet2->allowClientClass("class1");
+
+    std::vector<Subnet6Ptr> subnets;
+    subnets.push_back(subnet1);
+    subnets.push_back(subnet2);
+    subnets.push_back(subnet3);
+    subnets.push_back(subnet4);
+
+    // Subnets have unique IDs so they should successfully be added to the
+    // network.
+    for (auto i = 0; i < subnets.size(); ++i) {
+        ASSERT_NO_THROW(network->add(subnets[i]))
+            << "failed to add subnet with id " << subnets[i]->getID()
+            << " to shared network";
+    }
+
+    Subnet6Ptr preferred;
+
+    // Initially, for every subnet we sould get the same subnet as the preferred
+    // one, because none of them have been used.
+    for (auto i = 0; i < subnets.size(); ++i) {
+        preferred = network->getPreferredSubnet(subnets[i], Lease::TYPE_NA);
+        EXPECT_EQ(subnets[i]->getID(), preferred->getID());
+        preferred = network->getPreferredSubnet(subnets[i], Lease::TYPE_TA);
+        EXPECT_EQ(subnets[i]->getID(), preferred->getID());
+        preferred = network->getPreferredSubnet(subnets[i], Lease::TYPE_PD);
+        EXPECT_EQ(subnets[i]->getID(), preferred->getID());
+    }
+
+    // Allocating an address from subnet2 updates the last allocated timestamp
+    // for this subnet, which makes this subnet preferred over subnet1.
+    subnet2->setLastAllocated(Lease::TYPE_NA, IOAddress("2001:db8:1:2::"));
+    preferred = network->getPreferredSubnet(subnet1, Lease::TYPE_NA);
+    EXPECT_EQ(subnet2->getID(), preferred->getID());
+
+    // If selected is subnet2, the same is returned.
+    preferred = network->getPreferredSubnet(subnet2, Lease::TYPE_NA);
+    EXPECT_EQ(subnet2->getID(), preferred->getID());
+
+    // The preferred subnet is dependent on the lease type. For the PD
+    // we should get the same subnet as selected.
+    preferred = network->getPreferredSubnet(subnet1, Lease::TYPE_PD);
+    EXPECT_EQ(subnet1->getID(), preferred->getID());
+
+    // Although, if we pick a prefix from the subnet2, we should get the
+    // subnet2 as preferred instead.
+    subnet2->setLastAllocated(Lease::TYPE_PD, IOAddress("3000:1234::"));
+    preferred = network->getPreferredSubnet(subnet1, Lease::TYPE_PD);
+    EXPECT_EQ(subnet2->getID(), preferred->getID());
+
+    // Even though the subnet1 has been most recently used, the preferred
+    // subnet is subnet3 in this case, because of the client class
+    // mismatch.
+    preferred = network->getPreferredSubnet(subnet3, Lease::TYPE_NA);
+    EXPECT_EQ(subnet3->getID(), preferred->getID());
+
+    // Same for subnet4.
+    preferred = network->getPreferredSubnet(subnet4, Lease::TYPE_NA);
+    EXPECT_EQ(subnet4->getID(), preferred->getID());
+
+    // Allocate an address from the subnet3. This makes it preferred to
+    // subnet4.
+    subnet3->setLastAllocated(Lease::TYPE_NA, IOAddress("2001:db8:2:1234::"));
+
+    // If the selected is subnet1, the preferred subnet is subnet2, because
+    // it has the same set of classes as subnet1. The subnet3 can't be
+    // preferred here because of the client class mismatch.
+    preferred = network->getPreferredSubnet(subnet1, Lease::TYPE_NA);
+    EXPECT_EQ(subnet2->getID(), preferred->getID());
+
+    // If we select subnet4, the preferred subnet is subnet3 because
+    // it was used more recently.
+    preferred = network->getPreferredSubnet(subnet4, Lease::TYPE_NA);
+    EXPECT_EQ(subnet3->getID(), preferred->getID());
+
+    // Repeat the test for subnet3 being a selected subnet.
+    preferred = network->getPreferredSubnet(subnet3, Lease::TYPE_NA);
+    EXPECT_EQ(subnet3->getID(), preferred->getID());
+}
+
 // This test verifies that unparsing shared network returns valid structure.
 TEST(SharedNetwork6Test, unparse) {
     SharedNetwork6Ptr network(new SharedNetwork6("frog"));