Browse Source

[master] Merge branch 'trac5305'

# Conflicts:
#	src/lib/dhcpsrv/parsers/dhcp_parsers.cc
#	src/lib/dhcpsrv/subnet.cc
#	src/lib/dhcpsrv/subnet.h
Marcin Siodelski 7 years ago
parent
commit
76dd46f707
39 changed files with 3034 additions and 489 deletions
  1. 1 1
      src/bin/dhcp4/dhcp4_srv.cc
  2. 9 0
      src/bin/dhcp4/json_config_parser.cc
  3. 4 4
      src/bin/dhcp4/tests/config_parser_unittest.cc
  4. 1 1
      src/bin/dhcp4/tests/dhcp4_test_utils.cc
  5. 1 1
      src/bin/dhcp4/tests/dhcp4_test_utils.h
  6. 7 0
      src/bin/dhcp4/tests/get_config_unittest.cc
  7. 1 1
      src/bin/dhcp4/tests/get_config_unittest.cc.skel
  8. 1 1
      src/bin/dhcp6/dhcp6_srv.cc
  9. 9 0
      src/bin/dhcp6/json_config_parser.cc
  10. 4 4
      src/bin/dhcp6/tests/config_parser_unittest.cc
  11. 6 0
      src/bin/dhcp6/tests/get_config_unittest.cc
  12. 1 1
      src/bin/dhcp6/tests/get_config_unittest.cc.skel
  13. 7 0
      src/lib/dhcpsrv/Makefile.am
  14. 6 5
      src/lib/dhcpsrv/alloc_engine.cc
  15. 62 0
      src/lib/dhcpsrv/assignable_network.h
  16. 138 0
      src/lib/dhcpsrv/cfg_shared_networks.h
  17. 144 0
      src/lib/dhcpsrv/network.cc
  18. 424 0
      src/lib/dhcpsrv/network.h
  19. 121 48
      src/lib/dhcpsrv/parsers/dhcp_parsers.cc
  20. 21 3
      src/lib/dhcpsrv/parsers/dhcp_parsers.h
  21. 1 0
      src/lib/dhcpsrv/parsers/option_data_parser.h
  22. 117 0
      src/lib/dhcpsrv/parsers/shared_network_parser.cc
  23. 52 0
      src/lib/dhcpsrv/parsers/shared_network_parser.h
  24. 82 0
      src/lib/dhcpsrv/parsers/shared_networks_list_parser.h
  25. 311 0
      src/lib/dhcpsrv/shared_network.cc
  26. 282 0
      src/lib/dhcpsrv/shared_network.h
  27. 13 1
      src/lib/dhcpsrv/srv_config.cc
  28. 25 0
      src/lib/dhcpsrv/srv_config.h
  29. 26 135
      src/lib/dhcpsrv/subnet.cc
  30. 36 277
      src/lib/dhcpsrv/subnet.h
  31. 5 0
      src/lib/dhcpsrv/tests/Makefile.am
  32. 2 2
      src/lib/dhcpsrv/tests/alloc_engine4_unittest.cc
  33. 122 0
      src/lib/dhcpsrv/tests/cfg_shared_networks4_unittest.cc
  34. 124 0
      src/lib/dhcpsrv/tests/cfg_shared_networks6_unittest.cc
  35. 4 4
      src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc
  36. 242 0
      src/lib/dhcpsrv/tests/shared_network_parser_unittest.cc
  37. 545 0
      src/lib/dhcpsrv/tests/shared_network_unittest.cc
  38. 75 0
      src/lib/dhcpsrv/tests/shared_networks_list_parser_unittest.cc
  39. 2 0
      src/lib/dhcpsrv/tests/srv_config_unittest.cc

+ 1 - 1
src/bin/dhcp4/dhcp4_srv.cc

@@ -148,7 +148,7 @@ Dhcpv4Exchange::Dhcpv4Exchange(const AllocEnginePtr& alloc_engine,
         }
 
         // Find static reservations if not disabled for our subnet.
