]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[295-min-max-lease-time-configuration-options] Added DHCPv4 server tests
authorFrancis Dupont <fdupont@isc.org>
Mon, 20 May 2019 12:30:37 +0000 (14:30 +0200)
committerFrancis Dupont <fdupont@isc.org>
Sat, 22 Jun 2019 14:05:23 +0000 (10:05 -0400)
src/bin/dhcp4/json_config_parser.cc
src/bin/dhcp4/tests/config_backend_unittest.cc
src/bin/dhcp4/tests/config_parser_unittest.cc
src/bin/dhcp4/tests/dhcp4_srv_unittest.cc
src/bin/dhcp4/tests/dhcp4_test_utils.cc
src/bin/dhcp4/tests/dhcp4_test_utils.h
src/bin/dhcp4/tests/get_config_unittest.cc
src/bin/dhcp4/tests/simple_parser4_unittest.cc
src/bin/dhcp6/json_config_parser.cc

index 885c145cc8e9e33b00f63bd93a68ee2f6a3c9958..9f4089994563d9e11d0482491889b0d64e7812af 100644 (file)
@@ -546,6 +546,8 @@ configureDhcp4Server(Dhcpv4Srv& server, isc::data::ConstElementPtr config_set,
             if ( (config_pair.first == "renew-timer") ||
                  (config_pair.first == "rebind-timer") ||
                  (config_pair.first == "valid-lifetime") ||
+                 (config_pair.first == "min-valid-lifetime") ||
+                 (config_pair.first == "max-valid-lifetime") ||
                  (config_pair.first == "decline-probation-period") ||
                  (config_pair.first == "dhcp4o6-port") ||
                  (config_pair.first == "echo-client-id") ||
index 1dc87ad71b6439dec3745093aaa53d5c0fa89fb7..0a125a89e078e4e9905fc398bbfa401d01b8fa3c 100644 (file)
@@ -315,7 +315,7 @@ TEST_F(Dhcp4CBTest, mergeOptions) {
         "        \"name\": \"dhcp-message\", \n"
         "        \"data\": \"0A0B0C0D\", \n"
         "        \"csv-format\": false \n"
-        "     },{ \n" 
+        "     },{ \n"
         "        \"name\": \"host-name\", \n"
         "        \"data\": \"old.example.com\", \n"
         "        \"csv-format\": true \n"
@@ -372,7 +372,7 @@ TEST_F(Dhcp4CBTest, mergeOptions) {
     ASSERT_TRUE(found_opt.option_);
     EXPECT_EQ("0x0A0B0C0D", found_opt.option_->toHexString());
 
-    // host-name should come from the first back end, 
+    // host-name should come from the first back end,
     // (overwriting the original).
     found_opt = options->get("dhcp4", DHO_HOST_NAME);
     ASSERT_TRUE(found_opt.option_);
index 2ee1bca77482e2cc8bfb7b97a9ec808e40269bce..319408b1dbf37e9f396c1dab6d458513fa5b89c6 100644 (file)
@@ -726,10 +726,15 @@ public:
     /// @param t1 expected renew-timer value
     /// @param t2 expected rebind-timer value
     /// @param valid expected valid-lifetime value
+    /// @param min_valid expected min-valid-lifetime value
+    ///        (0 (default) means same than valid)
+    /// @param max_valid expected max-valid-lifetime value
+    ///        (0 (default) means same than valid)
     /// @return the subnet that was examined
     Subnet4Ptr
     checkSubnet(const Subnet4Collection& col, std::string subnet,
-                uint32_t t1, uint32_t t2, uint32_t valid) {
+                uint32_t t1, uint32_t t2, uint32_t valid,
+                uint32_t min_valid = 0, uint32_t max_valid = 0) {
         const auto& index = col.get<SubnetPrefixIndexTag>();
         auto subnet_it = index.find(subnet);
         if (subnet_it == index.cend()) {
@@ -741,6 +746,8 @@ public:
         EXPECT_EQ(t1, s->getT1());
         EXPECT_EQ(t2, s->getT2());
         EXPECT_EQ(valid, s->getValid());
+        EXPECT_EQ(min_valid ? min_valid : valid, s->getValid().getMin());
+        EXPECT_EQ(max_valid ? max_valid : valid, s->getValid().getMax());
 
         return (s);
     }
@@ -835,6 +842,35 @@ TEST_F(Dhcp4ParserTest, emptySubnet) {
     checkResult(status, 0);
 }
 
+/// Check that valid-lifetime must be between min-valid-lifetime and
+/// max-valid-lifetime when a bound is specified, *AND* a subnet is
+/// specified (boundary check is done when lifetimes are applied).
+TEST_F(Dhcp4ParserTest, outBoundValidLifetime) {
+
+    string too_small =  "{ " + genIfaceConfig() + "," +
+        "\"subnet4\": [ { "
+        "    \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
+        "    \"subnet\": \"192.0.2.0/24\" } ],"
+        "\"valid-lifetime\": 1000, \"min-valid-lifetime\": 1001 }";
+
+    ConstElementPtr json;
+    ASSERT_NO_THROW(json = parseDHCP4(too_small));
+
+    ConstElementPtr status;
+    EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+    checkResult(status, 1);
+
+    string too_large =  "{ " + genIfaceConfig() + "," +
+        "\"subnet4\": [ { "
+        "    \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
+        "    \"subnet\": \"192.0.2.0/24\" } ],"
+        "\"valid-lifetime\": 4001, \"max-valid-lifetime\": 4000 }";
+
+    ASSERT_NO_THROW(json = parseDHCP4(too_large));
+    EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+    checkResult(status, 1);
+}
+
 /// 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) {
@@ -912,7 +948,9 @@ TEST_F(Dhcp4ParserTest, subnetGlobalDefaults) {
         "\"subnet4\": [ { "
         "    \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
         "    \"subnet\": \"192.0.2.0/24\" } ],"
-        "\"valid-lifetime\": 4000 }";
+        "\"valid-lifetime\": 4000,"
+        "\"min-valid-lifetime\": 3000,"
+        "\"max-valid-lifetime\": 5000 }";
 
     ConstElementPtr json;
     ASSERT_NO_THROW(json = parseDHCP4(config));
@@ -932,6 +970,8 @@ TEST_F(Dhcp4ParserTest, subnetGlobalDefaults) {
     EXPECT_EQ(1000, subnet->getT1());
     EXPECT_EQ(2000, subnet->getT2());
     EXPECT_EQ(4000, subnet->getValid());
+    EXPECT_EQ(3000, subnet->getValid().getMin());
+    EXPECT_EQ(5000, subnet->getValid().getMax());
 
     // Check that subnet-id is 1
     EXPECT_EQ(1, subnet->getID());
@@ -1645,8 +1685,12 @@ TEST_F(Dhcp4ParserTest, subnetLocal) {
         "    \"renew-timer\": 1, "
         "    \"rebind-timer\": 2, "
         "    \"valid-lifetime\": 4,"
+        "    \"min-valid-lifetime\": 3,"
+        "    \"max-valid-lifetime\": 5,"
         "    \"subnet\": \"192.0.2.0/24\" } ],"
-        "\"valid-lifetime\": 4000 }";
+        "\"valid-lifetime\": 4000,"
+        "\"min-valid-lifetime\": 3000,"
+        "\"max-valid-lifetime\": 5000 }";
 
     ConstElementPtr json;
     ASSERT_NO_THROW(json = parseDHCP4(config));
@@ -1664,6 +1708,8 @@ TEST_F(Dhcp4ParserTest, subnetLocal) {
     EXPECT_EQ(1, subnet->getT1());
     EXPECT_EQ(2, subnet->getT2());
     EXPECT_EQ(4, subnet->getValid());
+    EXPECT_EQ(3, subnet->getValid().getMin());
+    EXPECT_EQ(5, subnet->getValid().getMax());
 }
 
 // This test checks that multiple pools can be defined and handled properly.
@@ -5910,6 +5956,8 @@ TEST_F(Dhcp4ParserTest, sharedNetworks1subnet) {
 TEST_F(Dhcp4ParserTest, sharedNetworks3subnets) {
     string config = "{\n"
         "\"valid-lifetime\": 4000, \n"
+        "\"min-valid-lifetime\": 3000, \n"
+        "\"max-valid-lifetime\": 5000, \n"
         "\"rebind-timer\": 2000, \n"
         "\"renew-timer\": 1000, \n"
         "\"shared-networks\": [ {\n"
@@ -5924,7 +5972,9 @@ TEST_F(Dhcp4ParserTest, sharedNetworks3subnets) {
         "        \"pools\": [ { \"pool\": \"192.0.2.1-192.0.2.10\" } ],\n"
         "        \"renew-timer\": 2,\n"
         "        \"rebind-timer\": 22,\n"
-        "        \"valid-lifetime\": 222\n"
+        "        \"valid-lifetime\": 222,\n"
+        "        \"min-valid-lifetime\": 111,\n"
+        "        \"max-valid-lifetime\": 333\n"
         "    },\n"
         "    { \n"
         "        \"subnet\": \"192.0.3.0/24\",\n"
@@ -5954,9 +6004,9 @@ TEST_F(Dhcp4ParserTest, sharedNetworks3subnets) {
     const Subnet4Collection * subs = net->getAllSubnets();
     ASSERT_TRUE(subs);
     EXPECT_EQ(3, subs->size());
-    checkSubnet(*subs, "192.0.1.0/24", 1000, 2000, 4000);
-    checkSubnet(*subs, "192.0.2.0/24", 2, 22, 222);
-    checkSubnet(*subs, "192.0.3.0/24", 1000, 2000, 4000);
+    checkSubnet(*subs, "192.0.1.0/24", 1000, 2000, 4000, 3000, 5000);
+    checkSubnet(*subs, "192.0.2.0/24", 2, 22, 222, 111, 333);
+    checkSubnet(*subs, "192.0.3.0/24", 1000, 2000, 4000, 3000, 5000);
 
     // Now make sure the subnet was added to global list of subnets.
     CfgSubnets4Ptr subnets4 = CfgMgr::instance().getStagingCfg()->getCfgSubnets4();
@@ -5964,9 +6014,9 @@ TEST_F(Dhcp4ParserTest, sharedNetworks3subnets) {
 
     subs = subnets4->getAll();
     ASSERT_TRUE(subs);
-    checkSubnet(*subs, "192.0.1.0/24", 1000, 2000, 4000);
-    checkSubnet(*subs, "192.0.2.0/24", 2, 22, 222);
-    checkSubnet(*subs, "192.0.3.0/24", 1000, 2000, 4000);
+    checkSubnet(*subs, "192.0.1.0/24", 1000, 2000, 4000, 3000, 5000);
+    checkSubnet(*subs, "192.0.2.0/24", 2, 22, 222, 111, 333);
+    checkSubnet(*subs, "192.0.3.0/24", 1000, 2000, 4000, 3000, 5000);
 }
 
 // This test checks if parameters are derived properly:
@@ -5990,6 +6040,8 @@ TEST_F(Dhcp4ParserTest, sharedNetworksDerive) {
         "\"renew-timer\": 1, \n" // global values here
         "\"rebind-timer\": 2, \n"
         "\"valid-lifetime\": 4, \n"
+        "\"min-valid-lifetime\": 3, \n"
+        "\"max-valid-lifetime\": 5, \n"
         "\"shared-networks\": [ {\n"
         "    \"name\": \"foo\"\n," // shared network values here
         "    \"interface\": \"eth0\",\n"
@@ -6005,6 +6057,8 @@ TEST_F(Dhcp4ParserTest, sharedNetworksDerive) {
         "    \"renew-timer\": 10,\n"
         "    \"rebind-timer\": 20,\n"
         "    \"valid-lifetime\": 40,\n"
+        "    \"min-valid-lifetime\": 30,\n"
+        "    \"max-valid-lifetime\": 50,\n"
         "    \"subnet4\": [\n"
         "    { \n"
         "        \"subnet\": \"192.0.1.0/24\",\n"
@@ -6016,6 +6070,8 @@ TEST_F(Dhcp4ParserTest, sharedNetworksDerive) {
         "        \"renew-timer\": 100,\n"
         "        \"rebind-timer\": 200,\n"
         "        \"valid-lifetime\": 400,\n"
+        "        \"min-valid-lifetime\": 300,\n"
+        "        \"max-valid-lifetime\": 500,\n"
         "        \"match-client-id\": true,\n"
         "        \"next-server\": \"11.22.33.44\",\n"
         "        \"server-hostname\": \"some-name.example.org\",\n"
@@ -6063,7 +6119,7 @@ TEST_F(Dhcp4ParserTest, sharedNetworksDerive) {
     // derived from shared-network level. Other parameters a derived
     // from global scope to shared-network level and later again to
     // subnet4 level.
-    Subnet4Ptr s = checkSubnet(*subs, "192.0.1.0/24", 10, 20, 40);
+    Subnet4Ptr s = checkSubnet(*subs, "192.0.1.0/24", 10, 20, 40, 30, 50);
     ASSERT_TRUE(s);
 
     // These are values derived from shared network scope:
@@ -6080,7 +6136,7 @@ TEST_F(Dhcp4ParserTest, sharedNetworksDerive) {
     // was specified explicitly. Other parameters a derived
     // from global scope to shared-network level and later again to
     // subnet4 level.
-    s = checkSubnet(*subs, "192.0.2.0/24", 100, 200, 400);
+    s = checkSubnet(*subs, "192.0.2.0/24", 100, 200, 400, 300, 500);
 
     // These are values derived from shared network scope:
     EXPECT_EQ("eth0", s->getIface().get());
@@ -6102,7 +6158,7 @@ TEST_F(Dhcp4ParserTest, sharedNetworksDerive) {
 
     // This subnet should derive its renew-timer from global scope.
     // All other parameters should have default values.
-    s = checkSubnet(*subs, "192.0.3.0/24", 1, 2, 4);
+    s = checkSubnet(*subs, "192.0.3.0/24", 1, 2, 4, 3, 5);
     EXPECT_EQ("", s->getIface().get());
     EXPECT_TRUE(s->getMatchClientId());
     EXPECT_FALSE(s->getAuthoritative());
index e838a2607cf57bfdaa849c329ea29d9d15e846a8..699b1a86f7133e5210aab91355185dc5ca2e15ad 100644 (file)
@@ -764,6 +764,82 @@ TEST_F(Dhcpv4SrvTest, DiscoverBasic) {
     checkClientId(offer, clientid);
 }
 
+// This test verifies that OFFERs return expected valid lifetimes.
+TEST_F(Dhcpv4SrvTest, DiscoverValidLifetime) {
+    IfaceMgrTestConfig test_config(true);
+    IfaceMgr::instance().openSockets4();
+
+    boost::scoped_ptr<NakedDhcpv4Srv> srv;
+    ASSERT_NO_THROW(srv.reset(new NakedDhcpv4Srv(0)));
+
+    // Recreate subnet
+    Triplet<uint32_t> unspecified;
+    Triplet<uint32_t> valid_lft(500, 1000, 1500);
+    subnet_.reset(new Subnet4(IOAddress("192.0.2.0"), 24,
+                              unspecified,
+                              unspecified,
+                              valid_lft));
+
+    pool_ = Pool4Ptr(new Pool4(IOAddress("192.0.2.100"),
+                               IOAddress("192.0.2.110")));
+    subnet_->addPool(pool_);
+    CfgMgr::instance().clear();
+    CfgMgr::instance().getStagingCfg()->getCfgSubnets4()->add(subnet_);
+    CfgMgr::instance().commit();
+
+    // Struct for describing an individual lifetime test scenario
+    struct LifetimeTest {
+        // logged test description
+        std::string description_;
+        // lifetime hint (0 means not send dhcp-lease-time option)
+        uint32_t hint;
+        // expected returned value
+        uint32_t expected;
+    };
+
+    // Test scenarios
+    std::vector<LifetimeTest> tests = {
+        { "default valid lifetime", 0, 1000 },
+        { "specified valid lifetime", 1001, 1001 },
+        { "too small valid lifetime", 100, 500 },
+        { "too large valid lifetime", 2000, 1500 }
+    };
+
+    // Iterate over the test scenarios.
+    for (auto test : tests) {
+        SCOPED_TRACE(test.description_);
+
+        // Create a discover packet to use
+        Pkt4Ptr dis = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 1234));
+        dis->setRemoteAddr(IOAddress("192.0.2.1"));
+        OptionPtr clientid = generateClientId();
+        dis->addOption(clientid);
+        dis->setIface("eth1");
+
+        // Add dhcp-lease-time option.
+        if (test.hint) {
+            OptionUint32Ptr opt(new OptionUint32(Option::V4,
+                                                 DHO_DHCP_LEASE_TIME,
+                                                 test.hint));
+            dis->addOption(opt);
+        }
+
+        // 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);
+
+        // Check that address was returned from proper range, that its lease
+        // lifetime is correct and has the expected value.
+        checkAddressParams(offer, subnet_, false, false, test.expected);
+
+        // Check identifiers
+        checkServerId(offer, srv->getServerID());
+        checkClientId(offer, clientid);
+    }
+}
+
 // Check that option 58 and 59 are only included if they were specified
 // (and calculate-tee-times = false) and the values are sane:
 //  T2 is less than valid lft;  T1 is less than T2 (if given) or valid
@@ -1370,6 +1446,377 @@ TEST_F(Dhcpv4SrvTest, RenewBasic) {
     EXPECT_TRUE(LeaseMgrFactory::instance().deleteLease(addr));
 }
 
+// This test verifies that renewal returns the default valid lifetime
+// when the client does not specify a value.
+TEST_F(Dhcpv4SrvTest, RenewDefaultLifetime) {
+    IfaceMgrTestConfig test_config(true);
+    IfaceMgr::instance().openSockets4();
+
+    boost::scoped_ptr<NakedDhcpv4Srv> srv;
+    ASSERT_NO_THROW(srv.reset(new NakedDhcpv4Srv(0)));
+
+    const IOAddress addr("192.0.2.106");
+    const uint32_t temp_t1 = 50;
+    const uint32_t temp_t2 = 75;
+    const uint32_t temp_valid = 100;
+    const time_t temp_timestamp = time(NULL) - 10;
+
+    // Generate client-id also sets client_id_ member
+    OptionPtr clientid = generateClientId();
+
+    // Check that the address we are about to use is indeed in pool
+    ASSERT_TRUE(subnet_->inPool(Lease::TYPE_V4, addr));
+
+    // let's create a lease and put it in the LeaseMgr
+    uint8_t hwaddr2_data[] = { 0, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe};
+    HWAddrPtr hwaddr2(new HWAddr(hwaddr2_data, sizeof(hwaddr2_data), HTYPE_ETHER));
+    Lease4Ptr used(new Lease4(IOAddress("192.0.2.106"), hwaddr2,
+                              &client_id_->getDuid()[0], client_id_->getDuid().size(),
+                              temp_valid, temp_t1, temp_t2, temp_timestamp,
+                              subnet_->getID()));
+    ASSERT_TRUE(LeaseMgrFactory::instance().addLease(used));
+
+    // Check that the lease is really in the database
+    Lease4Ptr l = LeaseMgrFactory::instance().getLease4(addr);
+    ASSERT_TRUE(l);
+
+    // Check that T1, T2, preferred, valid and cltt really set.
+    // Constructed lease looks as if it was assigned 10 seconds ago
+    // EXPECT_EQ(l->t1_, temp_t1);
+    // EXPECT_EQ(l->t2_, temp_t2);
+    EXPECT_EQ(l->valid_lft_, temp_valid);
+    EXPECT_EQ(l->cltt_, temp_timestamp);
+
+    // Set the valid lifetime interval.
+    subnet_->setValid(Triplet<uint32_t>(2000, 3000, 4000));
+
+    // Let's create a RENEW
+    Pkt4Ptr req = Pkt4Ptr(new Pkt4(DHCPREQUEST, 1234));
+    req->setRemoteAddr(IOAddress(addr));
+    req->setYiaddr(addr);
+    req->setCiaddr(addr); // client's address
+    req->setIface("eth0");
+    req->setHWAddr(hwaddr2);
+
+    req->addOption(clientid);
+    req->addOption(srv->getServerID());
+
+    // There is no valid lifetime hint so the default will be returned.
+
+    // Pass it to the server and hope for a REPLY
+    Pkt4Ptr ack = srv->processRequest(req);
+
+    // Check if we get response at all
+    checkResponse(ack, DHCPACK, 1234);
+    EXPECT_EQ(addr, ack->getYiaddr());
+
+    // Check that address was returned from proper range, that its lease
+    // lifetime is correct, that T1 and T2 are returned properly
+    checkAddressParams(ack, subnet_, true, true, subnet_->getValid());
+
+    // Check identifiers
+    checkServerId(ack, srv->getServerID());
+    checkClientId(ack, clientid);
+
+    // Check that the lease is really in the database
+    l = checkLease(ack, clientid, req->getHWAddr(), addr);
+    ASSERT_TRUE(l);
+
+    // Check that T1, T2, preferred, valid and cltt were really updated
+    EXPECT_EQ(l->t1_, subnet_->getT1());
+    EXPECT_EQ(l->t2_, subnet_->getT2());
+    EXPECT_EQ(l->valid_lft_, subnet_->getValid());
+
+    // Checking for CLTT is a bit tricky if we want to avoid off by 1 errors
+    int32_t cltt = static_cast<int32_t>(l->cltt_);
+    int32_t expected = static_cast<int32_t>(time(NULL));
+    // Equality or difference by 1 between cltt and expected is ok.
+    EXPECT_GE(1, abs(cltt - expected));
+
+    EXPECT_TRUE(LeaseMgrFactory::instance().deleteLease(addr));
+}
+
+// This test verifies that renewal returns the specified valid lifetime
+// when the client adds an in-bound hint in the DISCOVER.
+TEST_F(Dhcpv4SrvTest, RenewHintLifetime) {
+    IfaceMgrTestConfig test_config(true);
+    IfaceMgr::instance().openSockets4();
+
+    boost::scoped_ptr<NakedDhcpv4Srv> srv;
+    ASSERT_NO_THROW(srv.reset(new NakedDhcpv4Srv(0)));
+
+    const IOAddress addr("192.0.2.106");
+    const uint32_t temp_t1 = 50;
+    const uint32_t temp_t2 = 75;
+    const uint32_t temp_valid = 100;
+    const time_t temp_timestamp = time(NULL) - 10;
+
+    // Generate client-id also sets client_id_ member
+    OptionPtr clientid = generateClientId();
+
+    // Check that the address we are about to use is indeed in pool
+    ASSERT_TRUE(subnet_->inPool(Lease::TYPE_V4, addr));
+
+    // let's create a lease and put it in the LeaseMgr
+    uint8_t hwaddr2_data[] = { 0, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe};
+    HWAddrPtr hwaddr2(new HWAddr(hwaddr2_data, sizeof(hwaddr2_data), HTYPE_ETHER));
+    Lease4Ptr used(new Lease4(IOAddress("192.0.2.106"), hwaddr2,
+                              &client_id_->getDuid()[0], client_id_->getDuid().size(),
+                              temp_valid, temp_t1, temp_t2, temp_timestamp,
+                              subnet_->getID()));
+    ASSERT_TRUE(LeaseMgrFactory::instance().addLease(used));
+
+    // Check that the lease is really in the database
+    Lease4Ptr l = LeaseMgrFactory::instance().getLease4(addr);
+    ASSERT_TRUE(l);
+
+    // Check that T1, T2, preferred, valid and cltt really set.
+    // Constructed lease looks as if it was assigned 10 seconds ago
+    // EXPECT_EQ(l->t1_, temp_t1);
+    // EXPECT_EQ(l->t2_, temp_t2);
+    EXPECT_EQ(l->valid_lft_, temp_valid);
+    EXPECT_EQ(l->cltt_, temp_timestamp);
+
+    // Set the valid lifetime interval.
+    subnet_->setValid(Triplet<uint32_t>(2000, 3000, 4000));
+
+    // Let's create a RENEW
+    Pkt4Ptr req = Pkt4Ptr(new Pkt4(DHCPREQUEST, 1234));
+    req->setRemoteAddr(IOAddress(addr));
+    req->setYiaddr(addr);
+    req->setCiaddr(addr); // client's address
+    req->setIface("eth0");
+    req->setHWAddr(hwaddr2);
+
+    req->addOption(clientid);
+    req->addOption(srv->getServerID());
+
+    // Add a dhcp-lease-time with an in-bound valid lifetime hint
+    // which will be returned in the OFFER.
+    uint32_t hint = 3001;
+    OptionPtr opt(new OptionUint32(Option::V4, DHO_DHCP_LEASE_TIME, hint));
+    req->addOption(opt);
+
+    // Pass it to the server and hope for a REPLY
+    Pkt4Ptr ack = srv->processRequest(req);
+
+    // Check if we get response at all
+    checkResponse(ack, DHCPACK, 1234);
+    EXPECT_EQ(addr, ack->getYiaddr());
+
+    // Check that address was returned from proper range, that its lease
+    // lifetime is correct, that T1 and T2 are returned properly
+    checkAddressParams(ack, subnet_, true, true, hint);
+
+    // Check identifiers
+    checkServerId(ack, srv->getServerID());
+    checkClientId(ack, clientid);
+
+    // Check that the lease is really in the database
+    l = checkLease(ack, clientid, req->getHWAddr(), addr);
+    ASSERT_TRUE(l);
+
+    // Check that T1, T2, preferred, valid and cltt were really updated
+    EXPECT_EQ(l->t1_, subnet_->getT1());
+    EXPECT_EQ(l->t2_, subnet_->getT2());
+    EXPECT_EQ(l->valid_lft_, hint);
+
+    // Checking for CLTT is a bit tricky if we want to avoid off by 1 errors
+    int32_t cltt = static_cast<int32_t>(l->cltt_);
+    int32_t expected = static_cast<int32_t>(time(NULL));
+    // Equality or difference by 1 between cltt and expected is ok.
+    EXPECT_GE(1, abs(cltt - expected));
+
+    EXPECT_TRUE(LeaseMgrFactory::instance().deleteLease(addr));
+}
+
+// This test verifies that renewal returns the min valid lifetime
+// when the client adds a too small hint in the DISCOVER.
+TEST_F(Dhcpv4SrvTest, RenewMinLifetime) {
+    IfaceMgrTestConfig test_config(true);
+    IfaceMgr::instance().openSockets4();
+
+    boost::scoped_ptr<NakedDhcpv4Srv> srv;
+    ASSERT_NO_THROW(srv.reset(new NakedDhcpv4Srv(0)));
+
+    const IOAddress addr("192.0.2.106");
+    const uint32_t temp_t1 = 50;
+    const uint32_t temp_t2 = 75;
+    const uint32_t temp_valid = 100;
+    const time_t temp_timestamp = time(NULL) - 10;
+
+    // Generate client-id also sets client_id_ member
+    OptionPtr clientid = generateClientId();
+
+    // Check that the address we are about to use is indeed in pool
+    ASSERT_TRUE(subnet_->inPool(Lease::TYPE_V4, addr));
+
+    // let's create a lease and put it in the LeaseMgr
+    uint8_t hwaddr2_data[] = { 0, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe};
+    HWAddrPtr hwaddr2(new HWAddr(hwaddr2_data, sizeof(hwaddr2_data), HTYPE_ETHER));
+    Lease4Ptr used(new Lease4(IOAddress("192.0.2.106"), hwaddr2,
+                              &client_id_->getDuid()[0], client_id_->getDuid().size(),
+                              temp_valid, temp_t1, temp_t2, temp_timestamp,
+                              subnet_->getID()));
+    ASSERT_TRUE(LeaseMgrFactory::instance().addLease(used));
+
+    // Check that the lease is really in the database
+    Lease4Ptr l = LeaseMgrFactory::instance().getLease4(addr);
+    ASSERT_TRUE(l);
+
+    // Check that T1, T2, preferred, valid and cltt really set.
+    // Constructed lease looks as if it was assigned 10 seconds ago
+    // EXPECT_EQ(l->t1_, temp_t1);
+    // EXPECT_EQ(l->t2_, temp_t2);
+    EXPECT_EQ(l->valid_lft_, temp_valid);
+    EXPECT_EQ(l->cltt_, temp_timestamp);
+
+    // Set the valid lifetime interval.
+    subnet_->setValid(Triplet<uint32_t>(2000, 3000, 4000));
+
+    // Let's create a RENEW
+    Pkt4Ptr req = Pkt4Ptr(new Pkt4(DHCPREQUEST, 1234));
+    req->setRemoteAddr(IOAddress(addr));
+    req->setYiaddr(addr);
+    req->setCiaddr(addr); // client's address
+    req->setIface("eth0");
+    req->setHWAddr(hwaddr2);
+
+    req->addOption(clientid);
+    req->addOption(srv->getServerID());
+
+    // Add a dhcp-lease-time with too small valid lifetime hint.
+    // The min valid lifetime will be returned in the OFFER.
+    OptionPtr opt(new OptionUint32(Option::V4, DHO_DHCP_LEASE_TIME, 1000));
+    req->addOption(opt);
+
+    // Pass it to the server and hope for a REPLY
+    Pkt4Ptr ack = srv->processRequest(req);
+
+    // Check if we get response at all
+    checkResponse(ack, DHCPACK, 1234);
+    EXPECT_EQ(addr, ack->getYiaddr());
+
+    // Check that address was returned from proper range, that its lease
+    // lifetime is correct, that T1 and T2 are returned properly
+    // Note that T2 should be false for a reason which does not matter...
+    checkAddressParams(ack, subnet_, true, false, subnet_->getValid().getMin());
+
+    // Check identifiers
+    checkServerId(ack, srv->getServerID());
+    checkClientId(ack, clientid);
+
+    // Check that the lease is really in the database
+    l = checkLease(ack, clientid, req->getHWAddr(), addr);
+    ASSERT_TRUE(l);
+
+    // Check that T1, T2, preferred, valid and cltt were really updated
+    EXPECT_EQ(l->t1_, subnet_->getT1());
+    EXPECT_EQ(l->t2_, subnet_->getT2());
+    EXPECT_EQ(l->valid_lft_, subnet_->getValid().getMin());
+
+    // Checking for CLTT is a bit tricky if we want to avoid off by 1 errors
+    int32_t cltt = static_cast<int32_t>(l->cltt_);
+    int32_t expected = static_cast<int32_t>(time(NULL));
+    // Equality or difference by 1 between cltt and expected is ok.
+    EXPECT_GE(1, abs(cltt - expected));
+
+    EXPECT_TRUE(LeaseMgrFactory::instance().deleteLease(addr));
+}
+
+// This test verifies that renewal returns the max valid lifetime
+// when the client adds a too large hint in the DISCOVER.
+TEST_F(Dhcpv4SrvTest, RenewMaxLifetime) {
+    IfaceMgrTestConfig test_config(true);
+    IfaceMgr::instance().openSockets4();
+
+    boost::scoped_ptr<NakedDhcpv4Srv> srv;
+    ASSERT_NO_THROW(srv.reset(new NakedDhcpv4Srv(0)));
+
+    const IOAddress addr("192.0.2.106");
+    const uint32_t temp_t1 = 50;
+    const uint32_t temp_t2 = 75;
+    const uint32_t temp_valid = 100;
+    const time_t temp_timestamp = time(NULL) - 10;
+
+    // Generate client-id also sets client_id_ member
+    OptionPtr clientid = generateClientId();
+
+    // Check that the address we are about to use is indeed in pool
+    ASSERT_TRUE(subnet_->inPool(Lease::TYPE_V4, addr));
+
+    // let's create a lease and put it in the LeaseMgr
+    uint8_t hwaddr2_data[] = { 0, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe};
+    HWAddrPtr hwaddr2(new HWAddr(hwaddr2_data, sizeof(hwaddr2_data), HTYPE_ETHER));
+    Lease4Ptr used(new Lease4(IOAddress("192.0.2.106"), hwaddr2,
+                              &client_id_->getDuid()[0], client_id_->getDuid().size(),
+                              temp_valid, temp_t1, temp_t2, temp_timestamp,
+                              subnet_->getID()));
+    ASSERT_TRUE(LeaseMgrFactory::instance().addLease(used));
+
+    // Check that the lease is really in the database
+    Lease4Ptr l = LeaseMgrFactory::instance().getLease4(addr);
+    ASSERT_TRUE(l);
+
+    // Check that T1, T2, preferred, valid and cltt really set.
+    // Constructed lease looks as if it was assigned 10 seconds ago
+    // EXPECT_EQ(l->t1_, temp_t1);
+    // EXPECT_EQ(l->t2_, temp_t2);
+    EXPECT_EQ(l->valid_lft_, temp_valid);
+    EXPECT_EQ(l->cltt_, temp_timestamp);
+
+    // Set the valid lifetime interval.
+    subnet_->setValid(Triplet<uint32_t>(2000, 3000, 4000));
+
+    // Let's create a RENEW
+    Pkt4Ptr req = Pkt4Ptr(new Pkt4(DHCPREQUEST, 1234));
+    req->setRemoteAddr(IOAddress(addr));
+    req->setYiaddr(addr);
+    req->setCiaddr(addr); // client's address
+    req->setIface("eth0");
+    req->setHWAddr(hwaddr2);
+
+    req->addOption(clientid);
+    req->addOption(srv->getServerID());
+
+    // Add a dhcp-lease-time with too large valid lifetime hint.
+    // The max valid lifetime will be returned in the OFFER.
+    OptionPtr opt(new OptionUint32(Option::V4, DHO_DHCP_LEASE_TIME, 5000));
+    req->addOption(opt);
+
+    // Pass it to the server and hope for a REPLY
+    Pkt4Ptr ack = srv->processRequest(req);
+
+    // Check if we get response at all
+    checkResponse(ack, DHCPACK, 1234);
+    EXPECT_EQ(addr, ack->getYiaddr());
+
+    // Check that address was returned from proper range, that its lease
+    // lifetime is correct, that T1 and T2 are returned properly
+    checkAddressParams(ack, subnet_, true, true, subnet_->getValid().getMax());
+
+    // Check identifiers
+    checkServerId(ack, srv->getServerID());
+    checkClientId(ack, clientid);
+
+    // Check that the lease is really in the database
+    l = checkLease(ack, clientid, req->getHWAddr(), addr);
+    ASSERT_TRUE(l);
+
+    // Check that T1, T2, preferred, valid and cltt were really updated
+    EXPECT_EQ(l->t1_, subnet_->getT1());
+    EXPECT_EQ(l->t2_, subnet_->getT2());
+    EXPECT_EQ(l->valid_lft_, subnet_->getValid().getMax());
+
+    // Checking for CLTT is a bit tricky if we want to avoid off by 1 errors
+    int32_t cltt = static_cast<int32_t>(l->cltt_);
+    int32_t expected = static_cast<int32_t>(time(NULL));
+    // Equality or difference by 1 between cltt and expected is ok.
+    EXPECT_GE(1, abs(cltt - expected));
+
+    EXPECT_TRUE(LeaseMgrFactory::instance().deleteLease(addr));
+}
+
 // This test verifies that the logic which matches server identifier in the
 // received message with server identifiers used by a server works correctly:
 // - a message with no server identifier is accepted,
index c223984eab8ffd40d5d3b24ee9a111f00ae67169..08f335e0cfdb6f65c70eee171a9d4f52904f63d4 100644 (file)
@@ -273,7 +273,8 @@ HWAddrPtr Dhcpv4SrvTest::generateHWAddr(size_t size /*= 6*/) {
 void Dhcpv4SrvTest::checkAddressParams(const Pkt4Ptr& rsp,
                                        const Subnet4Ptr subnet,
                                        bool t1_present,
-                                       bool t2_present) {
+                                       bool t2_present,
+                                       uint32_t expected_valid) {
 
     // Technically inPool implies inRange, but let's be on the safe
     // side and check both.
@@ -286,10 +287,18 @@ void Dhcpv4SrvTest::checkAddressParams(const Pkt4Ptr& rsp,
     if (!opt) {
         ADD_FAILURE() << "Lease time option missing in response or the"
             " option has unexpected type";
+    } else if (subnet->getValid().getMin() != subnet->getValid().getMax()) {
+        EXPECT_GE(opt->getValue(), subnet->getValid().getMin());
+        EXPECT_LE(opt->getValue(), subnet->getValid().getMax());
     } else {
         EXPECT_EQ(opt->getValue(), subnet->getValid());
     }
 
+    // Check expected value when wanted.
+    if (opt && expected_valid) {
+      EXPECT_EQ(opt->getValue(), expected_valid);
+    }
+
     // Check T1 timer
     opt = boost::dynamic_pointer_cast<
         OptionUint32>(rsp->getOption(DHO_DHCP_RENEWAL_TIME));
index 2bc5cf5d7014efc1101458da6f94ae39459306ba..433db9f7b920b9eabf4f3ddd9dc9de780f7d2772 100644 (file)
@@ -354,9 +354,12 @@ public:
     /// present (false)
     /// @param t2_present check that t2 must be present (true) or must not be
     /// present (false)
+    /// @param expected_valid check that lease lifetime has the not-zero
+    /// expected value (zero value means that do not check).
     void checkAddressParams(const Pkt4Ptr& rsp, const Subnet4Ptr subnet,
                             bool t1_present = false,
-                            bool t2_present = false);
+                            bool t2_present = false,
+                            uint32_t expected_valid = 0);
 
     /// @brief Basic checks for generated response (message type and trans-id).
     ///
index 8cc5079252db8fa42d1b572b0398afbf0deaf9a2..409583a8f1228e12ab384ed9f4bc456265ce42c6 100644 (file)
@@ -123,6 +123,8 @@ const char* EXTRACTED_CONFIGS[] = {
 "            \"interfaces\": [ \"*\" ],\n"
 "            \"re-detect\": false\n"
 "        },\n"
+"        \"max-valid-lifetime\": 5000,\n"
+"        \"min-valid-lifetime\": 3000,\n"
 "        \"rebind-timer\": 2000,\n"
 "        \"renew-timer\": 1000,\n"
 "        \"subnet4\": [\n"
@@ -470,10 +472,14 @@ const char* EXTRACTED_CONFIGS[] = {
 "            \"interfaces\": [ \"*\" ],\n"
 "            \"re-detect\": false\n"
 "        },\n"
+"        \"max-valid-lifetime\": 5000,\n"
+"        \"min-valid-lifetime\": 3000,\n"
 "        \"rebind-timer\": 2000,\n"
 "        \"renew-timer\": 1000,\n"
 "        \"subnet4\": [\n"
 "            {\n"
+"                \"max-valid-lifetime\": 5,\n"
+"                \"min-valid-lifetime\": 3,\n"
 "                \"pools\": [\n"
 "                    {\n"
 "                        \"pool\": \"192.0.2.1 - 192.0.2.100\"\n"
@@ -2330,6 +2336,8 @@ const char* UNPARSED_CONFIGS[] = {
 "            \"type\": \"memfile\"\n"
 "        },\n"
 "        \"match-client-id\": true,\n"
+"        \"max-valid-lifetime\": 5000,\n"
+"        \"min-valid-lifetime\": 3000,\n"
 "        \"next-server\": \"0.0.0.0\",\n"
 "        \"option-data\": [ ],\n"
 "        \"option-def\": [ ],\n"
@@ -2350,6 +2358,8 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"calculate-tee-times\": false,\n"
 "                \"id\": 1,\n"
 "                \"match-client-id\": true,\n"
+"                \"max-valid-lifetime\": 5000,\n"
+"                \"min-valid-lifetime\": 3000,\n"
 "                \"next-server\": \"0.0.0.0\",\n"
 "                \"option-data\": [ ],\n"
 "                \"pools\": [\n"
@@ -3810,6 +3820,8 @@ const char* UNPARSED_CONFIGS[] = {
 "            \"type\": \"memfile\"\n"
 "        },\n"
 "        \"match-client-id\": true,\n"
+"        \"max-valid-lifetime\": 5000,\n"
+"        \"min-valid-lifetime\": 3000,\n"
 "        \"next-server\": \"0.0.0.0\",\n"
 "        \"option-data\": [ ],\n"
 "        \"option-def\": [ ],\n"
@@ -3831,6 +3843,8 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"calculate-tee-times\": false,\n"
 "                \"id\": 1,\n"
 "                \"match-client-id\": true,\n"
+"                \"max-valid-lifetime\": 5,\n"
+"                \"min-valid-lifetime\": 3,\n"
 "                \"next-server\": \"0.0.0.0\",\n"
 "                \"option-data\": [ ],\n"
 "                \"pools\": [\n"
index 395b6f37a2ec4f399342d0b74e85a1f2ed539c41..6ac157a99c11542c0592231d767d557c201973bb 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2016-2018 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2016-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
@@ -115,6 +115,8 @@ TEST_F(SimpleParser4Test, inheritGlobalToSubnet4) {
     ElementPtr global = parseJSON("{ \"renew-timer\": 1,"
                                   "  \"rebind-timer\": 2,"
                                   "  \"valid-lifetime\": 4,"
+                                  "  \"min-valid-lifetime\": 3,"
+                                  "  \"max-valid-lifetime\": 5,"
                                   "  \"subnet4\": [ { \"renew-timer\": 100 } ] "
                                   "}");
     ConstElementPtr subnets = global->find("subnet4");
@@ -122,17 +124,19 @@ TEST_F(SimpleParser4Test, inheritGlobalToSubnet4) {
     ConstElementPtr subnet = subnets->get(0);
     ASSERT_TRUE(subnet);
 
-    // we should inherit 3 parameters. Renew-timer should remain intact,
+    // we should inherit 4 parameters. Renew-timer should remain intact,
     // as it was already defined in the subnet scope.
     size_t num;
     EXPECT_NO_THROW(num = SimpleParser4::deriveParameters(global));
-    EXPECT_EQ(2, num);
+    EXPECT_EQ(4, num);
 
     // Check the values. 2 of them are inherited, while the third one
     // was already defined in the subnet, so should not be inherited.
     checkIntegerValue(subnet, "renew-timer", 100);
     checkIntegerValue(subnet, "rebind-timer", 2);
     checkIntegerValue(subnet, "valid-lifetime", 4);
+    checkIntegerValue(subnet, "min-valid-lifetime", 3);
+    checkIntegerValue(subnet, "max-valid-lifetime", 5);
 }
 
 // This test checks if the parameters in "subnet4" are assigned default values
index ff6cce7e13d370048235dad0d1822c43e3e266e5..6434f45ebc240011349883084b8c48211d11348c 100644 (file)
@@ -668,7 +668,11 @@ configureDhcp6Server(Dhcpv6Srv& server, isc::data::ConstElementPtr config_set,
             if ( (config_pair.first == "renew-timer") ||
                  (config_pair.first == "rebind-timer") ||
                  (config_pair.first == "preferred-lifetime") ||
+                 (config_pair.first == "min-preferred-lifetime") ||
+                 (config_pair.first == "max-preferred-lifetime") ||
                  (config_pair.first == "valid-lifetime") ||
+                 (config_pair.first == "min-valid-lifetime") ||
+                 (config_pair.first == "max-valid-lifetime") ||
                  (config_pair.first == "decline-probation-period") ||
                  (config_pair.first == "dhcp4o6-port") ||
                  (config_pair.first == "server-tag") ||