Browse Source

[3677] Code for IA_NA renewal moved to AllocEngine

Tomek Mrugalski 10 years ago
parent
commit
24666be791

+ 110 - 126
src/bin/dhcp6/dhcp6_srv.cc

@@ -75,8 +75,6 @@ struct Dhcp6Hooks {
     int hook_index_buffer6_receive_;///< index for "buffer6_receive" hook point
     int hook_index_buffer6_receive_;///< index for "buffer6_receive" hook point
     int hook_index_pkt6_receive_;   ///< index for "pkt6_receive" hook point
     int hook_index_pkt6_receive_;   ///< index for "pkt6_receive" hook point
     int hook_index_subnet6_select_; ///< index for "subnet6_select" hook point
     int hook_index_subnet6_select_; ///< index for "subnet6_select" hook point
-    int hook_index_lease6_renew_;   ///< index for "lease6_renew" hook point
-    int hook_index_lease6_rebind_;  ///< index for "lease6_rebind" hook point
     int hook_index_lease6_release_; ///< index for "lease6_release" hook point
     int hook_index_lease6_release_; ///< index for "lease6_release" hook point
     int hook_index_pkt6_send_;      ///< index for "pkt6_send" hook point
     int hook_index_pkt6_send_;      ///< index for "pkt6_send" hook point
     int hook_index_buffer6_send_;   ///< index for "buffer6_send" hook point
     int hook_index_buffer6_send_;   ///< index for "buffer6_send" hook point
@@ -86,8 +84,6 @@ struct Dhcp6Hooks {
         hook_index_buffer6_receive_= HooksManager::registerHook("buffer6_receive");
         hook_index_buffer6_receive_= HooksManager::registerHook("buffer6_receive");
         hook_index_pkt6_receive_   = HooksManager::registerHook("pkt6_receive");
         hook_index_pkt6_receive_   = HooksManager::registerHook("pkt6_receive");
         hook_index_subnet6_select_ = HooksManager::registerHook("subnet6_select");
         hook_index_subnet6_select_ = HooksManager::registerHook("subnet6_select");
-        hook_index_lease6_renew_   = HooksManager::registerHook("lease6_renew");
-        hook_index_lease6_rebind_   = HooksManager::registerHook("lease6_rebind");
         hook_index_lease6_release_ = HooksManager::registerHook("lease6_release");
         hook_index_lease6_release_ = HooksManager::registerHook("lease6_release");
         hook_index_pkt6_send_      = HooksManager::registerHook("pkt6_send");
         hook_index_pkt6_send_      = HooksManager::registerHook("pkt6_send");
         hook_index_buffer6_send_   = HooksManager::registerHook("buffer6_send");
         hook_index_buffer6_send_   = HooksManager::registerHook("buffer6_send");
@@ -1513,152 +1509,138 @@ Dhcpv6Srv::extendIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
         return (ia_rsp);
         return (ia_rsp);
     }
     }
 
 
-    Lease6Ptr lease = LeaseMgrFactory::instance().getLease6(Lease::TYPE_NA,
-                                                            *duid, ia->getIAID(),
-                                                            subnet->getID());
-
-    // Client extending a lease that we don't know about.
-    if (!lease) {
-        // Insert status code NoBinding to indicate that the lease does not
-        // exist for this client.
-        ia_rsp->addOption(createStatusCode(STATUS_NoBinding,
-                          "Sorry, no known leases for this duid/iaid/subnet."));
+    // Set up T1, T2 timers
+    ia_rsp->setT1(subnet->getT1());
+    ia_rsp->setT2(subnet->getT2());
 
 
-        LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL, DHCP6_EXTEND_NA_UNKNOWN)
-            .arg(duid->toText())
-            .arg(ia->getIAID())
-            .arg(subnet->toText());
+    // At this point, we have to make make some decisions with respect to
+    // the FQDN option that we have generated as a result of receiving
+    // client's FQDN. In particular, we have to get to know if the DNS
+    // update will be performed or not. It is possible that option is NULL,
+    // which is valid condition if client didn't request DNS updates and
+    // server didn't force the update.
+    bool do_fwd = false;
+    bool do_rev = false;
+    Option6ClientFqdnPtr fqdn = boost::dynamic_pointer_cast<
+        Option6ClientFqdn>(answer->getOption(D6O_CLIENT_FQDN));
+    if (fqdn) {
+        CfgMgr::instance().getD2ClientMgr().getUpdateDirections(*fqdn, do_fwd, do_rev);
+    }
 
 
-        return (ia_rsp);
+    std::string hostname;
+    if (fqdn && (do_fwd || do_rev)) {
+        hostname = fqdn->getDomainName();
     }
     }
 
 