-        if (subnet->getHostReservationMode() != Subnet::HR_DISABLED) {
+        if (subnet->getHostReservationMode() != Network::HR_DISABLED) {
             // Before we can check for static reservations, we need to prepare a set
             // of identifiers to be used for this.
             setHostIdentifiers();

+ 9 - 0
src/bin/dhcp4/json_config_parser.cc

@@ -290,6 +290,15 @@ configureDhcp4Server(Dhcpv4Srv&, isc::data::ConstElementPtr config_set,
                 continue;
             }
 
+            if (config_pair.first == "shared-networks") {
+                /// @todo We need to create instance of SharedNetworks4ListParser
+                /// and parse the list of the shared networks into the
+                /// CfgSharedNetworks4 object. One additional step is then to
+                /// add subnets from the CfgSharedNetworks4 into CfgSubnets4
+                /// as well.
+                continue;
+            }
+
             // Timers are not used in the global scope. Their values are derived
             // to specific subnets (see SimpleParser6::deriveParameters).
             // decline-probation-period, dhcp4o6-port, echo-client-id are

+ 4 - 4
src/bin/dhcp4/tests/config_parser_unittest.cc

@@ -4314,22 +4314,22 @@ TEST_F(Dhcp4ParserTest, hostReservationPerSubnet) {
     Subnet4Ptr subnet;
     subnet = subnets->selectSubnet(IOAddress("192.0.2.1"));
     ASSERT_TRUE(subnet);
-    EXPECT_EQ(Subnet::HR_ALL, subnet->getHostReservationMode());
+    EXPECT_EQ(Network::HR_ALL, subnet->getHostReservationMode());
 
     // Subnet 2
     subnet = subnets->selectSubnet(IOAddress("192.0.3.1"));
     ASSERT_TRUE(subnet);
-    EXPECT_EQ(Subnet::HR_OUT_OF_POOL, subnet->getHostReservationMode());
+    EXPECT_EQ(Network::HR_OUT_OF_POOL, subnet->getHostReservationMode());
 
     // Subnet 3
     subnet = subnets->selectSubnet(IOAddress("192.0.4.1"));
     ASSERT_TRUE(subnet);
-    EXPECT_EQ(Subnet::HR_DISABLED, subnet->getHostReservationMode());
+    EXPECT_EQ(Network::HR_DISABLED, subnet->getHostReservationMode());
 
     // Subnet 4
     subnet = subnets->selectSubnet(IOAddress("192.0.5.1"));
     ASSERT_TRUE(subnet);
-    EXPECT_EQ(Subnet::HR_ALL, subnet->getHostReservationMode());
+    EXPECT_EQ(Network::HR_ALL, subnet->getHostReservationMode());
 }
 
 /// Check that the decline-probation-period has a default value when not

+ 1 - 1
src/bin/dhcp4/tests/dhcp4_test_utils.cc

@@ -270,7 +270,7 @@ HWAddrPtr Dhcpv4SrvTest::generateHWAddr(size_t size /*= 6*/) {
 }
 
 void Dhcpv4SrvTest::checkAddressParams(const Pkt4Ptr& rsp,
-                                       const SubnetPtr subnet,
+                                       const Subnet4Ptr subnet,
                                        bool t1_present,
                                        bool t2_present) {
 

+ 1 - 1
src/bin/dhcp4/tests/dhcp4_test_utils.h

@@ -326,7 +326,7 @@ public:
     /// present (false)
     /// @param t2_present check that t2 must be present (true) or must not be
     /// present (false)
-    void checkAddressParams(const Pkt4Ptr& rsp, const SubnetPtr subnet,
+    void checkAddressParams(const Pkt4Ptr& rsp, const Subnet4Ptr subnet,
                             bool t1_present = false,
                             bool t2_present = false);
 

+ 7 - 0
src/bin/dhcp4/tests/get_config_unittest.cc

@@ -6135,6 +6135,7 @@ public:
 };
 
 /// Test a configuration
+
 TEST_P(Dhcp4GetConfigTest, run) {
     // configurations have not been extracted yet
     if (max_config_counter == 0) {
@@ -6199,8 +6200,14 @@ TEST_P(Dhcp4GetConfigTest, run) {
     EXPECT_TRUE(isEquivalent(unparsed, unparsed2));
 }
 
+#if 0
+// This test is temporarily disabled. The shared subnets structures have been
+// implemented (#5305), but the parsers are not there yet, so grammar will fail
+// when parseDHCP4 is called. That's comping up in #5357.
+
 /// Define the parameterized test loop
 INSTANTIATE_TEST_CASE_P(Dhcp4GetConfigTest, Dhcp4GetConfigTest,
                         ::testing::Range(static_cast<size_t>(0), max_config_counter));
+#endif
 
 };

+ 1 - 1
src/bin/dhcp4/tests/get_config_unittest.cc.skel

@@ -16,7 +16,7 @@
 #include <dhcp4/tests/get_config_unittest.h>
 #include <dhcp4/dhcp4_srv.h>
 #include <dhcp4/json_config_parser.h>
-#include <dhcp4/simple_parser4.h>
+#include <dhcpsrv/parsers/simple_parser4.h>
 
 #include <boost/algorithm/string.hpp>
 #include <gtest/gtest.h>

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

@@ -309,7 +309,7 @@ Dhcpv6Srv::initContext(const Pkt6Ptr& pkt, AllocEngine::ClientContext6& ctx) {
     // are stored in order of preference. The server will use them in that
     // order to search for host reservations.
     if (ctx.subnet_ &&
-        (ctx.subnet_->getHostReservationMode() != Subnet::HR_DISABLED)) {
+        (ctx.subnet_->getHostReservationMode() != Network::HR_DISABLED)) {
         const ConstCfgHostOperationsPtr cfg =
             CfgMgr::instance().getCurrentCfg()->getCfgHostOperations6();
         BOOST_FOREACH(const Host::IdentifierType& id_type,

+ 9 - 0
src/bin/dhcp6/json_config_parser.cc

@@ -382,6 +382,15 @@ configureDhcp6Server(Dhcpv6Srv&, isc::data::ConstElementPtr config_set,
                 continue;
             }
 
+            if (config_pair.first == "shared-networks") {
+                /// @todo We need to create instance of SharedNetworks4ListParser
+                /// and parse the list of the shared networks into the
+                /// CfgSharedNetworks4 object. One additional step is then to
+                /// add subnets from the CfgSharedNetworks6 into CfgSubnets6
+                /// as well.
+                continue;
+            }
+
             // Timers are not used in the global scope. Their values are derived
             // to specific subnets (see SimpleParser6::deriveParameters).
             // decline-probation-period and dhcp4o6-port are handled in the

+ 4 - 4
src/bin/dhcp6/tests/config_parser_unittest.cc

@@ -4697,22 +4697,22 @@ TEST_F(Dhcp6ParserTest, hostReservationPerSubnet) {
     Subnet6Ptr subnet;
     subnet = subnets->selectSubnet(IOAddress("2001:db8:1::1"));
     ASSERT_TRUE(subnet);
-    EXPECT_EQ(Subnet::HR_ALL, subnet->getHostReservationMode());
+    EXPECT_EQ(Network::HR_ALL, subnet->getHostReservationMode());
 
     // Subnet 2
     subnet = subnets->selectSubnet(IOAddress("2001:db8:2::1"));
     ASSERT_TRUE(subnet);
-    EXPECT_EQ(Subnet::HR_OUT_OF_POOL, subnet->getHostReservationMode());
+    EXPECT_EQ(Network::HR_OUT_OF_POOL, subnet->getHostReservationMode());
 
     // Subnet 3
     subnet = subnets->selectSubnet(IOAddress("2001:db8:3::1"));
     ASSERT_TRUE(subnet);
-    EXPECT_EQ(Subnet::HR_DISABLED, subnet->getHostReservationMode());
+    EXPECT_EQ(Network::HR_DISABLED, subnet->getHostReservationMode());
 
     // Subnet 4
     subnet = subnets->selectSubnet(IOAddress("2001:db8:4::1"));
     ASSERT_TRUE(subnet);
-    EXPECT_EQ(Subnet::HR_ALL, subnet->getHostReservationMode());
+    EXPECT_EQ(Network::HR_ALL, subnet->getHostReservationMode());
 }
 
 /// The goal of this test is to verify that configuration can include

+ 6 - 0
src/bin/dhcp6/tests/get_config_unittest.cc

@@ -6051,8 +6051,14 @@ TEST_P(Dhcp6GetConfigTest, run) {
     EXPECT_TRUE(isEquivalent(unparsed, unparsed2));
 }
 
+#if 0
+// This test is temporarily disabled. The shared subnets structures have been
+// implemented (#5305), but the parsers are not there yet, so grammar will fail
+// when parseDHCP4 is called. That's comping up in #5357.
+
 /// Define the parameterized test loop
 INSTANTIATE_TEST_CASE_P(Dhcp6GetConfigTest, Dhcp6GetConfigTest,
                         ::testing::Range(static_cast<size_t>(0), max_config_counter));
+#endif
 
 };

+ 1 - 1
src/bin/dhcp6/tests/get_config_unittest.cc.skel

@@ -16,7 +16,7 @@
 #include <dhcp6/tests/get_config_unittest.h>
 #include <dhcp6/dhcp6_srv.h>
 #include <dhcp6/json_config_parser.h>
-#include <dhcp6/simple_parser6.h>
+#include <dhcpsrv/parsers/simple_parser6.h>
 
 #include <boost/algorithm/string.hpp>
 #include <gtest/gtest.h>

+ 7 - 0
src/lib/dhcpsrv/Makefile.am

@@ -88,6 +88,7 @@ libkea_dhcpsrv_la_SOURCES  =
 libkea_dhcpsrv_la_SOURCES += addr_utilities.cc addr_utilities.h
 libkea_dhcpsrv_la_SOURCES += alloc_engine.cc alloc_engine.h
 libkea_dhcpsrv_la_SOURCES += alloc_engine_log.cc alloc_engine_log.h
+libkea_dhcpsrv_la_SOURCES += assignable_network.h
 libkea_dhcpsrv_la_SOURCES += base_host_data_source.h
 libkea_dhcpsrv_la_SOURCES += callout_handle_store.h
 libkea_dhcpsrv_la_SOURCES += cfg_4o6.cc cfg_4o6.h
@@ -101,6 +102,7 @@ libkea_dhcpsrv_la_SOURCES += cfg_host_operations.cc cfg_host_operations.h
 libkea_dhcpsrv_la_SOURCES += cfg_option.cc cfg_option.h
 libkea_dhcpsrv_la_SOURCES += cfg_option_def.cc cfg_option_def.h
 libkea_dhcpsrv_la_SOURCES += cfg_rsoo.cc cfg_rsoo.h
+libkea_dhcpsrv_la_SOURCES += cfg_shared_networks.h
 libkea_dhcpsrv_la_SOURCES += cfg_subnets4.cc cfg_subnets4.h
 libkea_dhcpsrv_la_SOURCES += cfg_subnets6.cc cfg_subnets6.h
 libkea_dhcpsrv_la_SOURCES += cfg_mac_source.cc cfg_mac_source.h
@@ -138,6 +140,7 @@ libkea_dhcpsrv_la_SOURCES += mysql_host_data_source.cc mysql_host_data_source.h
 endif
 
 libkea_dhcpsrv_la_SOURCES += ncr_generator.cc ncr_generator.h
+libkea_dhcpsrv_la_SOURCES += network.cc network.h
 
 if HAVE_PGSQL
 libkea_dhcpsrv_la_SOURCES += pgsql_connection.cc pgsql_connection.h
@@ -150,6 +153,7 @@ libkea_dhcpsrv_la_SOURCES += cql_lease_mgr.cc cql_lease_mgr.h
 libkea_dhcpsrv_la_SOURCES += cql_connection.cc cql_connection.h
 endif
 libkea_dhcpsrv_la_SOURCES += pool.cc pool.h
+libkea_dhcpsrv_la_SOURCES += shared_network.cc shared_network.h
 libkea_dhcpsrv_la_SOURCES += srv_config.cc srv_config.h
 libkea_dhcpsrv_la_SOURCES += subnet.cc subnet.h
 libkea_dhcpsrv_la_SOURCES += subnet_id.h
@@ -178,6 +182,9 @@ libkea_dhcpsrv_la_SOURCES += parsers/ifaces_config_parser.cc
 libkea_dhcpsrv_la_SOURCES += parsers/ifaces_config_parser.h
 libkea_dhcpsrv_la_SOURCES += parsers/option_data_parser.cc
 libkea_dhcpsrv_la_SOURCES += parsers/option_data_parser.h
+libkea_dhcpsrv_la_SOURCES += parsers/shared_network_parser.cc
+libkea_dhcpsrv_la_SOURCES += parsers/shared_network_parser.h
+libkea_dhcpsrv_la_SOURCES += parsers/shared_networks_list_parser.h
 libkea_dhcpsrv_la_SOURCES += parsers/simple_parser4.cc
 libkea_dhcpsrv_la_SOURCES += parsers/simple_parser4.h
 libkea_dhcpsrv_la_SOURCES += parsers/simple_parser6.cc

+ 6 - 5
src/lib/dhcpsrv/alloc_engine.cc

@@ -18,6 +18,7 @@
 #include <dhcpsrv/host.h>
 #include <dhcpsrv/lease_mgr_factory.h>
 #include <dhcpsrv/ncr_generator.h>
+#include <dhcpsrv/network.h>
 #include <hooks/callout_handle.h>
 #include <hooks/hooks_manager.h>
 #include <dhcpsrv/callout_handle_store.h>
@@ -570,7 +571,7 @@ AllocEngine::allocateUnreservedLeases6(ClientContext6& ctx) {
     }
 
     // Check which host reservation mode is supported in this subnet.
-    Subnet::HRMode hr_mode = ctx.subnet_->getHostReservationMode();
+    Network::HRMode hr_mode = ctx.subnet_->getHostReservationMode();
 
     Lease6Collection leases;
 
@@ -597,7 +598,7 @@ AllocEngine::allocateUnreservedLeases6(ClientContext6& ctx) {
             // it has been reserved for us we would have already allocated a lease.
 
             ConstHostPtr host;
-            if (hr_mode != Subnet::HR_DISABLED) {
+            if (hr_mode != Network::HR_DISABLED) {
                 host = HostMgr::instance().get6(ctx.subnet_->getID(), hint);
             }
 
@@ -633,7 +634,7 @@ AllocEngine::allocateUnreservedLeases6(ClientContext6& ctx) {
             if (lease->expired()) {
 
                 ConstHostPtr host;
-                if (hr_mode != Subnet::HR_DISABLED) {
+                if (hr_mode != Network::HR_DISABLED) {
                     host = HostMgr::instance().get6(ctx.subnet_->getID(), hint);
                 }
 
@@ -677,7 +678,7 @@ AllocEngine::allocateUnreservedLeases6(ClientContext6& ctx) {
         /// In-pool reservations: Check if this address is reserved for someone
         /// else. There is no need to check for whom it is reserved, because if
         /// it has been reserved for us we would have already allocated a lease.
-        if (hr_mode == Subnet::HR_ALL &&
+        if (hr_mode == Network::HR_ALL &&
             HostMgr::instance().get6(ctx.subnet_->getID(), candidate)) {
 
             // Don't allocate.
@@ -847,7 +848,7 @@ AllocEngine::removeNonmatchingReservedLeases6(ClientContext6& ctx,
     // host reservation is disabled (so there are no reserved leases),
     // just return.
     if (existing_leases.empty() || !ctx.subnet_ ||
-        (ctx.subnet_->getHostReservationMode() == Subnet::HR_DISABLED) ) {
+        (ctx.subnet_->getHostReservationMode() == Network::HR_DISABLED) ) {
         return;
     }
 

+ 62 - 0
src/lib/dhcpsrv/assignable_network.h

@@ -0,0 +1,62 @@
+// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef ASSIGNABLE_NETWORK_H
+#define ASSIGNABLE_NETWORK_H
+
+#include <dhcpsrv/network.h>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Represents a network that can be associated with a subnet.
+///
+/// This class represents a network that can be associated with a subnet
+/// using @c Subnet::setSharedNetwork method. This class is a friend
+/// of a @ref Subnet class, so it can call its @c Subnet::setSharedNetwork
+/// private method. Association of a network with a subnet must be always
+/// conducted using this class. This prevents unwanted replacements of
+/// shared networks within subnets.
+class AssignableNetwork {
+protected:
+
+    /// @brief Virtual destructor.
+    virtual ~AssignableNetwork() { }
+
+    /// @brief Returns shared pointer to this object.
+    ///
+    /// This abstract method must be implemented by derived classes to
+    /// return shared pointers the derivation.
+    ///
+    /// @return Pointer to this network.
+    virtual NetworkPtr sharedFromThis() = 0;
+
+    /// @brief Associates a subnet with this network.
+    ///
+    /// @param subnet Pointer to a subnet to be associated with the network.
+    ///
+    /// @tparam SubnetPtr Type of the subnet pointer.
+    template<typename SubnetPtr>
+    void setSharedNetwork(const SubnetPtr& subnet) {
+        subnet->setSharedNetwork(sharedFromThis());
+    }
+
+    /// @brief Removes association of a subnet with a network.
+    ///
+    /// @param subnet Pointer to a subnet for which association should be
+    /// removed.
+    ///
+    /// @tparam SubnetPtr Type of the subnet pointer.
+    template<typename SubnetPtr>
+    void clearSharedNetwork(const SubnetPtr& subnet) {
+        subnet->setSharedNetwork(NetworkPtr());
+    }
+};
+
+} // end of namespace isc::dhcp
+} // end of namespace isc
+
+#endif // ASSIGNABLE_NETWORK_H

+ 138 - 0
src/lib/dhcpsrv/cfg_shared_networks.h

@@ -0,0 +1,138 @@
+// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef CFG_SHARED_NETWORKS_H
+#define CFG_SHARED_NETWORKS_H
+
+#include <cc/cfg_to_element.h>
+#include <cc/data.h>
+#include <exceptions/exceptions.h>
+#include <dhcpsrv/shared_network.h>
+#include <boost/shared_ptr.hpp>
+#include <string>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief This class holds configuration of shared networks.
+///
+/// This is a generic class implementing basic functions such as shared network
+/// addition, removal and retrieval. It also dumps configuration in the JSON
+/// format.
+///
+/// There are specializations of this class implemented as
+/// @ref CfgSharedNetworks4 and @ref CfgSharedNetworks6 for IPv4 and IPv6 cases
+/// repspectively.
+///
+/// @tparam Type of the pointer to a shared network, i.e. @ref SharedNetwork4Ptr
+/// or @ref SharedNetwork6Ptr.
+template<typename SharedNetworkPtrType, typename SharedNetworkCollection>
+class CfgSharedNetworks : public data::CfgToElement {
+public:
+
+    /// @brief Adds new shared network to the configuration.
+    ///
+    /// @param network Pointer to a network
+    ///
+    /// @throw isc::BadValue when name is a duplicate of existing network's
+    /// name.
+    void add(const SharedNetworkPtrType& network) {
+        if (getByName(network->getName())) {
+            isc_throw(BadValue, "duplicate network '" << network->getName() <<
+                      "' found in the configuration");
+        }
+
+        networks_.push_back(network);
+    }
+
+    /// @brief Deletes shared network from the configuration.
+    ///
+    /// @param name Name of the network to be deleted.
+    ///
+    /// @throw isc::BadValue if the network can't be found.
+    void del(const std::string& name) {
+        auto& index = networks_.template get<SharedNetworkNameIndexTag>();
+        auto shared_network = index.find(name);
+        if (shared_network != index.end()) {
+            index.erase(shared_network);
+
+        } else {
+            isc_throw(BadValue, "unable to delete non-existing network '"
+                      << name << "' from shared networks configuration");
+        }
+    }
+
+    /// @brief Retrieves shared network by name.
+    ///
+    /// @param name Name of the network to be retrieved.
+    ///
+    /// @return Pointer to the shared network or null pointer if the network
+    /// is not found.
+    SharedNetworkPtrType getByName(const std::string& name) const {
+        const auto& index = networks_.template get<SharedNetworkNameIndexTag>();
+        auto shared_network = index.find(name);
+        if (shared_network != index.cend()) {
+            return (*shared_network);
+        }
+        return (SharedNetworkPtrType());
+    }
+
+    /// @brief Unparses shared networks configuration.
+    ///
+    /// @return Element object representing a list of shared networks held
+    /// within configuration. The networks are sorted by their names.
+    virtual data::ElementPtr toElement() const {
+        data::ElementPtr list = data::Element::createList();
+
+        // Insert shared networks sorted by their names into the list.
+        const auto& index = networks_.template get<SharedNetworkNameIndexTag>();
+        for (auto shared_network = index.begin(); shared_network != index.end();
+             ++shared_network) {
+            list->add((*shared_network)->toElement());
+        }
+        return (list);
+    }
+
+protected:
+
+    /// @brief Multi index container holding shared networks.
+    SharedNetworkCollection networks_;
+};
+
+/// @brief Represents configuration of IPv4 shared networks.
+class CfgSharedNetworks4 : public CfgSharedNetworks<SharedNetwork4Ptr,
+                                                    SharedNetwork4Collection> {
+public:
+
+    /// @brief Returns pointer to all configured shared networks.
+    const SharedNetwork4Collection* getAll() const {
+        return (&networks_);
+    }
+
+};
+
+/// @brief Pointer to the configuration of IPv4 shared networks.
+typedef boost::shared_ptr<CfgSharedNetworks4> CfgSharedNetworks4Ptr;
+
+/// @brief Represents configuration of IPv6 shared networks.
+class CfgSharedNetworks6 : public CfgSharedNetworks<SharedNetwork6Ptr,
+                                                    SharedNetwork6Collection> {
+public:
+
+    /// @brief Returns pointer to all configured shared networks.
+    const SharedNetwork6Collection* getAll() const {
+        return (&networks_);
+    }
+};
+
+/// @brief Pointer to the configuration of IPv6 shared networks.
+typedef boost::shared_ptr<CfgSharedNetworks6> CfgSharedNetworks6Ptr;
+
+
+} // end of namespace isc::dhcp
+} // end of namespace isc
+
+#endif // CFG_SHARED_NETWORKS_H

+ 144 - 0
src/lib/dhcpsrv/network.cc

@@ -0,0 +1,144 @@
+// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <dhcpsrv/network.h>
+
+using namespace isc::data;
+
+namespace isc {
+namespace dhcp {
+
+Network::RelayInfo::RelayInfo(const isc::asiolink::IOAddress& addr)
+    :addr_(addr) {
+}
+
+bool
+Network::clientSupported(const isc::dhcp::ClientClasses& classes) const {
+    if (white_list_.empty()) {
+        // There is no class defined for this network, so we do
+        // support everyone.
+        return (true);
+    }
+
+    for (ClientClasses::const_iterator it = white_list_.begin();
+         it != white_list_.end(); ++it) {
+        if (classes.contains(*it)) {
+            return (true);
+        }
+    }
+
+    return (false);
+}
+
+void
+Network::allowClientClass(const isc::dhcp::ClientClass& class_name) {
+    white_list_.insert(class_name);
+}
+
+ElementPtr
+Network::toElement() const {
+    ElementPtr map = Element::createMap();
+
+    // Set interface
+    const std::string& iface = getIface();
+    if (!iface.empty()) {
+        map->set("interface", Element::create(iface));
+    }
+
+    // Set relay info
+    const RelayInfo& relay_info = getRelayInfo();
+    ElementPtr relay = Element::createMap();
+    relay->set("ip-address", Element::create(relay_info.addr_.toText()));
+    map->set("relay", relay);
+
+    // Set client-class
+    const ClientClasses& cclasses = getClientClasses();
+    if (cclasses.size() > 1) {
+        isc_throw(ToElementError, "client-class has too many items: "
+                  << cclasses.size());
+    } else if (!cclasses.empty()) {
+        map->set("client-class", Element::create(*cclasses.cbegin()));
+    }
+
+    // Set renew-timer
+    map->set("renew-timer",
+             Element::create(static_cast<long long>
+                                 (getT1().get())));
+    // Set rebind-timer
+    map->set("rebind-timer",
+             Element::create(static_cast<long long>
+                                 (getT2().get())));
+    // Set valid-lifetime
+    map->set("valid-lifetime",
+             Element::create(static_cast<long long>
+                                 (getValid().get())));
+
+    // Set reservation mode
+    Network::HRMode hrmode = getHostReservationMode();
+    std::string mode;
+    switch (hrmode) {
+    case HR_DISABLED:
+        mode = "disabled";
+        break;
+    case HR_OUT_OF_POOL:
+        mode = "out-of-pool";
+        break;
+    case HR_ALL:
+        mode = "all";
+        break;
+    default:
+        isc_throw(ToElementError,
+                  "invalid host reservation mode: " << hrmode);
+    }
+    map->set("reservation-mode", Element::create(mode));
+
+    // Set options
+    ConstCfgOptionPtr opts = getCfgOption();
+    map->set("option-data", opts->toElement());
+
+    return (map);
+}
+
+ElementPtr
+Network4::toElement() const {
+    ElementPtr map = Network::toElement();
+
+    // Set match-client-id
+    map->set("match-client-id", Element::create(getMatchClientId()));
+
+    return (map);
+}
+
+ElementPtr
+Network6::toElement() const {
+    ElementPtr map = Network::toElement();
+
+    // Set preferred-lifetime
+    map->set("preferred-lifetime",
+             Element::create(static_cast<long long>
+                             (getPreferred().get())));
+
+    // Set interface-id
+    const OptionPtr& ifaceid = getInterfaceId();
+    if (ifaceid) {
+        std::vector<uint8_t> bin = ifaceid->getData();
+        std::string ifid;
+        ifid.resize(bin.size());
+        if (!bin.empty()) {
+            std::memcpy(&ifid[0], &bin[0], bin.size());
+        }
+        map->set("interface-id", Element::create(ifid));
+    }
+
+    // Set rapid-commit
+    bool rapid_commit = getRapidCommit();
+    map->set("rapid-commit", Element::create(rapid_commit));
+
+    return (map);
+}
+
+} // end of namespace isc::dhcp
+} // end of namespace isc

+ 424 - 0
src/lib/dhcpsrv/network.h

@@ -0,0 +1,424 @@
+// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef NETWORK_H
+#define NETWORK_H
+
+#include <asiolink/io_address.h>
+#include <cc/cfg_to_element.h>
+#include <cc/data.h>
+#include <dhcp/classify.h>
+#include <dhcp/option.h>
+#include <dhcpsrv/cfg_option.h>
+#include <dhcpsrv/cfg_4o6.h>
+#include <dhcpsrv/triplet.h>
+#include <boost/shared_ptr.hpp>
+#include <boost/weak_ptr.hpp>
+#include <cstdint>
+#include <string>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Common interface representing a network to which the DHCP clients
+/// are connected.
+///
+/// The most common type of network, in Kea's terminology, is a subnet. The
+/// @ref Subnet implements this interface. Another types of objects implementing
+/// this interface are @ref SharedNetwork objects. They group multiple subnets
+/// together to provide means for extending available address pools (a single
+/// client may obtain IP address from any of the pools belonging to subnets in
+/// the shared network), or for selecting a subnet on a given link, depending
+/// on the class of the client (e.g. cable network case: different subnet is
+/// selected for cable modems, different one for routers).
+///
+/// The subnets and shared networks share many data structures, e.g. DHCP
+/// options, local interface name, address manipulation methods, thus this
+/// class provides an abstract interface that must be implemented by derived
+/// classes and, where appropriate, implements common methods used by the
+/// derived classes.
+class Network : public data::CfgToElement {
+public:
+
+    /// @brief Holds optional information about relay.
+    ///
+    /// In some cases it is beneficial to have additional information about
+    /// a relay configured in the subnet. For now, the structure holds only
+    /// IP address, but there may potentially be additional parameters added
+    /// later, e.g. relay interface-id or relay-id.
+    struct RelayInfo {
+
+        /// @brief default and the only constructor
+        ///
+        /// @param addr an IP address of the relay (may be :: or 0.0.0.0)
+        RelayInfo(const isc::asiolink::IOAddress& addr);
+
+        /// @brief IP address of the relay
+        isc::asiolink::IOAddress addr_;
+    };
+
+    /// @brief Specifies allowed host reservation mode.
+    ///
+    typedef enum  {
+
+        /// None - host reservation is disabled. No reservation types
+        /// are allowed.
+        HR_DISABLED,
+
+        /// Only out-of-pool reservations is allowed. This mode
+        /// allows AllocEngine to skip reservation checks when
+        /// dealing with with addresses that are in pool.
+        HR_OUT_OF_POOL,
+
+        /// Both out-of-pool and in-pool reservations are allowed. This is the
+        /// most flexible mode, where sysadmin have biggest liberty. However,
+        /// there is a non-trivial performance penalty for it, as the
+        /// AllocEngine code has to check whether there are reservations, even
+        /// when dealing with reservations from within the dynamic pools.
+        HR_ALL
+    } HRMode;
+
+    /// Pointer to the RelayInfo structure
+    typedef boost::shared_ptr<Network::RelayInfo> RelayInfoPtr;
+
+    /// @brief Constructor.
+    Network()
+        : iface_name_(), relay_(asiolink::IOAddress::IPV4_ZERO_ADDRESS()),
+          white_list_(), t1_(0), t2_(0), valid_(0),
+          host_reservation_mode_(HR_ALL), cfg_option_(new CfgOption()) {
+    }
+
+    /// @brief Virtual destructor.
+    ///
+    /// Does nothing at the moment.
+    virtual ~Network() { };
+
+    /// @brief Sets local name of the interface for which this network is
+    /// selected.
+    ///
+    /// If the interface is specified, the server will use the network
+    /// associated with this local interface to allocate IP addresses and
+    /// other resources to a client.
+    ///
+    /// @param iface_name Interface name.
+    void setIface(const std::string& iface_name) {
+        iface_name_ = iface_name;
+    }
+
+    /// @brief Returns name of the local interface for which this network is
+    /// selected.
+    ///
+    /// @return Interface name as text.
+    std::string getIface() const {
+        return (iface_name_);
+    };
+
+    /// @brief Sets information about relay
+    ///
+    /// In some situations where there are shared subnets (i.e. two different
+    /// subnets are available on the same physical link), there is only one
+    /// relay that handles incoming requests from clients. In such a case,
+    /// the usual subnet selection criteria based on relay belonging to the
+    /// subnet being selected are no longer sufficient and we need to explicitly
+    /// specify a relay. One notable example of such uncommon, but valid
+    /// scenario is a cable network, where there is only one CMTS (one relay),
+    /// but there are 2 distinct subnets behind it: one for cable modems
+    /// and another one for CPEs and other user equipment behind modems.
+    /// From manageability perspective, it is essential that modems get addresses
+    /// from different subnet, so users won't tinker with their modems.
+    ///
+    /// Setting this parameter is not needed in most deployments.
+    /// This structure holds IP address only for now, but it is expected to
+    /// be extended in the future.
+    ///
+    /// @param relay structure that contains relay information
+    void setRelayInfo(const RelayInfo& relay) {
+        relay_ = relay;
+    }
+
+    /// @brief Returns const reference to relay information
+    ///
+    /// @note The returned reference is only valid as long as the object
+    /// returned it is valid.
+    ///
+    /// @return const reference to the relay information
+    const RelayInfo& getRelayInfo() const {
+        return (relay_);
+    }
+
+    /// @brief Checks whether this network supports client that belongs to
+    /// specified classes.
+    ///
+    /// This method checks whether a client that belongs to given classes can
+    /// use this network. For example, if this class is reserved for client
+    /// class "foo" and the client belongs to classes "foo", "bar" and "baz",
+    /// it is supported. On the other hand, client belonging to classes
+    /// "foobar" and "zyxxy" is not supported.
+    ///
+    /// @todo: Currently the logic is simple: client is supported if it belongs
+    /// to any class mentioned in white_list_. We will eventually need a
+    /// way to specify more fancy logic (e.g. to meet all classes, not just
+    /// any)
+    ///
+    /// @param client_classes list of all classes the client belongs to
+    /// @return true if client can be supported, false otherwise
+    bool
+    clientSupported(const isc::dhcp::ClientClasses& client_classes) const;
+
+    /// @brief Adds class class_name to the list of supported classes
+    ///
+    /// Also see explanation note in @ref white_list_.
+    ///
+    /// @param class_name client class to be supported by this subnet
+    void allowClientClass(const isc::dhcp::ClientClass& class_name);
+
+    /// @brief returns the client class white list
+    ///
+    /// @note The returned reference is only valid as long as the object
+    /// returned it is valid.
+    ///
+    /// @return client classes @ref white_list_
+    const isc::dhcp::ClientClasses& getClientClasses() const {
+        return (white_list_);
+    }
+
+    /// @brief Return valid-lifetime for addresses in that prefix
+    Triplet<uint32_t> getValid() const {
+        return (valid_);
+    }
+
+    /// @brief Sets new valid lifetime for a network.
+    ///
+    /// @param valid New valid lifetime in seconds.
+    void setValid(const Triplet<uint32_t>& valid) {
+        valid_ = valid;
+    }
+
+    /// @brief Returns T1 (renew timer), expressed in seconds
+    Triplet<uint32_t> getT1() const {
+        return (t1_);
+    }
+
+    /// @brief Sets new renew timer for a network.
+    ///
+    /// @param t1 New renew timer value in seconds.
+    void setT1(const Triplet<uint32_t>& t1) {
+        t1_ = t1;
+    }
+
+    /// @brief Returns T2 (rebind timer), expressed in seconds
+    Triplet<uint32_t> getT2() const {
+        return (t2_);
+    }
+
+    /// @brief Sets new rebind timer for a network.
+    ///
+    /// @param t2 New rebind timer value in seconds.
+    void setT2(const Triplet<uint32_t>& t2) {
+        t2_ = t2;
+    }
+
+    /// @brief Specifies what type of Host Reservations are supported.
+    ///
+    /// Host reservations may be either in-pool (they reserve an address that
+    /// is in the dynamic pool) or out-of-pool (they reserve an address that is
+    /// not in the dynamic pool). HR may also be completely disabled for
+    /// performance reasons.
+    ///
+    /// @return whether in-pool host reservations are allowed.
+    HRMode
+    getHostReservationMode() const {
+        return (host_reservation_mode_);
+    }
+
+    /// @brief Sets host reservation mode.
+    ///
+    /// See @ref getHostReservationMode for details.
+    ///
+    /// @param mode mode to be set
+    void setHostReservationMode(HRMode mode) {
+        host_reservation_mode_ = mode;
+    }
+
+    /// @brief Returns pointer to the option data configuration for this subnet.
+    CfgOptionPtr getCfgOption() {
+        return (cfg_option_);
+    }
+
+    /// @brief Returns const pointer to the option data configuration for this
+    /// subnet.
+    ConstCfgOptionPtr getCfgOption() const {
+        return (cfg_option_);
+    }
+
+    /// @brief Unparses network object.
+    ///
+    /// @return A pointer to unparsed network configuration.
+    virtual data::ElementPtr toElement() const;
+
+protected:
+
+    /// @brief Holds interface name for which this network is selected.
+    std::string iface_name_;
+
+    /// @brief Relay information
+    ///
+    /// See @ref RelayInfo for detailed description.
+    RelayInfo relay_;
+
+    /// @brief Optional definition of a client class
+    ///
+    /// If defined, only clients belonging to that class will be allowed to use
+    /// this particular network. The default value for this is an empty list,
+    /// which means that any client is allowed, regardless of its class.
+    ///
+    /// @todo This is just a single list of allowed classes. We'll also need
+    /// to add a black-list (only classes on the list are rejected, the rest
+    /// are allowed). Implementing this will require more fancy parser logic,
+    /// so it may be a while until we support this.
+    ClientClasses white_list_;
+
+    /// @brief a Triplet (min/default/max) holding allowed renew timer values
+    Triplet<uint32_t> t1_;
+
+    /// @brief a Triplet (min/default/max) holding allowed rebind timer values
+    Triplet<uint32_t> t2_;
+
+    /// @brief a Triplet (min/default/max) holding allowed valid lifetime values
+    Triplet<uint32_t> valid_;
+
+    /// @brief Specifies host reservation mode
+    ///
+    /// See @ref HRMode type for details.
+    HRMode host_reservation_mode_;
+
+    /// @brief Pointer to the option data configuration for this subnet.
+    CfgOptionPtr cfg_option_;
+};
+
+/// @brief Pointer to the @ref Network object.
+typedef boost::shared_ptr<Network> NetworkPtr;
+
+/// @brief Weak pointer to the @ref Network object.
+typedef boost::weak_ptr<Network> WeakNetworkPtr;
+
+/// @brief Specialization of the @ref Network object for DHCPv4 case.
+class Network4 : public Network {
+public:
+
+    /// @brief Constructor.
+    Network4()
+        : Network(), match_client_id_(true) {
+    }
+
+    /// @brief Returns the flag indicating if the client identifiers should
+    /// be used to identify the client's lease.
+    ///
+    /// @return true if client identifiers should be used, false otherwise.
+    bool getMatchClientId() const {
+        return (match_client_id_);
+    }
+
+    /// @brief Sets the flag indicating if the client identifier should be
+    /// used to identify the client's lease.
+    ///
+    /// @param match If this value is true, the client identifiers are not
+    /// used for lease lookup.
+    void setMatchClientId(const bool match) {
+        match_client_id_ = match;
+    }
+
+    /// @brief Unparses network object.
+    ///
+    /// @return A pointer to unparsed network configuration.
+    virtual data::ElementPtr toElement() const;
+
+private:
+
+    /// @brief Should server use client identifiers for client lease
+    /// lookup.
+    bool match_client_id_;
+};
+
+/// @brief Specialization of the @ref Network object for DHCPv6 case.
+class Network6 : public Network {
+public:
+
+    /// @brief Constructor.
+    Network6()
+        : Network(), preferred_(0), interface_id_(), rapid_commit_(false) {
+        setRelayInfo(asiolink::IOAddress::IPV6_ZERO_ADDRESS());
+    }
+
+    /// @brief Returns preferred lifetime (in seconds)
+    ///
+    /// @return a triplet with preferred lifetime
+    Triplet<uint32_t> getPreferred() const {
+        return (preferred_);
+    }
+
+    /// @brief Sets new preferred lifetime for a network.
+    ///
+    /// @param valid New preferred lifetime in seconds.
+    void setPreferred(const Triplet<uint32_t>& preferred) {
+        preferred_ = preferred;
+    }
+
+    /// @brief Returns interface-id value (if specified)
+    ///
+    /// @return interface-id option (if defined)
+    OptionPtr getInterfaceId() const {
+        return (interface_id_);
+    }
+
+    /// @brief sets interface-id option (if defined)
+    ///
+    /// @param ifaceid pointer to interface-id option
+    void setInterfaceId(const OptionPtr& ifaceid) {
+        interface_id_ = ifaceid;
+    }
+
+    /// @brief Returns boolean value indicating that the Rapid Commit option
+    /// is supported or unsupported for the subnet.
+    ///
+    /// @return true if the Rapid Commit option is supported, false otherwise.
+    bool getRapidCommit() const {
+        return (rapid_commit_);
+    }
+
+    /// @brief Enables or disables Rapid Commit option support for the subnet.
+    ///
+    /// @param rapid_commit A boolean value indicating that the Rapid Commit
+    /// option support is enabled (if true), or disabled (if false).
+    void setRapidCommit(const bool rapid_commit) {
+        rapid_commit_ = rapid_commit;
+    };
+
+    /// @brief Unparses network object.
+    ///
+    /// @return A pointer to unparsed network configuration.
+    virtual data::ElementPtr toElement() const;
+
+private:
+
+    /// @brief a triplet with preferred lifetime (in seconds)
+    Triplet<uint32_t> preferred_;
+
+    /// @brief specifies optional interface-id
+    OptionPtr interface_id_;
+
+    /// @brief A flag indicating if Rapid Commit option is supported
+    /// for this network.
+    ///
+    /// It's default value is false, which indicates that the Rapid
+    /// Commit is disabled for the subnet.
+    bool rapid_commit_;
+};
+
+} // end of namespace isc::dhcp
+} // end of namespace isc
+
+#endif // NETWORK_H

+ 121 - 48
src/lib/dhcpsrv/parsers/dhcp_parsers.cc

@@ -317,7 +317,7 @@ RelayInfoParser::RelayInfoParser(const Option::Universe& family)
 };
 
 void
-RelayInfoParser::parse(const isc::dhcp::Subnet::RelayInfoPtr& cfg,
+RelayInfoParser::parse(const isc::dhcp::Network::RelayInfoPtr& cfg,
                        ConstElementPtr relay_info) {
     // There is only one parameter which is mandatory
     IOAddress ip = getAddress(relay_info, "ip-address");
@@ -333,7 +333,7 @@ RelayInfoParser::parse(const isc::dhcp::Subnet::RelayInfoPtr& cfg,
 
     // Ok, we're done with parsing. Let's store the result in the structure
     // we were given as configuration storage.
-    *cfg = isc::dhcp::Subnet::RelayInfo(ip);
+    *cfg = isc::dhcp::Network::RelayInfo(ip);
 }
 
 //****************************** PoolParser ********************************
@@ -491,7 +491,7 @@ SubnetConfigParser::SubnetConfigParser(uint16_t family)
       address_family_(family),
       options_(new CfgOption()) {
     string addr = family == AF_INET ? "0.0.0.0" : "::";
-    relay_info_.reset(new isc::dhcp::Subnet::RelayInfo(IOAddress(addr)));
+    relay_info_.reset(new isc::dhcp::Network::RelayInfo(IOAddress(addr)));
 }
 
 SubnetPtr
@@ -521,15 +521,15 @@ SubnetConfigParser::parse(ConstElementPtr subnet) {
     return (subnet_);
 }
 
-Subnet::HRMode
+Network::HRMode
 SubnetConfigParser::hrModeFromText(const std::string& txt) {
     if ( (txt.compare("disabled") == 0) ||
          (txt.compare("off") == 0) )  {
-        return (Subnet::HR_DISABLED);
+        return (Network::HR_DISABLED);
     } else if (txt.compare("out-of-pool") == 0) {
-        return (Subnet::HR_OUT_OF_POOL);
+        return (Network::HR_OUT_OF_POOL);
     } else if (txt.compare("all") == 0) {
-        return (Subnet::HR_ALL);
+        return (Network::HR_ALL);
     } else {
         isc_throw(BadValue, "Can't convert '" << txt
                   << "' into any valid reservation-mode values");
@@ -585,41 +585,6 @@ SubnetConfigParser::createSubnet(ConstElementPtr params) {
                       ex.what() << " (" << params->getPosition() << ")");
         }
     }
-
-    // Now configure parameters that are common for v4 and v6:
-
-    // Get interface name. If it is defined, then the subnet is available
-    // directly over specified network interface.
-    std::string iface = getString(params, "interface");
-    if (!iface.empty()) {
-        if (!IfaceMgr::instance().getIface(iface)) {
-            ConstElementPtr error = params->get("interface");
-            isc_throw(DhcpConfigError, "Specified network interface name " << iface
-                      << " for subnet " << subnet_->toText()
-                      << " is not present in the system ("
-                      << error->getPosition() << ")");
-        }
-
-        subnet_->setIface(iface);
-    }
-
-    // Let's set host reservation mode. If not specified, the default value of
-    // all will be used.
-    try {
-        std::string hr_mode = getString(params, "reservation-mode");
-        subnet_->setHostReservationMode(hrModeFromText(hr_mode));
-    } catch (const BadValue& ex) { 
-       isc_throw(DhcpConfigError, "Failed to process specified value "
-                  " of reservation-mode parameter: " << ex.what()
-                  << "(" << getPosition("reservation-mode", params) << ")");
-    }
-
-    // Try setting up client class.
-    string client_class = getString(params, "client-class");
-    if (!client_class.empty()) {
-        subnet_->allowClientClass(client_class);
-    }
-
     // If there's user-context specified, store it.
     ConstElementPtr user_context = params->get("user-context");
     if (user_context) {
@@ -630,12 +595,6 @@ SubnetConfigParser::createSubnet(ConstElementPtr params) {
         subnet_->setContext(user_context);
     }
 
-    // Here globally defined options were merged to the subnet specific
-    // options but this is no longer the case (they have a different
-    // and not consecutive priority).
-
-    // Copy options to the subnet configuration.
-    options_->copyTo(*subnet_->getCfgOption());
 }
 
 //****************************** Subnet4ConfigParser *************************
@@ -749,6 +708,38 @@ Subnet4ConfigParser::initSubnet(data::ConstElementPtr params,
                   << next_server << "(" << pos << ")");
     }
 
+    // Get interface name. If it is defined, then the subnet is available
+    // directly over specified network interface.
+    std::string iface = getString(params, "interface");
+    if (!iface.empty()) {
+        if (!IfaceMgr::instance().getIface(iface)) {
+            ConstElementPtr error = params->get("interface");
+            isc_throw(DhcpConfigError, "Specified network interface name " << iface
+                      << " for subnet " << subnet4->toText()
+                      << " is not present in the system ("
+                      << error->getPosition() << ")");
+        }
+
+        subnet4->setIface(iface);
+    }
+
+    // Let's set host reservation mode. If not specified, the default value of
+    // all will be used.
+    try {
+        std::string hr_mode = getString(params, "reservation-mode");
+        subnet4->setHostReservationMode(hrModeFromText(hr_mode));
+    } catch (const BadValue& ex) { 
+       isc_throw(DhcpConfigError, "Failed to process specified value "
+                  " of reservation-mode parameter: " << ex.what()
+                  << "(" << getPosition("reservation-mode", params) << ")");
+    }
+
+    // Try setting up client class.
+    string client_class = getString(params, "client-class");
+    if (!client_class.empty()) {
+        subnet4->allowClientClass(client_class);
+    }
+
     // 4o6 specific parameter: 4o6-interface. If not explicitly specified,
     // it will have the default value of "".
     string iface4o6 = getString(params, "4o6-interface");
@@ -791,6 +782,13 @@ Subnet4ConfigParser::initSubnet(data::ConstElementPtr params,
 
     /// client-class processing is now generic and handled in the common
     /// code (see isc::data::SubnetConfigParser::createSubnet)
+
+    // Here globally defined options were merged to the subnet specific
+    // options but this is no longer the case (they have a different
+    // and not consecutive priority).
+
+    // Copy options to the subnet configuration.
+    options_->copyTo(*subnet4->getCfgOption());
 }
 
 //**************************** Subnets4ListConfigParser **********************
@@ -819,6 +817,28 @@ Subnets4ListConfigParser::parse(SrvConfigPtr cfg, ConstElementPtr subnets_list)
     return (cnt);
 }
 
+size_t
+Subnets4ListConfigParser::parse(Subnet4Collection& subnets,
+                                data::ConstElementPtr subnets_list) {
+    size_t cnt = 0;
+    BOOST_FOREACH(ConstElementPtr subnet_json, subnets_list->listValue()) {
+
+        Subnet4ConfigParser parser;
+        Subnet4Ptr subnet = parser.parse(subnet_json);
+        if (subnet) {
+            try {
+                subnets.push_back(subnet);
+                ++cnt;
+            } catch (const std::exception& ex) {
+                isc_throw(DhcpConfigError, ex.what() << " ("
+                          << subnet_json->getPosition() << ")");
+            }
+        }
+    }
+    return (cnt);
+}
+
+
 //**************************** Pool6Parser *********************************
 
 PoolPtr
@@ -1044,8 +1064,42 @@ Subnet6ConfigParser::initSubnet(data::ConstElementPtr params,
         subnet6->setInterfaceId(opt);
     }
 
+    // Get interface name. If it is defined, then the subnet is available
+    // directly over specified network interface.
+    if (!iface.empty()) {
+        if (!IfaceMgr::instance().getIface(iface)) {
+            ConstElementPtr error = params->get("interface");
+            isc_throw(DhcpConfigError, "Specified network interface name " << iface
+                      << " for subnet " << subnet6->toText()
+                      << " is not present in the system ("
+                      << error->getPosition() << ")");
+        }
+
+        subnet6->setIface(iface);
+    }
+
+    // Let's set host reservation mode. If not specified, the default value of
+    // all will be used.
+    try {
+        std::string hr_mode = getString(params, "reservation-mode");
+        subnet6->setHostReservationMode(hrModeFromText(hr_mode));
+    } catch (const BadValue& ex) { 
+       isc_throw(DhcpConfigError, "Failed to process specified value "
+                  " of reservation-mode parameter: " << ex.what()
+                  << "(" << getPosition("reservation-mode", params) << ")");
+    }
+
+    // Try setting up client class.
+    string client_class = getString(params, "client-class");
+    if (!client_class.empty()) {
+        subnet6->allowClientClass(client_class);
+    }
+
     /// client-class processing is now generic and handled in the common
     /// code (see isc::data::SubnetConfigParser::createSubnet)
+
+    // Copy options to the subnet configuration.
+    options_->copyTo(*subnet6->getCfgOption());
 }
 
 //**************************** Subnet6ListConfigParser ********************
@@ -1072,6 +1126,25 @@ Subnets6ListConfigParser::parse(SrvConfigPtr cfg, ConstElementPtr subnets_list)
     return (cnt);
 }
 
+size_t
+Subnets6ListConfigParser::parse(Subnet6Collection& subnets,
+                                ConstElementPtr subnets_list) {
+    size_t cnt = 0;
+    BOOST_FOREACH(ConstElementPtr subnet_json, subnets_list->listValue()) {
+
+        Subnet6ConfigParser parser;
+        Subnet6Ptr subnet = parser.parse(subnet_json);
+        try {
+            subnets.push_back(subnet);
+            ++cnt;
+        } catch (const std::exception& ex) {
+            isc_throw(DhcpConfigError, ex.what() << " ("
+                      << subnet_json->getPosition() << ")");
+        }
+    }
+    return (cnt);
+}
+
 
 //**************************** D2ClientConfigParser **********************
 

+ 21 - 3
src/lib/dhcpsrv/parsers/dhcp_parsers.h

@@ -14,6 +14,7 @@
 #include <dhcpsrv/d2_client_cfg.h>
 #include <dhcpsrv/cfg_iface.h>
 #include <dhcpsrv/cfg_option.h>
+#include <dhcpsrv/network.h>
 #include <dhcpsrv/subnet.h>
 #include <dhcpsrv/cfg_option_def.h>
 #include <dhcpsrv/cfg_mac_source.h>
@@ -521,7 +522,7 @@ public:
     ///
     /// @param cfg configuration will be stored here
     /// @param relay_info JSON structure holding relay parameters to parse
-    void parse(const isc::dhcp::Subnet::RelayInfoPtr& cfg,
+    void parse(const isc::dhcp::Network::RelayInfoPtr& cfg,
                isc::data::ConstElementPtr relay_info);
 
 private:
@@ -593,7 +594,7 @@ protected:
     /// @throw BadValue if the text cannot be converted.
     ///
     /// @return one of allowed HRMode values
-    static Subnet::HRMode hrModeFromText(const std::string& txt);
+    static Network::HRMode hrModeFromText(const std::string& txt);
 
 private:
 
@@ -616,7 +617,7 @@ protected:
     uint16_t address_family_;
 
     /// Pointer to relay information
-    isc::dhcp::Subnet::RelayInfoPtr relay_info_;
+    isc::dhcp::Network::RelayInfoPtr relay_info_;
 
     /// Pointer to the options configuration.
     CfgOptionPtr options_;
@@ -672,6 +673,14 @@ public:
     /// @param subnets_list pointer to a list of IPv4 subnets
     /// @return number of subnets created
     size_t parse(SrvConfigPtr cfg, data::ConstElementPtr subnets_list);
+
+    /// @brief Parses contents of the subnet4 list.
+    ///
+    /// @param [out] subnets Container where parsed subnets will be stored.
+    /// @param subnets_list pointer to a list of IPv4 subnets
+    /// @return Number of subnets created.
+    size_t parse(Subnet4Collection& subnets,
+                 data::ConstElementPtr subnets_list);
 };
 
 /// @brief Parser for IPv6 pool definitions.
@@ -849,6 +858,15 @@ public:
     /// @param subnets_list pointer to a list of IPv6 subnets
     /// @throw DhcpConfigError if CfgMgr rejects the subnet (e.g. subnet-id is a duplicate)
     size_t parse(SrvConfigPtr cfg, data::ConstElementPtr subnets_list);
+
+    /// @brief Parses contents of the subnet6 list.
+    ///
+    /// @param [out] subnets Container where parsed subnets will be stored.
+    /// @param subnets_list pointer to a list of IPv6 subnets
+    /// @return Number of subnets created.
+    size_t parse(Subnet6Collection& subnets,
+                 data::ConstElementPtr subnets_list);
+
 };
 
 /// @brief Parser for  D2ClientConfig

+ 1 - 0
src/lib/dhcpsrv/parsers/option_data_parser.h

@@ -9,6 +9,7 @@
 
 #include <cc/data.h>
 #include <cc/simple_parser.h>
+#include <dhcp/option_definition.h>
 #include <dhcpsrv/cfg_option.h>
 #include <util/optional_value.h>
 #include <cstdint>

+ 117 - 0
src/lib/dhcpsrv/parsers/shared_network_parser.cc

@@ -0,0 +1,117 @@
+// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef SHARED_NETWORK_PARSER_H
+#define SHARED_NETWORK_PARSER_H
+
+#include <cc/data.h>
+#include <dhcpsrv/cfg_option.h>
+#include <dhcpsrv/parsers/dhcp_parsers.h>
+#include <dhcpsrv/parsers/option_data_parser.h>
+#include <dhcpsrv/parsers/shared_network_parser.h>
+#include <dhcpsrv/shared_network.h>
+#include <boost/pointer_cast.hpp>
+#include <string>
+
+using namespace isc::data;
+
+namespace isc {
+namespace dhcp {
+
+SharedNetwork4Ptr
+SharedNetwork4Parser::parse(const data::ConstElementPtr& shared_network_data) {
+    SharedNetwork4Ptr shared_network;
+    try {
+        // Make sure that the network name has been specified. The name is required
+        // to create a SharedNetwork4 object.
+        std::string name = getString(shared_network_data, "name");
+        shared_network.reset(new SharedNetwork4(name));
+
+        // interface is an optional parameter
+        if (shared_network_data->contains("interface")) {
+            shared_network->setIface(getString(shared_network_data, "interface"));
+        }
+
+        if (shared_network_data->contains("option-data")) {
+            auto json = shared_network_data->get("option-data");
+            // Create parser instance for option-data.
+            CfgOptionPtr cfg_option = shared_network->getCfgOption();
+            OptionDataListParser parser(AF_INET);
+            parser.parse(cfg_option, json);
+        }
+
+        if (shared_network_data->contains("subnet4")) {
+            auto json = shared_network_data->get("subnet4");
+
+            // Create parser instance of subnet4.
+            Subnets4ListConfigParser parser;
+            Subnet4Collection subnets;
+            parser.parse(subnets, json);
+
+            // Add all returned subnets into shared network.
+            for (auto subnet = subnets.cbegin(); subnet != subnets.cend();
+                 ++subnet) {
+                shared_network->add(*subnet);
+            }
+        }
+    } catch (const std::exception& ex) {
+        isc_throw(DhcpConfigError, ex.what() << " ("
+                  << shared_network_data->getPosition() << ")");
+    }
+
+    return (shared_network);
+}
+
+SharedNetwork6Ptr
+SharedNetwork6Parser::parse(const data::ConstElementPtr& shared_network_data) {
+    SharedNetwork6Ptr shared_network;
+    std::string name;
+    try {
+        // Make sure that the network name has been specified. The name is required
+        // to create a SharedNetwork6 object.
+        std::string name = getString(shared_network_data, "name");
+        shared_network.reset(new SharedNetwork6(name));
+
+        // Interface is an optional parameter
+        if (shared_network_data->contains("interface")) {
+            shared_network->setIface(getString(shared_network_data, "interface"));
+        }
+
+        if (shared_network_data->contains("option-data")) {
+            auto json = shared_network_data->get("option-data");
+            // Create parser instance for option-data.
+            CfgOptionPtr cfg_option = shared_network->getCfgOption();
+            OptionDataListParser parser(AF_INET6);
+            parser.parse(cfg_option, json);
+        }
+
+        if (shared_network_data->contains("subnet6")) {
+            auto json = shared_network_data->get("subnet6");
+
+            // Create parser instance of subnet6.
+            Subnets6ListConfigParser parser;
+            Subnet6Collection subnets;
+            parser.parse(subnets, json);
+
+            // Add all returned subnets into shared network.
+            for (auto subnet = subnets.cbegin(); subnet != subnets.cend();
+                 ++subnet) {
+                shared_network->add(*subnet);
+            }
+        }
+
+    } catch (const std::exception& ex) {
+        isc_throw(DhcpConfigError, ex.what() << " ("
+                  << shared_network_data->getPosition() << ")");
+    }
+
+    return (shared_network);
+}
+
+} // end of namespace isc::dhcp
+} // end of namespace isc
+
+#endif // SHARED_NETWORK_PARSER_H

+ 52 - 0
src/lib/dhcpsrv/parsers/shared_network_parser.h

@@ -0,0 +1,52 @@
+// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef SHARED_SUBNET_PARSER_H
+#define SHARED_SUBNET_PARSER_H
+
+#include <cc/data.h>
+#include <cc/simple_parser.h>
+#include <dhcpsrv/cfg_subnets4.h>
+#include <dhcpsrv/cfg_subnets6.h>
+#include <dhcpsrv/shared_network.h>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Implements parser for IPv4 shared networks.
+class SharedNetwork4Parser : isc::data::SimpleParser {
+public:
+
+    /// @brief Parses shared configuration information for IPv4 shared network.
+    ///
+    /// @param shared_network_data Data element holding shared network
+    /// configuration to be parsed.
+    ///
+    /// @return Pointer to an object representing shared network.
+    /// @throw DhcpConfigError when shared network configuration is invalid.
+    SharedNetwork4Ptr
+    parse(const data::ConstElementPtr& shared_network_data);
+};
+
+/// @brief Implements parser for IPv6 shared networks.
+class SharedNetwork6Parser : isc::data::SimpleParser {
+public:
+
+    /// @brief Parses shared configuration information for IPv6 shared network.
+    ///
+    /// @param shared_network_data Data element holding shared network
+    /// configuration to be parsed.
+    ///
+    /// @return Pointer to an object representing shared network.
+    /// @throw DhcpConfigError when shared network configuration is invalid.
+    SharedNetwork6Ptr
+    parse(const data::ConstElementPtr& shared_network_data);
+};
+
+} // enf of namespace isc::dhcp
+} // end of namespace isc
+
+#endif // SHARED_SUBNET_PARSER_H

+ 82 - 0
src/lib/dhcpsrv/parsers/shared_networks_list_parser.h

@@ -0,0 +1,82 @@
+// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef SHARED_NETWORKS_LIST_PARSER_H
+#define SHARED_NETWORKS_LIST_PARSER_H
+
+#include <cc/data.h>
+#include <cc/dhcp_config_error.h>
+#include <cc/simple_parser.h>
+#include <exceptions/exceptions.h>
+#include <dhcpsrv/subnet.h>
+#include <dhcpsrv/parsers/shared_network_parser.h>
+#include <vector>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Parser for a list of shared networks.
+///
+/// This is a generic parser for a list of IPv4 or IPv6 shared networks.
+///
+/// @tparam SharedNetworkParserType Type of the parser to be used for
+/// parsing shared network, i.e. @ref SharedNetwork4Parser or
+/// @ref SharedNetwork6Parser.
+template<typename SharedNetworkParserType>
+class SharedNetworksListParser : public data::SimpleParser {
+public:
+
+    /// @brief Parses a list of shared networks.
+    ///
+    /// @param [out] cfg Shared networks configuration structure into which
+    /// the data should be parsed.
+    /// @param shared_networks_list_data List element holding a list of
+    /// shared networks.
+    /// @tparam Type of the configuration structure into which the result
+    /// will be stored, i.e. @ref CfgSharedNetworks4 or @ref CfgSharedNetworks6.
+    ///
+    /// @throw DhcpConfigError when error has occurred, e.g. when networks
+    /// with duplicated names have been specified.
+    template<typename CfgSharedNetworksTypePtr>
+    void parse(CfgSharedNetworksTypePtr& cfg,
+               const data::ConstElementPtr& shared_networks_list_data) {
+        try {
+            // Get the C++ vector holding networks.
+            const std::vector<data::ElementPtr>& networks_list =
+                shared_networks_list_data->listValue();
+            // Iterate over all networks and do the parsing.
+            for (auto network_element = networks_list.cbegin();
+                 network_element != networks_list.cend(); ++network_element) {
+                SharedNetworkParserType parser;
+                auto network = parser.parse(*network_element);
+                cfg->add(network);
+            }
+        } catch (const DhcpConfigError&) {
+            // Such exceptions are emitted by the lower level parsers and
+            // errors should already include element's positions. So, we
+            // simply rethrow.
+            throw;
+
+        } catch (const std::exception& ex) {
+            // Other exceptions don't include positions of the elements, so
+            // we should append one.
+            isc_throw(DhcpConfigError, ex.what() << " ("
+                      << shared_networks_list_data->getPosition() << ")");
+        }
+    }
+};
+
+/// @brief Type of the shared networks list parser for IPv4.
+typedef SharedNetworksListParser<SharedNetwork4Parser> SharedNetworks4ListParser;
+
+/// @brief Type of the shared networks list parser for IPv6.
+typedef SharedNetworksListParser<SharedNetwork6Parser> SharedNetworks6ListParser;
+
+
+} // end of namespace isc::dhcp
+} // end of namespace isc
+
+#endif // SHARED_NETWORKS_LIST_PARSER_H

+ 311 - 0
src/lib/dhcpsrv/shared_network.cc

@@ -0,0 +1,311 @@
+// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <exceptions/exceptions.h>
+#include <dhcpsrv/shared_network.h>
+
+using namespace isc;
+using namespace isc::data;
+using namespace isc::dhcp;
+
+namespace {
+
+/// @brief Implements common functionality for SharedNetwork4 and
+/// SharedNetwork6 classes.
+///
+/// It provides mechanisms to add, remove and find subnets within shared
+/// networks. It also provides means to walk over the subnets within a
+/// shared network.
+class Impl {
+public:
+
+    /// @brief Adds a subnet to a shared network.
+    ///
+    /// This is a generic method for adding a new subnet to a shared network.
+    ///
+    /// @param [out] subnets Container holding subnets for this shared network.
+    /// @param subnet Pointer to a subnet being added to this shared network.
+    ///
+    /// @tparam SubnetPtrType Type of a pointer to a subnet, i.e. Subnet4Ptr
+    /// or @ref Subnet6Ptr.
+    /// @tparam SubnetCollectionType Type of a container holding subnets, i.e.
+    /// @ref Subnet4Collection or @ref Subnet6Collection.
+    ///
+    /// @throw isc::BadValue if subnet is null.
+    /// @throw isc::DuplicateSubnetID if a subnet with the given subnet id
+    /// already exists in this shared network.
+    /// @throw InvalidOperation if a subnet is already associated with some
+    /// shared network.
+    template<typename SubnetPtrType, typename SubnetCollectionType>
+    static void add(SubnetCollectionType& subnets, const SubnetPtrType& subnet) {
+        //  Subnet must be non-null.
+        if (!subnet) {
+            isc_throw(BadValue, "null pointer specified when adding a subnet"
+                      " to a shared network");
+        }
+
+        // Check if a subnet with this id already exists.
+        if (getSubnet<SubnetPtrType>(subnets, subnet->getID())) {
+            isc_throw(DuplicateSubnetID, "attempted to add subnet with a"
+                      " duplicated subnet identifier " << subnet->getID());
+        }
+
+        // Check if the subnet is already associated with some network.
+        NetworkPtr network;
+        subnet->getSharedNetwork(network);
+        if (network) {
+            isc_throw(InvalidOperation, "subnet " << subnet->getID()
+                      << " being added to a shared network"
+                      " already belongs to a shared network");
+        }
+
+        // Add a subnet to the collection of subnets for this shared network.
+        subnets.push_back(subnet);
+    }
+
+    /// @brief Removes a subnet from the shared network.
+    ///
+    /// @param [out] subnets Container holding subnets for this shared network.
+    /// @param subnet_id Identifier of a subnet to be removed.
+    ///
+    /// @tparam SubnetCollectionType Type of a container holding subnets, i.e.
+    /// @ref Subnet4Collection or @ref Subnet6Collection.
+    ///
+    /// @return Erased subnet.
+    /// @throw BadValue if a subnet with specified identifier doesn't exist.
+    template<typename SubnetPtrType, typename SubnetCollectionType>
+    static SubnetPtrType del(SubnetCollectionType& subnets,
+                             const SubnetID& subnet_id) {
+        auto& index = subnets.template get<SubnetSubnetIdIndexTag>();
+        auto subnet_it = index.find(subnet_id);
+        if (subnet_it == index.end()) {
+            isc_throw(BadValue, "unable to delete subnet " << subnet_id
+                      << " from shared network. Subnet doesn't belong"
+                      " to this shared network");
+        }
+        auto subnet = *subnet_it;
+        index.erase(subnet_it);
+        return (subnet);
+    }
+
+    /// @brief Returns a subnet belonging to this network for a given subnet id.
+    ///
+    /// @param subnets Container holding subnets for this shared network.
+    /// @param subnet_id Identifier of a subnet being retrieved.
+    ///
+    /// @tparam SubnetPtrType Type of a pointer to a subnet, i.e. Subnet4Ptr
+    /// or @ref Subnet6Ptr.
+    /// @tparam SubnetCollectionType Type of a container holding subnets, i.e.
+    /// @ref Subnet4Collection or @ref Subnet6Collection.
+    ///
+    /// @return Pointer to the subnet or null if the subnet doesn't exist.
+    template<typename SubnetPtrType, typename SubnetCollectionType>
+    static SubnetPtrType getSubnet(const SubnetCollectionType& subnets,
+                                   const SubnetID& subnet_id) {
+        const auto& index = subnets.template get<SubnetSubnetIdIndexTag>();
+        auto subnet_it = index.find(subnet_id);
+        if (subnet_it != index.cend()) {
+            return (*subnet_it);
+        }
+
+        // Subnet not found.
+        return (SubnetPtrType());
+    }
+
+    /// @brief Retrieves next available subnet within shared network.
+    ///
+    /// This method returns next available subnet within a shared network.
+    /// The subnets are ordered and retrieved using random access index
+    /// (first in/first out). The next subnet means next in turn after
+    /// the current subnet, which is specified as an argument. A caller
+    /// can iterate over all subnets starting from any of the subnets
+    /// belonging to a shared network. This subnet is called here as
+    /// a first subnet and is also specified as a method argument. When the
+    /// method detects that the next available subnet is a first subnet, it
+    /// returns a null pointer to indicate that there are no more subnets
+    /// available.
+    ///
+    /// The typical use case for this method is to allow DHCP server's
+    /// allocation engine to walk over the available subnets within a shared
+    /// network, starting from a subnet that has been selected during the
+    /// "subnet selection" processing step. In some cases the allocation
+    /// engine is unable to allocate resources from a selected subnet due
+    /// to client classification restrictions or address shortage within
+    /// its pools. It then uses this mechanism to move to another subnet
+    /// belonging to the same shared network.
+    ///
+    /// @param subnets Container holding subnets belonging to this shared
+    /// network.
+    /// @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
+    /// to be found.
+    ///
+    /// @tparam SubnetPtrType Type of the pointer to a subnet, i.e.
+    /// @ref Subnet4Ptr or @ref Subnet6Ptr.
+    /// @tparam SubnetCollectionType Type of the container holding subnets, i.e.
+    /// @ref Subnet4Collection or @ref Subnet6Collection.
+    ///
+    /// @return Pointer to next subnet or null pointer if no more subnets found.
+    ///
+    /// @throw isc::BadValue if invalid arguments specified, e.g. unable to
+    /// find first or current subnet within the container.
+    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");
+        }
+
+        // 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()) {
+            return (SubnetPtrType());
+        }
+
+        // Need to retrieve an iterator to the current subnet first. The
+        // 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());
+        if (subnet_id_it == index.cend()) {
+            isc_throw(BadValue, "no such subnet " << current_subnet->getID()
+                      << " within shared network");
+        }
+
+        // We need to transform this iterator (by subnet id) to a random access
+        // index iterator. Multi index container has a nice way of doing it.
+        auto subnet_it = subnets.template project<SubnetRandomAccessIndexTag>(subnet_id_it);
+
+        // Step to a next subnet within random access index.
+        if (++subnet_it == subnets.cend()) {
+            // If we reached the end of the container, start over from the
+            // beginning.
+            subnet_it = subnets.cbegin();
+        }
+
+        // Check if we have made a full circle. If we did, return a null pointer
+        // to indicate that there are no more subnets.
+        if ((*subnet_it)->getID() == first_subnet->getID()) {
+            return (SubnetPtrType());
+        }
+
+        // Got the next subnet, so return it.
+        return (*subnet_it);
+    }
+};
+
+} // end of anonymous namespace
+
+namespace isc {
+namespace dhcp {
+
+NetworkPtr
+SharedNetwork4::sharedFromThis() {
+    return (shared_from_this());
+}
+
+void
+SharedNetwork4::add(const Subnet4Ptr& subnet) {
+    Impl::add(subnets_, subnet);
+    // Associate the subnet with this network.
+    setSharedNetwork(subnet);
+}
+
+void
+SharedNetwork4::del(const SubnetID& subnet_id) {
+    Subnet4Ptr subnet = Impl::del<Subnet4Ptr>(subnets_, subnet_id);
+    clearSharedNetwork(subnet);
+}
+
+Subnet4Ptr
+SharedNetwork4::getSubnet(const SubnetID& subnet_id) const {
+    return (Impl::getSubnet<Subnet4Ptr>(subnets_, subnet_id));
+}
+
+Subnet4Ptr
+SharedNetwork4::getNextSubnet(const Subnet4Ptr& first_subnet,
+                              const Subnet4Ptr& current_subnet) const {
+    return (Impl::getNextSubnet(subnets_, first_subnet, current_subnet));
+}
+
+ElementPtr
+SharedNetwork4::toElement() const {
+    ElementPtr map = Network4::toElement();
+
+    // Set shared network name.
+    if (!name_.empty()) {
+        map->set("name", Element::create(name_));
+    }
+
+    ElementPtr subnet4 = Element::createList();
+    for (auto subnet = subnets_.cbegin(); subnet != subnets_.cend(); ++subnet) {
+        subnet4->add((*subnet)->toElement());
+    }
+
+    map->set("subnet4", subnet4);
+
+    return (map);
+}
+
+NetworkPtr
+SharedNetwork6::sharedFromThis() {
+    return (shared_from_this());
+}
+
+void
+SharedNetwork6::add(const Subnet6Ptr& subnet) {
+    Impl::add(subnets_, subnet);
+    // Associate the subnet with this network.
+    setSharedNetwork(subnet);
+}
+
+void
+SharedNetwork6::del(const SubnetID& subnet_id) {
+    Subnet6Ptr subnet = Impl::del<Subnet6Ptr>(subnets_, subnet_id);
+    clearSharedNetwork(subnet);
+}
+
+Subnet6Ptr
+SharedNetwork6::getSubnet(const SubnetID& subnet_id) const {
+    return (Impl::getSubnet<Subnet6Ptr>(subnets_, subnet_id));
+}
+
+Subnet6Ptr
+SharedNetwork6::getNextSubnet(const Subnet6Ptr& first_subnet,
+                              const Subnet6Ptr& current_subnet) const {
+    return (Impl::getNextSubnet(subnets_, first_subnet,
+                                         current_subnet));
+}
+
+ElementPtr
+SharedNetwork6::toElement() const {
+    ElementPtr map = Network6::toElement();
+
+    // Set shared network name.
+    if (!name_.empty()) {
+        map->set("name", Element::create(name_));
+    }
+
+    ElementPtr subnet6 = Element::createList();
+    for (auto subnet = subnets_.cbegin(); subnet != subnets_.cend(); ++subnet) {
+        subnet6->add((*subnet)->toElement());
+    }
+
+    map->set("subnet6", subnet6);
+
+    return (map);
+}
+
+} // end of namespace isc::dhcp
+} // end of namespace isc

