]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#3049] Initial v6 changes
authorThomas Markwalder <tmark@isc.org>
Wed, 8 Jan 2025 18:09:53 +0000 (13:09 -0500)
committerThomas Markwalder <tmark@isc.org>
Wed, 22 Jan 2025 15:24:25 +0000 (15:24 +0000)
Pool6 UT and added keywords,
first cut of logic to Dhcpv6Srv

/src/bin/dhcp6/dhcp6_srv.*
    replaced Dhcpv6Srv::checkDynamicSubnetChange()
    with Dhcpv6Srv::checkPostAssignmentChanges()

/src/lib/dhcpsrv/parsers/simple_parser6.cc
    Added parameter to POOL6_PARAMETERS

/src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc
    TEST_F(DhcpParserTest, validDdnsParmatersPool4) - use function template
    TEST_F(DhcpParserTest, validDdnsParmatersPool6) - new test

src/bin/dhcp6/dhcp6_srv.cc
src/bin/dhcp6/dhcp6_srv.h
src/lib/dhcpsrv/parsers/simple_parser4.cc
src/lib/dhcpsrv/parsers/simple_parser6.cc
src/lib/dhcpsrv/srv_config.cc
src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc

index 81fd2ce8cba97139696f82ca4de95eff79d3ddff..3b99d231a68790534e31c1b74b8af28a3d37a3c8 100644 (file)
@@ -2249,9 +2249,10 @@ Dhcpv6Srv::assignLeases(const Pkt6Ptr& question, Pkt6Ptr& answer,
         }
     }
 
-    // Subnet may be modified by the allocation engine, there are things
-    // we need to do when that happens.
-    checkDynamicSubnetChange(question, answer, ctx, orig_subnet);
+    // Need to check for pool-level DDNS parameters and if the
+    // subnet was modified by the allocation engine, there are things
+    // we need to do either case.
+    checkPostAssignmentChanges(question, answer, ctx, orig_subnet);
 }
 
 void
