}
}
+ // 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");
// 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);
// 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);
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);
-// 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
" }\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.
/// @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()));
// 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_);
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
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