+ 282 - 0
src/lib/dhcpsrv/shared_network.h

@@ -0,0 +1,282 @@
+// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef SHARED_NETWORK_H
+#define SHARED_NETWORK_H
+
+#include <cc/data.h>
+#include <exceptions/exceptions.h>
+#include <dhcpsrv/assignable_network.h>
+#include <dhcpsrv/subnet.h>
+#include <dhcpsrv/subnet_id.h>
+#include <boost/enable_shared_from_this.hpp>
+#include <boost/multi_index/mem_fun.hpp>
+#include <boost/multi_index/indexed_by.hpp>
+#include <boost/multi_index/ordered_index.hpp>
+#include <boost/multi_index/random_access_index.hpp>
+#include <boost/multi_index_container.hpp>
+#include <boost/shared_ptr.hpp>
+#include <string>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief A tag for accessing random access index.
+struct SharedNetworkRandomAccessIndexTag { };
+
+/// @brief A tag for accessing index by shared network name.
+struct SharedNetworkNameIndexTag { };
+
+/// @brief Shared network holding IPv4 subnets.
+///
+/// Specialization of the @ref Network4 class for IPv4 shared networks.
+class SharedNetwork4 : public Network4,
+                       public boost::enable_shared_from_this<SharedNetwork4>,
+                       public AssignableNetwork {
+public:
+
+    /// @brief Constructor.
+    ///
+    /// Sets name of the shared network.
+    explicit SharedNetwork4(const std::string& name)
+        : name_(name), subnets_() {
+    }
+
+    /// @brief Returns shared pointer to this network.
+    ///
+    /// This method is required by the parent @ref AssignableNetwork class.
+    ///
+    /// @return Shared pointer to this object.
+    virtual NetworkPtr sharedFromThis();
+
+    /// @brief Returns a name of the shared network.
+    std::string getName() const {
+        return (name_);
+    }
+
+    /// @brief Sets new name for the shared network.
+    ///
+    /// @param name New name for the shared network.
+    void setName(const std::string& name) {
+        name_ = name;
+    }
+
+    /// @brief Adds IPv4 subnet to a shared network.
+    ///
+    /// @param subnet Pointer to a subnet being added to this shared network.
+    ///
+    /// @throw isc::BadValue if subnet is null.
+    /// @throw isc::DuplicateSubnetID if a subnet with the given subnet id
+    /// already exists in this shared network.
+    /// @throw InvalidOperation if a subnet is already associated with some
+    /// shared network.
+    void add(const Subnet4Ptr& subnet);
+
+    /// @brief Removes subnet from a shared network.
+    ///
+    /// @param subnet_id Identifier of a subnet to be removed.
+    ///
+    /// @throw BadValue When specified subnet doesn't exist.
+    void del(const SubnetID& subnet_id);
+
+    /// @brief Returns a pointer to the collection of subnets within this
+    /// shared network.
+    const Subnet4Collection* getAllSubnets() const {
+        return (&subnets_);
+    }
+
+    /// @brief Returns a subnet for a specified subnet id.
+    ///
+    /// @param subnet_id Subnet identifier.
+    ///
+    /// @return Shared pointer to a subnet using this id or null pointer
+    /// if such subnet doesn't exist within shared network.
+    Subnet4Ptr getSubnet(const SubnetID& subnet_id) const;
+
+    /// @brief Retrieves next available IPv4 subnet within shared network.
+    ///
+    /// See documentation for @ref SharedNetwork::getNextSubnet.
+    ///
+    /// @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
+    /// to be found.
+    ///
+    /// @return Pointer to next subnet or null pointer if no more subnets found.
+    ///
+    /// @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;
+
+    /// @brief Unparses shared network object.
+    ///
+    /// @return A pointer to unparsed shared network configuration.
+    virtual data::ElementPtr toElement() const;
+
+private:
+
+    /// @brief Holds a name of a shared network.
+    std::string name_;
+
+    /// @brief Collection of IPv4 subnets within shared network.
+    Subnet4Collection subnets_;
+};
+
+/// @brief Pointer to @ref SharedNetwork4 object.
+typedef boost::shared_ptr<SharedNetwork4> SharedNetwork4Ptr;
+
+/// @brief Multi index container holding shared networks.
+///
+/// This is multi index container can hold pointers to @ref SharedNetwork4
+/// objects. It provides indexes for shared network lookups using properties
+/// such as shared network's name.
+typedef boost::multi_index_container<
+    // Multi index container holds pointers to the shared networks.
+    SharedNetwork4Ptr,
+    boost::multi_index::indexed_by<
+        // First is the random access index allowing for accessing objects
+        // just like we'd do with vector.
+        boost::multi_index::random_access<
+            boost::multi_index::tag<SharedNetworkRandomAccessIndexTag>
+        >,
+        // Second index allows for access by shared network's name.
+        boost::multi_index::ordered_unique<
+            boost::multi_index::tag<SharedNetworkNameIndexTag>,
+            boost::multi_index::const_mem_fun<SharedNetwork4, std::string,
+                                              &SharedNetwork4::getName>
+        >
+    >
+> SharedNetwork4Collection;
+
+/// @brief Shared network holding IPv6 subnets.
+///
+/// Specialization of the @ref Network6 class for IPv6 shared networks.
+class SharedNetwork6 : public Network6,
+                       public boost::enable_shared_from_this<SharedNetwork6>,
+                       public AssignableNetwork {
+public:
+
+    /// @brief Constructor.
+    ///
+    /// Sets name of the shared network.
+    explicit SharedNetwork6(const std::string& name)
+        : name_(name), subnets_() {
+    }
+
+    /// @brief Returns shared pointer to this network.
+    ///
+    /// This method is required by the parent @ref AssignableNetwork class.
+    ///
+    /// @return Shared pointer to this object.
+    virtual NetworkPtr sharedFromThis();
+
+    /// @brief Returns a name of the shared network.
+    std::string getName() const {
+        return (name_);
+    }
+
+    /// @brief Sets new name for the shared network.
+    ///
+    /// @param name New name for the shared network.
+    void setName(const std::string& name) {
+        name_ = name;
+    }
+
+    /// @brief Adds IPv6 subnet to a shared network.
+    ///
+    /// @param subnet Pointer to a subnet being added to this shared network.
+    ///
+    /// @throw isc::BadValue if subnet is null.
+    /// @throw isc::DuplicateSubnetID if a subnet with the given subnet id
+    /// already exists in this shared network.
+    /// @throw InvalidOperation if a subnet is already associated with some
+    /// shared network.
+    void add(const Subnet6Ptr& subnet);
+
+    /// @brief Removes subnet from a shared network.
+    ///
+    /// @param subnet_id Identifier of a subnet to be removed.
+    ///
+    /// @throw BadValue When specified subnet doesn't exist.
+    void del(const SubnetID& subnet_id);
+
+    /// @brief Returns a pointer to the collection of subnets within this
+    /// shared network.
+    const Subnet6Collection* getAllSubnets() const {
+        return (&subnets_);
+    }
+
+    /// @brief Returns a subnet for a specified subnet id.
+    ///
+    /// @param subnet_id Subnet identifier.
+    ///
+    /// @return Shared pointer to a subnet using this id or null pointer
+    /// if such subnet doesn't exist within shared network.
+    Subnet6Ptr getSubnet(const SubnetID& subnet_id) const;
+
+    /// @brief Retrieves next available IPv6 subnet within shared network.
+    ///
+    /// See documentation for @ref SharedNetwork::getNextSubnet.
+    ///
+    /// @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
+    /// to be found.
+    ///
+    /// @return Pointer to next subnet or null pointer if no more subnets found.
+    ///
+    /// @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;
+
+    /// @brief Unparses shared network object.
+    ///
+    /// @return A pointer to unparsed shared network configuration.
+    virtual data::ElementPtr toElement() const;
+
+private:
+
+    /// @brief Holds a name of a shared network.
+    std::string name_;
+
+    /// @brief Collection of IPv6 subnets within shared network.
+    Subnet6Collection subnets_;
+};
+
+/// @brief Pointer to @ref SharedNetwork6 object.
+typedef boost::shared_ptr<SharedNetwork6> SharedNetwork6Ptr;
+
+/// @brief Multi index container holding shared networks.
+///
+/// This is multi index container can hold pointers to @ref SharedNetwork6
+/// objects. It provides indexes for shared network lookups using properties
+/// such as shared network's name.
+typedef boost::multi_index_container<
+    // Multi index container holds pointers to the shared networks.
+    SharedNetwork6Ptr,
+    boost::multi_index::indexed_by<
+        // First is the random access index allowing for accessing objects
+        // just like we'd do with vector.
+        boost::multi_index::random_access<
+            boost::multi_index::tag<SharedNetworkRandomAccessIndexTag>
+        >,
+        // Second index allows for access by shared network's name.
+        boost::multi_index::ordered_unique<
+            boost::multi_index::tag<SharedNetworkNameIndexTag>,
+            boost::multi_index::const_mem_fun<SharedNetwork6, std::string,
+                                              &SharedNetwork6::getName>
+        >
+    >
+> SharedNetwork6Collection;
+
+} // end of namespace isc::dhcp
+} // end of namespace isc
+
+#endif // SHARED_NETWORK_H

