Parcourir la source

[5357] Parser for shared-networks in DHCPv6 implemented.

Tomek Mrugalski il y a 7 ans
Parent
commit
a5abebaa0f

+ 11 - 2
src/bin/dhcp4/json_config_parser.cc

@@ -89,7 +89,6 @@ public:
         cfg->setDhcp4o6Port(dhcp4o6_port);
     }
 
-
     /// @brief Copies subnets from shared networks to regular subnets container
     ///
     /// @param from pointer to shared networks container (copy from here)
@@ -156,6 +155,13 @@ public:
     sharedNetworksSanityChecks(const SharedNetwork4Collection& networks,
                                ConstElementPtr json) {
 
+        /// @todo: in case of errors, use json to extract line numbers.
+        if (!json) {
+            // No json? That means that the shared-networks was never specified
+            // in the config.
+            return;
+        }
+
         // Used for names uniqueness checks.
         std::set<string> names;
 
@@ -315,6 +321,8 @@ configureDhcp4Server(Dhcpv4Srv&, isc::data::ConstElementPtr config_set,
             parser.parse(cfg_option_def, option_defs);
         }
 
+        // This parser is used in several places, so it should be available
+        // early.
         Dhcp4ConfigParser global_parser;
 
         // Make parsers grouping.
@@ -427,9 +435,10 @@ configureDhcp4Server(Dhcpv4Srv&, isc::data::ConstElementPtr config_set,
                 /// as well.
                 SharedNetworks4ListParser parser;
                 CfgSharedNetworks4Ptr cfg = srv_cfg->getCfgSharedNetworks4();
-
                 parser.parse(cfg, config_pair.second);
 
+                // We also need to put the subnets it contains into normal
+                // subnets list.
                 global_parser.copySubnets4(srv_cfg->getCfgSubnets4(), cfg);
                 continue;
             }

+ 8 - 3
src/bin/dhcp4/tests/config_parser_unittest.cc

@@ -604,6 +604,13 @@ public:
         return (ReturnType());
     }
 
+    /// @brief Checks if specified subnet is part of the collection
+    ///
+    /// @param col collection of subnets to be inspected
+    /// @param subnet text notation (e.g. 192.0.2.0/24)
+    /// @param t1 expected renew-timer value
+    /// @param t2 expected rebind-timer value
+    /// @param valid expected valid-lifetime value
     void
     checkSubnet(const Subnet4Collection& col, std::string subnet,
                 uint32_t t1, uint32_t t2, uint32_t valid) {
@@ -5066,17 +5073,15 @@ TEST_F(Dhcp4ParserTest, sharedNetworksName) {
     // Now verify that the shared network was indeed configured.
     CfgSharedNetworks4Ptr cfg_net = CfgMgr::instance().getStagingCfg()
         ->getCfgSharedNetworks4();
-
     ASSERT_TRUE(cfg_net);
     const SharedNetwork4Collection* nets = cfg_net->getAll();
     ASSERT_TRUE(nets);
     ASSERT_EQ(1, nets->size());
-
     SharedNetwork4Ptr net = *(nets->begin());
     ASSERT_TRUE(net);
-
     EXPECT_EQ("foo", net->getName());
 
+    // Verify that there are no subnets in this shared-network
     const Subnet4Collection * subs = net->getAllSubnets();
     ASSERT_TRUE(subs);
     EXPECT_EQ(0, subs->size());

+ 154 - 5
src/bin/dhcp6/json_config_parser.cc

@@ -31,6 +31,7 @@
 #include <dhcpsrv/parsers/ifaces_config_parser.h>
 #include <dhcpsrv/parsers/option_data_parser.h>
 #include <dhcpsrv/parsers/simple_parser6.h>
+#include <dhcpsrv/parsers/shared_networks_list_parser.h>
 #include <hooks/hooks_parser.h>
 #include <log/logger_support.h>
 #include <util/encode/hex.h>
@@ -129,7 +130,12 @@ public:
     }
 };
 