-    // Keep the old data in case the callout tells us to skip update.
-    Lease6 old_data = *lease;
+    // Create client context for this renewal
+    static const IOAddress none("::");
+    AllocEngine::ClientContext6 ctx(subnet, duid, ia->getIAID(),
+                                    none, Lease::TYPE_NA, do_fwd, do_rev,
+                                    hostname, false);
 
 
-    bool invalid_addr = false;
-    // Check what address the client has sent. The address should match the one
-    // that we have associated with the IAID. If it doesn't match we have two
-    // options: allocate the address for the client, if the server's
-    // configuration allows to do so, or notify the client that his address is
-    // wrong. For now we will just notify the client that the address is wrong,
-    // but both solutions require that we check the contents of the IA_NA option
-    // sent by the client. Without this check we would extend the existing lease
-    // even if the address being carried in the IA_NA is different than the
-    // one we are extending.
-    Option6IAAddrPtr iaaddr = boost::dynamic_pointer_cast<
-        Option6IAAddr>(ia->getOption(D6O_IAADDR));
-    if (iaaddr && (iaaddr->getAddress() != lease->addr_)) {
-        Option6IAAddrPtr zero_lft_addr(new Option6IAAddr(D6O_IAADDR,
-                                                         iaaddr->getAddress(),
-                                                         0, 0));
-        ia_rsp->addOption(zero_lft_addr);
-        // Mark that the client's notion of the address is invalid, so as
-        // we don't update the actual client's lease.
-        invalid_addr = true;
+    ctx.callout_handle_ = getCalloutHandle(query);
+    ctx.query_ = query;
+    ctx.ia_rsp_ = ia_rsp;
 
 
-    } else {
+    // Attempt to get MAC address using configured mechanisms.
+    // It's ok if there response is NULL. Hardware address is optional in Lease6.
+    ctx.hwaddr_ = getMAC(query);
 
 
-        // At this point, we have to make make some decisions with respect to
-        // the FQDN option that we have generated as a result of receiving
-        // client's FQDN. In particular, we have to get to know if the DNS
-        // update will be performed or not. It is possible that option is NULL,
-        // which is valid condition if client didn't request DNS updates and
-        // server didn't force the update.
-        bool do_fwd = false;
-        bool do_rev = false;
-        Option6ClientFqdnPtr fqdn = boost::dynamic_pointer_cast<
-            Option6ClientFqdn>(answer->getOption(D6O_CLIENT_FQDN));
-        if (fqdn) {
-            CfgMgr::instance().getD2ClientMgr().getUpdateDirections(*fqdn,
-                                                                    do_fwd,
-                                                                    do_rev);
+    // Extract addresses that are being renewed.
+    int hints_count = 0;
+    OptionCollection addrs = ia->getOptions();
+    for (OptionCollection::const_iterator it = addrs.begin();
+         it != addrs.end(); ++it) {
+        if (it->second->getType() != D6O_IAADDR) {
+            continue;
         }
         }
-
-        std::string hostname;
-        if (do_fwd || do_rev) {
-            hostname = fqdn->getDomainName();
+        Option6IAAddrPtr iaaddr = boost::dynamic_pointer_cast<Option6IAAddr>(it->second);
+        if (!iaaddr) {
+            // That's weird.
+            continue;
         }
         }
+        ctx.hints_.push_back(iaaddr->getAddress());
+
+        // We need to remember it as we'll be removing hints from this list as
+        // we extend, cancel or otherwise deal with the leases.
+        hints_count++;
+    }
+
+    Lease6Collection leases = alloc_engine_->renewLeases6(ctx);
+
+    // Ok, now we have the leases extended. We have:
+    // - what the client tried to renew in ctx.hints_
+    // - what we actually assigned in leases
+    // - old leases that are no longer valid in ctx.old_leases_
+
+    // For all leases we have now, add the IAADDR with non-zero lifetimes.
+    for (Lease6Collection::const_iterator l = leases.begin(); l != leases.end(); ++l) {
+        Option6IAAddrPtr iaaddr(new Option6IAAddr(D6O_IAADDR,
+                                (*l)->addr_, (*l)->preferred_lft_, (*l)->valid_lft_));
+        ia_rsp->addOption(iaaddr);
+
+        // Now remove this address from the hints list.
+        ctx.hints_.erase(std::remove(ctx.hints_.begin(), ctx.hints_.end(),
+                                     (*l)->addr_), ctx.hints_.end());
+    }
+
+    // For the leases that we just retired, send the addresses with 0 lifetimes.
+    for (Lease6Collection::const_iterator l = ctx.old_leases_.begin();
+                                          l != ctx.old_leases_.end(); ++l) {
+        Option6IAAddrPtr iaaddr(new Option6IAAddr(D6O_IAADDR,
+                                                  (*l)->addr_, 0, 0));
+        ia_rsp->addOption(iaaddr);
+
+        // Now remove this address from the hints list.
+        ctx.hints_.erase(std::remove(ctx.hints_.begin(), ctx.hints_.end(),
+                                     (*l)->addr_), ctx.hints_.end());
 
 
         // If the new FQDN settings have changed for the lease, we need to
         // If the new FQDN settings have changed for the lease, we need to
         // delete any existing FQDN records for this lease.
         // delete any existing FQDN records for this lease.
-        if ((lease->hostname_ != hostname) || (lease->fqdn_fwd_ != do_fwd) ||
-            (lease->fqdn_rev_ != do_rev)) {
+        if (((*l)->hostname_ != hostname) || ((*l)->fqdn_fwd_ != do_fwd) ||
+            ((*l)->fqdn_rev_ != do_rev)) {
             LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL,
             LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL,
                       DHCP6_DDNS_LEASE_RENEW_FQDN_CHANGE)
                       DHCP6_DDNS_LEASE_RENEW_FQDN_CHANGE)
-                .arg(lease->toText())
+                .arg((*l)->toText())
                 .arg(hostname)
                 .arg(hostname)
                 .arg(do_rev ? "true" : "false")
                 .arg(do_rev ? "true" : "false")
                 .arg(do_fwd ? "true" : "false");
                 .arg(do_fwd ? "true" : "false");
 
 
-            createRemovalNameChangeRequest(lease);
+            createRemovalNameChangeRequest(*l);
         }
         }
