// Collect host identifiers if host reservations enabled. The identifiers
// are stored in order of preference. The server will use them in that
// order to search for host reservations.
+ SharedNetwork6Ptr sn;
if (ctx.subnet_) {
const ConstCfgHostOperationsPtr cfg =
CfgMgr::instance().getCurrentCfg()->getCfgHostOperations6();
// Find host reservations using specified identifiers.
alloc_engine_->findReservation(ctx);
+
+ // Get shared network to see if it is set for a subnet.
+ ctx.subnet_->getSharedNetwork(sn);
}
// 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.
+ // they are meant to affect pool selection. Also, if the subnet does not
+ // belong to a shared network we can use the reserved client classes
+ // because there is no way our subnet could change. Such classes may
+ // affect selection of a pool within the selected subnet.
auto global_host = ctx.globalHost();
- if (global_host && !global_host->getClientClasses6().empty()) {
- // Previously evaluated classes must be ignored because having new
- // classes fetched from the hosts db may eliminate some of them.
- pkt->classes_.clear();
+ auto current_host = ctx.currentHost();
+ if ((global_host && !global_host->getClientClasses6().empty()) ||
+ (!sn && current_host && !current_host->getClientClasses6().empty())) {
+ // We have already evaluated client classes and some of them may
+ // be in conflict with the reserved classes. Therefore, we need to
+ // remove those that were assigned as a result of evaluation.
+ // That preserves built-in classes and the classes set explicitly
+ // by the hooks libraries.
+ const ClientClassDictionaryPtr& dict =
+ CfgMgr::instance().getCurrentCfg()->getClientClassDictionary();
+ const ClientClassDefListPtr& defs_ptr = dict->getClasses();
+ for (auto def : *defs_ptr) {
+ ctx.query_->classes_.erase(def->getName());
+ }
setReservedClientClasses(pkt, ctx);
+ evaluateClasses(pkt, false);
}
// Set KNOWN builtin class if something was found, UNKNOWN if not.
processClientFqdn(solicit, response, ctx);
assignLeases(solicit, response, ctx);
- setNonGlobalReservedClientClasses(solicit, ctx);
+ conditionallySetReservedClientClasses(solicit, ctx);
requiredClassify(solicit, ctx);
copyClientOptions(solicit, response);
processClientFqdn(request, reply, ctx);
assignLeases(request, reply, ctx);
- setNonGlobalReservedClientClasses(request, ctx);
+ conditionallySetReservedClientClasses(request, ctx);
requiredClassify(request, ctx);
copyClientOptions(request, reply);
processClientFqdn(renew, reply, ctx);
extendLeases(renew, reply, ctx);
- setNonGlobalReservedClientClasses(renew, ctx);
+ conditionallySetReservedClientClasses(renew, ctx);
requiredClassify(renew, ctx);
copyClientOptions(renew, reply);
processClientFqdn(rebind, reply, ctx);
extendLeases(rebind, reply, ctx);
- setNonGlobalReservedClientClasses(rebind, ctx);
+ conditionallySetReservedClientClasses(rebind, ctx);
requiredClassify(rebind, ctx);
copyClientOptions(rebind, reply);
Dhcpv6Srv::processConfirm(AllocEngine::ClientContext6& ctx) {
Pkt6Ptr confirm = ctx.query_;
- setNonGlobalReservedClientClasses(confirm, ctx);
+ conditionallySetReservedClientClasses(confirm, ctx);
requiredClassify(confirm, ctx);
// Get IA_NAs from the Confirm. If there are none, the message is
Dhcpv6Srv::processRelease(AllocEngine::ClientContext6& ctx) {
Pkt6Ptr release = ctx.query_;
- setNonGlobalReservedClientClasses(release, ctx);
+ conditionallySetReservedClientClasses(release, ctx);
requiredClassify(release, ctx);
// Create an empty Reply message.
Dhcpv6Srv::processDecline(AllocEngine::ClientContext6& ctx) {
Pkt6Ptr decline = ctx.query_;
- setNonGlobalReservedClientClasses(decline, ctx);
+ conditionallySetReservedClientClasses(decline, ctx);
requiredClassify(decline, ctx);
// Create an empty Reply message.
Dhcpv6Srv::processInfRequest(AllocEngine::ClientContext6& ctx) {
Pkt6Ptr inf_request = ctx.query_;
- setNonGlobalReservedClientClasses(inf_request, ctx);
+ conditionallySetReservedClientClasses(inf_request, ctx);
requiredClassify(inf_request, ctx);
// Create a Reply packet, with the same trans-id as the client's.
}
void
-Dhcpv6Srv::setNonGlobalReservedClientClasses(const Pkt6Ptr& pkt,
- const AllocEngine::ClientContext6& ctx) {
- if (!ctx.globalHost()) {
- setReservedClientClasses(pkt, ctx);
+Dhcpv6Srv::conditionallySetReservedClientClasses(const Pkt6Ptr& pkt,
+ const AllocEngine::ClientContext6& ctx) {
+ if (ctx.subnet_) {
+ SharedNetwork6Ptr shared_network;
+ ctx.subnet_->getSharedNetwork(shared_network);
+ if (shared_network && !ctx.globalHost()) {
+ setReservedClientClasses(pkt, ctx);
+ }
}
}
" }\n"
" ]\n"
"}]\n"
+ "}",
+
+ // Configuration 12 client-class reservation 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"
+ "\"valid-lifetime\": 4000,\n"
+ "\"subnet6\": [\n"
+ " {\n"
+ " \"subnet\": \"2001:db8:1::/64\", \n"
+ " \"id\": 10,"
+ " \"reservations\": [{ \n"
+ " \"duid\": \"01:02:03:05\",\n"
+ " \"client-classes\": [ \"reserved_class\" ]\n"
+ " }],\n"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"2001:db8:1::10-2001:db8:1::11\","
+ " \"client-class\": \"reserved_class\""
+ " },"
+ " {"
+ " \"pool\": \"2001:db8:1::20-2001:db8:1::21\","
+ " \"client-class\": \"unreserved_class\""
+ " }"
+ " ],\n"
+ " \"interface\": \"eth0\"\n"
+ " }\n"
+ "]\n"
"}"
};
///
/// @param config_idx Index of the server configuration from the
/// @c CONFIGS array.
- void testGlobalClassSubnetPoolSelection(const int config_idx);
+ /// @param first_address Address to be allocated from the pool having
+ /// a reservation.
+ /// @param second_address Address to be allocated from the pool not
+ /// having a reservation.
+ void testGlobalClassSubnetPoolSelection(const int config_idx,
+ const std::string& first_address = "2001:db8:1::10",
+ const std::string& second_address = "2001:db8:2::10");
/// @brief Configures client to include 6 IAs without hints.
///
}
void
-HostTest::testGlobalClassSubnetPoolSelection(const int config_idx) {
+HostTest::testGlobalClassSubnetPoolSelection(const int config_idx,
+ const std::string& first_address,
+ const std::string& second_address) {
Dhcp6Client client_resrv;
// Use DUID for which we have host reservation including client class.
// Let's use the 2001:db8:2::10 as a hint to make sure that the server
// refuses allocating it and uses the sole pool available for this
// client.
- client_resrv.requestAddress(1, IOAddress("2001:db8:2::10"));
+ client_resrv.requestAddress(1, IOAddress(second_address));
ASSERT_NO_THROW(client_resrv.doSARR());
ASSERT_EQ(1, client_resrv.getLeaseNum());
Lease6 lease_client = client_resrv.getLease(0);
- EXPECT_EQ("2001:db8:1::10", lease_client.addr_.toText());
+ EXPECT_EQ(first_address, lease_client.addr_.toText());
// This client has no reservation and therefore should be
// assigned to the unreserved_class and be given an address
// Let's use the address of 2001:db8:1::10 as a hint to make sure that the
// server refuses it in favor of the 2001:db8:2::10.
- client_no_resrv.requestAddress(1, IOAddress("2001:db8:1::10"));
+ client_no_resrv.requestAddress(1, IOAddress(first_address));
ASSERT_NO_THROW(client_no_resrv.doSARR());
ASSERT_EQ(1, client_no_resrv.getLeaseNum());
lease_client = client_no_resrv.getLease(0);
- EXPECT_EQ("2001:db8:2::10", lease_client.addr_.toText());
+ EXPECT_EQ(second_address, lease_client.addr_.toText());
}
void
ASSERT_NO_FATAL_FAILURE(testGlobalClassSubnetPoolSelection(11));
}
+// Verifies that client class specified in the reservation may be
+// used to influence pool selection within a subnet.
+TEST_F(HostTest, clientClassPoolSelection) {
+ ASSERT_NO_FATAL_FAILURE(testGlobalClassSubnetPoolSelection(12, "2001:db8:1::10",
+ "2001:db8:1::20"));
+}
+
} // end of anonymous namespace