-/// @brief Parser that takes care of global DHCPv6 parameters.
+/// @brief Parser that takes care of global DHCPv6 parameters and utility
+///        functions that work on global level.
+///
+/// This class is a collection of utility method that either handle
+/// global parameters (see @ref parse), or conducts operations on
+/// global level (see @ref sanityChecks and @ref copySubnets6).
 ///
 /// See @ref parse method for a list of supported parameters.
 class Dhcp6ConfigParser : public isc::data::SimpleParser {
@@ -158,6 +164,134 @@ public:
         uint16_t dhcp4o6_port = getUint16(global, "dhcp4o6-port");
         srv_config->setDhcp4o6Port(dhcp4o6_port);
     }
+
+    /// @brief Copies subnets from shared networks to regular subnets container
+    ///
+    /// @param from pointer to shared networks container (copy from here)
+    /// @param dest pointer to cfg subnets6 (copy to here)
+    /// @throw BadValue if any pointer is missing
+    /// @throw DhcpConfigError if there are duplicates (or other subnet defects)
+    void
+    copySubnets6(const CfgSubnets6Ptr dest, const CfgSharedNetworks6Ptr from) {
+
+        if (!dest || !from) {
+            isc_throw(BadValue, "Unable to copy subnets: at least one pointer is null");
+        }
+
+        const SharedNetwork6Collection* networks = from->getAll();
+        if (!networks) {
+            // Nothing to copy. Technically, it should return a pointer to empty
+            // container, but let's handle null pointer as well.
+            return;
+        }
+
+        // Let's go through all the networks one by one
+        for (auto net = networks->begin(); net != networks->end(); ++net) {
+
+            // For each network go through all the subnets in it.
+            const Subnet6Collection* subnets = (*net)->getAllSubnets();
+            if (!subnets) {
+                // Shared network without subnets it weird, but we decided to
+                // accept such configurations.
+                continue;
+            }
+
+            // For each subnet, add it to a list of regular subnets.
+            for (auto subnet = subnets->begin(); subnet != subnets->end(); ++subnet) {
+                dest->add(*subnet);
+            }
+        }
+    }
+
+    /// @brief Conducts global sanity checks
+    ///
+    /// This method is very simply now, but more sanity checks are expected
+    /// in the future.
+    ///
+    /// @param cfg - the parsed structure
+    /// @param global global Dhcp4 scope
+    /// @throw DhcpConfigError in case of issues found
+    void
+    sanityChecks(SrvConfigPtr cfg, ConstElementPtr global) {
+
+        /// Shared network sanity checks
+        const SharedNetwork6Collection* networks = cfg->getCfgSharedNetworks6()->getAll();
+        if (networks) {
+            sharedNetworksSanityChecks(*networks, global->get("shared-networks"));
+        }
+    }
+
+    /// @brief Sanity checks for shared networks
+    ///
+    /// This method verifies if there are no issues with shared networks.
+    /// @param networks pointer to shared networks being checked
+    /// @param json shared-networks element
+    /// @throw DhcpConfigError if issues are encountered
+    void
+    sharedNetworksSanityChecks(const SharedNetwork6Collection& networks,
+                               ConstElementPtr json) {
+
+        /// @todo: in case of errors, use json to extract line numbers.
+        if (!json) {
+            // No json? That means that the shared-networks was never specified
+            // in the config.
+            return;
+        }
+
+        // Used for names uniqueness checks.
+        std::set<string> names;
+
+        // Let's go through all the networks one by one
+        for (auto net = networks.begin(); net != networks.end(); ++net) {
+            string txt;
+
+            // Let's check if all subnets have either the same interface
+            // or don't have the interface specified at all.
+            string iface = (*net)->getIface();
+
+            const Subnet6Collection* subnets = (*net)->getAllSubnets();
+            if (subnets) {
+                // For each subnet, add it to a list of regular subnets.
+                for (auto subnet = subnets->begin(); subnet != subnets->end(); ++subnet) {
+                    if (iface.empty()) {
+                        iface = (*subnet)->getIface();
+                        continue;
+                    }
+
+                    if ((*subnet)->getIface().empty()) {
+                        continue;
+                    }
+
+                    if (iface != (*subnet)->getIface()) {
+                        isc_throw(DhcpConfigError, "Subnet " << (*subnet)->toText()
+                                  << " has specified interface " << (*subnet)->getIface()
+                                  << ", but earlier subnet in the same shared-network"
+                                  << " or the shared-network itself used " << iface);
+                    }
+
+                    // Let's collect the subnets in case we later find out the
+                    // subnet doesn't have a mandatory name.
+                    txt += (*subnet)->toText() + " ";
+                }
+            }
+
+            // Next, let's check name of the shared network.
+            if ((*net)->getName().empty()) {
+                isc_throw(DhcpConfigError, "Shared-network with subnets "
+                          << txt << " is missing mandatory 'name' parameter");
+            }
+
+            // Is it unique?
+            if (names.find((*net)->getName()) != names.end()) {
+                isc_throw(DhcpConfigError, "A shared-network with "
+                          "name " << (*net)->getName() << " defined twice.");
+            }
+            names.insert((*net)->getName());
+
+        }
+    }
+    
+    
 };
 
 } // anonymous namespace
