From: Marcin Siodelski Date: Tue, 12 Sep 2017 18:03:01 +0000 (+0200) Subject: [5307] Updated allocation engine to facilitate shared networks for v6. X-Git-Tag: trac5363_base~11^2~8 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=85b08a552f2736ce719e886d92b95b2a2e49c793;p=thirdparty%2Fkea.git [5307] Updated allocation engine to facilitate shared networks for v6. --- diff --git a/src/lib/dhcpsrv/alloc_engine.cc b/src/lib/dhcpsrv/alloc_engine.cc index 74e9e9fc87..248c077976 100644 --- a/src/lib/dhcpsrv/alloc_engine.cc +++ b/src/lib/dhcpsrv/alloc_engine.cc @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -308,7 +309,8 @@ AllocEngine::AllocatorPtr AllocEngine::getAllocator(Lease::Type type) { template 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_; @@ -316,16 +318,20 @@ AllocEngine::findReservationInternal(ContextType& ctx, // 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; + } } } @@ -336,11 +342,51 @@ AllocEngine::findReservationInternal(ContextType& ctx, } } +} // 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), @@ -423,13 +469,21 @@ AllocEngine::allocateLeases6(ClientContext6& ctx) { 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? @@ -447,7 +501,7 @@ AllocEngine::allocateLeases6(ClientContext6& ctx) { // 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) @@ -471,7 +525,7 @@ AllocEngine::allocateLeases6(ClientContext6& ctx) { // 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) @@ -490,7 +544,7 @@ AllocEngine::allocateLeases6(ClientContext6& ctx) { // 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) @@ -596,169 +650,200 @@ AllocEngine::allocateUnreservedLeases6(ClientContext6& ctx) { 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 + (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( - 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( + 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()); @@ -769,7 +854,7 @@ AllocEngine::allocateReservedLeases6(ClientContext6& ctx, 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()); @@ -785,8 +870,9 @@ AllocEngine::allocateReservedLeases6(ClientContext6& ctx, // 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, @@ -808,51 +894,74 @@ AllocEngine::allocateReservedLeases6(ClientContext6& ctx, // 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(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(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_); } } @@ -875,16 +984,16 @@ 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.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; } } @@ -900,7 +1009,7 @@ AllocEngine::removeNonmatchingReservedLeases6(ClientContext6& ctx, // 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; } @@ -929,7 +1038,7 @@ AllocEngine::removeNonmatchingReservedLeases6(ClientContext6& ctx, // 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(-1)); @@ -971,8 +1080,7 @@ AllocEngine::removeNonreservedLeases6(ClientContext6& ctx, 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; } @@ -983,10 +1091,18 @@ AllocEngine::removeNonreservedLeases6(ClientContext6& ctx, // 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 @@ -997,7 +1113,7 @@ AllocEngine::removeNonreservedLeases6(ClientContext6& ctx, // 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(-1)); @@ -1467,9 +1583,9 @@ AllocEngine::updateLeaseData(ClientContext6& ctx, const Lease6Collection& leases // 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(1)); diff --git a/src/lib/dhcpsrv/alloc_engine.h b/src/lib/dhcpsrv/alloc_engine.h index 315e5cd0cd..32dd51c08f 100644 --- a/src/lib/dhcpsrv/alloc_engine.h +++ b/src/lib/dhcpsrv/alloc_engine.h @@ -727,10 +727,13 @@ private: /// @param ctx Reference to a @ref ClientContext6 or @ref ClientContext4. /// @param host_get Pointer to the @ref HostMgr functions to be used /// to retrieve reservation by subnet identifier and host identifier. + /// @param ipv6_only Boolean value indicating if only IPv6 reservations + /// should be retrieved. /// @tparam ContextType Either @ref ClientContext6 or @ref ClientContext4. template static void findReservationInternal(ContextType& ctx, - const HostGetFunc& host_get); + const HostGetFunc& host_get, + const bool ipv6_only = false); /// @brief creates a lease and inserts it in LeaseMgr if necessary /// diff --git a/src/lib/dhcpsrv/tests/alloc_engine6_unittest.cc b/src/lib/dhcpsrv/tests/alloc_engine6_unittest.cc index aefd6d71e8..43e4f5b3f8 100644 --- a/src/lib/dhcpsrv/tests/alloc_engine6_unittest.cc +++ b/src/lib/dhcpsrv/tests/alloc_engine6_unittest.cc @@ -1977,6 +1977,316 @@ TEST_F(AllocEngine6Test, reuseReclaimedExpiredViaRequest) { 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 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(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 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 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 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 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 diff --git a/src/lib/dhcpsrv/tests/alloc_engine_utils.h b/src/lib/dhcpsrv/tests/alloc_engine_utils.h index 7f4554b04e..426189eaaa 100644 --- a/src/lib/dhcpsrv/tests/alloc_engine_utils.h +++ b/src/lib/dhcpsrv/tests/alloc_engine_utils.h @@ -1,6 +1,7 @@ // Copyright (C) 2015-2017 Internet Systems Consortium, Inc. ("ISC") // -// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. #ifndef LIBDHCPSRV_ALLOC_ENGINE_UTILS_H