Browse Source

[master] Merge branch 'trac3336'

Marcin Siodelski 11 years ago
parent
commit
b3c8a07988

+ 7 - 2
doc/examples/kea4/single-subnet.json

@@ -21,8 +21,13 @@
 # to start REBIND procedure (emergency renewal that allows switching
 # to a different server).
   "valid-lifetime": 4000,
-  "renew-timer": 1000,
-  "rebind-timer": 2000,
+
+# Renew and rebind timers are commented out. This implies that options
+# 58 and 59 will not be sent to the client. In this case it is up to
+# the client to pick the timer values according to RFC2131. Uncomment the
+# timers to send these options to the client.
+#  "renew-timer": 1000,
+#  "rebind-timer": 2000,
 
 # The following list defines subnets. We have only one subnet
 # here.

+ 5 - 0
doc/guide/bind10-guide.xml

@@ -2191,6 +2191,11 @@ Dhcp4/dhcp-ddns/qualifying-suffix	"example.com"	string
         and apply to all defined subnets, unless they are overridden on a
         per-subnet basis.
       </para>
+      <para>
+        The renew-timer and rebind-timer are optional. If they are not specified,
+        the DHCPv4 options 58 and 59 are not sent in the server's response to the
+        client.
+      </para>
 
       <section>
       <title>Default storage for leases</title>

+ 2 - 2
src/bin/dhcp4/dhcp4.spec

@@ -32,13 +32,13 @@
 
       { "item_name": "renew-timer",
         "item_type": "integer",
-        "item_optional": false,
+        "item_optional": true,
         "item_default": 1000
       },
 
       { "item_name": "rebind-timer",
         "item_type": "integer",
-        "item_optional": false,
+        "item_optional": true,
         "item_default": 2000
       },
 

+ 17 - 4
src/bin/dhcp4/dhcp4_srv.cc

@@ -1044,15 +1044,28 @@ Dhcpv4Srv::assignLease(const Pkt4Ptr& question, Pkt4Ptr& answer) {
         }
 
         // IP Address Lease time (type 51)
-        opt = OptionPtr(new Option(Option::V4, DHO_DHCP_LEASE_TIME));
-        opt->setUint32(lease->valid_lft_);
+        opt.reset(new OptionUint32(Option::V4, DHO_DHCP_LEASE_TIME,
+                                   lease->valid_lft_));
         answer->addOption(opt);
 
         // Subnet mask (type 1)
         answer->addOption(getNetmaskOption(subnet));
 
-        /// @todo: send renew timer option (T1, option 58)
-        /// @todo: send rebind timer option (T2, option 59)
+        // renewal-timer (type 58)
+        if (!subnet->getT1().unspecified()) {
+            OptionUint32Ptr t1(new OptionUint32(Option::V4,
+                                                DHO_DHCP_RENEWAL_TIME,
+                                                subnet->getT1()));
+            answer->addOption(t1);
+        }
+
+        // rebind timer (type 59)
+        if (!subnet->getT2().unspecified()) {
+            OptionUint32Ptr t2(new OptionUint32(Option::V4,
+                                                DHO_DHCP_REBINDING_TIME,
+                                                subnet->getT2()));
+            answer->addOption(t2);
+        }
 
         // Create NameChangeRequests if DDNS is enabled and this is a
         // real allocation.

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

@@ -281,24 +281,32 @@ protected:
     /// @param addr is IPv4 address of the subnet.
     /// @param len is the prefix length
     void initSubnet(isc::asiolink::IOAddress addr, uint8_t len) {
-        // Get all 'time' parameters using inheritance.
-        // If the subnet-specific value is defined then use it, else
-        // use the global value. The global value must always be
-        // present. If it is not, it is an internal error and exception
-        // is thrown.
-        Triplet<uint32_t> t1 = getParam("renew-timer");
-        Triplet<uint32_t> t2 = getParam("rebind-timer");
+        // The renew-timer and rebind-timer are optional. If not set, the
+        // option 58 and 59 will not be sent to a client. In this case the
+        // client will use default values based on the valid-lifetime.
+        Triplet<uint32_t> t1 = getOptionalParam("renew-timer");
+        Triplet<uint32_t> t2 = getOptionalParam("rebind-timer");
+        // The valid-lifetime is mandatory. It may be specified for a
+        // particular subnet. If not, the global value should be present.
+        // If there is no global value, exception is thrown.
         Triplet<uint32_t> valid = getParam("valid-lifetime");
         // Subnet ID is optional. If it is not supplied the value of 0 is used,
         // which means autogenerate.
         SubnetID subnet_id =
             static_cast<SubnetID>(uint32_values_->getOptionalParam("id", 0));
 
-        stringstream tmp;
-        tmp << addr << "/" << (int)len
-            << " with params t1=" << t1 << ", t2=" << t2 << ", valid=" << valid;
+        stringstream s;
+        s << addr << "/" << static_cast<int>(len) << " with params: ";
+        // t1 and t2 are optional may be not specified.
+        if (!t1.unspecified()) {
+            s << "t1=" << t1 << ", ";
+        }
+        if (!t2.unspecified()) {
+            s << "t2=" << t2 << ", ";
+        }
+        s <<"valid-lifetime=" << valid;
 
-        LOG_INFO(dhcp4_logger, DHCP4_CONFIG_NEW_SUBNET).arg(tmp.str());
+        LOG_INFO(dhcp4_logger, DHCP4_CONFIG_NEW_SUBNET).arg(s.str());
 
         Subnet4Ptr subnet4(new Subnet4(addr, len, t1, t2, valid, subnet_id));
         subnet_ = subnet4;

+ 75 - 0
src/bin/dhcp4/tests/config_parser_unittest.cc

@@ -79,6 +79,8 @@ public:
         // is sane.
         srv_.reset(new Dhcpv4Srv(0));
         CfgMgr::instance().deleteActiveIfaces();
+        // Create fresh context.
+        globalContext()->copyContext(ParserContext(Option::V4));
     }
 
     // Check that no hooks libraries are loaded.  This is a pre-condition for