-
-        lease->preferred_lft_ = subnet->getPreferred();
-        lease->valid_lft_ = subnet->getValid();
-        lease->t1_ = subnet->getT1();
-        lease->t2_ = subnet->getT2();
-        lease->cltt_ = time(NULL);
-        lease->hostname_ = hostname;
-        lease->fqdn_fwd_ = do_fwd;
-        lease->fqdn_rev_ = do_rev;
-
-        /// @todo: check if hardware address has changed since last update.
-        /// And modify lease->hwaddr_ if it did.
-
-        ia_rsp->setT1(subnet->getT1());
-        ia_rsp->setT2(subnet->getT2());
-
-        Option6IAAddrPtr addr(new Option6IAAddr(D6O_IAADDR, lease->addr_,
-                                                lease->preferred_lft_,
-                                                lease->valid_lft_));
-        ia_rsp->addOption(addr);
     }
     }
 
 
-    bool skip = false;
-    // Get the callouts specific for the processed message and execute them.
-    int hook_point = query->getType() == DHCPV6_RENEW ?
-        Hooks.hook_index_lease6_renew_ : Hooks.hook_index_lease6_rebind_;
-    if (HooksManager::calloutsPresent(hook_point)) {
-        CalloutHandlePtr callout_handle = getCalloutHandle(query);
-
-        // Delete all previous arguments
-        callout_handle->deleteAllArguments();
-
-        // Pass the original packet
-        callout_handle->setArgument("query6", query);
-
-        // Pass the lease to be updated
-        callout_handle->setArgument("lease6", lease);
-
-        // Pass the IA option to be sent in response
-        callout_handle->setArgument("ia_na", ia_rsp);
-
-        // Call all installed callouts
-        HooksManager::callCallouts(hook_point, *callout_handle);
-
-        // Callouts decided to skip the next processing step. The next
-        // processing step would to actually renew the lease, so skip at this
-        // stage means "keep the old lease as it is".
-        if (callout_handle->getSkip()) {
-            skip = true;
-            LOG_DEBUG(dhcp6_logger, DBG_DHCP6_HOOKS,
-                      DHCP6_HOOK_LEASE6_EXTEND_SKIP)
-                .arg(query->getName());
-        }
+    // Finally, if there are any addresses requested that we haven't dealt with
+    // already, inform the client that he can't have them.
+    for (vector<IOAddress>::const_iterator addr = ctx.hints_.begin();
+                                          addr != ctx.hints_.end(); ++addr) {
+        Option6IAAddrPtr iaaddr(new Option6IAAddr(D6O_IAADDR,
+                                                  *addr, 0, 0));
+        ia_rsp->addOption(iaaddr);
     }
     }
 
 
-    if (!skip) {
-        // If the client has sent an invalid address, it shouldn't affect the
-        // lease in our lease database.
-        if (!invalid_addr) {
-            LeaseMgrFactory::instance().updateLease6(lease);
+    // All is left is to insert the status code.
+    if (leases.empty()) {
+        // We did not assign anything. If client has sent something, then
+        // the status code is NoBinding, if he sent an empty IA_NA, then it's
+        // NoAddrsAvailable
+        if (hints_count) {
+            // Insert status code NoBinding to indicate that the lease does not
+            // exist for this client.
+            ia_rsp->addOption(createStatusCode(STATUS_NoBinding,
+                              "Sorry, no known leases for this duid/iaid/subnet."));
+
+            LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL, DHCP6_EXTEND_NA_UNKNOWN)
+                .arg(duid->toText())
+                .arg(ia->getIAID())
+                .arg(subnet->toText());
+        } else {
+            ia_rsp->addOption(createStatusCode(STATUS_NoAddrsAvail,
+                              "Sorry, no addresses could be assigned at this time."));
         }
         }
     } else {
     } else {
-        // Copy back the original date to the lease. For MySQL it doesn't make
-        // much sense, but for memfile, the Lease6Ptr points to the actual lease
-        // in memfile, so the actual update is performed when we manipulate
-        // fields of returned Lease6Ptr, the actual updateLease6() is no-op.
-        *lease = old_data;
+        // Yay, the client still has something. For now, let's not insert
+        // status-code=success to conserve bandwidth.
     }
     }
 
 
     return (ia_rsp);
     return (ia_rsp);
@@ -1785,7 +1767,8 @@ Dhcpv6Srv::extendIA_PD(const Subnet6Ptr& subnet, const DuidPtr& duid,
 
 
     }
     }
 
 
-
+    (void)invalid_prefix;
+#if 0
     bool skip = false;
     bool skip = false;
     // Execute all callouts registered for packet6_send
     // Execute all callouts registered for packet6_send
     // Get the callouts specific for the processed message and execute them.
     // Get the callouts specific for the processed message and execute them.
