From: Francis Dupont Date: Wed, 25 Apr 2018 04:14:22 +0000 (+0200) Subject: [5458a] Checkpoint: code and main tests done, todo prefix and all v6 corner cases X-Git-Tag: trac5488_base~2^2~12 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=89d8d0bce92afc6546e124d81eb24fc56384bb7d;p=thirdparty%2Fkea.git [5458a] Checkpoint: code and main tests done, todo prefix and all v6 corner cases --- diff --git a/src/bin/dhcp6/dhcp6_srv.cc b/src/bin/dhcp6/dhcp6_srv.cc index 6c547e533d..41a6340e06 100644 --- a/src/bin/dhcp6/dhcp6_srv.cc +++ b/src/bin/dhcp6/dhcp6_srv.cc @@ -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(opt->second)); + boost::dynamic_pointer_cast(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(opt->second)); + boost::dynamic_pointer_cast(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 ia) { + int& general_status, boost::shared_ptr 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 ia) { + int& general_status, boost::shared_ptr 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(opt->second)); + boost::dynamic_pointer_cast(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 ia) { + int& general_status, boost::shared_ptr 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); } } diff --git a/src/bin/dhcp6/dhcp6_srv.h b/src/bin/dhcp6/dhcp6_srv.h index fad5d09130..8f04aaf503 100644 --- a/src/bin/dhcp6/dhcp6_srv.h +++ b/src/bin/dhcp6/dhcp6_srv.h @@ -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 ia); + boost::shared_ptr 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 ia); + boost::shared_ptr 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 ia); + boost::shared_ptr ia, Lease6Collection& old_leases); /// @brief Declines specific IPv6 lease. /// diff --git a/src/bin/dhcp6/tests/hooks_unittest.cc b/src/bin/dhcp6/tests/hooks_unittest.cc index 49456cf605..81721fb38c 100644 --- a/src/bin/dhcp6/tests/hooks_unittest.cc +++ b/src/bin/dhcp6/tests/hooks_unittest.cc @@ -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 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 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 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 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 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 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. diff --git a/src/lib/dhcpsrv/alloc_engine.cc b/src/lib/dhcpsrv/alloc_engine.cc index b4c838d13a..452e970c7d 100644 --- a/src/lib/dhcpsrv/alloc_engine.cc +++ b/src/lib/dhcpsrv/alloc_engine.cc @@ -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) { diff --git a/src/lib/dhcpsrv/alloc_engine.h b/src/lib/dhcpsrv/alloc_engine.h index 3433031902..e16dc6a311 100644 --- a/src/lib/dhcpsrv/alloc_engine.h +++ b/src/lib/dhcpsrv/alloc_engine.h @@ -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.