Browse Source

[master] Merge branch 'trac5306_rebase'

Marcin Siodelski 7 years ago
parent
commit
4f2fca69be

+ 11 - 14
src/bin/dhcp4/dhcp4_messages.mes

@@ -66,12 +66,10 @@ renewing existing lease. The server is explicitly configured to not use
 client identifier to lookup existing leases for the client and will not
 record client identifier in the lease database. This mode of operation
 is useful when clients don't use stable client identifiers, e.g. multi
-stage booting. Note that the client identifier may be used for other
-operations than lease allocation, e.g. identifying host reservations
-for the client using client identifier. The first argument includes the
-client and transaction identification information. The second argument
-specifies the identifier of the subnet where the client is connected
-and for which this mode of operation is configured on the server.
+stage booting. The first argument includes the client and transaction
+identification information. The second argument specifies the identifier
+of the subnet where the client is connected and for which this mode of
+operation is configured on the server.
 
 % DHCP4_CLIENT_FQDN_DATA %1: Client sent FQDN option: %2
 This debug message includes the detailed information extracted from the
@@ -320,14 +318,6 @@ be sent to the client in the DHCPACK message. The first argument contains the
 client and the transaction identification information. The second argument
 contains the allocated IPv4 address.
 
-% DHCP4_NAME_GEN_UPDATE_FAIL %1: failed to update the lease after generating name %2 for a client: %3
-This message indicates the failure when trying to update the lease and/or
-options in the server's response with the hostname generated by the server
-from the acquired IPv4 address. The message argument indicates the reason
-for the failure. The first argument includes the client and the transaction
-identification information. The second argument specifies the hostname.
-The third argument contains the error details.
-
 % DHCP4_NCR_CREATE %1: DDNS updates enabled, therefore sending name change requests
 This debug message is issued when the server is starting to send
 name change requests to the D2 module to update records for the client
@@ -531,6 +521,13 @@ of the named configuration element, or the creation succeeded but the
 parsing actions and committal of changes failed.  The reason for the
 failure is given in the message.
 
+% DHCP4_POST_ALLOCATION_NAME_UPDATE_FAIL %1: failed to update hostname %2 in a lease after address allocation: %3
+This message indicates the failure when trying to update the lease and/or
+options in the server's response with the hostname generated by the server
+or reserved for the client belonging to a shared network. The latter is
+the case when the server dynamically switches to another subnet (than
+initially selected for allocation) from the same shared network.
+
 % DHCP4_QUERY_DATA %1, packet details: %2
 A debug message printing the details of the received packet. The first
 argument includes the client and the transaction identification

+ 122 - 67
src/bin/dhcp4/dhcp4_srv.cc

@@ -29,6 +29,7 @@
 #include <dhcpsrv/lease_mgr.h>
 #include <dhcpsrv/lease_mgr_factory.h>
 #include <dhcpsrv/ncr_generator.h>
+#include <dhcpsrv/shared_network.h>
 #include <dhcpsrv/subnet.h>
 #include <dhcpsrv/subnet_selector.h>
 #include <dhcpsrv/utils.h>