@@ -270,6 +404,10 @@ configureDhcp6Server(Dhcpv6Srv&, isc::data::ConstElementPtr config_set,
             parser.parse(cfg_option_def, option_defs);
         }
 
+        // This parser is used in several places, so it should be available
+        // early.
+        Dhcp6ConfigParser global_parser;
+
         BOOST_FOREACH(config_pair, values_map) {
             // In principle we could have the following code structured as a series
             // of long if else if clauses. That would give a marginal performance
@@ -375,19 +513,26 @@ configureDhcp6Server(Dhcpv6Srv&, isc::data::ConstElementPtr config_set,
             }
 
             if (config_pair.first == "subnet6") {
-                SrvConfigPtr srv_cfg = CfgMgr::instance().getStagingCfg();
                 Subnets6ListConfigParser subnets_parser;
                 // parse() returns number of subnets parsed. We may log it one day.
-                subnets_parser.parse(srv_cfg, config_pair.second);
+                subnets_parser.parse(srv_config, config_pair.second);
                 continue;
             }
 
             if (config_pair.first == "shared-networks") {
-                /// @todo We need to create instance of SharedNetworks4ListParser
+                /// 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.
+
+                SharedNetworks6ListParser parser;
+                CfgSharedNetworks6Ptr cfg = srv_config->getCfgSharedNetworks6();
+                parser.parse(cfg, config_pair.second);
+
+                // We also need to put the subnets it contains into normal
+                // subnets list.
+                global_parser.copySubnets6(srv_config->getCfgSubnets6(), cfg);
                 continue;
             }
 
@@ -417,9 +562,13 @@ configureDhcp6Server(Dhcpv6Srv&, isc::data::ConstElementPtr config_set,
         }
 
         // Apply global options in the staging config.
-        Dhcp6ConfigParser global_parser;
         global_parser.parse(srv_config, mutable_cfg);
 
+        // This method conducts final sanity checks and tweaks. In particular,
+        // it checks that there is no conflict between plain subnets and those
+        // defined as part of shared networks.
+        global_parser.sanityChecks(srv_config, mutable_cfg);
+        
     } catch (const isc::Exception& ex) {
         LOG_ERROR(dhcp6_logger, DHCP6_PARSER_FAIL)
                   .arg(config_pair.first).arg(ex.what());

+ 311 - 3
src/bin/dhcp6/tests/config_parser_unittest.cc

@@ -280,6 +280,62 @@ public:
         }
     }
 