@@ -2440,6 +2441,8 @@ Dhcpv6Srv::createNameChangeRequests(const Pkt6Ptr& answer,
 
     // Iterate over the IAContexts (there should be one per client IA processed).
     // For the first address in each IA_NA, create the appropriate NCR(s).
+    /// @todo TKM - we should really consider only doing this for the first
+    /// IA_NA.
     for (auto& ia_ctx : ctx.getIAContexts()) {
         if ((ia_ctx.type_ != Lease::TYPE_NA) || !ia_ctx.ia_rsp_) {
             continue;
@@ -3267,9 +3270,10 @@ Dhcpv6Srv::extendLeases(const Pkt6Ptr& query, Pkt6Ptr& reply,
         }
     }
 
-    // Subnet may be modified by the allocation engine, there are things
-    // we need to do when that happens.
-    checkDynamicSubnetChange(query, reply, ctx, orig_subnet);
+    // Need to check for pool-level DDNS parameters and if the
+    // subnet was modified by the allocation engine, there are things
+    // we need to do either case.
+    checkPostAssignmentChanges(query, reply, ctx, orig_subnet);
 }
 
 void
@@ -5069,52 +5073,71 @@ Dhcpv6Srv::setTeeTimes(uint32_t preferred_lft,
 }
 
 void
-Dhcpv6Srv::checkDynamicSubnetChange(const Pkt6Ptr& question, Pkt6Ptr& answer,
-                                    AllocEngine::ClientContext6& ctx,
-                                    const ConstSubnet6Ptr orig_subnet) {
-    // If the subnet's are the same there's nothing to do.
-    if ((!ctx.subnet_) || (!orig_subnet) || (orig_subnet->getID() == ctx.subnet_->getID())) {
-        return;
+Dhcpv6Srv::checkPostAssignmentChanges(const Pkt6Ptr& question, Pkt6Ptr& answer,
+                                      AllocEngine::ClientContext6& ctx,
+                                      const ConstSubnet6Ptr orig_subnet) {
+    bool reprocess_client_name = false;
+
+    // Find the pool to which the first active lease address belongs and use it
+    // to update DDNS parameters to include those from the pool.
+    OptionPtr ia = answer->getOption(D6O_IA_NA);
+    if (ia) {
+        Option6IAAddrPtr iaaddr = boost::dynamic_pointer_cast<Option6IAAddr>(ia->getOption(D6O_IAADDR));
+        if (iaaddr && (iaaddr->getValid() > 0)) {
+            auto ddns_params = ctx.getDdnsParams();
+            auto pool = ddns_params->setPoolFromAddress(iaaddr->getAddress());
+            if (pool) {
+                // If the pool has any DDNS parameters we need to recalculate the FQDN.
+                reprocess_client_name = pool->hasDdnsParameters();
+            }
+        }
     }
 
-    // We get the network for logging only. It should always be set as this a dynamic
-    // change should only happen within shared-networks.  Not having one might not be
-    // an error if a hook changed the subnet?
-    SharedNetwork6Ptr network;
-    orig_subnet->getSharedNetwork(network);
-    LOG_DEBUG(packet6_logger, DBG_DHCP6_BASIC_DATA, DHCP6_SUBNET_DYNAMICALLY_CHANGED)
-             .arg(question->getLabel())
-             .arg(orig_subnet->toText())
-             .arg(ctx.subnet_->toText())
-             .arg(network ? network->getName() : "<no network?>");
-
-    // The DDNS parameters may have changed with the subnet, so we need to
-    // recalculate the client name.
-
-    // Save the current DNS values on the context.
-    std::string prev_hostname = ctx.hostname_;
-    bool prev_fwd_dns_update = ctx.fwd_dns_update_;
-    bool prev_rev_dns_update = ctx.rev_dns_update_;
-
-    // Remove the current FQDN option from the answer.
-    answer->delOption(D6O_CLIENT_FQDN);
-
-    // Recalculate the client's FQDN.  This will replace the FQDN option and
-    // update the context values for hostname_ and DNS directions.
-    processClientFqdn(question, answer, ctx);
-
-    // If this is a real allocation and the DNS values changed we need to
-    // update the leases.
-    if (!ctx.fake_allocation_ &&
-        ((prev_hostname != ctx.hostname_) ||
-        (prev_fwd_dns_update != ctx.fwd_dns_update_) ||
-        (prev_rev_dns_update != ctx.rev_dns_update_))) {
-        for (auto const& l : ctx.new_leases_) {
-            l->hostname_ = ctx.hostname_;
-            l->fqdn_fwd_ = ctx.fwd_dns_update_;
-            l->fqdn_rev_ = ctx.rev_dns_update_;
-            l->reuseable_valid_lft_ = 0;
-            LeaseMgrFactory::instance().updateLease6(l);
+    // Check if the subnet was dynamnically changed by the allocation engine.
+    if (ctx.subnet_ && orig_subnet && (orig_subnet->getID() != ctx.subnet_->getID())) {
+        // We get the network for logging only. It should always be set as this a dynamic
+        // change should only happen within shared-networks.  Not having one might not be
+        // an error if a hook changed the subnet?
+        SharedNetwork6Ptr network;
+        orig_subnet->getSharedNetwork(network);
+        LOG_DEBUG(packet6_logger, DBG_DHCP6_BASIC_DATA, DHCP6_SUBNET_DYNAMICALLY_CHANGED)
+                 .arg(question->getLabel())
+                 .arg(orig_subnet->toText())
+                 .arg(ctx.subnet_->toText())
+                 .arg(network ? network->getName() : "<no network?>");
+
+        // The subnet changed so we need to recalculate the FQDN.
+        reprocess_client_name = true;
+    }
+
+    // Recalulate the FQDN if either the pool has DDNS parameters or the
+    // selected subnet was changed.
+    if (reprocess_client_name) {
+        // Save the current DNS values on the context.
+        std::string prev_hostname = ctx.hostname_;
+        bool prev_fwd_dns_update = ctx.fwd_dns_update_;
+        bool prev_rev_dns_update = ctx.rev_dns_update_;
+
+        // Remove the current FQDN option from the answer.
+        answer->delOption(D6O_CLIENT_FQDN);
+
+        // Recalculate the client's FQDN.  This will replace the FQDN option and
+        // update the context values for hostname_ and DNS directions.
+        processClientFqdn(question, answer, ctx);
+
+        // If this is a real allocation and the DNS values changed we need to
+        // update the leases.
+        if (!ctx.fake_allocation_ &&
+            ((prev_hostname != ctx.hostname_) ||
+            (prev_fwd_dns_update != ctx.fwd_dns_update_) ||
+            (prev_rev_dns_update != ctx.rev_dns_update_))) {
+            for (auto const& l : ctx.new_leases_) {
+                l->hostname_ = ctx.hostname_;
+                l->fqdn_fwd_ = ctx.fwd_dns_update_;
+                l->fqdn_rev_ = ctx.rev_dns_update_;
+                l->reuseable_valid_lft_ = 0;
+                LeaseMgrFactory::instance().updateLease6(l);
+            }
         }
     }
 }
index 8d921ea51cd4987730d3575a4e0d2500baf3a798..f02fdabb80b8b5075b1e6e9dd1d98d85d3c3f196 100644 (file)
@@ -1030,18 +1030,18 @@ protected:
 
     /// @brief Iterates over new leases, update stale DNS entries
     ///
-    /// Checks the context's current subnet (most recently selected) against
-    /// an original selected subnet.  If they are the same the function
-    /// simply returns.
-    ///
-    /// If they differ, we treat this as a dynamic subnet change made by the
-    /// allocation engine. It is possible that DDNS subnet parameters for
-    /// the new subnet are different and this needs to handled. We first
-    /// save the current DNS-related values from the context and then
-    /// re-run processClientFqdn().  This will rebuild the FQDN option
-    /// to send back to the client based on the new subnet as well as
-    /// update the context.  If the new values are different from the
-    /// previous values, we iterate over the leases and update the
+    /// Updates the context DDNS parameters to include those from the pool
+    /// associated with the first active NA lease address and then checks
+    /// to see if the subnet has been dynamicaly changed.  If either the
+    /// pool has DDNS parameters or the subnet has changed the FQDN and
+    /// DDNS flags are recalculated in the event the pool or subnet change
+    /// introduced different parameter values otherwise the function returns.
+    ///
+    /// When recalculating we first save the current DNS-related values
+    /// from the context and then re-run processClientFqdn().  This will
+    /// rebuild the FQDN option to send back to the client based on the new
+    /// subnet as well as update the context.  If the new values are different
+    /// from the previous values, we iterate over the leases and update the
     /// DNS values.
     ///
     /// @param question Client's message.
@@ -1063,9 +1063,9 @@ protected:
     /// class guards etc)
     /// 3. subnets have differing options or DDNS parameters
     //
-    void checkDynamicSubnetChange(const Pkt6Ptr& question, Pkt6Ptr& answer,
-                                  AllocEngine::ClientContext6& ctx,
-                                  const ConstSubnet6Ptr orig_subnet);
+    void checkPostAssignmentChanges(const Pkt6Ptr& question, Pkt6Ptr& answer,
+                                    AllocEngine::ClientContext6& ctx,
+                                    const ConstSubnet6Ptr orig_subnet);
 
     /// @brief Return the PD exclude option to include.
     ///
index 151ea5e6bdd66a55bf445416c8e822d300b0362c..705824e5069b0903a5568efeccd97c850e1b32bb 100644 (file)
@@ -341,7 +341,6 @@ const SimpleKeywords SimpleParser4::POOL4_PARAMETERS = {
     { "evaluate-additional-classes",    Element::list },
     { "user-context",                   Element::map },
     { "comment",                        Element::string },
-    { "metadata",                       Element::map },
     { "ddns-send-updates",              Element::boolean },
     { "ddns-override-no-update",        Element::boolean },
     { "ddns-override-client-update",    Element::boolean },
@@ -355,7 +354,8 @@ const SimpleKeywords SimpleParser4::POOL4_PARAMETERS = {
     { "ddns-conflict-resolution-mode",  Element::string },
     { "ddns-ttl",                       Element::integer },
     { "ddns-ttl-min",                   Element::integer },
-    { "ddns-ttl-max",                   Element::integer }
+    { "ddns-ttl-max",                   Element::integer },
+    { "metadata",                       Element::map }
 };
 
 /// @brief This table defines all shared network parameters for DHCPv4.
index ca8ca3a4a77743a39abd6a18e672616f50070837..0d4948e534a4f0a18c2eb24b3072a88d23b90333 100644 (file)
@@ -323,16 +323,30 @@ const ParamsList SimpleParser6::INHERIT_TO_SUBNET6 = {
 /// list and map types for entries.
 /// Order follows pool_param rules in bison grammar.
 const SimpleKeywords SimpleParser6::POOL6_PARAMETERS = {
-    { "pool",                        Element::string },
-    { "pool-id",                     Element::integer },
-    { "option-data",                 Element::list },
-    { "client-class",                Element::string },
-    { "client-classes",              Element::list },
-    { "require-client-classes",      Element::list },
-    { "evaluate-additional-classes", Element::list },
-    { "user-context",                Element::map },
-    { "comment",                     Element::string },
-    { "metadata",                    Element::map }
+    { "pool",                           Element::string },
+    { "pool-id",                        Element::integer },
+    { "option-data",                    Element::list },
+    { "client-class",                   Element::string },
+    { "client-classes",                 Element::list },
+    { "require-client-classes",         Element::list },
+    { "evaluate-additional-classes",    Element::list },
+    { "user-context",                   Element::map },
+    { "comment",                        Element::string },
+    { "ddns-send-updates",              Element::boolean },
+    { "ddns-override-no-update",        Element::boolean },
+    { "ddns-override-client-update",    Element::boolean },
+    { "ddns-replace-client-name",       Element::string },
+    { "ddns-generated-prefix",          Element::string },
+    { "ddns-qualifying-suffix",         Element::string },
+    { "hostname-char-set",              Element::string },
+    { "hostname-char-replacement",      Element::string },
+    { "ddns-update-on-renew",           Element::boolean },
+    { "ddns-ttl-percent",               Element::real },
+    { "ddns-conflict-resolution-mode",  Element::string },
+    { "ddns-ttl",                       Element::integer },
+    { "ddns-ttl-min",                   Element::integer },
+    { "ddns-ttl-max",                   Element::integer },
+    { "metadata",                       Element::map }
 };
 
 /// @brief This table defines all prefix delegation pool parameters.
index 24defcf9a800755a3c79349a6ff0145ac698ced7..3a19e1af28d5e7656b9b2c6849821ab2fa0ff884 100644 (file)
@@ -1244,7 +1244,8 @@ DdnsParams::setPoolFromAddress(const asiolink::IOAddress& address) {
                   "DdnsParams::setPoolFromAddress called without a subnet");
     }
 
-    pool_ = subnet_->getPool((address.isV4() ?  Lease::TYPE_V4 : Lease::TYPE_NA), address, false);
+    pool_ = subnet_->getPool((address.isV4() ?  Lease::TYPE_V4 : Lease::TYPE_NA),
+                              address, false);
     return (pool_);
 }
 
index 5ddbc0175e9b945bcc7aef85dd04ba7bebdfad6c..e37e629ec63b93e1f1e51efddde861fe71b0dd58 100644 (file)
@@ -100,49 +100,49 @@ public:
                __LINE__,
                R"^({
                    "id": 1,
-                   "ddns-ttl-percent": 5.0 
+                   "ddns-ttl-percent": 5.0
                })^",
                5.0, 0, 0, 0
            },{
                __LINE__,
                R"^({
                    "id": 1,
-                   "ddns-ttl-min": 25 
+                   "ddns-ttl-min": 25
                })^",
                0.0, 0, 25, 0
            },{
                __LINE__,
                R"^({
-                   "id": 1, 
-                   "ddns-ttl-max": 150 
+                   "id": 1,
+                   "ddns-ttl-max": 150
                })^",