@@ -131,20 +132,13 @@ Dhcpv4Exchange::Dhcpv4Exchange(const AllocEnginePtr& alloc_engine,
     // Pointer to client's query.
     context_->query_ = query;
 
-    // Set client identifier if the match-client-id flag is enabled (default).
-    // If the subnet wasn't found it doesn't matter because we will not be
-    // able to allocate a lease anyway so this context will not be used.
+    // If subnet found, retrieve client identifier which will be needed
+    // for allocations and search for reservations associated with a
+    // subnet/shared network.
     if (subnet) {
-        if (subnet->getMatchClientId()) {
-            OptionPtr opt_clientid = query->getOption(DHO_DHCP_CLIENT_IDENTIFIER);
-            if (opt_clientid) {
-                context_->clientid_.reset(new ClientId(opt_clientid->getData()));
-            }
-        } else {
-            /// @todo When merging with #3806 use different logger.
-            LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL, DHCP4_CLIENTID_IGNORED_FOR_LEASES)
-                .arg(query->getLabel())
-                .arg(subnet->getID());
+        OptionPtr opt_clientid = query->getOption(DHO_DHCP_CLIENT_IDENTIFIER);
+        if (opt_clientid) {
+            context_->clientid_.reset(new ClientId(opt_clientid->getData()));
         }
 
         // Find static reservations if not disabled for our subnet.
@@ -155,9 +149,6 @@ Dhcpv4Exchange::Dhcpv4Exchange(const AllocEnginePtr& alloc_engine,
 
             // Check for static reservations.
             alloc_engine->findReservation(*context_);
-
-            // Assign classes.
-            setReservedClientClasses();
         }
     }
 
@@ -384,9 +375,9 @@ Dhcpv4Exchange::setHostIdentifiers() {
 
 void
 Dhcpv4Exchange::setReservedClientClasses() {
-    if (context_->host_ && query_) {
+    if (context_->currentHost() && query_) {
         BOOST_FOREACH(const std::string& client_class,
-                      context_->host_->getClientClasses4()) {
+                      context_->currentHost()->getClientClasses4()) {
             query_->addClass(client_class);
         }
     }
@@ -394,7 +385,7 @@ Dhcpv4Exchange::setReservedClientClasses() {
 
 void
 Dhcpv4Exchange::setReservedMessageFields() {
-    ConstHostPtr host = context_->host_;
+    ConstHostPtr host = context_->currentHost();
     // Nothing to do if host reservations not specified for this client.
     if (host) {
         if (!host->getNextServer().isV4Zero()) {
@@ -1167,7 +1158,7 @@ Dhcpv4Srv::buildCfgOptionList(Dhcpv4Exchange& ex) {
     }
 
     // Firstly, host specific options.
-    const ConstHostPtr& host = ex.getContext()->host_;
+    const ConstHostPtr& host = ex.getContext()->currentHost();
     if (host && !host->getCfgOption4()->empty()) {
         co_list.push_back(host->getCfgOption4());
     }
@@ -1177,6 +1168,13 @@ Dhcpv4Srv::buildCfgOptionList(Dhcpv4Exchange& ex) {
         co_list.push_back(subnet->getCfgOption());
     }
 
+    // Thirdly, shared network specific options.
+    SharedNetwork4Ptr network;
+    subnet->getSharedNetwork(network);
+    if (network && !network->getCfgOption()->empty()) {
+        co_list.push_back(network->getCfgOption());
+    }
+
     // Each class in the incoming packet
     const ClientClasses& classes = ex.getQuery()->getClasses();
     for (ClientClasses::const_iterator cclass = classes.begin();
@@ -1471,9 +1469,10 @@ Dhcpv4Srv::processClientFqdnOption(Dhcpv4Exchange& ex) {
     fqdn_resp->setFlag(Option4ClientFqdn::FLAG_E,
                        fqdn->getFlag(Option4ClientFqdn::FLAG_E));
 
-    if (ex.getContext()->host_ && !ex.getContext()->host_->getHostname().empty()) {
+    if (ex.getContext()->currentHost() &&
+        !ex.getContext()->currentHost()->getHostname().empty()) {
         D2ClientMgr& d2_mgr = CfgMgr::instance().getD2ClientMgr();
-        fqdn_resp->setDomainName(d2_mgr.qualifyName(ex.getContext()->host_->getHostname(),
+        fqdn_resp->setDomainName(d2_mgr.qualifyName(ex.getContext()->currentHost()->getHostname(),
                                                     true), Option4ClientFqdn::FULL);
 
     } else {
@@ -1519,7 +1518,7 @@ Dhcpv4Srv::processHostnameOption(Dhcpv4Exchange& ex) {
 
     // Hostname reservations take precedence over any other configuration,
     // i.e. DDNS configuration.
-    if (ctx->host_ && !ctx->host_->getHostname().empty()) {
+    if (ctx->currentHost() && !ctx->currentHost()->getHostname().empty()) {
         // In order to send a reserved hostname value we expect that at least
         // one of the following is the case: the client has sent us a hostname
         // option, or the client has sent Parameter Request List option with
@@ -1551,7 +1550,8 @@ Dhcpv4Srv::processHostnameOption(Dhcpv4Exchange& ex) {
         // send back a hostname option, send this option with a reserved
         // name for this client.
         if (should_send_hostname) {
-            const std::string& hostname = d2_mgr.qualifyName(ctx->host_->getHostname(),
+            const std::string& hostname =
+                d2_mgr.qualifyName(ctx->currentHost()->getHostname(),
                                                              false);
             LOG_DEBUG(ddns4_logger, DBG_DHCP4_DETAIL_DATA,
                       DHCP4_RESERVED_HOSTNAME_ASSIGNED)
@@ -1739,12 +1739,23 @@ Dhcpv4Srv::assignLease(Dhcpv4Exchange& ex) {
             .arg(hint.toText());
 
         Lease4Ptr lease;
-        if (client_id) {
-            lease = LeaseMgrFactory::instance().getLease4(*client_id, subnet->getID());
-        }
+        Subnet4Ptr original_subnet = subnet;
+        Subnet4Ptr s = original_subnet;
+        while (s) {
+            if (client_id) {
+                lease = LeaseMgrFactory::instance().getLease4(*client_id, s->getID());
+            }
 
-        if (!lease && hwaddr) {
-            lease = LeaseMgrFactory::instance().getLease4(*hwaddr, subnet->getID());
+            if (!lease && hwaddr) {
+                lease = LeaseMgrFactory::instance().getLease4(*hwaddr, s->getID());
+            }
+
+            if (lease ) {
+                break;
+
+            } else {
+                s = s->getNextSubnet(original_subnet, query->getClasses());
+            }
         }
 
         // Check the first error case: unknown client. We check this before
@@ -1818,6 +1829,10 @@ Dhcpv4Srv::assignLease(Dhcpv4Exchange& ex) {
 
     Lease4Ptr lease = alloc_engine_->allocateLease4(*ctx);
 
+    // Subnet may be modified by the allocation engine, if the initial subnet
+    // belongs to a shared network.
+    subnet = ctx->subnet_;
+
     if (lease) {
         // We have a lease! Let's set it in the packet and send it back to
         // the client.
@@ -1825,6 +1840,16 @@ Dhcpv4Srv::assignLease(Dhcpv4Exchange& ex) {
             .arg(query->getLabel())
             .arg(lease->addr_.toText());
 
+        // We're logging this here, because this is the place where we know
+        // which subnet has been actually used for allocation. If the
+        // client identifier matching is disabled, we want to make sure that
+        // the user is notified.
+        if (!ctx->subnet_->getMatchClientId()) {
+            LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL, DHCP4_CLIENTID_IGNORED_FOR_LEASES)
+                .arg(ctx->query_->getLabel())
+                .arg(ctx->subnet_->getID());
+        }
+
         resp->setYiaddr(lease->addr_);
 
         /// @todo The server should check what ciaddr the client has supplied
@@ -1839,49 +1864,71 @@ Dhcpv4Srv::assignLease(Dhcpv4Exchange& ex) {
             resp->setCiaddr(query->getCiaddr());
         }
 
-        // If there has been Client FQDN or Hostname option sent, but the
-        // hostname is empty, it means that server is responsible for
-        // generating the entire hostname for the client. The example of the
-        // client's name, generated from the IP address is: host-192-0-2-3.
-        if ((fqdn || opt_hostname) && lease->hostname_.empty()) {
+        // We may need to update FQDN or hostname if the server is to generate
+        // new name from the allocated IP address or if the allocation engine
+        // has switched to a different subnet (from the same shared network)
+        // where the client has hostname reservations.
+        if (fqdn || opt_hostname) {
+            bool should_update = false;
+
+            // If there is a reservation in the current subnet for a hostname,
+            // we need to use this reserved name.
+            if (ctx->currentHost() && !ctx->currentHost()->getHostname().empty()) {
+
+                lease->hostname_ = CfgMgr::instance().getD2ClientMgr()
+                    .qualifyName(ctx->currentHost()->getHostname(),
+                                 static_cast<bool>(fqdn));
+                should_update = true;
+
+            // If there has been Client FQDN or Hostname option sent, but the
+            // hostname is empty, it means that server is responsible for
+            // generating the entire hostname for the client. The example of the
+            // client's name, generated from the IP address is: host-192-0-2-3.
+            } else if (lease->hostname_.empty()) {
+
+                // Note that if we have received the hostname option, rather than
+                // Client FQDN the trailing dot is not appended to the generated
+                // hostname because some clients don't handle the trailing dot in
+                // the hostname. Whether the trailing dot is appended or not is
+                // controlled by the second argument to the generateFqdn().
+                lease->hostname_ = CfgMgr::instance().getD2ClientMgr()
+                    .generateFqdn(lease->addr_, static_cast<bool>(fqdn));
+
+                LOG_DEBUG(ddns4_logger, DBG_DHCP4_DETAIL, DHCP4_RESPONSE_HOSTNAME_GENERATE)
+                    .arg(query->getLabel())
+                    .arg(lease->hostname_);
 
-            // Note that if we have received the hostname option, rather than
-            // Client FQDN the trailing dot is not appended to the generated
-            // hostname because some clients don't handle the trailing dot in
-            // the hostname. Whether the trailing dot is appended or not is
-            // controlled by the second argument to the generateFqdn().
-            lease->hostname_ = CfgMgr::instance().getD2ClientMgr()
-                .generateFqdn(lease->addr_, static_cast<bool>(fqdn));
+                should_update = true;
+            }
 
-            LOG_DEBUG(ddns4_logger, DBG_DHCP4_DETAIL, DHCP4_RESPONSE_HOSTNAME_GENERATE)
-                .arg(query->getLabel())
-                .arg(lease->hostname_);
+            if (should_update) {
+
+                // The operations below are rather safe, but we want to catch
+                // any potential exceptions (e.g. invalid lease database backend
+                // implementation) and log an error.
+                try {
+                    if (!fake_allocation) {
+                        // The lease update should be safe, because the lease should
+                        // be already in the database. In most cases the exception
+                        // would be thrown if the lease was missing.
+                        LeaseMgrFactory::instance().updateLease4(lease);
+                    }
 
-            // The operations below are rather safe, but we want to catch
-            // any potential exceptions (e.g. invalid lease database backend
-            // implementation) and log an error.
-            try {
-                if (!fake_allocation) {
-                    // The lease update should be safe, because the lease should
-                    // be already in the database. In most cases the exception
-                    // would be thrown if the lease was missing.
-                    LeaseMgrFactory::instance().updateLease4(lease);
-                }
+                    // The name update in the option should be also safe,
+                    // because the generated name is well formed.
+                    if (fqdn) {
+                        fqdn->setDomainName(lease->hostname_,
+                                            Option4ClientFqdn::FULL);
+                    } else if (opt_hostname) {
+                        opt_hostname->setValue(lease->hostname_);
+                    }
 
-                // The name update in the option should be also safe,
-                // because the generated name is well formed.
-                if (fqdn) {
-                    fqdn->setDomainName(lease->hostname_,
-                                        Option4ClientFqdn::FULL);
-                } else if (opt_hostname) {
-                    opt_hostname->setValue(lease->hostname_);
+                } catch (const Exception& ex) {
+                    LOG_ERROR(ddns4_logger, DHCP4_POST_ALLOCATION_NAME_UPDATE_FAIL)
+                        .arg(query->getLabel())
+                        .arg(lease->hostname_)
+                        .arg(ex.what());
                 }
-
-            } catch (const Exception& ex) {
-                LOG_ERROR(ddns4_logger, DHCP4_NAME_GEN_UPDATE_FAIL)
-                    .arg(query->getLabel())
-                    .arg(lease->hostname_)
-                    .arg(ex.what());
             }
         }
 
@@ -2202,6 +2249,9 @@ Dhcpv4Srv::processDiscover(Pkt4Ptr& discover) {
         return (Pkt4Ptr());
     }
 
+    // Assign reserved classes.
+    ex.setReservedClientClasses();
+
     // Adding any other options makes sense only when we got the lease.
     if (!ex.getResponse()->getYiaddr().isV4Zero()) {
         buildCfgOptionList(ex);
@@ -2254,6 +2304,9 @@ Dhcpv4Srv::processRequest(Pkt4Ptr& request) {
         return (Pkt4Ptr());
     }
 
+    // Assign reserved classes.
+    ex.setReservedClientClasses();
+
     // Adding any other options makes sense only when we got the lease.
     if (!ex.getResponse()->getYiaddr().isV4Zero()) {
         buildCfgOptionList(ex);
@@ -2538,6 +2591,8 @@ Dhcpv4Srv::processInform(Pkt4Ptr& inform) {
 
     Pkt4Ptr ack = ex.getResponse();
 
+    ex.setReservedClientClasses();
+
     buildCfgOptionList(ex);
     appendRequestedOptions(ex);
     appendRequestedVendorOptions(ex);

+ 3 - 3
src/bin/dhcp4/dhcp4_srv.h

@@ -123,6 +123,9 @@ public:
     /// server's response.
     void setReservedMessageFields();
 
+    /// @brief Assigns classes retrieved from host reservation database.
+    void setReservedClientClasses();
+
 private:
 
     /// @brief Copies default parameters from client's to server's message
@@ -155,9 +158,6 @@ private:
     /// host-reservation-identifiers
     void setHostIdentifiers();
 
-    /// @brief Assigns classes retrieved from host reservation database.
-    void setReservedClientClasses();
-
     /// @brief Pointer to the allocation engine used by the server.
     AllocEnginePtr alloc_engine_;
     /// @brief Pointer to the DHCPv4 message sent by the client.

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

@@ -96,6 +96,7 @@ dhcp4_unittests_SOURCES += kea_controller_unittest.cc
 dhcp4_unittests_SOURCES += dhcp4to6_ipc_unittest.cc
 dhcp4_unittests_SOURCES += simple_parser4_unittest.cc
 dhcp4_unittests_SOURCES += get_config_unittest.cc get_config_unittest.h
+dhcp4_unittests_SOURCES += shared_network_unittest.cc
 
 nodist_dhcp4_unittests_SOURCES = marker_file.h test_libraries.h
 

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


+ 8 - 8
src/bin/dhcp6/dhcp6_srv.cc

@@ -861,8 +861,8 @@ Dhcpv6Srv::buildCfgOptionList(const Pkt6Ptr& question,
                               AllocEngine::ClientContext6& ctx,
                               CfgOptionList& co_list) {
     // Firstly, host specific options.
-    if (ctx.host_ && !ctx.host_->getCfgOption6()->empty()) {
-        co_list.push_back(ctx.host_->getCfgOption6());
+    if (ctx.currentHost() && !ctx.currentHost()->getCfgOption6()->empty()) {
+        co_list.push_back(ctx.currentHost()->getCfgOption6());
     }
 
     // Secondly, pool specific options. Pools are defined within a subnet, so
@@ -1250,8 +1250,8 @@ Dhcpv6Srv::processClientFqdn(const Pkt6Ptr& question, const Pkt6Ptr& answer,
         } else {
             // No FQDN so get the lease hostname from the host reservation if
             // there is one.
-            if (ctx.host_) {
-                ctx.hostname_ = ctx.host_->getHostname();
+            if (ctx.currentHost()) {
+                ctx.hostname_ = ctx.currentHost()->getHostname();
             }
 
             return;
@@ -1271,10 +1271,10 @@ Dhcpv6Srv::processClientFqdn(const Pkt6Ptr& question, const Pkt6Ptr& answer,
     d2_mgr.adjustFqdnFlags<Option6ClientFqdn>(*fqdn, *fqdn_resp);
 
     // If there's a reservation and it has a hostname specified, use it!
-    if (ctx.host_ && !ctx.host_->getHostname().empty()) {
+    if (ctx.currentHost() && !ctx.currentHost()->getHostname().empty()) {
         // Add the qualifying suffix.
         // After #3765, this will only occur if the suffix is not empty.
-        fqdn_resp->setDomainName(d2_mgr.qualifyName(ctx.host_->getHostname(),
+        fqdn_resp->setDomainName(d2_mgr.qualifyName(ctx.currentHost()->getHostname(),
                                                     true),
                                                     Option6ClientFqdn::FULL);
     } else {
@@ -3082,9 +3082,9 @@ void Dhcpv6Srv::classifyPacket(const Pkt6Ptr& pkt) {
 void
 Dhcpv6Srv::setReservedClientClasses(const Pkt6Ptr& pkt,
                                     const AllocEngine::ClientContext6& ctx) {
-    if (ctx.host_ && pkt) {
+    if (ctx.currentHost() && pkt) {
         BOOST_FOREACH(const std::string& client_class,
-                      ctx.host_->getClientClasses6()) {
+                      ctx.currentHost()->getClientClasses6()) {
             pkt->addClass(client_class);
         }
     }

+ 241 - 100
src/lib/dhcpsrv/alloc_engine.cc

@@ -309,21 +309,30 @@ template<typename ContextType>
 void
 AllocEngine::findReservationInternal(ContextType& ctx,
                                      const AllocEngine::HostGetFunc& host_get) {
-    ctx.host_.reset();
+    ctx.hosts_.clear();
+
+    auto subnet = ctx.subnet_;
 
     // We can only search for the reservation if a subnet has been selected.
-    if (ctx.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.
-            ctx.host_ = host_get(ctx.subnet_->getID(), id_pair.first,
-                                 &id_pair.second[0], id_pair.second.size());
-            // If we found matching host, return.
-            if (ctx.host_) {
-                return;
+            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;
             }
         }
+
+        // We need to get to the next subnet if this is a shared network. If it
+        // is not (a plain subnet), getNextSubnet will return NULL and we're
+        // done here.
+        subnet = subnet->getNextSubnet(ctx.subnet_, ctx.query_->getClasses());
     }
 }
 
@@ -334,7 +343,7 @@ AllocEngine::findReservationInternal(ContextType& ctx,
 
 AllocEngine::ClientContext6::ClientContext6()
     : query_(), fake_allocation_(false), subnet_(), duid_(),
-      hwaddr_(), host_identifiers_(), host_(), fwd_dns_update_(false),
+      hwaddr_(), host_identifiers_(), hosts_(), fwd_dns_update_(false),
       rev_dns_update_(false), hostname_(), callout_handle_(),
       ias_() {
 }
@@ -348,7 +357,7 @@ AllocEngine::ClientContext6::ClientContext6(const Subnet6Ptr& subnet,
                                             const Pkt6Ptr& query,
                                             const CalloutHandlePtr& callout_handle)
     : query_(query), fake_allocation_(fake_allocation), subnet_(subnet),
-      duid_(duid), hwaddr_(), host_identifiers_(), host_(),
+      duid_(duid), hwaddr_(), host_identifiers_(), hosts_(),
       fwd_dns_update_(fwd_dns), rev_dns_update_(rev_dns),
       hostname_(hostname), callout_handle_(callout_handle),
       allocated_resources_(), ias_() {
@@ -386,6 +395,16 @@ isAllocated(const asiolink::IOAddress& prefix, const uint8_t prefix_len) const {
             (allocated_resources_.count(std::make_pair(prefix, prefix_len))));
 }
 
+ConstHostPtr
+AllocEngine::ClientContext6::currentHost() const {
+    if (subnet_) {
+        auto host = hosts_.find(subnet_->getID());
+        if (host != hosts_.cend()) {
+            return (host->second);
+        }
+    }
+    return (ConstHostPtr());
+}
 
 void AllocEngine::findReservation(ClientContext6& ctx) {
     findReservationInternal(ctx, boost::bind(&HostMgr::get6,
@@ -426,13 +445,9 @@ AllocEngine::allocateLeases6(ClientContext6& ctx) {
         //       no: release existing leases, assign new ones based on reservations
         // Case 4/catch-all. if there are no leases and no reservations...
         //       assign new leases
-        //
-        // We could implement those checks as nested ifs, but the performance
-        // gain would be minimal and the code readability loss would be substantial.
-        // Hence independent checks.
 
         // Case 1: There are no leases and there's a reservation for this host.
-        if (leases.empty() && ctx.host_) {
+        if (leases.empty() && ctx.currentHost()) {
 
             LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
                       ALLOC_ENGINE_V6_ALLOC_NO_LEASES_HR)
@@ -456,7 +471,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.host_) {
+        } else if (!leases.empty() && !ctx.currentHost()) {
 
             LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
                       ALLOC_ENGINE_V6_ALLOC_LEASES_NO_HR)
@@ -475,7 +490,7 @@ AllocEngine::allocateLeases6(ClientContext6& ctx) {
             // assign something new.
 
         // Case 3: There are leases and there are reservations.
-        } else if (!leases.empty() && ctx.host_) {
+        } else if (!leases.empty() && ctx.currentHost()) {
 
             LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
                       ALLOC_ENGINE_V6_ALLOC_LEASES_HR)
@@ -754,7 +769,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.host_ || !ctx.host_->hasIPv6Reservation()) {
+    if (!ctx.currentHost() || !ctx.currentHost()->hasIPv6Reservation()) {
         LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
                   ALLOC_ENGINE_V6_ALLOC_NO_V6_HR)
             .arg(ctx.query_->getLabel());
@@ -770,8 +785,8 @@ 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.host_->hasReservation(IPv6Resrv(type, lease->addr_,
-                                                    lease->prefixlen_))) {
+            if (ctx.currentHost()->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,
@@ -794,7 +809,7 @@ AllocEngine::allocateReservedLeases6(ClientContext6& ctx,
     // over reservations specified and try to allocate one of them for the IA.
 
     // Get the IPv6 reservations of specified type.
-    const IPv6ResrvRange& reservs = ctx.host_->getIPv6Reservations(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();
@@ -860,16 +875,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.host_) {
+        if (ctx.currentHost()) {
             if (candidate->type_ == Lease6::TYPE_NA) {
-                if (ctx.host_->hasReservation(IPv6Resrv(IPv6Resrv::TYPE_NA,
-                                                        candidate->addr_))) {
+                if (ctx.currentHost()->hasReservation(IPv6Resrv(IPv6Resrv::TYPE_NA,
+                                                                candidate->addr_))) {
                     continue;
                 }
             } else {
-                if (ctx.host_->hasReservation(IPv6Resrv(IPv6Resrv::TYPE_PD,
-                                                        candidate->addr_,
-                                                        candidate->prefixlen_))) {
+                if (ctx.currentHost()->hasReservation(IPv6Resrv(IPv6Resrv::TYPE_PD,
+                                                                candidate->addr_,
+                                                                candidate->prefixlen_))) {
                     continue;
                 }
             }
@@ -956,7 +971,8 @@ 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.host_ || !ctx.host_->hasIPv6Reservation()) {
+    if (existing_leases.empty() || !ctx.currentHost() ||
+        !ctx.currentHost()->hasIPv6Reservation()) {
         return;
     }
 
@@ -970,7 +986,7 @@ AllocEngine::removeNonreservedLeases6(ClientContext6& ctx,
         IPv6Resrv resv(ctx.currentIA().type_ == Lease::TYPE_NA ?
                        IPv6Resrv::TYPE_NA : IPv6Resrv::TYPE_PD,
                        (*lease)->addr_, (*lease)->prefixlen_);
-        if (!ctx.host_->hasReservation(resv)) {
+        if (!ctx.currentHost()->hasReservation(resv)) {
             // We have reservations, but not for this lease. Release it.
 
             // Remove this lease from LeaseMgr
@@ -1234,7 +1250,7 @@ AllocEngine::renewLeases6(ClientContext6& ctx) {
             removeNonmatchingReservedLeases6(ctx, leases);
         }
 
-        if (ctx.host_) {
+        if (ctx.currentHost()) {
 
             LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
                       ALLOC_ENGINE_V6_RENEW_HR)
@@ -2154,13 +2170,31 @@ addressReserved(const IOAddress& address, const AllocEngine::ClientContext4& ctx
 /// dynamic pool. The allocation engine uses this function to check if
 /// the reservation is made for the IPv4 address.
 ///
-/// @param ctx Client context holding the data extracted from the
+/// @param [out] ctx Client context holding the data extracted from the
 /// client's message.
 ///
 /// @return true if the context contains the reservation for the IPv4 address.
 bool
-hasAddressReservation(const AllocEngine::ClientContext4& ctx) {
-    return (ctx.host_ && !ctx.host_->getIPv4Reservation().isV4Zero());
+hasAddressReservation(AllocEngine::ClientContext4& ctx) {
+    if (ctx.hosts_.empty()) {
+        return (false);
+    }
+
+    Subnet4Ptr subnet = ctx.subnet_;
+    while (subnet) {
+        auto host = ctx.hosts_.find(subnet->getID());
+        if ((host != ctx.hosts_.end()) &&
+            !(host->second->getIPv4Reservation().isV4Zero())) {
+            ctx.subnet_ = subnet;
+            return (true);
+        }
+
+        // No address reservation found here, so let's try another subnet
+        // within the same shared network.
+        subnet = subnet->getNextSubnet(ctx.subnet_, ctx.query_->getClasses());
+    }
+
+    return (false);
 }
 
 /// @brief Finds existing lease in the database.
@@ -2173,41 +2207,97 @@ hasAddressReservation(const AllocEngine::ClientContext4& ctx) {
 /// address, the function also checks if the lease belongs to the client, i.e.
 /// there is no conflict between the client identifiers.
 ///
-/// @param ctx Context holding data extracted from the client's message,
-/// including the HW address and client identifier.
+/// @param [out] ctx Context holding data extracted from the client's message,
+/// including the HW address and client identifier. The current subnet may be
+/// modified by this function if it belongs to a shared network.
 /// @param [out] client_lease A pointer to the lease returned by this function
 /// or null value if no has been lease found.
-void findClientLease(const AllocEngine::ClientContext4& ctx, Lease4Ptr& client_lease) {
+void findClientLease(AllocEngine::ClientContext4& ctx, Lease4Ptr& client_lease) {
     LeaseMgr& lease_mgr = LeaseMgrFactory::instance();
-    // If client identifier has been supplied, use it to lookup the lease. This
-    // search will return no lease if the client doesn't have any lease in the
-    // database or if the client didn't use client identifier to allocate the
-    // existing lease (this include cases when the server was explicitly
-    // configured to ignore client identifier).
-    if (ctx.clientid_) {
-        client_lease = lease_mgr.getLease4(*ctx.clientid_, ctx.subnet_->getID());
-    }
-
-    // If no lease found using the client identifier, try the lookup using
-    // the HW address.
-    if (!client_lease && ctx.hwaddr_) {
-
-        // There may be cases when there is a lease for the same MAC address
-        // (even within the same subnet). Such situation may occur for PXE
-        // boot clients using the same MAC address but different client
-        // identifiers.
-        Lease4Collection client_leases = lease_mgr.getLease4(*ctx.hwaddr_);
-        for (Lease4Collection::const_iterator client_lease_it = client_leases.begin();
-             client_lease_it != client_leases.end(); ++client_lease_it) {
-            Lease4Ptr existing_lease = *client_lease_it;
-            if ((existing_lease->subnet_id_ == ctx.subnet_->getID()) &&
-                existing_lease->belongsToClient(ctx.hwaddr_, ctx.clientid_)) {
-                // Found the lease of this client, so return it.
-                client_lease = existing_lease;
-                break;
+
+    Subnet4Ptr original_subnet = ctx.subnet_;
+    Subnet4Ptr subnet = ctx.subnet_;
+
+    SharedNetwork4Ptr network;
+    subnet->getSharedNetwork(network);
+
+    while (subnet) {
+
+        ClientIdPtr client_id;
+        if (subnet->getMatchClientId()) {
+            client_id = ctx.clientid_;
+        }
+
+        // If client identifier has been supplied, use it to lookup the lease. This
+        // search will return no lease if the client doesn't have any lease in the
+        // database or if the client didn't use client identifier to allocate the
+        // existing lease (this include cases when the server was explicitly
+        // configured to ignore client identifier).
+        if (client_id) {
+            client_lease = lease_mgr.getLease4(*client_id, subnet->getID());
+        }
+
+        // If no lease found using the client identifier, try the lookup using
+        // the HW address.
+        if (!client_lease && ctx.hwaddr_) {
+
+            // There may be cases when there is a lease for the same MAC address
+            // (even within the same subnet). Such situation may occur for PXE
+            // boot clients using the same MAC address but different client
+            // identifiers.
+            Lease4Collection client_leases = lease_mgr.getLease4(*ctx.hwaddr_);
+            for (Lease4Collection::const_iterator client_lease_it = client_leases.begin();
+                 client_lease_it != client_leases.end(); ++client_lease_it) {
+                Lease4Ptr existing_lease = *client_lease_it;
+                if ((existing_lease->subnet_id_ == subnet->getID()) &&
+                    existing_lease->belongsToClient(ctx.hwaddr_, client_id)) {
+                    // Found the lease of this client, so return it.
+                    client_lease = existing_lease;
+                    // We got a lease but the subnet it belongs to may differ from
+                    // the original subnet. Let's now stick to this subnet.
+                    ctx.subnet_ = subnet;
+                    return;
+                }
             }
         }
+
+        // Haven't found any lease in this subnet, so let's try another subnet
+        // within the shared network.
+        subnet = subnet->getNextSubnet(original_subnet, ctx.query_->getClasses());
+    }
+}
+
+/// @brief Checks if the specified address belongs to one of the subnets
+/// within a shared network.
+///
+/// @todo Update this function to take client classification into account.
+///
+/// @param ctx Client context. Current subnet may be modified by this
+/// function when it belongs to a shared network.
+/// @param address IPv4 address 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::ClientContext4& ctx, const IOAddress& address) {
+    // If the subnet belongs to a shared network we will be iterating
+    // over the subnets that belong to this shared network.
+    Subnet4Ptr current_subnet = ctx.subnet_;
+    while (current_subnet) {
+
+        if (current_subnet->inPool(Lease::TYPE_V4, address)) {
+            // We found a subnet that this address belongs to, so it
+            // seems that this subnet is the good candidate for allocation.
+            // Let's update the selected subnet.
+            ctx.subnet_ = current_subnet;
+            return (true);
+        }
+
+        current_subnet = current_subnet->getNextSubnet(ctx.subnet_,
+                                                       ctx.query_->getClasses());
     }
+
+    return (false);
 }
 
 } // end of anonymous namespace
@@ -2220,7 +2310,7 @@ AllocEngine::ClientContext4::ClientContext4()
       requested_address_(IOAddress::IPV4_ZERO_ADDRESS()),
       fwd_dns_update_(false), rev_dns_update_(false),
       hostname_(""), callout_handle_(), fake_allocation_(false),
-      old_lease_(), host_(), conflicting_lease_(), query_(),
+      old_lease_(), hosts_(), conflicting_lease_(), query_(),
       host_identifiers_() {
 }
 
@@ -2236,7 +2326,7 @@ AllocEngine::ClientContext4::ClientContext4(const Subnet4Ptr& subnet,
       requested_address_(requested_addr),
       fwd_dns_update_(fwd_dns_update), rev_dns_update_(rev_dns_update),
       hostname_(hostname), callout_handle_(),
-      fake_allocation_(fake_allocation), old_lease_(), host_(),
+      fake_allocation_(fake_allocation), old_lease_(), hosts_(),
       host_identifiers_() {
 
     // Initialize host identifiers.
@@ -2245,6 +2335,17 @@ AllocEngine::ClientContext4::ClientContext4(const Subnet4Ptr& subnet,
     }
 }
 
+ConstHostPtr
+AllocEngine::ClientContext4::currentHost() const {
+    if (subnet_) {
+        auto host = hosts_.find(subnet_->getID());
+        if (host != hosts_.cend()) {
+            return (host->second);
+        }
+    }
+    return (ConstHostPtr());
+}
+
 Lease4Ptr
 AllocEngine::allocateLease4(ClientContext4& ctx) {
     // The NULL pointer indicates that the old lease didn't exist. It may
@@ -2254,6 +2355,16 @@ AllocEngine::allocateLease4(ClientContext4& ctx) {
 
     Lease4Ptr new_lease;
 
+    // Before we start allocation process, we need to make sure that the
+    // selected subnet is allowed for this client. If not, we'll try to
+    // use some other subnet within the shared network. If there are no
+    // subnets allowed for this client within the shared network, we
+    // can't allocate a lease.
+    Subnet4Ptr subnet = ctx.subnet_;
+    if (subnet && !subnet->clientSupported(ctx.query_->getClasses())) {
+        ctx.subnet_ = subnet->getNextSubnet(subnet, ctx.query_->getClasses());
+    }
+
     try {
         if (!ctx.subnet_) {
             isc_throw(BadValue, "Can't allocate IPv4 address without subnet");
@@ -2301,24 +2412,24 @@ AllocEngine::discoverLease4(AllocEngine::ClientContext4& ctx) {
         LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
                   ALLOC_ENGINE_V4_DISCOVER_HR)
             .arg(ctx.query_->getLabel())
-            .arg(ctx.host_->getIPv4Reservation().toText());
+            .arg(ctx.currentHost()->getIPv4Reservation().toText());
 
         // If the client doesn't have a lease or the leased address is different
         // than the reserved one then let's try to allocate the reserved address.
         // Otherwise the address that the client has is the one for which it
         // has a reservation, so just renew it.
-        if (!client_lease || (client_lease->addr_ != ctx.host_->getIPv4Reservation())) {
+        if (!client_lease || (client_lease->addr_ != ctx.currentHost()->getIPv4Reservation())) {
             // The call below will return a pointer to the lease for the address
             // reserved to this client, if the lease is available, i.e. is not
             // currently assigned to any other client.
             // Note that we don't remove the existing client's lease at this point
             // because this is not a real allocation, we just offer what we can
             // allocate in the DHCPREQUEST time.
-            new_lease = allocateOrReuseLease4(ctx.host_->getIPv4Reservation(), ctx);
+            new_lease = allocateOrReuseLease4(ctx.currentHost()->getIPv4Reservation(), ctx);
             if (!new_lease) {
                 LOG_WARN(alloc_engine_logger, ALLOC_ENGINE_V4_DISCOVER_ADDRESS_CONFLICT)
                     .arg(ctx.query_->getLabel())
-                    .arg(ctx.host_->getIPv4Reservation().toText())
+                    .arg(ctx.currentHost()->getIPv4Reservation().toText())
                     .arg(ctx.conflicting_lease_ ? ctx.conflicting_lease_->toText() :
                          "(no lease info)");
             }
@@ -2337,8 +2448,7 @@ AllocEngine::discoverLease4(AllocEngine::ClientContext4& ctx) {
     // new reservation for the address used by this client. The latter may
     // be due to the client using the reserved out-of-the pool address, for
     // which the reservation has just been removed.
-    if (!new_lease && client_lease &&
-        ctx.subnet_->inPool(Lease::TYPE_V4, client_lease->addr_) &&
+    if (!new_lease && client_lease && inAllowedPool(ctx, client_lease->addr_) &&
         !addressReserved(client_lease->addr_, ctx)) {
 
         LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
@@ -2356,7 +2466,7 @@ AllocEngine::discoverLease4(AllocEngine::ClientContext4& ctx) {
     // reserved for another client, and must be in the range of the
     // dynamic pool.
     if (!new_lease && !ctx.requested_address_.isV4Zero() &&
-        ctx.subnet_->inPool(Lease::TYPE_V4, ctx.requested_address_) &&
+        inAllowedPool(ctx, ctx.requested_address_) &&
         !addressReserved(ctx.requested_address_, ctx)) {
 
         LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
@@ -2425,7 +2535,7 @@ AllocEngine::requestLease4(AllocEngine::ClientContext4& ctx) {
         // allocation engine needs to find an appropriate address.
         // If there is a reservation for the client, let's try to
         // allocate the reserved address.
-        ctx.requested_address_ = ctx.host_->getIPv4Reservation();
+        ctx.requested_address_ = ctx.currentHost()->getIPv4Reservation();
 
         LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
                   ALLOC_ENGINE_V4_REQUEST_USE_HR)
@@ -2442,7 +2552,8 @@ AllocEngine::requestLease4(AllocEngine::ClientContext4& ctx) {
         // If it is in use by another client, the address can't be
         // allocated.
         if (existing && !existing->expired() &&
-            !existing->belongsToClient(ctx.hwaddr_, ctx.clientid_)) {
+            !existing->belongsToClient(ctx.hwaddr_, ctx.subnet_->getMatchClientId() ?
+                                       ctx.clientid_ : ClientIdPtr())) {
 
             LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
                       ALLOC_ENGINE_V4_REQUEST_IN_USE)
@@ -2457,8 +2568,9 @@ AllocEngine::requestLease4(AllocEngine::ClientContext4& ctx) {
         // address because the reserved address is in use. We will have to
         // check if the address is in use.
         if (hasAddressReservation(ctx) &&
-            (ctx.host_->getIPv4Reservation() != ctx.requested_address_)) {
-            existing = LeaseMgrFactory::instance().getLease4(ctx.host_->getIPv4Reservation());
+            (ctx.currentHost()->getIPv4Reservation() != ctx.requested_address_)) {
+            existing =
+                LeaseMgrFactory::instance().getLease4(ctx.currentHost()->getIPv4Reservation());
             // If the reserved address is not in use, i.e. the lease doesn't
             // exist or is expired, and the client is requesting a different
             // address, return NULL. The client should go back to the
@@ -2468,7 +2580,7 @@ AllocEngine::requestLease4(AllocEngine::ClientContext4& ctx) {
                 LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
                           ALLOC_ENGINE_V4_REQUEST_INVALID)
                     .arg(ctx.query_->getLabel())
-                    .arg(ctx.host_->getIPv4Reservation().toText())
+                    .arg(ctx.currentHost()->getIPv4Reservation().toText())
                     .arg(ctx.requested_address_.toText());
 
                 return (Lease4Ptr());
@@ -2479,8 +2591,8 @@ AllocEngine::requestLease4(AllocEngine::ClientContext4& ctx) {
         // address is reserved for the client. If the address is not reserved one
         // and it doesn't belong to the dynamic pool, do not allocate it.
         if ((!hasAddressReservation(ctx) ||
-             (ctx.host_->getIPv4Reservation() != ctx.requested_address_)) &&
-            !ctx.subnet_->inPool(Lease4::TYPE_V4, ctx.requested_address_)) {
+             (ctx.currentHost()->getIPv4Reservation() != ctx.requested_address_)) &&
+            !inAllowedPool(ctx, ctx.requested_address_)) {
 
             LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
                       ALLOC_ENGINE_V4_REQUEST_OUT_OF_POOL)
@@ -2579,7 +2691,7 @@ AllocEngine::createLease4(const ClientContext4& ctx, const IOAddress& addr) {
 
     // @todo: remove this kludge after ticket #2590 is implemented
     std::vector<uint8_t> local_copy;
-    if (ctx.clientid_) {
+    if (ctx.clientid_ && ctx.subnet_->getMatchClientId()) {
         local_copy = ctx.clientid_->getDuid();
     }
     const uint8_t* local_copy0 = local_copy.empty() ? 0 : &local_copy[0];
@@ -2719,10 +2831,15 @@ AllocEngine::renewLease4(const Lease4Ptr& lease,
         // version of dynamic_pointer_cast.
         Subnet4Ptr subnet4 = boost::dynamic_pointer_cast<Subnet4>(ctx.subnet_);
 
-        // Pass the parameters
+        // Pass the parameters. Note the clientid is passed only if match-client-id
+        // is set. This is done that way, because the lease4-renew hook point is
+        // about renewing a lease and the configuration parameter says the
+        // client-id should be ignored. Hence no clientid value if match-client-id
+        // is false.
         ctx.callout_handle_->setArgument("query4", ctx.query_);
         ctx.callout_handle_->setArgument("subnet4", subnet4);
-        ctx.callout_handle_->setArgument("clientid", ctx.clientid_);
+        ctx.callout_handle_->setArgument("clientid", subnet4->getMatchClientId() ?
+                                         ctx.clientid_ : ClientIdPtr());
         ctx.callout_handle_->setArgument("hwaddr", ctx.hwaddr_);
 
         // Pass the lease to be updated
@@ -2882,24 +2999,48 @@ Lease4Ptr
 AllocEngine::allocateUnreservedLease4(ClientContext4& ctx) {
     Lease4Ptr new_lease;
     AllocatorPtr allocator = getAllocator(Lease::TYPE_V4);
-    const uint64_t max_attempts = (attempts_ > 0 ? attempts_ :
-                                   ctx.subnet_->getPoolCapacity(Lease::TYPE_V4));
-    for (uint64_t i = 0; i < max_attempts; ++i) {
-        IOAddress candidate = allocator->pickAddress(ctx.subnet_, ctx.clientid_,
-                                                     ctx.requested_address_);
-        // If address is not reserved for another client, try to allocate it.
-        if (!addressReserved(candidate, ctx)) {
-            // The call below will return the non-NULL pointer if we
-            // successfully allocate this lease. This means that the
-            // address is not in use by another client.
-            new_lease = allocateOrReuseLease4(candidate, ctx);
-            if (new_lease) {
-                return (new_lease);
-            } else if (ctx.callout_handle_ &&
-                       (ctx.callout_handle_->getStatus() !=
-                        CalloutHandle::NEXT_STEP_CONTINUE)) {
-                // Don't retry when the callout status is not continue.
-                break;
+    Subnet4Ptr subnet = ctx.subnet_;
+    Subnet4Ptr original_subnet = subnet;
+    SharedNetwork4Ptr network;
+    subnet->getSharedNetwork(network);
+    uint64_t total_attempts = 0;
+    while (subnet) {
+
+        ClientIdPtr client_id;
+        if (subnet->getMatchClientId()) {
+            client_id = ctx.clientid_;
+        }
+
+        const uint64_t max_attempts = (attempts_ > 0 ? attempts_ :
+                                       subnet->getPoolCapacity(Lease::TYPE_V4));
+        for (uint64_t i = 0; i < max_attempts; ++i) {
+            IOAddress candidate = allocator->pickAddress(subnet, client_id,
+                                                         ctx.requested_address_);
+            // If address is not reserved for another client, try to allocate it.
+            if (!addressReserved(candidate, ctx)) {
+                // The call below will return the non-NULL pointer if we
+                // successfully allocate this lease. This means that the
+                // address is not in use by another client.
+                new_lease = allocateOrReuseLease4(candidate, ctx);
+                if (new_lease) {
+                    return (new_lease);
+                } else if (ctx.callout_handle_ &&
+                           (ctx.callout_handle_->getStatus() !=
+                            CalloutHandle::NEXT_STEP_CONTINUE)) {
+                    // Don't retry when the callout status is not continue.
+                    subnet.reset();
+                    break;
+                }
+            }
+            ++total_attempts;
+        }
+
+        // This pointer may be set to NULL if hooks set SKIP status.
+        if (subnet) {
+            subnet = subnet->getNextSubnet(original_subnet, ctx.query_->getClasses());
+
+            if (subnet) {
+                ctx.subnet_ = subnet;
             }
         }
     }
@@ -2907,7 +3048,7 @@ AllocEngine::allocateUnreservedLease4(ClientContext4& ctx) {
     // Unable to allocate an address, return an empty lease.
     LOG_WARN(alloc_engine_logger, ALLOC_ENGINE_V4_ALLOC_FAIL)
         .arg(ctx.query_->getLabel())
-        .arg(max_attempts);
+        .arg(total_attempts);
 
     return (new_lease);
 }
@@ -2917,7 +3058,7 @@ AllocEngine::updateLease4Information(const Lease4Ptr& lease,
                                      AllocEngine::ClientContext4& ctx) const {
     lease->subnet_id_ = ctx.subnet_->getID();
     lease->hwaddr_ = ctx.hwaddr_;
-    lease->client_id_ = ctx.clientid_;
+    lease->client_id_ = ctx.subnet_->getMatchClientId() ? ctx.clientid_ : ClientIdPtr();
     lease->cltt_ = time(NULL);
     lease->t1_ = ctx.subnet_->getT1();
     lease->t2_ = ctx.subnet_->getT2();

+ 22 - 6
src/lib/dhcpsrv/alloc_engine.h

@@ -315,10 +315,12 @@ public:
         /// received by the server.
         IdentifierList host_identifiers_;
 
-        /// @brief A pointer to the object identifying host reservations.
+        /// @brief Holds a map of hosts belonging to the client within different
+        /// subnets.
         ///
-        /// May be NULL if there are no reservations.
-        ConstHostPtr host_;
+        /// Multiple hosts may appear when the client belongs to a shared
+        /// network.
+        std::map<SubnetID, ConstHostPtr> hosts_;
 
         /// @brief A boolean value which indicates that server takes
         ///        responsibility for the forward DNS Update for this lease
@@ -441,6 +443,11 @@ public:
             ias_.push_back(IAContext());
         };
 
+        /// @brief Returns host for currently selected subnet.
+        ///
+        /// @return Pointer to the host object.
+        ConstHostPtr currentHost() const;
+
         /// @brief Default constructor.
         ClientContext6();
 
@@ -1031,7 +1038,7 @@ public:
     /// that the big advantage of using the context structure to pass
     /// information to the allocation engine methods is that adding
     /// new information doesn't modify the API of the allocation engine.
-    struct ClientContext4 {
+    struct ClientContext4 : public boost::noncopyable {
         /// @brief Subnet selected for the client by the server.
         Subnet4Ptr subnet_;
 
@@ -1072,8 +1079,12 @@ public:
         /// @brief A pointer to an old lease that the client had before update.
         Lease4Ptr old_lease_;
 
-        /// @brief A pointer to the object identifying host reservations.
-        ConstHostPtr host_;
+        /// @brief Holds a map of hosts belonging to the client within different
+        /// subnets.
+        ///
+        /// Multiple hosts may appear when the client belongs to a shared
+        /// network.
+        std::map<SubnetID, ConstHostPtr> hosts_;
 
         /// @brief A pointer to the object representing a lease in conflict.
         ///
@@ -1102,6 +1113,11 @@ public:
             host_identifiers_.push_back(IdentifierPair(id_type, identifier));
         }
 
+        /// @brief Returns host for currently selected subnet.
+        ///
+        /// @return Pointer to the host object.
+        ConstHostPtr currentHost() const;
+
         /// @brief Default constructor.
         ClientContext4();
 

+ 48 - 24
src/lib/dhcpsrv/cfg_subnets4.cc

@@ -9,6 +9,7 @@
 #include <dhcpsrv/cfg_subnets4.h>
 #include <dhcpsrv/dhcpsrv_log.h>
 #include <dhcpsrv/lease_mgr_factory.h>
+#include <dhcpsrv/shared_network.h>
 #include <dhcpsrv/subnet_id.h>
 #include <dhcpsrv/addr_utilities.h>
 #include <asiolink/io_address.h>
@@ -119,17 +120,29 @@ CfgSubnets4::selectSubnet(const SubnetSelector& selector) const {
     }
 
     // If relayed message has been received, try to match the giaddr with the
-    // relay address specified for a subnet. It is also possible that the relay
-    // address will not match with any of the relay addresses across all
-    // subnets, but we need to verify that for all subnets before we can try
-    // to use the giaddr to match with the subnet prefix.
+    // relay address specified for a subnet and/or shared network. It is also
+    // possible that the relay address will not match with any of the relay
+    // addresses across all subnets, but we need to verify that for all subnets
+    // before we can try to use the giaddr to match with the subnet prefix.
     if (!selector.giaddr_.isV4Zero()) {
         for (Subnet4Collection::const_iterator subnet = subnets_.begin();
              subnet != subnets_.end(); ++subnet) {
 
-            // Check if the giaddr is equal to the one defined for the subnet.
-            if (selector.giaddr_ != (*subnet)->getRelayInfo().addr_) {
-                continue;
+            // If relay information is specified for this subnet, it must match.
+            // Otherwise, we ignore this subnet.
+            if (!(*subnet)->getRelayInfo().addr_.isV4Zero()) {
+                if (selector.giaddr_ != (*subnet)->getRelayInfo().addr_) {
+                    continue;
+                }
+
+            } else {
+                // Relay information is not specified on the subnet level,
+                // so let's try matching on the shared network level.
+                SharedNetwork4Ptr network;
+                (*subnet)->getSharedNetwork(network);
+                if (!network || (selector.giaddr_ != network->getRelayInfo().addr_)) {
+                    continue;
+                }
             }
 
             // If a subnet meets the client class criteria return it.
@@ -198,29 +211,40 @@ CfgSubnets4::selectSubnet(const SubnetSelector& selector) const {
 
 Subnet4Ptr
 CfgSubnets4::selectSubnet(const std::string& iface,
-                 const ClientClasses& client_classes) const {
+                          const ClientClasses& client_classes) const {
     for (Subnet4Collection::const_iterator subnet = subnets_.begin();
          subnet != subnets_.end(); ++subnet) {
 
-        // If there's no interface specified for this subnet, proceed to
-        // the next subnet.
-        if ((*subnet)->getIface().empty()) {
-            continue;
-        }
+        Subnet4Ptr subnet_selected;
 
-        // If it's specified, but does not match, proceed to the next
-        // subnet.
-        if ((*subnet)->getIface() != iface) {
-            continue;
+        // First, try subnet specific interface name.
+        if (!(*subnet)->getIface().empty()) {
+            if ((*subnet)->getIface() == iface) {
+                subnet_selected = (*subnet);
+            }
+
+        } else {
+            // Interface not specified for a subnet, so let's try if
+            // we can match with shared network specific setting of
+            // the interface.
+            SharedNetwork4Ptr network;
+            (*subnet)->getSharedNetwork(network);
+            if (network && !network->getIface().empty() &&
+                (network->getIface() == iface)) {
+                subnet_selected = (*subnet);
+            }
         }
 
-        // If a subnet meets the client class criteria return it.
-        if ((*subnet)->clientSupported(client_classes)) {
-            LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
-                      DHCPSRV_CFGMGR_SUBNET4_IFACE)
-                .arg((*subnet)->toText())
-                .arg(iface);
-            return (*subnet);
+        if (subnet_selected) {
+
+            // If a subnet meets the client class criteria return it.
+            if (subnet_selected->clientSupported(client_classes)) {
+                LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
+                          DHCPSRV_CFGMGR_SUBNET4_IFACE)
+                    .arg((*subnet)->toText())
+                    .arg(iface);
+                return (subnet_selected);
+            }
         }
     }
 

+ 1 - 1
src/lib/dhcpsrv/network.h

@@ -165,7 +165,7 @@ public:
     ///
     /// @param client_classes list of all classes the client belongs to
     /// @return true if client can be supported, false otherwise
-    bool
+    virtual bool
     clientSupported(const isc::dhcp::ClientClasses& client_classes) const;
 
     /// @brief Adds class class_name to the list of supported classes

+ 5 - 14
src/lib/dhcpsrv/shared_network.cc

@@ -157,16 +157,7 @@ public:
     template<typename SubnetPtrType, typename SubnetCollectionType>
     static SubnetPtrType getNextSubnet(const SubnetCollectionType& subnets,
                                        const SubnetPtrType& first_subnet,
-                                       const SubnetPtrType& current_subnet) {
-        // Current subnet must not be null. The caller must explicitly set it
-        // to one of the pointers that belong to this shared network, typically
-        // to a selected subnet.
-        if (!current_subnet) {
-            isc_throw(BadValue, "null subnet specified for a shared"
-                      " network while searching for next subnet is this"
-                      " network");
-        }
-
+                                       const SubnetID& current_subnet) {
         // It is ok to have a shared network without any subnets, but in this
         // case there is nothing else we can return but null pointer.
         if (subnets.empty()) {
@@ -177,9 +168,9 @@ public:
         // subnet must exist in this container, thus we throw if the iterator
         // is not found.
         const auto& index = subnets.template get<SubnetSubnetIdIndexTag>();
-        auto subnet_id_it = index.find(current_subnet->getID());
+        auto subnet_id_it = index.find(current_subnet);
         if (subnet_id_it == index.cend()) {
-            isc_throw(BadValue, "no such subnet " << current_subnet->getID()
+            isc_throw(BadValue, "no such subnet " << current_subnet
                       << " within shared network");
         }
 
@@ -235,7 +226,7 @@ SharedNetwork4::getSubnet(const SubnetID& subnet_id) const {
 
 Subnet4Ptr
 SharedNetwork4::getNextSubnet(const Subnet4Ptr& first_subnet,
-                              const Subnet4Ptr& current_subnet) const {
+                              const SubnetID& current_subnet) const {
     return (Impl::getNextSubnet(subnets_, first_subnet, current_subnet));
 }
 
@@ -283,7 +274,7 @@ SharedNetwork6::getSubnet(const SubnetID& subnet_id) const {
 
 Subnet6Ptr
 SharedNetwork6::getNextSubnet(const Subnet6Ptr& first_subnet,
-                              const Subnet6Ptr& current_subnet) const {
+                              const SubnetID& current_subnet) const {
     return (Impl::getNextSubnet(subnets_, first_subnet,
                                          current_subnet));
 }

+ 4 - 4
src/lib/dhcpsrv/shared_network.h

@@ -103,7 +103,7 @@ public:
     /// @param first_subnet Pointer to a subnet from which the caller is
     /// iterating over subnets within shared network. This is typically a
     /// subnet selected during "subnet selection" step.
-    /// @param current_subnet Pointer to a subnet for which next subnet is
+    /// @param current_subnet Identifier of a subnet for which next subnet is
     /// to be found.
     ///
     /// @return Pointer to next subnet or null pointer if no more subnets found.
@@ -111,7 +111,7 @@ public:
     /// @throw isc::BadValue if invalid arguments specified, e.g. unable to
     /// find first or current subnet within shared network.
     Subnet4Ptr getNextSubnet(const Subnet4Ptr& first_subnet,
-                             const Subnet4Ptr& current_subnet) const;
+                             const SubnetID& current_subnet) const;
 
     /// @brief Unparses shared network object.
     ///
@@ -226,7 +226,7 @@ public:
     /// @param first_subnet Pointer to a subnet from which the caller is
     /// iterating over subnets within shared network. This is typically a
     /// subnet selected during "subnet selection" step.
-    /// @param current_subnet Pointer to a subnet for which next subnet is
+    /// @param current_subnet Identifier of a subnet for which next subnet is
     /// to be found.
     ///
     /// @return Pointer to next subnet or null pointer if no more subnets found.
@@ -234,7 +234,7 @@ public:
     /// @throw isc::BadValue if invalid arguments specified, e.g. unable to
     /// find first or current subnet within shared network.
     Subnet6Ptr getNextSubnet(const Subnet6Ptr& first_subnet,
-                             const Subnet6Ptr& current_subnet) const;
+                             const SubnetID& current_subnet) const;
 
     /// @brief Unparses shared network object.
     ///

+ 100 - 0
src/lib/dhcpsrv/subnet.cc

@@ -9,6 +9,7 @@
 #include <asiolink/io_address.h>
 #include <dhcp/option_space.h>
 #include <dhcpsrv/addr_utilities.h>
+#include <dhcpsrv/shared_network.h>
 #include <dhcpsrv/subnet.h>
 #include <algorithm>
 #include <sstream>
@@ -176,6 +177,56 @@ Subnet4::Subnet4(const isc::asiolink::IOAddress& prefix, uint8_t length,
     setValid(valid_lifetime);
 }
 
+Subnet4Ptr
+Subnet4::getNextSubnet(const Subnet4Ptr& first_subnet) const {
+    SharedNetwork4Ptr network;
+    getSharedNetwork(network);
+    if (network) {
+        return (network->getNextSubnet(first_subnet, getID()));
+    }
+
+    return (Subnet4Ptr());
+}
+
+Subnet4Ptr
+Subnet4::getNextSubnet(const Subnet4Ptr& first_subnet,
+                       const ClientClasses& client_classes) const {
+    SharedNetwork4Ptr network;
+    getSharedNetwork(network);
+    // We can only get next subnet if shared network has been defined for
+    // the current subnet.
+    if (network) {
+        Subnet4Ptr subnet;
+        do {
+            // Use subnet identifier of this subnet if this is the first
+            // time we're calling getNextSubnet. Otherwise, use the
+            // subnet id of the previously returned subnet.
+            SubnetID subnet_id = subnet ? subnet->getID() : getID();
+            subnet = network->getNextSubnet(first_subnet, subnet_id);
+            // If client classes match the subnet, return it. Otherwise,
+            // try another subnet.
+            if (subnet && subnet->clientSupported(client_classes)) {
+                return (subnet);
+            }
+        } while (subnet);
+    }
+
+    // No subnet found.
+    return (Subnet4Ptr());
+}
+
+
+bool
+Subnet4::clientSupported(const isc::dhcp::ClientClasses& client_classes) const {
+    NetworkPtr network;
+    getSharedNetwork(network);
+    if (network && !network->clientSupported(client_classes)) {
+        return (false);
+    }
+
+    return (Network4::clientSupported(client_classes));
+}
+
 void Subnet4::setSiaddr(const isc::asiolink::IOAddress& siaddr) {
     if (!siaddr.isV4()) {
         isc_throw(BadValue, "Can't set siaddr to non-IPv4 address "
@@ -439,6 +490,55 @@ void Subnet6::checkType(Lease::Type type) const {
     }
 }
 
+Subnet6Ptr
+Subnet6::getNextSubnet(const Subnet6Ptr& first_subnet) const {
+    SharedNetwork6Ptr network;
+    getSharedNetwork(network);
+    if (network) {
+        return (network->getNextSubnet(first_subnet, getID()));
+    }
+
+    return (Subnet6Ptr());
+}
+
+Subnet6Ptr
+Subnet6::getNextSubnet(const Subnet6Ptr& first_subnet,
+                       const ClientClasses& client_classes) const {
+    SharedNetwork6Ptr network;
+    getSharedNetwork(network);
+    // We can only get next subnet if shared network has been defined for
+    // the current subnet.
+    if (network) {
+        Subnet6Ptr subnet;
+        do {
+            // Use subnet identifier of this subnet if this is the first
+            // time we're calling getNextSubnet. Otherwise, use the
+            // subnet id of the previously returned subnet.
+            SubnetID subnet_id = subnet ? subnet->getID() : getID();
+            subnet = network->getNextSubnet(first_subnet, subnet_id);
+            // If client classes match the subnet, return it. Otherwise,
+            // try another subnet.
+            if (subnet && subnet->clientSupported(client_classes)) {
+                return (subnet);
+            }
+        } while (subnet);
+    }
+
+    // No subnet found.
+    return (Subnet6Ptr());
+}
+
+bool
+Subnet6::clientSupported(const isc::dhcp::ClientClasses& client_classes) const {
+    NetworkPtr network;
+    getSharedNetwork(network);
+    if (network && !network->clientSupported(client_classes)) {
+        return (false);
+    }
+
+    return (Network6::clientSupported(client_classes));
+}
+
 data::ElementPtr
 Subnet::toElement() const {
     ElementPtr map = Element::createMap();

+ 99 - 10
src/lib/dhcpsrv/subnet.h

@@ -371,6 +371,14 @@ protected:
 typedef boost::shared_ptr<Subnet> SubnetPtr;
 
 
+class Subnet4;
+
+/// @brief A const pointer to a @c Subnet4 object.
+typedef boost::shared_ptr<const Subnet4> ConstSubnet4Ptr;
+
+/// @brief A pointer to a @c Subnet4 object.
+typedef boost::shared_ptr<Subnet4> Subnet4Ptr;
+
 /// @brief A configuration holder for IPv4 subnet.
 ///
 /// This class represents an IPv4 subnet.
@@ -394,6 +402,49 @@ public:
             const Triplet<uint32_t>& valid_lifetime,
             const SubnetID id = 0);
 
+    /// @brief Returns next subnet within shared network.
+    ///
+    /// If the current subnet doesn't belong to any shared network or if
+    /// the next subnet is the same as first subnet (specified in the
+    /// argument) a NULL pointer is returned.
+    ///
+    /// @param first_subnet Pointer to the subnet from which iterations have
+    /// started.
+    ///
+    /// @return Pointer to the next subnet or NULL pointer if the next subnet
+    /// is the first subnet or if the current subnet doesn't belong to a
+    /// shared network.
+    Subnet4Ptr getNextSubnet(const Subnet4Ptr& first_subnet) const;
+
+    /// @brief Returns next subnet within shared network that matches
+    /// client classes.
+    ///
+    /// @param first_subnet Pointer to the subnet from which iterations have
+    /// started.
+    /// @param client_classes List of classes that the client belongs to.
+    /// The subnets not matching the classes aren't returned by this
+    /// method.
+    ///
+    /// @return Pointer to the next subnet or NULL pointer if the next subnet
+    /// is the first subnet or if the current subnet doesn't belong to a
+    /// shared network.
+    Subnet4Ptr getNextSubnet(const Subnet4Ptr& first_subnet,
+                             const ClientClasses& client_classes) const;
+
+    /// @brief Checks whether this subnet and parent shared network supports
+    /// the client that belongs to specified classes.
+    ///
+    /// This method extends the @ref Network::clientSupported method with
+    /// additional checks whether shared network owning this class supports
+    /// the client belonging to specified classes. If the class doesn't
+    /// belong to a shared network this method only checks if the subnet
+    /// supports specified classes.
+    ///
+    /// @param client_classes List of classes the client belongs to.
+    /// @return true if client can be supported, false otherwise.
+    virtual bool
+    clientSupported(const isc::dhcp::ClientClasses& client_classes) const;
+
     /// @brief Sets siaddr for the Subnet4
     ///
     /// Will be used for siaddr field (the next server) that typically is used
@@ -450,12 +501,13 @@ private:
     Cfg4o6 dhcp4o6_;
 };
 
-/// @brief A const pointer to a @c Subnet4 object.
-typedef boost::shared_ptr<const Subnet4> ConstSubnet4Ptr;
+class Subnet6;
 
-/// @brief A pointer to a @c Subnet4 object.
-typedef boost::shared_ptr<Subnet4> Subnet4Ptr;
+/// @brief A const pointer to a @c Subnet6 object.
+typedef boost::shared_ptr<const Subnet6> ConstSubnet6Ptr;
 
+/// @brief A pointer to a Subnet6 object
+typedef boost::shared_ptr<Subnet6> Subnet6Ptr;
 
 /// @brief A configuration holder for IPv6 subnet.
 ///
@@ -482,6 +534,49 @@ public:
             const Triplet<uint32_t>& valid_lifetime,
             const SubnetID id = 0);
 
+    /// @brief Returns next subnet within shared network.
+    ///
+    /// If the current subnet doesn't belong to any shared network or if
+    /// the next subnet is the same as first subnet (specified in the
+    /// arguments) a NULL pointer is returned.
+    ///
+    /// @param first_subnet Pointer to the subnet from which iterations have
+    /// started.
+    ///
+    /// @return Pointer to the next subnet or NULL pointer if the next subnet
+    /// is the first subnet or if the current subnet doesn't belong to a
+    /// shared network.
+    Subnet6Ptr getNextSubnet(const Subnet6Ptr& first_subnet) const;
+
+    /// @brief Returns next subnet within shared network that matches
+    /// client classes.
+    ///
+    /// @param first_subnet Pointer to the subnet from which iterations have
+    /// started.
+    /// @param client_classes List of classes that the client belongs to.
+    /// The subnets not matching the classes aren't returned by this
+    /// method.
+    ///
+    /// @return Pointer to the next subnet or NULL pointer if the next subnet
+    /// is the first subnet or if the current subnet doesn't belong to a
+    /// shared network.
+    Subnet6Ptr getNextSubnet(const Subnet6Ptr& first_subnet,
+                             const ClientClasses& client_classes) const;
+
+    /// @brief Checks whether this subnet and parent shared network supports
+    /// the client that belongs to specified classes.
+    ///
+    /// This method extends the @ref Network::clientSupported method with
+    /// additional checks whether shared network owning this class supports
+    /// the client belonging to specified classes. If the class doesn't
+    /// belong to a shared network this method only checks if the subnet
+    /// supports specified classes.
+    ///
+    /// @param client_classes List of classes the client belongs to.
+    /// @return true if client can be supported, false otherwise.
+    virtual bool
+    clientSupported(const isc::dhcp::ClientClasses& client_classes) const;
+
     /// @brief Unparse a subnet object.
     ///
     /// @return A pointer to unparsed subnet configuration.
@@ -505,12 +600,6 @@ private:
 
 };
 
-/// @brief A const pointer to a @c Subnet6 object.
-typedef boost::shared_ptr<const Subnet6> ConstSubnet6Ptr;
-
-/// @brief A pointer to a Subnet6 object
-typedef boost::shared_ptr<Subnet6> Subnet6Ptr;
-
 /// @name Definition of the multi index container holding subnet information
 ///
 //@{

+ 502 - 47
src/lib/dhcpsrv/tests/alloc_engine4_unittest.cc

@@ -6,6 +6,7 @@
 
 #include <config.h>
 #include <dhcp/pkt4.h>
+#include <dhcpsrv/shared_network.h>
 #include <dhcpsrv/tests/alloc_engine_utils.h>
 #include <dhcpsrv/tests/test_utils.h>
 #include <stats/stats_mgr.h>
@@ -48,7 +49,7 @@ TEST_F(AllocEngine4Test, constructor) {
 TEST_F(AllocEngine4Test, simpleAlloc4) {
     boost::scoped_ptr<AllocEngine> engine;
     ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE,
-                                                 100, false)));
+                                                 0, false)));
     ASSERT_TRUE(engine);
 
     // Assigned addresses should be zero.
@@ -83,7 +84,7 @@ TEST_F(AllocEngine4Test, simpleAlloc4) {
 TEST_F(AllocEngine4Test, fakeAlloc4) {
     boost::scoped_ptr<AllocEngine> engine;
     ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE,
-                                                 100, false)));
+                                                 0, false)));
     ASSERT_TRUE(engine);
 
     // Assigned addresses should be zero.
@@ -119,7 +120,7 @@ TEST_F(AllocEngine4Test, fakeAlloc4) {
 TEST_F(AllocEngine4Test, allocWithValidHint4) {
     boost::scoped_ptr<AllocEngine> engine;
     ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE,
-                                                 100, false)));
+                                                 0, false)));
     ASSERT_TRUE(engine);
 
     AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_,
@@ -154,7 +155,7 @@ TEST_F(AllocEngine4Test, allocWithValidHint4) {
 TEST_F(AllocEngine4Test, allocWithUsedHint4) {
     boost::scoped_ptr<AllocEngine> engine;
     ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE,
-                                                 100, false)));
+                                                 0, false)));
     ASSERT_TRUE(engine);
 
     // Let's create a lease and put it in the LeaseMgr
@@ -201,7 +202,7 @@ TEST_F(AllocEngine4Test, allocWithUsedHint4) {
 TEST_F(AllocEngine4Test, allocBogusHint4) {
     boost::scoped_ptr<AllocEngine> engine;
     ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE,
-                                                 100, false)));
+                                                 0, false)));
     ASSERT_TRUE(engine);
 
     // Client would like to get a 10.1.1.1 lease, which does not belong to any
@@ -233,7 +234,7 @@ TEST_F(AllocEngine4Test, allocBogusHint4) {
 TEST_F(AllocEngine4Test, allocateLease4Nulls) {
     boost::scoped_ptr<AllocEngine> engine;
     ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE,
-                                                 100, false)));
+                                                 0, false)));
     ASSERT_TRUE(engine);
 
     // Allocations without subnet are not allowed
@@ -283,7 +284,7 @@ TEST_F(AllocEngine4Test, allocateLease4Nulls) {
 TEST_F(AllocEngine4Test, simpleRenew4) {
     boost::scoped_ptr<AllocEngine> engine;
     ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE,
-                                                 100, false)));
+                                                 0, false)));
     ASSERT_TRUE(engine);
 
     EXPECT_TRUE(testStatistics("assigned-addresses", 0, subnet_->getID()));
@@ -392,7 +393,7 @@ TEST_F(AllocEngine4Test, IterativeAllocator_manyPools4) {
 TEST_F(AllocEngine4Test, smallPool4) {
     boost::scoped_ptr<AllocEngine> engine;
     ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE,
-                                                 100, false)));
+                                                 0, false)));
     ASSERT_TRUE(engine);
 
     IOAddress addr("192.0.2.17");
@@ -437,7 +438,7 @@ TEST_F(AllocEngine4Test, smallPool4) {
 TEST_F(AllocEngine4Test, outOfAddresses4) {
     boost::scoped_ptr<AllocEngine> engine;
     ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE,
-                                                 100, false)));
+                                                 0, false)));
     ASSERT_TRUE(engine);
 
     IOAddress addr("192.0.2.17");
@@ -474,12 +475,465 @@ TEST_F(AllocEngine4Test, outOfAddresses4) {
     EXPECT_FALSE(ctx.old_lease_);
 }
 
+/// @brief This test class is dedicated to testing shared networks
+///
+/// It uses one common configuration:
+/// 1 shared network with 2 subnets:
+///   - 192.0.2.0/24 subnet with a small pool of single address: 192.0.2.17
+///   - 10.1.2.0/24 subnet with pool with 96 addresses.
+class SharedNetworkAlloc4Test : public AllocEngine4Test {
+public:
+
+    /// @brief Initializes configuration (2 subnets, 1 shared network)
+    SharedNetworkAlloc4Test()
+        :engine_(AllocEngine::ALLOC_ITERATIVE, 0, false) {
+        // Create two subnets, each with a single address pool. The first subnet
+        // has only one address in its address pool to make it easier to simulate
+        // address exhaustion.
+        subnet1_.reset(new Subnet4(IOAddress("192.0.2.0"), 24, 1, 2, 3, SubnetID(1)));
+        subnet2_.reset(new Subnet4(IOAddress("10.1.2.0"), 24, 1, 2, 3, SubnetID(2)));
+        pool1_.reset(new Pool4(IOAddress("192.0.2.17"), IOAddress("192.0.2.17")));
+        pool2_.reset(new Pool4(IOAddress("10.1.2.5"), IOAddress("10.1.2.100")));
+
+        subnet1_->addPool(pool1_);
+        subnet2_->addPool(pool2_);
+
+        // Both subnets belong to the same network so they can be used
+        // interchangeably.
+        network_.reset(new SharedNetwork4("test_network"));
+        network_->add(subnet1_);
+        network_->add(subnet2_);
+
+        std::vector<uint8_t> hwaddr_vec = { 0, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe};
+        hwaddr2_.reset(new HWAddr(hwaddr_vec, HTYPE_ETHER));
+    }
+
+    /// @brief Inserts a new lease for specified address
+    ///
+    /// Creates a new lease for specified address and subnet-id and inserts
+    /// it into database. This is not particularly fancy method, it is used
+    /// just to mark existing addresses as used. It uses hwaddr2_ to allocate
+    /// the lease.
+    ///
+    /// @param addr text representation of the address
+    /// @param subnet_id ID of the subnet
+    /// @param return pointer to the lease
+    Lease4Ptr
+    insertLease(std::string addr, SubnetID subnet_id) {
+        Lease4Ptr lease(new Lease4(IOAddress(addr), hwaddr2_, ClientIdPtr(),
+                                   501, 502, 503, time(NULL), subnet_id));
+        lease->cltt_ = time(NULL) - 10; // Allocated 10 seconds ago
+        if (!LeaseMgrFactory::instance().addLease(lease)) {
+            ADD_FAILURE() << "Attempt to add a lease for IP " << addr
+                          << " in subnet " << subnet_id << " failed";
+        }
+
+        return (lease);
+    }
+
+    /// Covenience pointers to configuration elements. These are initialized
+    /// in the constructor and are used throughout the tests.
+    AllocEngine engine_;
+    Subnet4Ptr subnet1_;
+    Subnet4Ptr subnet2_;
+    Pool4Ptr pool1_;
+    Pool4Ptr pool2_;
+    SharedNetwork4Ptr network_;
+
+    HWAddrPtr hwaddr2_; // Note there's hwaddr_ already defined in base class.
+};
+
+// 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(SharedNetworkAlloc4Test, discoverSharedNetworkSimple) {
+
+    // 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.
+    AllocEngine::ClientContext4
+        ctx(subnet1_, ClientIdPtr(), hwaddr_, IOAddress::IPV4_ZERO_ADDRESS(),
+            false, false, "host.example.com.", true);
+    ctx.query_.reset(new Pkt4(DHCPDISCOVER, 1234));
+    Lease4Ptr lease = engine_.allocateLease4(ctx);
+
+    // The allocation engine should have assigned an address from the first
+    // subnet.
+    ASSERT_TRUE(lease);
+    EXPECT_EQ("192.0.2.17", lease->addr_.toText());
+
+    // Make sure the lease is not in the the lease mgr (this is only
+    // discover).
+    ASSERT_FALSE(LeaseMgrFactory::instance().getLease4(lease->addr_));
+}
+
+// This test verifies that the server will pick a second subnet out of two
+// shared subnets if there is a hint for the second subnet.
+TEST_F(SharedNetworkAlloc4Test, discoverSharedNetworkHint) {
+
+    // 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.
+
+    AllocEngine::ClientContext4
+        ctx(subnet1_, ClientIdPtr(), hwaddr_, IOAddress("10.1.2.25"),
+            false, false, "host.example.com.", true);
+    ctx.query_.reset(new Pkt4(DHCPDISCOVER, 1234));
+    Lease4Ptr lease = engine_.allocateLease4(ctx);
+
+    // The allocation engine should have assigned an address from the second
+    // subnet, because that's what the hint requested.
+    ASSERT_TRUE(lease);
+    EXPECT_TRUE(subnet2_->inPool(Lease::TYPE_V4, lease->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(SharedNetworkAlloc4Test, discoverSharedNetwork) {
+    // Create a lease for a single address in the first address pool. The
+    // pool is now exhausted.
+    Lease4Ptr lease = insertLease("192.0.2.17", subnet1_->getID());
+
+    // 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.
+    AllocEngine::ClientContext4
+        ctx(subnet1_, ClientIdPtr(), hwaddr_, IOAddress::IPV4_ZERO_ADDRESS(),
+            false, false, "host.example.com.", true);
+    ctx.query_.reset(new Pkt4(DHCPDISCOVER, 1234));
+    Lease4Ptr lease2 = engine_.allocateLease4(ctx);
+    // The allocation engine should have assigned an address from the second
+    // subnet. We could guess that this is 10.1.2.5, being the first address
+    // in the address pool, but to make the test more generic, we merely
+    // verify that the address is in the given address pool.
+    ASSERT_TRUE(lease2);
+    EXPECT_TRUE(subnet2_->inPool(Lease::TYPE_V4, lease2->addr_));
+
+    // The client should also be offered a lease when it specifies a hint
+    // that doesn't match the subnet from which the lease is offered. The
+    // engine should check alternative subnets to match the hint to
+    // a subnet. The requested lease is available, so it should be offered.
+    ctx.subnet_ = subnet1_;
+    ctx.requested_address_ = IOAddress("10.1.2.25");
+    lease2 = engine_.allocateLease4(ctx);
+    ASSERT_TRUE(lease2);
+    EXPECT_EQ("10.1.2.25", lease2->addr_.toText());
+
+    // The returning client (the one that has a lease) should also be able
+    // to renew its lease regardless of a subnet it begins with. So, it has
+    // an address assigned from subnet1, but we use subnet2 as a selected
+    // subnet.
+    AllocEngine::ClientContext4 ctx2(subnet2_, ClientIdPtr(), hwaddr2_,
+                                     IOAddress("0.0.0.0"), false, false,
+                                     "host.example.com.", true);
+    ctx2.query_.reset(new Pkt4(DHCPDISCOVER, 1234));
+    lease2 = engine_.allocateLease4(ctx2);
+    // The existing lease should be returned.
+    ASSERT_TRUE(lease2);
+    EXPECT_EQ("192.0.2.17", lease2->addr_.toText());
+}
+
+// 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(SharedNetworkAlloc4Test, discoverSharedNetworkClassification) {
+
+    // Try to offer address from subnet1. There is one address available
+    // so it should be offerred.
+    AllocEngine::ClientContext4
+        ctx(subnet1_, ClientIdPtr(), hwaddr_, IOAddress::IPV4_ZERO_ADDRESS(),
+            false, false, "host.example.com.", true);
+    ctx.query_.reset(new Pkt4(DHCPDISCOVER, 1234));
+    Lease4Ptr lease = engine_.allocateLease4(ctx);
+    ASSERT_TRUE(lease);
+    EXPECT_TRUE(subnet1_->inPool(Lease::TYPE_V4, 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.
+    ctx.subnet_ = subnet1_;
+    lease = engine_.allocateLease4(ctx);
+    ASSERT_TRUE(lease);
+    EXPECT_TRUE(subnet2_->inPool(Lease::TYPE_V4, lease->addr_));
+
+    // Create reservation for the client in subnet1. Because this subnet is
+    // not allowed for the client the client should still be offerred a
+    // lease from subnet2.
+    HostPtr host(new Host(&hwaddr_->hwaddr_[0], hwaddr_->hwaddr_.size(),
+                          Host::IDENT_HWADDR, subnet1_->getID(),
+                          SubnetID(0), IOAddress("192.0.2.17")));
+    CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
+    CfgMgr::instance().commit();
+    AllocEngine::findReservation(ctx);
+
+    ctx.subnet_ = subnet1_;
+    lease = engine_.allocateLease4(ctx);
+    ASSERT_TRUE(lease);
+    EXPECT_TRUE(subnet2_->inPool(Lease::TYPE_V4, lease->addr_));
+
+    // Assign cable-modem class and try again. This time, we should
+    // offer an address from the subnet1.
+    ctx.query_->addClass(ClientClass("cable-modem"));
+
+    ctx.subnet_ = subnet1_;
+    lease = engine_.allocateLease4(ctx);
+    ASSERT_TRUE(lease);
+    EXPECT_EQ("192.0.2.17", lease->addr_.toText());
+}
+
+// Test that reservations within shared network take precedence over the
+// existing leases regardless in which subnet belonging to a shared network
+// reservations belong.
+TEST_F(SharedNetworkAlloc4Test, discoverSharedNetworkReservations) {
+
+    // Create reservation for the client.
+    HostPtr host(new Host(&hwaddr_->hwaddr_[0], hwaddr_->hwaddr_.size(),
+                          Host::IDENT_HWADDR, subnet2_->getID(),
+                          SubnetID(0), IOAddress("10.2.3.23")));
+    CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
+    CfgMgr::instance().commit();
+
+    // Start allocation from subnet1. The engine should determine that the
+    // client has reservations in subnet2 and should rather assign reserved
+    // addresses.
+    AllocEngine::ClientContext4
+        ctx(subnet1_, ClientIdPtr(), hwaddr_, IOAddress::IPV4_ZERO_ADDRESS(),
+            false, false, "host.example.com.", true);
+    ctx.query_.reset(new Pkt4(DHCPDISCOVER, 1234));
+    AllocEngine::findReservation(ctx);
+    Lease4Ptr lease = engine_.allocateLease4(ctx);
+    ASSERT_TRUE(lease);
+    EXPECT_EQ("10.2.3.23", lease->addr_.toText());
+
+    // Let's create a lease for the client to make sure the lease is not
+    // renewed but a reserved lease is offerred.
+    Lease4Ptr lease2(new Lease4(IOAddress("192.0.2.17"), hwaddr_, ClientIdPtr(),
+                                501, 502, 503, time(NULL), subnet1_->getID()));
+    lease->cltt_ = time(NULL) - 10; // Allocated 10 seconds ago
+    ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease2));
+    ctx.subnet_ = subnet1_;
+    AllocEngine::findReservation(ctx);
+    lease = engine_.allocateLease4(ctx);
+    ASSERT_TRUE(lease);
+    EXPECT_EQ("10.2.3.23", lease->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(SharedNetworkAlloc4Test, runningOut) {
+
+    // Allocate everything in subnet1
+    insertLease("192.0.2.17", subnet1_->getID());
+
+    // Allocate everything, except one address in subnet2.
+    for (int i = 5; i < 100; i++) {
+        stringstream tmp;
+        tmp << "10.1.2." << 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 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.
+    AllocEngine::ClientContext4
+        ctx(subnet1_, ClientIdPtr(), hwaddr_, IOAddress::IPV4_ZERO_ADDRESS(),
+            false, false, "host.example.com.", true);
+    ctx.query_.reset(new Pkt4(DHCPDISCOVER, 1234));
+    Lease4Ptr lease = engine_.allocateLease4(ctx);
+    EXPECT_TRUE(lease);
+
+    // Now allocate the last address. Now both subnets are exhausted.
+    insertLease("10.1.2.100", subnet2_->getID());
+
+    // Ok, we're out. We should not get anything now.
+    lease = engine_.allocateLease4(ctx);
+    EXPECT_FALSE(lease);
+}
+
+// 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(SharedNetworkAlloc4Test, requestSharedNetworkSimple) {
+
+    // 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.
+    AllocEngine::ClientContext4
+        ctx(subnet1_, ClientIdPtr(), hwaddr_, IOAddress::IPV4_ZERO_ADDRESS(),
+            false, false, "host.example.com.", false);
+    ctx.query_.reset(new Pkt4(DHCPDISCOVER, 1234));
+    Lease4Ptr lease = engine_.allocateLease4(ctx);
+
+    // The allocation engine should have assigned an address from the first
+    // subnet.
+    ASSERT_TRUE(lease);
+    EXPECT_EQ("192.0.2.17", lease->addr_.toText());
+
+    // Make sure the lease is in the the lease mgr.
+    ASSERT_TRUE(LeaseMgrFactory::instance().getLease4(lease->addr_));
+}
+
+// This test verifies that the server can allocate an address from a
+// different subnet than orginally selected, when the address pool in
+// the first subnet is exhausted.
+TEST_F(SharedNetworkAlloc4Test, requestSharedNetwork) {
+
+    // Create a lease for a single address in the first address pool. The
+    // pool is now exhausted.
+    Lease4Ptr lease = insertLease("192.0.2.17", subnet1_->getID());
+
+    // 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.
+    AllocEngine::ClientContext4
+        ctx(subnet1_, ClientIdPtr(), hwaddr_, IOAddress::IPV4_ZERO_ADDRESS(),
+            false, false, "host.example.com.", false);
+    ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234));
+    Lease4Ptr lease2 = engine_.allocateLease4(ctx);
+    // The allocation engine should have assigned an address from the second
+    // subnet. We could guess that this is 10.1.2.5, being the first address
+    // in the address pool, but to make the test more generic, we merely
+    // verify that the address is in the given address pool.
+    ASSERT_TRUE(lease2);
+    EXPECT_TRUE(subnet2_->inPool(Lease::TYPE_V4, lease2->addr_));
+
+    ASSERT_TRUE(LeaseMgrFactory::instance().deleteLease(lease2->addr_));
+
+    // The client should also be assigned a lease when it specifies a hint
+    // that doesn't match the subnet from which the lease is offered. The
+    // engine should check alternative subnets to match the hint to
+    // a subnet. The requested lease is available, so it should be offered.
+    ctx.subnet_ = subnet1_;
+    ctx.requested_address_ = IOAddress("10.1.2.25");
+    lease2 = engine_.allocateLease4(ctx);
+    ASSERT_TRUE(lease2);
+    EXPECT_EQ("10.1.2.25", lease2->addr_.toText());
+
+    // The returning client (the one that has a lease) should also be able
+    // to renew its lease regardless of a subnet it begins with. So, it has
+    // an address assigned from subnet1, but we use subnet2 as a selected
+    // subnet.
+    AllocEngine::ClientContext4 ctx2(subnet2_, ClientIdPtr(), hwaddr2_,
+                                     IOAddress("0.0.0.0"), false, false,
+                                     "host.example.com.", false);
+    ctx2.query_.reset(new Pkt4(DHCPREQUEST, 1234));
+    lease2 = engine_.allocateLease4(ctx2);
+    // The existing lease should be returned.
+    ASSERT_TRUE(lease2);
+    EXPECT_EQ("192.0.2.17", lease2->addr_.toText());
+}
+
+// This test verifies that the server can assign an address from a
+// different subnet than orginally selected, when the address pool in
+// the first subnet is exhausted.
+TEST_F(SharedNetworkAlloc4Test, requestSharedNetworkClassification) {
+    // Try to offer address from subnet1. There is one address available
+    // so it should be offerred.
+    AllocEngine::ClientContext4
+        ctx(subnet1_, ClientIdPtr(), hwaddr_, IOAddress::IPV4_ZERO_ADDRESS(),
+            false, false, "host.example.com.", false);
+    ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234));
+    Lease4Ptr lease = engine_.allocateLease4(ctx);
+    ASSERT_TRUE(lease);
+    EXPECT_TRUE(subnet1_->inPool(Lease::TYPE_V4, lease->addr_));
+
+    // Remove the lease so as we can start over.
+    LeaseMgrFactory::instance().deleteLease(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 assign an address from subnet2 that belongs
+    // to the same shared network.
+    ctx.subnet_ = subnet1_;
+    lease = engine_.allocateLease4(ctx);
+    ASSERT_TRUE(lease);
+    EXPECT_TRUE(subnet2_->inPool(Lease::TYPE_V4, lease->addr_));
+
+    // Remove the lease so as we can start over.
+    LeaseMgrFactory::instance().deleteLease(lease->addr_);
+
+    // Assign cable-modem class and try again. This time, we should
+    // offer an address from the subnet1.
+    ctx.query_->addClass(ClientClass("cable-modem"));
+
+    ctx.subnet_ = subnet1_;
+    lease = engine_.allocateLease4(ctx);
+    ASSERT_TRUE(lease);
+    EXPECT_TRUE(subnet1_->inPool(Lease::TYPE_V4, lease->addr_));
+
+    // Let's now remove the client from the cable-modem class and try
+    // to renew the address. The engine should determine that the
+    // client doesn't have access to the subnet1 pools anymore and
+    // assign an address from unrestricted subnet.
+    ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234));
+    ctx.subnet_ = subnet1_;
+    lease = engine_.allocateLease4(ctx);
+    ASSERT_TRUE(lease);
+    EXPECT_TRUE(subnet2_->inPool(Lease::TYPE_V4, lease->addr_));
+}
+
+// Test that reservations within shared network take precedence over the
+// existing leases regardless in which subnet belonging to a shared network
+// reservations belong (DHCPREQUEST case).
+TEST_F(SharedNetworkAlloc4Test, requestSharedNetworkReservations) {
+
+    // Create reservation for the client.
+    HostPtr host(new Host(&hwaddr_->hwaddr_[0], hwaddr_->hwaddr_.size(),
+                          Host::IDENT_HWADDR, subnet2_->getID(),
+                          SubnetID(0), IOAddress("10.2.3.23")));
+    CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
+    CfgMgr::instance().commit();
+
+    // Start allocation from subnet1. The engine should determine that the
+    // client has reservations in subnet2 and should rather assign reserved
+    // addresses.
+    AllocEngine::ClientContext4
+        ctx(subnet1_, ClientIdPtr(), hwaddr_, IOAddress::IPV4_ZERO_ADDRESS(),
+            false, false, "host.example.com.", false);
+    ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234));
+    AllocEngine::findReservation(ctx);
+    Lease4Ptr lease = engine_.allocateLease4(ctx);
+    ASSERT_TRUE(lease);
+    EXPECT_EQ("10.2.3.23", lease->addr_.toText());
+
+    // Remove the lease for another test below.
+    ASSERT_TRUE(LeaseMgrFactory::instance().deleteLease(lease->addr_));
+
+    // Let's create a lease for the client to make sure the lease is not
+    // renewed but a reserved lease is allocated again.
+    Lease4Ptr lease2(new Lease4(IOAddress("192.0.2.17"), hwaddr_, ClientIdPtr(),
+                                501, 502, 503, time(NULL), subnet1_->getID()));
+    lease->cltt_ = time(NULL) - 10; // Allocated 10 seconds ago
+    ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease2));
+    ctx.subnet_ = subnet1_;
+    AllocEngine::findReservation(ctx);
+    lease = engine_.allocateLease4(ctx);
+    ASSERT_TRUE(lease);
+    EXPECT_EQ("10.2.3.23", lease->addr_.toText());
+}
+
 // This test checks if an expired lease can be reused in DHCPDISCOVER (fake
 // allocation)
 TEST_F(AllocEngine4Test, discoverReuseExpiredLease4) {
     boost::scoped_ptr<AllocEngine> engine;
     ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE,
-                                                 100, false)));
+                                                 0, false)));
     ASSERT_TRUE(engine);
 
     IOAddress addr("192.0.2.15");
@@ -548,7 +1002,7 @@ TEST_F(AllocEngine4Test, discoverReuseExpiredLease4) {
 TEST_F(AllocEngine4Test, requestReuseExpiredLease4) {
     boost::scoped_ptr<AllocEngine> engine;
     ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE,
-                                                 100, false)));
+                                                 0, false)));
     ASSERT_TRUE(engine);
 
     IOAddress addr("192.0.2.105");
@@ -610,7 +1064,7 @@ TEST_F(AllocEngine4Test, requestReuseExpiredLease4) {
 TEST_F(AllocEngine4Test, discoverReuseDeclinedLease4) {
 
     AllocEnginePtr engine(new AllocEngine(AllocEngine::ALLOC_ITERATIVE,
-                                          100, false));
+                                          0, false));
     ASSERT_TRUE(engine);
 
     // Now prepare a configuration with single address pool.
@@ -648,7 +1102,7 @@ TEST_F(AllocEngine4Test, discoverReuseDeclinedLease4Stats) {
 
     // Now prepare for DISCOVER processing
     AllocEnginePtr engine(new AllocEngine(AllocEngine::ALLOC_ITERATIVE,
-                                          100, false));
+                                          0, false));
     ASSERT_TRUE(engine);
 
     // Now prepare a configuration with single address pool.
@@ -683,7 +1137,7 @@ TEST_F(AllocEngine4Test, discoverReuseDeclinedLease4Stats) {
 TEST_F(AllocEngine4Test, requestReuseDeclinedLease4) {
 
     AllocEnginePtr engine(new AllocEngine(AllocEngine::ALLOC_ITERATIVE,
-                                          100, false));
+                                          0, false));
     ASSERT_TRUE(engine);
 
     // Now prepare a configuration with single address pool.
@@ -719,7 +1173,7 @@ TEST_F(AllocEngine4Test, requestReuseDeclinedLease4) {
 TEST_F(AllocEngine4Test, requestReuseDeclinedLease4Stats) {
 
     AllocEnginePtr engine(new AllocEngine(AllocEngine::ALLOC_ITERATIVE,
-                                          100, false));
+                                          0, false));
     ASSERT_TRUE(engine);
 
     // Now prepare a configuration with single address pool.
@@ -761,7 +1215,7 @@ TEST_F(AllocEngine4Test, identifyClientLease) {
                                100, 30, 60, time(NULL), subnet_->getID()));
     LeaseMgrFactory::instance().addLease(lease);
 
-    AllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 100, false);
+    AllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 0, false);
     AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_,
                                     IOAddress::IPV4_ZERO_ADDRESS(),
                                     false, false, "", true);
@@ -840,7 +1294,7 @@ TEST_F(AllocEngine4Test, requestOtherClientLease) {
     LeaseMgrFactory::instance().addLease(lease);
     LeaseMgrFactory::instance().addLease(lease2);
 
-    AllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 100, false);
+    AllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 0, false);
 
     // First client requests the lease which belongs to the second client.
     AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_, IOAddress("192.0.2.102"),
@@ -878,7 +1332,7 @@ TEST_F(AllocEngine4Test, reservedAddressNoHint) {
     CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
     CfgMgr::instance().commit();
 
-    AllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 100, false);
+    AllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 0, false);
 
     // Try to allocate a lease without specifying a hint. This is actually
     // incorrect behavior of the client to not send an address it wants to
@@ -917,7 +1371,7 @@ TEST_F(AllocEngine4Test,reservedAddressNoHintFakeAllocation) {
     CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
     CfgMgr::instance().commit();
 
-    AllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 100, false);
+    AllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 0, false);
 
     // Query allocation engine for the lease to be assigned to this
     // client without specifying the address to be assigned.
@@ -958,7 +1412,7 @@ TEST_F(AllocEngine4Test, reservedAddressHint) {
     CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
     CfgMgr::instance().commit();
 
-    AllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 100, false);
+    AllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 0, false);
 
     AllocEngine::ClientContext4 ctx1(subnet_, clientid_, hwaddr_,
                                     IOAddress("192.0.2.234"), false, false,
@@ -1007,7 +1461,7 @@ TEST_F(AllocEngine4Test, reservedAddressHintFakeAllocation) {
     CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
     CfgMgr::instance().commit();
 
-    AllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 100, false);
+    AllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 0, false);
 
     // Query the allocation engine for the lease to be assigned to the client
     // and specify a hint being a different address than the reserved one.
@@ -1055,7 +1509,7 @@ TEST_F(AllocEngine4Test, reservedAddressExistingLease) {
                                false, false, ""));
     LeaseMgrFactory::instance().addLease(lease);
 
-    AllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 100, false);
+    AllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 0, false);
 
     // Request allocation of the reserved address.
     AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_,
@@ -1104,7 +1558,7 @@ TEST_F(AllocEngine4Test, reservedAddressHijacked) {
                                false, false, ""));
     LeaseMgrFactory::instance().addLease(lease);
 
-    AllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 100, false);
+    AllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 0, false);
 
     // Try to allocate the reserved lease to client B.
     AllocEngine::ClientContext4 ctx1(subnet_, clientid_, hwaddr_,
@@ -1161,7 +1615,7 @@ TEST_F(AllocEngine4Test, reservedAddressHijackedFakeAllocation) {
                                false, false, ""));
     LeaseMgrFactory::instance().addLease(lease);
 
-    AllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 100, false);
+    AllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 0, false);
 
     // Query allocation engine for the lease to be allocated to the client B.
     // The allocation engine is not able to allocate the lease to the client
@@ -1221,7 +1675,7 @@ TEST_F(AllocEngine4Test, reservedAddressExistingLeaseInvalidHint) {
                                false, false, ""));
     LeaseMgrFactory::instance().addLease(lease);
 
-    AllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 100, false);
+    AllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 0, false);
 
     // Try to allocate a lease and specify a different address than reserved
     // and different from the one that client is currently using.
@@ -1277,7 +1731,7 @@ TEST_F(AllocEngine4Test, reservedAddressExistingLeaseFakeAllocation) {
                                false, false, ""));
     LeaseMgrFactory::instance().addLease(lease);
 
-    AllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 100, false);
+    AllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 0, false);
 
     // Try to allocate a lease and use a completely different address
     // as a hint.
@@ -1342,7 +1796,7 @@ TEST_F(AllocEngine4Test, reservedAddressExistingLeaseNoHint) {
                                false, false, ""));
     LeaseMgrFactory::instance().addLease(lease);
 
-    AllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 100, false);
+    AllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 0, false);
 
     // Try to allocate a lease with providing no hint.
     AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_,
@@ -1394,7 +1848,7 @@ TEST_F(AllocEngine4Test, reservedAddressExistingLeaseNoHintFakeAllocation) {
                                false, false, ""));
     LeaseMgrFactory::instance().addLease(lease);
 
-    AllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 100, false);
+    AllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 0, false);
 
     // Query the allocation engine for the lease to be allocated for the
     // client.
@@ -1458,7 +1912,7 @@ TEST_F(AllocEngine4Test, reservedAddressConflictResolution) {
                                false, false, ""));
     LeaseMgrFactory::instance().addLease(lease);
 
-    AllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 100, false);
+    AllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 0, false);
 
 
     // Client B sends a DHCPREQUEST to allocate a reserved lease. The
@@ -1542,7 +1996,7 @@ TEST_F(AllocEngine4Test, reservedAddressVsDynamicPool) {
     CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
     CfgMgr::instance().commit();
 
-    AllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 100, false);
+    AllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 0, false);
 
     // Different client tries to allocate a lease. Note, that we're using
     // an iterative allocator which would pick the first address from the
@@ -1574,7 +2028,7 @@ TEST_F(AllocEngine4Test, reservedAddressHintUsedByOtherClient) {
     CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
     CfgMgr::instance().commit();
 
-    AllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 100, false);
+    AllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 0, false);
 
     // Different client is requesting this address.
     AllocEngine::ClientContext4 ctx1(subnet_, ClientIdPtr(), hwaddr_,
@@ -1605,7 +2059,7 @@ TEST_F(AllocEngine4Test, reservedAddressHintUsedByOtherClient) {
 // address when the pool is exhausted, and the only available
 // address is reserved for a different client.
 TEST_F(AllocEngine4Test, reservedAddressShortPool) {
-    AllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 100, false);
+    AllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 0, false);
 
     // Create short pool with only one address.
     initSubnet(IOAddress("192.0.2.100"), IOAddress("192.0.2.100"));
@@ -1647,7 +2101,7 @@ TEST_F(AllocEngine4Test, reservedAddressShortPool) {
 // dynamic pool if the client's reservation is made for a hostname but
 // not for an address.
 TEST_F(AllocEngine4Test, reservedHostname) {
-    AllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 100, false);
+    AllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 0, false);
 
     // Create a reservation for a hostname. Address is set to 0 which
     // indicates that there is no reservation.
@@ -1682,18 +2136,19 @@ TEST_F(AllocEngine4Test, reservedHostname) {
 // the value of NULL in the host_ field of the client context.
 TEST_F(AllocEngine4Test, findReservation) {
     // Create the instance of the allocation engine.
-    AllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 100, false);
+    AllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 0, false);
 
     // Context is required to call the AllocEngine::findReservation.
     AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_,
                                     IOAddress("0.0.0.0"), false, false,
                                     "", false);
+    ctx.query_.reset(new Pkt4(DHCPDISCOVER, 1234));
     ctx.addHostIdentifier(Host::IDENT_HWADDR, hwaddr_->hwaddr_);
     ctx.addHostIdentifier(Host::IDENT_DUID, clientid_->getDuid());
 
     // There is no reservation in the database so no host should be returned.
     ASSERT_NO_THROW(engine.findReservation(ctx));
-    EXPECT_FALSE(ctx.host_);
+    EXPECT_FALSE(ctx.currentHost());
 
     // Create a reservation for the client.
     HostPtr host(new Host(&hwaddr_->hwaddr_[0], hwaddr_->hwaddr_.size(),
@@ -1704,21 +2159,21 @@ TEST_F(AllocEngine4Test, findReservation) {
 
     // This time the reservation should be returned.
     ASSERT_NO_THROW(engine.findReservation(ctx));
-    EXPECT_TRUE(ctx.host_);
-    EXPECT_EQ(ctx.host_->getIPv4Reservation(), host->getIPv4Reservation());
+    EXPECT_TRUE(ctx.currentHost());
+    EXPECT_EQ(ctx.currentHost()->getIPv4Reservation(), host->getIPv4Reservation());
 
     // Regardless of the host reservation mode, the host should be
     // always returned when findReservation() is called.
     subnet_->setHostReservationMode(Network::HR_DISABLED);
     ASSERT_NO_THROW(engine.findReservation(ctx));
-    EXPECT_TRUE(ctx.host_);
-    EXPECT_EQ(ctx.host_->getIPv4Reservation(), host->getIPv4Reservation());
+    EXPECT_TRUE(ctx.currentHost());
+    EXPECT_EQ(ctx.currentHost()->getIPv4Reservation(), host->getIPv4Reservation());
 
     // Check the third possible reservation mode.
     subnet_->setHostReservationMode(Network::HR_OUT_OF_POOL);
     ASSERT_NO_THROW(engine.findReservation(ctx));
-    EXPECT_TRUE(ctx.host_);
-    EXPECT_EQ(ctx.host_->getIPv4Reservation(), host->getIPv4Reservation());
+    EXPECT_TRUE(ctx.currentHost());
+    EXPECT_EQ(ctx.currentHost()->getIPv4Reservation(), host->getIPv4Reservation());
 
     // This time use the client identifier to search for the host.
     host.reset(new Host(&clientid_->getClientId()[0],
@@ -1729,14 +2184,14 @@ TEST_F(AllocEngine4Test, findReservation) {
     CfgMgr::instance().commit();
 
     ASSERT_NO_THROW(engine.findReservation(ctx));
-    EXPECT_TRUE(ctx.host_);
-    EXPECT_EQ(ctx.host_->getIPv4Reservation(), host->getIPv4Reservation());
+    EXPECT_TRUE(ctx.currentHost());
+    EXPECT_EQ(ctx.currentHost()->getIPv4Reservation(), host->getIPv4Reservation());
 
     // Remove the subnet. Subnet id is required to find host reservations, so
     // if it is set to NULL, no reservation should be returned
     ctx.subnet_.reset();
     ASSERT_NO_THROW(engine.findReservation(ctx));
-    EXPECT_FALSE(ctx.host_);
+    EXPECT_FALSE(ctx.currentHost());
 
     // The same if there is a mismatch of the subnet id between the reservation
     // and the context.
@@ -1748,7 +2203,7 @@ TEST_F(AllocEngine4Test, findReservation) {
     CfgMgr::instance().commit();
 
     ASSERT_NO_THROW(engine.findReservation(ctx));
-    EXPECT_FALSE(ctx.host_);
+    EXPECT_FALSE(ctx.currentHost());
 }
 
 // This test checks if the simple IPv4 allocation can succeed and that
@@ -1756,7 +2211,7 @@ TEST_F(AllocEngine4Test, findReservation) {
 TEST_F(AllocEngine4Test, simpleAlloc4Stats) {
     boost::scoped_ptr<AllocEngine> engine;
     ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE,
-                                                 100, false)));
+                                                 0, false)));
     ASSERT_TRUE(engine);
 
     AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_, IOAddress("0.0.0.0"),
@@ -1845,8 +2300,8 @@ TEST_F(AllocEngine4Test, reservedAddressExistingLeaseStat) {
     AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_,
                                     IOAddress("192.0.2.123"), false, false,
                                     "", false);
-    AllocEngine::findReservation(ctx);
     ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234));
+    AllocEngine::findReservation(ctx);
 
     Lease4Ptr allocated_lease = engine.allocateLease4(ctx);
 

+ 2 - 2
src/lib/dhcpsrv/tests/alloc_engine_utils.cc

@@ -178,8 +178,8 @@ AllocEngine6Test::findReservation(AllocEngine& engine,
     AllocEngine::ClientContext6& ctx) {
     engine.findReservation(ctx);
     // Let's check whether there's a hostname specified in the reservation
-    if (ctx.host_) {
-        std::string hostname = ctx.host_->getHostname();
+    if (ctx.currentHost()) {
+        std::string hostname = ctx.currentHost()->getHostname();
         // If there is, let's use it
         if (!hostname.empty()) {
             ctx.hostname_ = hostname;

+ 204 - 0
src/lib/dhcpsrv/tests/cfg_subnets4_unittest.cc

@@ -7,6 +7,7 @@
 #include <config.h>
 #include <dhcp/classify.h>
 #include <dhcp/tests/iface_mgr_test_config.h>
+#include <dhcpsrv/shared_network.h>
 #include <dhcpsrv/cfg_subnets4.h>
 #include <dhcpsrv/subnet.h>
 #include <dhcpsrv/subnet_id.h>
@@ -179,6 +180,78 @@ TEST(CfgSubnets4Test, selectSubnetByIface) {
     EXPECT_EQ(subnet3, selected);
 }
 
+// This test verifies that it is possible to select subnet by interface
+// name specified on the shared network level.
+TEST(CfgSubnets4Test, selectSharedNetworkByIface) {
+    // The IfaceMgrTestConfig object initializes fake interfaces:
+    // eth0, eth1 and lo on the configuration manager. The CfgSubnets4
+    // object uses interface names to select the appropriate subnet.
+    IfaceMgrTestConfig config(true);
+
+    CfgSubnets4 cfg;
+
+    // Create 3 subnets.
+    Subnet4Ptr subnet1(new Subnet4(IOAddress("172.16.2.0"), 24, 1, 2, 3,
+                                   SubnetID(1)));
+    Subnet4Ptr subnet2(new Subnet4(IOAddress("10.1.2.0"), 24, 1, 2, 3,
+                                   SubnetID(2)));
+    Subnet4Ptr subnet3(new Subnet4(IOAddress("192.3.4.0"), 24, 1, 2, 3,
+                                   SubnetID(3)));
+    subnet2->setIface("lo");
+
+    cfg.add(subnet1);
+    cfg.add(subnet2);
+    cfg.add(subnet3);
+
+    SharedNetwork4Ptr network(new SharedNetwork4("network_eth1"));
+    network->setIface("eth1");
+    ASSERT_NO_THROW(network->add(subnet1));
+    ASSERT_NO_THROW(network->add(subnet2));
+
+    // Make sure that initially the subnets don't exist.
+    SubnetSelector selector;
+    // Set an interface to a name that is not defined in the config.
+    // Subnet selection should fail.
+    selector.iface_name_ = "eth0";
+    ASSERT_FALSE(cfg.selectSubnet(selector));
+
+    // Now select an interface name that matches. Selection should succeed
+    // and return subnet3.
+    selector.iface_name_ = "eth1";
+    Subnet4Ptr selected = cfg.selectSubnet(selector);
+    ASSERT_TRUE(selected);
+    SharedNetwork4Ptr network_returned;
+    selected->getSharedNetwork(network_returned);
+    ASSERT_TRUE(network_returned);
+    EXPECT_EQ(network, network_returned);
+
+    const Subnet4Collection* subnets_eth1 = network_returned->getAllSubnets();
+    EXPECT_EQ(2, subnets_eth1->size());
+    ASSERT_TRUE(network_returned->getSubnet(SubnetID(1)));
+    ASSERT_TRUE(network_returned->getSubnet(SubnetID(2)));
+
+    // Make sure that it is still possible to select subnet2 which is
+    // outside of a shared network.
+    selector.iface_name_ = "lo";
+    selected = cfg.selectSubnet(selector);
+    ASSERT_TRUE(selected);
+    EXPECT_EQ(2, selected->getID());
+
+    // Try selecting by eth1 again, but this time set subnet specific
+    // interface name to eth0. Subnet selection should fail.
+    selector.iface_name_ = "eth1";
+    subnet1->setIface("eth0");
+    subnet3->setIface("eth0");
+    selected = cfg.selectSubnet(selector);
+    ASSERT_FALSE(selected);
+
+    // It should be possible to select by eth0, though.
+    selector.iface_name_ = "eth0";
+    selected = cfg.selectSubnet(selector);
+    ASSERT_TRUE(selected);
+    EXPECT_EQ(subnet1, selected);
+}
+
 // This test verifies that when the classification information is specified for
 // subnets, the proper subnets are returned by the subnet configuration.
 TEST(CfgSubnets4Test, selectSubnetByClasses) {
@@ -253,6 +326,63 @@ TEST(CfgSubnets4Test, selectSubnetByClasses) {
     EXPECT_FALSE(cfg.selectSubnet(selector));
 }
 
+// This test verifies that shared network can be selected based on client
+// classification.
+TEST(CfgSubnets4Test, selectSharedNetworkByClasses) {
+    IfaceMgrTestConfig config(true);
+
+    CfgSubnets4 cfg;
+
+    // Create 3 subnets.
+    Subnet4Ptr subnet1(new Subnet4(IOAddress("192.0.2.0"), 26, 1, 2, 3));
+    Subnet4Ptr subnet2(new Subnet4(IOAddress("192.0.2.64"), 26, 1, 2, 3));
+    Subnet4Ptr subnet3(new Subnet4(IOAddress("192.0.2.128"), 26, 1, 2, 3));
+
+    // Add them to the configuration.
+    cfg.add(subnet1);
+    cfg.add(subnet2);
+    cfg.add(subnet3);
+
+    // Create first network and add first two subnets to it.
+    SharedNetwork4Ptr network1(new SharedNetwork4("network1"));
+    network1->setIface("eth1");
+    network1->allowClientClass("device-type1");
+    ASSERT_NO_THROW(network1->add(subnet1));
+    ASSERT_NO_THROW(network1->add(subnet2));
+
+    // Create second network and add last subnet there.
+    SharedNetwork4Ptr network2(new SharedNetwork4("network2"));
+    network2->setIface("eth1");
+    network2->allowClientClass("device-type2");
+    ASSERT_NO_THROW(network2->add(subnet3));
+
+    // Use interface name as a selector. This guarantees that subnet
+    // selection will be made based on the classification.
+    SubnetSelector selector;
+    selector.iface_name_ = "eth1";
+
+    // If the client has "device-type2" class, it is expected that the
+    // second network will be used. This network has only one subnet
+    // in it, i.e. subnet3.
+    ClientClasses client_classes;
+    client_classes.insert("device-type2");
+    selector.client_classes_ = client_classes;
+    EXPECT_EQ(subnet3, cfg.selectSubnet(selector));
+
+    // Switch to device-type1 and expect that we assigned a subnet from
+    // another shared network.
+    client_classes.clear();
+    client_classes.insert("device-type1");
+    selector.client_classes_ = client_classes;
+
+    Subnet4Ptr subnet = cfg.selectSubnet(selector);
+    ASSERT_TRUE(subnet);
+    SharedNetwork4Ptr network;
+    subnet->getSharedNetwork(network);
+    ASSERT_TRUE(network);
+    EXPECT_EQ("network1", network->getName());
+}
+
 // This test verifies the option selection can be used and is only
 // used when present.
 TEST(CfgSubnets4Test, selectSubnetByOptionSelect) {
@@ -329,6 +459,80 @@ TEST(CfgSubnets4Test, selectSubnetByRelayAddress) {
     EXPECT_EQ(subnet3, cfg.selectSubnet(selector));
 }
 
+// This test verifies that the relay information specified on the shared
+// network level can be used to select a subnet.
+TEST(CfgSubnets4Test, selectSharedNetworkByRelayAddressNetworkLevel) {
+    CfgSubnets4 cfg;
+
+    // Create 3 subnets.
+    Subnet4Ptr subnet1(new Subnet4(IOAddress("192.0.2.0"), 26, 1, 2, 3));
+    Subnet4Ptr subnet2(new Subnet4(IOAddress("192.0.2.64"), 26, 1, 2, 3));
+    Subnet4Ptr subnet3(new Subnet4(IOAddress("192.0.2.128"), 26, 1, 2, 3));
+
+    // Add them to the configuration.
+    cfg.add(subnet1);
+    cfg.add(subnet2);
+    cfg.add(subnet3);
+
+    SharedNetwork4Ptr network(new SharedNetwork4("network"));
+    network->add(subnet2);
+
+    SubnetSelector selector;
+
+    // Now specify relay info. Note that for the second subnet we specify
+    // relay info on the network level.
+    subnet1->setRelayInfo(IOAddress("10.0.0.1"));
+    network->setRelayInfo(IOAddress("10.0.0.2"));
+    subnet3->setRelayInfo(IOAddress("10.0.0.3"));
+
+    // And try again. This time relay-info is there and should match.
+    selector.giaddr_ = IOAddress("10.0.0.1");
+    EXPECT_EQ(subnet1, cfg.selectSubnet(selector));
+    selector.giaddr_ = IOAddress("10.0.0.2");
+    EXPECT_EQ(subnet2, cfg.selectSubnet(selector));
+    selector.giaddr_ = IOAddress("10.0.0.3");
+    EXPECT_EQ(subnet3, cfg.selectSubnet(selector));
+}
+
+// This test verifies that the relay information specified on the subnet
+// level can be used to select a subnet and the fact that a subnet belongs
+// to a shared network doesn't affect the process.
+TEST(CfgSubnets4Test, selectSharedNetworkByRelayAddressSubnetLevel) {
+    CfgSubnets4 cfg;
+
+    // Create 3 subnets.
+    Subnet4Ptr subnet1(new Subnet4(IOAddress("192.0.2.0"), 26, 1, 2, 3));
+    Subnet4Ptr subnet2(new Subnet4(IOAddress("192.0.2.64"), 26, 1, 2, 3));
+    Subnet4Ptr subnet3(new Subnet4(IOAddress("192.0.2.128"), 26, 1, 2, 3));
+
+    // Add them to the configuration.
+    cfg.add(subnet1);
+    cfg.add(subnet2);
+    cfg.add(subnet3);
+
+    SharedNetwork4Ptr network1(new SharedNetwork4("network1"));
+    network1->add(subnet1);
+
+    SharedNetwork4Ptr network2(new SharedNetwork4("network2"));
+    network2->add(subnet2);
+
+    SubnetSelector selector;
+
+    // Now specify relay info. Note that for the second subnet we specify
+    // relay info on the network level.
+    subnet1->setRelayInfo(IOAddress("10.0.0.1"));
+    subnet2->setRelayInfo(IOAddress("10.0.0.2"));
+    subnet3->setRelayInfo(IOAddress("10.0.0.3"));
+
+    // And try again. This time relay-info is there and should match.
+    selector.giaddr_ = IOAddress("10.0.0.1");
+    EXPECT_EQ(subnet1, cfg.selectSubnet(selector));
+    selector.giaddr_ = IOAddress("10.0.0.2");
+    EXPECT_EQ(subnet2, cfg.selectSubnet(selector));
+    selector.giaddr_ = IOAddress("10.0.0.3");
+    EXPECT_EQ(subnet3, cfg.selectSubnet(selector));
+}
+
 // This test verifies that the subnet can be selected for the client
 // using a source address if the client hasn't set the ciaddr.
 TEST(CfgSubnets4Test, selectSubnetNoCiaddr) {

+ 2 - 2
src/lib/dhcpsrv/tests/shared_network_unittest.cc

@@ -164,7 +164,7 @@ TEST(SharedNetwork4Test, getNextSubnet) {
         // Iterate over the subnets starting from the subnet with index i.
         for (auto j = 0; j < subnets.size(); ++j) {
             // Get next subnet (following the one currently in s).
-            s = networks[0]->getNextSubnet(subnets[i], s);
+            s = networks[0]->getNextSubnet(subnets[i], s->getID());
             // The last iteration should return empty pointer to indicate end of
             // the subnets within shared network. If we're not at last iteration
             // check that the subnet identifier of the returned subnet is valid.
@@ -426,7 +426,7 @@ TEST(SharedNetwork6Test, getNextSubnet) {
         // Iterate over the subnets starting from the subnet with index i.
         for (auto j = 0; j < subnets.size(); ++j) {
             // Get next subnet (following the one currently in s).
-            s = networks[0]->getNextSubnet(subnets[i], s);
+            s = networks[0]->getNextSubnet(subnets[i], s->getID());
             // The last iteration should return empty pointer to indicate end of
             // the subnets within shared network. If we're not at last iteration
             // check that the subnet identifier of the returned subnet is valid.

+ 43 - 0
src/lib/dhcpsrv/tests/subnet_unittest.cc

@@ -10,6 +10,7 @@
 #include <dhcp/option.h>
 #include <dhcp/dhcp6.h>
 #include <dhcp/option_space.h>
+#include <dhcpsrv/shared_network.h>
 #include <dhcpsrv/subnet.h>
 #include <exceptions/exceptions.h>
 
@@ -292,6 +293,13 @@ TEST(Subnet4Test, clientClasses) {
     three_classes.insert("bar");
     three_classes.insert("baz");
 
+    // This client belongs to foo, bar, baz and network classes.
+    isc::dhcp::ClientClasses four_classes;
+    four_classes.insert("foo");
+    four_classes.insert("bar");
+    four_classes.insert("baz");
+    four_classes.insert("network");
+
     // No class restrictions defined, any client should be supported
     EXPECT_EQ(0, subnet->getClientClasses().size());
     EXPECT_TRUE(subnet->clientSupported(no_class));
@@ -307,6 +315,20 @@ TEST(Subnet4Test, clientClasses) {
     EXPECT_FALSE(subnet->clientSupported(foo_class));
     EXPECT_TRUE(subnet->clientSupported(bar_class));
     EXPECT_TRUE(subnet->clientSupported(three_classes));
+
+    // Add shared network which can only be selected when the client
+    // class is "network".
+    SharedNetwork4Ptr network(new SharedNetwork4("network"));
+    network->allowClientClass("network");
+    ASSERT_NO_THROW(network->add(subnet));
+
+    // This time, if the client doesn't support network class,
+    // the subnets from the shared network can't be selected.
+    EXPECT_FALSE(subnet->clientSupported(bar_class));
+    EXPECT_FALSE(subnet->clientSupported(three_classes));
+
+    // If the classes include "network", the subnet is selected.
+    EXPECT_TRUE(subnet->clientSupported(four_classes));
 }
 
 // Tests whether Subnet4 object is able to store and process properly
@@ -743,6 +765,13 @@ TEST(Subnet6Test, clientClasses) {
     three_classes.insert("bar");
     three_classes.insert("baz");
 
+    // This client belongs to foo, bar, baz and network classes.
+    isc::dhcp::ClientClasses four_classes;
+    four_classes.insert("foo");
+    four_classes.insert("bar");
+    four_classes.insert("baz");
+    four_classes.insert("network");
+
     // No class restrictions defined, any client should be supported
     EXPECT_EQ(0, subnet->getClientClasses().size());
     EXPECT_TRUE(subnet->clientSupported(no_class));
@@ -758,6 +787,20 @@ TEST(Subnet6Test, clientClasses) {
     EXPECT_FALSE(subnet->clientSupported(foo_class));
     EXPECT_TRUE(subnet->clientSupported(bar_class));
     EXPECT_TRUE(subnet->clientSupported(three_classes));
+
+    // Add shared network which can only be selected when the client
+    // class is "network".
+    SharedNetwork6Ptr network(new SharedNetwork6("network"));
+    network->allowClientClass("network");
+    ASSERT_NO_THROW(network->add(subnet));
+
+    // This time, if the client doesn't support network class,
+    // the subnets from the shared network can't be selected.
+    EXPECT_FALSE(subnet->clientSupported(bar_class));
+    EXPECT_FALSE(subnet->clientSupported(three_classes));
+
+    // If the classes include "network", the subnet is selected.
+    EXPECT_TRUE(subnet->clientSupported(four_classes));
 }
 
 // Tests whether Subnet6 object is able to store and process properly