]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#365,!296] Added kea-dhcp6 parsing support for calculate-tee-time and percents
authorThomas Markwalder <tmark@isc.org>
Fri, 5 Apr 2019 06:04:23 +0000 (02:04 -0400)
committerThomas Markwalder <tmark@isc.org>
Fri, 26 Apr 2019 14:49:30 +0000 (10:49 -0400)
src/bin/dhcp6/dhcp6_lexer.ll
src/bin/dhcp6/dhcp6_parser.yy
src/bin/dhcp6/json_config_parser.cc
src/lib/dhcpsrv/parsers/simple_parser6.cc
    added support for calculate-tee-times, t1-percent and t2-percent

src/bin/dhcp6/tests/tee_times_unittest.cc
    - new file with tests for t1/t2 determination

src/lib/dhcpsrv/parsers/dhcp_parsers.cc
    Subnet6ConfigParser::initSubnet() - added invocation
    of  parseTeePercents()

src/lib/dhcpsrv/tests/cfg_shared_networks6_unittest.cc
    updated tests

src/lib/dhcpsrv/tests/cfg_subnets6_unittest.cc
    TEST(CfgSubnets6Test, teeTimePercentValidation) - new test

src/bin/dhcp6/dhcp6_lexer.ll
src/bin/dhcp6/dhcp6_parser.yy
src/bin/dhcp6/json_config_parser.cc
src/bin/dhcp6/tests/Makefile.am
src/bin/dhcp6/tests/dhcp6_client.cc
src/bin/dhcp6/tests/dhcp6_client.h
src/bin/dhcp6/tests/tee_times_unittest.cc [new file with mode: 0644]
src/lib/dhcpsrv/parsers/dhcp_parsers.cc
src/lib/dhcpsrv/parsers/simple_parser6.cc
src/lib/dhcpsrv/tests/cfg_shared_networks6_unittest.cc
src/lib/dhcpsrv/tests/cfg_subnets6_unittest.cc

index 3a2f461158e025713c5243a84d42236b4bdd7f6e..10c54e8219c39d555bf8fb554e0ac4d37a5a1809 100644 (file)
@@ -1171,6 +1171,39 @@ ControlCharacterFill            [^"\\]|\\{JSONEscapeSequence}
     }
 }
 