-               0.0, 0, 0, 150 
+               0.0, 0, 0, 150
            },{
                __LINE__,
                R"^({
                    "id": 1,
                    "ddns-ttl-min": 25,
-                   "ddns-ttl-max": 150 
+                   "ddns-ttl-max": 150
                })^",
-               0.0, 0, 25, 150 
+               0.0, 0, 25, 150
            },{
                __LINE__,
                R"^({
                    "id": 1, "subnet": "192.0.2.0/24",
                    "ddns-ttl-percent": 5.0,
                    "ddns-ttl-min": 25,
-                   "ddns-ttl-max": 150 
+                   "ddns-ttl-max": 150
                })^",
-               5.0, 0, 25, 150 
+               5.0, 0, 25, 150
            }};
 
         ElementPtr subnet_elem = Element::create(family == AF_INET ?
-                                                 "192.0.2.0/24" : "2001:db8::/64");    
+                                                 "192.0.2.0/24" : "2001:db8::/64");
            for (const auto& scenario : scenarios) {
-               std::stringstream oss; 
+               std::stringstream oss;
                oss << "scenario at " << scenario.line_no_;
                SCOPED_TRACE(oss.str());
-       
+
                // Parse configuration specified above.
                ElementPtr config_element;
                ASSERT_NO_THROW_LOG(config_element = Element::fromJSON(scenario.json_));
@@ -151,24 +151,24 @@ public:
                ParserType parser(family);
 
                NetworkPtrType subnet;
-       
+
                ASSERT_NO_THROW_LOG(subnet = parser.parse(config_element));
                ASSERT_TRUE(subnet);
-       
-               EXPECT_EQ(subnet->getDdnsTtlPercent().unspecified(), (scenario.ddns_ttl_percent_ == 0.0)); 
-               EXPECT_EQ(subnet->getDdnsTtlPercent(), scenario.ddns_ttl_percent_); 
-       
-               EXPECT_EQ(subnet->getDdnsTtl().unspecified(), (scenario.ddns_ttl_ == 0)); 
-               EXPECT_EQ(subnet->getDdnsTtl(), scenario.ddns_ttl_); 
-       
-               EXPECT_EQ(subnet->getDdnsTtlMin().unspecified(), (scenario.ddns_ttl_min_ == 0)); 
-               EXPECT_EQ(subnet->getDdnsTtlMin(), scenario.ddns_ttl_min_); 
-       
-               EXPECT_EQ(subnet->getDdnsTtlMax().unspecified(), (scenario.ddns_ttl_max_ == 0)); 
-               EXPECT_EQ(subnet->getDdnsTtlMax(), scenario.ddns_ttl_max_); 
+
+               EXPECT_EQ(subnet->getDdnsTtlPercent().unspecified(), (scenario.ddns_ttl_percent_ == 0.0));
+               EXPECT_EQ(subnet->getDdnsTtlPercent(), scenario.ddns_ttl_percent_);
+
+               EXPECT_EQ(subnet->getDdnsTtl().unspecified(), (scenario.ddns_ttl_ == 0));
+               EXPECT_EQ(subnet->getDdnsTtl(), scenario.ddns_ttl_);
+
+               EXPECT_EQ(subnet->getDdnsTtlMin().unspecified(), (scenario.ddns_ttl_min_ == 0));
+               EXPECT_EQ(subnet->getDdnsTtlMin(), scenario.ddns_ttl_min_);
+
+               EXPECT_EQ(subnet->getDdnsTtlMax().unspecified(), (scenario.ddns_ttl_max_ == 0));
+               EXPECT_EQ(subnet->getDdnsTtlMax(), scenario.ddns_ttl_max_);
            }
        }
