]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#1010] Modified AllocateEngine to store extended lease4 info
authorThomas Markwalder <tmark@isc.org>
Thu, 26 Mar 2020 18:30:26 +0000 (14:30 -0400)
committerFrancis Dupont <fdupont@isc.org>
Thu, 2 Apr 2020 19:06:01 +0000 (21:06 +0200)
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()

src/lib/dhcpsrv/alloc_engine.cc
src/lib/dhcpsrv/alloc_engine.h
src/lib/dhcpsrv/tests/alloc_engine4_unittest.cc
src/lib/dhcpsrv/tests/alloc_engine_utils.h

index fca462ec94db7abdec2d6c61115b2463baec7db6..64d062c0a0d2fab63dc6cc110b0826c5d977cf93 100644 (file)
@@ -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
index d18d64fcc21ae29d61f3490742385d97999b82e7..509f8d36cd627fb87af79827026b2a7031bef5bd 100644 (file)
@@ -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
index d5b6bdcd2a40a69075cd8448b06dd94f84edf76f..fc8954b70efa7152f48b3f7e1012ae1856025dab 100644 (file)
@@ -12,6 +12,7 @@
 #include <dhcpsrv/host_mgr.h>
 #include <dhcpsrv/tests/alloc_engine_utils.h>
 #include <dhcpsrv/tests/test_utils.h>
+#include <testutils/gtest_utils.h>
 #include <hooks/hooks_manager.h>
 #include <hooks/callout_handle.h>
 #include <stats/stats_mgr.h>
@@ -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<Scenario> 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<uint8_t> 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<uint8_t> 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<uint8_t> mac1 = { 0, 0xfe, 0xfe, 0xfe, 0xfe, 0x01};
+    std::string mac1_addr = "192.0.2.100";
+
+    std::vector<uint8_t> 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<Scenario> 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<uint8_t> 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<uint8_t> mac_;      // MAC address
+        std::string rai_data_;          // RAI option the client packet contains
+        std::string exp_address_;       // expected lease address
+    };
+
+    std::vector<uint8_t> mac1 = { 0, 0xfe, 0xfe, 0xfe, 0xfe, 0x01};
+    std::string mac1_addr = "192.0.2.100";
+
+    std::vector<uint8_t> 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<Scenario> 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<uint8_t> 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
index 3a24f0520b4621ba774a87176a97daff123d8a75..552f1688361695ce00214a11df8436021c95a902 100644 (file)
@@ -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