]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[5306] Updated DHCPv4 server to use shared networks.
authorMarcin Siodelski <marcin@isc.org>
Mon, 18 Sep 2017 08:46:31 +0000 (10:46 +0200)
committerMarcin Siodelski <marcin@isc.org>
Mon, 18 Sep 2017 08:49:49 +0000 (10:49 +0200)
src/bin/dhcp4/dhcp4_messages.mes
src/bin/dhcp4/dhcp4_srv.cc
src/bin/dhcp4/dhcp4_srv.h
src/bin/dhcp4/tests/shared_network_unittest.cc

index 9a8421de92fc5743c39428b2660b2ed94c8301de..a81068b8dbb969d91aecdd34baa4f1ed9f79b8e7 100644 (file)
@@ -320,14 +320,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 +523,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
index 6c75e2fa0f26e51b5633b4ab7d2868a67e27a691..cde545d73250c3de2e70bbdf7381ef3fec910994 100644 (file)
@@ -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>
@@ -155,9 +156,6 @@ Dhcpv4Exchange::Dhcpv4Exchange(const AllocEnginePtr& alloc_engine,
 
             // Check for static reservations.
             alloc_engine->findReservation(*context_);
-
-            // Assign classes.
-            setReservedClientClasses();
         }
     }
 
@@ -1177,6 +1175,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();
@@ -1741,12 +1746,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, s->getID());
+            }
 
-        if (!lease && hwaddr) {
-            lease = LeaseMgrFactory::instance().getLease4(*hwaddr, subnet->getID());
+            if (lease ) {
+                break;
+
+            } else {
+                s = s->getNextSubnet(original_subnet, query->getClasses());
+            }
         }
 
         // Check the first error case: unknown client. We check this before
@@ -1820,6 +1836,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.
@@ -1841,49 +1861,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()) {
-
-            // 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_);
+        // 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_);
 
-            // 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);
-                }
+                should_update = true;
+            }
 
-                // 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_);
-                }
+            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);
+                    }
 
-            } catch (const Exception& ex) {
-                LOG_ERROR(ddns4_logger, DHCP4_NAME_GEN_UPDATE_FAIL)
-                    .arg(query->getLabel())
-                    .arg(lease->hostname_)
-                    .arg(ex.what());
+                    // 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());
+                }
             }
         }
 