+ 13 - 1
src/lib/dhcpsrv/srv_config.cc

@@ -25,6 +25,8 @@ SrvConfig::SrvConfig()
     : sequence_(0), cfg_iface_(new CfgIface()),
       cfg_option_def_(new CfgOptionDef()), cfg_option_(new CfgOption()),
       cfg_subnets4_(new CfgSubnets4()), cfg_subnets6_(new CfgSubnets6()),
+      cfg_shared_networks4_(new CfgSharedNetworks4()),
+      cfg_shared_networks6_(new CfgSharedNetworks6()),
       cfg_hosts_(new CfgHosts()), cfg_rsoo_(new CfgRSOO()),
       cfg_expiration_(new CfgExpiration()), cfg_duid_(new CfgDUID()),
       cfg_db_access_(new CfgDbAccess()),
@@ -39,6 +41,8 @@ SrvConfig::SrvConfig(const uint32_t sequence)
     : sequence_(sequence), cfg_iface_(new CfgIface()),
       cfg_option_def_(new CfgOptionDef()), cfg_option_(new CfgOption()),
       cfg_subnets4_(new CfgSubnets4()), cfg_subnets6_(new CfgSubnets6()),
+      cfg_shared_networks4_(new CfgSharedNetworks4()),
+      cfg_shared_networks6_(new CfgSharedNetworks6()),
       cfg_hosts_(new CfgHosts()), cfg_rsoo_(new CfgRSOO()),
       cfg_expiration_(new CfgExpiration()), cfg_duid_(new CfgDUID()),
       cfg_db_access_(new CfgDbAccess()),
