#include <dhcpsrv/lease_mgr_factory.h>
#include <dhcpsrv/ncr_generator.h>
#include <dhcpsrv/network.h>
+#include <dhcpsrv/shared_network.h>
#include <hooks/callout_handle.h>
#include <hooks/hooks_manager.h>
#include <dhcpsrv/callout_handle_store.h>
template<typename ContextType>
void
AllocEngine::findReservationInternal(ContextType& ctx,
- const AllocEngine::HostGetFunc& host_get) {
+ const AllocEngine::HostGetFunc& host_get,
+ const bool ipv6_only) {
ctx.hosts_.clear();
auto subnet = ctx.subnet_;
// We can only search for the reservation if a subnet has been selected.
while (subnet) {
- // Iterate over configured identifiers in the order of preference
- // and try to use each of them to search for the reservations.
- BOOST_FOREACH(const IdentifierPair& id_pair, ctx.host_identifiers_) {
- // Attempt to find a host using a specified identifier.
- ConstHostPtr host = host_get(subnet->getID(), id_pair.first,
- &id_pair.second[0], id_pair.second.size());
- // If we found matching host for this subnet.
- if (host) {
- ctx.hosts_[subnet->getID()] = host;
- break;
+ // Only makes sense to get reservations if the client has access
+ // to the class.
+ if (subnet->clientSupported(ctx.query_->getClasses())) {
+ // Iterate over configured identifiers in the order of preference
+ // and try to use each of them to search for the reservations.
+ BOOST_FOREACH(const IdentifierPair& id_pair, ctx.host_identifiers_) {
+ // Attempt to find a host using a specified identifier.
+ ConstHostPtr host = host_get(subnet->getID(), id_pair.first,
+ &id_pair.second[0], id_pair.second.size());
+ // If we found matching host for this subnet.
+ if (host && (!ipv6_only || host->hasIPv6Reservation())) {
+ ctx.hosts_[subnet->getID()] = host;
+ break;
+ }
}
}
}
}
+} // end of namespace isc::dhcp
+} // end of namespace isc
+
+namespace {
+
+/// @brief Checks if the specified address belongs to one of the subnets
+/// within a shared network.
+///
+/// @param ctx Client context. Current subnet may be modified by this
+/// function when it belongs to a shared network.
+/// @param lease_type Type of the lease.
+/// @param address IPv6 address or prefix to be checked.
+///
+/// @return true if address belongs to a pool in a selected subnet or in
+/// a pool within any of the subnets belonging to the current shared network.
+bool
+inAllowedPool(AllocEngine::ClientContext6& ctx, const Lease::Type& lease_type,
+ const IOAddress& address) {
+ // If the subnet belongs to a shared network we will be iterating
+ // over the subnets that belong to this shared network.
+ Subnet6Ptr current_subnet = ctx.subnet_;
+ while (current_subnet) {
+
+ if (current_subnet->clientSupported(ctx.query_->getClasses())) {
+ if (current_subnet->inPool(lease_type, address)) {
+ return (true);
+ }
+ }
+
+ current_subnet = current_subnet->getNextSubnet(ctx.subnet_);
+ }
+
+ return (false);
+}
+
+}
+
// ##########################################################################
// # DHCPv6 lease allocation code starts here.
// ##########################################################################
+namespace isc {
+namespace dhcp {
+
AllocEngine::ClientContext6::ClientContext6()
: query_(), fake_allocation_(false), subnet_(), duid_(),
hwaddr_(), host_identifiers_(), hosts_(), fwd_dns_update_(false),
isc_throw(InvalidOperation, "DUID is mandatory for IPv6 lease allocation");
}
- // Check if there are existing leases for that subnet/duid/iaid
- // combination.
- Lease6Collection leases =
- LeaseMgrFactory::instance().getLeases6(ctx.currentIA().type_,
- *ctx.duid_,
- ctx.currentIA().iaid_,
- ctx.subnet_->getID());
+ // Check if there are existing leases for that shared network and
+ // DUID/IAID.
+ Subnet6Ptr subnet = ctx.subnet_;
+ Lease6Collection leases;
+ while (subnet) {
+ Lease6Collection leases_subnet =
+ LeaseMgrFactory::instance().getLeases6(ctx.currentIA().type_,
+ *ctx.duid_,
+ ctx.currentIA().iaid_,
+ subnet->getID());
+ leases.insert(leases.end(), leases_subnet.begin(), leases_subnet.end());
+
+ subnet = subnet->getNextSubnet(ctx.subnet_);
+ }
+
// Now do the checks:
// Case 1. if there are no leases, and there are reservations...
// 1.1. are the reserved addresses are used by someone else?
// assign new leases
// Case 1: There are no leases and there's a reservation for this host.
- if (leases.empty() && ctx.currentHost()) {
+ if (leases.empty() && !ctx.hosts_.empty()) {
LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
ALLOC_ENGINE_V6_ALLOC_NO_LEASES_HR)
// There is at least one lease for this client and there are no reservations.
// We will return these leases for the client, but we may need to update
// FQDN information.
- } else if (!leases.empty() && !ctx.currentHost()) {
+ } else if (!leases.empty() && ctx.hosts_.empty()) {
LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
ALLOC_ENGINE_V6_ALLOC_LEASES_NO_HR)
// assign something new.
// Case 3: There are leases and there are reservations.
- } else if (!leases.empty() && ctx.currentHost()) {
+ } else if (!leases.empty() && !ctx.hosts_.empty()) {
LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
ALLOC_ENGINE_V6_ALLOC_LEASES_HR)
hint = ctx.currentIA().hints_[0].first;
}
- // check if the hint is in pool and is available
- // This is equivalent of subnet->inPool(hint), but returns the pool
- Pool6Ptr pool = boost::dynamic_pointer_cast<
- Pool6>(ctx.subnet_->getPool(ctx.currentIA().type_, hint, false));
+ Subnet6Ptr original_subnet = ctx.subnet_;
+ Subnet6Ptr subnet = ctx.subnet_;
+ SharedNetwork6Ptr network;
+ subnet->getSharedNetwork(network);
- if (pool) {
+ Pool6Ptr pool;
- /// @todo: We support only one hint for now
- Lease6Ptr lease =
- LeaseMgrFactory::instance().getLease6(ctx.currentIA().type_, hint);
- if (!lease) {
+ while (subnet) {
- // In-pool reservations: Check if this address is reserved for someone
- // else. There is no need to check for whom it is reserved, because if
- // it has been reserved for us we would have already allocated a lease.
+ if (!subnet->clientSupported(ctx.query_->getClasses())) {
+ subnet = subnet->getNextSubnet(original_subnet);
+ continue;
+ }
- ConstHostPtr host;
- if (hr_mode != Network::HR_DISABLED) {
- host = HostMgr::instance().get6(ctx.subnet_->getID(), hint);
- }
+ ctx.subnet_ = subnet;
- if (!host) {
- // If the in-pool reservations are disabled, or there is no
- // reservation for a given hint, we're good to go.
+ // check if the hint is in pool and is available
+ // This is equivalent of subnet->inPool(hint), but returns the pool
+ pool = boost::dynamic_pointer_cast<Pool6>
+ (subnet->getPool(ctx.currentIA().type_, hint, false));
- // The hint is valid and not currently used, let's create a
- // lease for it
- lease = createLease6(ctx, hint, pool->getLength());
+ if (pool) {
- // It can happen that the lease allocation failed (we could
- // have lost the race condition. That means that the hint is
- // no longer usable and we need to continue the regular
- // allocation path.
- if (lease) {
+ /// @todo: We support only one hint for now
+ Lease6Ptr lease =
+ LeaseMgrFactory::instance().getLease6(ctx.currentIA().type_, hint);
+ if (!lease) {
- /// @todo: We support only one lease per ia for now
- Lease6Collection collection;
- collection.push_back(lease);
- return (collection);
- }
- } else {
- LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
- ALLOC_ENGINE_V6_HINT_RESERVED)
- .arg(ctx.query_->getLabel())
- .arg(hint.toText());
- }
-
- } else {
-
- // If the lease is expired, we may likely reuse it, but...
- if (lease->expired()) {
+ // In-pool reservations: Check if this address is reserved for someone
+ // else. There is no need to check for whom it is reserved, because if
+ // it has been reserved for us we would have already allocated a lease.
ConstHostPtr host;
if (hr_mode != Network::HR_DISABLED) {
- host = HostMgr::instance().get6(ctx.subnet_->getID(), hint);
+ host = HostMgr::instance().get6(subnet->getID(), hint);
}
- // Let's check if there is a reservation for this address.
if (!host) {
-
- // Copy an existing, expired lease so as it can be returned
- // to the caller.
- Lease6Ptr old_lease(new Lease6(*lease));
- ctx.currentIA().old_leases_.push_back(old_lease);
-
- /// We found a lease and it is expired, so we can reuse it
- lease = reuseExpiredLease(lease, ctx, pool->getLength());
-
- /// @todo: We support only one lease per ia for now
- leases.push_back(lease);
- return (leases);
-
+ // If the in-pool reservations are disabled, or there is no
+ // reservation for a given hint, we're good to go.
+
+ // The hint is valid and not currently used, let's create a
+ // lease for it
+ lease = createLease6(ctx, hint, pool->getLength());
+
+ // It can happen that the lease allocation failed (we could
+ // have lost the race condition. That means that the hint is
+ // no longer usable and we need to continue the regular
+ // allocation path.
+ if (lease) {
+
+ /// @todo: We support only one lease per ia for now
+ Lease6Collection collection;
+ collection.push_back(lease);
+ return (collection);
+ }
} else {
LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
- ALLOC_ENGINE_V6_EXPIRED_HINT_RESERVED)
+ ALLOC_ENGINE_V6_HINT_RESERVED)
.arg(ctx.query_->getLabel())
.arg(hint.toText());
}
+
+ } else {
+
+ // If the lease is expired, we may likely reuse it, but...
+ if (lease->expired()) {
+
+ ConstHostPtr host;
+ if (hr_mode != Network::HR_DISABLED) {
+ host = HostMgr::instance().get6(subnet->getID(), hint);
+ }
+
+ // Let's check if there is a reservation for this address.
+ if (!host) {
+
+ // Copy an existing, expired lease so as it can be returned
+ // to the caller.
+ Lease6Ptr old_lease(new Lease6(*lease));
+ ctx.currentIA().old_leases_.push_back(old_lease);
+
+ /// We found a lease and it is expired, so we can reuse it
+ lease = reuseExpiredLease(lease, ctx, pool->getLength());
+
+ /// @todo: We support only one lease per ia for now
+ leases.push_back(lease);
+ return (leases);
+
+ } else {
+ LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
+ ALLOC_ENGINE_V6_EXPIRED_HINT_RESERVED)
+ .arg(ctx.query_->getLabel())
+ .arg(hint.toText());
+ }
+ }
}
}
+
+ subnet = subnet->getNextSubnet(original_subnet);
}
- // The hint was useless (it was not provided at all, was used by someone else,
- // was out of pool or reserved for someone else). Search the pool until first
- // of the following occurs:
- // - we find a free address
- // - we find an address for which the lease has expired
- // - we exhaust number of tries
- uint64_t max_attempts = (attempts_ > 0 ? attempts_ :
- ctx.subnet_->getPoolCapacity(ctx.currentIA().type_));
- for (uint64_t i = 0; i < max_attempts; ++i)
- {
- IOAddress candidate = allocator->pickAddress(ctx.subnet_, ctx.duid_, hint);
+ uint64_t total_attempts = 0;
+ subnet = original_subnet;
- /// In-pool reservations: Check if this address is reserved for someone
- /// else. There is no need to check for whom it is reserved, because if
- /// it has been reserved for us we would have already allocated a lease.
- if (hr_mode == Network::HR_ALL &&
- HostMgr::instance().get6(ctx.subnet_->getID(), candidate)) {
+ while (subnet) {
- // Don't allocate.
+ if (!subnet->clientSupported(ctx.query_->getClasses())) {
+ subnet = subnet->getNextSubnet(original_subnet);
continue;
}
- // The first step is to find out prefix length. It is 128 for
- // non-PD leases.
- uint8_t prefix_len = 128;
- if (ctx.currentIA().type_ == Lease::TYPE_PD) {
- pool = boost::dynamic_pointer_cast<Pool6>(
- ctx.subnet_->getPool(ctx.currentIA().type_, candidate, false));
- if (pool) {
- prefix_len = pool->getLength();
+ // The hint was useless (it was not provided at all, was used by someone else,
+ // was out of pool or reserved for someone else). Search the pool until first
+ // of the following occurs:
+ // - we find a free address
+ // - we find an address for which the lease has expired
+ // - we exhaust number of tries
+ uint64_t max_attempts = (attempts_ > 0 ? attempts_ :
+ subnet->getPoolCapacity(ctx.currentIA().type_));
+
+ total_attempts += max_attempts;
+
+ for (uint64_t i = 0; i < max_attempts; ++i) {
+ IOAddress candidate = allocator->pickAddress(subnet, ctx.duid_,
+ hint);
+
+ /// In-pool reservations: Check if this address is reserved for someone
+ /// else. There is no need to check for whom it is reserved, because if
+ /// it has been reserved for us we would have already allocated a lease.
+ if (hr_mode == Network::HR_ALL &&
+ HostMgr::instance().get6(subnet->getID(), candidate)) {
+
+ // Don't allocate.
+ continue;
+ }
+
+ // The first step is to find out prefix length. It is 128 for
+ // non-PD leases.
+ uint8_t prefix_len = 128;
+ if (ctx.currentIA().type_ == Lease::TYPE_PD) {
+ pool = boost::dynamic_pointer_cast<Pool6>(
+ subnet->getPool(ctx.currentIA().type_, candidate, false));
+ if (pool) {
+ prefix_len = pool->getLength();
+ }
}
- }
- Lease6Ptr existing = LeaseMgrFactory::instance().getLease6(ctx.currentIA().type_,
+ Lease6Ptr existing = LeaseMgrFactory::instance().getLease6(ctx.currentIA().type_,
candidate);
- if (!existing) {
+ if (!existing) {
- // there's no existing lease for selected candidate, so it is
- // free. Let's allocate it.
-
- Lease6Ptr lease = createLease6(ctx, candidate, prefix_len);
- if (lease) {
- // We are allocating a new lease (not renewing). So, the
- // old lease should be NULL.
- ctx.currentIA().old_leases_.clear();
-
- leases.push_back(lease);
- return (leases);
- } else if (ctx.callout_handle_ &&
- (ctx.callout_handle_->getStatus() !=
- CalloutHandle::NEXT_STEP_CONTINUE)) {
- // Don't retry when the callout status is not continue.
- break;
- }
+ // there's no existing lease for selected candidate, so it is
+ // free. Let's allocate it.
- // Although the address was free just microseconds ago, it may have
- // been taken just now. If the lease insertion fails, we continue
- // allocation attempts.
- } else {
- if (existing->expired()) {
- // Copy an existing, expired lease so as it can be returned
- // to the caller.
- Lease6Ptr old_lease(new Lease6(*existing));
- ctx.currentIA().old_leases_.push_back(old_lease);
-
- existing = reuseExpiredLease(existing,
- ctx,
- prefix_len);
-
- leases.push_back(existing);
- return (leases);
+ Lease6Ptr lease = createLease6(ctx, candidate, prefix_len);
+ if (lease) {
+ // We are allocating a new lease (not renewing). So, the
+ // old lease should be NULL.
+ ctx.currentIA().old_leases_.clear();
+
+ leases.push_back(lease);
+ return (leases);
+ } else if (ctx.callout_handle_ &&
+ (ctx.callout_handle_->getStatus() !=
+ CalloutHandle::NEXT_STEP_CONTINUE)) {
+ // Don't retry when the callout status is not continue.
+ break;
+ }
+
+ // Although the address was free just microseconds ago, it may have
+ // been taken just now. If the lease insertion fails, we continue
+ // allocation attempts.
+ } else {
+ if (existing->expired()) {
+ // Copy an existing, expired lease so as it can be returned
+ // to the caller.
+ Lease6Ptr old_lease(new Lease6(*existing));
+ ctx.currentIA().old_leases_.push_back(old_lease);
+
+ existing = reuseExpiredLease(existing, ctx, prefix_len);
+
+ leases.push_back(existing);
+ return (leases);
+ }
}
}
+
+ subnet = subnet->getNextSubnet(original_subnet);
}
// Unable to allocate an address, return an empty lease.
LOG_WARN(alloc_engine_logger, ALLOC_ENGINE_V6_ALLOC_FAIL)
.arg(ctx.query_->getLabel())
- .arg(max_attempts);
-
-
+ .arg(total_attempts);
// We failed to allocate anything. Let's return empty collection.
return (Lease6Collection());
Lease6Collection& existing_leases) {
// If there are no reservations or the reservation is v4, there's nothing to do.
- if (!ctx.currentHost() || !ctx.currentHost()->hasIPv6Reservation()) {
+ if (ctx.hosts_.empty()) {
LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
ALLOC_ENGINE_V6_ALLOC_NO_V6_HR)
.arg(ctx.query_->getLabel());
// we already have a lease for a reserved address or prefix.
BOOST_FOREACH(const Lease6Ptr& lease, existing_leases) {
if ((lease->valid_lft_ != 0)) {
- if (ctx.currentHost()->hasReservation(IPv6Resrv(type, lease->addr_,
- lease->prefixlen_))) {
+ if ((ctx.hosts_.count(lease->subnet_id_) > 0) &&
+ ctx.hosts_[lease->subnet_id_]->hasReservation(IPv6Resrv(type, lease->addr_,
+ lease->prefixlen_))) {
// 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,
// 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.
- // Get the IPv6 reservations of specified type.
- const IPv6ResrvRange& reservs = ctx.currentHost()->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();
+ Subnet6Ptr subnet = ctx.subnet_;
- // 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)) {
+ while (subnet) {
+
+ SubnetID subnet_id = subnet->getID();
+
+ // No hosts for this subnet or the subnet not supported.
+ if (!subnet->clientSupported(ctx.query_->getClasses()) ||
+ ctx.hosts_.count(subnet_id) == 0) {
+ subnet = subnet->getNextSubnet(ctx.subnet_);
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_,
+ ConstHostPtr host = ctx.hosts_[subnet_id];
+
+ // Get the IPv6 reservations of specified type.
+ const IPv6ResrvRange& reservs = host->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)) {
+ std::cout << "is allocated " << addr << std::endl;
+ 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)) {
- // Ok, let's create a new lease...
- Lease6Ptr lease = createLease6(ctx, addr, prefix_len);
- // ... and add it to the existing leases list.
- existing_leases.push_back(lease);
+ // Ok, let's create a new lease...
+ ctx.subnet_ = subnet;
- 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<int>(prefix_len))
- .arg(ctx.query_->getLabel());
+ Lease6Ptr lease = createLease6(ctx, addr, prefix_len);
+
+ // ... 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<int>(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;
}
- // 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;
}
+
+ subnet = subnet->getNextSubnet(ctx.subnet_);
}
}
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.currentHost()) {
+ if (ctx.hosts_.count(candidate->subnet_id_) > 0) {
if (candidate->type_ == Lease6::TYPE_NA) {
- if (ctx.currentHost()->hasReservation(IPv6Resrv(IPv6Resrv::TYPE_NA,
- candidate->addr_))) {
+ if (ctx.hosts_[candidate->subnet_id_]->hasReservation(
+ IPv6Resrv(IPv6Resrv::TYPE_NA, candidate->addr_))) {
continue;
}
} else {
- if (ctx.currentHost()->hasReservation(IPv6Resrv(IPv6Resrv::TYPE_PD,
- candidate->addr_,
- candidate->prefixlen_))) {
+ if (ctx.hosts_[candidate->subnet_id_]->hasReservation(
+ IPv6Resrv(IPv6Resrv::TYPE_PD,candidate->addr_,
+ candidate->prefixlen_))) {
continue;
}
}
// be allocated to us from a dynamic pool, but we must check if
// this lease belongs to any pool. If it does, we can proceed to
// checking the next lease.
- if (!host && ctx.subnet_->inPool(candidate->type_, candidate->addr_)) {
+ if (!host && inAllowedPool(ctx, candidate->type_, candidate->addr_)) {
continue;
}
// Need to decrease statistic for assigned addresses.
StatsMgr::instance().addValue(
- StatsMgr::generateName("subnet", ctx.subnet_->getID(),
+ StatsMgr::generateName("subnet", candidate->subnet_id_,
ctx.currentIA().type_ == Lease::TYPE_NA ?
"assigned-nas" : "assigned-pds"),
static_cast<int64_t>(-1));
Lease6Collection& existing_leases) {
// This method removes leases that are not reserved for this host.
// It will keep at least one lease, though.
- if (existing_leases.empty() || !ctx.currentHost() ||
- !ctx.currentHost()->hasIPv6Reservation()) {
+ if (existing_leases.empty()) {
return;
}
// leases for deletion, by setting appropriate pointers to NULL.
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 (!ctx.currentHost()->hasReservation(resv)) {
+
+ // If there is no reservation for this subnet.
+ if ((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
// Need to decrease statistic for assigned addresses.
StatsMgr::instance().addValue(
- StatsMgr::generateName("subnet", ctx.subnet_->getID(),
+ StatsMgr::generateName("subnet", (*lease)->subnet_id_,
ctx.currentIA().type_ == Lease::TYPE_NA ?
"assigned-nas" : "assigned-pds"),
static_cast<int64_t>(-1));
// If the lease is in the current subnet we need to account
// for the re-assignment of The lease.
- if (ctx.subnet_->inPool(ctx.currentIA().type_, lease->addr_)) {
+ if (inAllowedPool(ctx, ctx.currentIA().type_, lease->addr_)) {
StatsMgr::instance().addValue(
- StatsMgr::generateName("subnet", ctx.subnet_->getID(),
+ StatsMgr::generateName("subnet", lease->subnet_id_,
ctx.currentIA().type_ == Lease::TYPE_NA ?
"assigned-nas" : "assigned-pds"),
static_cast<int64_t>(1));
EXPECT_TRUE(testStatistics("reclaimed-leases", 0, subnet_->getID()));
}
+// This test verifies that the client is offerred an address from an
+// alternative subnet within shared network when the address pool is
+// exhausted in the first address pool.
+TEST_F(AllocEngine6Test, solicitSharedNetworkOutOfAddresses) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
+ ASSERT_TRUE(engine);
+
+ Subnet6Ptr subnet1(new Subnet6(IOAddress("2001:db8:1::"), 56, 1, 2, 3, 4));
+ Subnet6Ptr subnet2(new Subnet6(IOAddress("2001:db8:2::"), 56, 1, 2, 3, 4));
+ Pool6Ptr pool1(new Pool6(Lease::TYPE_NA, IOAddress("2001:db8:1::1"),
+ IOAddress("2001:db8:1::1")));
+ Pool6Ptr pool2(new Pool6(Lease::TYPE_NA, IOAddress("2001:db8:2::"),
+ IOAddress("2001:db8:2::FFFF")));
+ subnet1->addPool(pool1);
+ subnet2->addPool(pool2);
+
+ // Both subnets belong to the same network so they can be used
+ // interchangeably.
+ SharedNetwork6Ptr network(new SharedNetwork6("test_network"));
+ network->add(subnet1);
+ network->add(subnet2);
+
+ // Create a lease for a single address in the first address pool. The
+ // pool is now exhausted.
+ DuidPtr other_duid(new DUID(vector<uint8_t>(12, 0xff)));
+ const uint32_t other_iaid = 3568;
+ Lease6Ptr lease(new Lease6(Lease::TYPE_NA, IOAddress("2001:db8:1::1"),
+ other_duid, other_iaid, 501, 502, 503, 504,
+ subnet1->getID(),
+ HWAddrPtr(), 0));
+ lease->cltt_ = time(NULL) - 10; // Allocated 10 seconds ago
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+ // Create context which will be used to try to allocate leases from the
+ // shared network. The context points to subnet1, which address space
+ // is exhausted. We expect the allocation engine to find another subnet
+ // within the same shared network and offer an address from there.
+ Pkt6Ptr query(new Pkt6(DHCPV6_SOLICIT, 1234));
+ AllocEngine::ClientContext6 ctx(subnet1, duid_, false, false, "", true,
+ query);
+ ctx.currentIA().iaid_ = iaid_;
+
+ Lease6Ptr lease2;
+ ASSERT_NO_THROW(lease2 = expectOneLease(engine->allocateLeases6(ctx)));
+ ASSERT_TRUE(lease2);
+ ASSERT_TRUE(subnet2->inRange(lease2->addr_));
+
+ // The client having a lease should be offerred this lease, even if
+ // the client starts allocation from the second subnet. The code should
+ // determine that the client has a lease in subnet1 and use this subnet
+ // instead.
+ AllocEngine::ClientContext6 ctx2(subnet2, other_duid, false, false, "",
+ true, query);
+ ctx2.currentIA().iaid_ = other_iaid;
+ ASSERT_NO_THROW(lease2 = expectOneLease(engine->allocateLeases6(ctx2)));
+ ASSERT_TRUE(lease2);
+ ASSERT_EQ("2001:db8:1::1", lease2->addr_.toText());
+
+ // Delete the lease in the first subnet.
+ ASSERT_TRUE(LeaseMgrFactory::instance().deleteLease(lease->addr_));
+
+ // Now, try requesting this address by providing a hint. The engine
+ // should try to honor the hint even though we start from the subnet2.
+ ctx.subnet_ = subnet2;
+ ctx.currentIA().hints_.push_back(make_pair(IOAddress("2001:db8:1::1"), 128));
+ ASSERT_NO_THROW(lease2 = expectOneLease(engine->allocateLeases6(ctx)));
+ ASSERT_TRUE(lease2);
+ ASSERT_TRUE(subnet1->inRange(lease2->addr_));
+}
+
+// This test verifies that the server can offer an address from a
+// different subnet than orginally selected, when the address pool in
+// the first subnet is exhausted.
+TEST_F(AllocEngine6Test, solicitSharedNetworkClassification) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
+ ASSERT_TRUE(engine);
+
+ Subnet6Ptr subnet1(new Subnet6(IOAddress("2001:db8:1::"), 56, 1, 2, 3, 4));
+ Subnet6Ptr subnet2(new Subnet6(IOAddress("2001:db8:2::"), 56, 1, 2, 3, 4));
+ Pool6Ptr pool1(new Pool6(Lease::TYPE_NA, IOAddress("2001:db8:1::1"),
+ IOAddress("2001:db8:1::1")));
+ Pool6Ptr pool2(new Pool6(Lease::TYPE_NA, IOAddress("2001:db8:2::"),
+ IOAddress("2001:db8:2::FFFF")));
+ subnet1->addPool(pool1);
+ subnet2->addPool(pool2);
+
+ // Both subnets belong to the same network so they can be used
+ // interchangeably.
+ SharedNetwork6Ptr network(new SharedNetwork6("test_network"));
+ network->add(subnet1);
+ network->add(subnet2);
+
+ // Try to offer address from subnet1. There is an address available so
+ // it should be offerred.
+ Pkt6Ptr query(new Pkt6(DHCPV6_SOLICIT, 1234));
+ AllocEngine::ClientContext6 ctx(subnet1, duid_, false, false, "", true,
+ query);
+ ctx.currentIA().iaid_ = iaid_;
+
+ Lease6Ptr lease;
+ ASSERT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx)));
+ ASSERT_TRUE(lease);
+ ASSERT_TRUE(subnet1->inRange(lease->addr_));
+
+ // Apply restrictions on the subnet1. This should be only assigned
+ // to clients belonging to cable-modem class.
+ subnet1->allowClientClass("cable-modem");
+
+ // The allocation engine should determine that the subnet1 is not
+ // available for the client not belonging to the cable-modem class.
+ // Instead, it should offer an address from subnet2 that belongs
+ // to the same shared network.
+ AllocEngine::ClientContext6 ctx2(subnet1, duid_, false, false, "", true,
+ query);
+ ctx2.currentIA().iaid_ = iaid_;
+ ctx2.query_ = query;
+ ASSERT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx2)));
+ ASSERT_TRUE(lease);
+ ASSERT_TRUE(subnet2->inRange(lease->addr_));
+
+ AllocEngine::ClientContext6 ctx3(subnet1, duid_, false, false, "", true,
+ query);
+ ctx3.currentIA().iaid_ = iaid_;
+ ctx3.query_ = query;
+
+ // Create host reservation in the first subnet for this client. The
+ // allocation engine should not assign reserved address to the client
+ // because client classification doesn't allow that.
+ subnet_ = subnet1;
+ createHost6(true, IPv6Resrv::TYPE_NA, IOAddress("2001:db8:1::1"), 128);
+ AllocEngine::findReservation(ctx3);
+ ASSERT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx)));
+ ASSERT_TRUE(lease);
+ ASSERT_TRUE(subnet2->inRange(lease->addr_));
+
+ AllocEngine::ClientContext6 ctx4(subnet1, duid_, false, false, "", true,
+ query);
+ ctx4.currentIA().iaid_ = iaid_;
+ ctx4.query_ = query;
+
+ // Assign cable-modem class and try again. This time, we should
+ // offer an address from the subnet1.
+ ctx4.query_->addClass(ClientClass("cable-modem"));
+
+ AllocEngine::findReservation(ctx4);
+ ASSERT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx4)));
+ ASSERT_TRUE(lease);
+ EXPECT_EQ("2001:db8:1::1", lease->addr_.toText());
+}
+
+// This test verifies that the client is offerred a reserved address
+// even if this address belongs to another subnet within the same
+// shared network.
+TEST_F(AllocEngine6Test, solicitSharedNetworkReservations) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
+ ASSERT_TRUE(engine);
+
+ Subnet6Ptr subnet1(new Subnet6(IOAddress("2001:db8:1::"), 56, 1, 2, 3, 4));
+ Subnet6Ptr subnet2(new Subnet6(IOAddress("2001:db8:2::"), 56, 1, 2, 3, 4));
+ Pool6Ptr pool1(new Pool6(Lease::TYPE_NA, IOAddress("2001:db8:1::1"),
+ IOAddress("2001:db8:1::1")));
+ Pool6Ptr pool2(new Pool6(Lease::TYPE_NA, IOAddress("2001:db8:2::"),
+ IOAddress("2001:db8:2::FFFF")));
+ subnet1->addPool(pool1);
+ subnet2->addPool(pool2);
+
+ // Both subnets belong to the same network so they can be used
+ // interchangeably.
+ SharedNetwork6Ptr network(new SharedNetwork6("test_network"));
+ network->add(subnet1);
+ network->add(subnet2);
+
+ // Create reservation for the client in the second subnet.
+ subnet_ = subnet2;
+ createHost6(true, IPv6Resrv::TYPE_NA, IOAddress("2001:db8:2::15"), 128);
+
+ // Start allocation from subnet1. The engine should determine that the
+ // client has reservations in subnet2 and should rather assign reserved
+ // addresses.
+ Pkt6Ptr query(new Pkt6(DHCPV6_SOLICIT, 1234));
+ AllocEngine::ClientContext6 ctx(subnet1, duid_, false, false, "", true,
+ query);
+ ctx.currentIA().iaid_ = iaid_;
+
+ // Find reservations for this subnet/shared network.
+ AllocEngine::findReservation(ctx);
+
+ Lease6Ptr lease;
+ ASSERT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx)));
+ ASSERT_TRUE(lease);
+ ASSERT_EQ("2001:db8:2::15", lease->addr_.toText());
+}
+
+// This test verifies that the client is allocated a reserved address
+// even if this address belongs to another subnet within the same
+// shared network.
+TEST_F(AllocEngine6Test, requestSharedNetworkReservations) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
+ ASSERT_TRUE(engine);
+
+ Subnet6Ptr subnet1(new Subnet6(IOAddress("2001:db8:1::"), 56, 1, 2, 3, 4));
+ Subnet6Ptr subnet2(new Subnet6(IOAddress("2001:db8:2::"), 56, 1, 2, 3, 4));
+ Pool6Ptr pool1(new Pool6(Lease::TYPE_NA, IOAddress("2001:db8:1::1"),
+ IOAddress("2001:db8:1::1")));
+ Pool6Ptr pool2(new Pool6(Lease::TYPE_NA, IOAddress("2001:db8:2::"),
+ IOAddress("2001:db8:2::FFFF")));
+ subnet1->addPool(pool1);
+ subnet2->addPool(pool2);
+
+ // Both subnets belong to the same network so they can be used
+ // interchangeably.
+ SharedNetwork6Ptr network(new SharedNetwork6("test_network"));
+ network->add(subnet1);
+ network->add(subnet2);
+
+ // Create reservation for the client in the second subnet.
+ subnet_ = subnet2;
+ createHost6(true, IPv6Resrv::TYPE_NA, IOAddress("2001:db8:2::15"), 128);
+
+ // Start allocation from subnet1. The engine should determine that the
+ // client has reservations in subnet2 and should rather assign reserved
+ // addresses.
+ Pkt6Ptr query(new Pkt6(DHCPV6_REQUEST, 1234));
+ AllocEngine::ClientContext6 ctx(subnet1, duid_, false, false, "", false,
+ query);
+ ctx.currentIA().iaid_ = iaid_;
+
+ // Find reservations for this subnet/shared network.
+ AllocEngine::findReservation(ctx);
+
+ Lease6Ptr lease;
+ ASSERT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx)));
+ ASSERT_TRUE(lease);
+ ASSERT_EQ("2001:db8:2::15", lease->addr_.toText());
+}
+
+// This test verifies that client is assigned an existing lease from a
+// shared network, regardless of the default subnet. It also verifies that
+// the client is assigned a reserved address from a shared network which
+// replaces existing lease within this shared network.
+TEST_F(AllocEngine6Test, requestSharedNetworkExistingLeases) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
+ ASSERT_TRUE(engine);
+
+ Subnet6Ptr subnet1(new Subnet6(IOAddress("2001:db8:1::"), 56, 1, 2, 3, 4));
+ Subnet6Ptr subnet2(new Subnet6(IOAddress("2001:db8:2::"), 56, 1, 2, 3, 4));
+ Pool6Ptr pool1(new Pool6(Lease::TYPE_NA, IOAddress("2001:db8:1::1"),
+ IOAddress("2001:db8:1::1")));
+ Pool6Ptr pool2(new Pool6(Lease::TYPE_NA, IOAddress("2001:db8:2::"),
+ IOAddress("2001:db8:2::FFFF")));
+ subnet1->addPool(pool1);
+ subnet2->addPool(pool2);
+
+ // Both subnets belong to the same network so they can be used
+ // interchangeably.
+ SharedNetwork6Ptr network(new SharedNetwork6("test_network"));
+ network->add(subnet1);
+ network->add(subnet2);
+
+ // Create a lease in subnet 2 for this client. The lease is in expired
+ // reclaimed state initially to allow for checking whether the lease
+ // gets renewed.
+ Lease6Ptr lease(new Lease6(Lease::TYPE_NA, IOAddress("2001:db8:2::1"),
+ duid_, iaid_, 501, 502, 503, 504,
+ subnet2->getID(), HWAddrPtr(), 128));
+ lease->state_ = Lease::STATE_EXPIRED_RECLAIMED;
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+ // Create context which will be used to try to allocate leases from the
+ // shared network. The context points to subnet 1 initially, but the
+ // allocation engine should determine that there are existing leases
+ // in subnet 2 and renew those.
+ Pkt6Ptr query(new Pkt6(DHCPV6_REQUEST, 1234));
+ AllocEngine::ClientContext6 ctx(subnet1, duid_, false, false, "", false,
+ query);
+ ctx.currentIA().iaid_ = iaid_;
+
+ // Check that we have been allocated the existing lease.
+ Lease6Ptr lease2;
+ ASSERT_NO_THROW(lease2 = expectOneLease(engine->allocateLeases6(ctx)));
+ ASSERT_TRUE(lease2);
+ EXPECT_EQ("2001:db8:2::1", lease2->addr_.toText());
+
+ // Statistics should be bumped when the lease is re-assigned.
+ EXPECT_TRUE(testStatistics("assigned-nas", 1, subnet2->getID()));
+
+ // Another interesting case is when there is a reservation in a different
+ // subnet than the one from which the ease has been assigned.
+ subnet_ = subnet1;
+ createHost6(true, IPv6Resrv::TYPE_NA, IOAddress("2001:db8:1::1"), 128);
+
+ // The reserved lease should take precedence.
+ ctx.subnet_ = subnet1;
+ ctx.currentIA().iaid_ = iaid_;
+ AllocEngine::findReservation(ctx);
+ ASSERT_NO_THROW(lease2 = expectOneLease(engine->allocateLeases6(ctx)));
+ ASSERT_TRUE(lease2);
+ EXPECT_EQ("2001:db8:1::1", lease2->addr_.toText());
+
+ // The previous lease should have been removed.
+ ASSERT_EQ(1, ctx.currentIA().old_leases_.size());
+ EXPECT_EQ("2001:db8:2::1", ctx.currentIA().old_leases_[0]->addr_.toText());
+}
+
+
}; // namespace test
}; // namespace dhcp
}; // namespace isc