Commit a5484c4d authored by Thomas Markwalder's avatar Thomas Markwalder

[master] kea-dhcp6 now supports global host reservations

    Merge branch '13-global-host-reservations-task-3-add-v6-support-for-new-hr_global-mode'
parents 245df6e6 91f8ca94
......@@ -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)
......
......@@ -3103,7 +3103,7 @@ should include options from the isc option space:
</simpara>
</listitem>
</itemizedlist>
<para>This feature is currently implemented for memfile backend.</para>
<para>
......@@ -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.</para>
<para>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
<command>"global"</command>, 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.
</para>
<note>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.
</note>
</section>
<section xml:id="reservation6-conflict">
......@@ -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.
</para>
<note>
<para>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.</para>
</note>
</section>
<section xml:id="reservation6-hostname">
......@@ -3538,10 +3565,10 @@ should include options from the isc option space:
Allowed values are:
<itemizedlist>
<listitem><simpara> <command>all</command> - 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.
<listitem><simpara> <command>all</command> - 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.
</simpara></listitem>
<listitem><simpara> <command>out-of-pool</command> - 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
<command>reservation-mode</command> and misconfiguration may cause problems.
<command>reservation-mode</command> and misconfiguration may cause
problems.
</simpara></listitem>
<listitem><simpara> <command>global</command> - 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 <command>global</command> and
misconfiguration may cause problems.
</simpara></listitem>
<listitem><simpara>
......@@ -3576,9 +3614,44 @@ should include options from the isc option space:
}
]
}
</screen>
</screen>
</para>
<para>
An example configuration using global reservations is shown below:
<screen>
"Dhcp6": {
<userinput>
"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"
}
],
</userinput>
"subnet6": [
{
"subnet": "2001:db8:1::/64",
<userinput>"reservation-mode": "global"</userinput>,
...
},
{
"subnet": "2001:db8:2::/64",
<userinput>"reservation-mode": "global"</userinput>,
...
}
]
}
</screen>
For more details regarding global reservations, see
<xref linkend="global-reservations6"/>.
</para>
<para>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:
</screen>
</para>
</section>
<section id="global-reservations6">
<title>Global reservations in DHCPv6</title>
<para>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.</para>
<para>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.</para>
<para>
To use global host reservations a configuration similar to the following
can be used:
<screen>
"Dhcp6:" {
// This specifies global reservations. They will apply to all subnets that
// have global reservations enabled.
<userinput>
"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"
}
]</userinput>,
"valid-lifetime": 600,
"subnet4": [ {
"subnet": "2001:db8:1::/64",
<userinput>"reservation-mode": "global",</userinput>
"pools": [ { "pool": "2001:db8:1::-2001:db8:1::100" } ]
} ]
}
</screen>
</para>
<para>When using database backends, the global host reservations are
distinguished from regular reservations by using subnet-id value of
zero.</para>
<!-- see CfgHostOperations::createConfig6() in
src/lib/dhcpsrv/cfg_host_operations.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();
}
......
......@@ -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
......@@ -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<bool>(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