@@ -228,14 +232,22 @@ SrvConfig::toElement() const {
     dhcp->set("option-def", cfg_option_def_->toElement());
     // Set option-data
     dhcp->set("option-data", cfg_option_->toElement());
-    // Set subnets
+
+    // Set subnets and shared networks.
     ConstElementPtr subnets;
     if (family == AF_INET) {
         subnets = cfg_subnets4_->toElement();
         dhcp->set("subnet4", subnets);
+
+        ConstElementPtr shared_networks = cfg_shared_networks4_->toElement();
+        dhcp->set("shared-networks", shared_networks);
+
     } else {
         subnets = cfg_subnets6_->toElement();
         dhcp->set("subnet6", subnets);
+
+        ConstElementPtr shared_networks = cfg_shared_networks6_->toElement();
+        dhcp->set("shared-networks", shared_networks);
     }
     // Insert reservations
     CfgHostsList resv_list;

+ 25 - 0
src/lib/dhcpsrv/srv_config.h

@@ -17,6 +17,7 @@
 #include <dhcpsrv/cfg_option.h>
 #include <dhcpsrv/cfg_option_def.h>
 #include <dhcpsrv/cfg_rsoo.h>
+#include <dhcpsrv/cfg_shared_networks.h>
 #include <dhcpsrv/cfg_subnets4.h>
 #include <dhcpsrv/cfg_subnets6.h>
 #include <dhcpsrv/cfg_mac_source.h>
@@ -201,6 +202,24 @@ public:
         return (cfg_subnets4_);
     }
 
+    /// @brief Returns pointer to non-const object holding configuration of
+    /// shared networks in DHCPv4;
+    ///
+    /// @return Pointer to the object holding shared networks configuration
+    /// for DHCPv4.
+    CfgSharedNetworks4Ptr getCfgSharedNetworks4() const {
+        return (cfg_shared_networks4_);
+    }
+
+    /// @brief Returns pointer to non-const object holding configuration of
+    /// shared networks in DHCPv6.
+    ///
+    /// @return Pointer to the object holding shared networks configuration
+    /// for DHCPv6.
+    CfgSharedNetworks6Ptr getCfgSharedNetworks6() const {
+        return (cfg_shared_networks6_);
+    }
+
     /// @brief Returns pointer to const object holding subnets configuration for
     /// DHCPv4.
     ///
@@ -573,6 +592,12 @@ private:
     /// @brief Pointer to subnets configuration for IPv6.
     CfgSubnets6Ptr cfg_subnets6_;
 
+    /// @brief Pointer to IPv4 shared networks configuration.
+    CfgSharedNetworks4Ptr cfg_shared_networks4_;
+
+    /// @brief Pointer to IPv4 shared networks configuration.
+    CfgSharedNetworks6Ptr cfg_shared_networks6_;
+
     /// @brief Pointer to the configuration for hosts reservation.
     ///
     /// This object holds a list of @c Host objects representing host

+ 26 - 135
src/lib/dhcpsrv/subnet.cc