+\"calculate-tee-times\" {
+    switch(driver.ctx_) {
+    case isc::dhcp::Parser6Context::DHCP6:
+    case isc::dhcp::Parser6Context::SUBNET6:
+    case isc::dhcp::Parser6Context::SHARED_NETWORK:
+        return isc::dhcp::Dhcp6Parser::make_CALCULATE_TEE_TIMES(driver.loc_);
+    default:
+        return isc::dhcp::Dhcp6Parser::make_STRING("calculate-tee-times", driver.loc_);
+    }
+}
+
+\"t1-percent\" {
+    switch(driver.ctx_) {
+    case isc::dhcp::Parser6Context::DHCP6:
+    case isc::dhcp::Parser6Context::SUBNET6:
+    case isc::dhcp::Parser6Context::SHARED_NETWORK:
+        return isc::dhcp::Dhcp6Parser::make_T1_PERCENT(driver.loc_);
+    default:
+        return isc::dhcp::Dhcp6Parser::make_STRING("t1-percent", driver.loc_);
+    }
+}
+
+\"t2-percent\" {
+    switch(driver.ctx_) {
+    case isc::dhcp::Parser6Context::DHCP6:
+    case isc::dhcp::Parser6Context::SUBNET6:
+    case isc::dhcp::Parser6Context::SHARED_NETWORK:
+        return isc::dhcp::Dhcp6Parser::make_T2_PERCENT(driver.loc_);
+    default:
+        return isc::dhcp::Dhcp6Parser::make_STRING("t2-percent", driver.loc_);
+    }
+}
+
 \"Logging\" {
     switch(driver.ctx_) {
     case isc::dhcp::Parser6Context::CONFIG:
index bd321f3d99aaf6e559b08488411cc4ca5836997e..24f9f39de5baa78f82736a7dc6b74d92d60ee506 100644 (file)
@@ -89,6 +89,9 @@ using namespace std;
   VALID_LIFETIME "valid-lifetime"
   RENEW_TIMER "renew-timer"
   REBIND_TIMER "rebind-timer"
+  CALCULATE_TEE_TIMES "calculate-tee-times"
+  T1_PERCENT "t1-percent"
+  T2_PERCENT "t2-percent"
   DECLINE_PROBATION_PERIOD "decline-probation-period"
   SERVER_TAG "server-tag"
   SUBNET6 "subnet6"
@@ -472,6 +475,9 @@ global_param: data_directory
             | config_control
             | server_tag
             | reservation_mode
+            | calculate_tee_times
+            | t1_percent 
+            | t2_percent 
             | unknown_map_entry
             ;
 
@@ -503,6 +509,21 @@ rebind_timer: REBIND_TIMER COLON INTEGER {
     ctx.stack_.back()->set("rebind-timer", prf);
 };
 
+calculate_tee_times: CALCULATE_TEE_TIMES COLON BOOLEAN {
+    ElementPtr ctt(new BoolElement($3, ctx.loc2pos(@3)));
+    ctx.stack_.back()->set("calculate-tee-times", ctt);
+};
+
+t1_percent: T1_PERCENT COLON FLOAT {
+    ElementPtr t1(new DoubleElement($3, ctx.loc2pos(@3)));
+    ctx.stack_.back()->set("t1-percent", t1);
+};
+
+t2_percent: T2_PERCENT COLON FLOAT {
+    ElementPtr t2(new DoubleElement($3, ctx.loc2pos(@3)));
+    ctx.stack_.back()->set("t2-percent", t2);
+};
+
 decline_probation_period: DECLINE_PROBATION_PERIOD COLON INTEGER {
     ElementPtr dpp(new IntElement($3, ctx.loc2pos(@3)));
     ctx.stack_.back()->set("decline-probation-period", dpp);
index c15c2ce7bc214d20bb466361a91bef5be32976db..b495e41ed79637a6a218fd3ae4fe4e93eccc063d 100644 (file)
@@ -670,7 +670,11 @@ configureDhcp6Server(Dhcpv6Srv& server, isc::data::ConstElementPtr config_set,
                  (config_pair.first == "decline-probation-period") ||
                  (config_pair.first == "dhcp4o6-port") ||
                  (config_pair.first == "server-tag") ||
-                 (config_pair.first == "reservation-mode")) {
+                 (config_pair.first == "reservation-mode") ||
+                 (config_pair.first == "calculate-tee-times") ||
+                 (config_pair.first == "t1-percent") ||
+                 (config_pair.first == "t2-percent")) {
+
                 CfgMgr::instance().getStagingCfg()->addConfiguredGlobal(config_pair.first,
                                                                         config_pair.second);
                 continue;
index 15ad57b9de82df10269b7ab1ccd2be8261776acb..7b017efb73bcbcf81b0da0bba725f92ac6e02b43 100644 (file)
@@ -107,6 +107,7 @@ dhcp6_unittests_SOURCES += renew_unittest.cc
 dhcp6_unittests_SOURCES += sarr_unittest.cc
 dhcp6_unittests_SOURCES += simple_parser6_unittest.cc
 dhcp6_unittests_SOURCES += shared_network_unittest.cc
+dhcp6_unittests_SOURCES += tee_times_unittest.cc
 dhcp6_unittests_SOURCES += vendor_opts_unittest.cc
 
 nodist_dhcp6_unittests_SOURCES  = marker_file.h test_libraries.h
index 4c429e1f7d7c5df437f95b1ca581f84e095f5c26..fa23d7acbcdc928ffa4551b49e9124bfafde0b91 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2018 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2019 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
@@ -870,6 +870,21 @@ Dhcp6Client::getStatusCode(const uint32_t iaid) const {
     return (0xFFFF);
 }
 
+bool
+Dhcp6Client::getTeeTimes(const uint32_t iaid, uint32_t& t1, uint32_t& t2) const {
+
+    auto leases = getLeasesByIAID(iaid);
+    if (leases.empty()) { 
+        // No aquired leases so punt.
+        return (false);
+    }
+
+    // All leases for a given iaid should have the same values for T1 
+    // and T2, so using them from the first one should be fine.
+    t1 = leases[0].t1_;
+    t2 = leases[0].t2_;
+    return (true);
+}
 
 void
 Dhcp6Client::setDUID(const std::string& str) {
index 13efc6a2d80734f381882696ed5bf5f1789b000d..f69f3ab1ae55e7f69fbb1d3a67f5c13e65a80cd2 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2018 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2019 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
@@ -499,14 +499,29 @@ public:
 
     /// @brief Returns status code set by the server for the IAID.
     ///
-    /// @warning This method doesn't check if the specified index is out of
-    /// range. The caller is responsible for using a correct offset by
-    /// invoking the @c getLeaseNum function.
-    ///
-    /// @param at Index of the lease held by the client.
-    /// @return A status code for the lease at the specified index.
+    /// @param iaid for which the status is desired
+    /// @return A status code for the given iaid
     uint16_t getStatusCode(const uint32_t iaid) const;
 
+    /// @brief Returns T1 and T2 timers associated with a given iaid
+    ///
+    /// Currently this method gleans the T1 an T2 times from the first
+    /// aquired lease belonging to the target iaid.  Since all leases for
+    /// an iaid should have the same values for T1 and T2, this should be 
+    /// fine.  If there are no acquired leases for the iaid, then t1 and
+    /// t2 are indeterminate.
+    ///
+    /// The primary impetus for this method is isolate the fact that T1
+    /// and T2 are stored on each lease.  This may change in the future. 
+    ///
+    /// @param iaid  iaid of the target IA
+    /// @param[out] t1 set to the value of the IA's T1
+    /// @param[out] t2 set to the value of the IA's T2
+    /// if there are no leases for the iaid
+    ///
+    /// @return true if there are aquired leases for the given iaid
+    bool getTeeTimes(const uint32_t iaid, uint32_t& t1, uint32_t& t2) const;
+
     /// @brief Returns number of acquired leases.
     size_t getLeaseNum() const {
         return (config_.leases_.size());
diff --git a/src/bin/dhcp6/tests/tee_times_unittest.cc b/src/bin/dhcp6/tests/tee_times_unittest.cc
new file mode 100644 (file)
index 0000000..1f33980
--- /dev/null
@@ -0,0 +1,183 @@
+// Copyright (C) 2019 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 <cc/data.h>
+#include <dhcp/option_string.h>
+#include <dhcp/tests/iface_mgr_test_config.h>
+#include <dhcp6/json_config_parser.h>
+#include <dhcp6/tests/dhcp6_message_test.h>
+#include <dhcpsrv/utils.h>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::data;
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+
+namespace {
+
+/// @brief Set of JSON configurations used throughout the Rebind tests.
+///
+/// - Configuration 0:
+///   - only addresses (no prefixes)
+///   - 2 subnets with 2001:db8:1::/64 and 2001:db8:2::/64
+///   - 1 subnet for eth0 and 1 subnet for eth1
+///
+const char* TEE_CONFIGS[] = {
+    // Configuration 0, Timers explicitly set
+    "{ \n" 
+    "   \"interfaces-config\": { \n"
+    "       \"interfaces\": [ \"*\" ] \n"
+    "   }, \n"
+    "   \"renew-timer\": 1000, \n"
+    "   \"rebind-timer\": 2000, \n"
+    "   \"preferred-lifetime\": 3000, \n"
+    "   \"valid-lifetime\": 4000, \n"
+    "   \"subnet6\": [ { \n"
+    "       \"interface\": \"eth0\", \n"
+    "       \"subnet\": \"2001:db8:1::/48\", \n"
+    "       \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ], \n"
+    "       \"pd-pools\": [ \n"
+    "       { \n"
+    "           \"prefix\": \"3000::\", \n "
+    "           \"prefix-len\": 72, \n"
+    "           \"delegated-len\": 80 \n"
+    "       }] \n"
+    "   }] \n"
+    "} \n"
+    , // Configuration 1, Calculate default timers
+    "{ \n" 
+    "   \"interfaces-config\": { \n"
+    "       \"interfaces\": [ \"*\" ] \n"
+    "   }, \n"
+    "   \"calculate-tee-times\": true, \n"
+    "   \"preferred-lifetime\": 3000, \n"
+    "   \"valid-lifetime\": 4000, \n"
+    "   \"subnet6\": [ { \n"
+    "       \"interface\": \"eth0\", \n"
+    "       \"subnet\": \"2001:db8:1::/48\", \n"
+    "       \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ], \n"
+    "       \"pd-pools\": [ \n"
+    "       { \n"
+    "           \"prefix\": \"3000::\", \n "
+    "           \"prefix-len\": 72, \n"
+    "           \"delegated-len\": 80 \n"
+    "       }] \n"
+    "   }] \n"
+    "} \n"
+};
+
+/// @brief Test fixture class for testing Rebind.
+class TeeTest : public Dhcpv6MessageTest {
+public:
+
+    /// @brief Constructor.
+    ///
+    /// Sets up fake interfaces.
+    TeeTest()
+        : Dhcpv6MessageTest() {
+    }
+
+    void genRequest(const std::string& config, Dhcp6Client& client, 
+                    uint32_t exp_leases) {
+        // Configure the server.
+        ASSERT_NO_THROW(configure(config, *client.getServer()));
+
+        // Do the actual 4-way exchange.
+        ASSERT_NO_THROW(client.doSARR());
+
+        // Make sure that we go the expectec number of leases.
+        ASSERT_EQ(exp_leases, client.getLeaseNum());
+
+        // Simulate aging of leases, by moving their cltt_ back by 1000s.
+        client.fastFwdTime(1000);
+    }
+};
+
+TEST_F(TeeTest, explicitTimers) {
+    Dhcp6Client client;
+
+    uint32_t na_iaid = 2222;
+    client.requestAddress(na_iaid);
+
+    uint32_t pd_iaid = 3333;
+    client.requestPrefix(pd_iaid);
+
+    uint32_t exp_leases = 2;
+
+    // Configure client to request IA_NA.
+    // Make 4-way exchange to get the lease.
+    ASSERT_NO_FATAL_FAILURE(genRequest(TEE_CONFIGS[0], client, exp_leases));
+
+    // Make sure the timers are right for both IAs
+    uint32_t actual_t1;
+    uint32_t actual_t2;
+
+    ASSERT_TRUE(client.getTeeTimes(na_iaid, actual_t1, actual_t2));
+    EXPECT_EQ(1000, actual_t1);
+    EXPECT_EQ(2000, actual_t2);
+
+    ASSERT_TRUE(client.getTeeTimes(pd_iaid, actual_t1, actual_t2));
+    EXPECT_EQ(1000, actual_t1);
+    EXPECT_EQ(2000, actual_t2);
+
+    // Let's renew the leases.
+    ASSERT_NO_THROW(client.doRenew());
+
+    // Now check the timers again.
+    ASSERT_TRUE(client.getTeeTimes(na_iaid, actual_t1, actual_t2));
+    EXPECT_EQ(1000, actual_t1);
+    EXPECT_EQ(2000, actual_t2);
+
+    ASSERT_TRUE(client.getTeeTimes(pd_iaid, actual_t1, actual_t2));
+    EXPECT_EQ(1000, actual_t1);
+    EXPECT_EQ(2000, actual_t2);
+}
+
+TEST_F(TeeTest, calculateTimers) {
+    Dhcp6Client client;
+
+    uint32_t na_iaid = 2222;
+    client.requestAddress(na_iaid);
+
+    uint32_t pd_iaid = 3333;
+    client.requestPrefix(pd_iaid);
+
+    uint32_t exp_leases = 2;
+
+    // Configure client to request IA_NA.
+    // Make 4-way exchange to get the lease.
+    ASSERT_NO_FATAL_FAILURE(genRequest(TEE_CONFIGS[1], client, exp_leases));
+
+    // Make sure the timers are right for both IAs
+    uint32_t actual_t1;
+    uint32_t actual_t2;
+
+    ASSERT_TRUE(client.getTeeTimes(na_iaid, actual_t1, actual_t2));
+    EXPECT_EQ(2000, actual_t1);
+    EXPECT_EQ(3200, actual_t2);
+
+    ASSERT_TRUE(client.getTeeTimes(pd_iaid, actual_t1, actual_t2));
+    EXPECT_EQ(2000, actual_t1);
+    EXPECT_EQ(3200, actual_t2);
+
+    // Let's renew the leases.
+    ASSERT_NO_THROW(client.doRenew());
+
+    // Now check the timers again.
+    ASSERT_TRUE(client.getTeeTimes(na_iaid, actual_t1, actual_t2));
+    EXPECT_EQ(2000, actual_t1);
+    EXPECT_EQ(3200, actual_t2);
+
+    ASSERT_TRUE(client.getTeeTimes(pd_iaid, actual_t1, actual_t2));
+    EXPECT_EQ(2000, actual_t1);
+    EXPECT_EQ(3200, actual_t2);
+}
+
+
+} // end of anonymous namespace
index 79af4c879b200aca22b0b689ec6a55d941d31215..eaf0ba982199ed8fbbe45959836c637b7f331aea 100644 (file)
@@ -904,6 +904,7 @@ Subnet4ConfigParser::initSubnet(data::ConstElementPtr params,
     // Copy options to the subnet configuration.
     options_->copyTo(*subnet4->getCfgOption());
 
+    // Parse t1-percent and t2-percent
     parseTeePercents(params, network);
 }
 
@@ -1274,6 +1275,9 @@ Subnet6ConfigParser::initSubnet(data::ConstElementPtr params,
 
     // Copy options to the subnet configuration.
     options_->copyTo(*subnet6->getCfgOption());
+
+    // Parse t1-percent and t2-percent
+    parseTeePercents(params, network);
 }
 
 //**************************** Subnet6ListConfigParser ********************
index 3db71146767a8b942bb09bc67162866054343adc..6a2cd50b29d9c86e59e0e2a97af4030dbd0954e4 100644 (file)
@@ -66,7 +66,10 @@ const SimpleKeywords SimpleParser6::GLOBAL6_PARAMETERS = {
     { "reservations",                 Element::list },
     { "config-control",               Element::map },
     { "server-tag",                   Element::string },
-    { "reservation-mode",             Element::string }
+    { "reservation-mode",             Element::string },
+    { "calculate-tee-times",          Element::boolean },
+    { "t1-percent",                   Element::real },
+    { "t2-percent",                   Element::real }
 };
 
 /// @brief This table defines default values for option definitions in DHCPv6.
@@ -97,14 +100,15 @@ const SimpleDefaults SimpleParser6::OPTION6_DEFAULTS = {
 /// in Dhcp6) are optional. If not defined, the following values will be
 /// used.
 const SimpleDefaults SimpleParser6::GLOBAL6_DEFAULTS = {
-    { "renew-timer",              Element::integer, "900" },
-    { "rebind-timer",             Element::integer, "1800" },
     { "preferred-lifetime",       Element::integer, "3600" },
     { "valid-lifetime",           Element::integer, "7200" },
     { "decline-probation-period", Element::integer, "86400" }, // 24h
     { "dhcp4o6-port",             Element::integer, "0" },
     { "server-tag",               Element::string,  "" },
-    { "reservation-mode",         Element::string,  "all" }
+    { "reservation-mode",         Element::string,  "all" },
+    { "calculate-tee-times",      Element::boolean, "false" },
+    { "t1-percent",               Element::real,    ".50" },
+    { "t2-percent",               Element::real,    ".80" }
 };
 
 /// @brief This table defines default values for each IPv6 subnet.
@@ -154,7 +158,10 @@ const ParamsList SimpleParser6::INHERIT_TO_SUBNET6 = {
     "relay",
     "renew-timer",
     "reservation-mode",
-    "valid-lifetime"
+    "valid-lifetime",
+    "calculate-tee-times",
+    "t1-percent",
+    "t2-percent"
 };
 
 /// @brief This table defines default values for dhcp-queue-control in DHCPv4.
index 44f4b72c26278a604b084660fb0ece20056883f5..0f6bd7ce530b58600e3f118eaf1bc1c1f9902321 100644 (file)
@@ -196,6 +196,9 @@ TEST(CfgSharedNetworks6Test, unparse) {
     network1->setIface("eth0");
     network1->addRelayAddress(IOAddress("2001:db8:1::1"));
     network1->addRelayAddress(IOAddress("2001:db8:1::2"));
+    network1->setCalculateTeeTimes(true);
+    network1->setT1Percent(.35);
+    network1->setT2Percent(.655);
 
     network2->setIface("eth1");
     network2->setT1(Triplet<uint32_t>(100));
@@ -219,11 +222,14 @@ TEST(CfgSharedNetworks6Test, unparse) {
         "    \"valid-lifetime\": 300\n"
         "  },\n"
         "  {\n"
+        "    \"calculate-tee-times\": true,\n"
         "    \"interface\": \"eth0\",\n"
         "    \"name\": \"frog\",\n"
         "    \"option-data\": [ ],\n"
         "    \"relay\": { \"ip-addresses\": [ \"2001:db8:1::1\", \"2001:db8:1::2\" ] },\n"
-        "    \"subnet6\": [ ]\n"
+        "    \"subnet6\": [ ],\n"
+        "    \"t1-percent\": .35,\n"
+        "    \"t2-percent\": .655\n"
         "  }\n"
         "]\n";
 
index 332f8fc1f44ca75f4496401e948afb7ece230884..2fd5119f314050c148fcdb9fd6963cdb45019862 100644 (file)
 #include <dhcp/option_string.h>
 #include <dhcpsrv/cfg_shared_networks.h>
 #include <dhcpsrv/cfg_subnets6.h>
+#include <dhcpsrv/parsers/dhcp_parsers.h>
 #include <dhcpsrv/subnet.h>
 #include <dhcpsrv/subnet_id.h>
 #include <dhcpsrv/subnet_selector.h>
 #include <testutils/test_to_element.h>
+#include <util/doubles.h>
+
 #include <gtest/gtest.h>
 #include <string>
 
@@ -537,6 +540,10 @@ TEST(CfgSubnets6Test, unparseSubnet) {
     OptionPtr ifaceid = generateInterfaceId("relay.eth0");
     subnet1->setInterfaceId(ifaceid);
     subnet1->allowClientClass("foo");
+
+    subnet1->setT1Percent(0.45);
+    subnet1->setT2Percent(0.70);
+
     subnet2->setIface("lo");
     subnet2->addRelayAddress(IOAddress("2001:db8:ff::2"));
     subnet3->setIface("eth1");
@@ -544,6 +551,9 @@ TEST(CfgSubnets6Test, unparseSubnet) {
     subnet3->requireClientClass("bar");
     subnet3->setHostReservationMode(Network::HR_ALL);
     subnet3->setRapidCommit(false);
+    subnet3->setCalculateTeeTimes(true);
+    subnet3->setT1Percent(0.50);
+    subnet3->setT2Percent(0.65);
 
     data::ElementPtr ctx1 = data::Element::fromJSON("{ \"comment\": \"foo\" }");
     subnet1->setContext(ctx1);
@@ -560,6 +570,8 @@ TEST(CfgSubnets6Test, unparseSubnet) {
         "    \"comment\": \"foo\",\n"
         "    \"id\": 123,\n"
         "    \"subnet\": \"2001:db8:1::/48\",\n"
+        "    \"t1-percent\": 0.45,"
+        "    \"t2-percent\": 0.7,"
         "    \"interface-id\": \"relay.eth0\",\n"
         "    \"renew-timer\": 1,\n"
         "    \"rebind-timer\": 2,\n"
@@ -597,7 +609,10 @@ TEST(CfgSubnets6Test, unparseSubnet) {
         "    \"pools\": [ ],\n"
         "    \"pd-pools\": [ ],\n"
         "    \"option-data\": [ ],\n"
-        "    \"require-client-classes\": [ \"foo\", \"bar\" ]\n"
+        "    \"require-client-classes\": [ \"foo\", \"bar\" ],\n"
+        "    \"calculate-tee-times\": true,\n"
+        "    \"t1-percent\": 0.50,\n"
+        "    \"t2-percent\": 0.65\n"
         "} ]\n";
     runToElementTest<CfgSubnets6>(expected, cfg);
 }
@@ -898,4 +913,107 @@ TEST(CfgSubnets6Test, mergeSubnets) {
     EXPECT_EQ("RULE!", opstr->getValue());
 }
 
+// This test verifies the Subnet6 parser's validation logic for
+// t1-percent and t2-percent parameters.
+TEST(CfgSubnets6Test, teeTimePercentValidation) {
+
+    // Describes a single test scenario.
+    struct Scenario {
+        std::string label;         // label used for logging test failures
+        bool calculate_tee_times;  // value of calculate-tee-times parameter
+        double t1_percent;         // value of t1-percent parameter
+        double t2_percent;         // value of t2-percent parameter
+        std::string error_message; // expected error message is parsing should fail
+    };
+
+    // Test Scenarios.
+    std::vector<Scenario> tests = {
+        {"off and valid", false, .5, .95, ""},
+        {"on and valid", true, .5, .95, ""},
+        {"t2_negative", true, .5, -.95,
+         "subnet configuration failed: t2-percent:"
+         "  -0.95 is invalid, it must be greater than 0.0 and less than 1.0"
+        },
+        {"t2_too_big", true, .5, 1.95,
+         "subnet configuration failed: t2-percent:"
+         "  1.95 is invalid, it must be greater than 0.0 and less than 1.0"
+        },
+        {"t1_negative", true, -.5, .95,
+         "subnet configuration failed: t1-percent:"
+         "  -0.5 is invalid it must be greater than 0.0 and less than 1.0"
+        },
+        {"t1_too_big", true, 1.5, .95,
+         "subnet configuration failed: t1-percent:"
+         "  1.5 is invalid it must be greater than 0.0 and less than 1.0"
+        },
+        {"t1_bigger_than_t2", true, .85, .45,
+         "subnet configuration failed: t1-percent:"
+         "  0.85 is invalid, it must be less than t2-percent: 0.45"
+        }
+    };
+
+    // First we create a set of elements that provides all
+    // required for a Subnet6.
+    std::string json =
+        "        {"
+        "            \"id\": 1,\n"
+        "            \"subnet\": \"2001:db8:1::/64\", \n"
+        "            \"interface\": \"\", \n"
+        "            \"renew-timer\": 100, \n"
+        "            \"rebind-timer\": 200, \n"
+        "            \"valid-lifetime\": 300, \n"
+        "            \"client-class\": \"\", \n"
+        "            \"require-client-classes\": [] \n,"
+        "            \"reservation-mode\": \"all\", \n"
+        "            \"4o6-interface\": \"\", \n"
+        "            \"4o6-interface-id\": \"\", \n"
+        "            \"4o6-subnet\": \"\", \n"
+        "            \"dhcp4o6-port\": 0, \n"
+        "            \"decline-probation-period\": 86400 \n"
+        "        }";
+
+
+    data::ElementPtr elems;
+    ASSERT_NO_THROW(elems = data::Element::fromJSON(json))
+                    << "invalid JSON:" << json << "\n test is broken";
+
+    // Iterate over the test scenarios, verifying each prescribed
+    // outcome.
+    for (auto test = tests.begin(); test != tests.end(); ++test) {
+        {
+            SCOPED_TRACE("test: " + (*test).label);
+
+            // Set this scenario's configuration parameters
+            elems->set("calculate-tee-times", data::Element::create((*test).calculate_tee_times));
+            elems->set("t1-percent", data::Element::create((*test).t1_percent));
+            elems->set("t2-percent", data::Element::create((*test).t2_percent));
+
+            Subnet6Ptr subnet;
+            try {
+                // Attempt to parse the configuration.
+                Subnet6ConfigParser parser;
+                subnet = parser.parse(elems);
+            } catch (const std::exception& ex) {
+                if (!(*test).error_message.empty()) {
+                    // We expected a failure, did we fail the correct way?
+                    EXPECT_EQ((*test).error_message, ex.what());
+                } else {
+                    // Should not have failed.
+                    ADD_FAILURE() << "Scenario should not have failed: " << ex.what();
+                }
+
+                // Either way we're done with this scenario.
+                continue;
+            }
+
+            // We parsed correctly, make sure the values are right.
+            EXPECT_EQ((*test).calculate_tee_times, subnet->getCalculateTeeTimes());
+            EXPECT_TRUE(util::areDoublesEquivalent((*test).t1_percent, subnet->getT1Percent()))
+                << "expected:" << (*test).t1_percent << " actual: " << subnet->getT1Percent();
+            EXPECT_TRUE(util::areDoublesEquivalent((*test).t2_percent, subnet->getT2Percent()))
+                << "expected:" << (*test).t2_percent << " actual: " << subnet->getT2Percent();
+        }
+    }
+}
+
 } // end of anonymous namespace