Browse Source

[5307] Updated allocation engine to facilitate shared networks for v6.

Marcin Siodelski 7 years ago
parent
commit
85b08a552f

+ 312 - 196
src/lib/dhcpsrv/alloc_engine.cc

@@ -19,6 +19,7 @@
 #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>
@@ -308,7 +309,8 @@ AllocEngine::AllocatorPtr AllocEngine::getAllocator(Lease::Type type) {
 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_;
@@ -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<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());
@@ -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<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_);
     }
 }
 
@@ -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<int64_t>(-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<int64_t>(-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<int64_t>(1));

+ 4 - 1
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<typename ContextType>
     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
     ///

+ 310 - 0
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<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

+ 2 - 1
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