From: Marcin Siodelski Date: Tue, 27 Feb 2018 18:52:10 +0000 (+0100) Subject: [5437] Select last used subnet for new allocations in DHCPv6. X-Git-Tag: ha_checkpoints12~10^2~4 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=ad313e8694e8cd7df166183f88eaede43c02452d;p=thirdparty%2Fkea.git [5437] Select last used subnet for new allocations in DHCPv6. --- diff --git a/src/lib/dhcpsrv/alloc_engine.cc b/src/lib/dhcpsrv/alloc_engine.cc index 72b4a61318..67c33cabf5 100644 --- a/src/lib/dhcpsrv/alloc_engine.cc +++ b/src/lib/dhcpsrv/alloc_engine.cc @@ -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) { diff --git a/src/lib/dhcpsrv/shared_network.cc b/src/lib/dhcpsrv/shared_network.cc index 4f2cdb6a61..00d757d31b 100644 --- a/src/lib/dhcpsrv/shared_network.cc +++ b/src/lib/dhcpsrv/shared_network.cc @@ -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 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(subnets_, selected_subnet)); + return (Impl::getPreferredSubnet(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(); diff --git a/src/lib/dhcpsrv/shared_network.h b/src/lib/dhcpsrv/shared_network.h index b2fbd8b223..d9839bce06 100644 --- a/src/lib/dhcpsrv/shared_network.h +++ b/src/lib/dhcpsrv/shared_network.h @@ -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. diff --git a/src/lib/dhcpsrv/subnet.cc b/src/lib/dhcpsrv/subnet.cc index 82365240d6..3859891154 100644 --- a/src/lib/dhcpsrv/subnet.cc +++ b/src/lib/dhcpsrv/subnet.cc @@ -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 diff --git a/src/lib/dhcpsrv/subnet.h b/src/lib/dhcpsrv/subnet.h index 6c65a17443..cb41bb2ebd 100644 --- a/src/lib/dhcpsrv/subnet.h +++ b/src/lib/dhcpsrv/subnet.h @@ -25,6 +25,7 @@ #include #include #include +#include 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 last_allocated_time_; /// @brief Name of the network interface (if connected directly) std::string iface_; diff --git a/src/lib/dhcpsrv/tests/alloc_engine6_unittest.cc b/src/lib/dhcpsrv/tests/alloc_engine6_unittest.cc index e96ebbd37b..52a5aac6b7 100644 --- a/src/lib/dhcpsrv/tests/alloc_engine6_unittest.cc +++ b/src/lib/dhcpsrv/tests/alloc_engine6_unittest.cc @@ -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); diff --git a/src/lib/dhcpsrv/tests/shared_network_unittest.cc b/src/lib/dhcpsrv/tests/shared_network_unittest.cc index 3b3c0e0003..c3933477a0 100644 --- a/src/lib/dhcpsrv/tests/shared_network_unittest.cc +++ b/src/lib/dhcpsrv/tests/shared_network_unittest.cc @@ -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 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"));