-       
+
        // Verifies invalid permutations of ddns-ttl-percent, ddns-ttl,
        // ddns-ttl-min, and ddns-ttl-max values for SubnetX.
     template<typename ParserType>
@@ -178,7 +178,7 @@ public:
                std::string json_;
                std::string exp_message_;
            };
-       
+
            std::list<Scenario> scenarios = {
            {
                __LINE__,
@@ -201,7 +201,7 @@ public:
                R"^({
                    "id": 1,
                    "ddns-ttl": 100,
-                   "ddns-ttl-max": 150 
+                   "ddns-ttl-max": 150
                })^",
                "subnet configuration failed: cannot specify both ddns-ttl-max and ddns-ttl"
            },{
@@ -209,18 +209,18 @@ public:
                R"^({
                    "id": 1,
                    "ddns-ttl-min": 150,
-                   "ddns-ttl-max": 25 
+                   "ddns-ttl-max": 25
                })^",
                "subnet configuration failed: ddns-ttl-max: 25 must be greater than ddns-ttl-min: 150"
            }};
-       
+
         ElementPtr subnet_elem = Element::create(family == AF_INET ?
-                                                 "192.0.2.0/24" : "2001:db8::/64");    
+                                                 "192.0.2.0/24" : "2001:db8::/64");
            for (const auto& scenario : scenarios) {
-               std::stringstream oss; 
+               std::stringstream oss;
                oss << "scenario at " << scenario.line_no_;
                SCOPED_TRACE(oss.str());
-       
+
                // Parse configuration specified above.
                ElementPtr config_element;
                ASSERT_NO_THROW_LOG(config_element = Element::fromJSON(scenario.json_));
@@ -229,6 +229,121 @@ public:
                ASSERT_THROW_MSG(parser.parse(config_element), DhcpConfigError, scenario.exp_message_);
            }
        }
