From: Thomas Markwalder Date: Thu, 26 Mar 2020 18:30:26 +0000 (-0400) Subject: [#1010] Modified AllocateEngine to store extended lease4 info X-Git-Tag: Kea-1.7.7~90 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=3b75ebf2711d8faea6539caae6c02237f720c829;p=thirdparty%2Fkea.git [#1010] Modified AllocateEngine to store extended lease4 info src/lib/dhcpsrv/alloc_engine.* AllocEngine::updateLease4ExtendedInfo() - new function AllocEngine::createLease4() AllocEngine::updateLease4Info() - added call to updateLease4ExtendedInfo(lease, ctx); src/lib/dhcpsrv/tests/alloc_engine4_unittest.cc TEST_F(AllocEngine4Test, updateExtendedInfo4) TEST_F(AllocEngine4Test, storeExtendedInfoEnabled4) TEST_F(AllocEngine4Test, storeExtendedInfoDisabled4) src/lib/dhcpsrv/tests/alloc_engine_utils.h NakeAllocEngine::callUpdateLease4ExtendedInfo4() --- diff --git a/src/lib/dhcpsrv/alloc_engine.cc b/src/lib/dhcpsrv/alloc_engine.cc index fca462ec94..64d062c0a0 100644 --- a/src/lib/dhcpsrv/alloc_engine.cc +++ b/src/lib/dhcpsrv/alloc_engine.cc @@ -48,6 +48,7 @@ using namespace isc::dhcp_ddns; using namespace isc::hooks; using namespace isc::stats; using namespace isc::util; +using namespace isc::data; namespace { @@ -3597,6 +3598,9 @@ AllocEngine::createLease4(const ClientContext4& ctx, const IOAddress& addr, lease->fqdn_rev_ = ctx.rev_dns_update_; lease->hostname_ = ctx.hostname_; + // Add(update) the extended information on the lease. + updateLease4ExtendedInfo(lease, ctx); + // Let's execute all callouts registered for lease4_select if (ctx.callout_handle_ && HooksManager::getHooksManager().calloutsPresent(hook_index_lease4_select_)) { @@ -4026,9 +4030,46 @@ AllocEngine::updateLease4Information(const Lease4Ptr& lease, lease->valid_lft_ = ctx.subnet_->getValid(); } } + lease->fqdn_fwd_ = ctx.fwd_dns_update_; lease->fqdn_rev_ = ctx.rev_dns_update_; lease->hostname_ = ctx.hostname_; + + // Add(update) the extended information on the lease. + updateLease4ExtendedInfo(lease, ctx); +} + +void +AllocEngine::updateLease4ExtendedInfo(const Lease4Ptr& lease, + const AllocEngine::ClientContext4& ctx) const { + // Look for relay agent information option (option 82) + OptionPtr rai = ctx.query_->getOption(DHO_DHCP_AGENT_OPTIONS); + if (!rai) { + // Pkt4 doesn't have it, so nothing to store (or update). + return; + } + + std::stringstream ss; + ss << "{" + << "\"relay-agent-info\": \"" + << rai->toHexString() + << "\"}"; + + ConstElementPtr extended_info = Element::fromJSON(ss.str()); + + // Get a writable copy of the lease's current user context. + ElementPtr user_context; + if (lease->getContext()) { + user_context = UserContext::toElement(lease->getContext()); + } else { + user_context = Element::createMap(); + } + + // Add/replace the extended info entry. + user_context->set("ISC", extended_info); + + // Update the lease's user_context. + lease->setContext(user_context); } bool diff --git a/src/lib/dhcpsrv/alloc_engine.h b/src/lib/dhcpsrv/alloc_engine.h index d18d64fcc2..509f8d36cd 100644 --- a/src/lib/dhcpsrv/alloc_engine.h +++ b/src/lib/dhcpsrv/alloc_engine.h @@ -1785,6 +1785,25 @@ private: void updateLease4Information(const Lease4Ptr& lease, ClientContext4& ctx) const; +protected: + /// @brief Stores additional client query parameters on the lease + /// + /// Extended features such as LeaseQuery require addtional parameters + /// to be stored for each lease, than we would otherwise retain. + /// This function adds that information to the lease's user-context. + /// (Note it is protected to facilitate unit testing). + /// + /// @warning This method doesn't check if the pointer to the lease is + /// valid nor if the subnet to the pointer in the @c ctx is valid. + /// The caller is responsible for making sure that they are valid. + /// + /// @param [out] lease A pointer to the lease to be updated. + /// @param ctx A context containing information from the server about the + void updateLease4ExtendedInfo(const Lease4Ptr& lease, + const ClientContext4& ctx) const; + +private: + /// @brief Extends the lease lifetime. /// /// This function is called to conditionally extend the lifetime of diff --git a/src/lib/dhcpsrv/tests/alloc_engine4_unittest.cc b/src/lib/dhcpsrv/tests/alloc_engine4_unittest.cc index d5b6bdcd2a..fc8954b70e 100644 --- a/src/lib/dhcpsrv/tests/alloc_engine4_unittest.cc +++ b/src/lib/dhcpsrv/tests/alloc_engine4_unittest.cc @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -19,6 +20,7 @@ using namespace std; using namespace isc::hooks; using namespace isc::asiolink; +using namespace isc::data; using namespace isc::stats; namespace isc { @@ -3195,6 +3197,342 @@ TEST_F(AllocEngine4Test, globalReservationDynamicRequest) { EXPECT_FALSE(ctx.old_lease_); } + +// Exercises AllocEnginer4Test::updateExtendedInfo4() through various +// permutations of client packet content. +TEST_F(AllocEngine4Test, updateExtendedInfo4) { + + // Structure that defines a test scenario. + struct Scenario { + std::string description_; // test description + std::string orig_context_json_; // user context the lease begins with + std::string rai_data_; // RAI option the client packet contains + std::string exp_context_json_; // expected user context on the lease + }; + + // Test scenarios. + std::vector scenarios { + { + "no context, no rai", + "", + "", + "" + }, + { + "some original context, no rai", + "{\"foo\": 123}", + "", + "{\"foo\": 123}" + }, + { + "no original context, rai", + "", + "0x52050104aabbccdd", + "{ \"ISC\": { \"relay-agent-info\": \"0x52050104AABBCCDD\" } }", + }, + { + "some original context, rai", + "{\"foo\": 123}", + "0x52050104aabbccdd", + "{ \"ISC\": { \"relay-agent-info\": \"0x52050104AABBCCDD\" }, \"foo\": 123 }" + }, + { + "original rai context, no rai", + "{ \"ISC\": { \"relay-agent-info\": \"0x52050104AABBCCDD\" } }", + "", + "{ \"ISC\": { \"relay-agent-info\": \"0x52050104AABBCCDD\" } }", + }, + { + "original rai context, different rai", + "{ \"ISC\": { \"relay-agent-info\": \"0x52050104AABBCCDD\" } }", + "0x52050104ddeeffaa", + "{ \"ISC\": { \"relay-agent-info\": \"0x52050104DDEEFFAA\" } }", + }, + }; + + // @todo set store-extended-info true. + + // Create the allocation engine, context and lease. + NakedAllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 0, false); + + AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_, + IOAddress::IPV4_ZERO_ADDRESS(), + false, false, "", true); + + ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234)); + Lease4Ptr lease = engine.allocateLease4(ctx); + ASSERT_TRUE(lease); + EXPECT_EQ("192.0.2.100", lease->addr_.toText()); + + // Verify that the lease begins with no user context. + ConstElementPtr user_context = lease->getContext(); + ASSERT_FALSE(user_context); + + // Iterate over the test scenarios. + ElementPtr orig_context; + ElementPtr exp_context; + for (auto scenario : scenarios) { + SCOPED_TRACE(scenario.description_); + + // Create the original user context from JSON. + if (scenario.orig_context_json_.empty()) { + orig_context.reset(); + } else { + ASSERT_NO_THROW(orig_context = Element::fromJSON(scenario.orig_context_json_)) + << "invalid orig_context_json_, test is broken"; + } + + // Create the expected user context from JSON. + if (scenario.exp_context_json_.empty()) { + exp_context.reset(); + } else { + ASSERT_NO_THROW(exp_context = Element::fromJSON(scenario.exp_context_json_)) + << "invalid exp_context_json_, test is broken"; + } + + // Initialize lease's user context. + lease->setContext(orig_context); + if (!orig_context) { + ASSERT_FALSE(lease->getContext()); + } + else { + ASSERT_TRUE(lease->getContext()); + ASSERT_TRUE(orig_context->equals(*(lease->getContext()))); + } + + // Create the client packet and the add RAI option (if one). + ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234)); + if (!scenario.rai_data_.empty()) { + std::vector opt_data; + ASSERT_NO_THROW(util::str::decodeFormattedHexString(scenario.rai_data_, opt_data)) + << "scenario.rai_data_ is invalid, test is broken"; + OptionPtr rai; + ASSERT_NO_THROW(rai.reset(new Option(Option::V4, 0x52, opt_data))) + << "could not create rai option, test is broken"; + + ctx.query_->addOption(rai); + } + + // Call AllocEngine::updateLease4ExtendeInfo(). + ASSERT_NO_THROW_LOG(engine.callUpdateLease4ExtendedInfo(lease, ctx)); + + // Verify the lease has the expected user context content. + if (!exp_context) { + ASSERT_FALSE(lease->getContext()); + } + else { + ASSERT_TRUE(lease->getContext()); + ASSERT_TRUE(exp_context->equals(*(lease->getContext()))) + << "expected: " << *(exp_context) << std::endl + << " actual: " << *(lease->getContext()) << std::endl; + } + } +} + +// Verifies that the extended data (e.g. RAI option for now) is +// added to a V4 lease when leases are created and/or renewed, +// when store-extended-info is true. +TEST_F(AllocEngine4Test, storeExtendedInfoEnabled4) { + + // Structure that defines a test scenario. + struct Scenario { + std::string description_; // test description + std::vector mac_; // MAC address + std::string rai_data_; // RAI option the client packet contains + std::string exp_context_json_; // expected user context on the lease + std::string exp_address_; // expected lease address + }; + + std::vector mac1 = { 0, 0xfe, 0xfe, 0xfe, 0xfe, 0x01}; + std::string mac1_addr = "192.0.2.100"; + + std::vector mac2 = { 0, 0xfe, 0xfe, 0xfe, 0xfe, 0x02}; + std::string mac2_addr = "192.0.2.101"; + + // @todo set store-extended-info = true + + // Test scenarios. + std::vector scenarios { + { + "create client one without rai", + mac1, + "", + "", + mac1_addr + }, + { + "renew client one without rai", + {}, + "", + "", + mac1_addr + }, + { + "create client two with rai", + mac2, + "0x52050104a1b1c1d1", + "{ \"ISC\": { \"relay-agent-info\": \"0x52050104A1B1C1D1\" } }", + mac2_addr + }, + { + "renew client two without rai", + {}, + "", + "{ \"ISC\": { \"relay-agent-info\": \"0x52050104A1B1C1D1\" } }", + mac2_addr + }, + }; + + // Create the allocation engine, context and lease. + NakedAllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 0, false); + + AllocEngine::ClientContext4 ctx(subnet_, ClientIdPtr(), hwaddr_, + IOAddress::IPV4_ZERO_ADDRESS(), + false, false, "", false); + + Lease4Ptr lease; + + // Iterate over the test scenarios. + for (auto scenario : scenarios) { + SCOPED_TRACE(scenario.description_); + + ElementPtr exp_context; + // Create the expected user context from JSON. + if (!scenario.exp_context_json_.empty()) { + ASSERT_NO_THROW(exp_context = Element::fromJSON(scenario.exp_context_json_)) + << "invalid exp_context_json_, test is broken"; + } + + // If we have a MAC address this scenario is for a new client. + if (!scenario.mac_.empty()) { + std::cout << "setting mac address" << std::endl; + ASSERT_NO_THROW(ctx.hwaddr_.reset(new HWAddr(scenario.mac_, HTYPE_ETHER))) + << "invalid MAC address, test is broken"; + } + + // Create the client packet and the add RAI option (if one). + ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234)); + if (!scenario.rai_data_.empty()) { + std::vector opt_data; + ASSERT_NO_THROW(util::str::decodeFormattedHexString(scenario.rai_data_, opt_data)) + << "scenario.rai_data_ is invalid, test is broken"; + OptionPtr rai; + ASSERT_NO_THROW(rai.reset(new Option(Option::V4, 0x52, opt_data))) + << "could not create rai option, test is broken"; + + ctx.query_->addOption(rai); + } + + // Create or renew the lease. + Lease4Ptr lease = engine.allocateLease4(ctx); + ASSERT_TRUE(lease); + EXPECT_EQ(scenario.exp_address_, lease->addr_.toText()); + + // Verify the lease has the expected user context content. + if (!exp_context) { + ASSERT_FALSE(lease->getContext()); + } + else { + ASSERT_TRUE(lease->getContext()); + ASSERT_TRUE(exp_context->equals(*(lease->getContext()))) + << "expected: " << *(exp_context) << std::endl + << " actual: " << *(lease->getContext()) << std::endl; + } + } +} + +// Verifies that the extended data (e.g. RAI option for now) is +// not added to a V4 lease when leases are created and/or renewed, +// when store-extended-info is false. +TEST_F(AllocEngine4Test, storeExtendedInfoDisabled4) { + + // Structure that defines a test scenario. + struct Scenario { + std::string description_; // test description + std::vector mac_; // MAC address + std::string rai_data_; // RAI option the client packet contains + std::string exp_address_; // expected lease address + }; + + std::vector mac1 = { 0, 0xfe, 0xfe, 0xfe, 0xfe, 0x01}; + std::string mac1_addr = "192.0.2.100"; + + std::vector mac2 = { 0, 0xfe, 0xfe, 0xfe, 0xfe, 0x02}; + std::string mac2_addr = "192.0.2.101"; + + // @todo set store-extended-info = false + + // Test scenarios. + std::vector scenarios { + { + "create client one without rai", + mac1, + "", + mac1_addr + }, + { + "renew client one without rai", + {}, + "", + mac1_addr + }, + { + "create client two with rai", + mac2, + "0x52050104a1b1c1d1", + mac2_addr + }, + { + "renew client two with rai", + {}, + "0x52050104a1b1c1d1", + mac2_addr + }, + }; + + // Create the allocation engine, context and lease. + NakedAllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 0, false); + + AllocEngine::ClientContext4 ctx(subnet_, ClientIdPtr(), hwaddr_, + IOAddress::IPV4_ZERO_ADDRESS(), + false, false, "", false); + + Lease4Ptr lease; + + // Iterate over the test scenarios. + for (auto scenario : scenarios) { + SCOPED_TRACE(scenario.description_); + + // If we have a MAC address this scenario is for a new client. + if (!scenario.mac_.empty()) { + std::cout << "setting mac address" << std::endl; + ASSERT_NO_THROW(ctx.hwaddr_.reset(new HWAddr(scenario.mac_, HTYPE_ETHER))) + << "invalid MAC address, test is broken"; + } + + // Create the client packet and the add RAI option (if one). + ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234)); + if (!scenario.rai_data_.empty()) { + std::vector opt_data; + ASSERT_NO_THROW(util::str::decodeFormattedHexString(scenario.rai_data_, opt_data)) + << "scenario.rai_data_ is invalid, test is broken"; + OptionPtr rai; + ASSERT_NO_THROW(rai.reset(new Option(Option::V4, 0x52, opt_data))) + << "could not create rai option, test is broken"; + + ctx.query_->addOption(rai); + } + + // Create or renew the lease. + Lease4Ptr lease = engine.allocateLease4(ctx); + ASSERT_TRUE(lease); + EXPECT_EQ(scenario.exp_address_, lease->addr_.toText()); + + // Verify the lease does not have user context content. + ASSERT_FALSE(lease->getContext()); + } +} + } // namespace test } // namespace dhcp } // namespace isc diff --git a/src/lib/dhcpsrv/tests/alloc_engine_utils.h b/src/lib/dhcpsrv/tests/alloc_engine_utils.h index 3a24f0520b..552f168836 100644 --- a/src/lib/dhcpsrv/tests/alloc_engine_utils.h +++ b/src/lib/dhcpsrv/tests/alloc_engine_utils.h @@ -63,6 +63,7 @@ public: using AllocEngine::Allocator; using AllocEngine::IterativeAllocator; using AllocEngine::getAllocator; + using AllocEngine::updateLease4ExtendedInfo; /// @brief IterativeAllocator with internal methods exposed class NakedIterativeAllocator: public AllocEngine::IterativeAllocator { @@ -77,6 +78,11 @@ public: using AllocEngine::IterativeAllocator::increaseAddress; using AllocEngine::IterativeAllocator::increasePrefix; }; + + void callUpdateLease4ExtendedInfo(const Lease4Ptr& lease, + AllocEngine::ClientContext4& ctx) const { + updateLease4ExtendedInfo(lease,ctx); + } }; /// @brief Used in Allocation Engine tests for IPv6