mirror of
https://gitlab.isc.org/isc-projects/kea
synced 2025-08-31 22:15:23 +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:
@@ -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.
|
// Set KNOWN builtin class if something was found, UNKNOWN if not.
|
||||||
if (!context_->hosts_.empty()) {
|
if (!context_->hosts_.empty()) {
|
||||||
query->addClass("KNOWN");
|
query->addClass("KNOWN");
|
||||||
@@ -2784,8 +2795,12 @@ Dhcpv4Srv::processDiscover(Pkt4Ptr& discover) {
|
|||||||
|
|
||||||
// Adding any other options makes sense only when we got the lease.
|
// Adding any other options makes sense only when we got the lease.
|
||||||
if (!ex.getResponse()->getYiaddr().isV4Zero()) {
|
if (!ex.getResponse()->getYiaddr().isV4Zero()) {
|
||||||
// Assign reserved classes.
|
// If this is global reservation we have already fetched it and
|
||||||
ex.setReservedClientClasses();
|
// evaluated the classes.
|
||||||
|
if (!ex.getContext()->globalHost()) {
|
||||||
|
// Assign reserved classes.
|
||||||
|
ex.setReservedClientClasses();
|
||||||
|
}
|
||||||
// Required classification
|
// Required classification
|
||||||
requiredClassify(ex);
|
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.
|
// Adding any other options makes sense only when we got the lease.
|
||||||
if (!response->getYiaddr().isV4Zero()) {
|
if (!response->getYiaddr().isV4Zero()) {
|
||||||
// Assign reserved classes.
|
// If this is global reservation we have already fetched it and
|
||||||
ex.setReservedClientClasses();
|
// evaluated the classes.
|
||||||
|
if (!ex.getContext()->globalHost()) {
|
||||||
|
// Assign reserved classes.
|
||||||
|
ex.setReservedClientClasses();
|
||||||
|
}
|
||||||
// Required classification
|
// Required classification
|
||||||
requiredClassify(ex);
|
requiredClassify(ex);
|
||||||
|
|
||||||
@@ -3166,7 +3185,11 @@ Dhcpv4Srv::processInform(Pkt4Ptr& inform) {
|
|||||||
|
|
||||||
Pkt4Ptr ack = ex.getResponse();
|
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);
|
requiredClassify(ex);
|
||||||
|
|
||||||
buildCfgOptionList(ex);
|
buildCfgOptionList(ex);
|
||||||
|
@@ -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
|
// 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
|
// 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"
|
"]\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.
|
/// @brief Test fixture class for testing global v4 reservations.
|
||||||
@@ -206,7 +309,8 @@ public:
|
|||||||
/// @param expected_addr expected address to be assigned
|
/// @param expected_addr expected address to be assigned
|
||||||
void runDoraTest(const std::string& config, Dhcp4Client& client,
|
void runDoraTest(const std::string& config, Dhcp4Client& client,
|
||||||
const std::string& expected_host,
|
const std::string& expected_host,
|
||||||
const std::string& expected_addr) {
|
const std::string& expected_addr,
|
||||||
|
const std::string& requested_addr = "") {
|
||||||
|
|
||||||
// Configure DHCP server.
|
// Configure DHCP server.
|
||||||
ASSERT_NO_FATAL_FAILURE(configure(config, *client.getServer()));
|
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
|
// Perform 4-way exchange with the server but to not request any
|
||||||
// specific address in the DHCPDISCOVER message.
|
// 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.
|
// Make sure that the server responded.
|
||||||
ASSERT_TRUE(client.getContext().response_);
|
ASSERT_TRUE(client.getContext().response_);
|
||||||
@@ -237,7 +345,48 @@ public:
|
|||||||
EXPECT_EQ(client.config_.lease_.addr_.toText(), expected_addr);
|
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
|
// 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");
|
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
|
} // end of anonymous namespace
|
||||||
|
Reference in New Issue
Block a user