@@ -1833,6 +1816,7 @@ Dhcpv6Srv::extendIA_PD(const Subnet6Ptr& subnet, const DuidPtr& duid,
         // fields of returned Lease6Ptr, the actual updateLease6() is no-op.
         // fields of returned Lease6Ptr, the actual updateLease6() is no-op.
         *lease = old_data;
         *lease = old_data;
     }
     }
+#endif
 
 
     return (ia_rsp);
     return (ia_rsp);
 }
 }

+ 4 - 2
src/bin/dhcp6/tests/dhcp6_srv_unittest.cc

@@ -81,7 +81,8 @@ TEST_F(NakedDhcpv6SrvTest, SolicitNoSubnet) {
     Pkt6Ptr reply = srv.processSolicit(sol);
     Pkt6Ptr reply = srv.processSolicit(sol);
 
 
     // check that we get the right NAK
     // check that we get the right NAK
-    checkNakResponse (reply, DHCPV6_ADVERTISE, 1234, STATUS_NoAddrsAvail);
+    checkNakResponse(reply, DHCPV6_ADVERTISE, 1234, STATUS_NoAddrsAvail,
+                     0, 0);
 }
 }
 
 
 // This test verifies that incoming REQUEST can be handled properly when
 // This test verifies that incoming REQUEST can be handled properly when
@@ -113,7 +114,8 @@ TEST_F(NakedDhcpv6SrvTest, RequestNoSubnet) {
     Pkt6Ptr reply = srv.processRequest(req);
     Pkt6Ptr reply = srv.processRequest(req);
 
 
     // check that we get the right NAK
     // check that we get the right NAK
-    checkNakResponse (reply, DHCPV6_REPLY, 1234, STATUS_NoAddrsAvail);
+    checkNakResponse (reply, DHCPV6_REPLY, 1234, STATUS_NoAddrsAvail,
+                      0, 0);
 }
 }
 
 
 // This test verifies that incoming RENEW can be handled properly, even when
 // This test verifies that incoming RENEW can be handled properly, even when

+ 5 - 5
src/bin/dhcp6/tests/dhcp6_test_utils.cc