@@ -2204,6 +2246,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);
@@ -2256,6 +2301,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);
@@ -2540,6 +2588,8 @@ Dhcpv4Srv::processInform(Pkt4Ptr& inform) {
 
     Pkt4Ptr ack = ex.getResponse();
 
+    ex.setReservedClientClasses();
+
     buildCfgOptionList(ex);
     appendRequestedOptions(ex);
     appendRequestedVendorOptions(ex);
index c2eec9b0de1c2444dcb5884da6eedacc5e0d07bb..7ea76992963d5edd07461f7aa05daff496f7238a 100644 (file)
@@ -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.
index 6746f09dc46ed33f7a97fe90977bb3789d5d5f98..70d86dd6991030f7a1d0336278cce938931268aa 100644 (file)
@@ -5,10 +5,17 @@
 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 #include <config.h>
+#include <asiolink/io_address.h>
+#include <dhcp/dhcp4.h>
 #include <dhcp/tests/iface_mgr_test_config.h>
+#include <dhcp/option.h>
+#include <dhcp/option_int.h>
+#include <dhcp/option_string.h>
 #include <dhcp4/tests/dhcp4_client.h>
 #include <dhcp4/tests/dhcp4_test_utils.h>
 #include <stats/stats_mgr.h>
+#include <boost/pointer_cast.hpp>
+#include <boost/shared_ptr.hpp>
 
 using namespace isc;
 using namespace isc::asiolink;
@@ -18,49 +25,581 @@ using namespace isc::stats;
 
 namespace {
 
+/// @brief Array of server configurations used throughout the tests.
 const char* NETWORKS_CONFIG[] = {
+// Configuration #0.
     "{"
     "    \"interfaces-config\": {"
     "        \"interfaces\": [ \"*\" ]"
-    "},"
-    "\"valid-lifetime\": 600,"
-    "\"shared-networks\": ["
-    "    {"
-    "        \"name\": \"frog\""
-    "        \"subnet4\": ["
-    "            {"
-    "                \"subnet\": \"192.0.2.0/26\","
-    "                \"id\": 10,"
-    "                \"pools\": ["
-    "                    {"
-    "                        \"pool\": \"192.0.2.1 - 192.0.2.63\""
-    "                    }"
-    "                ]"
+    "    },"
+    "    \"valid-lifetime\": 600,"
+    "    \"shared-networks\": ["
+    "        {"
+    "            \"name\": \"frog\","
+    "            \"interface\": \"eth1\","
+    "            \"subnet4\": ["
+    "                {"
+    "                    \"subnet\": \"192.0.2.0/26\","
+    "                    \"id\": 10,"
+    "                    \"pools\": ["
+    "                        {"
+    "                            \"pool\": \"192.0.2.63 - 192.0.2.63\""
+    "                        }"
+    "                    ]"
+    "                },"
+    "                {"
+    "                    \"subnet\": \"10.0.0.0/24\","
+    "                    \"id\": 100,"
+    "                    \"pools\": ["
+    "                        {"
+    "                            \"pool\": \"10.0.0.16 - 10.0.0.16\""
+    "                        }"
+    "                    ]"
+    "                }"
+    "            ]"
+    "        }"
+    "    ],"
+    "    \"subnet4\": ["
+    "        {"
+    "            \"subnet\": \"192.0.2.64/26\","
+    "            \"id\": 1000,"
+    "            \"interface\": \"eth0\","
+    "            \"pools\": ["
+    "                {"
+    "                    \"pool\": \"192.0.2.65 - 192.0.2.65\""
+    "                }"
+    "            ]"
+    "        }"
+    "    ]"
+    "}",
+
+// Configuration #1.
+    "{"
+    "    \"interfaces-config\": {"
+    "        \"interfaces\": [ \"*\" ]"
+    "    },"
+    "    \"valid-lifetime\": 600,"
+    "    \"shared-networks\": ["
+    "        {"
+    "            \"name\": \"frog\","
+    "            \"relay\": {"
+    "                \"ip-address\": \"192.3.5.6\""
+    "            },"
+    "            \"subnet4\": ["
+    "                {"
+    "                    \"subnet\": \"192.0.2.0/26\","
+    "                    \"id\": 10,"
+    "                    \"pools\": ["
+    "                        {"
+    "                            \"pool\": \"192.0.2.63 - 192.0.2.63\""
+    "                        }"
+    "                    ]"
+    "                }"
+    "            ]"
+    "        }"
+    "    ],"
+    "    \"subnet4\": ["
+    "        {"
+    "            \"subnet\": \"192.0.2.64/26\","
+    "            \"id\": 1000,"
+    "            \"relay\": {"
+    "                \"ip-address\": \"192.1.2.3\""
+    "            },"
+    "            \"pools\": ["
+    "                {"
+    "                    \"pool\": \"192.0.2.65 - 192.0.2.65\""
+    "                }"
+    "            ]"
+    "        }"
+    "    ]"
+    "}",
+
+// Configuration #2.
+    "{"
+    "    \"interfaces-config\": {"
+    "        \"interfaces\": [ \"*\" ]"
+    "    },"
+    "    \"valid-lifetime\": 600,"
+    "    \"client-classes\": ["
+    "        {"
+    "            \"name\": \"a-devices\","
+    "            \"test\": \"option[93].hex == 0x0001\""
+    "        },"
+    "        {"
+    "            \"name\": \"b-devices\","
+    "            \"test\": \"option[93].hex == 0x0002\""
+    "        }"
+    "    ],"
+    "    \"shared-networks\": ["
+    "        {"
+    "            \"name\": \"frog\","
+    "            \"relay\": {"
+    "                \"ip-address\": \"192.3.5.6\""
+    "            },"
+    "            \"subnet4\": ["
+    "                {"
+    "                    \"subnet\": \"192.0.2.0/26\","
+    "                    \"id\": 10,"
+    "                    \"pools\": ["
+    "                        {"
+    "                            \"pool\": \"192.0.2.63 - 192.0.2.63\""
+    "                        }"
+    "                    ],"
+    "                    \"client-class\": \"a-devices\""
+    "                },"
+    "                {"
+    "                    \"subnet\": \"10.0.0.0/24\","
+    "                    \"id\": 100,"
+    "                    \"pools\": ["
+    "                        {"
+    "                            \"pool\": \"10.0.0.16 - 10.0.0.16\""
+    "                        }"
+    "                    ]"
+    "                }"
+    "            ]"
+    "        }"
+    "    ]"
+    "}",
+
+// Configuration #3.
+    "{"
+    "    \"interfaces-config\": {"
+    "        \"interfaces\": [ \"*\" ]"
+    "    },"
+    "    \"valid-lifetime\": 600,"
+    "    \"client-classes\": ["
+    "        {"
+    "            \"name\": \"a-devices\","
+    "            \"test\": \"option[93].hex == 0x0001\""
+    "        },"
+    "        {"
+    "            \"name\": \"b-devices\","
+    "            \"test\": \"option[93].hex == 0x0002\""
+    "        }"
+    "    ],"
+    "    \"shared-networks\": ["
+    "        {"
+    "            \"name\": \"frog\","
+    "            \"relay\": {"
+    "                \"ip-address\": \"192.3.5.6\""
+    "            },"
+    "            \"subnet4\": ["
+    "                {"
+    "                    \"subnet\": \"192.0.2.0/26\","
+    "                    \"id\": 10,"
+    "                    \"pools\": ["
+    "                        {"
+    "                            \"pool\": \"192.0.2.63 - 192.0.2.63\""
+    "                        }"
+    "                    ],"
+    "                    \"client-class\": \"a-devices\""
+    "                },"
+    "                {"
+    "                    \"subnet\": \"10.0.0.0/24\","
+    "                    \"id\": 100,"
+    "                    \"pools\": ["
+    "                        {"
+    "                            \"pool\": \"10.0.0.16 - 10.0.0.16\""
+    "                        }"
+    "                    ],"
+    "                    \"client-class\": \"b-devices\""
+    "                }"
+    "            ]"
+    "        }"
+    "    ]"
+    "}",
+
+// Configuration #4.
+    "{"
+    "    \"interfaces-config\": {"
+    "        \"interfaces\": [ \"*\" ]"
+    "    },"
+    "    \"valid-lifetime\": 600,"
+    "    \"shared-networks\": ["
+    "        {"
+    "            \"name\": \"frog\","
+    "            \"relay\": {"
+    "                \"ip-address\": \"192.3.5.6\""
+    "            },"
+    "            \"subnet4\": ["
+    "                {"
+    "                    \"subnet\": \"192.0.2.0/26\","
+    "                    \"id\": 10,"
+    "                    \"pools\": ["
+    "                        {"
+    "                            \"pool\": \"192.0.2.1 - 192.0.2.63\""
+    "                        }"
+    "                    ],"
+    "                    \"reservations\": ["
+    "                        {"
+    "                            \"hw-address\": \"aa:bb:cc:dd:ee:ff\","
+    "                            \"ip-address\": \"192.0.2.28\""
+    "                        }"
+    "                    ]"
+    "                },"
+    "                {"
+    "                    \"subnet\": \"10.0.0.0/24\","
+    "                    \"id\": 100,"
+    "                    \"pools\": ["
+    "                        {"
+    "                            \"pool\": \"10.0.0.1 - 10.0.0.254\""
+    "                        }"
+    "                    ],"
+    "                    \"reservations\": ["
+    "                        {"
+    "                            \"hw-address\": \"11:22:33:44:55:66\","
+    "                            \"ip-address\": \"10.0.0.29\""
+    "                        }"
+    "                    ]"
+    "                }"
+    "            ]"
+    "        }"
+    "    ]"
+    "}",
+
+// Configuration #5.
+    "{"
+    "    \"interfaces-config\": {"
+    "        \"interfaces\": [ \"*\" ]"
+    "    },"
+    "    \"valid-lifetime\": 600,"
+    "    \"shared-networks\": ["
+    "        {"
+    "            \"name\": \"frog\","
+    "            \"relay\": {"
+    "                \"ip-address\": \"192.3.5.6\""
+    "            },"
+    "            \"subnet4\": ["
+    "                {"
+    "                    \"subnet\": \"192.0.2.0/26\","
+    "                    \"id\": 10,"
+    "                    \"pools\": ["
+    "                        {"
+    "                            \"pool\": \"192.0.2.1 - 192.0.2.63\""
+    "                        }"
+    "                    ],"
+    "                    \"reservations\": ["
+    "                        {"
+    "                            \"hw-address\": \"11:22:33:44:55:66\","
+    "                            \"ip-address\": \"192.0.2.28\""
+    "                        }"
+    "                    ]"
+    "                },"
+    "                {"
+    "                    \"subnet\": \"10.0.0.0/24\","
+    "                    \"id\": 100,"
+    "                    \"pools\": ["
+    "                        {"
+    "                            \"pool\": \"10.0.0.1 - 10.0.0.254\""
+    "                        }"
+    "                    ],"
+    "                    \"reservations\": ["
+    "                        {"
+    "                            \"hw-address\": \"aa:bb:cc:dd:ee:ff\","
+    "                            \"ip-address\": \"10.0.0.29\""
+    "                        }"
+    "                    ]"
+    "                }"
+    "            ]"
+    "        }"
+    "    ]"
+    "}",
+
+// Configuration #6
+    "{"
+    "    \"interfaces-config\": {"
+    "        \"interfaces\": [ \"*\" ]"
+    "    },"
+    "    \"valid-lifetime\": 600,"
+    "    \"client-classes\": ["
+    "        {"
+    "            \"name\": \"a-devices\","
+    "            \"test\": \"option[93].hex == 0x0001\""
+    "        }"
+    "    ],"
+    "    \"shared-networks\": ["
+    "        {"
+    "            \"name\": \"frog\","
+    "            \"relay\": {"
+    "                \"ip-address\": \"192.3.5.6\""
+    "            },"
+    "            \"subnet4\": ["
+    "                {"
+    "                    \"subnet\": \"192.0.2.0/26\","
+    "                    \"id\": 10,"
+    "                    \"pools\": ["
+    "                        {"
+    "                            \"pool\": \"192.0.2.1 - 192.0.2.63\""
+    "                        }"
+    "                    ],"
+    "                    \"client-class\": \"a-devices\","
+    "                    \"reservations\": ["
+    "                        {"
+    "                            \"hw-address\": \"aa:bb:cc:dd:ee:ff\","
+    "                            \"ip-address\": \"192.0.2.28\""
+    "                        }"
+    "                    ]"
+    "                },"
+    "                {"
+    "                    \"subnet\": \"10.0.0.0/24\","
+    "                    \"id\": 100,"
+    "                    \"pools\": ["
+    "                        {"
+    "                            \"pool\": \"10.0.0.16 - 10.0.0.16\""
+    "                        }"
+    "                    ]"
+    "                }"
+    "            ]"
+    "        }"
+    "    ]"
+    "}",
+
+// Configuration #7
+    "{"
+    "    \"interfaces-config\": {"
+    "        \"interfaces\": [ \"*\" ]"
+    "    },"
+    "    \"valid-lifetime\": 600,"
+    "    \"option-data\": ["
+    "        {"
+    "            \"name\": \"log-servers\","
+    "            \"data\": \"1.2.3.4\""
+    "        }"
+    "    ],"
+    "    \"shared-networks\": ["
+    "        {"
+    "            \"name\": \"frog\","
+    "            \"interface\": \"eth1\","
+    "            \"option-data\": ["
+    "                {"
+    "                    \"name\": \"domain-name-servers\","
+    "                    \"data\": \"10.1.2.3\""
+    "                },"
+    "                {"
+    "                    \"name\": \"cookie-servers\","
+    "                    \"data\": \"10.6.5.4\""
+    "                }"
+    "            ],"
+    "            \"subnet4\": ["
+    "                {"
+    "                    \"subnet\": \"192.0.2.0/26\","
+    "                    \"id\": 10,"
+    "                    \"option-data\": ["
+    "                        {"
+    "                            \"name\": \"routers\","
+    "                            \"data\": \"192.0.2.5\""
+    "                        },"
+    "                        {"
+    "                            \"name\": \"cookie-servers\","
+    "                            \"data\": \"10.5.4.3\""
+    "                        }"
+    "                    ],"
+    "                    \"pools\": ["
+    "                        {"
+    "                            \"pool\": \"192.0.2.63 - 192.0.2.63\""
+    "                        }"
+    "                    ]"
+    "                },"
+    "                {"
+    "                    \"subnet\": \"10.0.0.0/24\","
+    "                    \"id\": 100,"
+    "                    \"pools\": ["
+    "                        {"
+    "                            \"pool\": \"10.0.0.16 - 10.0.0.16\""
+    "                        }"
+    "                    ]"
+    "                }"
+    "            ]"
+    "        }"
+    "    ],"
+    "    \"subnet4\": ["
+    "        {"
+    "            \"subnet\": \"192.0.2.64/26\","
+    "            \"id\": 1000,"
+    "            \"interface\": \"eth0\","
+    "            \"option-data\": ["
+    "                {"
+    "                    \"name\": \"cookie-servers\","
+    "                    \"data\": \"10.1.1.1\""
+    "                }"
+    "            ],"
+    "            \"pools\": ["
+    "                {"
+    "                    \"pool\": \"192.0.2.65 - 192.0.2.65\""
+    "                }"
+    "            ]"
+    "        }"
+    "    ]"
+    "}",
+
+// Configuration #8
+    "{"
+    "    \"interfaces-config\": {"
+    "        \"interfaces\": [ \"*\" ]"
+    "    },"
+    "    \"valid-lifetime\": 600,"
+    "    \"shared-networks\": ["
+    "        {"
+    "            \"name\": \"frog\","
+    "            \"interface\": \"eth1\","
+    "            \"subnet4\": ["
+    "                {"
+    "                    \"subnet\": \"192.0.2.0/26\","
+    "                    \"id\": 10,"
+    "                    \"pools\": ["
+    "                        {"
+    "                            \"pool\": \"192.0.2.1 - 192.0.2.63\""
+    "                        }"
+    "                    ]"
+    "                },"
+    "                {"
+    "                    \"subnet\": \"192.0.2.64/26\","
+    "                    \"id\": 100,"
+    "                    \"pools\": ["
+    "                        {"
+    "                            \"pool\": \"192.0.2.65 - 192.0.2.127\""
+    "                        }"
+    "                    ]"
+    "                }"
+    "            ]"
+    "        },"
+    "        {"
+    "            \"name\": \"dog\","
+    "            \"interface\": \"eth0\","
+    "            \"subnet4\": ["
+    "                {"
+    "                    \"subnet\": \"10.0.0.0/26\","
+    "                    \"id\": 1000,"
+    "                    \"pools\": ["
+    "                        {"
+    "                            \"pool\": \"10.0.0.1 - 10.0.0.63\""
+    "                        }"
+    "                    ]"
+    "                },"
+    "                {"
+    "                    \"subnet\": \"10.0.0.64/26\","
+    "                    \"id\": 10000,"
+    "                    \"pools\": ["
+    "                        {"
+    "                            \"pool\": \"10.0.0.65 - 10.0.0.127\""
+    "                        }"
+    "                    ]"
+    "                }"
+    "            ]"
+    "        }"
+    "    ]"
+    "}",
+
+// Configuration #9
+    "{"
+    "    \"interfaces-config\": {"
+    "        \"interfaces\": [ \"*\" ]"
+    "    },"
+    "    \"valid-lifetime\": 600,"
+    "    \"shared-networks\": ["
+    "        {"
+    "            \"name\": \"frog\","
+    "            \"relay\": { \"ip-address\": \"10.1.2.3\" },"
+    "            \"subnet4\": ["
+    "                {"
+    "                    \"subnet\": \"192.0.2.0/26\","
+    "                    \"id\": 10,"
+    "                    \"pools\": ["
+    "                        {"
+    "                            \"pool\": \"192.0.2.1 - 192.0.2.63\""
+    "                        }"
+    "                    ]"
+    "                },"
+    "                {"
+    "                    \"subnet\": \"192.0.2.64/26\","
+    "                    \"id\": 100,"
+    "                    \"pools\": ["
+    "                        {"
+    "                            \"pool\": \"192.0.2.65 - 192.0.2.127\""
+    "                        }"
+    "                    ]"
+    "                }"
+    "            ]"
+    "        },"
+    "        {"
+    "            \"name\": \"dog\","
+    "            \"relay\": { \"ip-address\": \"192.1.2.3\" },"
+    "            \"subnet4\": ["
+    "                {"
+    "                    \"subnet\": \"10.0.0.0/26\","
+    "                    \"id\": 1000,"
+    "                    \"pools\": ["
+    "                        {"
+    "                            \"pool\": \"10.0.0.1 - 10.0.0.63\""
+    "                        }"
+    "                    ]"
+    "                },"
+    "                {"
+    "                    \"subnet\": \"10.0.0.64/26\","
+    "                    \"id\": 10000,"
+    "                    \"pools\": ["
+    "                        {"
+    "                            \"pool\": \"10.0.0.65 - 10.0.0.127\""
+    "                        }"
+    "                    ]"
+    "                }"
+    "            ]"
+    "        }"
+    "    ]"
+    "}",
+// Configuration #10.
+    "{"
+    "    \"interfaces-config\": {"
+    "        \"interfaces\": [ \"*\" ]"
+    "    },"
+    "    \"valid-lifetime\": 600,"
+    "    \"client-classes\": ["
+    "        {"
+    "            \"name\": \"class-with-bootfile\","
+    "            \"boot-file-name\": \"/dev/null\""
+    "        }"
+    "    ],"
+    "    \"shared-networks\": ["
+    "        {"
+    "            \"name\": \"frog\","
+    "            \"relay\": {"
+    "                \"ip-address\": \"192.3.5.6\""
     "            },"
-    "            {"
-    "                \"subnet\": \"10.0.0.0/24\","
-    "                \"id\": 100,"
-    "                \"pools\": ["
-    "                    {"
-    "                        \"pool\": \10.0.0.1 - 10.0.0.254\""
-    "                    }"
-    "                ]"
-    "            }"
-    "        ]"
-    "    }"
-    "],"
-    "\"subnet4\": ["
-    "    \"subnet\": \"192.0.2.64/26\","
-    "    \"id\": 1000,"
-    "    \"pools\": ["
-    "        {"
-    "            \"pool\": \"192.0.2.65 - 192.0.2.127\""
+    "            \"subnet4\": ["
+    "                {"
+    "                    \"subnet\": \"192.0.2.0/26\","
+    "                    \"id\": 10,"
+    "                    \"pools\": ["
+    "                        {"
+    "                            \"pool\": \"192.0.2.1 - 192.0.2.63\""
+    "                        }"
+    "                    ]"
+    "                },"
+    "                {"
+    "                    \"subnet\": \"10.0.0.0/24\","
+    "                    \"id\": 100,"
+    "                    \"pools\": ["
+    "                        {"
+    "                            \"pool\": \"10.0.0.1 - 10.0.0.254\""
+    "                        }"
+    "                    ],"
+    "                    \"reservations\": ["
+    "                        {"
+    "                            \"hw-address\": \"11:22:33:44:55:66\","
+    "                            \"ip-address\": \"10.0.0.29\","
+    "                            \"hostname\": \"test.example.org\","
+    "                            \"next-server\": \"10.10.10.10\","
+    "                            \"client-classes\": [ \"class-with-bootfile\" ]"
+    "                        }"
+    "                    ]"
+    "                }"
+    "            ]"
     "        }"
     "    ]"
-    "]"
+    "}"
 };
 
-/// @brief Test fixture class for DHCPv4 server using shared networks.
+/// @Brief Test fixture class for DHCPv4 server using shared networks.
 class Dhcpv4SharedNetworkTest : public Dhcpv4SrvTest {
 public:
 
@@ -81,55 +620,580 @@ public:
     IfaceMgrTestConfig iface_mgr_test_config_;
 };
 
-// Selected subnet is out of shared network.
-TEST_F(Dhcpv4SharedNetworkTest, outOfSharedNetworkAllocation) {
+// Running out of addresses within a subnet in a shared network.
+TEST_F(Dhcpv4SharedNetworkTest, poolInSharedNetworkShortage) {
+    // Create client #1
+    Dhcp4Client client1(Dhcp4Client::SELECTING);
+    client1.setIfaceName("eth1");
+
+    // Configure the server with one shared network including two subnets and
+    // one subnet outside of the shared network.
+    configure(NETWORKS_CONFIG[0], *client1.getServer());
+
+    // Client #1 requests an address in first subnet within a shared network.
+    ASSERT_NO_THROW(client1.doDORA(boost::shared_ptr<IOAddress>(new IOAddress("192.0.2.63"))));
+    Pkt4Ptr resp1 = client1.getContext().response_;
+    ASSERT_TRUE(resp1);
+    EXPECT_EQ(DHCPACK, resp1->getType());
+    EXPECT_EQ("192.0.2.63", resp1->getYiaddr().toText());
+
+    // Client #2 The second client will request a lease and should be assigned
+    // an address from the second subnet.
+    Dhcp4Client client2(client1.getServer(), Dhcp4Client::SELECTING);
+    client2.setIfaceName("eth1");
+    ASSERT_NO_THROW(client2.doDORA());
+    Pkt4Ptr resp2 = client2.getContext().response_;
+    ASSERT_TRUE(resp2);
+    EXPECT_EQ(DHCPACK, resp2->getType());
+    EXPECT_EQ("10.0.0.16", resp2->getYiaddr().toText());
+
+    // Client #3. It sends DHCPDISCOVER which should be dropped by the server because
+    // the server has no more addresses to assign.
+    Dhcp4Client client3(client1.getServer(), Dhcp4Client::SELECTING);
+    client3.setIfaceName("eth1");
+    ASSERT_NO_THROW(client3.doDiscover());
+    Pkt4Ptr resp3 = client3.getContext().response_;
+    ASSERT_FALSE(resp3);
+
+    // Client #3 should be assigned an address if subnet 3 is selected for this client.
+    client3.setIfaceName("eth0");
+    ASSERT_NO_THROW(client3.doDORA());
+    resp3 = client3.getContext().response_;
+    ASSERT_TRUE(resp3);
+    EXPECT_EQ(DHCPACK, resp3->getType());
+    EXPECT_EQ("192.0.2.65", resp3->getYiaddr().toText());
+
+    // Client #1 should be able to renew its address.
+    client1.setState(Dhcp4Client::RENEWING);
+    ASSERT_NO_THROW(client1.doRequest());
+    resp1 = client1.getContext().response_;
+    ASSERT_TRUE(resp1);
+    EXPECT_EQ(DHCPACK, resp1->getType());
+    EXPECT_EQ("192.0.2.63", resp1->getYiaddr().toText());
+
+    // Client #2 should be able to renew its address.
+    client2.setState(Dhcp4Client::RENEWING);
+    ASSERT_NO_THROW(client2.doRequest());
+    resp2 = client2.getContext().response_;
+    ASSERT_TRUE(resp2);
+    EXPECT_EQ(DHCPACK, resp2->getType());
+    EXPECT_EQ("10.0.0.16", resp2->getYiaddr().toText());
 }
 
 // Shared network is selected based on giaddr value.
 TEST_F(Dhcpv4SharedNetworkTest, sharedNetworkSelectedByRelay) {
-}
+    // Create client #1. This is a relayed client which is using relay
+    // address matching configured shared network.
+    Dhcp4Client client1(Dhcp4Client::SELECTING);
+    client1.useRelay(true, IOAddress("192.3.5.6"), IOAddress("10.0.0.2"));
 
-// Running out of addresses within a subnet in a shared network.
-TEST_F(Dhcpv4SharedNetworkTest, poolInSharedNetworkShortage) {
-}
+    // Configure the server with one shared network and one subnet outside of the
+    // shared network.
+    configure(NETWORKS_CONFIG[1], *client1.getServer());
+
+    // Client #1 should be assigned an address from shared network.
+    ASSERT_NO_THROW(client1.doDORA(boost::shared_ptr<IOAddress>(new IOAddress("192.0.2.63"))));
+    Pkt4Ptr resp1 = client1.getContext().response_;
+    ASSERT_TRUE(resp1);
+    EXPECT_EQ(DHCPACK, resp1->getType());
+    EXPECT_EQ("192.0.2.63", resp1->getYiaddr().toText());
 
-// Running out of addresses within entire shared network.
-TEST_F(Dhcpv4SharedNetworkTest, addressShortageInNetwork) {
+    // Create client #2. This is a relayed client which is using relay
+    // address matching subnet outside of the shared network.
+    Dhcp4Client client2(client1.getServer(), Dhcp4Client::SELECTING);
+    client2.useRelay(true, IOAddress("192.1.2.3"), IOAddress("10.0.0.3"));
+    ASSERT_NO_THROW(client2.doDORA(boost::shared_ptr<IOAddress>(new IOAddress("192.0.2.63"))));
+    Pkt4Ptr resp2 = client2.getContext().response_;
+    ASSERT_TRUE(resp2);
+    EXPECT_EQ(DHCPACK, resp2->getType());
+    EXPECT_EQ("192.0.2.65", resp2->getYiaddr().toText());
 }
 
 // Providing a hint for any address belonging to a shared network.
 TEST_F(Dhcpv4SharedNetworkTest, hintWithinSharedNetwork) {
+    // Create client.
+    Dhcp4Client client(Dhcp4Client::SELECTING);
+    client.setIfaceName("eth1");
+
+    // Configure the server with one shared network including two subnets and
+    // one subnet outside of the shared network.
+    configure(NETWORKS_CONFIG[0], *client.getServer());
+
+    // Provide a hint to an existing address within first subnet. This address
+    // should be offered out of this subnet.
+    ASSERT_NO_THROW(client.doDiscover(boost::shared_ptr<IOAddress>(new IOAddress("192.0.2.63"))));
+    Pkt4Ptr resp = client.getContext().response_;
+    ASSERT_TRUE(resp);
+    EXPECT_EQ(DHCPOFFER, resp->getType());
+    EXPECT_EQ("192.0.2.63", resp->getYiaddr().toText());
+
+    // Similarly, we should be offerred an address from another subnet within
+    // the same shared network when we ask for it.
+    ASSERT_NO_THROW(client.doDiscover(boost::shared_ptr<IOAddress>(new IOAddress("10.0.0.16"))));
+    resp = client.getContext().response_;
+    ASSERT_TRUE(resp);
+    EXPECT_EQ(DHCPOFFER, resp->getType());
+    EXPECT_EQ("10.0.0.16", resp->getYiaddr().toText());
+
+    // Asking for an address that is not in address pool should result in getting
+    // an address from one of the subnets, but generally hard to tell from which one.
+    ASSERT_NO_THROW(client.doDiscover(boost::shared_ptr<IOAddress>(new IOAddress("10.0.0.23"))));
+    resp = client.getContext().response_;
+    ASSERT_TRUE(resp);
+
+    // We expect one of the two addresses available in this shared network.
+    EXPECT_EQ(DHCPOFFER, resp->getType());
+    if ((resp->getYiaddr() != IOAddress("10.0.0.16")) &&
+        (resp->getYiaddr() != IOAddress("192.0.2.63"))) {
+        ADD_FAILURE() << "Unexpected address offerred by the server " << resp->getYiaddr();
+    }
 }
 
 // Access to a subnet within shared network is restricted by client
 // classification.
 TEST_F(Dhcpv4SharedNetworkTest, subnetInSharedNetworkSelectedByClass) {
+    // Create client #1
+    Dhcp4Client client1(Dhcp4Client::SELECTING);
+    client1.useRelay(true, IOAddress("192.3.5.6"));
+
+    // Configure the server with one shared network including two subnets in
+    // it. The access to one of the subnets is restricted by client classification.
+    configure(NETWORKS_CONFIG[2], *client1.getServer());
+
+    // Client #1 requests an address in the restricted subnet but can't be assigned
+    // this address because the client doesn't belong to a certain class.
+    ASSERT_NO_THROW(client1.doDORA(boost::shared_ptr<IOAddress>(new IOAddress("192.0.2.63"))));
+    Pkt4Ptr resp1 = client1.getContext().response_;
+    ASSERT_TRUE(resp1);
+    EXPECT_EQ(DHCPACK, resp1->getType());
+    EXPECT_EQ("10.0.0.16", resp1->getYiaddr().toText());
+
+    // Release the lease that the client has got, because we'll need this address
+    // further in the test.
+    ASSERT_NO_THROW(client1.doRelease());
+
+    // Add option93 which would cause the client to be classified as "a-devices".
+    OptionPtr option93(new OptionUint16(Option::V4, 93, 0x0001));
+    client1.addExtraOption(option93);
+
+    // This time, the allocation of the address provided as hint should be successful.
+    ASSERT_NO_THROW(client1.doDORA(boost::shared_ptr<IOAddress>(new IOAddress("192.0.2.63"))));
+    resp1 = client1.getContext().response_;
+    ASSERT_TRUE(resp1);
+    EXPECT_EQ(DHCPACK, resp1->getType());
+    EXPECT_EQ("192.0.2.63", resp1->getYiaddr().toText());
+
+    // Client 2 should be assigned an address from the unrestricted subnet.
+    Dhcp4Client client2(client1.getServer(), Dhcp4Client::SELECTING);
+    client2.useRelay(true, IOAddress("192.3.5.6"));
+    client2.setIfaceName("eth1");
+    ASSERT_NO_THROW(client2.doDORA());
+    Pkt4Ptr resp2 = client2.getContext().response_;
+    ASSERT_TRUE(resp2);
+    EXPECT_EQ(DHCPACK, resp2->getType());
+    EXPECT_EQ("10.0.0.16", resp2->getYiaddr().toText());
+
+    // Now, let's reconfigure the server to also apply restrictions on the
+    // subnet to which client2 now belongs.
+    configure(NETWORKS_CONFIG[3], *client1.getServer());
+
+    // The client should be refused to renew the lease because it doesn't belong
+    // to "b-devices" class.
+    client2.setState(Dhcp4Client::RENEWING);
+    ASSERT_NO_THROW(client2.doRequest());
+    resp2 = client2.getContext().response_;
+    ASSERT_TRUE(resp2);
+    EXPECT_EQ(DHCPNAK, resp2->getType());
+
+    // If we add option93 with a value matching this class, the lease should
+    // get renewed.
+    OptionPtr option93_bis(new OptionUint16(Option::V4, 93, 0x0002));
+    client2.addExtraOption(option93_bis);
+
+    ASSERT_NO_THROW(client2.doRequest());
+    resp2 = client2.getContext().response_;
+    ASSERT_TRUE(resp2);
+    EXPECT_EQ(DHCPACK, resp2->getType());
+    EXPECT_EQ("10.0.0.16", resp2->getYiaddr().toText());
 }
 
 // IPv4 address reservation exists in one of the subnets within
 // shared network.
 TEST_F(Dhcpv4SharedNetworkTest, reservationInSharedNetwork) {
+    // Create client #1. Explicitly set client's MAC address to the one that
+    // has a reservation in the first subnet within shared network.
+    Dhcp4Client client1(Dhcp4Client::SELECTING);
+    client1.useRelay(true, IOAddress("192.3.5.6"));
+    client1.setHWAddress("11:22:33:44:55:66");
+
+    // Create server configuration with a shared network including two subnets. There
+    // is an IP address reservation in each subnet for two respective clients.
+    configure(NETWORKS_CONFIG[4], *client1.getServer());
+
+    // Client #1 should get his reserved address from the second subnet.
+    ASSERT_NO_THROW(client1.doDORA(boost::shared_ptr<IOAddress>(new IOAddress("192.0.2.28"))));
+    Pkt4Ptr resp1 = client1.getContext().response_;
+    ASSERT_TRUE(resp1);
+    EXPECT_EQ(DHCPACK, resp1->getType());
+    EXPECT_EQ("10.0.0.29", resp1->getYiaddr().toText());
+
+    // Create client #2
+    Dhcp4Client client2(client1.getServer(), Dhcp4Client::SELECTING);
+    client2.useRelay(true, IOAddress("192.3.5.6"));
+    client2.setHWAddress("aa:bb:cc:dd:ee:ff");
+
+    // Client #2 should get its reserved address from the first subnet.
+    ASSERT_NO_THROW(client2.doDORA());
+    Pkt4Ptr resp2 = client2.getContext().response_;
+    ASSERT_TRUE(resp2);
+    EXPECT_EQ(DHCPACK, resp2->getType());
+    EXPECT_EQ("192.0.2.28", resp2->getYiaddr().toText());
+
+    // Reconfigure the server. Now, the first client get's second client's
+    // reservation and vice versa.
+    configure(NETWORKS_CONFIG[5], *client1.getServer());
+
+    // The first client is trying to renew the lease and should get a DHCPNAK.
+    client1.setState(Dhcp4Client::RENEWING);
+    ASSERT_NO_THROW(client1.doRequest());
+    resp1 = client1.getContext().response_;
+    ASSERT_TRUE(resp1);
+    EXPECT_EQ(DHCPNAK, resp1->getType());
+
+    // Similarly, the second client is trying to renew the lease and should
+    // get a DHCPNAK.
+    client2.setState(Dhcp4Client::RENEWING);
+    ASSERT_NO_THROW(client2.doRequest());
+    resp2 = client2.getContext().response_;
+    ASSERT_TRUE(resp2);
+    EXPECT_EQ(DHCPNAK, resp2->getType());
+
+    // But the client should get a lease, if it does 4-way exchange. However, it
+    // must not get any of the reserved addresses because one of them is reserved
+    // for another client and for another one there is a valid lease.
+    client1.setState(Dhcp4Client::SELECTING);
+    ASSERT_NO_THROW(client1.doDORA());
+    resp1 = client1.getContext().response_;
+    ASSERT_TRUE(resp1);
+    EXPECT_EQ(DHCPACK, resp1->getType());
+    EXPECT_NE("10.0.0.29", resp1->getYiaddr().toText());
+    EXPECT_NE("192.0.2.28", resp1->getYiaddr().toText());
+
+    // Client #2 is now doing 4-way exchange and should get its newly reserved
+    // address, released by the 4-way transaction of client 1.
+    client2.setState(Dhcp4Client::SELECTING);
+    ASSERT_NO_THROW(client2.doDORA());
+    resp2 = client2.getContext().response_;
+    ASSERT_TRUE(resp2);
+    EXPECT_EQ(DHCPACK, resp2->getType());
+    EXPECT_EQ("10.0.0.29", resp2->getYiaddr().toText());
+
+    // Same for client #1.
+    client1.setState(Dhcp4Client::SELECTING);
+    ASSERT_NO_THROW(client1.doDORA());
+    resp1 = client1.getContext().response_;
+    ASSERT_TRUE(resp1);
+    EXPECT_EQ(DHCPACK, resp1->getType());
+    EXPECT_EQ("192.0.2.28", resp1->getYiaddr().toText());
 }
 
-// Reserved address can't be assigned until access to a subnet is
+// Reserved address can't be assigned as long as access to a subnet is
 // restricted by classification.
 TEST_F(Dhcpv4SharedNetworkTest, reservationAccessRestrictedByClass) {
-}
+    // Create a client and set explicit MAC address for which there is a reservation
+    // in first subnet belonging to a shared network.
+    Dhcp4Client client(Dhcp4Client::SELECTING);
+    client.useRelay(true, IOAddress("192.3.5.6"));
+    client.setHWAddress("aa:bb:cc:dd:ee:ff");
+
+    // Create configuration with a shared network including two subnets. Access to
+    // one of the subnets is restricted by client classification.
+    configure(NETWORKS_CONFIG[6], *client.getServer());
+
+    // Perform 4-way exchange to get an address.
+    ASSERT_NO_THROW(client.doDORA());
+    Pkt4Ptr resp = client.getContext().response_;
+    ASSERT_TRUE(resp);
+    EXPECT_EQ(DHCPACK, resp->getType());
+    // Assigned address should be allocated from the second subnet, because the
+    // client doesn't belong to the "a-devices" class.
+    EXPECT_EQ("10.0.0.16", resp->getYiaddr().toText());
 
-// Multiple subnets within a shared network contain reservations for
-// the same client.
-TEST_F(Dhcpv4SharedNetworkTest, reservationsInDifferentSubnets) {
+    // Add option93 which would cause the client to be classified as "a-devices".
+    OptionPtr option93(new OptionUint16(Option::V4, 93, 0x0001));
+    client.addExtraOption(option93);
+
+    // Client renews its lease and should get DHCPNAK because this client now belongs
+    // to the "a-devices" class and can be assigned a reserved address instead.
+    client.setState(Dhcp4Client::RENEWING);
+    ASSERT_NO_THROW(client.doRequest());
+    resp = client.getContext().response_;
+    ASSERT_TRUE(resp);
+    EXPECT_EQ(DHCPNAK, resp->getType());
+
+    // Perform 4-way exchange again. It should be assigned a reserved address this time.
+    client.setState(Dhcp4Client::SELECTING);
+    ASSERT_NO_THROW(client.doDORA());
+    resp = client.getContext().response_;
+    ASSERT_TRUE(resp);
+    EXPECT_EQ(DHCPACK, resp->getType());
+    EXPECT_EQ("192.0.2.28", resp->getYiaddr().toText());
 }
 
 // Some options are specified on the shared subnet level, some on the
 // subnets level.
 TEST_F(Dhcpv4SharedNetworkTest, optionsDerivation) {
+    // Client #1.
+    Dhcp4Client client1(Dhcp4Client::SELECTING);
+    client1.setIfaceName("eth1");
+    client1.requestOptions(DHO_LOG_SERVERS, DHO_COOKIE_SERVERS, DHO_DOMAIN_NAME_SERVERS);
+
+    configure(NETWORKS_CONFIG[7], *client1.getServer());
+
+    // Client #1 belongs to shared network. By providing a hint "192.0.2.63" we force
+    // the server to select first subnet within the shared network for this client.
+    ASSERT_NO_THROW(client1.doDORA(boost::shared_ptr<IOAddress>(new IOAddress("192.0.2.63"))));
+    Pkt4Ptr resp = client1.getContext().response_;
+    ASSERT_TRUE(resp);
+    EXPECT_EQ(DHCPACK, resp->getType());
+    EXPECT_EQ("192.0.2.63", resp->getYiaddr().toText());
+
+    // This option is specified at the global level.
+    ASSERT_EQ(1, client1.config_.log_servers_.size());
+    EXPECT_EQ("1.2.3.4", client1.config_.log_servers_[0].toText());
+
+    // This option is specified on the subnet level.
+    ASSERT_EQ(1, client1.config_.routers_.size());
+    EXPECT_EQ("192.0.2.5", client1.config_.routers_[0].toText());
+
+    // This option is specified on the shared network level and the subnet level.
+    // The instance on the subnet level should take precedence.
+    ASSERT_EQ(1, client1.config_.quotes_servers_.size());
+    EXPECT_EQ("10.5.4.3", client1.config_.quotes_servers_[0].toText());
+
+    // This option is only specified on the shared network level and should be
+    // inherited by all subnets within this network.
+    ASSERT_EQ(1, client1.config_.dns_servers_.size());
+    EXPECT_EQ("10.1.2.3", client1.config_.dns_servers_[0].toText());
+
+    // Client #2.
+    Dhcp4Client client2(Dhcp4Client::SELECTING);
+    client2.setIfaceName("eth1");
+    client2.requestOptions(DHO_LOG_SERVERS, DHO_COOKIE_SERVERS, DHO_DOMAIN_NAME_SERVERS);
+
+    // Request an address from the second subnet within the shared network.
+    ASSERT_NO_THROW(client2.doDORA(boost::shared_ptr<IOAddress>(new IOAddress("10.0.0.16"))));
+    Pkt4Ptr resp2 = client2.getContext().response_;
+    ASSERT_TRUE(resp2);
+    EXPECT_EQ(DHCPACK, resp2->getType());
+    EXPECT_EQ("10.0.0.16", resp2->getYiaddr().toText());
+
+    // This option is specified at the global level.
+    ASSERT_EQ(1, client2.config_.log_servers_.size());
+    EXPECT_EQ("1.2.3.4", client2.config_.log_servers_[0].toText());
+
+    // This option is only specified on the shared network level and should be 
+    // inherited by all subnets within this network.
+    ASSERT_EQ(1, client2.config_.quotes_servers_.size());
+    EXPECT_EQ("10.6.5.4", client2.config_.quotes_servers_[0].toText());
+
+    // This option is only specified on the shared network level and should be
+    // inherited by all subnets within this network.
+    ASSERT_EQ(1, client2.config_.dns_servers_.size());
+    EXPECT_EQ("10.1.2.3", client2.config_.dns_servers_[0].toText());
+
+    // Client #3.
+    Dhcp4Client client3(Dhcp4Client::SELECTING);
+    client3.setIfaceName("eth0");
+    client3.requestOptions(DHO_LOG_SERVERS, DHO_COOKIE_SERVERS, DHO_DOMAIN_NAME_SERVERS);
+
+    // Client 3 should get an address from the subnet defined outside of the shared network.
+    ASSERT_NO_THROW(client3.doDORA());
+    Pkt4Ptr resp3 = client3.getContext().response_;
+    ASSERT_TRUE(resp3);
+    EXPECT_EQ(DHCPACK, resp3->getType());
+    EXPECT_EQ("192.0.2.65", resp3->getYiaddr().toText());
+
+    // This option is specified at the global level.
+    ASSERT_EQ(1, client3.config_.log_servers_.size());
+    EXPECT_EQ("1.2.3.4", client3.config_.log_servers_[0].toText());
+
+    // This option is specified on the subnet level.
+    ASSERT_EQ(1, client3.config_.quotes_servers_.size());
+    EXPECT_EQ("10.1.1.1", client3.config_.quotes_servers_[0].toText());
+
+    // This option is only specified on the shared network level and thus it should
+    // not be returned to this client, because the client doesn't belong to the
+    // shared network.
+    ASSERT_EQ(0, client3.config_.dns_servers_.size());
 }
 
-// Host reservations include class specification.
-TEST_F(Dhcpv4SharedNetworkTest, classesInReservations) {
+// Client has a lease in a subnet within shared network.
+TEST_F(Dhcpv4SharedNetworkTest, initReboot) {
+    // Create client #1.
+    Dhcp4Client client1(Dhcp4Client::SELECTING);
+    client1.setIfaceName("eth1");
+
+    configure(NETWORKS_CONFIG[0], *client1.getServer());
+
+    // Perform 4-way exchange to obtain a lease. The client should get the lease from
+    // the second subnet.
+    ASSERT_NO_THROW(client1.doDORA(boost::shared_ptr<IOAddress>(new IOAddress("10.0.0.16"))));
+    Pkt4Ptr resp1 = client1.getContext().response_;
+    ASSERT_TRUE(resp1);
+    EXPECT_EQ(DHCPACK, resp1->getType());
+    EXPECT_EQ("10.0.0.16", resp1->getYiaddr().toText());
+
+    // The client1 transitions to INIT-REBOOT state in which the client1 remembers the
+    // lease and sends DHCPREQUEST to all servers (server id) is not specified. If
+    // the server doesn't know the client1 (doesn't have its lease), it should
+    // drop the request. We want to make sure that the server resp1onds regardless
+    // of the subnet from which the lease has been allocated.
+    client1.setState(Dhcp4Client::INIT_REBOOT);
+    ASSERT_NO_THROW(client1.doRequest());
+    resp1 = client1.getContext().response_;
+    ASSERT_TRUE(resp1);
+    EXPECT_EQ(DHCPACK, resp1->getType());
+    EXPECT_EQ("10.0.0.16", resp1->getYiaddr().toText());
+
+    // Create client #2.
+    Dhcp4Client client2(client1.getServer(), Dhcp4Client::SELECTING);
+    client2.setIfaceName("eth1");
+
+    // Let's make sure that the behavior is the same for the other subnet within the
+    // same shared network.
+    ASSERT_NO_THROW(client2.doDORA(boost::shared_ptr<IOAddress>(new IOAddress("192.0.2.63"))));
+    Pkt4Ptr resp2 = client2.getContext().response_;
+    ASSERT_TRUE(resp2);
+    EXPECT_EQ(DHCPACK, resp2->getType());
+    EXPECT_EQ("192.0.2.63", resp2->getYiaddr().toText());
+
+    // The client2 transitions to INIT-REBOOT state in which the client2 remembers the
+    // lease and sends DHCPREQUEST to all servers (server id) is not specified. If
+    // the server doesn't know the client2 (doesn't have its lease), it should
+    // drop the request. We want to make sure that the server resp2onds regardless
+    // of the subnet from which the lease has been allocated.
+    client2.setState(Dhcp4Client::INIT_REBOOT);
+    ASSERT_NO_THROW(client2.doRequest());
+    resp2 = client2.getContext().response_;
+    ASSERT_TRUE(resp2);
+    EXPECT_EQ(DHCPACK, resp2->getType());
+    EXPECT_EQ("192.0.2.63", resp2->getYiaddr().toText());
+}
+
+// Host reservations include hostname, next server and client class.
+TEST_F(Dhcpv4SharedNetworkTest, variousFieldsInReservation) {
+    // Create client.
+    Dhcp4Client client(Dhcp4Client::SELECTING);
+    client.setIfaceName("eth1");
+    client.setHWAddress("11:22:33:44:55:66");
+
+    // Include hostname to force the server to return hostname to
+    // the client.
+    client.includeHostname("my.example.org");
+
+    // Configure the server with a shared network including two subnets.
+    // The client has address/hostname reservation in the second subnet.
+    configure(NETWORKS_CONFIG[10], *client.getServer());
+
+    // Perform 4-way exchange.
+    ASSERT_NO_THROW(client.doDORA());
+    Pkt4Ptr resp = client.getContext().response_;
+    ASSERT_TRUE(resp);
+    EXPECT_EQ(DHCPACK, resp->getType());
+    EXPECT_EQ("10.0.0.29", resp->getYiaddr().toText());
+
+    // The client should get a hostname from the reservation, rather than
+    // the hostname it has sent to the server. If there is a logic error,
+    // the server would use the first subnet from the shared network to
+    // assign the hostname. This subnet has no reservation so it would
+    // return the same hostname that the client has sent. We expect
+    // that the hostname being sent is the one that is incldued in the
+    // reservations.
+    OptionStringPtr hostname;
+    hostname = boost::dynamic_pointer_cast<OptionString>(resp->getOption(DHO_HOST_NAME));
+    ASSERT_TRUE(hostname);
+    EXPECT_EQ("test.example.org", hostname->getValue());
+
+    // The next server value should also be set according to the settings
+    // in host reservations.
+    EXPECT_EQ("10.10.10.10", resp->getSiaddr().toText());
+
+    // The boot-file-name value should be derived from the client class
+    // based on the static class reservations.
+    const std::string expected_fname = "/dev/null";
+    const OptionBuffer fname = resp->getFile();
+    const std::string converted_fname(fname.cbegin(),
+                                      fname.cbegin() + expected_fname.size());
+    EXPECT_EQ(expected_fname, converted_fname);
 }
 
+// Different shared network is selected for different local interface.
+TEST_F(Dhcpv4SharedNetworkTest, sharedNetworkSelectionByInterface) {
+    // Create client #1. The server receives requests from this client
+    // via interface eth1 and should assign shared network "frog" for
+    // this client.
+    Dhcp4Client client1(Dhcp4Client::SELECTING);
+    client1.setIfaceName("eth1");
+
+    // Create server configuration with two shared networks selected
+    // by the local interface: eth1 and eth0.
+    configure(NETWORKS_CONFIG[8], *client1.getServer());
+
+    // Perform 4-way exchange.
+    ASSERT_NO_THROW(client1.doDORA());
+    Pkt4Ptr resp1 = client1.getContext().response_;
+    ASSERT_TRUE(resp1);
+    EXPECT_EQ(DHCPACK, resp1->getType());
+    // The client should be assigned an address from the 192.0.2.X
+    // address range.
+    EXPECT_EQ("192.0.2", resp1->getYiaddr().toText().substr(0, 7));
+
+    // Create client #2 which requests are received on eth0.
+    Dhcp4Client client2(client1.getServer(), Dhcp4Client::SELECTING);
+    client2.setIfaceName("eth0");
+
+    // Perform 4-way exchange.
+    ASSERT_NO_THROW(client2.doDORA());
+    Pkt4Ptr resp2 = client2.getContext().response_;
+    ASSERT_TRUE(resp2);
+    EXPECT_EQ(DHCPACK, resp2->getType());
+    // The client should be assigned an address from the 10.0.0.X
+    // address range.
+    EXPECT_EQ("10.0.0", resp2->getYiaddr().toText().substr(0, 6));
+}
 
+// Different shared network is selected for different relay address.
+TEST_F(Dhcpv4SharedNetworkTest, sharedNetworkSelectionByRelay) {
+    // Create relayed client #1.
+    Dhcp4Client client1(Dhcp4Client::SELECTING);
+    client1.useRelay(true, IOAddress("10.1.2.3"));
+
+    // Create server configuration with two shared networks selected
+    // by the relay address.
+    configure(NETWORKS_CONFIG[9], *client1.getServer());
+
+    // Perform 4-way exchange.
+    ASSERT_NO_THROW(client1.doDORA());
+    Pkt4Ptr resp1 = client1.getContext().response_;
+    ASSERT_TRUE(resp1);
+    EXPECT_EQ(DHCPACK, resp1->getType());
+    // The client should be assigned an address from the 192.0.2.X
+    // address range.
+    EXPECT_EQ("192.0.2", resp1->getYiaddr().toText().substr(0, 7));
+
+    // Create relayed client #2.
+    Dhcp4Client client2(client1.getServer(), Dhcp4Client::SELECTING);
+    client2.useRelay(true, IOAddress("192.1.2.3"));
+
+    // Perform 4-way exchange.
+    ASSERT_NO_THROW(client2.doDORA());
+    Pkt4Ptr resp2 = client2.getContext().response_;
+    ASSERT_TRUE(resp2);
+    EXPECT_EQ(DHCPACK, resp2->getType());
+    // The client should be assigned an address from the 10.0.0.X
+    // address range.
+    EXPECT_EQ("10.0.0", resp2->getYiaddr().toText().substr(0, 6));
+}
 
 } // end of anonymous namespace