Browse Source

[master] Merge branch 'trac5307'

Marcin Siodelski 7 years ago
parent
commit
110d0c9e40

+ 68 - 7
src/bin/dhcp6/dhcp6_srv.cc

@@ -879,9 +879,18 @@ Dhcpv6Srv::buildCfgOptionList(const Pkt6Ptr& question,
         }
         }
     };
     };
 
 
-    // Next, subnet configured options.
-    if (ctx.subnet_ && !ctx.subnet_->getCfgOption()->empty()) {
-        co_list.push_back(ctx.subnet_->getCfgOption());
+    if (ctx.subnet_) {
+        // Next, subnet configured options.
+        if (!ctx.subnet_->getCfgOption()->empty()) {
+            co_list.push_back(ctx.subnet_->getCfgOption());
+        }
+
+        // Then, shared network specific options.
+        SharedNetwork6Ptr network;
+        ctx.subnet_->getSharedNetwork(network);
+        if (network && !network->getCfgOption()->empty()) {
+            co_list.push_back(network->getCfgOption());
+        }
     }
     }
 
 
     // Each class in the incoming packet
     // Each class in the incoming packet
@@ -903,10 +912,12 @@ Dhcpv6Srv::buildCfgOptionList(const Pkt6Ptr& question,
             // Skip it
             // Skip it
             continue;
             continue;
         }
         }
+
         if (ccdef->getCfgOption()->empty()) {
         if (ccdef->getCfgOption()->empty()) {
             // Skip classes which don't configure options
             // Skip classes which don't configure options
             continue;
             continue;
         }
         }
+
         co_list.push_back(ccdef->getCfgOption());
         co_list.push_back(ccdef->getCfgOption());
     }
     }
 
 
