mirror of
https://gitlab.isc.org/isc-projects/kea
synced 2025-08-30 21:45:37 +00:00
[5437] Select last used subnet for new allocations in DHCPv6.
This commit is contained in:
@@ -761,8 +761,6 @@ AllocEngine::allocateUnreservedLeases6(ClientContext6& ctx) {
|
|||||||
|
|
||||||
Subnet6Ptr original_subnet = ctx.subnet_;
|
Subnet6Ptr original_subnet = ctx.subnet_;
|
||||||
Subnet6Ptr subnet = ctx.subnet_;
|
Subnet6Ptr subnet = ctx.subnet_;
|
||||||
SharedNetwork6Ptr network;
|
|
||||||
subnet->getSharedNetwork(network);
|
|
||||||
|
|
||||||
Pool6Ptr pool;
|
Pool6Ptr pool;
|
||||||
|
|
||||||
@@ -869,7 +867,28 @@ AllocEngine::allocateUnreservedLeases6(ClientContext6& ctx) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
uint64_t total_attempts = 0;
|
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) {
|
while (subnet) {
|
||||||
|
|
||||||
|
@@ -214,17 +214,21 @@ public:
|
|||||||
/// @param subnets Container holding subnets belonging to this shared
|
/// @param subnets Container holding subnets belonging to this shared
|
||||||
/// network.
|
/// network.
|
||||||
/// @param selected_subnet Pointer to a currently 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
|
/// @return Pointer to a preferred subnet. It may be the same as @c selected_subnet
|
||||||
/// if no better subnet was found.
|
/// if no better subnet was found.
|
||||||
template<typename SubnetPtrType, typename SubnetCollectionType>
|
template<typename SubnetPtrType, typename SubnetCollectionType>
|
||||||
static SubnetPtrType getPreferredSubnet(const SubnetCollectionType& subnets,
|
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) {
|
for (auto s = subnets.begin(); s != subnets.end(); ++s) {
|
||||||
if (((*s)->getClientClasses() == selected_subnet->getClientClasses()) &&
|
if (((*s)->getClientClasses() == selected_subnet->getClientClasses()) &&
|
||||||
((*s)->getLastAllocatedTime() > selected_subnet->getLastAllocatedTime())) {
|
((*s)->getLastAllocatedTime(lease_type) >
|
||||||
|
selected_subnet->getLastAllocatedTime(lease_type))) {
|
||||||
preferred_subnet = (*s);
|
preferred_subnet = (*s);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -277,7 +281,8 @@ SharedNetwork4::getNextSubnet(const Subnet4Ptr& first_subnet,
|
|||||||
|
|
||||||
Subnet4Ptr
|
Subnet4Ptr
|
||||||
SharedNetwork4::getPreferredSubnet(const Subnet4Ptr& selected_subnet) const {
|
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
|
ElementPtr
|
||||||
@@ -335,6 +340,12 @@ SharedNetwork6::getNextSubnet(const Subnet6Ptr& first_subnet,
|
|||||||
return (Impl::getNextSubnet(subnets_, first_subnet, current_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
|
ElementPtr
|
||||||
SharedNetwork6::toElement() const {
|
SharedNetwork6::toElement() const {
|
||||||
ElementPtr map = Network6::toElement();
|
ElementPtr map = Network6::toElement();
|
||||||
|
@@ -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
|
// 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
|
// 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,
|
Subnet6Ptr getNextSubnet(const Subnet6Ptr& first_subnet,
|
||||||
const SubnetID& current_subnet) const;
|
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.
|
/// @brief Unparses shared network object.
|
||||||
///
|
///
|
||||||
/// @return A pointer to unparsed shared network configuration.
|
/// @return A pointer to unparsed shared network configuration.
|
||||||
|
@@ -57,12 +57,18 @@ Subnet::Subnet(const isc::asiolink::IOAddress& prefix, uint8_t len,
|
|||||||
last_allocated_ia_(lastAddrInPrefix(prefix, len)),
|
last_allocated_ia_(lastAddrInPrefix(prefix, len)),
|
||||||
last_allocated_ta_(lastAddrInPrefix(prefix, len)),
|
last_allocated_ta_(lastAddrInPrefix(prefix, len)),
|
||||||
last_allocated_pd_(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) ||
|
if ((prefix.isV6() && len > 128) ||
|
||||||
(prefix.isV4() && len > 32)) {
|
(prefix.isV4() && len > 32)) {
|
||||||
isc_throw(BadValue,
|
isc_throw(BadValue,
|
||||||
"Invalid prefix length specified for subnet: " << len);
|
"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
|
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,
|
void Subnet::setLastAllocated(Lease::Type type,
|
||||||
const isc::asiolink::IOAddress& addr) {
|
const isc::asiolink::IOAddress& addr) {
|
||||||
|
|
||||||
@@ -112,7 +131,7 @@ void Subnet::setLastAllocated(Lease::Type type,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update the timestamp of last allocation.
|
// 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
|
std::string
|
||||||
|
@@ -25,6 +25,7 @@
|
|||||||
#include <boost/date_time/posix_time/posix_time.hpp>
|
#include <boost/date_time/posix_time/posix_time.hpp>
|
||||||
#include <boost/pointer_cast.hpp>
|
#include <boost/pointer_cast.hpp>
|
||||||
#include <boost/shared_ptr.hpp>
|
#include <boost/shared_ptr.hpp>
|
||||||
|
#include <map>
|
||||||
|
|
||||||
namespace isc {
|
namespace isc {
|
||||||
namespace dhcp {
|
namespace dhcp {
|
||||||
@@ -84,9 +85,14 @@ public:
|
|||||||
|
|
||||||
/// @brief Returns the timestamp when the @c setLastAllocated function
|
/// @brief Returns the timestamp when the @c setLastAllocated function
|
||||||
/// was called.
|
/// 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
|
/// @brief sets the last address that was tried from this pool
|
||||||
///
|
///
|
||||||
@@ -391,9 +397,9 @@ protected:
|
|||||||
/// See @ref last_allocated_ia_ for details.
|
/// See @ref last_allocated_ia_ for details.
|
||||||
isc::asiolink::IOAddress last_allocated_pd_;
|
isc::asiolink::IOAddress last_allocated_pd_;
|
||||||
|
|
||||||
/// @brief Timestamp indicating when an address has been last allocated
|
/// @brief Timestamp indicating when a lease of a specified type has been
|
||||||
/// from this subnet.
|
/// last allocated from this subnet.
|
||||||
boost::posix_time::ptime last_allocated_time_;
|
std::map<Lease::Type, boost::posix_time::ptime> last_allocated_time_;
|
||||||
|
|
||||||
/// @brief Name of the network interface (if connected directly)
|
/// @brief Name of the network interface (if connected directly)
|
||||||
std::string iface_;
|
std::string iface_;
|
||||||
|
@@ -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
|
// 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
|
// 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_.
|
// offer an address from the pool1_.
|
||||||
ctx4.query_->addClass(ClientClass("cable-modem"));
|
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);
|
AllocEngine::findReservation(ctx4);
|
||||||
ASSERT_NO_THROW(lease = expectOneLease(engine_.allocateLeases6(ctx4)));
|
ASSERT_NO_THROW(lease = expectOneLease(engine_.allocateLeases6(ctx4)));
|
||||||
ASSERT_TRUE(lease);
|
ASSERT_TRUE(lease);
|
||||||
|
@@ -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
|
// 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
|
// 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.
|
// This test verifies that unparsing shared network returns valid structure.
|
||||||
TEST(SharedNetwork6Test, unparse) {
|
TEST(SharedNetwork6Test, unparse) {
|
||||||
SharedNetwork6Ptr network(new SharedNetwork6("frog"));
|
SharedNetwork6Ptr network(new SharedNetwork6("frog"));
|
||||||
|
Reference in New Issue
Block a user