diff --git a/AUTHORS b/AUTHORS
index fe0a2a7349..1a45750ffa 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -11,7 +11,7 @@ Primary developers:
- Marcin Siodelski (DHCPv4, DHCPv6 components, options handling, perfdhcp,
host reservation, lease file cleanup, lease expiration,
control agent, shared networks, high availability)
- - Thomas Markwalder (DDNS, user_chk)
+ - Thomas Markwalder (DDNS, user_chk, global host reservations)
- Jeremy C. Reed (documentation, build system, testing, release engineering)
- Wlodek Wencel (testing, release engineering)
- Francis Dupont (crypto, perfdhcp, control agent)
diff --git a/doc/guide/dhcp6-srv.xml b/doc/guide/dhcp6-srv.xml
index cf9cf3987c..ef57889451 100644
--- a/doc/guide/dhcp6-srv.xml
+++ b/doc/guide/dhcp6-srv.xml
@@ -3103,7 +3103,7 @@ should include options from the isc option space:
-
+
This feature is currently implemented for memfile backend.
@@ -3268,6 +3268,24 @@ should include options from the isc option space:
reservation checks when dealing with existing leases. Therefore, system
administrators are encouraged to use out-of-pool reservations if
possible.
+
+ Beginning with Kea 1.5.0, there is now support for global
+ host reservations. These are reservations that are specified at the
+ global level within the configuration and that do not belong to any
+ specific subnet. Kea will still match inbound client packets to a
+ subnet as before, but when the subnet's reservation mode is set to
+ "global", Kea will look for host reservations only
+ among the global reservations defined. Typcially, such reservations would
+ be used to reserve hostnames for clients which may move from one subnet
+ to another.
+
+
+ You can reserve any ip-address or prefix in a global reservation.
+ Just keep in mind that Kea will not do any sanity checking on the address
+ or prefix and that for Kea 1.5.0, support for global reservations should
+ be considered experimental.
+
+
@@ -3317,6 +3335,15 @@ should include options from the isc option space:
out-of-pool reservations. If the reserved address does not belong to a
pool, there is no way that other clients could get this address.
+
+
+ The conflict resolution mechanism does not work for global
+ reservations. As of Kea 1.5.0, it is generally recommended to not use
+ global reservations for addresses or prefixes. If you want to use it
+ anyway, you have to manually ensure that the reserved values are not
+ in the dynamic pools.
+
+
@@ -3538,10 +3565,10 @@ should include options from the isc option space:
Allowed values are:
- all - enables all host reservation
- types. This is the default value. This setting is the safest and the most
- flexible. It allows in-pool and out-of-pool reservations. As all checks
- are conducted, it is also the slowest.
+ all - enables both in-pool
+ and out-of-pool host reservation types. This is the default value. This
+ setting is the safest and the most flexible. As all checks are conducted,
+ it is also the slowest. This does not check against global reservations.
out-of-pool - allows only out of
@@ -3551,7 +3578,18 @@ should include options from the isc option space:
with in-pool addresses, thus improving performance. Do not use this mode
if any of your reservations use in-pool address. Caution is advised when
using this setting. Kea does not sanity check the reservations against
- reservation-mode and misconfiguration may cause problems.
+ reservation-mode and misconfiguration may cause
+ problems.
+
+
+ global - allows only global
+ host reservations. With this setting in place, the server searches for
+ reservations for a client only among the defined global reservations.
+ If an address is specified, the server will skip the reservation checks
+ done when dealing in other modes, thus improving performance.
+ Caution is advised when using this setting: Kea does not sanity check
+ the reservations when global and
+ misconfiguration may cause problems.
@@ -3576,9 +3614,44 @@ should include options from the isc option space:
}
]
}
-
+
+
+ An example configuration using global reservations is shown below:
+
+"Dhcp6": {
+
+
+ "reservations": [
+ {
+ "duid": "00:03:00:01:11:22:33:44:55:66",
+ "hostname": "host-one"
+ },
+ {
+ "duid": "00:03:00:01:99:88:77:66:55:44",
+ "hostname": "host-two"
+ }
+ ],
+
+ "subnet6": [
+ {
+ "subnet": "2001:db8:1::/64",
+ "reservation-mode": "global",
+ ...
+ },
+ {
+ "subnet": "2001:db8:2::/64",
+ "reservation-mode": "global",
+ ...
+ }
+ ]
+}
+
+ For more details regarding global reservations, see
+ .
+
+
Another aspect of the host reservations are different types of
identifiers. Kea 1.1.0 supports two types of identifiers
in DHCPv6: hw-address and duid, but more identifier types
@@ -3620,6 +3693,76 @@ If not specified, the default value is:
+
+
+
+ Global reservations in DHCPv6
+
+ In some deployments, such as mobile, clients can roam within the
+ network and there is a desire to specify certain parameters regardless of
+ the client's current location. To facilitate such a need, a global
+ reservation mechanism has been implemented. The idea behind it is that
+ regular host reservations are tied to specific subnets, by using specific
+ subnet-id. Kea 1.5.0 introduced a new capability to specify global
+ reservation that can be used in every subnet that has global reservations
+ enabled.
+
+ This feature can be used to assign certain parameters, such as
+ hostname or other dedicated, host-specific options. It can also be used to
+ assign addresses or prefixes. However, global reservations that assign
+ either of these bypass the whole topology determination provided by DHCP
+ logic implemented in Kea. It is very easy to misuse this feature and get
+ configuration that is inconsistent. To give a specific example, imagine a
+ global reservation for an address 2001:db8:1111::1 and two subnets
+ 2001:db8:1111::/48 and 2001:db8:ffff::/48. If global reservations are used
+ in both subnets and a device matching global host reservations visits part
+ of the network that is covered by 2001:db8:ffff::/48, it will get an IP
+ address 2001:db8:ffff::1, which will be outside of the prefix announced
+ by its local router using Router Advertisements. Such a configuration
+ would be unsuable or at the very least ridden with issues, such as the
+ downlink traffic not reaching the device.
+
+
+ To use global host reservations a configuration similar to the following
+ can be used:
+
+
+"Dhcp6:" {
+ // This specifies global reservations. They will apply to all subnets that
+ // have global reservations enabled.
+
+ "reservations": [
+ {
+ "hw-address": "aa:bb:cc:dd:ee:ff",
+ "hostname": "hw-host-dynamic"
+ },
+ {
+ "hw-address": "01:02:03:04:05:06",
+ "hostname": "hw-host-fixed",
+
+ // Use of IP address is global reservation is risky. If used outside of
+ // matching subnet, such as 3001::/64, it will result in a broken
+ // configuration being handled to the client.
+ "ip-address": "2001:db8:ff::77"
+ },
+ {
+ "duid": "01:02:03:04:05",
+ "hostname": "duid-host"
+ }
+ ],
+ "valid-lifetime": 600,
+ "subnet4": [ {
+ "subnet": "2001:db8:1::/64",
+ "reservation-mode": "global",
+ "pools": [ { "pool": "2001:db8:1::-2001:db8:1::100" } ]
+ } ]
+}
+
+
+
+ When using database backends, the global host reservations are
+ distinguished from regular reservations by using subnet-id value of
+ zero.
diff --git a/src/bin/dhcp6/tests/dhcp6_test_utils.cc b/src/bin/dhcp6/tests/dhcp6_test_utils.cc
index b836b8fbc9..274c7a04c6 100644
--- a/src/bin/dhcp6/tests/dhcp6_test_utils.cc
+++ b/src/bin/dhcp6/tests/dhcp6_test_utils.cc
@@ -699,7 +699,13 @@ Dhcpv6SrvTest::configure(const std::string& config) {
void
Dhcpv6SrvTest::configure(const std::string& config, NakedDhcpv6Srv& srv) {
ConstElementPtr json;
- ASSERT_NO_THROW(json = parseJSON(config));
+ try {
+ json = parseJSON(config);
+ } catch (const std::exception& ex) {
+ // Fatal failure on parsing error
+ FAIL() << "config parsing failed, test is broken: " << ex.what();
+ }
+
ConstElementPtr status;
// Disable the re-detect flag
@@ -710,7 +716,8 @@ Dhcpv6SrvTest::configure(const std::string& config, NakedDhcpv6Srv& srv) {
ASSERT_TRUE(status);
int rcode;
ConstElementPtr comment = isc::config::parseAnswer(rcode, status);
- ASSERT_EQ(0, rcode);
+ ASSERT_EQ(0, rcode) << "configuration failed, test is broken: "
+ << comment->str();
CfgMgr::instance().commit();
}
diff --git a/src/bin/dhcp6/tests/host_unittest.cc b/src/bin/dhcp6/tests/host_unittest.cc
index 0028b3ddb0..b3d9f33c3f 100644
--- a/src/bin/dhcp6/tests/host_unittest.cc
+++ b/src/bin/dhcp6/tests/host_unittest.cc
@@ -321,7 +321,105 @@ const char* CONFIGS[] = {
" }"
" ]"
"} ]"
- "}"
+ "}",
+
+ // Configuration 8: Global HRs TYPE_NAs
+ "{ "
+ "\"interfaces-config\": { \n"
+ " \"interfaces\": [ \"*\" ] \n"
+ "},\n "
+ "\"host-reservation-identifiers\": [ \"duid\", \"hw-address\" ], \n"
+ "\"reservations\": [ \n"
+ "{ \n"
+ " \"duid\": \"01:02:03:04\", \n"
+ " \"hostname\": \"duid-host-fixed\", \n"
+ " \"ip-addresses\": [ \"3001::1\" ] \n"
+ "}, \n"
+ "{ \n"
+ " \"duid\": \"01:02:03:05\", \n"
+ " \"hostname\": \"duid-host-dynamic\" \n"
+ "}, \n"
+ "{ \n"
+ " \"hw-address\": \"38:60:77:d5:ff:ee\", \n"
+ " \"hostname\": \"hw-host\" \n"
+ "} \n"
+ "], \n"
+ "\"valid-lifetime\": 4000, \n"
+ "\"preferred-lifetime\": 3000, \n"
+ "\"rebind-timer\": 2000, \n"
+ "\"renew-timer\": 1000, \n"
+ "\"mac-sources\": [ \"ipv6-link-local\" ], \n"
+ "\"subnet6\": [ \n"
+ " { \n"
+ " \"subnet\": \"2001:db8:1::/48\", \n"
+ " \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ], \n"
+ " \"interface\" : \"eth0\", \n"
+ " \"reservation-mode\": \"global\" \n"
+ " },"
+ " { \n"
+ " \"subnet\": \"2001:db8:2::/48\", \n"
+ " \"pools\": [ { \"pool\": \"2001:db8:2::/64\" } ], \n"
+ " \"interface\" : \"eth1\", \n"
+ " \"reservations\": [ \n"
+ " { \n"
+ " \"duid\": \"01:02:03:05\", \n"
+ " \"hostname\": \"subnet-duid-host\" \n"
+ " }] \n"
+ " }"
+ " ] \n"
+ "} \n"
+ ,
+ // Configuration 9: Global HRs TYPE_PDs
+ "{ "
+ "\"interfaces-config\": { \n"
+ " \"interfaces\": [ \"*\" ] \n"
+ "},\n "
+ "\"host-reservation-identifiers\": [ \"duid\", \"hw-address\" ], \n"
+ "\"reservations\": [ \n"
+ "{ \n"
+ " \"duid\": \"01:02:03:04\", \n"
+ " \"hostname\": \"duid-host-fixed\", \n"
+ " \"prefixes\": [ \"4000::100/120\" ]"
+ "}, \n"
+ "{ \n"
+ " \"duid\": \"01:02:03:05\", \n"
+ " \"hostname\": \"duid-host-dynamic\" \n"
+ "} \n"
+ "], \n"
+ "\"valid-lifetime\": 4000, \n"
+ "\"preferred-lifetime\": 3000, \n"
+ "\"rebind-timer\": 2000, \n"
+ "\"renew-timer\": 1000, \n"
+ "\"mac-sources\": [ \"ipv6-link-local\" ], \n"
+ "\"subnet6\": [ \n"
+ " { \n"
+ " \"subnet\": \"2001:db8:1::/48\", \n"
+ " \"interface\" : \"eth0\", \n"
+ " \"reservation-mode\": \"global\", \n"
+ " \"pd-pools\": [ \n"
+ " { \n"
+ " \"prefix\": \"3000::\", \n"
+ " \"prefix-len\": 119, \n"
+ " \"delegated-len\": 120 \n"
+ " }] \n"
+ " },"
+ " { \n"
+ " \"subnet\": \"2001:db8:2::/48\", \n"
+ " \"interface\" : \"eth1\", \n"
+ " \"pd-pools\": [ \n"
+ " { \n"
+ " \"prefix\": \"3001::\", \n"
+ " \"prefix-len\": 119, \n"
+ " \"delegated-len\": 120 \n"
+ " }], \n"
+ " \"reservations\": [ \n"
+ " { \n"
+ " \"duid\": \"01:02:03:05\", \n"
+ " \"hostname\": \"subnet-duid-host\" \n"
+ " }] \n"
+ " }"
+ " ] \n"
+ "} \n"
};
/// @brief Base class representing leases and hints conveyed within IAs.
@@ -701,6 +799,14 @@ public:
const Reservation& r5 = Reservation::UNSPEC(),
const Reservation& r6 = Reservation::UNSPEC()) const;
+ /// @brief Verifies that an SARR exchange results in the expected lease
+ ///
+ /// @param client Client configured to request a single lease
+ /// @param exp_address expected address/prefix of the lease
+ /// @param exp_hostname expected hostname on the lease
+ void sarrTest(Dhcp6Client& client, const std::string& exp_address,
+ const std::string& exp_hostname);
+
/// @brief Configures client to include hint.
///
/// @param client Reference to a client.
@@ -1063,6 +1169,25 @@ HostTest::requestEmptyIAs(Dhcp6Client& client) {
client.requestPrefix(6);
}
+void
+HostTest::sarrTest(Dhcp6Client& client, const std::string& exp_address,
+ const std::string& exp_hostname) {
+ // Perform 4-way exchange.
+ ASSERT_NO_THROW(client.doSARR());
+
+ // Verify that the client got a dynamic address
+ ASSERT_EQ(1, client.getLeaseNum());
+ Lease6 lease_client = client.getLease(0);
+ EXPECT_EQ(exp_address, lease_client.addr_.toText());
+
+ // Check that the server recorded the lease
+ // and that the server lease has expected hostname.
+ Lease6Ptr lease_server = checkLease(lease_client);
+ ASSERT_TRUE(lease_server);
+ EXPECT_EQ(exp_hostname, lease_server->hostname_);
+}
+
+
// Test basic SARR scenarios against a server configured with one subnet
// containing two reservations. One reservation with a hostname, one
// without a hostname. Scenarios:
@@ -1918,5 +2043,108 @@ TEST_F(HostTest, conflictResolutionReuseExpired) {
EXPECT_FALSE(client2.hasLeaseWithZeroLifetimeForPrefix(IOAddress("3000::"), 120));
}
+// Verifies fundamental Global vs Subnet host reservations for NA leases
+TEST_F(HostTest, globalReservationsNA) {
+ Dhcp6Client client;
+ ASSERT_NO_FATAL_FAILURE(configure(CONFIGS[8], *client.getServer()));
+
+ const Subnet6Collection* subnets = CfgMgr::instance().getCurrentCfg()->
+ getCfgSubnets6()->getAll();
+ ASSERT_EQ(2, subnets->size());
+
+ {
+ SCOPED_TRACE("Global HR by DUID with reserved address");
+ client.setDUID("01:02:03:04");
+ client.requestAddress(1234, IOAddress("::"));
+ // Should get global reserved address and reserved host name
+ ASSERT_NO_FATAL_FAILURE(sarrTest(client, "3001::1", "duid-host-fixed"));
+ }
+
+ {
+ SCOPED_TRACE("Global HR by DUID with dynamic address");
+ client.clearConfig();
+ client.setDUID("01:02:03:05");
+ client.requestAddress(1234, IOAddress("::"));
+ // Should get dynamic address and reserved host name
+ ASSERT_NO_FATAL_FAILURE(sarrTest(client, "2001:db8:1::", "duid-host-dynamic"));
+ }
+
+ {
+ SCOPED_TRACE("Global HR by HW Address with dynamic address");
+ client.clearConfig();
+ client.setDUID("33:44:55:66");
+ client.setLinkLocal(IOAddress("fe80::3a60:77ff:fed5:ffee"));
+ client.requestAddress(1234, IOAddress("::"));
+ // Should get dynamic address and hardware host name
+ ASSERT_NO_FATAL_FAILURE(sarrTest(client, "2001:db8:1::1", "hw-host"));
+ }
+
+ {
+ SCOPED_TRACE("Default subnet mode excludes Global HR");
+ client.clearConfig();
+ client.setInterface("eth1");
+ client.setDUID("01:02:03:04");
+ client.requestAddress(1234, IOAddress("::"));
+ // Should get dynamic address and no host name
+ ASSERT_NO_FATAL_FAILURE(sarrTest(client, "2001:db8:2::", ""));
+ }
+
+ {
+ SCOPED_TRACE("Subnet reservation over global");
+ client.clearConfig();
+ client.setInterface("eth1");
+ client.setDUID("01:02:03:05");
+ client.requestAddress(1234, IOAddress("::"));
+ // Should get dynamic address and host name
+ ASSERT_NO_FATAL_FAILURE(sarrTest(client, "2001:db8:2::1", "subnet-duid-host"));
+ }
+}
+
+// Verifies fundamental Global vs Subnet host reservations for PD leases
+TEST_F(HostTest, globalReservationsPD) {
+ Dhcp6Client client;
+ ASSERT_NO_FATAL_FAILURE(configure(CONFIGS[9], *client.getServer()));
+
+ const Subnet6Collection* subnets = CfgMgr::instance().getCurrentCfg()->
+ getCfgSubnets6()->getAll();
+ ASSERT_EQ(2, subnets->size());
+
+ {
+ SCOPED_TRACE("Global HR by DUID with reserved prefix");
+ client.setDUID("01:02:03:04");
+ client.requestPrefix(1);
+ // Should get global reserved prefix and reserved host name
+ ASSERT_NO_FATAL_FAILURE(sarrTest(client, "4000::100", "duid-host-fixed"));
+ }
+
+ {
+ SCOPED_TRACE("Global HR by DUID with dynamic prefix");
+ client.clearConfig();
+ client.setDUID("01:02:03:05");
+ client.requestPrefix(1);
+ // Should get dynamic prefix and reserved host name
+ ASSERT_NO_FATAL_FAILURE(sarrTest(client, "3000::", "duid-host-dynamic"));
+ }
+
+ {
+ SCOPED_TRACE("Default subnet mode excludes Global HR");
+ client.clearConfig();
+ client.setInterface("eth1");
+ client.setDUID("01:02:03:04");
+ client.requestPrefix(1);
+ // Should get dynamic prefix and no host name
+ ASSERT_NO_FATAL_FAILURE(sarrTest(client, "3001::", ""));
+ }
+
+ {
+ SCOPED_TRACE("Subnet reservation over global");
+ client.clearConfig();
+ client.setInterface("eth1");
+ client.setDUID("01:02:03:05");
+ client.requestPrefix(1);
+ // Should get dynamic prefix and subnet reserved host name
+ ASSERT_NO_FATAL_FAILURE(sarrTest(client, "3001::100", "subnet-duid-host"));
+ }
+}
} // end of anonymous namespace
diff --git a/src/lib/dhcpsrv/alloc_engine.cc b/src/lib/dhcpsrv/alloc_engine.cc
index 90b9058de4..95f2e0d025 100644
--- a/src/lib/dhcpsrv/alloc_engine.cc
+++ b/src/lib/dhcpsrv/alloc_engine.cc
@@ -483,14 +483,37 @@ ConstHostPtr
AllocEngine::ClientContext6::currentHost() const {
Subnet6Ptr subnet = host_subnet_ ? host_subnet_ : subnet_;
if (subnet) {
- auto host = hosts_.find(subnet->getID());
+ SubnetID id = (subnet_->getHostReservationMode() == Network::HR_GLOBAL ?
+ SUBNET_ID_GLOBAL : subnet->getID());
+
+ auto host = hosts_.find(id);
if (host != hosts_.cend()) {
return (host->second);
}
}
+
return (ConstHostPtr());
}
+ConstHostPtr
+AllocEngine::ClientContext6::globalHost() const {
+ Subnet6Ptr subnet = host_subnet_ ? host_subnet_ : subnet_;
+ if (subnet && subnet_->getHostReservationMode() == Network::HR_GLOBAL) {
+ auto host = hosts_.find(SUBNET_ID_GLOBAL);
+ if (host != hosts_.cend()) {
+ return (host->second);
+ }
+ }
+
+ return (ConstHostPtr());
+}
+
+bool
+AllocEngine::ClientContext6::hasGlobalReservation(const IPv6Resrv& resv) const {
+ ConstHostPtr ghost = globalHost();
+ return (ghost && ghost->hasReservation(resv));
+}
+
void AllocEngine::findReservation(ClientContext6& ctx) {
ctx.hosts_.clear();
@@ -505,6 +528,17 @@ void AllocEngine::findReservation(ClientContext6& ctx) {
SharedNetwork6Ptr network;
subnet->getSharedNetwork(network);
+ if (subnet->getHostReservationMode() == Network::HR_GLOBAL) {
+ ConstHostPtr ghost = findGlobalReservation(ctx);
+ if (ghost) {
+ ctx.hosts_[SUBNET_ID_GLOBAL] = ghost;
+
+ // @todo In theory, to support global as part of HR_ALL,
+ // we would just keep going, instead of returning.
+ return;
+ }
+ }
+
// If the subnet belongs to a shared network it is usually going to be
// more efficient to make a query for all reservations for a particular
// client rather than a query for each subnet within this shared network.
@@ -567,6 +601,24 @@ void AllocEngine::findReservation(ClientContext6& ctx) {
}
}
+ConstHostPtr
+AllocEngine::findGlobalReservation(ClientContext6& ctx) {
+ ConstHostPtr host;
+ BOOST_FOREACH(const IdentifierPair& id_pair, ctx.host_identifiers_) {
+ // Attempt to find a host using a specified identifier.
+ host = HostMgr::instance().get6(SUBNET_ID_GLOBAL, id_pair.first,
+ &id_pair.second[0], id_pair.second.size());
+
+ // If we found matching global host we're done.
+ if (host) {
+ break;
+ }
+ }
+
+ return (host);
+}
+
+
Lease6Collection
AllocEngine::allocateLeases6(ClientContext6& ctx) {
@@ -1020,7 +1072,6 @@ void
AllocEngine::allocateReservedLeases6(ClientContext6& ctx,
Lease6Collection& existing_leases) {
-
// If there are no reservations or the reservation is v4, there's nothing to do.
if (ctx.hosts_.empty()) {
LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
@@ -1029,6 +1080,11 @@ AllocEngine::allocateReservedLeases6(ClientContext6& ctx,
return;
}
+ if (allocateGlobalReservedLeases6(ctx, existing_leases)) {
+ // global reservation provided the lease, we're done
+ return;
+ }
+
// Let's convert this from Lease::Type to IPv6Reserv::Type
IPv6Resrv::Type type = ctx.currentIA().type_ == Lease::TYPE_NA ?
IPv6Resrv::TYPE_NA : IPv6Resrv::TYPE_PD;
@@ -1039,8 +1095,7 @@ AllocEngine::allocateReservedLeases6(ClientContext6& ctx,
BOOST_FOREACH(const Lease6Ptr& lease, existing_leases) {
if ((lease->valid_lft_ != 0)) {
if ((ctx.hosts_.count(lease->subnet_id_) > 0) &&
- ctx.hosts_[lease->subnet_id_]->hasReservation(IPv6Resrv(type, lease->addr_,
- lease->prefixlen_))) {
+ ctx.hosts_[lease->subnet_id_]->hasReservation(makeIPv6Resrv(*lease))) {
// We found existing lease for a reserved address or prefix.
// We'll simply extend the lifetime of the lease.
LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
@@ -1188,6 +1243,127 @@ AllocEngine::allocateReservedLeases6(ClientContext6& ctx,
}
}
+bool
+AllocEngine::allocateGlobalReservedLeases6(ClientContext6& ctx,
+ Lease6Collection& existing_leases) {
+ // Get the global host
+ ConstHostPtr ghost = ctx.globalHost();
+ if (!ghost) {
+ return (false);
+ }
+
+ // We want to avoid allocating a new lease for an IA if there is already
+ // a valid lease for which client has reservation. So, we first check if
+ // we already have a lease for a reserved address or prefix.
+ BOOST_FOREACH(const Lease6Ptr& lease, existing_leases) {
+ if ((lease->valid_lft_ != 0) &&
+ (ghost->hasReservation(makeIPv6Resrv(*lease)))) {
+ // We found existing lease for a reserved address or prefix.
+ // We'll simply extend the lifetime of the lease.
+ LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
+ ALLOC_ENGINE_V6_ALLOC_HR_LEASE_EXISTS)
+ .arg(ctx.query_->getLabel())
+ .arg(lease->typeToText(lease->type_))
+ .arg(lease->addr_.toText());
+
+ // Besides IP reservations we're also going to return other reserved
+ // parameters, such as hostname. We want to hand out the hostname value
+ // from the same reservation entry as IP addresses. Thus, let's see if
+ // there is any hostname reservation.
+ if (!ghost->getHostname().empty()) {
+ // We have to determine whether the hostname is generated
+ // in response to client's FQDN or not. If yes, we will
+ // need to qualify the hostname. Otherwise, we just use
+ // the hostname as it is specified for the reservation.
+ OptionPtr fqdn = ctx.query_->getOption(D6O_CLIENT_FQDN);
+ ctx.hostname_ = CfgMgr::instance().getD2ClientMgr().
+ qualifyName(ghost->getHostname(), static_cast(fqdn));
+ }
+
+ // If this is a real allocation, we may need to extend the lease
+ // lifetime.
+ if (!ctx.fake_allocation_ && conditionalExtendLifetime(*lease)) {
+ LeaseMgrFactory::instance().updateLease6(lease);
+ }
+
+ return(true);
+ }
+ }
+
+ // There is no lease for a reservation in this IA. So, let's now iterate
+ // over reservations specified and try to allocate one of them for the IA.
+
+ // Let's convert this from Lease::Type to IPv6Reserv::Type
+ IPv6Resrv::Type type = ctx.currentIA().type_ == Lease::TYPE_NA ?
+ IPv6Resrv::TYPE_NA : IPv6Resrv::TYPE_PD;
+
+ const IPv6ResrvRange& reservs = ghost->getIPv6Reservations(type);
+ BOOST_FOREACH(IPv6ResrvTuple type_lease_tuple, reservs) {
+ // We do have a reservation for address or prefix.
+ const IOAddress& addr = type_lease_tuple.second.getPrefix();
+ uint8_t prefix_len = type_lease_tuple.second.getPrefixLen();
+
+ // We have allocated this address/prefix while processing one of the
+ // previous IAs, so let's try another reservation.
+ if (ctx.isAllocated(addr, prefix_len)) {
+ continue;
+ }
+
+ // If there's a lease for this address, let's not create it.
+ // It doesn't matter whether it is for this client or for someone else.
+ if (!LeaseMgrFactory::instance().getLease6(ctx.currentIA().type_, addr)) {
+
+ if (!ghost->getHostname().empty()) {
+ // If there is a hostname reservation here we should stick
+ // to this reservation. By updating the hostname in the
+ // context we make sure that the database is updated with
+ // this new value and the server doesn't need to do it and
+ // its processing performance is not impacted by the hostname
+ // updates.
+
+ // We have to determine whether the hostname is generated
+ // in response to client's FQDN or not. If yes, we will
+ // need to qualify the hostname. Otherwise, we just use
+ // the hostname as it is specified for the reservation.
+ OptionPtr fqdn = ctx.query_->getOption(D6O_CLIENT_FQDN);
+ ctx.hostname_ = CfgMgr::instance().getD2ClientMgr().
+ qualifyName(ghost->getHostname(), static_cast(fqdn));
+ }
+
+ // Ok, let's create a new lease...
+ CalloutHandle::CalloutNextStep callout_status = CalloutHandle::NEXT_STEP_CONTINUE;
+ Lease6Ptr lease = createLease6(ctx, addr, prefix_len, callout_status);
+
+ // ... and add it to the existing leases list.
+ existing_leases.push_back(lease);
+
+ if (ctx.currentIA().type_ == Lease::TYPE_NA) {
+ LOG_INFO(alloc_engine_logger, ALLOC_ENGINE_V6_HR_ADDR_GRANTED)
+ .arg(addr.toText())
+ .arg(ctx.query_->getLabel());
+ } else {
+ LOG_INFO(alloc_engine_logger, ALLOC_ENGINE_V6_HR_PREFIX_GRANTED)
+ .arg(addr.toText())
+ .arg(static_cast(prefix_len))
+ .arg(ctx.query_->getLabel());
+ }
+
+ // We found a lease for this client and this IA. Let's return.
+ // Returning after the first lease was assigned is useful if we
+ // have multiple reservations for the same client. If the client
+ // sends 2 IAs, the first time we call allocateReservedLeases6 will
+ // use the first reservation and return. The second time, we'll
+ // go over the first reservation, but will discover that there's
+ // a lease corresponding to it and will skip it and then pick
+ // the second reservation and turn it into the lease. This approach
+ // would work for any number of reservations.
+ return (true);
+ }
+ }
+
+ return(false);
+}
+
void
AllocEngine::removeNonmatchingReservedLeases6(ClientContext6& ctx,
Lease6Collection& existing_leases) {
@@ -1210,19 +1386,12 @@ AllocEngine::removeNonmatchingReservedLeases6(ClientContext6& ctx,
BOOST_FOREACH(const Lease6Ptr& candidate, copy) {
// If we have reservation we should check if the reservation is for
// the candidate lease. If so, we simply accept the lease.
- if (ctx.hosts_.count(candidate->subnet_id_) > 0) {
- if (candidate->type_ == Lease6::TYPE_NA) {
- if (ctx.hosts_[candidate->subnet_id_]->hasReservation(
- IPv6Resrv(IPv6Resrv::TYPE_NA, candidate->addr_))) {
- continue;
- }
- } else {
- if (ctx.hosts_[candidate->subnet_id_]->hasReservation(
- IPv6Resrv(IPv6Resrv::TYPE_PD,candidate->addr_,
- candidate->prefixlen_))) {
- continue;
- }
- }
+ IPv6Resrv resv = makeIPv6Resrv(*candidate);
+ if ((ctx.hasGlobalReservation(resv)) ||
+ ((ctx.hosts_.count(candidate->subnet_id_) > 0) &&
+ (ctx.hosts_[candidate->subnet_id_]->hasReservation(resv)))) {
+ // We have a subnet reservation
+ continue;
}
// The candidate address doesn't appear to be reserved for us.
@@ -1357,46 +1526,41 @@ AllocEngine::removeNonreservedLeases6(ClientContext6& ctx,
for (Lease6Collection::iterator lease = existing_leases.begin();
lease != existing_leases.end(); ++lease) {
- IPv6Resrv resv(ctx.currentIA().type_ == Lease::TYPE_NA ?
- IPv6Resrv::TYPE_NA : IPv6Resrv::TYPE_PD,
- (*lease)->addr_, (*lease)->prefixlen_);
-
- // If there is no reservation for this subnet.
- if ((ctx.hosts_.count((*lease)->subnet_id_) > 0) &&
- (ctx.hosts_[(*lease)->subnet_id_]->hasReservation(resv))) {
+ // If there is reservation for this keep it.
+ IPv6Resrv resv = makeIPv6Resrv(*(*lease));
+ if (ctx.hasGlobalReservation(resv) ||
+ ((ctx.hosts_.count((*lease)->subnet_id_) > 0) &&
+ (ctx.hosts_[(*lease)->subnet_id_]->hasReservation(resv)))) {
continue;
+ }
- } else {
+ // We have reservations, but not for this lease. Release it.
+ // Remove this lease from LeaseMgr
+ LeaseMgrFactory::instance().deleteLease((*lease)->addr_);
- // We have reservations, but not for this lease. Release it.
+ // Update DNS if required.
+ queueNCR(CHG_REMOVE, *lease);
- // Remove this lease from LeaseMgr
- LeaseMgrFactory::instance().deleteLease((*lease)->addr_);
+ // Need to decrease statistic for assigned addresses.
+ StatsMgr::instance().addValue(
+ StatsMgr::generateName("subnet", (*lease)->subnet_id_,
+ ctx.currentIA().type_ == Lease::TYPE_NA ?
+ "assigned-nas" : "assigned-pds"),
+ static_cast(-1));
- // Update DNS if required.
- queueNCR(CHG_REMOVE, *lease);
+ /// @todo: Probably trigger a hook here
- // Need to decrease statistic for assigned addresses.
- StatsMgr::instance().addValue(
- StatsMgr::generateName("subnet", (*lease)->subnet_id_,
- ctx.currentIA().type_ == Lease::TYPE_NA ?
- "assigned-nas" : "assigned-pds"),
- static_cast(-1));
+ // Add this to the list of removed leases.
+ ctx.currentIA().old_leases_.push_back(*lease);
- /// @todo: Probably trigger a hook here
+ // Set this pointer to NULL. The pointer is still valid. We're just
+ // setting the Lease6Ptr to NULL value. We'll remove all NULL
+ // pointers once the loop is finished.
+ lease->reset();
- // Add this to the list of removed leases.
- ctx.currentIA().old_leases_.push_back(*lease);
-
- // Set this pointer to NULL. The pointer is still valid. We're just
- // setting the Lease6Ptr to NULL value. We'll remove all NULL
- // pointers once the loop is finished.
- lease->reset();
-
- if (--total == 1) {
- // If there's only one lease left, break the loop.
- break;
- }
+ if (--total == 1) {
+ // If there's only one lease left, break the loop.
+ break;
}
}
@@ -1740,10 +1904,11 @@ AllocEngine::extendLease6(ClientContext6& ctx, Lease6Ptr lease) {
}
}
- // Check if the lease still belongs to the subnet and that the use of this subnet
- // is allowed per client classification. If not, remove this lease.
- if (((lease->type_ != Lease::TYPE_PD) && !ctx.subnet_->inRange(lease->addr_)) ||
- !ctx.subnet_->clientSupported(ctx.query_->getClasses())) {
+ // If the lease is not global and it is either out of range (NAs only) or it
+ // is not permitted by subnet client classification, delete it.
+ if (!(ctx.hasGlobalReservation(makeIPv6Resrv(*lease))) &&
+ (((lease->type_ != Lease::TYPE_PD) && !ctx.subnet_->inRange(lease->addr_)) ||
+ !ctx.subnet_->clientSupported(ctx.query_->getClasses()))) {
// Oh dear, the lease is no longer valid. We need to get rid of it.
// Remove this lease from LeaseMgr
diff --git a/src/lib/dhcpsrv/alloc_engine.h b/src/lib/dhcpsrv/alloc_engine.h
index 198e32c9cd..e32e208f93 100644
--- a/src/lib/dhcpsrv/alloc_engine.h
+++ b/src/lib/dhcpsrv/alloc_engine.h
@@ -484,6 +484,23 @@ public:
/// @return Pointer to the host object.
ConstHostPtr currentHost() const;
+ /// @brief Returns global host reservation if there is one
+ ///
+ /// If the current subnet's reservation mode is global and
+ /// there is a global host (i.e. reservation belonging to
+ /// the global subnet), return it. Otherwise return an
+ /// empty pointer.
+ ///
+ /// @return Pointer to the host object.
+ ConstHostPtr globalHost() const;
+
+ /// @brief Determines if a global reservation exists
+ ///
+ /// @return true if there current subnet's reservation mode is
+ /// global and there is global host containing the given
+ /// lease reservation, false otherwise
+ bool hasGlobalReservation(const IPv6Resrv& resv) const;
+
/// @brief Default constructor.
ClientContext6();
@@ -747,6 +764,30 @@ public:
/// @param ctx Client context that contains all necessary information.
static void findReservation(ClientContext6& ctx);
+ /// @brief Attempts to find the host reservation for the client.
+ ///
+ /// This method attempts to find a "global" host reservation matching the
+ /// client identifier. It will return the first global reservation that
+ /// matches per the configured list of host identifiers, or an empty
+ /// pointer if no matches are found.
+ ///
+ /// @param ctx Client context holding various information about the client.
+ /// @return Pointer to the reservation found, or an empty pointer.
+ static ConstHostPtr findGlobalReservation(ClientContext6& ctx);
+
+ /// @brief Creates an IPv6Resrv instance from a Lease6
+ ///
+ /// @param lease Reference to the Lease6
+ /// @return The newly formed IPv6Resrv instance
+ static IPv6Resrv makeIPv6Resrv(const Lease6& lease) {
+ if (lease.type_ == Lease::TYPE_NA) {
+ return (IPv6Resrv(IPv6Resrv::TYPE_NA, lease.addr_,
+ (lease.prefixlen_ ? lease.prefixlen_ : 128)));
+ }
+
+ return (IPv6Resrv(IPv6Resrv::TYPE_PD, lease.addr_, lease.prefixlen_));
+ }
+
private:
/// @brief creates a lease and inserts it in LeaseMgr if necessary
@@ -801,16 +842,36 @@ private:
/// @brief Creates new leases based on reservations.
///
- /// This method allocates new leases, based on host reservation. Existing
- /// leases are specified in existing_leases parameter. A new lease is not created,
- /// if there is a lease for specified address on existing_leases list or there is
- /// a lease used by someone else.
+ /// This method allcoates new leases, based on host reservations.
+ /// Existing leases are specified in the existing_leases parameter.
+ /// It first calls @c allocateGlobalReservedLeases6 to accomodate
+ /// subnets using global reservations. If that method allocates
+ /// addresses, we return, otherwise we continue and check for non-global
+ /// reservations. A new lease is not created, if there is a lease for
+ /// specified address on existing_leases list or there is a lease used by
+ /// someone else.
///
/// @param ctx client context that contains all details (subnet, client-id, etc.)
/// @param existing_leases leases that are already associated with the client
void
allocateReservedLeases6(ClientContext6& ctx, Lease6Collection& existing_leases);
+ /// @brief Creates new leases based on global reservations.
+ ///
+ /// This method is used by @allocateReservedLeases6, to allocate new leases based
+ /// on global reservation if one exists and global reservations are enabled for
+ /// the selected subnet. It differs from it's caller by looking only at the global
+ /// reservation and therefore has no need to iterate over the selected subnet or it's
+ /// siblings looking for host reservations. Like it's caller, existing leases are
+ /// specified in existing_leases parameter. A new lease is not created, if there is
+ /// a lease for specified address on existing_leases list or there is a lease used by
+ /// someone else.
+ ///
+ /// @param ctx client context that contains all details (subnet, client-id, etc.)
+ /// @param existing_leases leases that are already associated with the client
+ bool
+ allocateGlobalReservedLeases6(ClientContext6& ctx, Lease6Collection& existing_leases);
+
/// @brief Removes leases that are reserved for someone else.
///
/// Goes through the list specified in existing_leases and removes those that
diff --git a/src/lib/dhcpsrv/tests/alloc_engine6_unittest.cc b/src/lib/dhcpsrv/tests/alloc_engine6_unittest.cc
index 8394fd79be..da90675c33 100644
--- a/src/lib/dhcpsrv/tests/alloc_engine6_unittest.cc
+++ b/src/lib/dhcpsrv/tests/alloc_engine6_unittest.cc
@@ -753,7 +753,7 @@ TEST_F(AllocEngine6Test, smallPool6) {
// Create a subnet with a pool that has one address.
initSubnet(IOAddress("2001:db8:1::"), addr, addr);
-
+
// Initialize FQDN for a lease.
initFqdn("myhost.example.com", true, true);
@@ -2717,6 +2717,197 @@ TEST_F(SharedNetworkAlloc6Test, requestRunningOut) {
EXPECT_TRUE(leases.empty());
}
+// Verifies that client with a global hostname reservation can get and
+// renew a dynamic lease from their selected subnet.
+TEST_F(AllocEngine6Test, globalHostDynamicAddress) {
+ boost::scoped_ptr engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
+ ASSERT_TRUE(engine);
+
+ HostPtr host(new Host(&duid_->getDuid()[0], duid_->getDuid().size(),
+ Host::IDENT_DUID, SUBNET_ID_UNUSED, SUBNET_ID_GLOBAL,
+ asiolink::IOAddress("0.0.0.0")));
+ host->setHostname("ghost1");
+
+ CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
+ CfgMgr::instance().commit();
+
+ subnet_->setHostReservationMode(Network::HR_GLOBAL);
+
+ // Create context which will be used to try to allocate leases
+ Pkt6Ptr query(new Pkt6(DHCPV6_REQUEST, 1234));
+ AllocEngine::ClientContext6 ctx(subnet_, duid_, false, false, "", false, query);
+ ctx.currentIA().iaid_ = iaid_;
+
+ // Look up the reservation.
+ findReservation(*engine, ctx);
+ // Make sure we found our host.
+ ConstHostPtr current = ctx.currentHost();
+ ASSERT_TRUE(current);
+ ASSERT_EQ("ghost1", current->getHostname());
+
+ // Check that we have been allocated a dynamic address.
+ Lease6Ptr lease;
+ ASSERT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx)));
+ ASSERT_TRUE(lease);
+ EXPECT_EQ("2001:db8:1::10", lease->addr_.toText());
+
+ // We're going to rollback the clock a little so we can verify a renewal.
+ // We verify the "time" change in case the lease returned to us
+ // by expectOneLease ever becomes a copy and not what is in the lease mgr.
+ --lease->cltt_;
+ Lease6Ptr from_mgr = LeaseMgrFactory::instance().getLease6(lease->type_,
+ lease->addr_);
+ ASSERT_TRUE(from_mgr);
+ EXPECT_EQ(from_mgr->cltt_, lease->cltt_);
+
+ // This is what the client will send in his renew message.
+ AllocEngine::HintContainer hints;
+ hints.push_back(make_pair(IOAddress("2001:db8:1::10"), 128));
+
+ // Set test fixture hostname_ to the expected value. This gets checked in
+ // renewTest.
+ hostname_ = "ghost1";
+
+ // Client should receive a lease.
+ Lease6Collection renewed = renewTest(*engine, pool_, hints, true);
+ ASSERT_EQ(1, renewed.size());
+
+ // And the lease lifetime should be extended.
+ EXPECT_GT(renewed[0]->cltt_, lease->cltt_)
+ << "Lease lifetime was not extended, but it should";
+}
+
+// Verifies that client with a global address reservation can get and
+// renew a lease for an arbitrary address.
+TEST_F(AllocEngine6Test, globalHostReservedAddress) {
+ boost::scoped_ptr engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
+ ASSERT_TRUE(engine);
+
+ HostPtr host(new Host(&duid_->getDuid()[0], duid_->getDuid().size(),
+ Host::IDENT_DUID, SUBNET_ID_UNUSED, SUBNET_ID_GLOBAL,
+ asiolink::IOAddress("0.0.0.0")));
+ host->setHostname("ghost1");
+ IPv6Resrv resv(IPv6Resrv::TYPE_NA, asiolink::IOAddress("3001::1"), 128);
+ host->addReservation(resv);
+
+ CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
+ CfgMgr::instance().commit();
+
+ subnet_->setHostReservationMode(Network::HR_GLOBAL);
+
+ // Create context which will be used to try to allocate leases
+ Pkt6Ptr query(new Pkt6(DHCPV6_REQUEST, 1234));
+ AllocEngine::ClientContext6 ctx(subnet_, duid_, false, false, "", false, query);
+ ctx.currentIA().iaid_ = iaid_;
+
+ // Look up the reservation.
+ findReservation(*engine, ctx);
+ // Make sure we found our host.
+ ConstHostPtr current = ctx.currentHost();
+ ASSERT_TRUE(current);
+ ASSERT_EQ("ghost1", current->getHostname());
+
+ // Check that we have been allocated the fixed address.
+ Lease6Ptr lease;
+ ASSERT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx)));
+ ASSERT_TRUE(lease);
+ EXPECT_EQ("3001::1", lease->addr_.toText());
+
+ // We're going to rollback the clock a little so we can verify a renewal.
+ // We verify the "time" change in case the lease returned to us
+ // by expectOneLease ever becomes a copy and not what is in the lease mgr.
+ --lease->cltt_;
+ Lease6Ptr from_mgr = LeaseMgrFactory::instance().getLease6(lease->type_,
+ lease->addr_);
+ ASSERT_TRUE(from_mgr);
+ EXPECT_EQ(from_mgr->cltt_, lease->cltt_);
+
+ // This is what the client will send in his renew message.
+ AllocEngine::HintContainer hints;
+ hints.push_back(make_pair(IOAddress("3001::1"), 128));
+
+ // Set test fixture hostname_ to the expected value. This gets checked in
+ // renewTest.
+ hostname_ = "ghost1";
+
+ // Client should receive a lease.
+ Lease6Collection renewed = renewTest(*engine, pool_, hints, false);
+ ASSERT_EQ(1, renewed.size());
+
+ // And the lease lifetime should be extended.
+ EXPECT_GT(renewed[0]->cltt_, lease->cltt_)
+ << "Lease lifetime was not extended, but it should";
+}
+
+// Verifies that client with a global prefix reservation can get and
+// renew a lease for an arbitrary prefix.
+TEST_F(AllocEngine6Test, globalHostReservedPrefix) {
+ boost::scoped_ptr engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
+ ASSERT_TRUE(engine);
+
+ HostPtr host(new Host(&duid_->getDuid()[0], duid_->getDuid().size(),
+ Host::IDENT_DUID, SUBNET_ID_UNUSED, SUBNET_ID_GLOBAL,
+ asiolink::IOAddress("0.0.0.0")));
+ host->setHostname("ghost1");
+ IPv6Resrv resv(IPv6Resrv::TYPE_PD, asiolink::IOAddress("3001::"), 64);
+ host->addReservation(resv);
+
+ CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
+ CfgMgr::instance().commit();
+
+ subnet_->setHostReservationMode(Network::HR_GLOBAL);
+
+ // Create context which will be used to try to allocate leases
+ Pkt6Ptr query(new Pkt6(DHCPV6_REQUEST, 1234));
+ AllocEngine::ClientContext6 ctx(subnet_, duid_, false, false, "", false, query);
+ ctx.currentIA().type_ = Lease::TYPE_PD;
+ ctx.currentIA().iaid_ = iaid_;
+
+ // Look up the reservation.
+ findReservation(*engine, ctx);
+ // Make sure we found our host.
+ ConstHostPtr current = ctx.currentHost();
+ ASSERT_TRUE(current);
+ ASSERT_EQ("ghost1", current->getHostname());
+
+ // Check that we have been allocated the fixed address.
+ Lease6Ptr lease;
+ ASSERT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx)));
+ ASSERT_TRUE(lease);
+ EXPECT_EQ("3001::", lease->addr_.toText());
+
+ // We're going to rollback the clock a little so we can verify a renewal.
+ // We verify the "time" change in case the lease returned to us
+ // by expectOneLease ever becomes a copy and not what is in the lease mgr.
+ --lease->cltt_;
+ Lease6Ptr from_mgr = LeaseMgrFactory::instance().getLease6(lease->type_,
+ lease->addr_);
+ ASSERT_TRUE(from_mgr);
+ EXPECT_EQ(from_mgr->cltt_, lease->cltt_);
+
+ // This is what the client will send in his renew message.
+ AllocEngine::HintContainer hints;
+ hints.push_back(make_pair(IOAddress("3001::"), 64));
+
+ // Set test fixture hostname_ to the expected value. This gets checked via
+ // renewTest.
+ hostname_ = "ghost1";
+
+ // We need a PD pool to fake renew_test
+ Pool6Ptr dummy_pool(new Pool6(Lease::TYPE_PD, IOAddress("2001:db8::"), 64, 64));
+
+ // Client should receive a lease.
+ Lease6Collection renewed = renewTest(*engine, dummy_pool, hints, false);
+ ASSERT_EQ(1, renewed.size());
+
+ // And the lease lifetime should be extended.
+ EXPECT_GT(renewed[0]->cltt_, lease->cltt_)
+ << "Lease lifetime was not extended, but it should";
+}
+
}; // namespace test
}; // namespace dhcp
}; // namespace isc
diff --git a/src/lib/dhcpsrv/tests/alloc_engine_utils.cc b/src/lib/dhcpsrv/tests/alloc_engine_utils.cc
index 8fa12fa838..67e389c00c 100644
--- a/src/lib/dhcpsrv/tests/alloc_engine_utils.cc
+++ b/src/lib/dhcpsrv/tests/alloc_engine_utils.cc
@@ -44,7 +44,7 @@ namespace test {
bool testStatistics(const std::string& stat_name, const int64_t exp_value,
const SubnetID subnet_id) {
try {
- std::string name = (!subnet_id ? stat_name :
+ std::string name = (subnet_id == SUBNET_ID_UNUSED ? stat_name :
StatsMgr::generateName("subnet", subnet_id, stat_name));
ObservationPtr observation = StatsMgr::instance().getObservation(name);
if (observation) {
diff --git a/src/lib/dhcpsrv/tests/alloc_engine_utils.h b/src/lib/dhcpsrv/tests/alloc_engine_utils.h
index 1d83d505cd..9a0e9b5b66 100644
--- a/src/lib/dhcpsrv/tests/alloc_engine_utils.h
+++ b/src/lib/dhcpsrv/tests/alloc_engine_utils.h
@@ -44,7 +44,7 @@ namespace test {
/// @return true if the statistic manager holds a particular value,
/// false otherwise.
bool testStatistics(const std::string& stat_name, const int64_t exp_value,
- const SubnetID subnet_id = 0);
+ const SubnetID subnet_id = SUBNET_ID_UNUSED);
/// @brief Allocation engine with some internal methods exposed
class NakedAllocEngine : public AllocEngine {
@@ -178,9 +178,11 @@ public:
EXPECT_EQ(lease->subnet_id_, subnet_->getID());
if (expected_in_subnet) {
- EXPECT_TRUE(subnet_->inRange(lease->addr_));
+ EXPECT_TRUE(subnet_->inRange(lease->addr_))
+ << " address: " << lease->addr_.toText();
} else {
- EXPECT_FALSE(subnet_->inRange(lease->addr_));
+ EXPECT_FALSE(subnet_->inRange(lease->addr_))
+ << " address: " << lease->addr_.toText();
}
if (expected_in_pool) {
@@ -374,7 +376,7 @@ public:
createHost6(bool add_to_host_mgr, IPv6Resrv::Type type,
const asiolink::IOAddress& addr, uint8_t prefix_len) {
HostPtr host(new Host(&duid_->getDuid()[0], duid_->getDuid().size(),
- Host::IDENT_DUID, SubnetID(0), subnet_->getID(),
+ Host::IDENT_DUID, SUBNET_ID_UNUSED, subnet_->getID(),
asiolink::IOAddress("0.0.0.0")));
IPv6Resrv resv(type, addr, prefix_len);
host->addReservation(resv);