@@ -349,7 +349,7 @@ Dhcpv6SrvTest::testRenewReject(Lease::Type type, const IOAddress& addr) {
     // Check that IA_?? was returned and that there's proper status code
     // Check that IA_?? was returned and that there's proper status code
     boost::shared_ptr<Option6IA> ia = boost::dynamic_pointer_cast<Option6IA>(tmp);
     boost::shared_ptr<Option6IA> ia = boost::dynamic_pointer_cast<Option6IA>(tmp);
     ASSERT_TRUE(ia);
     ASSERT_TRUE(ia);
-    checkIA_NAStatusCode(ia, STATUS_NoBinding);
+    checkIA_NAStatusCode(ia, STATUS_NoBinding, subnet_->getT1(), subnet_->getT2());
 
 
     // Check that there is no lease added
     // Check that there is no lease added
     l = LeaseMgrFactory::instance().getLease6(type, addr);
     l = LeaseMgrFactory::instance().getLease6(type, addr);
@@ -375,7 +375,7 @@ Dhcpv6SrvTest::testRenewReject(Lease::Type type, const IOAddress& addr) {
     // Check that IA_?? was returned and that there's proper status code
     // Check that IA_?? was returned and that there's proper status code
     ia = boost::dynamic_pointer_cast<Option6IA>(tmp);
     ia = boost::dynamic_pointer_cast<Option6IA>(tmp);
     ASSERT_TRUE(ia);
     ASSERT_TRUE(ia);
-    checkIA_NAStatusCode(ia, STATUS_NoBinding);
+    checkIA_NAStatusCode(ia, STATUS_NoBinding, subnet_->getT1(), subnet_->getT2());
 
 
     // There is a iaid mis-match, so server should respond that there is
     // There is a iaid mis-match, so server should respond that there is
     // no such address to renew.
     // no such address to renew.
@@ -395,7 +395,7 @@ Dhcpv6SrvTest::testRenewReject(Lease::Type type, const IOAddress& addr) {
     // Check that IA_?? was returned and that there's proper status code
     // Check that IA_?? was returned and that there's proper status code
     ia = boost::dynamic_pointer_cast<Option6IA>(tmp);
     ia = boost::dynamic_pointer_cast<Option6IA>(tmp);
     ASSERT_TRUE(ia);
     ASSERT_TRUE(ia);
-    checkIA_NAStatusCode(ia, STATUS_NoBinding);
+    checkIA_NAStatusCode(ia, STATUS_NoBinding, subnet_->getT1(), subnet_->getT2());
 
 
     lease = LeaseMgrFactory::instance().getLease6(type, addr);
     lease = LeaseMgrFactory::instance().getLease6(type, addr);
     ASSERT_TRUE(lease);
     ASSERT_TRUE(lease);
@@ -457,7 +457,7 @@ Dhcpv6SrvTest::testReleaseBasic(Lease::Type type, const IOAddress& existing,
 
 
     // Check that IA_NA was returned and that there's an address included
     // Check that IA_NA was returned and that there's an address included
     boost::shared_ptr<Option6IA> ia = boost::dynamic_pointer_cast<Option6IA>(tmp);
     boost::shared_ptr<Option6IA> ia = boost::dynamic_pointer_cast<Option6IA>(tmp);
-    checkIA_NAStatusCode(ia, STATUS_Success);
+    checkIA_NAStatusCode(ia, STATUS_Success, subnet_->getT1(), subnet_->getT2());
     checkMsgStatusCode(reply, STATUS_Success);
     checkMsgStatusCode(reply, STATUS_Success);
 
 
     // There should be no address returned in RELEASE (see RFC3315, 18.2.6)
     // There should be no address returned in RELEASE (see RFC3315, 18.2.6)
@@ -528,7 +528,7 @@ Dhcpv6SrvTest::testReleaseReject(Lease::Type type, const IOAddress& addr) {
     // Check that IA_NA/IA_PD was returned and that there's status code in it
     // Check that IA_NA/IA_PD was returned and that there's status code in it
     boost::shared_ptr<Option6IA> ia = boost::dynamic_pointer_cast<Option6IA>(tmp);
     boost::shared_ptr<Option6IA> ia = boost::dynamic_pointer_cast<Option6IA>(tmp);
     ASSERT_TRUE(ia);
     ASSERT_TRUE(ia);
-    checkIA_NAStatusCode(ia, STATUS_NoBinding);
+    checkIA_NAStatusCode(ia, STATUS_NoBinding, subnet_->getT1(), subnet_->getT2());
     checkMsgStatusCode(reply, STATUS_NoBinding);
     checkMsgStatusCode(reply, STATUS_NoBinding);
 
 
     // Check that the lease is not there
     // Check that the lease is not there

+ 13 - 8
src/bin/dhcp6/tests/dhcp6_test_utils.h

@@ -211,7 +211,8 @@ public:
     void checkNakResponse(const isc::dhcp::Pkt6Ptr& rsp,
     void checkNakResponse(const isc::dhcp::Pkt6Ptr& rsp,
                           uint8_t expected_message_type,
                           uint8_t expected_message_type,
                           uint32_t expected_transid,
                           uint32_t expected_transid,
-                          uint16_t expected_status_code)
+                          uint16_t expected_status_code,
+                          uint32_t expected_t1, uint32_t expected_t2)
     {
     {
         // Check if we get response at all
         // Check if we get response at all
         checkResponse(rsp, expected_message_type, expected_transid);
         checkResponse(rsp, expected_message_type, expected_transid);
@@ -225,7 +226,8 @@ public:
             boost::dynamic_pointer_cast<isc::dhcp::Option6IA>(option_ia_na);
             boost::dynamic_pointer_cast<isc::dhcp::Option6IA>(option_ia_na);
         ASSERT_TRUE(ia);
         ASSERT_TRUE(ia);
 
 
-        checkIA_NAStatusCode(ia, expected_status_code);
+        checkIA_NAStatusCode(ia, expected_status_code, expected_t1,
+                             expected_t2);
     }
     }
 
 
     // Checks that server rejected IA_NA, i.e. that it has no addresses and
     // Checks that server rejected IA_NA, i.e. that it has no addresses and
@@ -238,14 +240,17 @@ public:
     // as this is the default result and it saves bandwidth)
     // as this is the default result and it saves bandwidth)
     void checkIA_NAStatusCode
     void checkIA_NAStatusCode
         (const boost::shared_ptr<isc::dhcp::Option6IA>& ia,
         (const boost::shared_ptr<isc::dhcp::Option6IA>& ia,
-         uint16_t expected_status_code)
+         uint16_t expected_status_code, uint32_t expected_t1,
+         uint32_t expected_t2)
     {
     {
         // Make sure there is no address assigned.
         // Make sure there is no address assigned.
         EXPECT_FALSE(ia->getOption(D6O_IAADDR));
         EXPECT_FALSE(ia->getOption(D6O_IAADDR));
 
 
-        // T1, T2 should be zeroed
-        EXPECT_EQ(0, ia->getT1());
-        EXPECT_EQ(0, ia->getT2());
+        // T1, T2 should NOT be zeroed. draft-ietf-dhc-dhcpv6-stateful-issues-10,
+        // section 4.4.6 says says that T1,T2 should be consistent along all
+        // provided IA options.
+        EXPECT_EQ(expected_t1, ia->getT1());
+        EXPECT_EQ(expected_t2, ia->getT2());
 
 
         isc::dhcp::OptionCustomPtr status =
         isc::dhcp::OptionCustomPtr status =
             boost::dynamic_pointer_cast<isc::dhcp::OptionCustom>
             boost::dynamic_pointer_cast<isc::dhcp::OptionCustom>
@@ -397,8 +402,8 @@ public:
         // an ostream, which means it can't be used in EXPECT_EQ.
         // an ostream, which means it can't be used in EXPECT_EQ.
         EXPECT_TRUE(subnet_->inPool(type, addr->getAddress()));
         EXPECT_TRUE(subnet_->inPool(type, addr->getAddress()));
         EXPECT_EQ(expected_addr.toText(), addr->getAddress().toText());
         EXPECT_EQ(expected_addr.toText(), addr->getAddress().toText());
-        EXPECT_EQ(addr->getPreferred(), subnet_->getPreferred());
-        EXPECT_EQ(addr->getValid(), subnet_->getValid());
+        EXPECT_EQ(subnet_->getPreferred(), addr->getPreferred());
+        EXPECT_EQ(subnet_->getValid(), addr->getValid());
     }
     }
 
 
     // Checks if the lease sent to client is present in the database
     // Checks if the lease sent to client is present in the database

+ 137 - 0
src/lib/dhcpsrv/alloc_engine.cc

@@ -17,6 +17,7 @@
 #include <dhcpsrv/host_mgr.h>
 #include <dhcpsrv/host_mgr.h>
 #include <dhcpsrv/host.h>
 #include <dhcpsrv/host.h>
 #include <dhcpsrv/lease_mgr_factory.h>
 #include <dhcpsrv/lease_mgr_factory.h>
+#include <dhcp/dhcp6.h>
 
 
 #include <hooks/server_hooks.h>
 #include <hooks/server_hooks.h>
 #include <hooks/hooks_manager.h>
 #include <hooks/hooks_manager.h>
@@ -37,12 +38,16 @@ struct AllocEngineHooks {
     int hook_index_lease4_select_; ///< index for "lease4_receive" hook point
     int hook_index_lease4_select_; ///< index for "lease4_receive" hook point
     int hook_index_lease4_renew_;  ///< index for "lease4_renew" hook point
     int hook_index_lease4_renew_;  ///< index for "lease4_renew" hook point
     int hook_index_lease6_select_; ///< index for "lease6_receive" hook point
     int hook_index_lease6_select_; ///< index for "lease6_receive" hook point
+    int hook_index_lease6_renew_;  ///< index for "lease6_renew" hook point
+    int hook_index_lease6_rebind_; ///< index for "lease6_rebind" hook point
 
 
     /// Constructor that registers hook points for AllocationEngine
     /// Constructor that registers hook points for AllocationEngine
     AllocEngineHooks() {
     AllocEngineHooks() {
         hook_index_lease4_select_ = HooksManager::registerHook("lease4_select");
         hook_index_lease4_select_ = HooksManager::registerHook("lease4_select");
         hook_index_lease4_renew_  = HooksManager::registerHook("lease4_renew");
         hook_index_lease4_renew_  = HooksManager::registerHook("lease4_renew");
         hook_index_lease6_select_ = HooksManager::registerHook("lease6_select");
         hook_index_lease6_select_ = HooksManager::registerHook("lease6_select");
+        hook_index_lease6_renew_   = HooksManager::registerHook("lease6_renew");
+        hook_index_lease6_rebind_   = HooksManager::registerHook("lease6_rebind");
     }
     }
 };
 };
 
 
@@ -1618,6 +1623,138 @@ AllocEngine::AllocatorPtr AllocEngine::getAllocator(Lease::Type type) {
     return (alloc->second);
     return (alloc->second);
 }
 }
 
 
+Lease6Collection
+AllocEngine::renewLeases6(ClientContext6& ctx) {
+    try {
+        if (!ctx.subnet_) {
+            isc_throw(InvalidOperation, "Subnet is required for allocation");
+        }
+
+        if (!ctx.duid_) {
+            isc_throw(InvalidOperation, "DUID is mandatory for allocation");
+        }
+
+        // Check which host reservation mode is supported in this subnet.
+        Subnet::HRMode hr_mode = ctx.subnet_->getHostReservationMode();
+
+        // Check if there's a host reservation for this client. Attempt to get
+        // host info only if reservations are not disabled.
+        if (hr_mode != Subnet::HR_DISABLED) {
+
+            ctx.host_ = HostMgr::instance().get6(ctx.subnet_->getID(), ctx.duid_,
+                                                 ctx.hwaddr_);
+        }
+
+        // Check if there are any leases for this client.
+        Lease6Collection leases = LeaseMgrFactory::instance()
+            .getLeases6(ctx.type_, *ctx.duid_, ctx.iaid_, ctx.subnet_->getID());
+
+        // Now we have 3 lists:
+        // 1. what client requested (ctx.hints_)
+        // 2. what leases we currently have (leases)
+        // 3. reservation list: ctx.hosts_->getIPv6Reservations(TYPE_NA)
+
+        // The behavior here is similar to how we process requests, but
+        // there are noticable differences.
+
+        // Now do the checks:
+        // Case 1. if there are no leases, and there are reservations...
+        //   1.1. are the reserved addresses are used by someone else?
+        //       yes: we have a problem
+        //       no: assign them => done
+        // Case 2. if there are leases and there are no reservations...
+        //   2.1 are the leases reserved for someone else?
+        //       yes: release them, assign something else
+        //       no: renew them => done
+        // Case 3. if there are leases and there are reservations...
+        //   3.1 are the leases matching reservations?
+        //       yes: renew them => done
+        //       no: release existing leases, assign new ones based on reservations
+        // Case 4/catch-all. if there are no leases and no reservations...
+        //       assign new leases
+        //
+
+        // Extend all existing leases.
+        for (Lease6Collection::iterator l = leases.begin(); l != leases.end(); ++l) {
+            extendLease6(ctx, *l);
+        }
+
+        return (leases);
+
+    } catch (const isc::Exception& e) {
+
+        // Some other error, return an empty lease.
+        LOG_ERROR(dhcpsrv_logger, DHCPSRV_ADDRESS6_ALLOC_ERROR).arg(e.what());
+    }
+
+    return (Lease6Collection());
+}
+
+void
+AllocEngine::extendLease6(ClientContext6& ctx, Lease6Ptr lease) {
+
+    if (!lease || !ctx.subnet_) {
+        return;
+    }
+
+    // Keep the old data in case the callout tells us to skip update.
+    Lease6 old_data = *lease;
+
+    lease->preferred_lft_ = ctx.subnet_->getPreferred();
+    lease->valid_lft_ = ctx.subnet_->getValid();
+    lease->t1_ = ctx.subnet_->getT1();
+    lease->t2_ = ctx.subnet_->getT2();
+    lease->cltt_ = time(NULL);
+    lease->hostname_ = ctx.hostname_;
+    lease->fqdn_fwd_ = ctx.fwd_dns_update_;
+    lease->fqdn_rev_ = ctx.rev_dns_update_;
+    lease->hwaddr_ = ctx.hwaddr_;
+
+    bool skip = false;
+    // Get the callouts specific for the processed message and execute them.
+    int hook_point = ctx.query_->getType() == DHCPV6_RENEW ?
+        Hooks.hook_index_lease6_renew_ : Hooks.hook_index_lease6_rebind_;
+    if (HooksManager::calloutsPresent(hook_point)) {
+        CalloutHandlePtr callout_handle = ctx.callout_handle_;
+
+        // Delete all previous arguments
+        callout_handle->deleteAllArguments();
+
+        // Pass the original packet
+        callout_handle->setArgument("query6", ctx.query_);
+
+        // Pass the lease to be updated
+        callout_handle->setArgument("lease6", lease);
+
+        /// @todo: get the IA_NA/IA_PD
+        // Pass the IA option to be sent in response
+        callout_handle->setArgument("ia_na", ctx.ia_rsp_);
+
+        // Call all installed callouts
+        HooksManager::callCallouts(hook_point, *callout_handle);
+
+        // Callouts decided to skip the next processing step. The next
+        // processing step would to actually renew the lease, so skip at this
+        // stage means "keep the old lease as it is".
+        if (callout_handle->getSkip()) {
+            skip = true;
+            LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_HOOKS,
+                      DHCPSRV_HOOK_LEASE6_EXTEND_SKIP)
+                .arg(ctx.query_->getName());
+        }
+    }
+
+    if (!skip) {
+        LeaseMgrFactory::instance().updateLease6(lease);
+    } else {
+        // Copy back the original date to the lease. For MySQL it doesn't make
+        // much sense, but for memfile, the Lease6Ptr points to the actual lease
+        // in memfile, so the actual update is performed when we manipulate
+        // fields of returned Lease6Ptr, the actual updateLease6() is no-op.
+        *lease = old_data;
+    }
+}
+
 AllocEngine::~AllocEngine() {
 AllocEngine::~AllocEngine() {
     // no need to delete allocator. smart_ptr will do the trick for us
     // no need to delete allocator. smart_ptr will do the trick for us
 }
 }

+ 57 - 5
src/lib/dhcpsrv/alloc_engine.h

@@ -18,6 +18,8 @@
 #include <asiolink/io_address.h>
 #include <asiolink/io_address.h>
 #include <dhcp/duid.h>
 #include <dhcp/duid.h>
 #include <dhcp/hwaddr.h>
 #include <dhcp/hwaddr.h>
+#include <dhcp/pkt6.h>
+#include <dhcp/option6_ia.h>
 #include <dhcpsrv/host.h>
 #include <dhcpsrv/host.h>
 #include <dhcpsrv/subnet.h>
 #include <dhcpsrv/subnet.h>
 #include <dhcpsrv/lease_mgr.h>
 #include <dhcpsrv/lease_mgr.h>
@@ -386,6 +388,14 @@ protected:
         /// May be NULL if there are no reservations.
         /// May be NULL if there are no reservations.
         ConstHostPtr host_;
         ConstHostPtr host_;
 
 
+        /// @brief A pointer to the client's message
+        ///
+        /// This is used exclusively for hook purposes.
+        Pkt6Ptr query_;
+
+        /// @brief A pointer to the IA_NA/IA_PD option to be sent in response
+        Option6IAPtr ia_rsp_;
+
         /// @brief Default constructor.
         /// @brief Default constructor.
         ClientContext6()
         ClientContext6()
            : subnet_(), duid_(), iaid_(0), type_(Lease::TYPE_NA), hwaddr_(),
            : subnet_(), duid_(), iaid_(0), type_(Lease::TYPE_NA), hwaddr_(),
@@ -418,11 +428,16 @@ protected:
                        const Lease::Type type, const bool fwd_dns, const bool
                        const Lease::Type type, const bool fwd_dns, const bool
                        rev_dns, const std::string& hostname, const bool
                        rev_dns, const std::string& hostname, const bool
                        fake_allocation):
                        fake_allocation):
