2
0
mirror of https://gitlab.isc.org/isc-projects/kea synced 2025-08-31 14:05:33 +00:00

[#1139] Host based classes used in allocation

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.
This commit is contained in:
Marcin Siodelski
2020-03-06 17:33:16 +01:00
parent e5c189505e
commit 2b705d9172
2 changed files with 192 additions and 8 deletions

View 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);

View 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