From: Marcin Siodelski Date: Mon, 8 May 2023 08:27:59 +0000 (+0200) Subject: [#2843] Use different allocators in shared net X-Git-Tag: Kea-2.3.8~151 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=9d8d99a980ffbe782074cbecebf05f998b0dd928;p=thirdparty%2Fkea.git [#2843] Use different allocators in shared net Fixed a bug in the allocation engine which could result in a failure to allocate available addresses from a subnet that used an FLQ allocator, or returning a zero lease. --- diff --git a/src/bin/dhcp4/tests/dhcp4_test_utils.cc b/src/bin/dhcp4/tests/dhcp4_test_utils.cc index 7bfbe01550..c4a4db593b 100644 --- a/src/bin/dhcp4/tests/dhcp4_test_utils.cc +++ b/src/bin/dhcp4/tests/dhcp4_test_utils.cc @@ -873,6 +873,13 @@ Dhcpv4SrvTest::configure(const std::string& config, expiration_cfg->setHoldReclaimedTime(0); } + try { + CfgMgr::instance().getStagingCfg()->getCfgSubnets4()->initAllocatorsAfterConfigure(); + } catch (const std::exception& ex) { + ADD_FAILURE() << "Error initializing the allocators after configure: " + << ex.what(); + } + try { CfgMultiThreading::apply(CfgMgr::instance().getStagingCfg()->getDHCPMultiThreading()); } catch (const std::exception& ex) { diff --git a/src/bin/dhcp4/tests/shared_network_unittest.cc b/src/bin/dhcp4/tests/shared_network_unittest.cc index 10afcc2b14..05e1aaab49 100644 --- a/src/bin/dhcp4/tests/shared_network_unittest.cc +++ b/src/bin/dhcp4/tests/shared_network_unittest.cc @@ -1053,6 +1053,45 @@ const char* NETWORKS_CONFIG[] = { " ]" " }" " ]" + "}", + +// Configuration #21 +// - a shared network with two subnets +// - first subnet uses the FLQ allocator +// - second subnet uses the random allocator + "{" + " \"interfaces-config\": {" + " \"interfaces\": [ \"*\" ]" + " }," + " \"valid-lifetime\": 600," + " \"shared-networks\": [" + " {" + " \"name\": \"frog\"," + " \"relay\": {" + " \"ip-address\": \"192.3.5.6\"" + " }," + " \"subnet4\": [" + " {" + " \"subnet\": \"192.0.2.0/24\"," + " \"allocator\": \"flq\"," + " \"pools\": [" + " {" + " \"pool\": \"192.0.2.1 - 192.0.2.10\"" + " }" + " ]" + " }," + " {" + " \"subnet\": \"192.0.3.0/24\"," + " \"allocator\": \"random\"," + " \"pools\": [" + " {" + " \"pool\": \"192.0.3.1 - 192.0.3.10\"" + " }" + " ]" + " }" + " ]" + " }" + " ]" "}" }; @@ -2873,4 +2912,35 @@ TEST_F(Dhcpv4SharedNetworkTest, authoritative) { } } +// Test that different allocator types can be used within a shared network. +// All available addresses should be assigned from the subnets belonging to +// the shared network. +TEST_F(Dhcpv4SharedNetworkTest, randomAndFlqAllocation) { + // Create the base client and server configuration. + Dhcp4Client client(Dhcp4Client::SELECTING); + configure(NETWORKS_CONFIG[21], *client.getServer()); + + // Record what addresses have been allocated. + std::set allocated_set; + + // Simulate allocations from different clients. + for (auto i = 0; i < 20; ++i) { + // Create a client from the base client. + Dhcp4Client next_client(client.getServer(), Dhcp4Client::SELECTING); + next_client.useRelay(true, IOAddress("192.3.5.6"), IOAddress("10.0.0.2")); + // Run 4-way exchange. + ASSERT_NO_THROW(next_client.doDORA()); + // Make sure that the server responded. + ASSERT_TRUE(next_client.getContext().response_); + // Make sure that the server has responded with DHCPACK. + ASSERT_EQ(DHCPACK, static_cast(next_client.getContext().response_->getType())); + // Make sure that the address is not zero. + ASSERT_FALSE(next_client.config_.lease_.addr_.isV4Zero()); + // Remember allocated address uniqueness. + allocated_set.insert(next_client.config_.lease_.addr_.toText()); + } + // Make sure that we have 20 distinct allocations. + ASSERT_EQ(20, allocated_set.size()); +} + } // end of anonymous namespace diff --git a/src/bin/dhcp6/tests/dhcp6_test_utils.cc b/src/bin/dhcp6/tests/dhcp6_test_utils.cc index c80c601594..68fd25c117 100644 --- a/src/bin/dhcp6/tests/dhcp6_test_utils.cc +++ b/src/bin/dhcp6/tests/dhcp6_test_utils.cc @@ -1029,6 +1029,13 @@ Dhcpv6SrvTest::configure(const std::string& config, expiration_cfg->setHoldReclaimedTime(0); } + try { + CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->initAllocatorsAfterConfigure(); + } catch (const std::exception& ex) { + ADD_FAILURE() << "Error initializing the allocators after configure: " + << ex.what(); + } + try { CfgMultiThreading::apply(CfgMgr::instance().getStagingCfg()->getDHCPMultiThreading()); } catch (const std::exception& ex) { diff --git a/src/bin/dhcp6/tests/shared_network_unittest.cc b/src/bin/dhcp6/tests/shared_network_unittest.cc index 714102f44b..17c09b9cac 100644 --- a/src/bin/dhcp6/tests/shared_network_unittest.cc +++ b/src/bin/dhcp6/tests/shared_network_unittest.cc @@ -1101,8 +1101,44 @@ const char* NETWORKS_CONFIG[] = { " ]" " }" " ]" - "}" + "}", +// Configuration #23. +// - a shared network with two subnets +// - first subnet uses the FLQ allocator +// - second subnet uses the random allocator + "{" + " \"shared-networks\": [" + " {" + " \"name\": \"frog\"," + " \"interface\": \"eth1\"," + " \"subnet6\": [" + " {" + " \"subnet\": \"2001:db8:1::/64\"," + " \"pd-allocator\": \"flq\"," + " \"pd-pools\": [" + " {" + " \"prefix\": \"2001:db8:1::\"," + " \"prefix-len\": 64," + " \"delegated-len\": 68" + " }" + " ]" + " }," + " {" + " \"subnet\": \"2001:db8:2::/64\"," + " \"pd-allocator\": \"random\"," + " \"pd-pools\": [" + " {" + " \"prefix\": \"2001:db8:2::\"," + " \"prefix-len\": 64," + " \"delegated-len\": 68" + " }" + " ]" + " }" + " ]" + " }" + " ]" + "}" }; /// @Brief Test fixture class for DHCPv6 server using shared networks. @@ -2497,6 +2533,39 @@ TEST_F(Dhcpv6SharedNetworkTest, poolInSubnetSelectedByClass) { EXPECT_EQ(1, client2.getLeasesWithNonZeroLifetime().size()); } +// Test that different allocator types can be used within a shared network. +// All available prefixes should be delegated from the subnets belonging to +// the shared network. +TEST_F(Dhcpv6SharedNetworkTest, randomAndFlqAllocation) { + // Create the base client and server configuration. + Dhcp6Client client; + ASSERT_NO_FATAL_FAILURE(configure(NETWORKS_CONFIG[23], *client.getServer())); + + // Record what prefixes have been allocated. + std::set allocated_set; + + // Simulate allocations from different clients. + for (auto i = 0; i < 32; ++i) { + // Create a client from the base client. + Dhcp6Client next_client(client.getServer()); + next_client.setInterface("eth1"); + next_client.requestPrefix(); + // Run 4-way exchange. + ASSERT_NO_THROW(next_client.doSARR()); + // Make sure that the server responded. + ASSERT_TRUE(next_client.getContext().response_); + auto leases = next_client.getLeasesByType(Lease::TYPE_PD); + ASSERT_EQ(1, leases.size()); + // Make sure that the prefix is not zero. + ASSERT_FALSE(leases[0].addr_.isV6Zero()); + // Remember the allocated prefix uniqueness. + allocated_set.insert(leases[0].addr_.toText()); + } + // Make sure that we have 32 distinct allocations. + ASSERT_EQ(32, allocated_set.size()); +} + + // Verify option processing precedence // Order is global < class < shared-network < subnet < pools < host reservation TEST_F(Dhcpv6SharedNetworkTest, precedenceGlobal) { diff --git a/src/lib/dhcpsrv/alloc_engine.cc b/src/lib/dhcpsrv/alloc_engine.cc index 64ec6dce17..9f1b2f2d15 100644 --- a/src/lib/dhcpsrv/alloc_engine.cc +++ b/src/lib/dhcpsrv/alloc_engine.cc @@ -1022,6 +1022,11 @@ AllocEngine::allocateBestMatch(ClientContext6& ctx, candidate = allocator->pickAddress(classes, ctx.duid_, hint); } + // An allocator may return zero address when it has pools exhausted. + if (candidate.isV6Zero()) { + break; + } + // First check for reservation when it is the choice. if (check_reservation_first && in_subnet && !out_of_pool) { auto hosts = getIPv6Resrv(subnet->getID(), candidate); @@ -4406,6 +4411,11 @@ AllocEngine::allocateUnreservedLease4(ClientContext4& ctx) { client_id, ctx.requested_address_); + // An allocator may return zero address when it has pools exhausted. + if (candidate.isV4Zero()) { + break; + } + if (exclude_first_last_24) { // Exclude .0 and .255 addresses. auto const& bytes = candidate.toBytes();