-        subnet_(subnet), duid_(duid), iaid_(iaid), type_(type), hwaddr_(),
-            hints_(), fwd_dns_update_(fwd_dns), rev_dns_update_(rev_dns),
-            hostname_(hostname), fake_allocation_(fake_allocation),
-            old_leases_(), host_() {
-            hints_.push_back(hint);
+            subnet_(subnet), duid_(duid), iaid_(iaid), type_(type), hwaddr_(),
+            hints()_, fwd_dns_update_(fwd_dns), rev_dns_update_(rev_dns),
+            hostname_(hostname), fake_allocation_(fake_allocation), old_leases_(),
+            host_() {
+
+            static asiolink::IOAddress any("::");
+
+            if (hint != any) {
+                hints_.push_back(hint);
+            }
             // callout_handle, host pointers initiated to NULL by their
             // callout_handle, host pointers initiated to NULL by their
             // respective constructors.
             // respective constructors.
         }
         }
@@ -644,6 +659,31 @@ protected:
     Lease6Collection
     Lease6Collection
     allocateLeases6(ClientContext6& ctx);
     allocateLeases6(ClientContext6& ctx);
 
 
+
+    /// @brief Renews existing DHCPv6 leases for a given IA.
+    ///
+    /// This method updates the leases associated with a specified IA container.
+    /// It will extend the leases under normal circumstances, but sometimes
+    /// there may be reasons why not to do so. Such a reasons may be:
+    /// - client attempts to renew an address that is not valid
+    /// - client attempts to renew an address that is now reserved for someone
+    ///   else (see host reservation)
+    /// - client's leases does not match his reservations
+    ///
+    /// This method will call  the lease4_renew callout.
+    ///
+    /// @param ctx Message processing context. It holds various information
+    /// extracted from the client's message and required to allocate a lease.
+    /// In particular, @ref ClientContext6::hints_ provides list of addresses or
+    /// prefixes the client had sent. @ref ClientContext6::old_leases_ will
+    /// contain removed leases in this case.
+    ///
+    /// @return Returns renewed lease.
+    Lease6Collection
+    renewLeases6(ClientContext6& ctx);
+
+
+
     /// @brief returns allocator for a given pool type
     /// @brief returns allocator for a given pool type
     /// @param type type of pool (V4, IA, TA or PD)
     /// @param type type of pool (V4, IA, TA or PD)
     /// @throw BadValue if allocator for a given type is missing
     /// @throw BadValue if allocator for a given type is missing
