]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#2843] Use different allocators in shared net
authorMarcin Siodelski <marcin@isc.org>
Mon, 8 May 2023 08:27:59 +0000 (10:27 +0200)
committerMarcin Siodelski <marcin@isc.org>
Fri, 12 May 2023 11:02:03 +0000 (13:02 +0200)
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.

src/bin/dhcp4/tests/dhcp4_test_utils.cc
src/bin/dhcp4/tests/shared_network_unittest.cc
src/bin/dhcp6/tests/dhcp6_test_utils.cc
src/bin/dhcp6/tests/shared_network_unittest.cc
src/lib/dhcpsrv/alloc_engine.cc

index 7bfbe01550f2309e879384baa2b56b848db5b729..c4a4db593bf410e343b0bd82af3c458d016acccf 100644 (file)
@@ -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) {
index 10afcc2b1403962412ba8ef040d4adaf4d01829b..05e1aaab4965fbd2f0577c7b92418e005ead832a 100644 (file)
@@ -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<std::string> 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<int>(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
index c80c6015943ad0ffa113b348ca72a404f460b4ca..68fd25c117cc0f012d43e7f33488a0fdf6b1ab1c 100644 (file)
@@ -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) {
index 714102f44b603cde343fdacc8fc3a2e46e570728..17c09b9cacc643ed0dafc830a4de79ab68453faf 100644 (file)
@@ -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<std::string> 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) {
index 64ec6dce171cb84731003cb60561787d5bc269b2..9f1b2f2d15c1565b3ff25984faad5e6daca11b4e 100644 (file)
@@ -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();