From: Marcin Siodelski Date: Wed, 6 Sep 2017 17:15:41 +0000 (+0200) Subject: [5306] Basic IPv4 shared subnets scenarios implemented in alloc engine. X-Git-Tag: trac5363_base~21^2~16 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=4e8ac9fcb74fe70aea6ff9d90d76090fbf116720;p=thirdparty%2Fkea.git [5306] Basic IPv4 shared subnets scenarios implemented in alloc engine. --- diff --git a/src/lib/dhcpsrv/alloc_engine.cc b/src/lib/dhcpsrv/alloc_engine.cc index f11c964414..7c328513e7 100644 --- a/src/lib/dhcpsrv/alloc_engine.cc +++ b/src/lib/dhcpsrv/alloc_engine.cc @@ -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); } diff --git a/src/lib/dhcpsrv/tests/alloc_engine4_unittest.cc b/src/lib/dhcpsrv/tests/alloc_engine4_unittest.cc index 67b10bcfd6..4ee254137e 100644 --- a/src/lib/dhcpsrv/tests/alloc_engine4_unittest.cc +++ b/src/lib/dhcpsrv/tests/alloc_engine4_unittest.cc @@ -6,6 +6,7 @@ #include #include +#include #include #include #include @@ -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 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 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) {