Commit 4e8ac9fc authored by Marcin Siodelski's avatar Marcin Siodelski
Browse files

[5306] Basic IPv4 shared subnets scenarios implemented in alloc engine.

parent 18d31ca1
......@@ -2173,41 +2173,101 @@ hasAddressReservation(const AllocEngine::ClientContext4& ctx) {
/// address, the function also checks if the lease belongs to the client, i.e.
/// there is no conflict between the client identifiers.
///
/// @param ctx Context holding data extracted from the client's message,
/// including the HW address and client identifier.
/// @param [out] ctx Context holding data extracted from the client's message,
/// including the HW address and client identifier. The current subnet may be
/// modified by this function if it belongs to a shared network.
/// @param [out] client_lease A pointer to the lease returned by this function
/// or null value if no has been lease found.
void findClientLease(const AllocEngine::ClientContext4& ctx, Lease4Ptr& client_lease) {
void findClientLease(AllocEngine::ClientContext4& ctx, Lease4Ptr& client_lease) {
LeaseMgr& lease_mgr = LeaseMgrFactory::instance();
// If client identifier has been supplied, use it to lookup the lease. This
// search will return no lease if the client doesn't have any lease in the
// database or if the client didn't use client identifier to allocate the
// existing lease (this include cases when the server was explicitly
// configured to ignore client identifier).
if (ctx.clientid_) {
client_lease = lease_mgr.getLease4(*ctx.clientid_, ctx.subnet_->getID());
}
// If no lease found using the client identifier, try the lookup using
// the HW address.
if (!client_lease && ctx.hwaddr_) {
// There may be cases when there is a lease for the same MAC address
// (even within the same subnet). Such situation may occur for PXE
// boot clients using the same MAC address but different client
// identifiers.
Lease4Collection client_leases = lease_mgr.getLease4(*ctx.hwaddr_);
for (Lease4Collection::const_iterator client_lease_it = client_leases.begin();
client_lease_it != client_leases.end(); ++client_lease_it) {
Lease4Ptr existing_lease = *client_lease_it;
if ((existing_lease->subnet_id_ == ctx.subnet_->getID()) &&
existing_lease->belongsToClient(ctx.hwaddr_, ctx.clientid_)) {
// Found the lease of this client, so return it.
client_lease = existing_lease;
break;
Subnet4Ptr subnet = ctx.subnet_;
SharedNetwork4Ptr network;
subnet->getSharedNetwork(network);
while (subnet) {
// If client identifier has been supplied, use it to lookup the lease. This
// search will return no lease if the client doesn't have any lease in the
// database or if the client didn't use client identifier to allocate the
// existing lease (this include cases when the server was explicitly
// configured to ignore client identifier).
if (ctx.clientid_) {
client_lease = lease_mgr.getLease4(*ctx.clientid_, subnet->getID());
}
// If no lease found using the client identifier, try the lookup using
// the HW address.
if (!client_lease && ctx.hwaddr_) {
// There may be cases when there is a lease for the same MAC address
// (even within the same subnet). Such situation may occur for PXE
// boot clients using the same MAC address but different client
// identifiers.
Lease4Collection client_leases = lease_mgr.getLease4(*ctx.hwaddr_);
for (Lease4Collection::const_iterator client_lease_it = client_leases.begin();
client_lease_it != client_leases.end(); ++client_lease_it) {
Lease4Ptr existing_lease = *client_lease_it;
if ((existing_lease->subnet_id_ == subnet->getID()) &&
existing_lease->belongsToClient(ctx.hwaddr_, ctx.clientid_)) {
// Found the lease of this client, so return it.
client_lease = existing_lease;
break;
}
}
}
if (client_lease || !network) {
// We got the leases but the subnet they belong to may differ from
// the original subnet. Let's now stick to this subnet.
ctx.subnet_ = subnet;
subnet.reset();
} else {
subnet = network->getNextSubnet(ctx.subnet_, subnet);
}
}
}
/// @brief Checks if the specified address belongs to one of the subnets
/// within a shared network.
///
/// @todo Update this function to take client classification into account.
///
/// @param ctx Client context. Current subnet may be modified by this
/// function when it belongs to a shared network.
/// @param address IPv4 address to be checked.
///
/// @return true if address belongs to a pool in a selected subnet or in
/// a pool within any of the subnets belonging to the current shared network.
bool
inAllowedPool(AllocEngine::ClientContext4& ctx, const IOAddress& address) {
SharedNetwork4Ptr network;
ctx.subnet_->getSharedNetwork(network);
if (!network) {
// If there is no shared network associated with this subnet, we
// simply check if the address is within the pool in this subnet.
return (ctx.subnet_->inPool(Lease::TYPE_V4, address));
}
// If the subnet belongs to a shared network we will be iterating
// over the subnets that belong to this shared network.
Subnet4Ptr current_subnet = ctx.subnet_;
while (current_subnet) {
if (current_subnet->inPool(Lease::TYPE_V4, address)) {
// We found a subnet that this address belongs to, so it
// seems that this subnet is the good candidate for allocation.
// Let's update the selected subnet.
ctx.subnet_ = current_subnet;
return (true);
}
// Address is not within pools in this subnet, so let's proceed
// to the next subnet.
current_subnet = network->getNextSubnet(ctx.subnet_, current_subnet);
}
return (false);
}
} // end of anonymous namespace
......@@ -2337,8 +2397,7 @@ AllocEngine::discoverLease4(AllocEngine::ClientContext4& ctx) {
// new reservation for the address used by this client. The latter may
// be due to the client using the reserved out-of-the pool address, for
// which the reservation has just been removed.
if (!new_lease && client_lease &&
ctx.subnet_->inPool(Lease::TYPE_V4, client_lease->addr_) &&
if (!new_lease && client_lease && inAllowedPool(ctx, client_lease->addr_) &&
!addressReserved(client_lease->addr_, ctx)) {
LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
......@@ -2356,7 +2415,7 @@ AllocEngine::discoverLease4(AllocEngine::ClientContext4& ctx) {
// reserved for another client, and must be in the range of the
// dynamic pool.
if (!new_lease && !ctx.requested_address_.isV4Zero() &&
ctx.subnet_->inPool(Lease::TYPE_V4, ctx.requested_address_) &&
inAllowedPool(ctx, ctx.requested_address_) &&
!addressReserved(ctx.requested_address_, ctx)) {
LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
......@@ -2480,7 +2539,7 @@ AllocEngine::requestLease4(AllocEngine::ClientContext4& ctx) {
// and it doesn't belong to the dynamic pool, do not allocate it.
if ((!hasAddressReservation(ctx) ||
(ctx.host_->getIPv4Reservation() != ctx.requested_address_)) &&
!ctx.subnet_->inPool(Lease4::TYPE_V4, ctx.requested_address_)) {
!inAllowedPool(ctx, ctx.requested_address_)) {
LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
ALLOC_ENGINE_V4_REQUEST_OUT_OF_POOL)
......@@ -2882,32 +2941,56 @@ Lease4Ptr
AllocEngine::allocateUnreservedLease4(ClientContext4& ctx) {
Lease4Ptr new_lease;
AllocatorPtr allocator = getAllocator(Lease::TYPE_V4);
const uint64_t max_attempts = (attempts_ > 0 ? attempts_ :
ctx.subnet_->getPoolCapacity(Lease::TYPE_V4));
for (uint64_t i = 0; i < max_attempts; ++i) {
IOAddress candidate = allocator->pickAddress(ctx.subnet_, ctx.clientid_,
ctx.requested_address_);
// If address is not reserved for another client, try to allocate it.
if (!addressReserved(candidate, ctx)) {
// The call below will return the non-NULL pointer if we
// successfully allocate this lease. This means that the
// address is not in use by another client.
new_lease = allocateOrReuseLease4(candidate, ctx);
if (new_lease) {
return (new_lease);
} else if (ctx.callout_handle_ &&
(ctx.callout_handle_->getStatus() !=
CalloutHandle::NEXT_STEP_CONTINUE)) {
// Don't retry when the callout status is not continue.
break;
Subnet4Ptr subnet = ctx.subnet_;
Subnet4Ptr original_subnet = subnet;
SharedNetwork4Ptr network;
subnet->getSharedNetwork(network);
uint64_t total_attempts = 0;
while (subnet) {
const uint64_t max_attempts = (attempts_ > 0 ? attempts_ :
subnet->getPoolCapacity(Lease::TYPE_V4));
for (uint64_t i = 0; i < max_attempts; ++i) {
IOAddress candidate = allocator->pickAddress(subnet, ctx.clientid_,
ctx.requested_address_);
// If address is not reserved for another client, try to allocate it.
if (!addressReserved(candidate, ctx)) {
// The call below will return the non-NULL pointer if we
// successfully allocate this lease. This means that the
// address is not in use by another client.
new_lease = allocateOrReuseLease4(candidate, ctx);
if (new_lease) {
return (new_lease);
} else if (ctx.callout_handle_ &&
(ctx.callout_handle_->getStatus() !=
CalloutHandle::NEXT_STEP_CONTINUE)) {
// Don't retry when the callout status is not continue.
subnet.reset();
break;
}
}
}
total_attempts += max_attempts;
// If our current subnet belongs to a shared network, let's try other
// subnets in the same shared network.
if (network) {
subnet = network->getNextSubnet(original_subnet, subnet);
if (subnet) {
ctx.subnet_ = subnet;
}
} else {
// Subnet doesn't belong to a shared network so we have no more
// subnets/address pools to try. The client won't get the lease.
subnet.reset();
}
}
// Unable to allocate an address, return an empty lease.
LOG_WARN(alloc_engine_logger, ALLOC_ENGINE_V4_ALLOC_FAIL)
.arg(ctx.query_->getLabel())
.arg(max_attempts);
.arg(total_attempts);
return (new_lease);
}
......
......@@ -6,6 +6,7 @@
#include <config.h>
#include <dhcp/pkt4.h>
#include <dhcpsrv/shared_network.h>
#include <dhcpsrv/tests/alloc_engine_utils.h>
#include <dhcpsrv/tests/test_utils.h>
#include <stats/stats_mgr.h>
......@@ -474,6 +475,151 @@ TEST_F(AllocEngine4Test, outOfAddresses4) {
EXPECT_FALSE(ctx.old_lease_);
}
// This test verifies that the server can offer an address from a
// different subnet than orginally selected, when the address pool in
// the first subnet is exhausted.
TEST_F(AllocEngine4Test, discoverSharedNetwork) {
AllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 100, false);
// Create two subnets, each with a single address pool. The first subnet
// has only one address in its address pool to make it easier to simulate
// address exhaustion.
Subnet4Ptr subnet1(new Subnet4(IOAddress("192.0.2.0"), 24, 1, 2, 3, SubnetID(1)));
Subnet4Ptr subnet2(new Subnet4(IOAddress("10.1.2.0"), 24, 1, 2, 3, SubnetID(2)));
Pool4Ptr pool1(new Pool4(IOAddress("192.0.2.17"), IOAddress("192.0.2.17")));
Pool4Ptr pool2(new Pool4(IOAddress("10.1.2.5"), IOAddress("10.1.2.100")));
subnet1->addPool(pool1);
subnet2->addPool(pool2);
// Both subnets belong to the same network so they can be used
// interchangeably.
SharedNetwork4Ptr network(new SharedNetwork4("test_network"));
network->add(subnet1);
network->add(subnet2);
// Create a lease for a single address in the first address pool. The
// pool is now exhausted.
std::vector<uint8_t> hwaddr_vec = { 0, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe};
HWAddrPtr hwaddr(new HWAddr(hwaddr_vec, HTYPE_ETHER));
Lease4Ptr lease(new Lease4(IOAddress("192.0.2.17"), hwaddr, ClientIdPtr(),
501, 502, 503, time(NULL), subnet1->getID()));
lease->cltt_ = time(NULL) - 10; // Allocated 10 seconds ago
ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
// Create context which will be used to try to allocate leases from the
// shared network. The context poits to subnet1, which address space
// is exhausted. We expect the allocation engine to find another subnet
// within the same shared network and offer an address from there.
AllocEngine::ClientContext4
ctx(subnet1, ClientIdPtr(), hwaddr_, IOAddress::IPV4_ZERO_ADDRESS(),
false, false, "host.example.com.", true);
ctx.query_.reset(new Pkt4(DHCPDISCOVER, 1234));
Lease4Ptr lease2 = engine.allocateLease4(ctx);
// The allocation engine should have assigned an address from the second
// subnet. We could guess that this is 10.1.2.5, being the first address
// in the address pool, but to make the test more generic, we merely
// verify that the address is in the given address pool.
ASSERT_TRUE(lease2);
EXPECT_TRUE(subnet2->inPool(Lease::TYPE_V4, lease2->addr_));
// The client should also be offered a lease when it specifies a hint
// that doesn't match the subnet from which the lease is offered. The
// engine should check alternative subnets to match the hint to
// a subnet. The requested lease is available, so it should be offered.
ctx.subnet_ = subnet1;
ctx.requested_address_ = IOAddress("10.1.2.25");
lease2 = engine.allocateLease4(ctx);
ASSERT_TRUE(lease2);
EXPECT_EQ("10.1.2.25", lease2->addr_.toText());
// The returning client (the one that has a lease) should also be able
// to renew its lease regardless of a subnet it begins with. So, it has
// an address assigned from subnet1, but we use subnet2 as a selected
// subnet.
AllocEngine::ClientContext4 ctx2(subnet2, ClientIdPtr(), hwaddr,
IOAddress("0.0.0.0"), false, false,
"host.example.com.", true);
ctx2.query_.reset(new Pkt4(DHCPDISCOVER, 1234));
lease2 = engine.allocateLease4(ctx2);
// The existing lease should be returned.
ASSERT_TRUE(lease2);
EXPECT_EQ("192.0.2.17", lease2->addr_.toText());
}
// This test verifies that the server can allocate an address from a
// different subnet than orginally selected, when the address pool in
// the first subnet is exhausted.
TEST_F(AllocEngine4Test, reuqestSharedNetwork) {
AllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 100, false);
// Create two subnets, each with a single address pool. The first subnet
// has only one address in its address pool to make it easier to simulate
// address exhaustion.
Subnet4Ptr subnet1(new Subnet4(IOAddress("192.0.2.0"), 24, 1, 2, 3, SubnetID(1)));
Subnet4Ptr subnet2(new Subnet4(IOAddress("10.1.2.0"), 24, 1, 2, 3, SubnetID(2)));
Pool4Ptr pool1(new Pool4(IOAddress("192.0.2.17"), IOAddress("192.0.2.17")));
Pool4Ptr pool2(new Pool4(IOAddress("10.1.2.5"), IOAddress("10.1.2.100")));
subnet1->addPool(pool1);
subnet2->addPool(pool2);
// Both subnets belong to the same network so they can be used
// interchangeably.
SharedNetwork4Ptr network(new SharedNetwork4("test_network"));
network->add(subnet1);
network->add(subnet2);
// Create a lease for a single address in the first address pool. The
// pool is now exhausted.
std::vector<uint8_t> hwaddr_vec = { 0, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe};
HWAddrPtr hwaddr(new HWAddr(hwaddr_vec, HTYPE_ETHER));
Lease4Ptr lease(new Lease4(IOAddress("192.0.2.17"), hwaddr, ClientIdPtr(),
501, 502, 503, time(NULL), subnet1->getID()));
lease->cltt_ = time(NULL) - 10; // Allocated 10 seconds ago
ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
// Create context which will be used to try to allocate leases from the
// shared network. The context poits to subnet1, which address space
// is exhausted. We expect the allocation engine to find another subnet
// within the same shared network and offer an address from there.
AllocEngine::ClientContext4
ctx(subnet1, ClientIdPtr(), hwaddr_, IOAddress::IPV4_ZERO_ADDRESS(),
false, false, "host.example.com.", false);
ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234));
Lease4Ptr lease2 = engine.allocateLease4(ctx);
// The allocation engine should have assigned an address from the second
// subnet. We could guess that this is 10.1.2.5, being the first address
// in the address pool, but to make the test more generic, we merely
// verify that the address is in the given address pool.
ASSERT_TRUE(lease2);
EXPECT_TRUE(subnet2->inPool(Lease::TYPE_V4, lease2->addr_));
ASSERT_TRUE(LeaseMgrFactory::instance().deleteLease(lease2->addr_));
// The client should also be assigned a lease when it specifies a hint
// that doesn't match the subnet from which the lease is offered. The
// engine should check alternative subnets to match the hint to
// a subnet. The requested lease is available, so it should be offered.
ctx.subnet_ = subnet1;
ctx.requested_address_ = IOAddress("10.1.2.25");
lease2 = engine.allocateLease4(ctx);
ASSERT_TRUE(lease2);
EXPECT_EQ("10.1.2.25", lease2->addr_.toText());
// The returning client (the one that has a lease) should also be able
// to renew its lease regardless of a subnet it begins with. So, it has
// an address assigned from subnet1, but we use subnet2 as a selected
// subnet.
AllocEngine::ClientContext4 ctx2(subnet2, ClientIdPtr(), hwaddr,
IOAddress("0.0.0.0"), false, false,
"host.example.com.", false);
ctx2.query_.reset(new Pkt4(DHCPREQUEST, 1234));
lease2 = engine.allocateLease4(ctx2);
// The existing lease should be returned.
ASSERT_TRUE(lease2);
EXPECT_EQ("192.0.2.17", lease2->addr_.toText());
}
// This test checks if an expired lease can be reused in DHCPDISCOVER (fake
// allocation)
TEST_F(AllocEngine4Test, discoverReuseExpiredLease4) {
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment