]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[5458a] Checkpoint: code and main tests done, todo prefix and all v6 corner cases
authorFrancis Dupont <fdupont@isc.org>
Wed, 25 Apr 2018 04:14:22 +0000 (06:14 +0200)
committerFrancis Dupont <fdupont@isc.org>
Wed, 25 Apr 2018 04:14:22 +0000 (06:14 +0200)
src/bin/dhcp6/dhcp6_srv.cc
src/bin/dhcp6/dhcp6_srv.h
src/bin/dhcp6/tests/hooks_unittest.cc
src/lib/dhcpsrv/alloc_engine.cc
src/lib/dhcpsrv/alloc_engine.h

index 6c547e533d7b53177fe5a85bc276ebeda1cf627d..41a6340e06d4f343fb1c734172fe6b1eed01b131 100644 (file)
@@ -805,8 +805,27 @@ Dhcpv6Srv::processPacket(Pkt6Ptr& query, Pkt6Ptr& rsp) {
         }
         callout_handle->setArgument("leases6", new_leases);
 
-        // Two points: check only address? avoid duplicates in deleted_leases?
         Lease6CollectionPtr deleted_leases(new Lease6Collection());
+        // Do global list first
+        for (auto old_lease : ctx.deleted_leases_) {
+            if (ctx.new_leases_.empty()) {
+                deleted_leases->push_back(old_lease);
+                continue;
+            }
+            bool in_new = false;
+            for (auto const new_lease : ctx.new_leases_) {
+                if ((new_lease->addr_ == old_lease->addr_) &&
+                    (new_lease->prefixlen_ == old_lease->prefixlen_)) {
+                    in_new = true;
+                    break;
+                }
+            }
+            if (in_new) {
+                continue;
+            }
+            deleted_leases->push_back(old_lease);
+        }
+        // Do per IA lists
         for (auto const iac : ctx.ias_) {
             if (!iac.old_leases_.empty()) {
                 for (auto old_lease : iac.old_leases_) {
@@ -2328,10 +2347,12 @@ Dhcpv6Srv::releaseLeases(const Pkt6Ptr& release, Pkt6Ptr& reply,
     int general_status = STATUS_Success;
     for (OptionCollection::iterator opt = release->options_.begin();
          opt != release->options_.end(); ++opt) {
+        Lease6Ptr old_lease;
         switch (opt->second->getType()) {
         case D6O_IA_NA: {
             OptionPtr answer_opt = releaseIA_NA(ctx.duid_, release, general_status,
-                                   boost::dynamic_pointer_cast<Option6IA>(opt->second));
+                                                boost::dynamic_pointer_cast<Option6IA>(opt->second),
+                                                old_lease);
             if (answer_opt) {
                 reply->addOption(answer_opt);
             }
@@ -2339,7 +2360,8 @@ Dhcpv6Srv::releaseLeases(const Pkt6Ptr& release, Pkt6Ptr& reply,
         }
         case D6O_IA_PD: {
             OptionPtr answer_opt = releaseIA_PD(ctx.duid_, release, general_status,
-                                   boost::dynamic_pointer_cast<Option6IA>(opt->second));
+                                                boost::dynamic_pointer_cast<Option6IA>(opt->second),
+                                                old_lease);
             if (answer_opt) {
                 reply->addOption(answer_opt);
             }
@@ -2350,6 +2372,11 @@ Dhcpv6Srv::releaseLeases(const Pkt6Ptr& release, Pkt6Ptr& reply,
             // remaining options are stateless and thus ignored in this context
             ;
         }
+
+        // Store the old lease.
+        if (old_lease) {
+            ctx.deleted_leases_.push_back(old_lease);
+        }
     }
 
     // To be pedantic, we should also include status code in the top-level
@@ -2361,7 +2388,8 @@ Dhcpv6Srv::releaseLeases(const Pkt6Ptr& release, Pkt6Ptr& reply,
 
 OptionPtr
 Dhcpv6Srv::releaseIA_NA(const DuidPtr& duid, const Pkt6Ptr& query,
-                        int& general_status, boost::shared_ptr<Option6IA> ia) {
+                        int& general_status, boost::shared_ptr<Option6IA> ia,
+                        Lease6Ptr& old_lease) {
 
     LOG_DEBUG(lease6_logger, DBG_DHCP6_DETAIL, DHCP6_PROCESS_IA_NA_RELEASE)
         .arg(query->getLabel())
@@ -2501,6 +2529,8 @@ Dhcpv6Srv::releaseIA_NA(const DuidPtr& duid, const Pkt6Ptr& query,
 
         return (ia_rsp);
     } else {
+        old_lease = lease;
+
         LOG_INFO(lease6_logger, DHCP6_RELEASE_NA)
             .arg(query->getLabel())
             .arg(lease->addr_.toText())
@@ -2525,7 +2555,8 @@ Dhcpv6Srv::releaseIA_NA(const DuidPtr& duid, const Pkt6Ptr& query,
 
 OptionPtr
 Dhcpv6Srv::releaseIA_PD(const DuidPtr& duid, const Pkt6Ptr& query,
-                        int& general_status, boost::shared_ptr<Option6IA> ia) {
+                        int& general_status, boost::shared_ptr<Option6IA> ia,
+                        Lease6Ptr& old_lease) {
     // Release can be done in one of two ways:
     // Approach 1: extract address from client's IA_NA and see if it belongs
     // to this particular client.
@@ -2659,6 +2690,8 @@ Dhcpv6Srv::releaseIA_PD(const DuidPtr& duid, const Pkt6Ptr& query,
         general_status = STATUS_UnspecFail;
 
     } else {
+        old_lease = lease;
+
         LOG_INFO(lease6_logger, DHCP6_RELEASE_PD)
             .arg(query->getLabel())
             .arg(lease->addr_.toText())
@@ -2975,7 +3008,8 @@ Dhcpv6Srv::declineLeases(const Pkt6Ptr& decline, Pkt6Ptr& reply,
         switch (opt->second->getType()) {
         case D6O_IA_NA: {
             OptionPtr answer_opt = declineIA(decline, ctx.duid_, general_status,
-                                   boost::dynamic_pointer_cast<Option6IA>(opt->second));
+                                             boost::dynamic_pointer_cast<Option6IA>(opt->second),
+                                             ctx.deleted_leases_);
             if (answer_opt) {
 
                 // We have an answer, let's use it.
@@ -3000,7 +3034,8 @@ Dhcpv6Srv::declineLeases(const Pkt6Ptr& decline, Pkt6Ptr& reply,
 
 OptionPtr
 Dhcpv6Srv::declineIA(const Pkt6Ptr& decline, const DuidPtr& duid,
-                     int& general_status, boost::shared_ptr<Option6IA> ia) {
+                     int& general_status, boost::shared_ptr<Option6IA> ia,
+                     Lease6Collection& old_leases) {
 
     LOG_DEBUG(lease6_logger, DBG_DHCP6_DETAIL, DHCP6_DECLINE_PROCESS_IA)
         .arg(decline->getLabel())
@@ -3106,6 +3141,8 @@ Dhcpv6Srv::declineIA(const Pkt6Ptr& decline, const DuidPtr& duid,
             // declineLease returns false only when hook callouts set the next
             // step status to drop. We just propagate the bad news here.
             return (OptionPtr());
+        } else {
+            old_leases.push_back(lease);
         }
     }
 
index fad5d0913065c1869341d39f0dd1f58532b922bb..8f04aaf503c818f1e928ae53de52d6e48b3eeb23 100644 (file)
@@ -457,10 +457,12 @@ protected:
     /// @param query client's message
     /// @param general_status a global status (it may be updated in case of errors)
     /// @param ia IA_NA option that is being released
+    /// @param old_lease a pointer to the lease being released
     /// @return IA_NA option (server's response)
     OptionPtr releaseIA_NA(const DuidPtr& duid, const Pkt6Ptr& query,
                            int& general_status,
-                           boost::shared_ptr<Option6IA> ia);
+                           boost::shared_ptr<Option6IA> ia,
+                           Lease6Ptr& old_lease);
 
     /// @brief Releases specific IA_PD option
     ///
@@ -473,10 +475,12 @@ protected:
     /// @param query client's message
     /// @param general_status a global status (it may be updated in case of errors)
     /// @param ia IA_PD option that is being released
+    /// @param old_lease a pointer to the lease being released
     /// @return IA_PD option (server's response)
     OptionPtr releaseIA_PD(const DuidPtr& duid, const Pkt6Ptr& query,
                            int& general_status,
-                           boost::shared_ptr<Option6IA> ia);
+                           boost::shared_ptr<Option6IA> ia,
+                           Lease6Ptr& old_lease);
 
     /// @brief Copies required options from client message to server answer.
     ///
@@ -771,10 +775,11 @@ protected:
     /// @param duid client's duid (used to verify if the client owns the lease)
     /// @param general_status [out] status in top-level message (may be updated)
     /// @param ia specific IA_NA option to process.
+    /// @param old_leases a collection of leases being declined.
     /// @return IA_NA option with response (to be included in Reply message)
     OptionPtr
     declineIA(const Pkt6Ptr& decline, const DuidPtr& duid, int& general_status,
-              boost::shared_ptr<Option6IA> ia);
+              boost::shared_ptr<Option6IA> ia, Lease6Collection& old_leases);
 
     /// @brief Declines specific IPv6 lease.
     ///
index 49456cf605c8c17ed737c679527d7c35bf1ad9cb..81721fb38c2516153571e9ddfa9fce949faa6fc5 100644 (file)
@@ -1762,6 +1762,46 @@ TEST_F(HooksDhcpv6SrvTest, leases6CommittedSolicit) {
     EXPECT_TRUE(callback_name_.empty());
 }
 
+// This test verifies that the leases6_committed hook point is not triggered
+// for the CONFIRM.
+TEST_F(HooksDhcpv6SrvTest, leases6CommittedConfirm) {
+    IfaceMgrTestConfig test_config(true);
+
+    string config = "{ \"interfaces-config\": {"
+        "  \"interfaces\": [ \"*\" ]"
+        "},"
+        "\"preferred-lifetime\": 3000,"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet6\": [ { "
+        "    \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
+        "    \"subnet\": \"2001:db8:1::/48\", "
+        "    \"interface\": \"eth1\" "
+        " } ],"
+        "\"valid-lifetime\": 4000 }";
+
+    Dhcp6Client client;
+    client.setInterface("eth1");
+    client.requestAddress(0xabca, IOAddress("2001:db8:1::28"));
+
+    ASSERT_NO_THROW(configure(config, *client.getServer()));
+    
+    // Get a lease for the client.
+    ASSERT_NO_THROW(client.doSARR());
+    
+    // Install leases6_committed callout
+    ASSERT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+                    "leases6_committed", leases6_committed_callout));
+
+    ASSERT_NO_THROW(client.doConfirm());
+
+    // Make sure that we received a response
+    ASSERT_TRUE(client.getContext().response_);
+
+    // Make sure that the callout wasn't called.
+    EXPECT_TRUE(callback_name_.empty());
+}
+
 // This test verifies that the leases6_committed hook point is not triggered
 // for the INFREQUEST.
 TEST_F(HooksDhcpv6SrvTest, leases6CommittedInfRequest) {
@@ -1797,6 +1837,239 @@ TEST_F(HooksDhcpv6SrvTest, leases6CommittedInfRequest) {
     EXPECT_TRUE(callback_name_.empty());
 }
 
+// This test verifies that the callout installed on the leases6_committed hook
+// point is executed as a result of REQUEST message sent to allocate new
+// lease or renew an existing lease.
+TEST_F(HooksDhcpv6SrvTest, leases6CommittedRequest) {
+    IfaceMgrTestConfig test_config(true);
+
+    string config = "{ \"interfaces-config\": {"
+        "  \"interfaces\": [ \"*\" ]"
+        "},"
+        "\"preferred-lifetime\": 3000,"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet6\": [ { "
+        "    \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
+        "    \"subnet\": \"2001:db8:1::/48\", "
+        "    \"interface\": \"eth1\" "
+        " } ],"
+        "\"valid-lifetime\": 4000 }";
+
+    Dhcp6Client client;
+    client.setInterface("eth1");
+    client.requestAddress(0xabca, IOAddress("2001:db8:1::28"));
+
+    ASSERT_NO_THROW(configure(config, *client.getServer()));
+
+    ASSERT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+                    "leases6_committed", leases6_committed_callout));
+
+    ASSERT_NO_THROW(client.doSARR());
+
+    // Make sure that we received a response
+    ASSERT_TRUE(client.getContext().response_);
+
+    // Check that the callback called is indeed the one we installed
+    EXPECT_EQ("leases6_committed", callback_name_);
+
+    // Check if all expected parameters were really received
+    vector<string> expected_argument_names;
+    expected_argument_names.push_back("query6");
+    expected_argument_names.push_back("deleted_leases6");
+    expected_argument_names.push_back("leases6");
+
+    sort(expected_argument_names.begin(), expected_argument_names.end());
+    EXPECT_TRUE(callback_argument_names_ == expected_argument_names);
+
+    // Newly allocated lease should be returned.
+    ASSERT_TRUE(callback_new_leases6_);
+    ASSERT_EQ(1, callback_new_leases6_->size());
+    Lease6Ptr lease = callback_new_leases6_->at(0);
+    ASSERT_TRUE(lease);
+    EXPECT_EQ("2001:db8:1::28", lease->addr_.toText());
+
+    // Deleted lease must not be present, because it is a new allocation.
+    ASSERT_TRUE(callback_deleted_leases6_);
+    EXPECT_TRUE(callback_deleted_leases6_->empty());
+
+    // Pkt passed to a callout must be configured to copy retrieved options.
+    EXPECT_TRUE(callback_qry_options_copy_);
+
+    resetCalloutBuffers();
+
+    // Request the lease and make sure that the callout has been executed.
+    ASSERT_NO_THROW(client.doRequest());
+
+    // Make sure that we received a response
+    ASSERT_TRUE(client.getContext().response_);
+
+    // Check that the callback called is indeed the one we installed
+    EXPECT_EQ("leases6_committed", callback_name_);
+
+    // Requested lease should be returned.
+    ASSERT_TRUE(callback_new_leases6_);
+    ASSERT_EQ(1, callback_new_leases6_->size());
+    lease = callback_new_leases6_->at(0);
+    ASSERT_TRUE(lease);
+    EXPECT_EQ("2001:db8:1::28", lease->addr_.toText());
+
+    // Deleted lease must not be present, because it is a new allocation.
+    ASSERT_TRUE(callback_deleted_leases6_);
+    EXPECT_TRUE(callback_deleted_leases6_->empty());
+
+    // Pkt passed to a callout must be configured to copy retrieved options.
+    EXPECT_TRUE(callback_qry_options_copy_);
+
+    resetCalloutBuffers();
+
+    // Let's try to request again but force the client to request a different
+    // address with a different IAID.
+    client.requestAddress(0x2233, IOAddress("2001:db8:1::29"));
+
+    ASSERT_NO_THROW(client.doRequest());
+
+    // Make sure that we received a response
+    ASSERT_TRUE(client.getContext().response_);
+
+    // Check that the callback called is indeed the one we installed
+    EXPECT_EQ("leases6_committed", callback_name_);
+
+    // New lease should be returned.
+    ASSERT_TRUE(callback_new_leases6_);
+    ASSERT_EQ(2, callback_new_leases6_->size());
+    lease = callback_new_leases6_->at(1);
+    ASSERT_TRUE(lease);
+    EXPECT_EQ("2001:db8:1::29", lease->addr_.toText());
+
+    // The old lease is kept.
+    ASSERT_TRUE(callback_deleted_leases6_);
+    ASSERT_TRUE(callback_deleted_leases6_->empty());
+
+    // Pkt passed to a callout must be configured to copy retrieved options.
+    EXPECT_TRUE(callback_qry_options_copy_);
+
+    resetCalloutBuffers();
+
+    // The requested address is just a hint.
+    client.requestAddress(0x5577, IOAddress("4000::2"));
+
+    ASSERT_NO_THROW(client.doRequest());
+
+    // Make sure that we received a response
+    ASSERT_TRUE(client.getContext().response_);
+
+    // Check that the callback called is indeed the one we installed
+    EXPECT_EQ("leases6_committed", callback_name_);
+
+    ASSERT_TRUE(callback_new_leases6_);
+    EXPECT_EQ(3, callback_new_leases6_->size());
+    lease = callback_new_leases6_->at(2);
+    ASSERT_TRUE(lease);
+    EXPECT_EQ("2001:db8:1::", lease->addr_.toText());
+    ASSERT_TRUE(callback_deleted_leases6_);
+    EXPECT_TRUE(callback_deleted_leases6_->empty());
+}
+
+// This test verifies that it is possible to park a packet as a result of
+// the leases6_committed callouts.
+TEST_F(HooksDhcpv6SrvTest, leases6CommittedParkRequests) {
+    IfaceMgrTestConfig test_config(true);
+
+    string config = "{ \"interfaces-config\": {"
+        "  \"interfaces\": [ \"*\" ]"
+        "},"
+        "\"preferred-lifetime\": 3000,"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet6\": [ { "
+        "    \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
+        "    \"subnet\": \"2001:db8:1::/48\", "
+        "    \"interface\": \"eth1\" "
+        " } ],"
+        "\"valid-lifetime\": 4000 }";
+
+    // Create first client and perform SARR.
+    Dhcp6Client client1;
+    client1.setInterface("eth1");
+    client1.requestAddress(0xabca, IOAddress("2001:db8:1::28"));
+
+    ASSERT_NO_THROW(configure(config, *client1.getServer()));
+
+    // This callout uses provided IO service object to post a function
+    // that unparks the packet. The packet is parked and can be unparked
+    // by simply calling IOService::poll.
+    ASSERT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+                    "leases6_committed", leases6_committed_park_callout));
+
+    ASSERT_NO_THROW(client1.doSARR());
+
+    // We should be offered an address but the REPLY should not arrive
+    // at this point, because the packet is parked.
+    ASSERT_FALSE(client1.getContext().response_);
+
+    // Check that the callback called is indeed the one we installed
+    EXPECT_EQ("leases6_committed", callback_name_);
+
+    // Check if all expected parameters were really received
+    vector<string> expected_argument_names;
+    expected_argument_names.push_back("query6");
+    expected_argument_names.push_back("deleted_leases6");
+    expected_argument_names.push_back("leases6");
+
+    sort(expected_argument_names.begin(), expected_argument_names.end());
+    EXPECT_TRUE(callback_argument_names_ == expected_argument_names);
+
+    // Newly allocated lease should be passed to the callout.
+    ASSERT_TRUE(callback_new_leases6_);
+    ASSERT_EQ(1, callback_new_leases6_->size());
+    Lease6Ptr lease = callback_new_leases6_->at(0);
+    ASSERT_TRUE(lease);
+    EXPECT_EQ("2001:db8:1::28", lease->addr_.toText());
+
+    // Deleted lease must not be present, because it is a new allocation.
+    ASSERT_TRUE(callback_deleted_leases6_);
+    EXPECT_TRUE(callback_deleted_leases6_->empty());
+
+    // Pkt passed to a callout must be configured to copy retrieved options.
+    EXPECT_TRUE(callback_qry_options_copy_);
+
+    // Reset all indicators because we'll be now creating a second client.
+    resetCalloutBuffers();
+
+    // Create the second client to test that it may communicate with the
+    // server while the previous packet is parked.
+    Dhcp6Client client2;
+    client2.setInterface("eth1");
+    client2.requestAddress(0xabca, IOAddress("2001:db8:1::29"));
+    ASSERT_NO_THROW(client2.doSARR());
+
+    // The ADVERTISE should have been returned but not REPLAY, as this
+    // packet got parked too.
+    ASSERT_FALSE(client2.getContext().response_);
+
+    // Check that the callback called is indeed the one we installed.
+    EXPECT_EQ("leases6_committed", callback_name_);
+
+    // There should be now two actions scheduled on our IO service
+    // by the invoked callouts. They unpark both REPLY messages.
+    ASSERT_NO_THROW(io_service_->poll());
+
+    // Receive and check the first response.
+    ASSERT_NO_THROW(client1.receiveResponse());
+    ASSERT_TRUE(client1.getContext().response_);
+    Pkt6Ptr rsp = client1.getContext().response_;
+    EXPECT_EQ(DHCPV6_REPLY, rsp->getType());
+    EXPECT_TRUE(client1.hasLeaseForAddress(IOAddress("2001:db8:1::28")));
+
+    // Receive and check the second response.
+    ASSERT_NO_THROW(client2.receiveResponse());
+    ASSERT_TRUE(client2.getContext().response_);
+    rsp = client2.getContext().response_;
+    EXPECT_EQ(DHCPV6_REPLY, rsp->getType());
+    EXPECT_TRUE(client2.hasLeaseForAddress(IOAddress("2001:db8:1::29")));
+}
+
 // This test verifies that incoming (positive) RENEW can be handled properly,
 // and the lease6_renew callouts are triggered.
 TEST_F(HooksDhcpv6SrvTest, basicLease6Renew) {
@@ -2064,9 +2337,9 @@ TEST_F(HooksDhcpv6SrvTest, skipLease6Renew) {
 }
 
 // This test verifies that the callout installed on the leases6_committed hook
-// point is executed as a result of REQUEST message sent to allocate new
+// point is executed as a result of RENEW message sent to allocate new
 // lease or renew an existing lease.
-TEST_F(HooksDhcpv6SrvTest, leases6CommittedRequest) {
+TEST_F(HooksDhcpv6SrvTest, leases6CommittedRenew) {
     IfaceMgrTestConfig test_config(true);
 
     string config = "{ \"interfaces-config\": {"
@@ -2149,11 +2422,9 @@ TEST_F(HooksDhcpv6SrvTest, leases6CommittedRequest) {
 
     resetCalloutBuffers();
 
-    // Let's try to renew again but force the client to request a different
-    // address.
-    client.clearConfig();
-    client.clearRequestedIAs();
-    client.requestAddress(0xabca, IOAddress("2001:db8:1::29"));
+    // Let's try to renew again but force the client to renew a different
+    // address with a different IAID.
+    client.requestAddress(0x2233, IOAddress("2001:db8:1::29"));
 
     ASSERT_NO_THROW(client.doRenew());
 
@@ -2161,147 +2432,42 @@ TEST_F(HooksDhcpv6SrvTest, leases6CommittedRequest) {
     ASSERT_TRUE(client.getContext().response_);
 
     // Check that the callback called is indeed the one we installed
-    EXPECT_EQ("leases6_committed", callback_name_);
-
-#if 0
-    // New lease should be returned.
-    ASSERT_TRUE(callback_new_leases6_);
-    ASSERT_EQ(1, callback_new_leases6_->size());
-    lease = callback_new_leases6_->at(0);
-    ASSERT_TRUE(lease);
-    EXPECT_EQ("2001:db8:1::29", lease->addr_.toText());
-
-    // The old lease should have been deleted.
-    ASSERT_TRUE(callback_deleted_leases6_);
-    ASSERT_EQ(1, callback_deleted_leases6_->size());
-    lease = callback_deleted_leases6_->at(0);
-    ASSERT_TRUE(lease);
-    EXPECT_EQ("2001:db8:1::28", lease->addr_.toText());
-#endif
-
-    // Pkt passed to a callout must be configured to copy retrieved options.
-    EXPECT_TRUE(callback_qry_options_copy_);
-
-    resetCalloutBuffers();
-
-    // Now request an address that can't be allocated. The client should receive
-    // an error.
-
-    client.clearConfig();
-    client.clearRequestedIAs();
-    client.requestAddress(0xabca, IOAddress("4000::2"));
-
-    ASSERT_NO_THROW(client.doRequest());
-
-    // Make sure that we received a response
-    ASSERT_TRUE(client.getContext().response_);
-
-    // Check that the callback called is indeed the one we installed
-    EXPECT_EQ("leases6_committed", callback_name_);
-
-    ASSERT_TRUE(callback_new_leases6_);
-    ////EXPECT_TRUE(callback_new_leases6_->empty());
-    ASSERT_TRUE(callback_deleted_leases6_);
-    EXPECT_TRUE(callback_deleted_leases6_->empty());
-}
-
-// This test verifies that it is possible to park a packet as a result of
-// the leases6_committed callouts.
-TEST_F(HooksDhcpv6SrvTest, leases6CommittedParkRequests) {
-    IfaceMgrTestConfig test_config(true);
-
-    string config = "{ \"interfaces-config\": {"
-        "  \"interfaces\": [ \"*\" ]"
-        "},"
-        "\"preferred-lifetime\": 3000,"
-        "\"rebind-timer\": 2000, "
-        "\"renew-timer\": 1000, "
-        "\"subnet6\": [ { "
-        "    \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
-        "    \"subnet\": \"2001:db8:1::/48\", "
-        "    \"interface\": \"eth1\" "
-        " } ],"
-        "\"valid-lifetime\": 4000 }";
-
-    // Create first client and perform DORA.
-    Dhcp6Client client1;
-    client1.setInterface("eth1");
-    client1.requestAddress(0xabca, IOAddress("2001:db8:1::28"));
-
-    ASSERT_NO_THROW(configure(config, *client1.getServer()));
-
-    // This callout uses provided IO service object to post a function
-    // that unparks the packet. The packet is parked and can be unparked
-    // by simply calling IOService::poll.
-    ASSERT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
-                    "leases6_committed", leases6_committed_park_callout));
-
-    ASSERT_NO_THROW(client1.doSARR());
-
-    // We should be offered an address but the REPLY should not arrive
-    // at this point, because the packet is parked.
-    ASSERT_FALSE(client1.getContext().response_);
-
-    // Check that the callback called is indeed the one we installed
-    EXPECT_EQ("leases6_committed", callback_name_);
-
-    // Check if all expected parameters were really received
-    vector<string> expected_argument_names;
-    expected_argument_names.push_back("query6");
-    expected_argument_names.push_back("deleted_leases6");
-    expected_argument_names.push_back("leases6");
-
-    sort(expected_argument_names.begin(), expected_argument_names.end());
-    EXPECT_TRUE(callback_argument_names_ == expected_argument_names);
+    EXPECT_EQ("leases6_committed", callback_name_);
 
-    // Newly allocated lease should be passed to the callout.
+    // New lease should be returned.
     ASSERT_TRUE(callback_new_leases6_);
-    ASSERT_EQ(1, callback_new_leases6_->size());
-    Lease6Ptr lease = callback_new_leases6_->at(0);
+    ASSERT_EQ(2, callback_new_leases6_->size());
+    lease = callback_new_leases6_->at(1);
     ASSERT_TRUE(lease);
-    EXPECT_EQ("2001:db8:1::28", lease->addr_.toText());
+    EXPECT_EQ("2001:db8:1::29", lease->addr_.toText());
 
-    // Deleted lease must not be present, because it is a new allocation.
+    // The old lease is kept.
     ASSERT_TRUE(callback_deleted_leases6_);
-    EXPECT_TRUE(callback_deleted_leases6_->empty());
+    ASSERT_TRUE(callback_deleted_leases6_->empty());
 
     // Pkt passed to a callout must be configured to copy retrieved options.
     EXPECT_TRUE(callback_qry_options_copy_);
 
-    // Reset all indicators because we'll be now creating a second client.
     resetCalloutBuffers();
 
-    // Create the second client to test that it may communicate with the
-    // server while the previous packet is parked.
-    Dhcp6Client client2;
-    client2.setInterface("eth1");
-    client2.requestAddress(0xabca, IOAddress("2001:db8:1::29"));
-    ASSERT_NO_THROW(client2.doSARR());
-
-    // The DHCPOFFER should have been returned but not DHCPACK, as this
-    // packet got parked too.
-    ASSERT_FALSE(client2.getContext().response_);
+    // The renewed address is just a hint.
+    client.requestAddress(0x5577, IOAddress("4000::2"));
 
-    // Check that the callback called is indeed the one we installed.
-    EXPECT_EQ("leases6_committed", callback_name_);
+    ASSERT_NO_THROW(client.doRenew());
 
-    // There should be now two actions scheduled on our IO service
-    // by the invoked callouts. They unpark both DHCPACK messages.
-    ASSERT_NO_THROW(io_service_->poll());
+    // Make sure that we received a response
+    ASSERT_TRUE(client.getContext().response_);
 
-    // Receive and check the first response.
-    ASSERT_NO_THROW(client1.receiveResponse());
-    ASSERT_TRUE(client1.getContext().response_);
-    Pkt6Ptr rsp = client1.getContext().response_;
-    EXPECT_EQ(DHCPV6_REPLY, rsp->getType());
-    //// EXPECT_EQ("2001:db8:1::28", rsp->getYiaddr().toText());
+    // Check that the callback called is indeed the one we installed
+    EXPECT_EQ("leases6_committed", callback_name_);
 
-    // Receive and check the second response.
-    ASSERT_NO_THROW(client2.receiveResponse());
-    ASSERT_TRUE(client2.getContext().response_);
-    rsp = client2.getContext().response_;
-    EXPECT_EQ(DHCPV6_REPLY, rsp->getType());
-    //// EXPECT_EQ("2001:db8:1::29", rsp->getYiaddr().toText());
+    ASSERT_TRUE(callback_new_leases6_);
+    EXPECT_EQ(3, callback_new_leases6_->size());
+    lease = callback_new_leases6_->at(2);
+    ASSERT_TRUE(lease);
+    EXPECT_EQ("2001:db8:1::", lease->addr_.toText());
+    ASSERT_TRUE(callback_deleted_leases6_);
+    EXPECT_TRUE(callback_deleted_leases6_->empty());
 }
 
 // This test verifies that incoming (positive) RELEASE can be handled properly,
@@ -2596,7 +2762,65 @@ TEST_F(HooksDhcpv6SrvTest, dropLease6Release) {
     ASSERT_TRUE(l);
 }
 
-/// leases4CommittedRelease
+// This test verifies that the leases6_committed callout is executed
+// with deleted leases as argument when RELEASE is processed.
+TEST_F(HooksDhcpv6SrvTest, leases6CommittedRelease) {
+    IfaceMgrTestConfig test_config(true);
+
+    string config = "{ \"interfaces-config\": {"
+        "  \"interfaces\": [ \"*\" ]"
+        "},"
+        "\"preferred-lifetime\": 3000,"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet6\": [ { "
+        "    \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
+        "    \"subnet\": \"2001:db8:1::/48\", "
+        "    \"interface\": \"eth1\" "
+        " } ],"
+        "\"valid-lifetime\": 4000 }";
+
+    Dhcp6Client client;
+    client.setInterface("eth1");
+    client.requestAddress(0xabca, IOAddress("2001:db8:1::28"));
+
+    ASSERT_NO_THROW(configure(config, *client.getServer()));
+
+    ASSERT_NO_THROW(client.doSARR());
+    // Make sure that we received a response
+    ASSERT_TRUE(client.getContext().response_);
+
+    ASSERT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+                    "leases6_committed", leases6_committed_callout));
+
+    ASSERT_NO_THROW(client.doRelease());
+
+    // Check that the callback called is indeed the one we installed
+    EXPECT_EQ("leases6_committed", callback_name_);
+
+    // Check if all expected parameters were really received
+    vector<string> expected_argument_names;
+    expected_argument_names.push_back("query6");
+    expected_argument_names.push_back("deleted_leases6");
+    expected_argument_names.push_back("leases6");
+
+    sort(expected_argument_names.begin(), expected_argument_names.end());
+    EXPECT_TRUE(callback_argument_names_ == expected_argument_names);
+
+    // No new allocations.
+    ASSERT_TRUE(callback_new_leases6_);
+    ASSERT_TRUE(callback_new_leases6_->empty());
+
+    // Released lease should be returned.
+    ASSERT_TRUE(callback_deleted_leases6_);
+    EXPECT_EQ(1, callback_deleted_leases6_->size());
+    Lease6Ptr lease = callback_deleted_leases6_->at(0);
+    ASSERT_TRUE(lease);
+    EXPECT_EQ("2001:db8:1::28", lease->addr_.toText());
+
+    // Pkt passed to a callout must be configured to copy retrieved options.
+    EXPECT_TRUE(callback_qry_options_copy_);
+}
 
 // This test verifies that incoming (positive) REBIND can be handled properly,
 // and the lease6_rebind callouts are triggered.
@@ -2852,7 +3076,139 @@ TEST_F(HooksDhcpv6SrvTest, skipLease6Rebind) {
     EXPECT_NE(l->cltt_, time(NULL));
 }
 
-/// leases4CommittedRebind
+// This test verifies that the callout installed on the leases6_committed hook
+// point is executed as a result of REBIND message sent to allocate new
+// lease or renew an existing lease.
+TEST_F(HooksDhcpv6SrvTest, leases6CommittedRebind) {
+    IfaceMgrTestConfig test_config(true);
+
+    string config = "{ \"interfaces-config\": {"
+        "  \"interfaces\": [ \"*\" ]"
+        "},"
+        "\"preferred-lifetime\": 3000,"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet6\": [ { "
+        "    \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
+        "    \"subnet\": \"2001:db8:1::/48\", "
+        "    \"interface\": \"eth1\" "
+        " } ],"
+        "\"valid-lifetime\": 4000 }";
+
+    Dhcp6Client client;
+    client.setInterface("eth1");
+    client.requestAddress(0xabca, IOAddress("2001:db8:1::28"));
+
+    ASSERT_NO_THROW(configure(config, *client.getServer()));
+
+    ASSERT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+                    "leases6_committed", leases6_committed_callout));
+
+    ASSERT_NO_THROW(client.doSARR());
+
+    // Make sure that we received a response
+    ASSERT_TRUE(client.getContext().response_);
+
+    // Check that the callback called is indeed the one we installed
+    EXPECT_EQ("leases6_committed", callback_name_);
+
+    // Check if all expected parameters were really received
+    vector<string> expected_argument_names;
+    expected_argument_names.push_back("query6");
+    expected_argument_names.push_back("deleted_leases6");
+    expected_argument_names.push_back("leases6");
+
+    sort(expected_argument_names.begin(), expected_argument_names.end());
+    EXPECT_TRUE(callback_argument_names_ == expected_argument_names);
+
+    // Newly allocated lease should be returned.
+    ASSERT_TRUE(callback_new_leases6_);
+    ASSERT_EQ(1, callback_new_leases6_->size());
+    Lease6Ptr lease = callback_new_leases6_->at(0);
+    ASSERT_TRUE(lease);
+    EXPECT_EQ("2001:db8:1::28", lease->addr_.toText());
+
+    // Deleted lease must not be present, because it is a new allocation.
+    ASSERT_TRUE(callback_deleted_leases6_);
+    EXPECT_TRUE(callback_deleted_leases6_->empty());
+
+    // Pkt passed to a callout must be configured to copy retrieved options.
+    EXPECT_TRUE(callback_qry_options_copy_);
+
+    resetCalloutBuffers();
+
+    // Rebind the lease and make sure that the callout has been executed.
+    ASSERT_NO_THROW(client.doRebind());
+
+    // Make sure that we received a response
+    ASSERT_TRUE(client.getContext().response_);
+
+    // Check that the callback called is indeed the one we installed
+    EXPECT_EQ("leases6_committed", callback_name_);
+
+    // Rebound lease should be returned.
+    ASSERT_TRUE(callback_new_leases6_);
+    ASSERT_EQ(1, callback_new_leases6_->size());
+    lease = callback_new_leases6_->at(0);
+    ASSERT_TRUE(lease);
+    EXPECT_EQ("2001:db8:1::28", lease->addr_.toText());
+
+    // Deleted lease must not be present, because it is a new allocation.
+    ASSERT_TRUE(callback_deleted_leases6_);
+    EXPECT_TRUE(callback_deleted_leases6_->empty());
+
+    // Pkt passed to a callout must be configured to copy retrieved options.
+    EXPECT_TRUE(callback_qry_options_copy_);
+
+    resetCalloutBuffers();
+
+    // Let's try to rebind again but force the client to rebind a different
+    // address with a different IAID.
+    client.requestAddress(0x2233, IOAddress("2001:db8:1::29"));
+
+    ASSERT_NO_THROW(client.doRebind());
+
+    // Make sure that we received a response
+    ASSERT_TRUE(client.getContext().response_);
+
+    // Check that the callback called is indeed the one we installed
+    EXPECT_EQ("leases6_committed", callback_name_);
+
+    // New lease should be returned.
+    ASSERT_TRUE(callback_new_leases6_);
+    ASSERT_EQ(2, callback_new_leases6_->size());
+    lease = callback_new_leases6_->at(1);
+    ASSERT_TRUE(lease);
+    EXPECT_EQ("2001:db8:1::29", lease->addr_.toText());
+
+    // The old lease is kept.
+    ASSERT_TRUE(callback_deleted_leases6_);
+    ASSERT_TRUE(callback_deleted_leases6_->empty());
+
+    // Pkt passed to a callout must be configured to copy retrieved options.
+    EXPECT_TRUE(callback_qry_options_copy_);
+
+    resetCalloutBuffers();
+
+    // The rebound address is just a hint.
+    client.requestAddress(0x5577, IOAddress("4000::2"));
+
+    ASSERT_NO_THROW(client.doRebind());
+
+    // Make sure that we received a response
+    ASSERT_TRUE(client.getContext().response_);
+
+    // Check that the callback called is indeed the one we installed
+    EXPECT_EQ("leases6_committed", callback_name_);
+
+    ASSERT_TRUE(callback_new_leases6_);
+    EXPECT_EQ(3, callback_new_leases6_->size());
+    lease = callback_new_leases6_->at(2);
+    ASSERT_TRUE(lease);
+    EXPECT_EQ("2001:db8:1::", lease->addr_.toText());
+    ASSERT_TRUE(callback_deleted_leases6_);
+    EXPECT_TRUE(callback_deleted_leases6_->empty());
+}
 
 // This test checks that the basic decline hook (lease6_decline) is
 // triggered properly.
@@ -2997,7 +3353,65 @@ TEST_F(HooksDhcpv6SrvTest, lease6DeclineDrop) {
     EXPECT_EQ(Lease::STATE_DEFAULT, from_mgr->state_);
 }
 
-/// leases4CommittedDecline
+// This test verifies that the leases6_committed callout is executed
+// with deleted leases as argument when DECLINE is processed.
+TEST_F(HooksDhcpv6SrvTest, leases6CommittedDecline) {
+    IfaceMgrTestConfig test_config(true);
+
+    string config = "{ \"interfaces-config\": {"
+        "  \"interfaces\": [ \"*\" ]"
+        "},"
+        "\"preferred-lifetime\": 3000,"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet6\": [ { "
+        "    \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
+        "    \"subnet\": \"2001:db8:1::/48\", "
+        "    \"interface\": \"eth1\" "
+        " } ],"
+        "\"valid-lifetime\": 4000 }";
+
+    Dhcp6Client client;
+    client.setInterface("eth1");
+    client.requestAddress(0xabca, IOAddress("2001:db8:1::28"));
+
+    ASSERT_NO_THROW(configure(config, *client.getServer()));
+
+    ASSERT_NO_THROW(client.doSARR());
+    // Make sure that we received a response
+    ASSERT_TRUE(client.getContext().response_);
+
+    ASSERT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+                    "leases6_committed", leases6_committed_callout));
+
+    ASSERT_NO_THROW(client.doDecline());
+
+    // Check that the callback called is indeed the one we installed
+    EXPECT_EQ("leases6_committed", callback_name_);
+
+    // Check if all expected parameters were really received
+    vector<string> expected_argument_names;
+    expected_argument_names.push_back("query6");
+    expected_argument_names.push_back("deleted_leases6");
+    expected_argument_names.push_back("leases6");
+
+    sort(expected_argument_names.begin(), expected_argument_names.end());
+    EXPECT_TRUE(callback_argument_names_ == expected_argument_names);
+
+    // No new allocations.
+    ASSERT_TRUE(callback_new_leases6_);
+    ASSERT_TRUE(callback_new_leases6_->empty());
+
+    // Declined lease should be returned.
+    ASSERT_TRUE(callback_deleted_leases6_);
+    EXPECT_EQ(1, callback_deleted_leases6_->size());
+    Lease6Ptr lease = callback_deleted_leases6_->at(0);
+    ASSERT_TRUE(lease);
+    EXPECT_EQ("2001:db8:1::28", lease->addr_.toText());
+
+    // Pkt passed to a callout must be configured to copy retrieved options.
+    EXPECT_TRUE(callback_qry_options_copy_);
+}
 
 // Checks if callout installed on host6_identifier can generate an
 // identifier and whether that identifier is actually used.
index b4c838d13a39ffc8f06403a88b882cad325137b7..452e970c7d3f4e3bb40ed3854af188ea1e041bb1 100644 (file)
@@ -445,7 +445,7 @@ AllocEngine::ClientContext6::ClientContext6(const Subnet6Ptr& subnet,
       duid_(duid), hwaddr_(), host_identifiers_(), hosts_(),
       fwd_dns_update_(fwd_dns), rev_dns_update_(rev_dns), committed_(false),
       hostname_(hostname), callout_handle_(callout_handle),
-      allocated_resources_(), new_leases_(), ias_() {
+      allocated_resources_(), new_leases_(), deleted_leases_(), ias_() {
 
     // Initialize host identifiers.
     if (duid) {
index 34330319024aa9d953707e68ae5604452e1fd6ed..e16dc6a311a08154d5a81e44c2ecc8c233375d11 100644 (file)
@@ -384,6 +384,9 @@ public:
         /// @brief A collection of newly allocated leases.
         Lease6Collection new_leases_;
 
+        /// @brief A collection of old leases that the client had before.
+        Lease6Collection deleted_leases_;
+
         //@}
 
         /// @brief Parameters pertaining to individual IAs.