@@ -908,6 +948,18 @@ private:
     removeLeases(Lease6Collection& container,
     removeLeases(Lease6Collection& container,
                  const asiolink::IOAddress& addr);
                  const asiolink::IOAddress& addr);
 
 
+    /// @brief Extends specified IPv6 lease
+    ///
+    /// This method attempts to extend the lease. It will call the lease6_renew
+    /// or lease6_rebind hooks (depending on the client's message specified in
+    /// ctx.query). The lease will be extended in LeaseMgr, unless the hooks
+    /// library will set skip flag.
+    ///
+    /// @param ctx client context that passes all necessary information. See
+    ///        @ref ClientContext6 for details.
+    /// @param lease IPv6 lease to be extended.
+    void extendLease6(ClientContext6& ctx, Lease6Ptr lease);
+
     /// @brief a pointer to currently used allocator
     /// @brief a pointer to currently used allocator
     ///
     ///
     /// For IPv4, there will be only one allocator: TYPE_V4
     /// For IPv4, there will be only one allocator: TYPE_V4

+ 7 - 0
src/lib/dhcpsrv/dhcpsrv_messages.mes

@@ -202,6 +202,13 @@ hook point sets the skip flag. It means that the server was told that
 no lease4 should be assigned. The server will not put that lease in its
 no lease4 should be assigned. The server will not put that lease in its
 database and the client will get a NAK packet.
 database and the client will get a NAK packet.
 
 