@@ -2432,7 +2443,6 @@ Dhcpv6Srv::processSolicit(const Pkt6Ptr& solicit) {
     // Let's create a simplified client context here.
     // Let's create a simplified client context here.
     AllocEngine::ClientContext6 ctx;
     AllocEngine::ClientContext6 ctx;
     initContext(solicit, ctx);
     initContext(solicit, ctx);
-    setReservedClientClasses(solicit, ctx);
 
 
     Pkt6Ptr response(new Pkt6(DHCPV6_ADVERTISE, solicit->getTransid()));
     Pkt6Ptr response(new Pkt6(DHCPV6_ADVERTISE, solicit->getTransid()));
 
 
@@ -2454,6 +2464,8 @@ Dhcpv6Srv::processSolicit(const Pkt6Ptr& solicit) {
     processClientFqdn(solicit, response, ctx);
     processClientFqdn(solicit, response, ctx);
     assignLeases(solicit, response, ctx);
     assignLeases(solicit, response, ctx);
 
 
+    setReservedClientClasses(solicit, ctx);
+
     copyClientOptions(solicit, response);
     copyClientOptions(solicit, response);
     CfgOptionList co_list;
     CfgOptionList co_list;
     buildCfgOptionList(solicit, ctx, co_list);
     buildCfgOptionList(solicit, ctx, co_list);
@@ -2461,6 +2473,8 @@ Dhcpv6Srv::processSolicit(const Pkt6Ptr& solicit) {
     appendRequestedOptions(solicit, response, co_list);
     appendRequestedOptions(solicit, response, co_list);
     appendRequestedVendorOptions(solicit, response, ctx, co_list);
     appendRequestedVendorOptions(solicit, response, ctx, co_list);
 
 
+    updateReservedFqdn(ctx, response);
+
     // Only generate name change requests if sending a Reply as a result
     // Only generate name change requests if sending a Reply as a result
     // of receiving Rapid Commit option.
     // of receiving Rapid Commit option.
     if (response->getType() == DHCPV6_REPLY) {
     if (response->getType() == DHCPV6_REPLY) {
@@ -2478,13 +2492,14 @@ Dhcpv6Srv::processRequest(const Pkt6Ptr& request) {
     // Let's create a simplified client context here.
     // Let's create a simplified client context here.
     AllocEngine::ClientContext6 ctx;
     AllocEngine::ClientContext6 ctx;
     initContext(request, ctx);
     initContext(request, ctx);
-    setReservedClientClasses(request, ctx);
 
 
     Pkt6Ptr reply(new Pkt6(DHCPV6_REPLY, request->getTransid()));
     Pkt6Ptr reply(new Pkt6(DHCPV6_REPLY, request->getTransid()));
 
 
     processClientFqdn(request, reply, ctx);
     processClientFqdn(request, reply, ctx);
     assignLeases(request, reply, ctx);
     assignLeases(request, reply, ctx);
 
 
+    setReservedClientClasses(request, ctx);
+
     copyClientOptions(request, reply);
     copyClientOptions(request, reply);
     CfgOptionList co_list;
     CfgOptionList co_list;
     buildCfgOptionList(request, ctx, co_list);
     buildCfgOptionList(request, ctx, co_list);
@@ -2492,6 +2507,7 @@ Dhcpv6Srv::processRequest(const Pkt6Ptr& request) {
     appendRequestedOptions(request, reply, co_list);
     appendRequestedOptions(request, reply, co_list);
     appendRequestedVendorOptions(request, reply, ctx, co_list);
     appendRequestedVendorOptions(request, reply, ctx, co_list);
 
 
+    updateReservedFqdn(ctx, reply);
     generateFqdn(reply);
     generateFqdn(reply);
     createNameChangeRequests(reply, ctx);
     createNameChangeRequests(reply, ctx);
 
 
@@ -2506,13 +2522,14 @@ Dhcpv6Srv::processRenew(const Pkt6Ptr& renew) {
     // Let's create a simplified client context here.
     // Let's create a simplified client context here.
     AllocEngine::ClientContext6 ctx;
     AllocEngine::ClientContext6 ctx;
     initContext(renew, ctx);
     initContext(renew, ctx);
-    setReservedClientClasses(renew, ctx);
 
 
     Pkt6Ptr reply(new Pkt6(DHCPV6_REPLY, renew->getTransid()));
     Pkt6Ptr reply(new Pkt6(DHCPV6_REPLY, renew->getTransid()));
 
 
     processClientFqdn(renew, reply, ctx);
     processClientFqdn(renew, reply, ctx);
     extendLeases(renew, reply, ctx);
     extendLeases(renew, reply, ctx);
 
 
+    setReservedClientClasses(renew, ctx);
+
     copyClientOptions(renew, reply);
     copyClientOptions(renew, reply);
     CfgOptionList co_list;
     CfgOptionList co_list;
     buildCfgOptionList(renew, ctx, co_list);
     buildCfgOptionList(renew, ctx, co_list);
@@ -2520,6 +2537,7 @@ Dhcpv6Srv::processRenew(const Pkt6Ptr& renew) {
     appendRequestedOptions(renew, reply, co_list);
     appendRequestedOptions(renew, reply, co_list);
     appendRequestedVendorOptions(renew, reply, ctx, co_list);
     appendRequestedVendorOptions(renew, reply, ctx, co_list);
 
 
+    updateReservedFqdn(ctx, reply);
     generateFqdn(reply);
     generateFqdn(reply);
     createNameChangeRequests(reply, ctx);
     createNameChangeRequests(reply, ctx);
 
 
@@ -2534,13 +2552,14 @@ Dhcpv6Srv::processRebind(const Pkt6Ptr& rebind) {
     // Let's create a simplified client context here.
     // Let's create a simplified client context here.
     AllocEngine::ClientContext6 ctx;
     AllocEngine::ClientContext6 ctx;
     initContext(rebind, ctx);
     initContext(rebind, ctx);
-    setReservedClientClasses(rebind, ctx);
 
 
     Pkt6Ptr reply(new Pkt6(DHCPV6_REPLY, rebind->getTransid()));
     Pkt6Ptr reply(new Pkt6(DHCPV6_REPLY, rebind->getTransid()));
 
 
     processClientFqdn(rebind, reply, ctx);
     processClientFqdn(rebind, reply, ctx);
     extendLeases(rebind, reply, ctx);
     extendLeases(rebind, reply, ctx);
 
 
+    setReservedClientClasses(rebind, ctx);
+
     copyClientOptions(rebind, reply);
     copyClientOptions(rebind, reply);
     CfgOptionList co_list;
     CfgOptionList co_list;
     buildCfgOptionList(rebind, ctx, co_list);
     buildCfgOptionList(rebind, ctx, co_list);
@@ -2548,6 +2567,7 @@ Dhcpv6Srv::processRebind(const Pkt6Ptr& rebind) {
     appendRequestedOptions(rebind, reply, co_list);
     appendRequestedOptions(rebind, reply, co_list);
     appendRequestedVendorOptions(rebind, reply, ctx, co_list);
     appendRequestedVendorOptions(rebind, reply, ctx, co_list);
 
 
+    updateReservedFqdn(ctx, reply);
     generateFqdn(reply);
     generateFqdn(reply);
     createNameChangeRequests(reply, ctx);
     createNameChangeRequests(reply, ctx);
 
 
@@ -3099,6 +3119,44 @@ Dhcpv6Srv::setReservedClientClasses(const Pkt6Ptr& pkt,
 }
 }
 
 
 void
 void
+Dhcpv6Srv::updateReservedFqdn(const AllocEngine::ClientContext6& ctx,
+                              const Pkt6Ptr& answer) {
+    if (!answer) {
+        isc_throw(isc::Unexpected, "an instance of the object encapsulating"
+                  " a message must not be NULL when updating reserved FQDN");
+    }
+
+    Option6ClientFqdnPtr fqdn = boost::dynamic_pointer_cast<Option6ClientFqdn>
+        (answer->getOption(D6O_CLIENT_FQDN));
+
+   // If Client FQDN option is not included, there is nothing to do.
+   if (!fqdn) {
+       return;
+   }
+
+   std::string name = fqdn->getDomainName();
+
+   // If there is a host reservation for this client we have to check whether
+   // this reservation has the same hostname as the hostname currently
+   // present in the FQDN option. If not, it indicates that the allocation
+   // engine picked a different subnet (from within a shared network) for
+   // reservations and we have to send this new value to the client.
+   if (ctx.currentHost() &&
+       !ctx.currentHost()->getHostname().empty()) {
+       std::string new_name = CfgMgr::instance().getD2ClientMgr().
+           qualifyName(ctx.currentHost()->getHostname(), true);
+
+       if (new_name != name) {
+           fqdn->setDomainName(new_name, Option6ClientFqdn::FULL);
+
+           // Replace previous instance of Client FQDN option.
+           answer->delOption(D6O_CLIENT_FQDN);
+           answer->addOption(fqdn);
+       }
+   }
+}
+
+void
 Dhcpv6Srv::generateFqdn(const Pkt6Ptr& answer) {
 Dhcpv6Srv::generateFqdn(const Pkt6Ptr& answer) {
     if (!answer) {
     if (!answer) {
         isc_throw(isc::Unexpected, "an instance of the object encapsulating"
         isc_throw(isc::Unexpected, "an instance of the object encapsulating"
@@ -3162,6 +3220,9 @@ Dhcpv6Srv::generateFqdn(const Pkt6Ptr& answer) {
         // Set the generated FQDN in the Client FQDN option.
         // Set the generated FQDN in the Client FQDN option.
         fqdn->setDomainName(generated_name, Option6ClientFqdn::FULL);
         fqdn->setDomainName(generated_name, Option6ClientFqdn::FULL);
 
 
+       answer->delOption(D6O_CLIENT_FQDN);
+       answer->addOption(fqdn);
+
     } catch (const Exception& ex) {
     } catch (const Exception& ex) {
         LOG_ERROR(ddns6_logger, DHCP6_DDNS_GENERATED_FQDN_UPDATE_FAIL)
         LOG_ERROR(ddns6_logger, DHCP6_DDNS_GENERATED_FQDN_UPDATE_FAIL)
             .arg(answer->getLabel())
             .arg(answer->getLabel())

+ 19 - 0
src/bin/dhcp6/dhcp6_srv.h

@@ -764,6 +764,25 @@ private:
     /// @param classes a reference to added class names for logging
     /// @param classes a reference to added class names for logging
     void classifyByVendor(const Pkt6Ptr& pkt, std::string& classes);
     void classifyByVendor(const Pkt6Ptr& pkt, std::string& classes);
 
 
+    /// @brief Update FQDN based on the reservations in the current subnet.
+    ///
+    /// When shared networks are in use the allocation engine may switch to
+    /// a different subnet than originally selected. If this new subnet has
+    /// hostname reservations there is a need to update the FQDN option
+    /// value.
+    ///
+    /// This method should be called after lease assignments to perform
+    /// such update when required.
+    ///
+    /// @param ctx Client context.
+    /// @param answer Message being sent to a client, which may hold an FQDN
+    /// option to be updated.
+    ///
+    /// @throw isc::Unexpected if specified message is NULL. This is treated
+    /// as a programmatic error.
+    void updateReservedFqdn(const AllocEngine::ClientContext6& ctx,
+                            const Pkt6Ptr& answer);
+
     /// @private
     /// @private
     /// @brief Generate FQDN to be sent to a client if none exists.
     /// @brief Generate FQDN to be sent to a client if none exists.
     ///
     ///

+ 1 - 0
src/bin/dhcp6/tests/Makefile.am

@@ -97,6 +97,7 @@ dhcp6_unittests_SOURCES += classify_unittests.cc
 dhcp6_unittests_SOURCES += parser_unittest.cc
 dhcp6_unittests_SOURCES += parser_unittest.cc
 dhcp6_unittests_SOURCES += simple_parser6_unittest.cc
 dhcp6_unittests_SOURCES += simple_parser6_unittest.cc
 dhcp6_unittests_SOURCES += get_config_unittest.cc get_config_unittest.h
 dhcp6_unittests_SOURCES += get_config_unittest.cc get_config_unittest.h
+dhcp6_unittests_SOURCES += shared_network_unittest.cc
 
 
 nodist_dhcp6_unittests_SOURCES  = marker_file.h test_libraries.h
 nodist_dhcp6_unittests_SOURCES  = marker_file.h test_libraries.h
 
 

+ 21 - 1
src/bin/dhcp6/tests/dhcp6_client.cc

@@ -474,10 +474,11 @@ Dhcp6Client::doRequest() {
     copyIAs(context_.response_, query);
     copyIAs(context_.response_, query);
     appendRequestedIAs(query);
     appendRequestedIAs(query);
 
 
+    context_.query_ = query;
+
     // Add Client FQDN if configured.
     // Add Client FQDN if configured.
     appendFQDN();
     appendFQDN();
 
 
-    context_.query_ = query;
     sendMsg(context_.query_);
     sendMsg(context_.query_);
     context_.response_ = receiveOneMsg();
     context_.response_ = receiveOneMsg();
 
 
@@ -591,6 +592,25 @@ Dhcp6Client::doDecline(const bool include_address) {
 }
 }
 
 
 void
 void
+Dhcp6Client::doRelease() {
+    Pkt6Ptr query = createMsg(DHCPV6_RELEASE);
+    query->addOption(context_.response_->getOption(D6O_SERVERID));
+    copyIAsFromLeases(query);
+
+    context_.query_ = query;
+
+    sendMsg(context_.query_);
+    context_.response_ = receiveOneMsg();
+
+    // Apply configuration only if the server has responded.
+    if (context_.response_) {
+        config_.clear();
+        applyRcvdConfiguration(context_.response_);
+    }
+}
+
+
+void
 Dhcp6Client::generateIAFromLeases(const Pkt6Ptr& query,
 Dhcp6Client::generateIAFromLeases(const Pkt6Ptr& query,
                                   const bool include_address) {
                                   const bool include_address) {
     /// @todo: add support for IAPREFIX here.
     /// @todo: add support for IAPREFIX here.

+ 7 - 1
src/bin/dhcp6/tests/dhcp6_client.h

@@ -305,6 +305,12 @@ public:
     /// way, just stores it.
     /// way, just stores it.
     void doInfRequest();
     void doInfRequest();
 
 
+    /// @brief Sends Release to the server.
+    ///
+    /// This function simulates sending the Release message to the server
+    /// and receiving server's response.
+    void doRelease();
+
     /// @brief Removes the stateful configuration obtained from the server.
     /// @brief Removes the stateful configuration obtained from the server.
     ///
     ///
     /// It removes all leases held by the client.
     /// It removes all leases held by the client.
@@ -503,7 +509,7 @@ public:
     }
     }
 
 
     /// @brief Returns the server that the client is communicating with.
     /// @brief Returns the server that the client is communicating with.
-    boost::shared_ptr<isc::dhcp::test::NakedDhcpv6Srv> getServer() const {
+    boost::shared_ptr<isc::dhcp::test::NakedDhcpv6Srv>& getServer() {
         return (srv_);
         return (srv_);
     }
     }
 
 

File diff suppressed because it is too large
+ 1303 - 0
src/bin/dhcp6/tests/shared_network_unittest.cc


+ 393 - 203
src/lib/dhcpsrv/alloc_engine.cc

@@ -19,6 +19,7 @@
 #include <dhcpsrv/lease_mgr_factory.h>
 #include <dhcpsrv/lease_mgr_factory.h>
 #include <dhcpsrv/ncr_generator.h>
 #include <dhcpsrv/ncr_generator.h>
 #include <dhcpsrv/network.h>
 #include <dhcpsrv/network.h>
+#include <dhcpsrv/shared_network.h>
 #include <hooks/callout_handle.h>
 #include <hooks/callout_handle.h>
 #include <hooks/hooks_manager.h>
 #include <hooks/hooks_manager.h>
 #include <dhcpsrv/callout_handle_store.h>
 #include <dhcpsrv/callout_handle_store.h>
@@ -308,7 +309,8 @@ AllocEngine::AllocatorPtr AllocEngine::getAllocator(Lease::Type type) {
 template<typename ContextType>
 template<typename ContextType>
 void
 void
 AllocEngine::findReservationInternal(ContextType& ctx,
 AllocEngine::findReservationInternal(ContextType& ctx,
-                                     const AllocEngine::HostGetFunc& host_get) {
+                                     const AllocEngine::HostGetFunc& host_get,
+                                     const bool ipv6_only) {
     ctx.hosts_.clear();
     ctx.hosts_.clear();
 
 
     auto subnet = ctx.subnet_;
     auto subnet = ctx.subnet_;
@@ -316,17 +318,28 @@ AllocEngine::findReservationInternal(ContextType& ctx,
     // We can only search for the reservation if a subnet has been selected.
     // We can only search for the reservation if a subnet has been selected.
     while (subnet) {
     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;
+                }
             }
             }
+
+        } else {
+            LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
+                      ALLOC_ENGINE_RESERVATIONS_SKIPPED)
+                .arg(ctx.query_->getLabel())
+                .arg(subnet->toText());
+
         }
         }
 
 
         // We need to get to the next subnet if this is a shared network. If it
         // We need to get to the next subnet if this is a shared network. If it
@@ -336,13 +349,53 @@ 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.
 // #    DHCPv6 lease allocation code starts here.
 // ##########################################################################
 // ##########################################################################
 
 
+namespace isc {
+namespace dhcp {
+
 AllocEngine::ClientContext6::ClientContext6()
 AllocEngine::ClientContext6::ClientContext6()
-    : query_(), fake_allocation_(false), subnet_(), duid_(),
+    : query_(), fake_allocation_(false), subnet_(), host_subnet_(), duid_(),
       hwaddr_(), host_identifiers_(), hosts_(), fwd_dns_update_(false),
       hwaddr_(), host_identifiers_(), hosts_(), fwd_dns_update_(false),
       rev_dns_update_(false), hostname_(), callout_handle_(),
       rev_dns_update_(false), hostname_(), callout_handle_(),
       ias_() {
       ias_() {
@@ -397,8 +450,9 @@ isAllocated(const asiolink::IOAddress& prefix, const uint8_t prefix_len) const {
 
 
 ConstHostPtr
 ConstHostPtr
 AllocEngine::ClientContext6::currentHost() const {
 AllocEngine::ClientContext6::currentHost() const {
-    if (subnet_) {
-        auto host = hosts_.find(subnet_->getID());
+    Subnet6Ptr subnet = host_subnet_ ? host_subnet_ : subnet_;
+    if (subnet) {
+        auto host = hosts_.find(subnet->getID());
         if (host != hosts_.cend()) {
         if (host != hosts_.cend()) {
             return (host->second);
             return (host->second);
         }
         }
@@ -423,13 +477,21 @@ AllocEngine::allocateLeases6(ClientContext6& ctx) {
             isc_throw(InvalidOperation, "DUID is mandatory for IPv6 lease allocation");
             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:
         // Now do the checks:
         // Case 1. if there are no leases, and there are reservations...
         // Case 1. if there are no leases, and there are reservations...
         //   1.1. are the reserved addresses are used by someone else?
         //   1.1. are the reserved addresses are used by someone else?
@@ -447,7 +509,7 @@ AllocEngine::allocateLeases6(ClientContext6& ctx) {
         //       assign new leases
         //       assign new leases
 
 
         // Case 1: There are no leases and there's a reservation for this host.
         // 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,
             LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
                       ALLOC_ENGINE_V6_ALLOC_NO_LEASES_HR)
                       ALLOC_ENGINE_V6_ALLOC_NO_LEASES_HR)
@@ -471,7 +533,7 @@ AllocEngine::allocateLeases6(ClientContext6& ctx) {
         // There is at least one lease for this client and there are no reservations.
         // 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
         // We will return these leases for the client, but we may need to update
         // FQDN information.
         // FQDN information.
-        } else if (!leases.empty() && !ctx.currentHost()) {
+        } else if (!leases.empty() && ctx.hosts_.empty()) {
 
 
             LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
             LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
                       ALLOC_ENGINE_V6_ALLOC_LEASES_NO_HR)
                       ALLOC_ENGINE_V6_ALLOC_LEASES_NO_HR)
@@ -490,7 +552,7 @@ AllocEngine::allocateLeases6(ClientContext6& ctx) {
             // assign something new.
             // assign something new.
 
 
         // Case 3: There are leases and there are reservations.
         // 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,
             LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
                       ALLOC_ENGINE_V6_ALLOC_LEASES_HR)
                       ALLOC_ENGINE_V6_ALLOC_LEASES_HR)
@@ -596,169 +658,201 @@ AllocEngine::allocateUnreservedLeases6(ClientContext6& ctx) {
         hint = ctx.currentIA().hints_[0].first;
         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));
-
-    if (pool) {
-
-        /// @todo: We support only one hint for now
-        Lease6Ptr lease =
-            LeaseMgrFactory::instance().getLease6(ctx.currentIA().type_, hint);
-        if (!lease) {
+    Subnet6Ptr original_subnet = ctx.subnet_;
+    Subnet6Ptr subnet = ctx.subnet_;
+    SharedNetwork6Ptr network;
+    subnet->getSharedNetwork(network);
 
 
-            // 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.
+    Pool6Ptr pool;
 
 
-            ConstHostPtr host;
-            if (hr_mode != Network::HR_DISABLED) {
-                host = HostMgr::instance().get6(ctx.subnet_->getID(), hint);
-            }
+    while (subnet) {
 
 
-            if (!host) {
-                // If the in-pool reservations are disabled, or there is no
-                // reservation for a given hint, we're good to go.
+        if (!subnet->clientSupported(ctx.query_->getClasses())) {
+            subnet = subnet->getNextSubnet(original_subnet);
+            continue;
+        }
 
 
-                // The hint is valid and not currently used, let's create a
-                // lease for it
-                lease = createLease6(ctx, hint, pool->getLength());
+        ctx.subnet_ = subnet;
 
 
-                // 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) {
+        // 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));
 
 
-                    /// @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());
-            }
+        if (pool) {
 
 
-        } else {
+            /// @todo: We support only one hint for now
+            Lease6Ptr lease =
+                LeaseMgrFactory::instance().getLease6(ctx.currentIA().type_, hint);
+            if (!lease) {
 
 
-            // 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;
                 ConstHostPtr host;
                 if (hr_mode != Network::HR_DISABLED) {
                 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) {
                 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 {
                 } else {
                     LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
                     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(ctx.query_->getLabel())
                         .arg(hint.toText());
                         .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;
             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_));
+
+        for (uint64_t i = 0; i < max_attempts; ++i) {
+
+            ++total_attempts;
+
+            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;
             }
             }
-        }
 
 
-        Lease6Ptr existing = LeaseMgrFactory::instance().getLease6(ctx.currentIA().type_,
+            // 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_,
                                                                    candidate);
                                                                    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.
     // Unable to allocate an address, return an empty lease.
     LOG_WARN(alloc_engine_logger, ALLOC_ENGINE_V6_ALLOC_FAIL)
     LOG_WARN(alloc_engine_logger, ALLOC_ENGINE_V6_ALLOC_FAIL)
         .arg(ctx.query_->getLabel())
         .arg(ctx.query_->getLabel())
-        .arg(max_attempts);
-
-
+        .arg(total_attempts);
 
 
     // We failed to allocate anything. Let's return empty collection.
     // We failed to allocate anything. Let's return empty collection.
     return (Lease6Collection());
     return (Lease6Collection());
@@ -768,8 +862,9 @@ void
 AllocEngine::allocateReservedLeases6(ClientContext6& ctx,
 AllocEngine::allocateReservedLeases6(ClientContext6& ctx,
                                      Lease6Collection& existing_leases) {
                                      Lease6Collection& existing_leases) {
 
 
+
     // If there are no reservations or the reservation is v4, there's nothing to do.
     // 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,
         LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
                   ALLOC_ENGINE_V6_ALLOC_NO_V6_HR)
                   ALLOC_ENGINE_V6_ALLOC_NO_V6_HR)
             .arg(ctx.query_->getLabel());
             .arg(ctx.query_->getLabel());
@@ -785,8 +880,9 @@ AllocEngine::allocateReservedLeases6(ClientContext6& ctx,
     // we already have a lease for a reserved address or prefix.
     // we already have a lease for a reserved address or prefix.
     BOOST_FOREACH(const Lease6Ptr& lease, existing_leases) {
     BOOST_FOREACH(const Lease6Ptr& lease, existing_leases) {
         if ((lease->valid_lft_ != 0)) {
         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 found existing lease for a reserved address or prefix.
                 // We'll simply extend the lifetime of the lease.
                 // We'll simply extend the lifetime of the lease.
                 LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
                 LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
@@ -795,6 +891,37 @@ AllocEngine::allocateReservedLeases6(ClientContext6& ctx,
                     .arg(lease->typeToText(lease->type_))
                     .arg(lease->typeToText(lease->type_))
                     .arg(lease->addr_.toText());
                     .arg(lease->addr_.toText());
 
 
+                // Besides IP reservations we're also going to return other reserved
+                // parameters, such as hostname. We want to hand out the hostname value
+                // from the same reservation entry as IP addresses. Thus, let's see if
+                // there is any hostname reservation.
+                if (!ctx.host_subnet_) {
+                    SharedNetwork6Ptr network;
+                    ctx.subnet_->getSharedNetwork(network);
+                    if (network) {
+                        // Remember the subnet that holds this preferred host
+                        // reservation. The server will use it to return appropriate
+                        // FQDN, classes etc.
+                        ctx.host_subnet_ = network->getSubnet(lease->subnet_id_);
+                        ConstHostPtr host = ctx.hosts_[lease->subnet_id_];
+                        // If there is a hostname reservation here we should stick
+                        // to this reservation. By updating the hostname in the
+                        // context we make sure that the database is updated with
+                        // this new value and the server doesn't need to do it and
+                        // its processing performance is not impacted by the hostname
+                        // updates.
+                        if (host && !host->getHostname().empty()) {
+                            // We have to determine whether the hostname is generated
+                            // in response to client's FQDN or not. If yes, we will
+                            // need to qualify the hostname. Otherwise, we just use
+                            // the hostname as it is specified for the reservation.
+                            OptionPtr fqdn = ctx.query_->getOption(D6O_CLIENT_FQDN);
+                            ctx.hostname_ = CfgMgr::instance().getD2ClientMgr().
+                                qualifyName(host->getHostname(), static_cast<bool>(fqdn));
+                        }
+                    }
+                }
+
                 // If this is a real allocation, we may need to extend the lease
                 // If this is a real allocation, we may need to extend the lease
                 // lifetime.
                 // lifetime.
                 if (!ctx.fake_allocation_ && conditionalExtendLifetime(*lease)) {
                 if (!ctx.fake_allocation_ && conditionalExtendLifetime(*lease)) {
@@ -808,51 +935,97 @@ AllocEngine::allocateReservedLeases6(ClientContext6& ctx,
     // There is no lease for a reservation in this IA. So, let's now iterate
     // 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.
     // 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_;
+
+    while (subnet) {
+
+        SubnetID subnet_id = subnet->getID();
 
 
-        // 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)) {
+        // 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;
             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)) {
+                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)) {
                                                    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);
+                // Let's remember the subnet from which the reserved address has been
+                // allocated. We'll use this subnet for allocating other reserved
+                // resources.
+                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());
+                if (!ctx.host_subnet_) {
+                    ctx.host_subnet_ = subnet;
+                    if (!host->getHostname().empty()) {
+                        // If there is a hostname reservation here we should stick
+                        // to this reservation. By updating the hostname in the
+                        // context we make sure that the database is updated with
+                        // this new value and the server doesn't need to do it and
+                        // its processing performance is not impacted by the hostname
+                        // updates.
+
+                        // We have to determine whether the hostname is generated
+                        // in response to client's FQDN or not. If yes, we will
+                        // need to qualify the hostname. Otherwise, we just use
+                        // the hostname as it is specified for the reservation.
+                        OptionPtr fqdn = ctx.query_->getOption(D6O_CLIENT_FQDN);
+                        ctx.hostname_ = CfgMgr::instance().getD2ClientMgr().
+                            qualifyName(host->getHostname(), static_cast<bool>(fqdn));
+                    }
+                }
+
+                // 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);
+
+
+                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 +1048,16 @@ AllocEngine::removeNonmatchingReservedLeases6(ClientContext6& ctx,
     BOOST_FOREACH(const Lease6Ptr& candidate, copy) {
     BOOST_FOREACH(const Lease6Ptr& candidate, copy) {
         // If we have reservation we should check if the reservation is for
         // If we have reservation we should check if the reservation is for
         // the candidate lease. If so, we simply accept the lease.
         // 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 (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;
                     continue;
                 }
                 }
             } else {
             } 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;
                     continue;
                 }
                 }
             }
             }
@@ -900,7 +1073,7 @@ AllocEngine::removeNonmatchingReservedLeases6(ClientContext6& ctx,
         // be allocated to us from a dynamic pool, but we must check if
         // 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
         // this lease belongs to any pool. If it does, we can proceed to
         // checking the next lease.
         // checking the next lease.
-        if (!host && ctx.subnet_->inPool(candidate->type_, candidate->addr_)) {
+        if (!host && inAllowedPool(ctx, candidate->type_, candidate->addr_)) {
             continue;
             continue;
         }
         }
 
 
@@ -929,7 +1102,7 @@ AllocEngine::removeNonmatchingReservedLeases6(ClientContext6& ctx,
 
 
         // Need to decrease statistic for assigned addresses.
         // Need to decrease statistic for assigned addresses.
         StatsMgr::instance().addValue(
         StatsMgr::instance().addValue(
-            StatsMgr::generateName("subnet", ctx.subnet_->getID(),
+            StatsMgr::generateName("subnet", candidate->subnet_id_,
                                    ctx.currentIA().type_ == Lease::TYPE_NA ?
                                    ctx.currentIA().type_ == Lease::TYPE_NA ?
                                    "assigned-nas" : "assigned-pds"),
                                    "assigned-nas" : "assigned-pds"),
             static_cast<int64_t>(-1));
             static_cast<int64_t>(-1));
@@ -971,8 +1144,7 @@ AllocEngine::removeNonreservedLeases6(ClientContext6& ctx,
                                       Lease6Collection& existing_leases) {
                                       Lease6Collection& existing_leases) {
     // This method removes leases that are not reserved for this host.
     // This method removes leases that are not reserved for this host.
     // It will keep at least one lease, though.
     // It will keep at least one lease, though.
-    if (existing_leases.empty() || !ctx.currentHost() ||
-        !ctx.currentHost()->hasIPv6Reservation()) {
+    if (existing_leases.empty()) {
         return;
         return;
     }
     }
 
 
@@ -983,10 +1155,18 @@ AllocEngine::removeNonreservedLeases6(ClientContext6& ctx,
     // leases for deletion, by setting appropriate pointers to NULL.
     // leases for deletion, by setting appropriate pointers to NULL.
     for (Lease6Collection::iterator lease = existing_leases.begin();
     for (Lease6Collection::iterator lease = existing_leases.begin();
          lease != existing_leases.end(); ++lease) {
          lease != existing_leases.end(); ++lease) {
+
         IPv6Resrv resv(ctx.currentIA().type_ == Lease::TYPE_NA ?
         IPv6Resrv resv(ctx.currentIA().type_ == Lease::TYPE_NA ?
                        IPv6Resrv::TYPE_NA : IPv6Resrv::TYPE_PD,
                        IPv6Resrv::TYPE_NA : IPv6Resrv::TYPE_PD,
                        (*lease)->addr_, (*lease)->prefixlen_);
                        (*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.
             // We have reservations, but not for this lease. Release it.
 
 
             // Remove this lease from LeaseMgr
             // Remove this lease from LeaseMgr
@@ -997,7 +1177,7 @@ AllocEngine::removeNonreservedLeases6(ClientContext6& ctx,
 
 
             // Need to decrease statistic for assigned addresses.
             // Need to decrease statistic for assigned addresses.
             StatsMgr::instance().addValue(
             StatsMgr::instance().addValue(
-                StatsMgr::generateName("subnet", ctx.subnet_->getID(),
+                StatsMgr::generateName("subnet", (*lease)->subnet_id_,
                                        ctx.currentIA().type_ == Lease::TYPE_NA ?
                                        ctx.currentIA().type_ == Lease::TYPE_NA ?
                                        "assigned-nas" : "assigned-pds"),
                                        "assigned-nas" : "assigned-pds"),
                 static_cast<int64_t>(-1));
                 static_cast<int64_t>(-1));
@@ -1236,9 +1416,19 @@ AllocEngine::renewLeases6(ClientContext6& ctx) {
         }
         }
 
 
         // Check if there are any leases for this client.
         // Check if there are any leases for this client.
-        Lease6Collection leases = LeaseMgrFactory::instance()
-            .getLeases6(ctx.currentIA().type_, *ctx.duid_,
-                        ctx.currentIA().iaid_, ctx.subnet_->getID());
+        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_);
+        }
+
 
 
         if (!leases.empty()) {
         if (!leases.empty()) {
             LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
             LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
@@ -1250,7 +1440,7 @@ AllocEngine::renewLeases6(ClientContext6& ctx) {
             removeNonmatchingReservedLeases6(ctx, leases);
             removeNonmatchingReservedLeases6(ctx, leases);
         }
         }
 
 
-        if (ctx.currentHost()) {
+        if (!ctx.hosts_.empty()) {
 
 
             LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
             LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
                       ALLOC_ENGINE_V6_RENEW_HR)
                       ALLOC_ENGINE_V6_RENEW_HR)
@@ -1467,9 +1657,9 @@ AllocEngine::updateLeaseData(ClientContext6& ctx, const Lease6Collection& leases
 
 
                 // If the lease is in the current subnet we need to account
                 // If the lease is in the current subnet we need to account
                 // for the re-assignment of The lease.
                 // 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::instance().addValue(
-                        StatsMgr::generateName("subnet", ctx.subnet_->getID(),
+                        StatsMgr::generateName("subnet", lease->subnet_id_,
                                                ctx.currentIA().type_ == Lease::TYPE_NA ?
                                                ctx.currentIA().type_ == Lease::TYPE_NA ?
                                                "assigned-nas" : "assigned-pds"),
                                                "assigned-nas" : "assigned-pds"),
                         static_cast<int64_t>(1));
                         static_cast<int64_t>(1));

+ 10 - 2
src/lib/dhcpsrv/alloc_engine.h

@@ -305,6 +305,11 @@ public:
         /// @brief Subnet selected for the client by the server.
         /// @brief Subnet selected for the client by the server.
         Subnet6Ptr subnet_;
         Subnet6Ptr subnet_;
 
 
+        /// @brief Subnet from which host reservations should be retrieved.
+        ///
+        /// It can be NULL, in which case @c subnet_ value is used.
+        Subnet6Ptr host_subnet_;
+
         /// @brief Client identifier
         /// @brief Client identifier
         DuidPtr duid_;
         DuidPtr duid_;
 
 
@@ -443,7 +448,7 @@ public:
             ias_.push_back(IAContext());
             ias_.push_back(IAContext());
         };
         };
 
 
-        /// @brief Returns host for currently selected subnet.
+        /// @brief Returns host from the most preferred subnet.
         ///
         ///
         /// @return Pointer to the host object.
         /// @return Pointer to the host object.
         ConstHostPtr currentHost() const;
         ConstHostPtr currentHost() const;
@@ -727,10 +732,13 @@ private:
     /// @param ctx Reference to a @ref ClientContext6 or @ref ClientContext4.
     /// @param ctx Reference to a @ref ClientContext6 or @ref ClientContext4.
     /// @param host_get Pointer to the @ref HostMgr functions to be used
     /// @param host_get Pointer to the @ref HostMgr functions to be used
     /// to retrieve reservation by subnet identifier and host identifier.
     /// 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.
     /// @tparam ContextType Either @ref ClientContext6 or @ref ClientContext4.
     template<typename ContextType>
     template<typename ContextType>
     static void findReservationInternal(ContextType& ctx,
     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
     /// @brief creates a lease and inserts it in LeaseMgr if necessary
     ///
     ///

+ 6 - 0
src/lib/dhcpsrv/alloc_engine_messages.mes

@@ -18,6 +18,12 @@ reclaimed has a corresponding DNS entry it needs to be removed.
 This message indicates that removal of the DNS entry has failed.
 This message indicates that removal of the DNS entry has failed.
 Nevertheless the lease will be reclaimed.
 Nevertheless the lease will be reclaimed.
 
 
+% ALLOC_ENGINE_RESERVATIONS_SKIPPED %1 not using reservations in subnet %2 because client's classes do not match
+This debug message is issued when the allocation engine will not use host
+reservations in a given subnet because this subnet is not allowed for
+the given client due to client classification. The first argument includes
+client identification information. The second argument identifies a subnet.
+
 % ALLOC_ENGINE_V4_ALLOC_ERROR %1: error during attempt to allocate an IPv4 address: %2
 % ALLOC_ENGINE_V4_ALLOC_ERROR %1: error during attempt to allocate an IPv4 address: %2
 An error occurred during an attempt to allocate an IPv4 address, the
 An error occurred during an attempt to allocate an IPv4 address, the
 reason for the failure being contained in the message.  The server will
 reason for the failure being contained in the message.  The server will

+ 349 - 0
src/lib/dhcpsrv/tests/alloc_engine6_unittest.cc

@@ -1977,6 +1977,355 @@ TEST_F(AllocEngine6Test, reuseReclaimedExpiredViaRequest) {
     EXPECT_TRUE(testStatistics("reclaimed-leases", 0, subnet_->getID()));
     EXPECT_TRUE(testStatistics("reclaimed-leases", 0, subnet_->getID()));
 }
 }
 
 
+/// @brief This test class is dedicated to testing shared networks
+///
+/// It uses one common configuration:
+/// 1 shared network with 2 subnets:
+///   - 2001:db8:1::/56 subnet with a small pool of single address
+///   - 2001:db8:1::/56 subnet with pool with 64K addresses.
+class SharedNetworkAlloc6Test : public AllocEngine6Test {
+public:
+    SharedNetworkAlloc6Test()
+        :engine_(AllocEngine::ALLOC_ITERATIVE, 0) {
+
+        subnet1_.reset(new Subnet6(IOAddress("2001:db8:1::"), 56, 1, 2, 3, 4));
+        subnet2_.reset(new Subnet6(IOAddress("2001:db8:2::"), 56, 1, 2, 3, 4));
+        pool1_.reset(new Pool6(Lease::TYPE_NA, IOAddress("2001:db8:1::1"),
+                               IOAddress("2001:db8:1::1")));
+        pool2_.reset(new Pool6(Lease::TYPE_NA, IOAddress("2001:db8:2::"),
+                               IOAddress("2001:db8:2::FF")));
+        subnet1_->addPool(pool1_);
+        subnet2_->addPool(pool2_);
+
+        // Both subnets belong to the same network so they can be used
+        // interchangeably.
+        network_.reset(new SharedNetwork6("test_network"));
+        network_->add(subnet1_);
+        network_->add(subnet2_);
+    }
+
+    Lease6Ptr
+    insertLease(std::string addr, SubnetID subnet_id) {
+        Lease6Ptr lease(new Lease6(Lease::TYPE_NA, IOAddress(addr), duid_, iaid_,
+                                   501, 502, 503, 504, subnet_->getID(),
+                                   HWAddrPtr(), 0));
+        lease->cltt_ = time(NULL) - 10; // Allocated 10 seconds ago
+        if (!LeaseMgrFactory::instance().addLease(lease)) {
+            ADD_FAILURE() << "Failed to add a lease for address " << addr
+                          << " in subnet with subnet-id " << subnet_id;
+            return (Lease6Ptr());
+        }
+        return (lease);
+    }
+
+    /// Covenience pointers to configuration elements. These are initialized
+    /// in the constructor and are used throughout the tests.
+    AllocEngine engine_;
+    Subnet6Ptr subnet1_;
+    Subnet6Ptr subnet2_;
+    Pool6Ptr pool1_;
+    Pool6Ptr pool2_;
+    SharedNetwork6Ptr network_;
+};
+
+// This test verifies that the server can offer an address from a
+// subnet and the introduction of shared network doesn't break anything here.
+TEST_F(SharedNetworkAlloc6Test, solicitSharedNetworkSimple) {
+
+    // 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 lease;
+    ASSERT_NO_THROW(lease = expectOneLease(engine_.allocateLeases6(ctx)));
+    ASSERT_TRUE(lease);
+    ASSERT_TRUE(subnet1_->inRange(lease->addr_));
+}
+
+
+// This test verifies that the server can pick a subnet from shared subnets
+// based on hints.
+TEST_F(SharedNetworkAlloc6Test, solicitSharedNetworkHint) {
+
+    // Create context which will be used to try to allocate leases from the
+    // shared network. There's a hint that points to the subnet2. The
+    // shared network mechanism should be able to pick the second subnet
+    // based on it.
+    Pkt6Ptr query(new Pkt6(DHCPV6_SOLICIT, 1234));
+    AllocEngine::ClientContext6 ctx(subnet1_, duid_, false, false, "", true,
+                                    query);
+    ctx.currentIA().iaid_ = iaid_;
+    ctx.currentIA().addHint(IOAddress("2001:db8:2::12"));
+
+    Lease6Ptr lease;
+    ASSERT_NO_THROW(lease = expectOneLease(engine_.allocateLeases6(ctx)));
+    ASSERT_TRUE(lease);
+
+    // The second subnet should be selected.
+    ASSERT_TRUE(subnet2_->inRange(lease->addr_));
+}
+
+// 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(SharedNetworkAlloc6Test, solicitSharedNetworkOutOfAddresses) {
+
+    // 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(SharedNetworkAlloc6Test, solicitSharedNetworkClassification) {
+    // 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(SharedNetworkAlloc6Test, solicitSharedNetworkReservations) {
+    // 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(SharedNetworkAlloc6Test, requestSharedNetworkReservations) {
+    // 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(SharedNetworkAlloc6Test, requestSharedNetworkExistingLeases) {
+    // 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());
+}
+
+// This test verifies that the server can offer an address from a shared
+// subnet if there's at least 1 address left there, but will not offer
+// anything if both subnets are completely full.
+TEST_F(SharedNetworkAlloc6Test, requestRunningOut) {
+
+    // Allocate everything in subnet1
+    insertLease("2001:db8:1::1", subnet1_->getID());
+
+    // Allocate everything, except one address in subnet2.
+    for (int i = 0; i < 255; i++) {
+        stringstream tmp;
+        tmp << "2001:db8:2::" << hex << i;
+        insertLease(tmp.str(), subnet2_->getID());
+    }
+
+    // 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 ctx1(subnet1_, duid_, false, false, "", false,
+                                    query);
+    ctx1.currentIA().iaid_ = iaid_;
+
+    // Check that we have been allocated the existing lease (there's only
+    // one lease left, so we know exactly which one will be given out.
+    Lease6Ptr lease;
+    ASSERT_NO_THROW(lease = expectOneLease(engine_.allocateLeases6(ctx1)));
+    ASSERT_TRUE(lease);
+    EXPECT_EQ("2001:db8:2::ff", lease->addr_.toText());
+
+    // Ok, now try for anoher client. We should be completely full.
+    DuidPtr other_duid(new DUID(vector<uint8_t>(12, 0xff)));
+    AllocEngine::ClientContext6 ctx2(subnet2_, other_duid, false, false, "", false,
+                                    query);
+    Lease6Collection leases = engine_.allocateLeases6(ctx2);
+
+    // Bugger off, we're full!
+    EXPECT_TRUE(leases.empty());
+}
+
 }; // namespace test
 }; // namespace test
 }; // namespace dhcp
 }; // namespace dhcp
 }; // namespace isc
 }; // 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")
 // 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/.
 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 
 #ifndef LIBDHCPSRV_ALLOC_ENGINE_UTILS_H
 #ifndef LIBDHCPSRV_ALLOC_ENGINE_UTILS_H