+
+    /// @brief Tests valid DDNS parameters in v4 or v6 pools.
+    ///
+    /// As this uses the
+    /// same parser as Network derivatives we skip retesting all the
+    /// invalid permuations. This test ensures all supported
+    /// parameters can be set.
+    /// @tparam PoolListParserType type of pool list parser to use, Pools4ListParser
+    /// or Pools6ListParser.
+    /// @param pool1 string pool specification for the first pool
+    /// @param pool2 string pool specification for the first pool
+    template<typename PoolListParserType>
+       void validPoolDdnsParmaters(const std::string& pool1, const std::string& pool2) {
+        std::stringstream ss;
+
+        ss <<
+           R"^([{ "pool": ")^" << pool1 << "\"," <<
+           R"^(
+                "ddns-send-updates": true,
+                "ddns-override-no-update": true,
+                "ddns-override-client-update": true,
+                "ddns-replace-client-name": "always",
+                "ddns-generated-prefix": "prefix",
+                "ddns-qualifying-suffix": "suffix",
+                "hostname-char-set": "[a-z]",
+                "hostname-char-replacement": "X",
+                "ddns-update-on-renew": true,
+                "ddns-ttl-percent": 0.5,
+                "ddns-conflict-resolution-mode": "check-with-dhcid",
+                "ddns-ttl-min": 200,
+                "ddns-ttl-max": 500
+            },
+            {
+                "pool": ")^" << pool2 << "\"," <<
+            R"^(
+                "ddns-ttl": 300
+            }])^";
+
+        ElementPtr config_element = Element::fromJSON(ss.str());
+
+        PoolStoragePtr pools(new PoolStorage());
+        PoolListParserType parser;
+        ASSERT_NO_THROW_LOG(parser.parse(pools, config_element));
+
+        // Should have two pools.
+        ASSERT_EQ(pools->size(), 2);
+
+        // First pool specifies all but ddns-ttl.
+        PoolPtr pool  = (*pools)[0];
+        ASSERT_TRUE(pool);
+
+        ASSERT_FALSE(pool->getDdnsSendUpdates().unspecified());
+        EXPECT_TRUE(pool->getDdnsSendUpdates().get());
+
+        ASSERT_FALSE(pool->getDdnsOverrideNoUpdate().unspecified());
+        EXPECT_TRUE(pool->getDdnsOverrideNoUpdate().get());
+
+        ASSERT_FALSE(pool->getDdnsOverrideClientUpdate().unspecified());
+        EXPECT_TRUE(pool->getDdnsOverrideClientUpdate().get());
+
+        ASSERT_FALSE(pool->getDdnsReplaceClientNameMode().unspecified());
+        EXPECT_EQ(pool->getDdnsReplaceClientNameMode().get(),
+                  D2ClientConfig::RCM_ALWAYS);
+
+        ASSERT_FALSE(pool->getDdnsGeneratedPrefix().unspecified());
+        EXPECT_EQ(pool->getDdnsGeneratedPrefix().get(), "prefix");
+
+        ASSERT_FALSE(pool->getDdnsQualifyingSuffix().unspecified());
+        EXPECT_EQ(pool->getDdnsQualifyingSuffix().get(), "suffix");
+
+        ASSERT_FALSE(pool->getHostnameCharSet().unspecified());
+        EXPECT_EQ(pool->getHostnameCharSet().get(), "[a-z]");
+
+        ASSERT_FALSE(pool->getHostnameCharReplacement().unspecified());
+        EXPECT_EQ(pool->getHostnameCharReplacement().get(), "X");
+
+        ASSERT_FALSE(pool->getDdnsUpdateOnRenew().unspecified());
+        EXPECT_TRUE(pool->getDdnsUpdateOnRenew().get());
+
+        ASSERT_FALSE(pool->getDdnsTtlPercent().unspecified());
+        EXPECT_EQ(pool->getDdnsTtlPercent().get(), 0.5);
+
+        ASSERT_FALSE(pool->getDdnsConflictResolutionMode().unspecified());
+        EXPECT_EQ(pool->getDdnsConflictResolutionMode().get(), "check-with-dhcid");
+
+        ASSERT_TRUE(pool->getDdnsTtl().unspecified());
+
+        ASSERT_FALSE(pool->getDdnsTtlMin().unspecified());
+        EXPECT_EQ(pool->getDdnsTtlMin().get(), 200);
+
+        ASSERT_FALSE(pool->getDdnsTtlMax().unspecified());
+        EXPECT_EQ(pool->getDdnsTtlMax().get(), 500);
+
+        // Second pool only specifies ddns-ttl.
+        pool = (*pools)[1];
+        ASSERT_TRUE(pool);
+
+        ASSERT_TRUE(pool->getDdnsSendUpdates().unspecified());
+        ASSERT_TRUE(pool->getDdnsOverrideNoUpdate().unspecified());
+        ASSERT_TRUE(pool->getDdnsOverrideClientUpdate().unspecified());
+        ASSERT_TRUE(pool->getDdnsReplaceClientNameMode().unspecified());
+        ASSERT_TRUE(pool->getDdnsGeneratedPrefix().unspecified());
+        ASSERT_TRUE(pool->getDdnsQualifyingSuffix().unspecified());
+        ASSERT_TRUE(pool->getHostnameCharSet().unspecified());
+        ASSERT_TRUE(pool->getHostnameCharReplacement().unspecified());
+        ASSERT_TRUE(pool->getDdnsUpdateOnRenew().unspecified());
+        ASSERT_TRUE(pool->getDdnsTtlPercent().unspecified());
+        ASSERT_TRUE(pool->getDdnsConflictResolutionMode().unspecified());
+        ASSERT_TRUE(pool->getDdnsTtlMin().unspecified());
+
+        ASSERT_FALSE(pool->getDdnsTtl().unspecified());
+        EXPECT_EQ(pool->getDdnsTtl().get(), 300);
+
+        ASSERT_TRUE(pool->getDdnsTtlMax().unspecified());
+    }
 };
 
 /// Verifies the code that parses mac sources and adds them to CfgMgr