+% DHCPSRV_HOOK_LEASE6_EXTEND_SKIP DHCPv6 lease lifetime was not extended because a callout set the skip flag for message %1
+or lease6_rebind hook point set the skip flag. For this particular hook
+point, the setting of the flag by a callout instructs the server to not
+extend the lifetime for a lease. If client requested renewal of multiples
+leases (by sending multiple IA options), the server will skip the renewal
+of the one in question and will proceed with other renewals as usual.
+
 % DHCPSRV_HOOK_LEASE6_SELECT_SKIP Lease6 (non-temporary) creation was skipped, because of callout skip flag.
 % DHCPSRV_HOOK_LEASE6_SELECT_SKIP Lease6 (non-temporary) creation was skipped, because of callout skip flag.
 This debug message is printed when a callout installed on lease6_select
 This debug message is printed when a callout installed on lease6_select
 hook point sets the skip flag. It means that the server was told that
 hook point sets the skip flag. It means that the server was told that

+ 1 - 1
src/lib/dhcpsrv/lease_mgr.h

@@ -294,7 +294,7 @@ public:
     /// transition from single to multi address per IA. It may also be used
     /// transition from single to multi address per IA. It may also be used
     /// in other cases where at most one lease is expected in the database.
     /// in other cases where at most one lease is expected in the database.
     ///
     ///
-    /// It is a wrapper around getLease6(), which returns a collection of
+    /// It is a wrapper around getLeases6(), which returns a collection of
     /// leases. That collection can be converted into a single pointer if
     /// leases. That collection can be converted into a single pointer if
     /// there are no leases (NULL pointer) or one lease (use that single lease).
     /// there are no leases (NULL pointer) or one lease (use that single lease).
     /// If there are more leases in the collection, the function will
     /// If there are more leases in the collection, the function will