/// 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
// 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,
// 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,
// 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)
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);
}
#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>
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) {