@@ -4231,7 +4346,7 @@ TEST_F(DhcpParserTest, deprecatedClientClassSubnet6) {
 }
 
 // Verify that deprecated client-class is handled properly
-// by Pool4 parser. 
+// by Pool4 parser.
 TEST_F(DhcpParserTest, deprecatedClientClassPool4) {
     // Valid entry.
     std::string config =
@@ -4337,110 +4452,14 @@ TEST_F(DhcpParserTest, invalidDdnsTtlParmatersSubnet6) {
     invalidDdnsTtlParmatersSubnet<Subnet6ConfigParser>(AF_INET6);
 }
 
-// Verifies valid DDNS parameters in v4 pools.  As this uses the
-// same parser as Network derivatives we skip retesting all the
-// invalid permuations. This test ensures all supported
-// parameters can be set. 
+// Verifies valid DDNS parameters in v4 pools.
 TEST_F(DhcpParserTest, validDdnsParmatersPool4) {
+       validPoolDdnsParmaters<Pools4ListParser>("192.0.1.0/24", "192.0.2.0/24");
+}
 
-    std::string config =
-       R"^([{
-            "pool": "192.0.1.0/24",
-            "ddns-send-updates": true,
-            "ddns-override-no-update": true,
-            "ddns-override-client-update": true,
-            "ddns-replace-client-name": "always",
-            "ddns-generated-prefix": "prefix", 
-            "ddns-qualifying-suffix": "suffix", 
-            "hostname-char-set": "[a-z]",
-            "hostname-char-replacement": "X",
-            "ddns-update-on-renew": true, 
-            "ddns-ttl-percent": 0.5,
-            "ddns-conflict-resolution-mode": "check-with-dhcid",
-            "ddns-ttl-min": 200,
-            "ddns-ttl-max": 500 
-        },
-        {
-            "pool": "192.0.2.0/24",
-            "ddns-ttl": 300
-        }])^";
-
-    ElementPtr config_element = Element::fromJSON(config);
-
-    PoolStoragePtr pools(new PoolStorage());
-    Pools4ListParser parser;
-    ASSERT_NO_THROW_LOG(parser.parse(pools, config_element));
-
-    // Should have two pools.
-    ASSERT_EQ(pools->size(), 2);
-
-    // First pool specifies all but ddns-ttl.
-    PoolPtr pool  = (*pools)[0];
-    ASSERT_TRUE(pool);
-    
-    ASSERT_FALSE(pool->getDdnsSendUpdates().unspecified());
-    EXPECT_TRUE(pool->getDdnsSendUpdates().get());
-
-    ASSERT_FALSE(pool->getDdnsOverrideNoUpdate().unspecified());
-    EXPECT_TRUE(pool->getDdnsOverrideNoUpdate().get());
-
-    ASSERT_FALSE(pool->getDdnsOverrideClientUpdate().unspecified());
-    EXPECT_TRUE(pool->getDdnsOverrideClientUpdate().get());
-
-    ASSERT_FALSE(pool->getDdnsReplaceClientNameMode().unspecified());
-    EXPECT_EQ(pool->getDdnsReplaceClientNameMode().get(), 
-              D2ClientConfig::RCM_ALWAYS);
-
-    ASSERT_FALSE(pool->getDdnsGeneratedPrefix().unspecified());
-    EXPECT_EQ(pool->getDdnsGeneratedPrefix().get(), "prefix");
-
-    ASSERT_FALSE(pool->getDdnsQualifyingSuffix().unspecified());
-    EXPECT_EQ(pool->getDdnsQualifyingSuffix().get(), "suffix");
-
-    ASSERT_FALSE(pool->getHostnameCharSet().unspecified());
-    EXPECT_EQ(pool->getHostnameCharSet().get(), "[a-z]");
-
-    ASSERT_FALSE(pool->getHostnameCharReplacement().unspecified());
-    EXPECT_EQ(pool->getHostnameCharReplacement().get(), "X");
-
-    ASSERT_FALSE(pool->getDdnsUpdateOnRenew().unspecified());
-    EXPECT_TRUE(pool->getDdnsUpdateOnRenew().get());
-
-    ASSERT_FALSE(pool->getDdnsTtlPercent().unspecified());
-    EXPECT_EQ(pool->getDdnsTtlPercent().get(), 0.5);
-
-    ASSERT_FALSE(pool->getDdnsConflictResolutionMode().unspecified());
-    EXPECT_EQ(pool->getDdnsConflictResolutionMode().get(), "check-with-dhcid");
-
-    ASSERT_TRUE(pool->getDdnsTtl().unspecified());
-
-    ASSERT_FALSE(pool->getDdnsTtlMin().unspecified());
-    EXPECT_EQ(pool->getDdnsTtlMin().get(), 200);
-
-    ASSERT_FALSE(pool->getDdnsTtlMax().unspecified());
-    EXPECT_EQ(pool->getDdnsTtlMax().get(), 500);
-
-    // Second pool only specifies ddns-ttl.
-    pool = (*pools)[1];
-    ASSERT_TRUE(pool);
-    
-    ASSERT_TRUE(pool->getDdnsSendUpdates().unspecified());
-    ASSERT_TRUE(pool->getDdnsOverrideNoUpdate().unspecified());
-    ASSERT_TRUE(pool->getDdnsOverrideClientUpdate().unspecified());
-    ASSERT_TRUE(pool->getDdnsReplaceClientNameMode().unspecified());
-    ASSERT_TRUE(pool->getDdnsGeneratedPrefix().unspecified());
-    ASSERT_TRUE(pool->getDdnsQualifyingSuffix().unspecified());
-    ASSERT_TRUE(pool->getHostnameCharSet().unspecified());
-    ASSERT_TRUE(pool->getHostnameCharReplacement().unspecified());
-    ASSERT_TRUE(pool->getDdnsUpdateOnRenew().unspecified());
-    ASSERT_TRUE(pool->getDdnsTtlPercent().unspecified());
-    ASSERT_TRUE(pool->getDdnsConflictResolutionMode().unspecified());
-    ASSERT_TRUE(pool->getDdnsTtlMin().unspecified());
-
-    ASSERT_FALSE(pool->getDdnsTtl().unspecified());
-    EXPECT_EQ(pool->getDdnsTtl().get(), 300);
-
-    ASSERT_TRUE(pool->getDdnsTtlMax().unspecified());
+// Verifies valid DDNS parameters in v6 pools.
+TEST_F(DhcpParserTest, validDdnsParmatersPool6) {
+       validPoolDdnsParmaters<Pools6ListParser>("2001:db8:1::/64", "2001:db8:2::/64");
 }
 
 }  // Anonymous namespace