+    /// @brief Convenience method for running configuration
+    ///
+    /// This method does not throw, but signals errors using gtest macros.
+    ///
+    /// @param config text to be parsed as JSON
+    /// @param expected_code expected code (see cc/command_interpreter.h)
+    /// @param exp_error expected text error (check skipped if empty)
+    void configure(std::string config, int expected_code,
+                   std::string exp_error = "") {
+        ConstElementPtr json;
+        ASSERT_NO_THROW(json = parseDHCP6(config, true));
+
+        ConstElementPtr status;
+        EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+        ASSERT_TRUE(status);
+
+        int rcode;
+        ConstElementPtr comment = parseAnswer(rcode, status);
+        EXPECT_EQ(expected_code, rcode);
+
+        string text;
+        ASSERT_NO_THROW(text = comment->stringValue());
+
+        if (expected_code != rcode) {
+            std::cout << "Reported status: " << text << std::endl;
+        }
+
+        if ((rcode != 0)) {
+            if (!exp_error.empty()) {
+                EXPECT_EQ(exp_error, text);
+            }
+        }
+    }
+
+    /// @brief Checks if specified subnet is part of the collection
+    ///
+    /// @param col collection of subnets to be inspected
+    /// @param subnet text notation (e.g. 192.0.2.0/24)
+    /// @param t1 expected renew-timer value
+    /// @param t2 expected rebind-timer value
+    /// @param preferred expected preferred-lifetime value
+    /// @param valid expected valid-lifetime value
+    void
+    checkSubnet(const Subnet6Collection& col, std::string subnet,
+                uint32_t t1, uint32_t t2, uint32_t pref, uint32_t valid) {
+        const auto& index = col.get<SubnetPrefixIndexTag>();
+        auto subnet_it = index.find(subnet);
+        ASSERT_NE(subnet_it, index.cend());
+        Subnet6Ptr s = *subnet_it;
+
+        EXPECT_EQ(t1, s->getT1());
+        EXPECT_EQ(t2, s->getT2());
+        EXPECT_EQ(pref, s->getPreferred());
+        EXPECT_EQ(valid, s->getValid());
+    }
+
     /// @brief Returns an interface configuration used by the most of the
     /// unit tests.
     std::string genIfaceConfig() const {
@@ -5060,9 +5116,9 @@ TEST_F(Dhcp6ParserTest, invalidClientClassDictionary) {
         "       \"bogus\": \"bad\" \n"
         "   } \n"
         "], \n"
-        "\"subnet4\": [ {  \n"
-        "    \"pools\": [ { \"pool\":  \"192.0.2.1 - 192.0.2.100\" } ], \n"
-        "    \"subnet\": \"192.0.2.0/24\"  \n"
+        "\"subnet6\": [ {  \n"
+        "    \"pools\": [ { \"pool\":  \"2001:db8::1 - 2001:db8::ffff\" } ], \n"
+        "    \"subnet\": \"2001:db8::/64\"  \n"
         " } ] \n"
         "} \n";
 
@@ -5303,4 +5359,256 @@ TEST_F(Dhcp6ParserTest, outsideSubnetPool) {
     EXPECT_EQ(expected, text);
 }
 
+// Test verifies that empty shared networks are accepted.
+TEST_F(Dhcp6ParserTest, sharedNetworksEmpty) {
+    string config = "{\n"
+        "\"valid-lifetime\": 4000, \n"
+        "\"rebind-timer\": 2000, \n"
+        "\"renew-timer\": 1000, \n"
+        "\"subnet6\": [ {  \n"
+        "    \"subnet\": \"2001:db8::/48\"  \n"
+        " } ],\n"
+        "\"shared-networks\": [ ]\n"
+        "} \n";
+
+    configure(config, CONTROL_RESULT_SUCCESS, "");
+}
+
+// Test verifies that if a shared network is defined, it at least has to have
+// a name.
+TEST_F(Dhcp6ParserTest, sharedNetworksNoName) {
+    string config = "{\n"
+        "\"valid-lifetime\": 4000, \n"
+        "\"rebind-timer\": 2000, \n"
+        "\"renew-timer\": 1000, \n"
+        "\"subnet6\": [ {  \n"
+        "    \"subnet\": \"2001:db8::/48\"  \n"
+        " } ],\n"
+        "\"shared-networks\": [ { } ]\n"
+        "} \n";
+
+    EXPECT_THROW(parseDHCP6(config, true), Dhcp6ParseError);
+}
+
+// Test verifies that empty shared networks are accepted.
+TEST_F(Dhcp6ParserTest, sharedNetworksEmptyName) {
+    string config = "{\n"
+        "\"valid-lifetime\": 4000, \n"
+        "\"rebind-timer\": 2000, \n"
+        "\"renew-timer\": 1000, \n"
+        "\"subnet6\": [ {  \n"
+        "    \"subnet\": \"2001:db8::/48\"  \n"
+        " } ],\n"
+        "\"shared-networks\": [ { \"name\": \"\" } ]\n"
+        "} \n";
+
+    configure(config, CONTROL_RESULT_ERROR,
+              "Shared-network with subnets  is missing mandatory 'name' parameter");
+}
+
+// Test verifies that a degenerated shared-network (no subnets) is
+// accepted.
+TEST_F(Dhcp6ParserTest, sharedNetworksName) {
+    string config = "{\n"
+        "\"subnet6\": [ {  \n"
+        "    \"subnet\": \"2001:db8::/48\",\n"
+        "    \"pools\": [ { \"pool\": \"2001:db8::1 - 2001:db8::ffff\" } ]\n"
+        " } ],\n"
+        "\"shared-networks\": [ { \"name\": \"foo\" } ]\n"
+        "} \n";
+
+    configure(config, CONTROL_RESULT_SUCCESS, "");
+
+    // Now verify that the shared network was indeed configured.
+    CfgSharedNetworks6Ptr cfg_net = CfgMgr::instance().getStagingCfg()
+        ->getCfgSharedNetworks6();
+    ASSERT_TRUE(cfg_net);
+    const SharedNetwork6Collection* nets = cfg_net->getAll();
+    ASSERT_TRUE(nets);
+    ASSERT_EQ(1, nets->size());
+    SharedNetwork6Ptr net = *(nets->begin());
+    ASSERT_TRUE(net);
+    EXPECT_EQ("foo", net->getName());
+
+    // Verify that there are no subnets in this shared-network
+    const Subnet6Collection * subs = net->getAllSubnets();
+    ASSERT_TRUE(subs);
+    EXPECT_EQ(0, subs->size());
+}
+
+// Test verifies that a degenerated shared-network (just one subnet) is
+// accepted. Also tests that, unless explicitly specified, the subnet
+// gets default values.
+TEST_F(Dhcp6ParserTest, sharedNetworks1subnet) {
+    string config = "{\n"
+        "\"shared-networks\": [ {\n"
+        "    \"name\": \"foo\"\n,"
+        "    \"subnet6\": [ { \n"
+        "        \"subnet\": \"2001:db8::/48\",\n"
+        "        \"pools\": [ { \"pool\": \"2001:db8::1 - 2001:db8::ffff\" } ]\n"
+        "    } ]\n"
+        " } ]\n"
+        "} \n";
+
+    configure(config, CONTROL_RESULT_SUCCESS, "");
+
+    // Now verify that the shared network was indeed configured.
+    CfgSharedNetworks6Ptr cfg_net = CfgMgr::instance().getStagingCfg()
+        ->getCfgSharedNetworks6();
+    ASSERT_TRUE(cfg_net);
+
+    // There should be exactly one shared subnet.
+    const SharedNetwork6Collection* nets = cfg_net->getAll();
+    ASSERT_TRUE(nets);
+    ASSERT_EQ(1, nets->size());
+
+    SharedNetwork6Ptr net = *(nets->begin());
+    ASSERT_TRUE(net);
+    EXPECT_EQ("foo", net->getName());
+
+    // It should have one subnet. The subnet should have default values.
+    const Subnet6Collection * subs = net->getAllSubnets();
+    ASSERT_TRUE(subs);
+    EXPECT_EQ(1, subs->size());
+    checkSubnet(*subs, "2001:db8::/48", 900, 1800, 3600, 7200);
+
+    // Now make sure the subnet was added to global list of subnets.
+    CfgSubnets6Ptr subnets6 = CfgMgr::instance().getStagingCfg()->getCfgSubnets6();
+    ASSERT_TRUE(subnets6);
+
+    subs = subnets6->getAll();
+    ASSERT_TRUE(subs);
+    checkSubnet(*subs, "2001:db8::/48", 900, 1800, 3600, 7200);
+}
+
+// Test verifies that a a proper shared-network (three subnets) is
+// accepted. It verifies several things:
+// - that more than one subnet can be added to shared subnets
+// - that each subnet being part of the shared subnets is also stored in
+//   global subnets collection
+// - that a subnet can inherit global values
+// - that subnet can override global parameters
+// - that overridden parameters only affect one subnet and not others
+TEST_F(Dhcp6ParserTest, sharedNetworks3subnets) {
+    string config = "{\n"
+        "\"renew-timer\": 1000, \n"
+        "\"rebind-timer\": 2000, \n"
+        "\"preferred-lifetime\": 3000, \n"
+        "\"valid-lifetime\": 4000, \n"
+        "\"shared-networks\": [ {\n"
+        "    \"name\": \"foo\"\n,"
+        "    \"subnet6\": [\n"
+        "    { \n"
+        "        \"subnet\": \"2001:db1::/48\",\n"
+        "        \"pools\": [ { \"pool\": \"2001:db1::/64\" } ]\n"
+        "    },\n"
+        "    { \n"
+        "        \"subnet\": \"2001:db2::/48\",\n"
+        "        \"pools\": [ { \"pool\": \"2001:db2::/64\" } ],\n"
+        "        \"renew-timer\": 2,\n"
+        "        \"rebind-timer\": 22,\n"
+        "        \"preferred-lifetime\": 222,\n"
+        "        \"valid-lifetime\": 2222\n"
+        "    },\n"
+        "    { \n"
+        "        \"subnet\": \"2001:db3::/48\",\n"
+        "        \"pools\": [ { \"pool\": \"2001:db3::/64\" } ]\n"
+        "    }\n"
+        "    ]\n"
+        " } ]\n"
+        "} \n";
+
+    configure(config, CONTROL_RESULT_SUCCESS, "");
+
+    // Now verify that the shared network was indeed configured.
+    CfgSharedNetworks6Ptr cfg_net = CfgMgr::instance().getStagingCfg()
+        ->getCfgSharedNetworks6();
+
+    // There is expected one shared subnet.
+    ASSERT_TRUE(cfg_net);
+    const SharedNetwork6Collection* nets = cfg_net->getAll();
+    ASSERT_TRUE(nets);
+    ASSERT_EQ(1, nets->size());
+
+    SharedNetwork6Ptr net = *(nets->begin());
+    ASSERT_TRUE(net);
+
+    EXPECT_EQ("foo", net->getName());
+
+    const Subnet6Collection * subs = net->getAllSubnets();
+    ASSERT_TRUE(subs);
+    EXPECT_EQ(3, subs->size());
+    checkSubnet(*subs, "2001:db1::/48", 1000, 2000, 3000, 4000);
+    checkSubnet(*subs, "2001:db2::/48", 2, 22, 222, 2222);
+    checkSubnet(*subs, "2001:db3::/48", 1000, 2000, 3000, 4000);
+
+    // Now make sure the subnet was added to global list of subnets.
+    CfgSubnets6Ptr subnets6 = CfgMgr::instance().getStagingCfg()->getCfgSubnets6();
+    ASSERT_TRUE(subnets6);
+
+    subs = subnets6->getAll();
+    ASSERT_TRUE(subs);
+    checkSubnet(*subs, "2001:db1::/48", 1000, 2000, 3000, 4000);
+    checkSubnet(*subs, "2001:db2::/48", 2, 22, 222, 2222);
+    checkSubnet(*subs, "2001:db3::/48", 1000, 2000, 3000, 4000);
+}
+
+// This test checks if parameters are derived properly:
+// - global to shared network
+// - shared network to subnet
+TEST_F(Dhcp6ParserTest, sharedNetworksDerive) {
+    string config = "{\n"
+        "\"renew-timer\": 1, \n"
+        "\"rebind-timer\": 2, \n"
+        "\"preferred-lifetime\": 3,\n"
+        "\"valid-lifetime\": 4, \n"
+        "\"shared-networks\": [ {\n"
+        "    \"name\": \"foo\"\n,"
+        "    \"renew-timer\": 10,\n"
+        "    \"subnet6\": [\n"
+        "    { \n"
+        "        \"subnet\": \"2001:db1::/48\",\n"
+        "        \"pools\": [ { \"pool\": \"2001:db1::/64\" } ]\n"
+        "    },\n"
+        "    { \n"
+        "        \"subnet\": \"2001:db2::/48\",\n"
+        "        \"pools\": [ { \"pool\": \"2001:db2::/64\" } ],\n"
+        "        \"renew-timer\": 100\n"
+        "    }\n"
+        "    ]\n"
+        " } ]\n"
+        "} \n";
+
+    configure(config, CONTROL_RESULT_SUCCESS, "");
+
+    // Now verify that the shared network was indeed configured.
+    CfgSharedNetworks6Ptr cfg_net = CfgMgr::instance().getStagingCfg()
+        ->getCfgSharedNetworks6();
+
+    // There is expected one shared subnet.
+    ASSERT_TRUE(cfg_net);
+    const SharedNetwork6Collection* nets = cfg_net->getAll();
+    ASSERT_TRUE(nets);
+    ASSERT_EQ(1, nets->size());
+
+    SharedNetwork6Ptr net = *(nets->begin());
+    ASSERT_TRUE(net);
+
+    const Subnet6Collection * subs = net->getAllSubnets();
+    ASSERT_TRUE(subs);
+    EXPECT_EQ(2, subs->size());
+
+    // For the first subnet, the renew-timer should be 10, because it was
+    // derived from shared-network level. Other parameters a derived
+    // from global scope to shared-network level and later again to
+    // subnet6 level.
+    checkSubnet(*subs, "2001:db1::/48", 10, 2, 3, 4);
+
+    // For the second subnet, the renew-timer should be 100, because it
+    // was specified explicitly. Other parameters a derived
+    // from global scope to shared-network level and later again to
+    // subnet6 level.
+    checkSubnet(*subs, "2001:db2::/48", 100, 2, 3, 4);
+}
+
 };

+ 3 - 4
src/lib/dhcpsrv/parsers/simple_parser4.cc

@@ -159,13 +159,12 @@ size_t SimpleParser4::deriveParameters(isc::data::ElementPtr global) {
         }
     }
 