@@ -50,18 +50,12 @@ namespace dhcp {
 SubnetID Subnet::static_id_ = 1;
 
 Subnet::Subnet(const isc::asiolink::IOAddress& prefix, uint8_t len,
-               const Triplet<uint32_t>& t1,
-               const Triplet<uint32_t>& t2,
-               const Triplet<uint32_t>& valid_lifetime,
-               const isc::dhcp::Subnet::RelayInfo& relay,
                const SubnetID id)
-    :id_(id == 0 ? generateNextID() : id), prefix_(prefix), prefix_len_(len),
-     t1_(t1), t2_(t2), valid_(valid_lifetime),
-     last_allocated_ia_(lastAddrInPrefix(prefix, len)),
-     last_allocated_ta_(lastAddrInPrefix(prefix, len)),
-     last_allocated_pd_(lastAddrInPrefix(prefix, len)), relay_(relay),
-     host_reservation_mode_(HR_ALL), cfg_option_(new CfgOption())
-      {
+    : id_(id == 0 ? generateNextID() : id), prefix_(prefix),
+      prefix_len_(len),
+      last_allocated_ia_(lastAddrInPrefix(prefix, len)),
+      last_allocated_ta_(lastAddrInPrefix(prefix, len)),
+      last_allocated_pd_(lastAddrInPrefix(prefix, len)) {
     if ((prefix.isV6() && len > 128) ||
         (prefix.isV4() && len > 32)) {
         isc_throw(BadValue,
@@ -69,10 +63,6 @@ Subnet::Subnet(const isc::asiolink::IOAddress& prefix, uint8_t len,
     }
 }
 
-Subnet::RelayInfo::RelayInfo(const isc::asiolink::IOAddress& addr)
-    :addr_(addr) {
-}
-
 bool
 Subnet::inRange(const isc::asiolink::IOAddress& addr) const {
     IOAddress first = firstAddrInPrefix(prefix_, prefix_len_);
@@ -81,33 +71,6 @@ Subnet::inRange(const isc::asiolink::IOAddress& addr) const {
     return ((first <= addr) && (addr <= last));
 }
 
-void
-Subnet::setRelayInfo(const isc::dhcp::Subnet::RelayInfo& relay) {
-    relay_ = relay;
-}
-
-bool
-Subnet::clientSupported(const isc::dhcp::ClientClasses& classes) const {
-    if (white_list_.empty()) {
-        return (true); // There is no class defined for this subnet, so we do
-                       // support everyone.
-    }
-
-    for (ClientClasses::const_iterator it = white_list_.begin();
-         it != white_list_.end(); ++it) {
-        if (classes.contains(*it)) {
-            return (true);
-        }
-    }
-
-    return (false);
-}
-
-void
-Subnet::allowClientClass(const isc::dhcp::ClientClass& class_name) {
-    white_list_.insert(class_name);
-}
-
 isc::asiolink::IOAddress Subnet::getLastAllocated(Lease::Type type) const {
     // check if the type is valid (and throw if it isn't)
     checkType(type);
@@ -199,12 +162,18 @@ Subnet4::Subnet4(const isc::asiolink::IOAddress& prefix, uint8_t length,
                  const Triplet<uint32_t>& t2,
                  const Triplet<uint32_t>& valid_lifetime,
                  const SubnetID id)
-    : Subnet(prefix, length, t1, t2, valid_lifetime, RelayInfo(IOAddress("0.0.0.0")), id),
-      siaddr_(IOAddress("0.0.0.0")), match_client_id_(true) {
+    : Subnet(prefix, length, id), Network4(),
+      siaddr_(IOAddress("0.0.0.0")) {
     if (!prefix.isV4()) {
         isc_throw(BadValue, "Non IPv4 prefix " << prefix.toText()
                   << " specified in subnet4");
     }
+    // Relay info.
+    setRelayInfo(IOAddress::IPV4_ZERO_ADDRESS());
+    // Timers.
+    setT1(t1);
+    setT2(t2);
+    setValid(valid_lifetime);
 }
 
 void Subnet4::setSiaddr(const isc::asiolink::IOAddress& siaddr) {
@@ -354,16 +323,6 @@ Subnet::delPools(Lease::Type type) {
     getPoolsWritable(type).clear();
 }
 
-void
-Subnet::setIface(const std::string& iface_name) {
-    iface_ = iface_name;
-}
-
-std::string
-Subnet::getIface() const {
-    return (iface_);
-}
-
 bool
 Subnet::inPool(Lease::Type type, const isc::asiolink::IOAddress& addr) const {
 
@@ -456,12 +415,19 @@ Subnet6::Subnet6(const isc::asiolink::IOAddress& prefix, uint8_t length,
                  const Triplet<uint32_t>& preferred_lifetime,
                  const Triplet<uint32_t>& valid_lifetime,
                  const SubnetID id)
-    :Subnet(prefix, length, t1, t2, valid_lifetime, RelayInfo(IOAddress("::")), id),
-     preferred_(preferred_lifetime), rapid_commit_(false) {
+    : Subnet(prefix, length, id), Network6() {
     if (!prefix.isV6()) {
         isc_throw(BadValue, "Non IPv6 prefix " << prefix
                   << " specified in subnet6");
     }
+
+    // Relay info.
+    setRelayInfo(RelayInfo(IOAddress::IPV6_ZERO_ADDRESS()));
+    // Timers.
+    setT1(t1);
+    setT2(t2);
+    setPreferred(preferred_lifetime);
+    setValid(valid_lifetime);
 }
 
 void Subnet6::checkType(Lease::Type type) const {
@@ -475,73 +441,15 @@ void Subnet6::checkType(Lease::Type type) const {
 
 data::ElementPtr
 Subnet::toElement() const {
-    // Prepare the map
     ElementPtr map = Element::createMap();
 
     // Set subnet id
     SubnetID id = getID();
     map->set("id", Element::create(static_cast<long long>(id)));
 
-    // Set relay info
-    const Subnet::RelayInfo& relay_info = getRelayInfo();
-    ElementPtr relay = Element::createMap();
-    relay->set("ip-address", Element::create(relay_info.addr_.toText()));
-    map->set("relay", relay);
-
     // Set subnet
     map->set("subnet", Element::create(toText()));
 
-    // Set interface
-    const std::string& iface = getIface();
-    if (!iface.empty()) {
-        map->set("interface", Element::create(iface));
-    }
-
-    // Set renew-timer
-    map->set("renew-timer",
-             Element::create(static_cast<long long>
-                                 (getT1().get())));
-    // Set rebind-timer
-    map->set("rebind-timer",
-             Element::create(static_cast<long long>
-                                 (getT2().get())));
-    // Set valid-lifetime
-    map->set("valid-lifetime",
-             Element::create(static_cast<long long>
-                                 (getValid().get())));
-
-    // Set reservation mode
-    Subnet::HRMode hrmode = getHostReservationMode();
-    std::string mode;
-    switch (hrmode) {
-    case Subnet::HR_DISABLED:
-        mode = "disabled";
-        break;
-    case Subnet::HR_OUT_OF_POOL:
-        mode = "out-of-pool";
-        break;
-    case Subnet::HR_ALL:
-        mode = "all";
-        break;
-    default:
-        isc_throw(ToElementError,
-                  "invalid host reservation mode: " << hrmode);
-    }
-    map->set("reservation-mode", Element::create(mode));
-
-    // Set client-class
-    const ClientClasses& cclasses = getClientClasses();
-    if (cclasses.size() > 1) {
-        isc_throw(ToElementError, "client-class has too many items: "
-                  << cclasses.size());
-    } else if (!cclasses.empty()) {
-        map->set("client-class", Element::create(*cclasses.cbegin()));
-    }
-
-    // Set options
-    ConstCfgOptionPtr opts = getCfgOption();
-    map->set("option-data", opts->toElement());
-
     // Add user-context, but only if defined. Omit if it was not.
     ConstElementPtr ctx = getContext();
     if (ctx) {
@@ -555,9 +463,9 @@ data::ElementPtr
 Subnet4::toElement() const {
     // Prepare the map
     ElementPtr map = Subnet::toElement();
+    ElementPtr network_map = Network4::toElement();
 
-    // Set match-client-id
-    map->set("match-client-id", Element::create(getMatchClientId()));
+    merge(map, network_map);
 
     // Set DHCP4o6
     const Cfg4o6& d4o6 = get4o6();
@@ -583,26 +491,9 @@ data::ElementPtr
 Subnet6::toElement() const {
     // Prepare the map
     ElementPtr map = Subnet::toElement();
+    ElementPtr network_map = Network6::toElement();
 
-    // Set interface-id
-    const OptionPtr& ifaceid = getInterfaceId();
-    if (ifaceid) {
-        std::vector<uint8_t> bin = ifaceid->getData();
-        std::string ifid;
-        ifid.resize(bin.size());
-        if (!bin.empty()) {
-            std::memcpy(&ifid[0], &bin[0], bin.size());
-        }
-        map->set("interface-id", Element::create(ifid));
-    } 
-
-    // Set preferred-lifetime
-    map->set("preferred-lifetime",
-             Element::create(static_cast<long long>
-                             (getPreferred().get())));
-    // Set rapid-commit
-    bool rapid_commit = getRapidCommit();
-    map->set("rapid-commit", Element::create(rapid_commit));
+    merge(map, network_map);
 
     // Set pools
     const PoolCollection& pools = getPools(Lease::TYPE_NA);

+ 36 - 277
src/lib/dhcpsrv/subnet.h

@@ -9,11 +9,8 @@
 
 #include <asiolink/io_address.h>
 #include <cc/data.h>
-#include <dhcp/option.h>
-#include <dhcp/classify.h>
 #include <dhcp/option_space_container.h>
-#include <dhcpsrv/cfg_option.h>
-#include <dhcpsrv/cfg_4o6.h>
+#include <dhcpsrv/assignable_network.h>
 #include <dhcpsrv/lease.h>
 #include <dhcpsrv/pool.h>
 #include <dhcpsrv/subnet_id.h>
@@ -24,54 +21,19 @@
 #include <boost/multi_index/ordered_index.hpp>
 #include <boost/multi_index/random_access_index.hpp>
 #include <boost/multi_index_container.hpp>
+#include <boost/pointer_cast.hpp>
 #include <boost/shared_ptr.hpp>
 
 namespace isc {
 namespace dhcp {
 
-class Subnet {
-public:
-
-    /// @brief Holds optional information about relay.
-    ///
-    /// In some cases it is beneficial to have additional information about
-    /// a relay configured in the subnet. For now, the structure holds only
-    /// IP address, but there may potentially be additional parameters added
-    /// later, e.g. relay interface-id or relay-id.
-    struct RelayInfo {
-
-        /// @brief default and the only constructor
-        ///
-        /// @param addr an IP address of the relay (may be :: or 0.0.0.0)
-        RelayInfo(const isc::asiolink::IOAddress& addr);
-
-        /// @brief IP address of the relay
-        isc::asiolink::IOAddress addr_;
-    };
+class Subnet : public data::CfgToElement {
 
-    /// @brief Specifies allowed host reservation mode.
-    ///
-    typedef enum  {
-
-        /// None - host reservation is disabled. No reservation types
-        /// are allowed.
-        HR_DISABLED,
-
-        /// Only out-of-pool reservations is allowed. This mode
-        /// allows AllocEngine to skip reservation checks when
-        /// dealing with with addresses that are in pool.
-        HR_OUT_OF_POOL,
+    // Assignable network is our friend to allow it to call
+    // @ref Subnet::setSharedNetwork private function.
+    friend class AssignableNetwork;
 
-        /// Both out-of-pool and in-pool reservations are allowed. This is the
-        /// most flexible mode, where sysadmin have biggest liberty. However,
-        /// there is a non-trivial performance penalty for it, as the
-        /// AllocEngine code has to check whether there are reservations, even
-        /// when dealing with reservations from within the dynamic pools.
-        HR_ALL
-    } HRMode;
-
-    /// Pointer to the RelayInfo structure
-    typedef boost::shared_ptr<Subnet::RelayInfo> RelayInfoPtr;
+public:
 
     /// @brief checks if specified address is in range
     bool inRange(const isc::asiolink::IOAddress& addr) const;
@@ -92,32 +54,6 @@ public:
     /// @return true if the address is in any of the pools
     bool inPool(Lease::Type type, const isc::asiolink::IOAddress& addr) const;
 
-    /// @brief Return valid-lifetime for addresses in that prefix
-    Triplet<uint32_t> getValid() const {
-        return (valid_);
-    }
-
-    /// @brief Returns T1 (renew timer), expressed in seconds
-    Triplet<uint32_t> getT1() const {
-        return (t1_);
-    }
-
-    /// @brief Returns T2 (rebind timer), expressed in seconds
-    Triplet<uint32_t> getT2() const {
-        return (t2_);
-    }
-
-    /// @brief Returns pointer to the option data configuration for this subnet.
-    CfgOptionPtr getCfgOption() {
-        return (cfg_option_);
-    }
-
-    /// @brief Returns const pointer to the option data configuration for this
-    /// subnet.
-    ConstCfgOptionPtr getCfgOption() const {
-        return (cfg_option_);
-    }
-
     /// @brief returns the last address that was tried from this pool
     ///
     /// This method returns the last address that was attempted to be allocated
@@ -233,16 +169,6 @@ public:
     /// @param type type of the lease
     uint64_t getPoolCapacity(Lease::Type type) const;
 
-    /// @brief Sets name of the network interface for directly attached networks
-    ///
-    /// @param iface_name name of the interface
-    void setIface(const std::string& iface_name);
-
-    /// @brief Network interface name used to reach subnet (or "" for remote
-    /// subnets)
-    /// @return network interface name for directly attached subnets or ""
-    std::string getIface() const;
-
     /// @brief Returns textual representation of the subnet (e.g.
     /// "2001:db8::/64")
     ///
@@ -258,96 +184,42 @@ public:
         static_id_ = 1;
     }
 
-    /// @brief Sets information about relay
-    ///
-    /// In some situations where there are shared subnets (i.e. two different
-    /// subnets are available on the same physical link), there is only one
-    /// relay that handles incoming requests from clients. In such a case,
-    /// the usual subnet selection criteria based on relay belonging to the
-    /// subnet being selected are no longer sufficient and we need to explicitly
-    /// specify a relay. One notable example of such uncommon, but valid
-    /// scenario is a cable network, where there is only one CMTS (one relay),
-    /// but there are 2 distinct subnets behind it: one for cable modems
-    /// and another one for CPEs and other user equipment behind modems.
-    /// From manageability perspective, it is essential that modems get addresses
-    /// from different subnet, so users won't tinker with their modems.
+    /// @brief Retrieves pointer to a shared network associated with a subnet.
     ///
-    /// Setting this parameter is not needed in most deployments.
-    /// This structure holds IP address only for now, but it is expected to
-    /// be extended in the future.
+    /// By implementing it as a template function we overcome a need to
+    /// include shared_network.h header file to specify return type explicitly.
+    /// The header can't be included because it would cause circular dependency
+    /// between subnet.h and shared_network.h.
     ///
-    /// @param relay structure that contains relay information
-    void setRelayInfo(const isc::dhcp::Subnet::RelayInfo& relay);
-
-
-    /// @brief Returns const reference to relay information
+    /// This method uses an argument to hold a return value to allow the compiler
+    /// to infer the return type without a need to call this function with an
+    /// explicit return type as template argument.
     ///
-    /// @note The returned reference is only valid as long as the object
-    /// returned it is valid.
+    /// @param [out] shared_network Pointer to the shared network where returned
+    /// value should be assigned.
     ///
-    /// @return const reference to the relay information
-    const isc::dhcp::Subnet::RelayInfo& getRelayInfo() const {
-        return (relay_);
+    /// @tparam Type of the shared network, i.e. @ref SharedNetwork4 or a
+    /// @ref SharedNetwork6.
+    template<typename SharedNetworkPtrType>
+    void getSharedNetwork(SharedNetworkPtrType& shared_network) const {
+        shared_network = boost::dynamic_pointer_cast<
+            typename SharedNetworkPtrType::element_type>(shared_network_.lock());
     }
 
-    /// @brief checks whether this subnet supports client that belongs to
-    ///        specified classes.
-    ///
-    /// This method checks whether a client that belongs to given classes can
-    /// use this subnet. For example, if this class is reserved for client
-    /// class "foo" and the client belongs to classes "foo", "bar" and "baz",
-    /// it is supported. On the other hand, client belonging to classes
-    /// "foobar" and "zyxxy" is not supported.
-    ///
-    /// @todo: Currently the logic is simple: client is supported if it belongs
-    /// to any class mentioned in white_list_. We will eventually need a
-    /// way to specify more fancy logic (e.g. to meet all classes, not just
-    /// any)
-    ///
-    /// @param client_classes list of all classes the client belongs to
-    /// @return true if client can be supported, false otherwise
-    bool
-    clientSupported(const isc::dhcp::ClientClasses& client_classes) const;
-
-    /// @brief adds class class_name to the list of supported classes
-    ///
-    /// Also see explanation note in @ref white_list_.
-    ///
-    /// @param class_name client class to be supported by this subnet
-    void
-    allowClientClass(const isc::dhcp::ClientClass& class_name);
-
-    /// @brief returns the client class white list
-    ///
-    /// @note The returned reference is only valid as long as the object
-    /// returned it is valid.
-    ///
-    /// @return client classes @ref white_list_
-    const isc::dhcp::ClientClasses& getClientClasses() const {
-        return (white_list_);
-    }
+private:
 
-    /// @brief Specifies what type of Host Reservations are supported.
+    /// @brief Assigns shared network to a subnet.
     ///
-    /// Host reservations may be either in-pool (they reserve an address that
-    /// is in the dynamic pool) or out-of-pool (they reserve an address that is
-    /// not in the dynamic pool). HR may also be completely disabled for
-    /// performance reasons.
+    /// This method replaces any shared network associated with a subnet with
+    /// a new shared network.
     ///
-    /// @return whether in-pool host reservations are allowed.
-    HRMode
-    getHostReservationMode() const {
-        return (host_reservation_mode_);
+    /// @param shared_network Pointer to a new shared network to be associated
+    /// with the subnet.
+    void setSharedNetwork(const NetworkPtr& shared_network) {
+        shared_network_ = shared_network;
     }
 
-    /// @brief Sets host reservation mode.
-    ///
-    /// See @ref getHostReservationMode for details.
-    ///
-    /// @param mode mode to be set
-    void setHostReservationMode(HRMode mode) {
-        host_reservation_mode_ = mode;
-    }
+public:
 
     /// @brief Sets user context.
     /// @param ctx user context to be stored.
@@ -381,17 +253,9 @@ protected:
     ///
     /// @param prefix subnet prefix
     /// @param len prefix length for the subnet
-    /// @param t1 T1 (renewal-time) timer, expressed in seconds
-    /// @param t2 T2 (rebind-time) timer, expressed in seconds
-    /// @param valid_lifetime valid lifetime of leases in this subnet (in seconds)
-    /// @param relay optional relay information (currently with address only)
     /// @param id arbitrary subnet id, value of 0 triggers autogeneration
     /// of subnet id
     Subnet(const isc::asiolink::IOAddress& prefix, uint8_t len,
-           const Triplet<uint32_t>& t1,
-           const Triplet<uint32_t>& t2,
-           const Triplet<uint32_t>& valid_lifetime,
-           const isc::dhcp::Subnet::RelayInfo& relay,
            const SubnetID id);
 
     /// @brief virtual destructor
@@ -472,15 +336,6 @@ protected:
     /// @brief a prefix length of the subnet
     uint8_t prefix_len_;
 
-    /// @brief a triplet (min/default/max) holding allowed renew timer values
-    Triplet<uint32_t> t1_;
-
-    /// @brief a triplet (min/default/max) holding allowed rebind timer values
-    Triplet<uint32_t> t2_;
-
-    /// @brief a triplet (min/default/max) holding allowed valid lifetime values
-    Triplet<uint32_t> valid_;
-
     /// @brief last allocated address
     ///
     /// This is the last allocated address that was previously allocated from
@@ -505,37 +360,11 @@ protected:
     /// @brief Name of the network interface (if connected directly)
     std::string iface_;
 
-    /// @brief Relay information
-    ///
-    /// See @ref RelayInfo for detailed description. This structure is public,
-    /// so its fields are easily accessible. Making it protected would bring in
-    /// the issue of returning references that may become stale after its parent
-    /// subnet object disappears.
-    RelayInfo relay_;
-
-    /// @brief optional definition of a client class
-    ///
-    /// If defined, only clients belonging to that class will be allowed to use
-    /// this particular subnet. The default value for this is an empty list,
-    /// which means that any client is allowed, regardless of its class.
-    ///
-    /// @todo This is just a single list of allowed classes. We'll also need
-    /// to add a black-list (only classes on the list are rejected, the rest
-    /// are allowed). Implementing this will require more fancy parser logic,
-    /// so it may be a while until we support this.
-    ClientClasses white_list_;
-
-    /// @brief Specifies host reservation mode
-    ///
-    /// See @ref HRMode type for details.
-    HRMode host_reservation_mode_;
+    /// @brief Pointer to a shared network that subnet belongs to.
+    WeakNetworkPtr shared_network_;
 
     /// @brief Pointer to the user context (may be NULL)
     data::ConstElementPtr user_context_;
-private:
-
-    /// @brief Pointer to the option data configuration for this subnet.
-    CfgOptionPtr cfg_option_;
 };
 
 /// @brief A generic pointer to either Subnet4 or Subnet6 object
@@ -545,7 +374,7 @@ typedef boost::shared_ptr<Subnet> SubnetPtr;
 /// @brief A configuration holder for IPv4 subnet.
 ///
 /// This class represents an IPv4 subnet.
-class Subnet4 : public Subnet {
+class Subnet4 : public Subnet, public Network4 {
 public:
 
     /// @brief Constructor with all parameters
@@ -577,23 +406,6 @@ public:
     /// @return siaddr value
     isc::asiolink::IOAddress getSiaddr() const;
 
-    /// @brief Sets the flag indicating if the client identifier should be
-    /// used to identify the client's lease.
-    ///
-    /// @param match If this value is true, the client identifiers are not
-    /// used for lease lookup.
-    void setMatchClientId(const bool match) {
-        match_client_id_ = match;
-    }
-
-    /// @brief Returns the flag indicating if the client identifiers should
-    /// be used to identify the client's lease.
-    ///
-    /// @return true if client identifiers should be used, false otherwise.
-    bool getMatchClientId() const {
-        return (match_client_id_);
-    }
-
     /// @brief Returns DHCP4o6 configuration parameters.
     ///
     /// This structure is always available. If the 4o6 is not enabled, its
@@ -634,10 +446,6 @@ private:
     /// @brief siaddr value for this subnet
     isc::asiolink::IOAddress siaddr_;
 
-    /// @brief Should server use client identifiers for client lease
-    /// lookup.
-    bool match_client_id_;
-
     /// @brief All the information related to DHCP4o6
     Cfg4o6 dhcp4o6_;
 };
@@ -652,7 +460,7 @@ typedef boost::shared_ptr<Subnet4> Subnet4Ptr;
 /// @brief A configuration holder for IPv6 subnet.
 ///
 /// This class represents an IPv6 subnet.
-class Subnet6 : public Subnet {
+class Subnet6 : public Subnet, public Network6 {
 public:
 
     /// @brief Constructor with all parameters
@@ -674,42 +482,6 @@ public:
             const Triplet<uint32_t>& valid_lifetime,
             const SubnetID id = 0);
 
-    /// @brief Returns preferred lifetime (in seconds)
-    ///
-    /// @return a triplet with preferred lifetime
-    Triplet<uint32_t> getPreferred() const {
-        return (preferred_);
-    }
-
-    /// @brief sets interface-id option (if defined)
-    ///
-    /// @param ifaceid pointer to interface-id option
-    void setInterfaceId(const OptionPtr& ifaceid) {
-        interface_id_ = ifaceid;
-    }
-
-    /// @brief returns interface-id value (if specified)
-    /// @return interface-id option (if defined)
-    OptionPtr getInterfaceId() const {
-        return interface_id_;
-    }
-
-    /// @brief Enables or disables Rapid Commit option support for the subnet.
-    ///
-    /// @param rapid_commit A boolean value indicating that the Rapid Commit
-    /// option support is enabled (if true), or disabled (if false).
-    void setRapidCommit(const bool rapid_commit) {
-        rapid_commit_ = rapid_commit;
-    };
-
-    /// @brief Returns boolean value indicating that the Rapid Commit option
-    /// is supported or unsupported for the subnet.
-    ///
-    /// @return true if the Rapid Commit option is supported, false otherwise.
-    bool getRapidCommit() const {
-        return (rapid_commit_);
-    }
-
     /// @brief Unparse a subnet object.
     ///
     /// @return A pointer to unparsed subnet configuration.
@@ -731,19 +503,6 @@ private:
     /// @throw BadValue if invalid value is used
     virtual void checkType(Lease::Type type) const;
 
-    /// @brief specifies optional interface-id
-    OptionPtr interface_id_;
-
-    /// @brief a triplet with preferred lifetime (in seconds)
-    Triplet<uint32_t> preferred_;
-
-    /// @brief A flag indicating if Rapid Commit option is supported
-    /// for this subnet.
-    ///
-    /// It's default value is false, which indicates that the Rapid
-    /// Commit is disabled for the subnet.
-    bool rapid_commit_;
-
 };
 
 /// @brief A const pointer to a @c Subnet6 object.

+ 5 - 0
src/lib/dhcpsrv/tests/Makefile.am

@@ -74,6 +74,8 @@ libdhcpsrv_unittests_SOURCES += cfg_mac_source_unittest.cc
 libdhcpsrv_unittests_SOURCES += cfg_option_unittest.cc
 libdhcpsrv_unittests_SOURCES += cfg_option_def_unittest.cc
 libdhcpsrv_unittests_SOURCES += cfg_rsoo_unittest.cc
+libdhcpsrv_unittests_SOURCES += cfg_shared_networks4_unittest.cc
+libdhcpsrv_unittests_SOURCES += cfg_shared_networks6_unittest.cc
 libdhcpsrv_unittests_SOURCES += cfg_subnets4_unittest.cc
 libdhcpsrv_unittests_SOURCES += cfg_subnets6_unittest.cc
 libdhcpsrv_unittests_SOURCES += cfgmgr_unittest.cc
@@ -121,6 +123,9 @@ if HAVE_CQL
 libdhcpsrv_unittests_SOURCES += cql_lease_mgr_unittest.cc
 endif
 libdhcpsrv_unittests_SOURCES += pool_unittest.cc
+libdhcpsrv_unittests_SOURCES += shared_network_parser_unittest.cc
+libdhcpsrv_unittests_SOURCES += shared_network_unittest.cc
+libdhcpsrv_unittests_SOURCES += shared_networks_list_parser_unittest.cc
 libdhcpsrv_unittests_SOURCES += srv_config_unittest.cc
 libdhcpsrv_unittests_SOURCES += subnet_unittest.cc
 libdhcpsrv_unittests_SOURCES += test_get_callout_handle.cc test_get_callout_handle.h

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

@@ -1709,13 +1709,13 @@ TEST_F(AllocEngine4Test, findReservation) {
 
     // Regardless of the host reservation mode, the host should be
     // always returned when findReservation() is called.
-    subnet_->setHostReservationMode(Subnet::HR_DISABLED);
+    subnet_->setHostReservationMode(Network::HR_DISABLED);
     ASSERT_NO_THROW(engine.findReservation(ctx));
     EXPECT_TRUE(ctx.host_);
     EXPECT_EQ(ctx.host_->getIPv4Reservation(), host->getIPv4Reservation());
 
     // Check the third possible reservation mode.
-    subnet_->setHostReservationMode(Subnet::HR_OUT_OF_POOL);
+    subnet_->setHostReservationMode(Network::HR_OUT_OF_POOL);
     ASSERT_NO_THROW(engine.findReservation(ctx));
     EXPECT_TRUE(ctx.host_);
     EXPECT_EQ(ctx.host_->getIPv4Reservation(), host->getIPv4Reservation());

+ 122 - 0
src/lib/dhcpsrv/tests/cfg_shared_networks4_unittest.cc

@@ -0,0 +1,122 @@
+// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+#include <exceptions/exceptions.h>
+#include <dhcpsrv/cfg_shared_networks.h>
+#include <testutils/test_to_element.h>
+#include <gtest/gtest.h>
+
+using namespace isc;
+using namespace isc::dhcp;
+
+namespace {
+
+// This test verifies that shared networks can be added to the configruation
+// and retrieved by name.
+TEST(CfgSharedNetworks4Test, getByName) {
+    SharedNetwork4Ptr network1(new SharedNetwork4("frog"));
+    SharedNetwork4Ptr network2(new SharedNetwork4("dog"));
+
+    CfgSharedNetworks4 cfg;
+    ASSERT_NO_THROW(cfg.add(network1));
+    ASSERT_NO_THROW(cfg.add(network2));
+
+    SharedNetwork4Ptr returned_network1 = cfg.getByName("frog");
+    ASSERT_TRUE(returned_network1);
+    SharedNetwork4Ptr returned_network2 = cfg.getByName("dog");
+    ASSERT_TRUE(returned_network2);
+
+    // Check that non-existent name does not return bogus data.
+    EXPECT_FALSE(cfg.getByName("ant"));
+}
+
+// This test verifies that it is possible to delete a network.
+TEST(CfgSharedNetworks4Test, deleteByName) {
+    SharedNetwork4Ptr network1(new SharedNetwork4("frog"));
+    SharedNetwork4Ptr network2(new SharedNetwork4("dog"));
+
+    // Add two networks to the configuration.
+    CfgSharedNetworks4 cfg;
+    ASSERT_NO_THROW(cfg.add(network1));
+    ASSERT_NO_THROW(cfg.add(network2));
+
+    // Try to delete non-existing network. This should throw.
+    ASSERT_THROW(cfg.del("lion"), BadValue);
+
+    // Delete network #1.
+    ASSERT_NO_THROW(cfg.del(network1->getName()));
+    ASSERT_FALSE(cfg.getByName(network1->getName()));
+    ASSERT_TRUE(cfg.getByName(network2->getName()));
+
+    // Delete network #2.
+    ASSERT_NO_THROW(cfg.del(network2->getName()));
+    ASSERT_FALSE(cfg.getByName(network1->getName()));
+    ASSERT_FALSE(cfg.getByName(network2->getName()));
+
+    // Check that attempting to delete the same subnet twice will fail.
+    ASSERT_THROW(cfg.del(network1->getName()), BadValue);
+    ASSERT_THROW(cfg.del(network2->getName()), BadValue);
+}
+
+// This test verifies that shared networks must have unique names.
+TEST(CfgSharedNetworks4Test, duplicateName) {
+    SharedNetwork4Ptr network1(new SharedNetwork4("frog"));
+    SharedNetwork4Ptr network2(new SharedNetwork4("frog"));
+
+    CfgSharedNetworks4 cfg;
+    ASSERT_NO_THROW(cfg.add(network1));
+    ASSERT_THROW(cfg.add(network2), BadValue);
+}
+
+// This test verifies that unparsing shared networks returns valid structure.
+TEST(CfgSharedNetworks4Test, unparse) {
+    SharedNetwork4Ptr network1(new SharedNetwork4("frog"));
+    SharedNetwork4Ptr network2(new SharedNetwork4("dog"));
+
+    network1->setIface("eth0");
+    network2->setIface("eth1");
+
+    CfgSharedNetworks4 cfg;
+    ASSERT_NO_THROW(cfg.add(network1));
+    ASSERT_NO_THROW(cfg.add(network2));
+
+    std::string expected =
+        "[\n"
+        "  {\n"
+        "    \"interface\": \"eth1\",\n"
+        "    \"match-client-id\": true,\n"
+        "    \"name\": \"dog\",\n"
+        "    \"option-data\": [ ],\n"
+        "    \"rebind-timer\": 0,\n"
+        "    \"relay\": {\n"
+        "        \"ip-address\": \"0.0.0.0\"\n"
+        "    },\n"
+        "    \"renew-timer\": 0,\n"
+        "    \"reservation-mode\": \"all\","
+        "    \"subnet4\": [ ],\n"
+        "    \"valid-lifetime\": 0\n"
+        "  },\n"
+        "  {\n"
+        "    \"interface\": \"eth0\",\n"
+        "    \"match-client-id\": true,\n"
+        "    \"name\": \"frog\",\n"
+        "    \"option-data\": [ ],\n"
+        "    \"rebind-timer\": 0,\n"
+        "    \"relay\": {\n"
+        "        \"ip-address\": \"0.0.0.0\"\n"
+        "    },\n"
+        "    \"renew-timer\": 0,\n"
+        "    \"reservation-mode\": \"all\","
+        "    \"subnet4\": [ ],\n"
+        "    \"valid-lifetime\": 0\n"
+        "  }\n"
+        "]\n";
+
+    test::runToElementTest<CfgSharedNetworks4>(expected, cfg);
+}
+
+} // end of anonymous namespace

+ 124 - 0
src/lib/dhcpsrv/tests/cfg_shared_networks6_unittest.cc

@@ -0,0 +1,124 @@
+// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+#include <exceptions/exceptions.h>
+#include <dhcpsrv/cfg_shared_networks.h>
+#include <testutils/test_to_element.h>
+#include <gtest/gtest.h>
+
+using namespace isc;
+using namespace isc::dhcp;
+
+namespace {
+
+// This test verifies that shared networks can be added to the configruation
+// and retrieved by name.
+TEST(CfgSharedNetworks6Test, getByName) {
+    SharedNetwork6Ptr network1(new SharedNetwork6("frog"));
+    SharedNetwork6Ptr network2(new SharedNetwork6("dog"));
+
+    CfgSharedNetworks6 cfg;
+    ASSERT_NO_THROW(cfg.add(network1));
+    ASSERT_NO_THROW(cfg.add(network2));
+
+    SharedNetwork6Ptr returned_network1 = cfg.getByName("frog");
+    ASSERT_TRUE(returned_network1);
+    SharedNetwork6Ptr returned_network2 = cfg.getByName("dog");
+    ASSERT_TRUE(returned_network2);
+
+    // Check that non-existent name does not return bogus data.
+    EXPECT_FALSE(cfg.getByName("ant"));
+}
+
+// This test verifies that it is possible to delete a network.
+TEST(CfgSharedNetworks6Test, deleteByName) {
+    SharedNetwork6Ptr network1(new SharedNetwork6("frog"));
+    SharedNetwork6Ptr network2(new SharedNetwork6("dog"));
+
+    // Add two networks to the configuration.
+    CfgSharedNetworks6 cfg;
+    ASSERT_NO_THROW(cfg.add(network1));
+    ASSERT_NO_THROW(cfg.add(network2));
+
+    // Try to delete non-existing network. This should throw.
+    ASSERT_THROW(cfg.del("lion"), BadValue);
+
+    // Delete network #1.
+    ASSERT_NO_THROW(cfg.del(network1->getName()));
+    ASSERT_FALSE(cfg.getByName(network1->getName()));
+    ASSERT_TRUE(cfg.getByName(network2->getName()));
+
+    // Delete network #2.
+    ASSERT_NO_THROW(cfg.del(network2->getName()));
+    ASSERT_FALSE(cfg.getByName(network1->getName()));
+    ASSERT_FALSE(cfg.getByName(network2->getName()));
+
+    // Check that attempting to delete the same subnet twice will fail.
+    ASSERT_THROW(cfg.del(network1->getName()), BadValue);
+    ASSERT_THROW(cfg.del(network2->getName()), BadValue);
+}
+
+// This test verifies that shared networks must have unique names.
+TEST(CfgSharedNetworks6Test, duplicateName) {
+    SharedNetwork6Ptr network1(new SharedNetwork6("frog"));
+    SharedNetwork6Ptr network2(new SharedNetwork6("frog"));
+
+    CfgSharedNetworks6 cfg;
+    ASSERT_NO_THROW(cfg.add(network1));
+    ASSERT_THROW(cfg.add(network2), BadValue);
+}
+
+// This test verifies that unparsing shared networks returns valid structure.
+TEST(CfgSharedNetworks6Test, unparse) {
+    SharedNetwork6Ptr network1(new SharedNetwork6("frog"));
+    SharedNetwork6Ptr network2(new SharedNetwork6("dog"));
+
+    network1->setIface("eth0");
+    network2->setIface("eth1");
+
+    CfgSharedNetworks6 cfg;
+    ASSERT_NO_THROW(cfg.add(network1));
+    ASSERT_NO_THROW(cfg.add(network2));
+
+    std::string expected =
+        "[\n"
+        "  {\n"
+        "    \"interface\": \"eth1\",\n"
+        "    \"name\": \"dog\",\n"
+        "    \"option-data\": [ ],\n"
+        "    \"preferred-lifetime\": 0,\n"
+        "    \"rapid-commit\": false,\n"
+        "    \"rebind-timer\": 0,\n"
+        "    \"relay\": {\n"
+        "        \"ip-address\": \"::\"\n"
+        "    },\n"
+        "    \"renew-timer\": 0,\n"
+        "    \"reservation-mode\": \"all\","
+        "    \"subnet6\": [ ],\n"
+        "    \"valid-lifetime\": 0\n"
+        "  },\n"
+        "  {\n"
+        "    \"interface\": \"eth0\",\n"
+        "    \"name\": \"frog\",\n"
+        "    \"option-data\": [ ],\n"
+        "    \"preferred-lifetime\": 0,\n"
+        "    \"rapid-commit\": false,\n"
+        "    \"rebind-timer\": 0,\n"
+        "    \"relay\": {\n"
+        "        \"ip-address\": \"::\"\n"
+        "    },\n"
+        "    \"renew-timer\": 0,\n"
+        "    \"reservation-mode\": \"all\","
+        "    \"subnet6\": [ ],\n"
+        "    \"valid-lifetime\": 0\n"
+        "  }\n"
+        "]\n";
+
+    test::runToElementTest<CfgSharedNetworks6>(expected, cfg);
+}
+
+} // end of anonymous namespace

+ 4 - 4
src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc

@@ -2272,7 +2272,7 @@ TEST_F(ParseConfigTest, validRelayInfo4) {
     ElementPtr json = Element::fromJSON(config_str);
 
     // We need to set the default ip-address to something.
-    Subnet::RelayInfoPtr result(new Subnet::RelayInfo(asiolink::IOAddress("0.0.0.0")));
+    Network::RelayInfoPtr result(new Network::RelayInfo(asiolink::IOAddress("0.0.0.0")));
 
     RelayInfoParser parser(Option::V4);
 
@@ -2305,7 +2305,7 @@ TEST_F(ParseConfigTest, bogusRelayInfo4) {
     ElementPtr json_bogus3 = Element::fromJSON(config_str_bogus3);
 
     // We need to set the default ip-address to something.
-    Subnet::RelayInfoPtr result(new Subnet::RelayInfo(IOAddress::IPV4_ZERO_ADDRESS()));
+    Network::RelayInfoPtr result(new Network::RelayInfo(IOAddress::IPV4_ZERO_ADDRESS()));
 
     RelayInfoParser parser(Option::V4);
 
@@ -2330,7 +2330,7 @@ TEST_F(ParseConfigTest, validRelayInfo6) {
     ElementPtr json = Element::fromJSON(config_str);
 
     // We need to set the default ip-address to something.
-    Subnet::RelayInfoPtr result(new Subnet::RelayInfo(asiolink::IOAddress("::")));
+    Network::RelayInfoPtr result(new Network::RelayInfo(asiolink::IOAddress("::")));
 
     RelayInfoParser parser(Option::V6);
     // Subnet4 parser will pass :: to the RelayInfoParser
@@ -2362,7 +2362,7 @@ TEST_F(ParseConfigTest, bogusRelayInfo6) {
     ElementPtr json_bogus3 = Element::fromJSON(config_str_bogus3);
 
     // We need to set the default ip-address to something.
-    Subnet::RelayInfoPtr result(new Subnet::RelayInfo(asiolink::IOAddress("::")));
+    Network::RelayInfoPtr result(new Network::RelayInfo(asiolink::IOAddress("::")));
 
     RelayInfoParser parser(Option::V6);
 

+ 242 - 0
src/lib/dhcpsrv/tests/shared_network_parser_unittest.cc

@@ -0,0 +1,242 @@
+// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+#include <cc/data.h>
+#include <dhcp/dhcp4.h>
+#include <dhcp/dhcp6.h>
+#include <dhcp/option.h>
+#include <dhcp/option4_addrlst.h>
+#include <dhcp/option6_addrlst.h>
+#include <dhcpsrv/cfg_option.h>
+#include <dhcpsrv/parsers/shared_network_parser.h>
+#include <gtest/gtest.h>
+#include <string>
+
+using namespace isc;
+using namespace isc::data;
+using namespace isc::dhcp;
+
+namespace {
+
+/// @brief Test fixture class for SharedNetwork4Parser class.
+class SharedNetwork4ParserTest : public ::testing::Test {
+public:
+
+    /// @brief Creates valid shared network configuration.
+    ///
+    /// @return Valid shared network configuration.
+    std::string getWorkingConfig() const {
+            std::string config = "{"
+                "    \"name\": \"bird\","
+                "    \"interface\": \"eth1\","
+                "    \"option-data\": ["
+                "        {"
+                "            \"name\": \"domain-name-servers\","
+                "            \"data\": \"192.0.2.3\""
+                "        }"
+                "    ],"
+                "    \"subnet4\": ["
+                "        {"
+                "            \"id\": 1,"
+                "            \"subnet\": \"10.1.2.0/24\","
+                "            \"interface\": \"\","
+                "            \"renew-timer\": 100,"
+                "            \"rebind-timer\": 200,"
+                "            \"valid-lifetime\": 300,"
+                "            \"match-client-id\": false,"
+                "            \"next-server\": \"\","
+                "            \"client-class\": \"\","
+                "            \"reservation-mode\": \"all\","
+                "            \"4o6-interface\": \"\","
+                "            \"4o6-interface-id\": \"\","
+                "            \"4o6-subnet\": \"\","
+                "            \"dhcp4o6-port\": 0,"
+                "            \"decline-probation-period\": 86400,"
+                "            \"reservation-mode\": \"all\""
+                "        },"
+                "        {"
+                "            \"id\": 2,"
+                "            \"subnet\": \"192.0.2.0/24\","
+                "            \"interface\": \"\","
+                "            \"renew-timer\": 10,"
+                "            \"rebind-timer\": 20,"
+                "            \"valid-lifetime\": 30,"
+                "            \"match-client-id\": false,"
+                "            \"next-server\": \"\","
+                "            \"client-class\": \"\","
+                "            \"reservation-mode\": \"all\","
+                "            \"4o6-interface\": \"\","
+                "            \"4o6-interface-id\": \"\","
+                "            \"4o6-subnet\": \"\","
+                "            \"dhcp4o6-port\": 0,"
+                "            \"decline-probation-period\": 86400,"
+                "            \"reservation-mode\": \"all\""
+                "        }"
+                "    ]"
+                "}";
+
+            return (config);
+    }
+};
+
+// This test verifies that shared network parser for IPv4 works properly
+// in a positive test scenario.
+TEST_F(SharedNetwork4ParserTest, parse) {
+    // Basic configuration for shared network. A bunch of parameters
+    // have to be specified for subnets because subnet parsers expect
+    // that default and global values are set.
+    std::string config = getWorkingConfig();
+    ElementPtr config_element = Element::fromJSON(config);
+
+    // Parse configuration specified above.
+    SharedNetwork4Parser parser;
+    SharedNetwork4Ptr network;
+    ASSERT_NO_THROW(network = parser.parse(config_element));
+    ASSERT_TRUE(network);
+
+    // Check basic parameters.
+    EXPECT_EQ("bird", network->getName());
+    EXPECT_EQ("eth1", network->getIface());
+
+    // Subnet with id 1
+    Subnet4Ptr subnet1 = network->getSubnet(SubnetID(1));
+    ASSERT_TRUE(subnet1);
+    EXPECT_EQ("10.1.2.0", subnet1->get().first.toText());
+
+    // Subnet with id 2
+    Subnet4Ptr subnet2 = network->getSubnet(SubnetID(2));
+    ASSERT_TRUE(subnet2);
+    EXPECT_EQ("192.0.2.0", subnet2->get().first.toText());
+
+    // DHCP options
+    ConstCfgOptionPtr cfg_option = network->getCfgOption();
+    ASSERT_TRUE(cfg_option);
+    OptionDescriptor opt_dns_servers = cfg_option->get("dhcp4",
+                                                       DHO_DOMAIN_NAME_SERVERS);
+    ASSERT_TRUE(opt_dns_servers.option_);
+    Option4AddrLstPtr dns_servers = boost::dynamic_pointer_cast<
+        Option4AddrLst>(opt_dns_servers.option_);
+    ASSERT_TRUE(dns_servers);
+    Option4AddrLst::AddressContainer addresses = dns_servers->getAddresses();
+    ASSERT_EQ(1, addresses.size());
+    EXPECT_EQ("192.0.2.3", addresses[0].toText());
+}
+
+// This test verifies that parser throws an exception when mandatory parameter
+// "name" is not specified.
+TEST_F(SharedNetwork4ParserTest, missingName) {
+    // Remove a name parameter from the valid configuration.
+    std::string config = getWorkingConfig();
+    ElementPtr config_element = Element::fromJSON(config);
+    ASSERT_NO_THROW(config_element->remove("name"));
+
+    // Parse configuration specified above.
+    SharedNetwork4Parser parser;
+    SharedNetwork4Ptr network;
+    ASSERT_THROW(network = parser.parse(config_element), DhcpConfigError);
+}
+
+/// @brief Test fixture class for SharedNetwork6Parser class.
+class SharedNetwork6ParserTest : public ::testing::Test {
+public:
+
+    /// @brief Creates valid shared network configuration.
+    ///
+    /// @return Valid shared network configuration.
+    std::string getWorkingConfig() const {
+            std::string config = "{"
+                "    \"name\": \"bird\","
+                "    \"interface\": \"eth1\","
+                "    \"option-data\": ["
+                "        {"
+                "            \"name\": \"dns-servers\","
+                "            \"data\": \"2001:db8:1::cafe\""
+                "        }"
+                "    ],"
+                "    \"subnet6\": ["
+                "        {"
+                "            \"id\": 1,"
+                "            \"subnet\": \"3000::/16\","
+                "            \"interface\": \"\","
+                "            \"interface-id\": \"\","
+                "            \"renew-timer\": 100,"
+                "            \"rebind-timer\": 200,"
+                "            \"preferred-lifetime\": 300,"
+                "            \"valid-lifetime\": 400,"
+                "            \"client-class\": \"\","
+                "            \"reservation-mode\": \"all\","
+                "            \"decline-probation-period\": 86400,"
+                "            \"dhcp4o6-port\": 0,"
+                "            \"rapid-commit\": false"
+                "        },"
+                "        {"
+                "            \"id\": 2,"
+                "            \"subnet\": \"2001:db8:1::/64\","
+                "            \"interface\": \"\","
+                "            \"interface-id\": \"\","
+                "            \"renew-timer\": 10,"
+                "            \"rebind-timer\": 20,"
+                "            \"preferred-lifetime\": 30,"
+                "            \"valid-lifetime\": 40,"
+                "            \"client-class\": \"\","
+                "            \"reservation-mode\": \"all\","
+                "            \"decline-probation-period\": 86400,"
+                "            \"dhcp4o6-port\": 0,"
+                "            \"rapid-commit\": false"
+                "        }"
+                "    ]"
+                "}";
+
+            return (config);
+    }
+};
+
+// This test verifies that shared network parser for IPv4 works properly
+// in a positive test scenario.
+TEST_F(SharedNetwork6ParserTest, parse) {
+    // Basic configuration for shared network. A bunch of parameters
+    // have to be specified for subnets because subnet parsers expect
+    // that default and global values are set.
+    std::string config = getWorkingConfig();
+    ElementPtr config_element = Element::fromJSON(config);
+
+    // Parse configuration specified above.
+    SharedNetwork6Parser parser;
+    SharedNetwork6Ptr network;
+    ASSERT_NO_THROW(network = parser.parse(config_element));
+    ASSERT_TRUE(network);
+
+    // Check basic parameters.
+    EXPECT_EQ("bird", network->getName());
+    EXPECT_EQ("eth1", network->getIface());
+
+    // Subnet with id 1
+    Subnet6Ptr subnet1 = network->getSubnet(SubnetID(1));
+    ASSERT_TRUE(subnet1);
+    EXPECT_EQ("3000::", subnet1->get().first.toText());
+
+    // Subnet with id 2
+    Subnet6Ptr subnet2 = network->getSubnet(SubnetID(2));
+    ASSERT_TRUE(subnet2);
+    EXPECT_EQ("2001:db8:1::", subnet2->get().first.toText());
+
+    // DHCP options
+    ConstCfgOptionPtr cfg_option = network->getCfgOption();
+    ASSERT_TRUE(cfg_option);
+    OptionDescriptor opt_dns_servers = cfg_option->get("dhcp6",
+                                                       D6O_NAME_SERVERS);
+    ASSERT_TRUE(opt_dns_servers.option_);
+    Option6AddrLstPtr dns_servers = boost::dynamic_pointer_cast<
+        Option6AddrLst>(opt_dns_servers.option_);
+    ASSERT_TRUE(dns_servers);
+    Option6AddrLst::AddressContainer addresses = dns_servers->getAddresses();
+    ASSERT_EQ(1, addresses.size());
+    EXPECT_EQ("2001:db8:1::cafe", addresses[0].toText());
+}
+
+
+} // end of anonymous namespace

+ 545 - 0
src/lib/dhcpsrv/tests/shared_network_unittest.cc

@@ -0,0 +1,545 @@
+// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+#include <asiolink/io_address.h>
+#include <dhcpsrv/shared_network.h>
+#include <dhcpsrv/subnet.h>
+#include <dhcpsrv/subnet_id.h>
+#include <dhcpsrv/triplet.h>
+#include <exceptions/exceptions.h>
+#include <testutils/test_to_element.h>
+#include <gtest/gtest.h>
+#include <cstdint>
+#include <vector>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::dhcp;
+
+namespace {
+
+// This test verifies that shared network can be given a name and that
+// this name can be retrieved.
+TEST(SharedNetwork4Test, getName) {
+    // Create shared network with an initial name "dog".
+    SharedNetwork4Ptr network(new SharedNetwork4("frog"));
+    EXPECT_EQ("frog", network->getName());
+
+    // Override the name.
+    network->setName("dog");
+    EXPECT_EQ("dog", network->getName());
+}
+
+// This test verifies that an IPv4 subnet can be added to a shared network.
+// It also verifies that two subnets with the same ID can't be added to
+// a shared network and that a single subnet can't be added to two different
+// shared subnets.
+TEST(SharedNetwork4Test, addSubnet4) {
+    // First, create a network.
+    SharedNetwork4Ptr network(new SharedNetwork4("frog"));
+
+    // Try to add null pointer. It should throw.
+    Subnet4Ptr subnet;
+    ASSERT_THROW(network->add(subnet), BadValue);
+
+    // Create a valid subnet. It should now be added successfully.
+    subnet.reset(new Subnet4(IOAddress("10.0.0.0"), 8, 10, 20, 30,
+                             SubnetID(15)));
+    ASSERT_NO_THROW(network->add(subnet));
+    ASSERT_EQ(1, network->getAllSubnets()->size());
+
+    // Retrieve the subnet from the network and make sure it is returned
+    // as expected.
+    Subnet4Ptr returned_subnet = network->getAllSubnets()->front();
+    ASSERT_TRUE(returned_subnet);
+    EXPECT_EQ(subnet->getID(), returned_subnet->getID());
+    SharedNetwork4Ptr network1;
+    subnet->getSharedNetwork(network1);
+    ASSERT_TRUE(network1);
+    EXPECT_TRUE(network1 == network);
+
+    // Create another subnet with the same ID. Adding a network with the
+    // same ID should cause an error.
+    Subnet4Ptr subnet2(new Subnet4(IOAddress("192.0.2.0"), 24, 10, 20, 30,
+                                   SubnetID(15)));
+    ASSERT_THROW(network->add(subnet), DuplicateSubnetID);
+
+    // Create another network and try to add a subnet to it. It should fail
+    // because the subnet is already associated with the first network.
+    SharedNetwork4Ptr network2(new SharedNetwork4("dog"));
+    ASSERT_THROW(network2->add(subnet), InvalidOperation);
+}
+
+// This test verifies that it is possible to remove a specified subnet.
+TEST(SharedNetwork4Test, delSubnet4) {
+    // Create two subnets and add them to the shared network.
+    Subnet4Ptr subnet1(new Subnet4(IOAddress("10.0.0.0"), 8, 10, 20, 30,
+                                   SubnetID(1)));
+    Subnet4Ptr subnet2(new Subnet4(IOAddress("192.0.2.0"), 24, 10, 20, 30,
+                                   SubnetID(2)));
+
+    SharedNetwork4Ptr network(new SharedNetwork4("frog"));
+    ASSERT_NO_THROW(network->add(subnet1));
+    ASSERT_NO_THROW(network->add(subnet2));
+
+    // Make sure they have been added successfully.
+    ASSERT_EQ(2, network->getAllSubnets()->size());
+
+    // Try to remove a subnet that doesn't exist in this shared network.
+    // It should cause an error.
+    ASSERT_THROW(network->del(SubnetID(5)), BadValue);
+
+    // Now delete the subnet that exists.
+    ASSERT_NO_THROW(network->del(subnet1->getID()));
+    // We should be left with only one subnet.
+    ASSERT_EQ(1, network->getAllSubnets()->size());
+    Subnet4Ptr subnet_returned = network->getAllSubnets()->front();
+    ASSERT_TRUE(subnet_returned);
+    EXPECT_EQ(subnet2->getID(), subnet_returned->getID());
+
+    // Check that shared network has been cleared for the removed subnet.
+    SharedNetwork4Ptr network1;
+    subnet1->getSharedNetwork(network1);
+    EXPECT_FALSE(network1);
+
+    // Remove another subnet and make sure there are no subnets left.
+    ASSERT_NO_THROW(network->del(subnet2->getID()));
+    EXPECT_EQ(0, network->getAllSubnets()->size());
+
+    // The network pointer should be cleared for this second subnet too.
+    SharedNetwork4Ptr network2;
+    subnet1->getSharedNetwork(network2);
+    EXPECT_FALSE(network2);
+}
+
+// This test verifies that it is possible to iterate over the subnets
+// associated with a particular shared network.
+TEST(SharedNetwork4Test, getNextSubnet) {
+    SharedNetwork4Ptr network(new SharedNetwork4("frog"));
+
+    // Create three subnets.
+    Subnet4Ptr subnet1(new Subnet4(IOAddress("10.0.0.0"), 8, 10, 20, 30,
+                                   SubnetID(1)));
+    Subnet4Ptr subnet2(new Subnet4(IOAddress("192.0.2.0"), 24, 10, 20, 30,
+                                   SubnetID(2)));
+    Subnet4Ptr subnet3(new Subnet4(IOAddress("172.16.25.0"), 24, 10, 20, 30,
+                                   SubnetID(3)));
+    std::vector<Subnet4Ptr> subnets;
+    subnets.push_back(subnet1);
+    subnets.push_back(subnet2);
+    subnets.push_back(subnet3);
+
+    // Subnets have unique IDs so they should successfully be added to the
+    // network.
+    for (auto i = 0; i < subnets.size(); ++i) {
+        ASSERT_NO_THROW(network->add(subnets[i]))
+            << "failed to add subnet with id " << subnets[i]->getID()
+            << " to shared network";
+    }
+
+    // Collect networks associated with our subnets in the vector.
+    std::vector<SharedNetwork4Ptr> networks;
+    for (auto i = 0; i < subnets.size(); ++i) {
+        SharedNetwork4Ptr network;
+        subnets[i]->getSharedNetwork(network);
+        ASSERT_TRUE(network) << "failed to retrieve shared network for a"
+            << " subnet id " << subnets[i]->getID();
+        networks.push_back(network);
+    }
+
+    // All subnets should be associated with the same network.
+    for (auto i = 1; i < networks.size(); ++i) {
+        EXPECT_TRUE(networks[0] == networks[i]);
+    }
+
+    // Perform the test 3 times where each subnet belonging to the shared
+    // network is treated as a "first" subnet in the call to getNextSubnet.
+    for (auto i = 0; i < subnets.size(); ++i) {
+        Subnet4Ptr s = subnets[i];
+
+        // 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);
+            // 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.
+            if (j < subnets.size() - 1) {
+                ASSERT_TRUE(s) << "retrieving next subnet failed for pair of"
+                    " indexes (i, j) = (" << i << ", " << j << ")";
+                const auto expected_subnet_id = (i + j + 1) % subnets.size() + 1;
+                EXPECT_EQ(expected_subnet_id, s->getID());
+            } else {
+                // Null subnet returned for a last iteration.
+                ASSERT_FALSE(s) << "expected null pointer to be returned as"
+                    " next subnet for pair of indexes (i, j) = ("
+                                << i << ", " << j << ")";
+            }
+        }
+    }
+}
+
+// This test verifies that unparsing shared network returns valid structure.
+TEST(SharedNetwork4Test, unparse) {
+    SharedNetwork4Ptr network(new SharedNetwork4("frog"));
+
+    // Set interface name.
+    network->setIface("eth1");
+
+    network->setT1(100);
+    network->setT2(150);
+    network->setValid(200);
+    network->setMatchClientId(false);
+
+    // Add several subnets.
+    Subnet4Ptr subnet1(new Subnet4(IOAddress("10.0.0.0"), 8, 10, 20, 30,
+                                   SubnetID(1)));
+    Subnet4Ptr subnet2(new Subnet4(IOAddress("192.0.2.0"), 24, 10, 20, 30,
+                                   SubnetID(2)));
+    network->add(subnet1);
+    network->add(subnet2);
+
+    std::string expected = "{\n"
+        "    \"interface\": \"eth1\",\n"
+        "    \"match-client-id\": false,\n"
+        "    \"name\": \"frog\",\n"
+        "    \"option-data\": [ ],\n"
+        "    \"rebind-timer\": 150,\n"
+        "    \"relay\": {\n"
+        "        \"ip-address\": \"0.0.0.0\"\n"
+        "    },\n"
+        "    \"renew-timer\": 100,\n"
+        "    \"reservation-mode\": \"all\","
+        "    \"subnet4\": [\n"
+        "      {\n"
+        "        \"4o6-interface\": \"\",\n"
+        "        \"4o6-interface-id\": \"\",\n"
+        "        \"4o6-subnet\": \"\",\n"
+        "        \"id\": 1,\n"
+        "        \"match-client-id\": true,\n"
+        "        \"next-server\": \"0.0.0.0\",\n"
+        "        \"option-data\": [ ],\n"
+        "        \"pools\": [ ],\n"
+        "        \"rebind-timer\": 20,\n"
+        "        \"relay\": {\n"
+        "          \"ip-address\": \"0.0.0.0\"\n"
+        "        },\n"
+        "        \"renew-timer\": 10,\n"
+        "        \"reservation-mode\": \"all\",\n"
+        "        \"subnet\": \"10.0.0.0/8\",\n"
+        "        \"valid-lifetime\": 30\n"
+        "      },\n"
+        "      {\n"
+        "        \"4o6-interface\": \"\",\n"
+        "        \"4o6-interface-id\": \"\",\n"
+        "        \"4o6-subnet\": \"\",\n"
+        "        \"id\": 2,\n"
+        "        \"match-client-id\": true,\n"
+        "        \"next-server\": \"0.0.0.0\",\n"
+        "        \"option-data\": [ ],\n"
+        "        \"pools\": [ ],\n"
+        "        \"rebind-timer\": 20,\n"
+        "        \"relay\": {\n"
+        "          \"ip-address\": \"0.0.0.0\"\n"
+        "        },\n"
+        "        \"renew-timer\": 10,\n"
+        "        \"reservation-mode\": \"all\",\n"
+        "        \"subnet\": \"192.0.2.0/24\",\n"
+        "        \"valid-lifetime\": 30\n"
+        "      }\n"
+        "    ],\n"
+        "    \"valid-lifetime\": 200\n"
+        "}\n";
+
+    test::runToElementTest<SharedNetwork4>(expected, *network);
+}
+
+// This test verifies that when the shared network object is destroyed,
+// the subnets belonging to this shared network will not hold the pointer
+// to the destroyed network.
+TEST(SharedNetwork4Test, destructSharedNetwork) {
+    // Create a network and add a subnet to it.
+    SharedNetwork4Ptr network(new SharedNetwork4("frog"));
+    Subnet4Ptr subnet(new Subnet4(IOAddress("10.0.0.0"), 8, 10, 20, 30,
+                                  SubnetID(1)));
+    ASSERT_NO_THROW(network->add(subnet));
+
+    // Get the pointer to the network from subnet.
+    SharedNetwork4Ptr subnet_to_network;
+    subnet->getSharedNetwork(subnet_to_network);
+    ASSERT_TRUE(subnet_to_network);
+
+    // Reset the pointer to not hold the reference to the shared network.
+    subnet_to_network.reset();
+
+    // Destroy the network object.
+    network.reset();
+
+    // The reference to the network from the subnet should be lost.
+    subnet->getSharedNetwork(subnet_to_network);
+    ASSERT_FALSE(subnet_to_network);
+}
+
+// This test verifies that shared network can be given a name and that
+// this name can be retrieved.
+TEST(SharedNetwork6Test, getName) {
+    // Create shared network with an initial name "frog".
+    SharedNetwork6Ptr network(new SharedNetwork6("frog"));
+    EXPECT_EQ("frog", network->getName());
+
+    // Override the name.
+    network->setName("dog");
+    EXPECT_EQ("dog", network->getName());
+}
+
+// This test verifies that an IPv6 subnet can be added to a shared network.
+// It also verifies that two subnets with the same ID can't be added to
+// a shared network and that a single subnet can't be added to two different
+// shared subnets.
+TEST(SharedNetwork6Test, addSubnet6) {
+    // First, create a network.
+    SharedNetwork6Ptr network(new SharedNetwork6("frog"));
+
+    // Try to add null pointer. It should throw.
+    Subnet6Ptr subnet;
+    ASSERT_THROW(network->add(subnet), BadValue);
+
+    // Create a valid subnet. It should now be added successfully.
+    subnet.reset(new Subnet6(IOAddress("2001:db8:1::"), 64, 10, 20, 30, 40,
+                             SubnetID(15)));
+    ASSERT_NO_THROW(network->add(subnet));
+    ASSERT_EQ(1, network->getAllSubnets()->size());
+
+    // Retrieve the subnet from the network and make sure it is returned
+    // as expected.
+    Subnet6Ptr returned_subnet = network->getAllSubnets()->front();
+    ASSERT_TRUE(returned_subnet);
+    EXPECT_EQ(subnet->getID(), returned_subnet->getID());
+    SharedNetwork6Ptr network1;
+    subnet->getSharedNetwork(network1);
+    ASSERT_TRUE(network1);
+    EXPECT_TRUE(network1 == network);
+
+    // Create another subnet with the same ID. Adding a network with the
+    // same ID should cause an error.
+    Subnet6Ptr subnet2(new Subnet6(IOAddress("3000::"), 16, 10, 20, 30, 40,
+                                   SubnetID(15)));
+    ASSERT_THROW(network->add(subnet), DuplicateSubnetID);
+
+    // Create another network and try to add a subnet to it. It should fail
+    // because the subnet is already associated with the first network.
+    SharedNetwork6Ptr network2(new SharedNetwork6("dog"));
+    ASSERT_THROW(network2->add(subnet), InvalidOperation);
+}
+
+// This test verifies that it is possible to remove a specified subnet.
+TEST(SharedNetwork6Test, delSubnet6) {
+    // Create two subnets and add them to the shared network.
+    Subnet6Ptr subnet1(new Subnet6(IOAddress("2001:db8:1::"), 64, 10, 20, 30,
+                                   40, SubnetID(1)));
+    Subnet6Ptr subnet2(new Subnet6(IOAddress("3000::"), 16, 10, 20, 30, 40,
+                                   SubnetID(2)));
+
+    SharedNetwork6Ptr network(new SharedNetwork6("frog"));
+    ASSERT_NO_THROW(network->add(subnet1));
+    ASSERT_NO_THROW(network->add(subnet2));
+
+    // Make sure they have been added successfully.
+    ASSERT_EQ(2, network->getAllSubnets()->size());
+
+    // Try to remove a subnet that doesn't exist in this shared network.
+    // It should cause an error.
+    ASSERT_THROW(network->del(SubnetID(5)), BadValue);
+
+    // Now delete the subnet that exists.
+    ASSERT_NO_THROW(network->del(subnet1->getID()));
+    // We should be left with only one subnet.
+    ASSERT_EQ(1, network->getAllSubnets()->size());
+    Subnet6Ptr subnet_returned = network->getAllSubnets()->front();
+    ASSERT_TRUE(subnet_returned);
+    EXPECT_EQ(subnet2->getID(), subnet_returned->getID());
+
+    // Check that shared network has been cleared for the removed subnet.
+    SharedNetwork6Ptr network1;
+    subnet1->getSharedNetwork(network1);
+    EXPECT_FALSE(network1);
+
+    // Remove another subnet and make sure there are no subnets left.
+    ASSERT_NO_THROW(network->del(subnet2->getID()));
+    EXPECT_EQ(0, network->getAllSubnets()->size());
+
+    // The network pointer should be cleared for this second subnet too.
+    SharedNetwork6Ptr network2;
+    subnet1->getSharedNetwork(network2);
+    EXPECT_FALSE(network2);
+}
+
+// This test verifies that it is possible to iterate over the subnets
+// associated with a particular shared network.
+TEST(SharedNetwork6Test, getNextSubnet) {
+    SharedNetwork6Ptr network(new SharedNetwork6("frog"));
+
+    // Create three subnets.
+    Subnet6Ptr subnet1(new Subnet6(IOAddress("2001:db8:1::"), 64, 10, 20, 30,
+                                   40, SubnetID(1)));
+    Subnet6Ptr subnet2(new Subnet6(IOAddress("3000::"), 16, 10, 20, 30, 40,
+                                   SubnetID(2)));
+    Subnet6Ptr subnet3(new Subnet6(IOAddress("2001:db8:2::"), 64, 10, 20, 30,
+                                   40, SubnetID(3)));
+    std::vector<Subnet6Ptr> subnets;
+    subnets.push_back(subnet1);
+    subnets.push_back(subnet2);
+    subnets.push_back(subnet3);
+
+    // Subnets have unique IDs so they should successfully be added to the
+    // network.
+    for (auto i = 0; i < subnets.size(); ++i) {
+        ASSERT_NO_THROW(network->add(subnets[i]))
+            << "failed to add subnet with id " << subnets[i]->getID()
+            << " to shared network";
+    }
+
+    // Collect networks associated with our subnets in the vector.
+    std::vector<SharedNetwork6Ptr> networks;
+    for (auto i = 0; i < subnets.size(); ++i) {
+        SharedNetwork6Ptr network;
+        subnets[i]->getSharedNetwork(network);
+        ASSERT_TRUE(network) << "failed to retrieve shared network for a"
+            << " subnet id " << subnets[i]->getID();
+        networks.push_back(network);
+    }
+
+    // All subnets should be associated with the same network.
+    for (auto i = 1; i < networks.size(); ++i) {
+        EXPECT_TRUE(networks[0] == networks[i]);
+    }
+
+    // Perform the test 3 times where each subnet belonging to the shared
+    // network is treated as a "first" subnet in the call to getNextSubnet.
+    for (auto i = 0; i < subnets.size(); ++i) {
+        Subnet6Ptr s = subnets[i];
+
+        // 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);
+            // 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.
+            if (j < subnets.size() - 1) {
+                ASSERT_TRUE(s) << "retrieving next subnet failed for pair of"
+                    " indexes (i, j) = (" << i << ", " << j << ")";
+                const auto expected_subnet_id = (i + j + 1) % subnets.size() + 1;
+                EXPECT_EQ(expected_subnet_id, s->getID());
+            } else {
+                // Null subnet returned for a last iteration.
+                ASSERT_FALSE(s) << "expected null pointer to be returned as"
+                    " next subnet for pair of indexes (i, j) = ("
+                                << i << ", " << j << ")";
+            }
+        }
+    }
+}
+
+// This test verifies that unparsing shared network returns valid structure.
+TEST(SharedNetwork6Test, unparse) {
+    SharedNetwork6Ptr network(new SharedNetwork6("frog"));
+    network->setIface("eth1");
+    network->setT1(100);
+    network->setT2(150);
+    network->setPreferred(200);
+    network->setValid(300);
+    network->setRapidCommit(true);
+
+    // Add several subnets.
+    Subnet6Ptr subnet1(new Subnet6(IOAddress("2001:db8:1::"), 64, 10, 20, 30,
+                                   40, SubnetID(1)));
+    Subnet6Ptr subnet2(new Subnet6(IOAddress("3000::"), 16, 10, 20, 30, 40,
+                                   SubnetID(2)));
+    network->add(subnet1);
+    network->add(subnet2);
+
+    std::string expected = "{\n"
+        "    \"interface\": \"eth1\",\n"
+        "    \"name\": \"frog\",\n"
+        "    \"option-data\": [ ],\n"
+        "    \"preferred-lifetime\": 200,\n"
+        "    \"rapid-commit\": true,\n"
+        "    \"rebind-timer\": 150,\n"
+        "    \"relay\": {\n"
+        "        \"ip-address\": \"::\"\n"
+        "    },\n"
+        "    \"renew-timer\": 100,\n"
+        "    \"reservation-mode\": \"all\","
+        "    \"subnet6\": [\n"
+        "      {\n"
+        "        \"id\": 1,\n"
+        "        \"option-data\": [ ],\n"
+        "        \"pd-pools\": [ ],\n"
+        "        \"pools\": [ ],\n"
+        "        \"preferred-lifetime\": 30,\n"
+        "        \"rapid-commit\": false,\n"
+        "        \"rebind-timer\": 20,\n"
+        "        \"relay\": {\n"
+        "          \"ip-address\": \"::\"\n"
+        "        },\n"
+        "        \"renew-timer\": 10,\n"
+        "        \"reservation-mode\": \"all\",\n"
+        "        \"subnet\": \"2001:db8:1::/64\",\n"
+        "        \"valid-lifetime\": 40\n"
+        "      },\n"
+        "      {\n"
+        "        \"id\": 2,\n"
+        "        \"option-data\": [ ],\n"
+        "        \"pd-pools\": [ ],\n"
+        "        \"pools\": [ ],\n"
+        "        \"preferred-lifetime\": 30,\n"
+        "        \"rapid-commit\": false,\n"
+        "        \"rebind-timer\": 20,\n"
+        "        \"relay\": {\n"
+        "          \"ip-address\": \"::\"\n"
+        "        },\n"
+        "        \"renew-timer\": 10,\n"
+        "        \"reservation-mode\": \"all\",\n"
+        "        \"subnet\": \"3000::/16\",\n"
+        "        \"valid-lifetime\": 40\n"
+        "      }\n"
+        "    ],\n"
+        "    \"valid-lifetime\": 300\n"
+        "}\n";
+
+    test::runToElementTest<SharedNetwork6>(expected, *network);
+}
+
+// This test verifies that when the shared network object is destroyed,
+// the subnets belonging to this shared network will not hold the pointer
+// to the destroyed network.
+TEST(SharedNetwork6Test, destructSharedNetwork) {
+    // Create a network and add a subnet to it.
+    SharedNetwork6Ptr network(new SharedNetwork6("frog"));
+    Subnet6Ptr subnet(new Subnet6(IOAddress("2001:db8:1::"), 64, 10, 20, 30,
+                                  40, SubnetID(1)));
+    ASSERT_NO_THROW(network->add(subnet));
+
+    // Get the pointer to the network from subnet.
+    SharedNetwork6Ptr subnet_to_network;
+    subnet->getSharedNetwork(subnet_to_network);
+    ASSERT_TRUE(subnet_to_network);
+
+    // Reset the pointer to not hold the reference to the shared network.
+    subnet_to_network.reset();
+
+    // Destroy the network object.
+    network.reset();
+
+    // The reference to the network from the subnet should be lost.
+    subnet->getSharedNetwork(subnet_to_network);
+    ASSERT_FALSE(subnet_to_network);
+}
+
+
+} // end of anonymous namespace

+ 75 - 0
src/lib/dhcpsrv/tests/shared_networks_list_parser_unittest.cc

@@ -0,0 +1,75 @@
+// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+#include <cc/data.h>
+#include <dhcpsrv/cfg_shared_networks.h>
+#include <dhcpsrv/shared_network.h>
+#include <dhcpsrv/parsers/shared_networks_list_parser.h>
+#include <gtest/gtest.h>
+#include <string>
+
+using namespace isc;
+using namespace isc::data;
+using namespace isc::dhcp;
+
+namespace {
+
+// This is a basic test verifying that all shared networks are correctly
+// parsed.
+TEST(SharedNetworkListParserTest, parse) {
+    // Basic configuration with array of shared networks.
+    std::string config = "["
+        "    {"
+        "        \"name\": \"bird\","
+        "        \"interface\": \"eth0\""
+        "    },"
+        "    {"
+        "        \"name\": \"monkey\","
+        "        \"interface\": \"eth1\""
+        "    }"
+        "]";
+
+    ElementPtr config_element = Element::fromJSON(config);
+
+    SharedNetworks4ListParser parser;
+    CfgSharedNetworks4Ptr cfg(new CfgSharedNetworks4());;
+    ASSERT_NO_THROW(parser.parse(cfg, config_element));
+
+    SharedNetwork4Ptr network1 = cfg->getByName("bird");
+    ASSERT_TRUE(network1);
+    EXPECT_EQ("bird", network1->getName());
+    EXPECT_EQ("eth0", network1->getIface());
+
+    SharedNetwork4Ptr network2 = cfg->getByName("monkey");
+    ASSERT_TRUE(network2);
+    EXPECT_EQ("monkey", network2->getName());
+    EXPECT_EQ("eth1", network2->getIface());
+}
+
+// This test verifies that specifying two networks with the same name
+// yields an error.
+TEST(SharedNetworkListParserTest, duplicatedName) {
+    // Basic configuration with two networks having the same name.
+    std::string config = "["
+        "    {"
+        "        \"name\": \"bird\","
+        "        \"interface\": \"eth0\""
+        "    },"
+        "    {"
+        "        \"name\": \"bird\","
+        "        \"interface\": \"eth1\""
+        "    }"
+        "]";
+
+    ElementPtr config_element = Element::fromJSON(config);
+
+    SharedNetworks4ListParser parser;
+    CfgSharedNetworks4Ptr cfg(new CfgSharedNetworks4());;
+    EXPECT_THROW(parser.parse(cfg, config_element), DhcpConfigError);
+}
+
+} // end of anonymous namespace

+ 2 - 0
src/lib/dhcpsrv/tests/srv_config_unittest.cc

@@ -448,11 +448,13 @@ TEST_F(SrvConfigTest, unparse) {
     defaults += conf.getD2ClientConfig()->toElement()->str() + ",\n";
 
     std::string defaults4 = "\"echo-client-id\": true,\n";
+    defaults4 += "\"shared-networks\": [ ],\n";
     defaults4 += "\"subnet4\": [ ],\n";
     defaults4 += "\"host-reservation-identifiers\": ";
     defaults4 += "[ \"hw-address\", \"duid\", \"circuit-id\", \"client-id\" ],\n";
 
     std::string defaults6 = "\"relay-supplied-options\": [ \"65\" ],\n";
+    defaults6 += "\"shared-networks\": [ ],\n";
     defaults6 += "\"subnet6\": [ ],\n";
     defaults6 += "\"server-id\": ";
     defaults6 += conf.getCfgDUID()->toElement()->str() + ",\n";