@@ -510,6 +512,73 @@ TEST_F(Dhcp4ParserTest, emptySubnet) {
     checkGlobalUint32("valid-lifetime", 4000);
 }
 
+/// Check that the renew-timer doesn't have to be specified, in which case
+/// it is marked unspecified in the Subnet.
+TEST_F(Dhcp4ParserTest, unspecifiedRenewTimer) {
+    ConstElementPtr status;
+
+    string config = "{ \"interfaces\": [ \"*\" ],"
+        "\"rebind-timer\": 2000, "
+        "\"subnet4\": [ { "
+        "    \"pool\": [ \"192.0.2.1 - 192.0.2.100\" ],"
+        "    \"subnet\": \"192.0.2.0/24\" } ],"
+        "\"valid-lifetime\": 4000 }";
+
+    ElementPtr json = Element::fromJSON(config);
+
+    EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+
+    // returned value should be 0 (success)
+    checkResult(status, 0);
+    checkGlobalUint32("rebind-timer", 2000);
+    checkGlobalUint32("valid-lifetime", 4000);
+
+    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.200"),
+                                                      classify_);
+    ASSERT_TRUE(subnet);
+    EXPECT_TRUE(subnet->getT1().unspecified());
+    EXPECT_FALSE(subnet->getT2().unspecified());
+    EXPECT_EQ(2000, subnet->getT2());
+    EXPECT_EQ(4000, subnet->getValid());
+
+    // Check that subnet-id is 1
+    EXPECT_EQ(1, subnet->getID());
+
+}
+
+/// Check that the rebind-timer doesn't have to be specified, in which case
+/// it is marked unspecified in the Subnet.
+TEST_F(Dhcp4ParserTest, unspecifiedRebindTimer) {
+    ConstElementPtr status;
+
+    string config = "{ \"interfaces\": [ \"*\" ],"
+        "\"renew-timer\": 1000, "
+        "\"subnet4\": [ { "
+        "    \"pool\": [ \"192.0.2.1 - 192.0.2.100\" ],"
+        "    \"subnet\": \"192.0.2.0/24\" } ],"
+        "\"valid-lifetime\": 4000 }";
+
+    ElementPtr json = Element::fromJSON(config);
+
+    EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+
+    // returned value should be 0 (success)
+    checkResult(status, 0);
+    checkGlobalUint32("renew-timer", 1000);
+    checkGlobalUint32("valid-lifetime", 4000);
+
+    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.200"),
+                                                      classify_);
+    ASSERT_TRUE(subnet);
+    EXPECT_FALSE(subnet->getT1().unspecified());
+    EXPECT_EQ(1000, subnet->getT1());
+    EXPECT_TRUE(subnet->getT2().unspecified());
+    EXPECT_EQ(4000, subnet->getValid());
+
+    // Check that subnet-id is 1
+    EXPECT_EQ(1, subnet->getID());
+}
+
 /// The goal of this test is to verify if defined subnet uses global
 /// parameter timer definitions.
 TEST_F(Dhcp4ParserTest, subnetGlobalDefaults) {
@@ -1700,6 +1769,7 @@ TEST_F(Dhcp4ParserTest, optionDataTwoSpaces) {
     // belongs to the 'dhcp4' option space as it is the
     // standard option.
     string config = "{ \"interfaces\": [ \"*\" ],"
+        "\"valid-lifetime\": 4000,"
         "\"rebind-timer\": 2000,"
         "\"renew-timer\": 1000,"
         "\"option-data\": [ {"
@@ -1779,6 +1849,7 @@ TEST_F(Dhcp4ParserTest, optionDataEncapsulate) {
 
     // Starting stage 1. Configure sub-options and their definitions.
     string config = "{ \"interfaces\": [ \"*\" ],"
+        "\"valid-lifetime\": 4000,"
         "\"rebind-timer\": 2000,"
         "\"renew-timer\": 1000,"
         "\"option-data\": [ {"
@@ -2352,6 +2423,7 @@ TEST_F(Dhcp4ParserTest, stdOptionDataEncapsulate) {
     // that we will add to the base option.
     // Let's create some dummy options: foo and foo2.
     string config = "{ \"interfaces\": [ \"*\" ],"
+        "\"valid-lifetime\": 4000,"
         "\"rebind-timer\": 2000,"
         "\"renew-timer\": 1000,"
         "\"option-data\": [ {"
@@ -2512,6 +2584,7 @@ TEST_F(Dhcp4ParserTest, vendorOptionsHex) {
     // sharing the code 1 and belonging to the different vendor spaces.
     // (different vendor-id values).
     string config = "{ \"interfaces\": [ \"*\" ],"
+        "\"valid-lifetime\": 4000,"
         "\"rebind-timer\": 2000,"
         "\"renew-timer\": 1000,"
         "\"option-data\": [ {"
@@ -2570,6 +2643,7 @@ TEST_F(Dhcp4ParserTest, vendorOptionsCsv) {
     // sharing the code 1 and belonging to the different vendor spaces.
     // (different vendor-id values).
     string config = "{ \"interfaces\": [ \"*\" ],"
+        "\"valid-lifetime\": 4000,"
         "\"rebind-timer\": 2000,"
         "\"renew-timer\": 1000,"
         "\"option-data\": [ {"
@@ -2646,6 +2720,7 @@ buildHooksLibrariesConfig(const std::vector<std::string>& libraries) {
     // Append the remainder of the configuration.
     config += string(
         "],"
+        "\"valid-lifetime\": 4000,"
         "\"rebind-timer\": 2000,"
         "\"renew-timer\": 1000,"
         "\"option-data\": [ {"

+ 89 - 12
src/bin/dhcp4/tests/dhcp4_srv_unittest.cc

@@ -558,13 +558,51 @@ TEST_F(Dhcpv4SrvTest, DiscoverBasic) {
 
     // Check that address was returned from proper range, that its lease
     // lifetime is correct, that T1 and T2 are returned properly
-    checkAddressParams(offer, subnet_);
+    checkAddressParams(offer, subnet_, true, true);
 
     // Check identifiers
     checkServerId(offer, srv->getServerID());
     checkClientId(offer, clientid);
 }
 
+// Check that option 58 and 59 are not included if they are not specified.
+TEST_F(Dhcpv4SrvTest, DiscoverNoTimers) {
+    IfaceMgrTestConfig test_config(true);
+    IfaceMgr::instance().openSockets4();
+
+    boost::scoped_ptr<NakedDhcpv4Srv> srv;
+    ASSERT_NO_THROW(srv.reset(new NakedDhcpv4Srv(0)));
+
+    Pkt4Ptr dis = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 1234));
+    dis->setRemoteAddr(IOAddress("192.0.2.1"));
+    OptionPtr clientid = generateClientId();
+    dis->addOption(clientid);
+    dis->setIface("eth1");
+
+    // Recreate a subnet but set T1 and T2 to "unspecified".
+    subnet_.reset(new Subnet4(IOAddress("192.0.2.0"), 24,
+                              Triplet<uint32_t>(),
+                              Triplet<uint32_t>(),
+                              3000));
+    pool_ = Pool4Ptr(new Pool4(IOAddress("192.0.2.100"),
+                               IOAddress("192.0.2.110")));
+    subnet_->addPool(pool_);
+    CfgMgr::instance().deleteSubnets4();
+    CfgMgr::instance().addSubnet4(subnet_);
+
+    // Pass it to the server and get an offer
+    Pkt4Ptr offer = srv->processDiscover(dis);
+
+    // Check if we get response at all
+    checkResponse(offer, DHCPOFFER, 1234);
+
+    // T1 and T2 timers must not be present.
+    checkAddressParams(offer, subnet_, false, false);
+
+    // Check identifiers
+    checkServerId(offer, srv->getServerID());
+    checkClientId(offer, clientid);
+}
 
 // This test verifies that incoming DISCOVER can be handled properly, that an
 // OFFER is generated, that the response has an address and that address
@@ -601,7 +639,7 @@ TEST_F(Dhcpv4SrvTest, DiscoverHint) {
 
     // Check that address was returned from proper range, that its lease
     // lifetime is correct, that T1 and T2 are returned properly
-    checkAddressParams(offer, subnet_);
+    checkAddressParams(offer, subnet_, true, true);
 
     EXPECT_EQ(offer->getYiaddr(), hint);
 
@@ -644,7 +682,7 @@ TEST_F(Dhcpv4SrvTest, DiscoverNoClientId) {
 
     // Check that address was returned from proper range, that its lease
     // lifetime is correct, that T1 and T2 are returned properly
-    checkAddressParams(offer, subnet_);
+    checkAddressParams(offer, subnet_, true, true);
 
     EXPECT_EQ(offer->getYiaddr(), hint);
 
@@ -687,7 +725,7 @@ TEST_F(Dhcpv4SrvTest, DiscoverInvalidHint) {
 
     // Check that address was returned from proper range, that its lease
     // lifetime is correct, that T1 and T2 are returned properly
-    checkAddressParams(offer, subnet_);
+    checkAddressParams(offer, subnet_, true, true);
 
     EXPECT_NE(offer->getYiaddr(), hint);
 
@@ -750,9 +788,9 @@ TEST_F(Dhcpv4SrvTest, ManyDiscovers) {
     IOAddress addr3 = offer3->getYiaddr();
 
     // Check that the assigned address is indeed from the configured pool
-    checkAddressParams(offer1, subnet_);
-    checkAddressParams(offer2, subnet_);
-    checkAddressParams(offer3, subnet_);
+    checkAddressParams(offer1, subnet_, true, true);
+    checkAddressParams(offer2, subnet_, true, true);
+    checkAddressParams(offer3, subnet_, true, true);
 
     // Check server-ids
     checkServerId(offer1, srv->getServerID());
@@ -840,7 +878,7 @@ TEST_F(Dhcpv4SrvTest, RequestBasic) {
 
     // Check that address was returned from proper range, that its lease
     // lifetime is correct, that T1 and T2 are returned properly
-    checkAddressParams(ack, subnet_);
+    checkAddressParams(ack, subnet_, true, true);
 
     // Check identifiers
     checkServerId(ack, srv->getServerID());
@@ -853,6 +891,45 @@ TEST_F(Dhcpv4SrvTest, RequestBasic) {
     LeaseMgrFactory::instance().deleteLease(l->addr_);
 }
 
+// Check that option 58 and 59 are not included if they are not specified.
+TEST_F(Dhcpv4SrvTest, RequestNoTimers) {
+    IfaceMgrTestConfig test_config(true);
+    IfaceMgr::instance().openSockets4();
+
+    boost::scoped_ptr<NakedDhcpv4Srv> srv;
+    ASSERT_NO_THROW(srv.reset(new NakedDhcpv4Srv(0)));
+
+    Pkt4Ptr req = Pkt4Ptr(new Pkt4(DHCPREQUEST, 1234));
+    req->setRemoteAddr(IOAddress("192.0.2.1"));
+    OptionPtr clientid = generateClientId();
+    req->addOption(clientid);
+    req->setIface("eth1");
+
+    // Recreate a subnet but set T1 and T2 to "unspecified".
+    subnet_.reset(new Subnet4(IOAddress("192.0.2.0"), 24,
+                              Triplet<uint32_t>(),
+                              Triplet<uint32_t>(),
+                              3000));
+    pool_ = Pool4Ptr(new Pool4(IOAddress("192.0.2.100"),
+                               IOAddress("192.0.2.110")));
+    subnet_->addPool(pool_);
+    CfgMgr::instance().deleteSubnets4();
+    CfgMgr::instance().addSubnet4(subnet_);
+
+    // Pass it to the server and get an ACK.
+    Pkt4Ptr ack = srv->processRequest(req);
+
+    // Check if we get response at all
+    checkResponse(ack, DHCPACK, 1234);
+
+    // T1 and T2 timers must not be present.
+    checkAddressParams(ack, subnet_, false, false);
+
+    // Check identifiers
+    checkServerId(ack, srv->getServerID());
+    checkClientId(ack, clientid);
+}
+
 // This test verifies that incoming REQUEST can be handled properly, that an
 // ACK is generated, that the response has an address and that address
 // really belongs to the configured pool.
@@ -927,9 +1004,9 @@ TEST_F(Dhcpv4SrvTest, ManyRequests) {
     EXPECT_EQ(req_addr3, addr3);
 
     // Check that the assigned address is indeed from the configured pool
-    checkAddressParams(ack1, subnet_);
-    checkAddressParams(ack2, subnet_);
-    checkAddressParams(ack3, subnet_);
+    checkAddressParams(ack1, subnet_, true, true);
+    checkAddressParams(ack2, subnet_, true, true);
+    checkAddressParams(ack3, subnet_, true, true);
 
     // Check DUIDs
     checkServerId(ack1, srv->getServerID());
@@ -1049,7 +1126,7 @@ TEST_F(Dhcpv4SrvTest, RenewBasic) {
 
     // Check that address was returned from proper range, that its lease
     // lifetime is correct, that T1 and T2 are returned properly
-    checkAddressParams(ack, subnet_);
+    checkAddressParams(ack, subnet_, true, true);
 
     // Check identifiers
     checkServerId(ack, srv->getServerID());

+ 24 - 18
src/bin/dhcp4/tests/dhcp4_test_utils.cc

@@ -20,6 +20,7 @@
 #include <dhcp4/json_config_parser.h>
 #include <dhcp4/tests/dhcp4_test_utils.h>
 #include <dhcp/option4_addrlst.h>
+#include <dhcp/option_int.h>
 #include <dhcp/option_int_array.h>
 #include <dhcp/option_custom.h>
 #include <dhcp/iface_mgr.h>
@@ -238,9 +239,10 @@ HWAddrPtr Dhcpv4SrvTest::generateHWAddr(size_t size /*= 6*/) {
     return (HWAddrPtr(new HWAddr(mac, hw_type)));
 }
 
-void Dhcpv4SrvTest::checkAddressParams(const Pkt4Ptr& rsp, const SubnetPtr subnet,
-                                       bool t1_mandatory /*= false*/,
-                                       bool t2_mandatory /*= false*/) {
+void Dhcpv4SrvTest::checkAddressParams(const Pkt4Ptr& rsp,
+                                       const SubnetPtr subnet,
+                                       bool t1_present,
+                                       bool t2_present) {
 
     // Technically inPool implies inRange, but let's be on the safe
     // side and check both.
@@ -248,31 +250,35 @@ void Dhcpv4SrvTest::checkAddressParams(const Pkt4Ptr& rsp, const SubnetPtr subne
     EXPECT_TRUE(subnet->inPool(Lease::TYPE_V4, rsp->getYiaddr()));
 
     // Check lease time
-    OptionPtr opt = rsp->getOption(DHO_DHCP_LEASE_TIME);
+    OptionUint32Ptr opt = boost::dynamic_pointer_cast<
+        OptionUint32>(rsp->getOption(DHO_DHCP_LEASE_TIME));
     if (!opt) {
-        ADD_FAILURE() << "Lease time option missing in response";
+        ADD_FAILURE() << "Lease time option missing in response or the"
+            " option has unexpected type";
     } else {
-        EXPECT_EQ(opt->getUint32(), subnet->getValid());
+        EXPECT_EQ(opt->getValue(), subnet->getValid());
     }
 
     // Check T1 timer
-    opt = rsp->getOption(DHO_DHCP_RENEWAL_TIME);
-    if (opt) {
-        EXPECT_EQ(opt->getUint32(), subnet->getT1());
+    opt = boost::dynamic_pointer_cast<
+        OptionUint32>(rsp->getOption(DHO_DHCP_RENEWAL_TIME));
+    if (t1_present) {
+        ASSERT_TRUE(opt) << "Required T1 option missing or it has"
+            " an unexpected type";
+        EXPECT_EQ(opt->getValue(), subnet->getT1());
     } else {
-        if (t1_mandatory) {
-            ADD_FAILURE() << "Required T1 option missing";
-        }
+        EXPECT_FALSE(opt);
     }
 
     // Check T2 timer
-    opt = rsp->getOption(DHO_DHCP_REBINDING_TIME);
-    if (opt) {
-        EXPECT_EQ(opt->getUint32(), subnet->getT2());
+    opt = boost::dynamic_pointer_cast<
+        OptionUint32>(rsp->getOption(DHO_DHCP_REBINDING_TIME));
+    if (t2_present) {
+        ASSERT_TRUE(opt) << "Required T2 option missing or it has"
+            " an unexpected type";
+        EXPECT_EQ(opt->getValue(), subnet->getT2());
     } else {
-        if (t2_mandatory) {
-            ADD_FAILURE() << "Required T2 option missing";
-        }
+        EXPECT_FALSE(opt);
     }
 }
 

+ 6 - 4
src/bin/dhcp4/tests/dhcp4_test_utils.h

@@ -290,11 +290,13 @@ public:
     /// @param rsp response to be checked
     /// @param subnet subnet that should be used to verify assigned address
     ///        and options
-    /// @param t1_mandatory is T1 mandatory?
-    /// @param t2_mandatory is T2 mandatory?
+    /// @param t1_present check that t1 must be present (true) or must not be
+    /// 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,
-                            bool t1_mandatory = false,
-                            bool t2_mandatory = false);
+                            bool t1_present = false,
+                            bool t2_present = false);
 
     /// @brief Basic checks for generated response (message type and trans-id).
     ///

+ 14 - 0
src/bin/dhcp6/tests/config_parser_unittest.cc

@@ -358,6 +358,8 @@ public:
         // all interfaces before each test and later check that this setting
         // has been overriden by the configuration used in the test.
         CfgMgr::instance().deleteActiveIfaces();
+        // Create fresh context.
+        globalContext()->copyContext(ParserContext(Option::V6));
     }
 
     /// @brief Test invalid option parameter value.
@@ -2002,6 +2004,8 @@ TEST_F(Dhcp6ParserTest, optionDataTwoSpaces) {
     // belongs to the 'dhcp6' option space as it is the
     // standard option.
     string config = "{ \"interfaces\": [ \"*\" ],"
+        "\"preferred-lifetime\": 3000,"
+        "\"valid-lifetime\": 4000,"
         "\"rebind-timer\": 2000,"
         "\"renew-timer\": 1000,"
         "\"option-data\": [ {"
@@ -2081,6 +2085,8 @@ TEST_F(Dhcp6ParserTest, optionDataEncapsulate) {
 
     // Starting stage 1. Configure sub-options and their definitions.
     string config = "{ \"interfaces\": [ \"*\" ],"
+        "\"preferred-lifetime\": 3000,"
+        "\"valid-lifetime\": 4000,"
         "\"rebind-timer\": 2000,"
         "\"renew-timer\": 1000,"
         "\"option-data\": [ {"
@@ -2540,6 +2546,8 @@ TEST_F(Dhcp6ParserTest, vendorOptionsHex) {
     // sharing the code 1 and belonging to the different vendor spaces.
     // (different vendor-id values).
     string config = "{ \"interfaces\": [ \"*\" ],"
+        "\"preferred-lifetime\": 3000,"
+        "\"valid-lifetime\": 4000,"
         "\"rebind-timer\": 2000,"
         "\"renew-timer\": 1000,"
         "\"option-data\": [ {"
@@ -2598,6 +2606,8 @@ TEST_F(Dhcp6ParserTest, vendorOptionsCsv) {
     // sharing the code 1 and belonging to the different vendor spaces.
     // (different vendor-id values).
     string config = "{ \"interfaces\": [ \"*\" ],"
+        "\"preferred-lifetime\": 3000,"
+        "\"valid-lifetime\": 4000,"
         "\"rebind-timer\": 2000,"
         "\"renew-timer\": 1000,"
         "\"option-data\": [ {"
@@ -2658,6 +2668,8 @@ TEST_F(Dhcp6ParserTest, stdOptionDataEncapsulate) {
     // that we will add to the base option.
     // Let's create some dummy options: foo and foo2.
     string config = "{ \"interfaces\": [ \"*\" ],"
+        "\"preferred-lifetime\": 3000,"
+        "\"valid-lifetime\": 4000,"
         "\"rebind-timer\": 2000,"
         "\"renew-timer\": 1000,"
         "\"option-data\": [ {"
@@ -3157,6 +3169,8 @@ TEST_F(Dhcp6ParserTest, d2ClientConfig) {
     ASSERT_FALSE(CfgMgr::instance().ddnsEnabled());
 
     string config_str = "{ \"interfaces\": [ \"*\" ],"
+        "\"preferred-lifetime\": 3000,"
+        "\"valid-lifetime\": 4000,"
         "\"rebind-timer\": 2000, "
         "\"renew-timer\": 1000, "
         "\"subnet6\": [ { "

+ 17 - 1
src/lib/dhcp/option_int.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2014 Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -25,6 +25,22 @@
 namespace isc {
 namespace dhcp {
 
+template<typename T>
+class OptionInt;
+
+/// @defgroup option_int_array_defs Typedefs for OptionInt class.
+///
+/// @brief Classes that represent options comprising an integer.
+///
+/// @{
+typedef OptionInt<uint8_t> OptionUint8;
+typedef boost::shared_ptr<OptionUint8> OptionUint8Ptr;
+typedef OptionInt<uint16_t> OptionUint16;
+typedef boost::shared_ptr<OptionUint16> OptionUint16Ptr;
+typedef OptionInt<uint32_t> OptionUint32;
+typedef boost::shared_ptr<OptionUint32> OptionUint32Ptr;
+/// @}
+
 /// This template class represents DHCP option with single value.
 /// This value is of integer type and can be any of the following:
 /// - uint8_t,

+ 10 - 0
src/lib/dhcpsrv/dhcp_parsers.cc

@@ -1336,6 +1336,16 @@ SubnetConfigParser::getParam(const std::string& name) {
     return (Triplet<uint32_t>(value));
 }
 
+isc::dhcp::Triplet<uint32_t>
+SubnetConfigParser::getOptionalParam(const std::string& name) {
+    try {
+        return (getParam(name));
+    } catch (const DhcpConfigError &) {
+        // No error. We will return an unspecified value.
+    }
+    return (Triplet<uint32_t>());
+}
+
 //**************************** D2ClientConfigParser **********************
 D2ClientConfigParser::D2ClientConfigParser(const std::string& entry_name)
     : entry_name_(entry_name), boolean_values_(new BooleanStorage()),

+ 12 - 0
src/lib/dhcpsrv/dhcp_parsers.h

@@ -993,6 +993,18 @@ protected:
     /// @throw DhcpConfigError when requested parameter is not present
     isc::dhcp::Triplet<uint32_t> getParam(const std::string& name);
 
+    /// @brief Returns optional value for a given parameter.
+    ///
+    /// This method checks if an optional parameter has been specified for
+    /// a subnet. If not, it will try to use a global value. If the global
+    /// value is not specified it will return an object representing an
+    /// unspecified value.
+    ///
+    /// @param name name of the configuration parameter.
+    /// @return An optional value or a @c Triplet object representing
+    /// unspecified value.
+    isc::dhcp::Triplet<uint32_t> getOptionalParam(const std::string& name);
+
 private:
 
     /// @brief Append sub-options to an option.

+ 28 - 2
src/lib/dhcpsrv/tests/triplet_unittest.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012, 2014 Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -38,6 +38,7 @@ TEST(TripletTest, constructor) {
     EXPECT_EQ(min, x.getMin());
     EXPECT_EQ(value, x.get());
     EXPECT_EQ(max, x.getMax());
+    EXPECT_FALSE(x.unspecified());
 
     // requested values below min should return allowed min value
     EXPECT_EQ(min, x.get(min - 5));
@@ -58,6 +59,7 @@ TEST(TripletTest, constructor) {
     EXPECT_EQ(42, y.getMin()); // min, default and max are equal to 42
     EXPECT_EQ(42, y.get());    // it returns ...
     EXPECT_EQ(42, y.getMax()); // the exact value...
+    EXPECT_FALSE(x.unspecified());
 
     // requested values below or above are ignore
     EXPECT_EQ(42, y.get(5));   // all...
@@ -65,6 +67,29 @@ TEST(TripletTest, constructor) {
     EXPECT_EQ(42, y.get(80));  // time!
 }
 
+TEST(TripletTest, unspecified) {
+    Triplet<uint32_t> x;
+    // When using the constructor without parameters, the triplet
+    // value is unspecified.
+    EXPECT_EQ(0, x.getMin());
+    EXPECT_EQ(0, x.get());
+    EXPECT_EQ(0, x.getMax());
+    EXPECT_TRUE(x.unspecified());
+
+    // For the triplet which has unspecified value we can call accessors
+    // without an exception.
+    uint32_t exp_unspec = 0;
+    EXPECT_EQ(exp_unspec, x);
+
+    x = 72;
+    // Check if the new value has been assigned.
+    EXPECT_EQ(72, x.getMin());
+    EXPECT_EQ(72, x.get());
+    EXPECT_EQ(72, x.getMax());
+    // Triplet is now specified.
+    EXPECT_FALSE(x.unspecified());
+}
+
 // Triplets must be easy to use.
 // Simple to/from int conversions must be done on the fly.
 TEST(TripletTest, operator) {
@@ -79,6 +104,7 @@ TEST(TripletTest, operator) {
     EXPECT_EQ(4, foo.getMin());
     EXPECT_EQ(5, foo.get());
     EXPECT_EQ(6, foo.getMax());
+    EXPECT_FALSE(foo.unspecified());
 
     // assignment operator: uint32_t => triplet
     Triplet<uint32_t> y(0);
@@ -94,7 +120,7 @@ TEST(TripletTest, operator) {
 }
 
 // check if specified values are sane
-TEST(TripletTest, sanity_check) {
+TEST(TripletTest, sanityCheck) {
 
     // min is larger than default
     EXPECT_THROW(Triplet<uint32_t>(6,5,5), BadValue);

+ 64 - 20
src/lib/dhcpsrv/triplet.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012, 2014 Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -20,12 +20,23 @@
 namespace isc {
 namespace dhcp {
 
-/// @brief this template specifies a parameter value
+/// @brief This template specifies a parameter value
 ///
-/// This template class is used to store configuration parameters, like lifetime or T1.
-/// It defines 3 parameters: min, default, and max value. There are 2 constructors:
+/// This template class is used to store configuration parameters, like lifetime
+/// or T1. It defines 3 parameters: min, default, and max value. If the
+/// particular configuration parameter is not mandatory, it is possible to
+/// mark the parameter described by a @c Triplet "unspecified". For example, the
+/// T1 and T2 values in DHCPv4 server are optional and may be not specified
+/// in the configuration. The @c Triplets describing these parameters will be
+/// marked "unspecified". If the server finds that the particular parameter
+/// is unspecified it will not include it (e.g. option 58 or 59) in the message
+/// to a client.
+///
+/// There are 3 constructors:
+/// - without parameters - marks the parameter "unspecified"
 /// - simple (just one value that sets all parameters)
 /// - extended (that sets default value and two thresholds)
+///
 /// It will be used with integer types. It provides necessary operators, so
 /// it can be assigned to a plain integer or integer assigned to a Triplet.
 /// See TripletTest.operator test for details on an easy Triplet usage.
@@ -33,18 +44,22 @@ template <class T>
 class Triplet {
 public:
 
-    /// @brief base type to Triple conversion
+    /// @brief Base type to Triplet conversion.
     ///
     /// Typically: uint32_t to Triplet assignment. It is very convenient
     /// to be able to simply write Triplet<uint32_t> x = 7;
+    ///
+    /// @param other A number to be assigned as min, max and default value.
     Triplet<T>& operator=(T other) {
         min_ = other;
         default_ = other;
         max_ = other;
-        return *this;
+        // The value is now specified because we just assigned one.
+        unspecified_ = false;
+        return (*this);
     }
 
-    /// @brief triplet to base type conversion
+    /// @brief Triplet to base type conversion
     ///
     /// Typically: Triplet to uint32_t assignment. It is very convenient
     /// to be able to simply write uint32_t z = x; (where x is a Triplet)
@@ -52,35 +67,54 @@ public:
         return (default_);
     }
 
-    /// @brief sets a fixed value
+    /// @brief Constructor without parameters.
+    ///
+    /// Marks value in @c Triplet unspecified.
+    Triplet()
+        : min_(0), default_(0), max_(0),
+          unspecified_(true) {
+    }
+
+    /// @brief Sets a fixed value.
     ///
     /// This constructor assigns a fixed (i.e. no range, just a single value)
     /// value.
+    ///
+    /// @param value A number to be assigned as min, max and default value.
     Triplet(T value)
-        :min_(value), default_(value), max_(value) {
+        : min_(value), default_(value), max_(value),
+          unspecified_(false) {
     }
 
-    /// @brief sets the default value and thresholds
+    /// @brief Sets the default value and thresholds
     ///
     /// @throw BadValue if min <= def <= max rule is violated
     Triplet(T min, T def, T max)
-        :min_(min), default_(def), max_(max) {
+        : min_(min), default_(def), max_(max),
+          unspecified_(false) {
         if ( (min_ > def) || (def > max_) ) {
             isc_throw(BadValue, "Invalid triplet values.");
         }
     }
 
-    /// @brief returns a minimum allowed value
-    T getMin() const { return min_;}
+    /// @brief Returns a minimum allowed value
+    T getMin() const { return (min_);}
 
-    /// @brief returns the default value
-    T get() const { return default_;}
+    /// @brief Returns the default value
+    T get() const { return (default_); }
 
-    /// @brief returns value with a hint
+    /// @brief Returns value with a hint
     ///
     /// DHCP protocol treats any values sent by a client as hints.
     /// This is a method that implements that. We can assign any value
     /// from configured range that client asks.
+    ///
+    /// @param hint A value being returned when if it is within the range
+    /// between min and max value of @c Triplet. If the hint value is lower
+    /// than min value, the min value is returned. if the hint is greater
+    /// than max value, the max value is returned.
+    ///
+    /// @return A value adjusted to the hint.
     T get(T hint) const {
         if (hint <= min_) {
             return (min_);
@@ -93,10 +127,17 @@ public:
         return (hint);
     }
 
-    /// @brief returns a maximum allowed value
-    T getMax() const { return max_; }
+    /// @brief Returns a maximum allowed value
+    T getMax() const { return (max_); }
+
+    /// @brief Check if the value has been specified.
+    ///
+    /// @return true if the value hasn't been specified, or false otherwise.
+    bool unspecified() const {
+        return (unspecified_);
+    }
 
-protected:
+private:
 
     /// @brief the minimum value
     T min_;
@@ -106,10 +147,13 @@ protected:
 
     /// @brief the maximum value
     T max_;
+
+    /// @brief Indicates whether the value is unspecified.
+    bool unspecified_;
 };
 
 
 } // namespace isc::dhcp
 } // namespace isc
 
-#endif // ifdef TRIPLET_H
+#endif // TRIPLET_H