2
0
mirror of https://gitlab.isc.org/isc-projects/kea synced 2025-08-22 18:08:16 +00:00

[#1387] Checkpoint: server code

This commit is contained in:
Francis Dupont 2024-07-02 23:36:29 +02:00
parent 9e208d0248
commit 3cb38f1943
9 changed files with 360 additions and 35 deletions

View File

@ -1152,6 +1152,9 @@
// List of reserved IPv6 prefixes.
"prefixes": [ "2001:db8:2:abcd::/64" ],
// List of excluded IPv6 prefixes.
"excluded-prefixes": [ "2001:db8:2:abcd:1::/80" ],
// Reserved hostname.
"hostname": "foo.example.com",

View File

@ -4285,8 +4285,26 @@ another.
An empty ``excluded-prefixes`` list or a list with only empty strings
can be omitted (and will be omitted when produced by Kea).
todo: add something about adding the pd-exclude option to returned
prefixes as done for prefix delegation pools.
::
"reservations": [
{
"duid": "01:02:03:04:05:06:07:08:09:0A",
"ip-addresses": [ "2001:db8:1::103" ],
"prefixes": [ "2001:db8::/48 ],
"excluded-prefixes": [ "2001:db8:0:1::/64 ],
"hostname": "foo.example.com"
}
],
...
.. note::
Host reservations have precedence over prefix pools so when a reserved
prefix without an excluded prefix is assigned no pd-exclude option
is added to the prefix option even the prefix is in a configured
prefix pool with an excluded prefix (different from previous behavior).
.. _reservation6-conflict:

View File

@ -1720,6 +1720,15 @@ ControlCharacterFill [^"\\]|\\["\\/bfnrtu]
}
}
\"excluded-prefixes\" {
switch(driver.ctx_) {
case isc::dhcp::Parser6Context::RESERVATIONS:
return isc::dhcp::Dhcp6Parser::make_EXCLUDED_PREFIXES(driver.loc_);
default:
return isc::dhcp::Dhcp6Parser::make_STRING("excluded-prefixes", driver.loc_);
}
}
\"duid\" {
switch(driver.ctx_) {
case isc::dhcp::Parser6Context::MAC_SOURCES:

View File

@ -189,6 +189,7 @@ using namespace std;
RESERVATIONS "reservations"
IP_ADDRESSES "ip-addresses"
PREFIXES "prefixes"
EXCLUDED_PREFIXES "excluded-prefixes"
DUID "duid"
HW_ADDRESS "hw-address"
HOSTNAME "hostname"
@ -2377,6 +2378,7 @@ reservation_param: duid
| reservation_client_classes
| ip_addresses
| prefixes
| excluded_prefixes
| hw_address
| hostname
| flex_id_value
@ -2408,6 +2410,17 @@ prefixes: PREFIXES {
ctx.leave();
};
excluded_prefixes: EXCLUDED_PREFIXES {
ctx.unique("excluded-prefixes", ctx.loc2pos(@1));
ElementPtr l(new ListElement(ctx.loc2pos(@1)));
ctx.stack_.back()->set("excluded-prefixes", l);
ctx.stack_.push_back(l);
ctx.enter(ctx.NO_KEYWORD);
} COLON list_strings {
ctx.stack_.pop_back();
ctx.leave();
};
duid: DUID {
ctx.unique("duid", ctx.loc2pos(@1));
ctx.enter(ctx.NO_KEYWORD);

View File

@ -2462,6 +2462,32 @@ Dhcpv6Srv::getMAC(const Pkt6Ptr& pkt) {
return (hwaddr);
}
OptionPtr
Dhcpv6Srv::getPDExclude(const AllocEngine::ClientContext6& ctx,
const Lease6Ptr& lease) {
// Search the reservation the prefix is from.
ConstHostPtr host = ctx.currentHost();
if (host) {
IPv6ResrvRange resvs = host->getIPv6Reservations(IPv6Resrv::TYPE_PD);
BOOST_FOREACH(auto const& resv, resvs) {
if ((resv.second.getPrefix() == lease->addr_) &&
(resv.second.getPrefixLen() == lease->prefixlen_)) {
return (resv.second.getPDExclude());
}
}
}
// Search the pool the address is from.
const Subnet6Ptr& subnet = ctx.subnet_;
Pool6Ptr pool = boost::dynamic_pointer_cast<Pool6>(
subnet->getPool(Lease::TYPE_PD, lease->addr_));
if (pool) {
return (pool->getPrefixExcludeOption());
}
return (OptionPtr());
}
OptionPtr
Dhcpv6Srv::assignIA_NA(const Pkt6Ptr& query,
AllocEngine::ClientContext6& ctx,
@ -2718,15 +2744,9 @@ Dhcpv6Srv::assignIA_PD(const Pkt6Ptr& query,
ia_rsp->addOption(addr);
if (pd_exclude_requested) {
// PD exclude option has been requested via ORO, thus we need to
// include it if the pool configuration specifies this option.
Pool6Ptr pool = boost::dynamic_pointer_cast<
Pool6>(subnet->getPool(Lease::TYPE_PD, l->addr_));
if (pool) {
Option6PDExcludePtr pd_exclude_option = pool->getPrefixExcludeOption();
if (pd_exclude_option) {
addr->addOption(pd_exclude_option);
}
OptionPtr pd_exclude_option = getPDExclude(ctx, l);
if (pd_exclude_option) {
addr->addOption(pd_exclude_option);
}
}
}
@ -3049,16 +3069,9 @@ Dhcpv6Srv::extendIA_PD(const Pkt6Ptr& query,
ia_rsp->addOption(prf);
if (pd_exclude_requested) {
// PD exclude option has been requested via ORO, thus we need to
// include it if the pool configuration specifies this option.
Pool6Ptr pool = boost::dynamic_pointer_cast<
Pool6>(subnet->getPool(Lease::TYPE_PD, l->addr_));
if (pool) {
Option6PDExcludePtr pd_exclude_option = pool->getPrefixExcludeOption();
if (pd_exclude_option) {
prf->addOption(pd_exclude_option);
}
OptionPtr pd_exclude_option = getPDExclude(ctx, l);
if (pd_exclude_option) {
prf->addOption(pd_exclude_option);
}
}

View File

@ -1059,6 +1059,14 @@ protected:
void checkDynamicSubnetChange(const Pkt6Ptr& question, Pkt6Ptr& answer,
AllocEngine::ClientContext6& ctx,
const Subnet6Ptr orig_subnet);
/// @brief Return the PD exclude option to include.
///
/// @param ctx client context (contains subnet and hosts).
/// @param lease lease (contains address/prefix and prefix length).
OptionPtr getPDExclude(const AllocEngine::ClientContext6& ctx,
const Lease6Ptr& lease);
public:
/// Used for DHCPv4-over-DHCPv6 too.

View File

@ -4050,7 +4050,7 @@ TEST_F(Dhcpv6SrvTest, generateFqdnUpdate) {
EXPECT_EQ("myhost-2001-db8-1--1.example.com.", l->hostname_);
// Should see follow log message ids in the log file.
EXPECT_EQ(1, countFile("DHCP6_DDNS_FQDN_GENERATED"));
EXPECT_EQ(1, countFile("DHCP6_DDNS_FQDN_GENERATED"));
EXPECT_EQ(0, countFile("DHCP6_DDNS_GENERATED_FQDN_UPDATE_FAIL"));
}
@ -4104,7 +4104,7 @@ TEST_F(Dhcpv6SrvTest, generateFqdnNoUpdate) {
// Check that an IA_NA with an iaaddr was returned for the requested
// address with lifetimes of 0.
boost::shared_ptr<Option6IAAddr> iaaddr = checkIA_NA(reply, 234, 0, 0);
boost::shared_ptr<Option6IAAddr> iaaddr = checkIA_NA(reply, 234, 0, 0);
ASSERT_TRUE(iaaddr);
EXPECT_EQ(addr, iaaddr->getAddress());
EXPECT_EQ(0, iaaddr->getPreferred());

View File

@ -1,4 +1,4 @@
// Copyright (C) 2015-2023 Internet Systems Consortium, Inc. ("ISC")
// Copyright (C) 2015-2024 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
@ -61,6 +61,22 @@ namespace {
/// - prefix pool: 3000::/72
/// - excluded prefix 3000::1000/120 in a prefix pool.
///
/// - Configuration 6:
/// - addresses and prefixes
/// - 1 subnet with one address pool and one prefix pool
/// - address pool: 2001:db8:1::/64
/// - prefix pool: 3000::/72
/// - excluded prefix 3000::1000/120 in a prefix pool.
/// - reservation for 3000::/80 (has precedence over the prefix pool).
///
/// - Configuration 7:
/// - addresses and prefixes
/// - 1 subnet with one address pool and one prefix pool
/// - address pool: 2001:db8:1::/64
/// - prefix pool: 3000::/72
/// - excluded prefix 3000::1000/120 in a prefix pool.
/// - reservation for 3000::/80 with 3000::2000/120 excluded prefix.
///
const char* RENEW_CONFIGS[] = {
// Configuration 0
"{ \"interfaces-config\": {"
@ -224,7 +240,67 @@ const char* RENEW_CONFIGS[] = {
" \"interface-id\": \"\","
" \"interface\": \"eth0\""
" } ],"
"\"valid-lifetime\": 4000 }"
"\"valid-lifetime\": 4000"
"}",
// Configuration 6
"{ \"interfaces-config\": {"
" \"interfaces\": [ \"*\" ]"
"},"
"\"preferred-lifetime\": 3000,"
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
"\"subnet6\": [ { "
" \"id\": 1, "
" \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
" \"pd-pools\": ["
" { \"prefix\": \"3000::\", "
" \"prefix-len\": 72, "
" \"delegated-len\": 80,"
" \"excluded-prefix\": \"3000::1000\","
" \"excluded-prefix-len\": 120"
" } ],"
" \"subnet\": \"2001:db8:1::/48\", "
" \"reservations\": ["
" {"
" \"duid\": \"01:02:03:05\","
" \"prefixes\": [ \"3000::/80\" ]"
" } ],"
" \"interface-id\": \"\","
" \"interface\": \"eth0\""
" } ],"
"\"valid-lifetime\": 4000"
"}",
// Configuration 7
"{ \"interfaces-config\": {"
" \"interfaces\": [ \"*\" ]"
"},"
"\"preferred-lifetime\": 3000,"
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
"\"subnet6\": [ { "
" \"id\": 1, "
" \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
" \"pd-pools\": ["
" { \"prefix\": \"3000::\", "
" \"prefix-len\": 72, "
" \"delegated-len\": 80,"
" \"excluded-prefix\": \"3000::1000\","
" \"excluded-prefix-len\": 120"
" } ],"
" \"subnet\": \"2001:db8:1::/48\", "
" \"reservations\": ["
" {"
" \"duid\": \"01:02:03:05\","
" \"prefixes\": [ \"3000::/80\" ],"
" \"excluded-prefixes\": [ \"3000::2000/120\" ]"
" } ],"
" \"interface-id\": \"\","
" \"interface\": \"eth0\""
" } ],"
"\"valid-lifetime\": 4000"
"}"
};
/// @brief Test fixture class for testing Renew.
@ -314,8 +390,8 @@ TEST_F(RenewTest, requestPrefixInRenew) {
}
// Test that it is possible to renew a prefix lease with a Prefix Exclude
// option being included during renew.
TEST_F(RenewTest, renewWithExcludedPrefix) {
// option from PD pool being included during renew.
TEST_F(RenewTest, renewWithExcludedPrefixPool) {
Dhcp6Client client;
// Configure client to request IA_NA and IA_PD.
@ -325,7 +401,7 @@ TEST_F(RenewTest, renewWithExcludedPrefix) {
// Request Prefix Exclude option.
client.requestOption(D6O_PD_EXCLUDE);
// Configure the server with NA pools only.
// Configure the server with NA and PD pools but without excluded prefix.
ASSERT_NO_THROW(configure(RENEW_CONFIGS[2], *client.getServer()));
// Perform 4-way exchange.
@ -399,6 +475,95 @@ TEST_F(RenewTest, renewWithExcludedPrefix) {
EXPECT_EQ(120, static_cast<unsigned>(pd_exclude->getExcludedPrefixLength()));
}
// Test that it is possible to renew a prefix lease with a Prefix Exclude
// option from host reservation being included during renew.
TEST_F(RenewTest, renewWithExcludedPrefixHost) {
Dhcp6Client client;
// Set DUID matching the one used to create host reservations.
client.setDUID("01:02:03:05");
// Configure client to request IA_NA and IA_PD.
client.requestAddress(na_iaid_);
client.requestPrefix(pd_iaid_);
// Request Prefix Exclude option.
client.requestOption(D6O_PD_EXCLUDE);
// Configure the server with a reservation but without excluded prefix.
ASSERT_NO_THROW(configure(RENEW_CONFIGS[6], *client.getServer()));
// Perform 4-way exchange.
ASSERT_NO_THROW(client.doSARR());
// Simulate aging of leases.
client.fastFwdTime(1000);
// Make sure that the client has acquired NA lease.
std::vector<Lease6> leases_client_na = client.getLeasesByType(Lease::TYPE_NA);
ASSERT_EQ(1, leases_client_na.size());
EXPECT_EQ(STATUS_Success, client.getStatusCode(na_iaid_));
// The client should also acquire a PD lease.
std::vector<Lease6> leases_client_pd = client.getLeasesByType(Lease::TYPE_PD);
ASSERT_EQ(1, leases_client_pd.size());
ASSERT_EQ(STATUS_Success, client.getStatusCode(pd_iaid_));
// Send Renew message to the server, including IA_NA and IA_PD.
ASSERT_NO_THROW(client.doRenew());
std::vector<Lease6> leases_client_na_renewed =
client.getLeasesByType(Lease::TYPE_NA);
ASSERT_EQ(1, leases_client_na_renewed.size());
EXPECT_EQ(STATUS_Success, client.getStatusCode(na_iaid_));
std::vector<Lease6> leases_client_pd_renewed =
client.getLeasesByType(Lease::TYPE_PD);
ASSERT_EQ(1, leases_client_pd_renewed.size());
EXPECT_EQ(STATUS_Success, client.getStatusCode(pd_iaid_));
// Make sure that Prefix Exclude option hasn't been included.
OptionPtr option = client.getContext().response_->getOption(D6O_IA_PD);
ASSERT_TRUE(option);
option = option->getOption(D6O_IAPREFIX);
ASSERT_TRUE(option);
option = option->getOption(D6O_PD_EXCLUDE);
ASSERT_FALSE(option);
// Reconfigure the server to use the reservation with excluded prefix.
configure(RENEW_CONFIGS[7], *client.getServer());
// Send Renew message to the server, including IA_NA and IA_PD.
ASSERT_NO_THROW(client.doRenew());
// Make sure that the client has acquired NA lease.
leases_client_na_renewed = client.getLeasesByType(Lease::TYPE_NA);
ASSERT_EQ(1, leases_client_na_renewed.size());
EXPECT_EQ(STATUS_Success, client.getStatusCode(na_iaid_));
// Make sure that the client has acquired PD lease.
leases_client_pd_renewed = client.getLeasesByType(Lease::TYPE_PD);
ASSERT_EQ(1, leases_client_pd_renewed.size());
EXPECT_EQ(STATUS_Success, client.getStatusCode(pd_iaid_));
// The leases should have been renewed.
EXPECT_GE(leases_client_na_renewed[0].cltt_ - leases_client_na[0].cltt_, 1000);
EXPECT_GE(leases_client_pd_renewed[0].cltt_ - leases_client_pd[0].cltt_, 1000);
// This time, the Prefix Exclude option should be included.
option = client.getContext().response_->getOption(D6O_IA_PD);
ASSERT_TRUE(option);
option = option->getOption(D6O_IAPREFIX);
ASSERT_TRUE(option);
option = option->getOption(D6O_PD_EXCLUDE);
ASSERT_TRUE(option);
Option6PDExcludePtr pd_exclude = boost::dynamic_pointer_cast<Option6PDExclude>(option);
ASSERT_TRUE(pd_exclude);
EXPECT_EQ("3000::2000", pd_exclude->getExcludedPrefix(IOAddress("3000::"),
80).toText());
EXPECT_EQ(120, static_cast<unsigned>(pd_exclude->getExcludedPrefixLength()));
}
// This test verifies that the client can request a prefix delegation
// with a hint, while it is renewing an address lease.
TEST_F(RenewTest, requestPrefixInRenewUseHint) {

View File

@ -74,6 +74,16 @@ namespace {
/// - One subnet with three distinct pools.
/// - Random allocator enabled globally for delegated prefixes.
/// - Iterative allocator for address allocation.
///
/// - Configation 7:
/// - Cache max age and threshold.
///
/// - Configuration 8 (derived from 3):
/// - one subnet 3000::/32 used on eth0 interface
/// - prefixes of length 64, delegated from the pool: 2001:db8:3::/48
/// - Excluded Prefix specified (RFC 6603).
/// - Reservation (which has precedence over the pool) with excluded prefix.
///
const char* CONFIGS[] = {
// Configuration 0
"{ \"interfaces-config\": {"
@ -328,6 +338,35 @@ const char* CONFIGS[] = {
],
"valid-lifetime": 600
})",
// Configuration 8
"{ \"interfaces-config\": {"
" \"interfaces\": [ \"*\" ]"
"},"
"\"preferred-lifetime\": 3000,"
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
"\"subnet6\": [ { "
" \"id\": 1, "
" \"pd-pools\": ["
" { \"prefix\": \"2001:db8:3::\", "
" \"prefix-len\": 48, "
" \"delegated-len\": 64,"
" \"excluded-prefix\": \"2001:db8:3::1000\","
" \"excluded-prefix-len\": 120"
" } ],"
" \"subnet\": \"3000::/32\", "
" \"reservations\": ["
" {"
" \"duid\": \"01:02:03:05\","
" \"prefixes\": [ \"2001:db8:3::/64\" ],"
" \"excluded-prefixes\": [ \"2001:db8:3::2000/120\" ]"
" } ],"
" \"interface-id\": \"\","
" \"interface\": \"eth0\""
" } ],"
"\"valid-lifetime\": 4000"
"}",
};
/// @brief Test fixture class for testing 4-way exchange: Solicit-Advertise,
@ -379,8 +418,13 @@ public:
/// @brief This test verifies that it is possible to specify an excluded
/// prefix (RFC 6603) and send it back to the client requesting prefix
/// delegation.
void directClientExcludedPrefix();
/// delegation using a pool.
void directClientExcludedPrefixPool();
/// @brief This test verifies that it is possible to specify an excluded
/// prefix (RFC 6603) and send it back to the client requesting prefix
/// delegation using a reservation.
void directClientExcludedPrefixHost();
/// @brief Check that when the client includes the Rapid Commit option in
/// its Solicit, the server responds with Reply and commits the lease.
@ -638,7 +682,7 @@ TEST_F(SARRTest, optionsInheritanceMultiThreading) {
}
void
SARRTest::directClientExcludedPrefix() {
SARRTest::directClientExcludedPrefixPool() {
Dhcp6Client client;
// Configure client to request IA_PD.
client.requestPrefix();
@ -677,14 +721,66 @@ SARRTest::directClientExcludedPrefix() {
EXPECT_EQ(120, static_cast<unsigned>(pd_exclude->getExcludedPrefixLength()));
}
TEST_F(SARRTest, directClientExcludedPrefix) {
TEST_F(SARRTest, directClientExcludedPrefixPool) {
Dhcpv6SrvMTTestGuard guard(*this, false);
directClientExcludedPrefix();
directClientExcludedPrefixPool();
}
TEST_F(SARRTest, directClientExcludedPrefixMultiThreading) {
TEST_F(SARRTest, directClientExcludedPrefixPoolMultiThreading) {
Dhcpv6SrvMTTestGuard guard(*this, true);
directClientExcludedPrefix();
directClientExcludedPrefixPool();
}
void
SARRTest::directClientExcludedPrefixHost() {
Dhcp6Client client;
// Set DUID matching the one used to create host reservations.
client.setDUID("01:02:03:05");
// Configure client to request IA_PD.
client.requestPrefix();
client.requestOption(D6O_PD_EXCLUDE);
configure(CONFIGS[8], *client.getServer());
// Make sure we ended-up having expected number of subnets configured.
const Subnet6Collection* subnets = CfgMgr::instance().getCurrentCfg()->
getCfgSubnets6()->getAll();
ASSERT_EQ(1, subnets->size());
// Perform 4-way exchange.
ASSERT_NO_THROW(client.doSARR());
// Server should have assigned a prefix.
ASSERT_EQ(1, client.getLeaseNum());
Lease6 lease_client = client.getLease(0);
EXPECT_EQ(64, lease_client.prefixlen_);
EXPECT_EQ(3000, lease_client.preferred_lft_);
EXPECT_EQ(4000, lease_client.valid_lft_);
Lease6Ptr lease_server = checkLease(lease_client);
// Check that the server recorded the lease.
ASSERT_TRUE(lease_server);
OptionPtr option = client.getContext().response_->getOption(D6O_IA_PD);
ASSERT_TRUE(option);
Option6IAPtr ia = boost::dynamic_pointer_cast<Option6IA>(option);
ASSERT_TRUE(ia);
option = ia->getOption(D6O_IAPREFIX);
ASSERT_TRUE(option);
Option6IAPrefixPtr pd_option = boost::dynamic_pointer_cast<Option6IAPrefix>(option);
ASSERT_TRUE(pd_option);
option = pd_option->getOption(D6O_PD_EXCLUDE);
ASSERT_TRUE(option);
Option6PDExcludePtr pd_exclude = boost::dynamic_pointer_cast<Option6PDExclude>(option);
ASSERT_TRUE(pd_exclude);
EXPECT_EQ("2001:db8:3::2000", pd_exclude->getExcludedPrefix(IOAddress("2001:db8:3::"),
64).toText());
EXPECT_EQ(120, static_cast<unsigned>(pd_exclude->getExcludedPrefixLength()));
}
TEST_F(SARRTest, directClientExcludedPrefixHost) {
Dhcpv6SrvMTTestGuard guard(*this, false);
directClientExcludedPrefixHost();
}
TEST_F(SARRTest, directClientExcludedPrefixHostMultiThreading) {
Dhcpv6SrvMTTestGuard guard(*this, true);
directClientExcludedPrefixHost();
}
void