]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#1139] Host based classes used in allocation
authorMarcin Siodelski <marcin@isc.org>
Fri, 6 Mar 2020 16:33:16 +0000 (17:33 +0100)
committerMarcin Siodelski <marcin@isc.org>
Thu, 19 Mar 2020 12:14:51 +0000 (12:14 +0000)
The DHCPv4 server now takes into account client classes specified in
gobal reservations to select a subnet and/or pool for allocation within
a shared network.

src/bin/dhcp4/dhcp4_srv.cc
src/bin/dhcp4/tests/host_unittest.cc

index e44bfb17cc9d769fa51d13875a35c632038e03eb..8d21e2758681e46602136d78ffd910343cf1ac2b 100644 (file)
@@ -186,6 +186,17 @@ Dhcpv4Exchange::Dhcpv4Exchange(const AllocEnginePtr& alloc_engine,
         }
     }
 
+    // Global host reservations are independent of a selected subnet. If the
+    // global reservations contain client classes we should use them in case
+    // they are meant to affect pool selection.
+    auto global_host = context_->globalHost();
+    if (global_host && !global_host->getClientClasses4().empty()) {
+        // Previously evaluated classes must be ignored because having new
+        // classes fetched from the hosts db may eliminate some of them.
+        query->classes_.clear();
+        setReservedClientClasses();
+    }
+
     // Set KNOWN builtin class if something was found, UNKNOWN if not.
     if (!context_->hosts_.empty()) {
         query->addClass("KNOWN");
@@ -2784,8 +2795,12 @@ Dhcpv4Srv::processDiscover(Pkt4Ptr& discover) {
 
     // Adding any other options makes sense only when we got the lease.
     if (!ex.getResponse()->getYiaddr().isV4Zero()) {
-        // Assign reserved classes.
-        ex.setReservedClientClasses();
+        // If this is global reservation we have already fetched it and
+        // evaluated the classes.
+        if (!ex.getContext()->globalHost()) {
+            // Assign reserved classes.
+            ex.setReservedClientClasses();
+        }
         // Required classification
         requiredClassify(ex);
 
@@ -2852,8 +2867,12 @@ Dhcpv4Srv::processRequest(Pkt4Ptr& request, AllocEngine::ClientContext4Ptr& cont
 
     // Adding any other options makes sense only when we got the lease.
     if (!response->getYiaddr().isV4Zero()) {
-        // Assign reserved classes.
-        ex.setReservedClientClasses();
+        // If this is global reservation we have already fetched it and
+        // evaluated the classes.
+        if (!ex.getContext()->globalHost()) {
+            // Assign reserved classes.
+            ex.setReservedClientClasses();
+        }
         // Required classification
         requiredClassify(ex);
 
@@ -3166,7 +3185,11 @@ Dhcpv4Srv::processInform(Pkt4Ptr& inform) {
 
     Pkt4Ptr ack = ex.getResponse();
 
-    ex.setReservedClientClasses();
+    // If this is global reservation we have already fetched it and
+    // evaluated the classes.
+    if (!ex.getContext()->globalHost()) {
+        ex.setReservedClientClasses();
+    }
     requiredClassify(ex);
 
     buildCfgOptionList(ex);
index 9b1aa6c904738be7d84a5292559541c4396e0640..99672af753a5416c22ebf7912225fb26ec752cb3 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2018 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2018-2020 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -166,6 +166,109 @@ const char* CONFIGS[] = {
         "    }\n"
         "]\n"
         "}\n"
+    ,
+
+    // Configuration 4 client-class reservation in global, shared network
+    // and client-class guarded pools.
+    "{ \"interfaces-config\": {\n"
+        "      \"interfaces\": [ \"*\" ]\n"
+        "},\n"
+        "\"client-classes\": ["
+        "{"
+        "     \"name\": \"reserved_class\""
+        "},"
+        "{"
+        "     \"name\": \"unreserved_class\","
+        "     \"test\": \"not member('reserved_class')\""
+        "}"
+        "],\n"
+        "\"reservation-mode\": \"global\","
+        "\"valid-lifetime\": 600,\n"
+        "\"reservations\": [ \n"
+        "{\n"
+        "   \"hw-address\": \"aa:bb:cc:dd:ee:fe\",\n"
+        "   \"client-classes\": [ \"reserved_class\" ]\n"
+        "}\n"
+        "],\n"
+        "\"shared-networks\": [{"
+        "    \"name\": \"frog\",\n"
+        "    \"subnet4\": [\n"
+        "        {\n"
+        "            \"subnet\": \"10.0.0.0/24\", \n"
+        "            \"id\": 10,"
+        "            \"pools\": ["
+        "                {"
+        "                    \"pool\": \"10.0.0.10-10.0.0.11\","
+        "                    \"client-class\": \"reserved_class\""
+        "                }"
+        "            ],\n"
+        "            \"interface\": \"eth0\"\n"
+        "        },\n"
+        "        {\n"
+        "            \"subnet\": \"192.0.3.0/24\", \n"
+        "            \"id\": 11,"
+        "            \"pools\": ["
+        "                {"
+        "                    \"pool\": \"192.0.3.10-192.0.3.11\","
+        "                    \"client-class\": \"unreserved_class\""
+        "                }"
+        "            ],\n"
+        "            \"interface\": \"eth0\"\n"
+        "        }\n"
+        "    ]\n"
+        "}]\n"
+    "}",
+
+    // Configuration 5 client-class reservation in global, shared network
+    // and client-class guarded subnets.
+    "{ \"interfaces-config\": {\n"
+        "      \"interfaces\": [ \"*\" ]\n"
+        "},\n"
+        "\"client-classes\": ["
+        "{"
+        "     \"name\": \"reserved_class\""
+        "},"
+        "{"
+        "     \"name\": \"unreserved_class\","
+        "     \"test\": \"not member('reserved_class')\""
+        "}"
+        "],\n"
+        "\"reservation-mode\": \"global\","
+        "\"valid-lifetime\": 600,\n"
+        "\"reservations\": [ \n"
+        "{\n"
+        "   \"hw-address\": \"aa:bb:cc:dd:ee:fe\",\n"
+        "   \"client-classes\": [ \"reserved_class\" ]\n"
+        "}\n"
+        "],\n"
+        "\"shared-networks\": [{"
+        "    \"name\": \"frog\",\n"
+        "    \"subnet4\": [\n"
+        "        {\n"
+        "            \"subnet\": \"10.0.0.0/24\", \n"
+        "            \"id\": 10,"
+        "            \"client-class\": \"reserved_class\","
+        "            \"pools\": ["
+        "                {"
+        "                    \"pool\": \"10.0.0.10-10.0.0.10\""
+        "                }"
+        "            ],\n"
+        "            \"interface\": \"eth0\"\n"
+        "        },\n"
+        "        {\n"
+        "            \"subnet\": \"192.0.3.0/24\", \n"
+        "            \"id\": 11,"
+        "            \"client-class\": \"unreserved_class\","
+        "            \"pools\": ["
+        "                {"
+        "                    \"pool\": \"192.0.3.10-192.0.3.10\""
+        "                }"
+        "            ],\n"
+        "            \"interface\": \"eth0\"\n"
+        "        }\n"
+        "    ]\n"
+        "}]\n"
+    "}"
 };
 
 /// @brief Test fixture class for testing global v4 reservations.
@@ -206,7 +309,8 @@ public:
     /// @param expected_addr expected address to be assigned
     void runDoraTest(const std::string& config, Dhcp4Client& client,
                      const std::string& expected_host,
-                     const std::string& expected_addr) {
+                     const std::string& expected_addr,
+                     const std::string& requested_addr = "") {
 
         // Configure DHCP server.
         ASSERT_NO_FATAL_FAILURE(configure(config, *client.getServer()));
@@ -214,7 +318,11 @@ public:
 
         // Perform 4-way exchange with the server but to not request any
         // specific address in the DHCPDISCOVER message.
-        ASSERT_NO_THROW(client.doDORA());
+        boost::shared_ptr<IOAddress> hint; 
+        if (!requested_addr.empty()) {
+            hint = boost::make_shared<IOAddress>(requested_addr);
+        }
+        ASSERT_NO_THROW(client.doDORA(hint));
 
         // Make sure that the server responded.
         ASSERT_TRUE(client.getContext().response_);
@@ -237,7 +345,48 @@ public:
         EXPECT_EQ(client.config_.lease_.addr_.toText(), expected_addr);
     }
 
-
+    /// @brief Test pool or subnet selection using global class reservation.
+    ///
+    /// Verifies that client class specified in the global reservation
+    /// may be used to influence pool or subnet selection.
+    ///
+    /// @param config_idx Index of the server configuration from the
+    /// @c CONFIGS array.
+    void testGlobalClassSubnetPoolSelection(const int config_idx) {
+        Dhcp4Client client_resrv(Dhcp4Client::SELECTING);
+
+        // Use HW address for which we have host reservation including
+        // client class.
+        client_resrv.setHWAddress("aa:bb:cc:dd:ee:fe");
+        client_resrv.setIfaceName("eth0");
+
+        ASSERT_NO_FATAL_FAILURE(configure(CONFIGS[config_idx], *client_resrv.getServer()));
+
+        // This client should be given an address from the 10.0.0.0/24 pool.
+        // Let's use the 192.0.3.10 as a hint to make sure that the server
+        // refuses allocating it and uses the sole pool available for this
+        // client.
+        ASSERT_NO_THROW(client_resrv.doDORA(boost::make_shared<IOAddress>("192.0.3.10")));
+        ASSERT_TRUE(client_resrv.getContext().response_);
+        auto resp = client_resrv.getContext().response_;
+        ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+        EXPECT_EQ("10.0.0.10", resp->getYiaddr().toText());
+
+        // This client has no reservation and therefore should be
+        // assigned to the unreserved_class and be given an address
+        // from the other pool.
+        Dhcp4Client client_no_resrv(client_resrv.getServer(), Dhcp4Client::SELECTING);
+        client_no_resrv.setHWAddress("aa:bb:cc:dd:ee:ff");
+        client_no_resrv.setIfaceName("eth0");
+
+        // Let's use the address of 10.0.0.10 as a hint to make sure that the
+        // server refuses it in favor of the 192.0.3.10.
+        ASSERT_NO_THROW(client_no_resrv.doDORA(boost::make_shared<IOAddress>("10.0.0.10")));
+        ASSERT_TRUE(client_no_resrv.getContext().response_);
+        resp = client_no_resrv.getContext().response_;
+        ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+        EXPECT_EQ("192.0.3.10", resp->getYiaddr().toText());
+    }
 };
 
 // Verifies that a client, which fails to match to a global
@@ -377,4 +526,16 @@ TEST_F(HostTest, allOverGlobal) {
     runDoraTest(CONFIGS[3], client, "subnet-10-host", "192.0.5.10");
 }
 
+// Verifies that client class specified in the global reservation
+// may be used to influence pool selection.
+TEST_F(HostTest, clientClassGlobalPoolSelection) {
+    ASSERT_NO_FATAL_FAILURE(testGlobalClassSubnetPoolSelection(4));
+}
+
+// Verifies that client class specified in the global reservation
+// may be used to influence subnet selection within shared network.
+TEST_F(HostTest, clientClassGlobalSubnetSelection) {
+    ASSERT_NO_FATAL_FAILURE(testGlobalClassSubnetPoolSelection(5));
+}
+
 } // end of anonymous namespace