+    // Deriving parameters for shared networks is a bit more involved.
+    // First, the shared-network level derives from global, and then
+    // subnets within derive from it.
     ConstElementPtr shared = global->get("shared-networks");
     if (shared) {
         BOOST_FOREACH(ElementPtr net, shared->listValue()) {
-
-            // This level derives global parameters to shared network
-            // level.
-
             // First try to inherit the parameters from shared network,
             // if defined there.
             // Then try to inherit them from global.

+ 34 - 1
src/lib/dhcpsrv/parsers/simple_parser6.cc

@@ -119,7 +119,6 @@ size_t SimpleParser6::setAllDefaults(isc::data::ElementPtr global) {
     }
 
     // Now set the defaults for defined subnets
-    // Now set the defaults for defined subnets
     ConstElementPtr subnets = global->get("subnet6");
     if (subnets) {
         cnt += setListDefaults(subnets, SUBNET6_DEFAULTS);
@@ -132,6 +131,17 @@ size_t SimpleParser6::setAllDefaults(isc::data::ElementPtr global) {
         cnt += setDefaults(mutable_cfg, IFACE6_DEFAULTS);
     }
 
+    // Set defaults for shared networks
+    ConstElementPtr shared = global->get("shared-networks");
+    if (shared) {
+        BOOST_FOREACH(ElementPtr net, shared->listValue()) {
+            ConstElementPtr subs = net->get("subnet6");
+            if (subs) {
+                cnt += setListDefaults(subs, SUBNET6_DEFAULTS);
+            }
+        }
+    }
+
     return (cnt);
 }
 
@@ -146,6 +156,29 @@ size_t SimpleParser6::deriveParameters(isc::data::ElementPtr global) {
         }
     }
 
+    // Deriving parameters for shared networks is a bit more involved.
+    // First, the shared-network level derives from global, and then
+    // subnets within derive from it.
+    ConstElementPtr shared = global->get("shared-networks");
+    if (shared) {
+        BOOST_FOREACH(ElementPtr net, shared->listValue()) {
+            // First try to inherit the parameters from shared network,
+            // if defined there.
+            // Then try to inherit them from global.
+            cnt += SimpleParser::deriveParams(global, net,
+                                              INHERIT_GLOBAL_TO_SUBNET6);
+
+            // Now we need to go thrugh all the subnets in this net.
+            subnets = net->get("subnet6");
+            if (subnets) {
+                BOOST_FOREACH(ElementPtr single_subnet, subnets->listValue()) {
+                    cnt += SimpleParser::deriveParams(net, single_subnet,
+                                                      INHERIT_GLOBAL_TO_SUBNET6);
+                }
+            }
+        }
+    }
+
     return (cnt);
 }