// 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",
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:
}
}
+\"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:
RESERVATIONS "reservations"
IP_ADDRESSES "ip-addresses"
PREFIXES "prefixes"
+ EXCLUDED_PREFIXES "excluded-prefixes"
DUID "duid"
HW_ADDRESS "hw-address"
HOSTNAME "hostname"
| reservation_client_classes
| ip_addresses
| prefixes
+ | excluded_prefixes
| hw_address
| hostname
| flex_id_value
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);
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,
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);
}
}
}
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);
}
}
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.
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"));
}
// 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());
-// 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
/// - 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\": {"
" \"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.
}
// 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.
// 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.
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) {
/// - 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\": {"
],
"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,
/// @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.
}
void
-SARRTest::directClientExcludedPrefix() {
+SARRTest::directClientExcludedPrefixPool() {
Dhcp6Client client;
// Configure client to request IA_PD.
client.requestPrefix();
EXPECT_EQ(120, static_cast<unsigned>(pd_exclude->getExcludedPrefixLength()));
}
-TEST_F(SARRTest, directClientExcludedPrefix) {
+TEST_F(SARRTest, directClientExcludedPrefixPool) {
+ Dhcpv6SrvMTTestGuard guard(*this, false);
+ directClientExcludedPrefixPool();
+}
+
+TEST_F(SARRTest, directClientExcludedPrefixPoolMultiThreading) {
+ Dhcpv6SrvMTTestGuard guard(*this, true);
+ 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);
- directClientExcludedPrefix();
+ directClientExcludedPrefixHost();
}
-TEST_F(SARRTest, directClientExcludedPrefixMultiThreading) {
+TEST_F(SARRTest, directClientExcludedPrefixHostMultiThreading) {
Dhcpv6SrvMTTestGuard guard(*this, true);
- directClientExcludedPrefix();
+ directClientExcludedPrefixHost();
}
void