]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[5705] AllocEngine4 now matches clients to global HRs
authorThomas Markwalder <tmark@isc.org>
Fri, 10 Aug 2018 12:56:21 +0000 (08:56 -0400)
committerThomas Markwalder <tmark@isc.org>
Tue, 14 Aug 2018 13:43:04 +0000 (09:43 -0400)
src/bin/dhcp4/tests/Makefile.am
src/bin/dhcp4/tests/host_unittest.cc - new file with global HR tests

src/lib/dhcpsrv/alloc_engine.*
    findGlobalReservation() - new function that searches for global HR

    findReservation() - calls findGlobalReservation() if mode is global

    hasAddressReservation()
    ClientContext4::currentHost()
        - modified to recognize global HRs

src/lib/dhcpsrv/network.h
    Added new mode, Netork::HR_GLOBAL

src/lib/dhcpsrv/tests/alloc_engine4_unittest.cc
    TEST_F(AllocEngine4Test, globalReservationReservedAddressDiscover)
    TEST_F(AllocEngine4Test, globalReservationReservedAddressRequest)
    TEST_F(AllocEngine4Test, globalReservationDynamicDiscover)
    TEST_F(AllocEngine4Test, globalReservationDynamicRequest) - new tests

src/bin/dhcp4/tests/Makefile.am
src/bin/dhcp4/tests/host_unittest.cc [new file with mode: 0644]
src/lib/dhcpsrv/alloc_engine.cc
src/lib/dhcpsrv/alloc_engine.h
src/lib/dhcpsrv/network.h
src/lib/dhcpsrv/tests/alloc_engine4_unittest.cc

index 0c31643ae6d682075ad1c0fa32f8694c7a0b584e..d9d58779d9164df62a461edf8f318f1134380d6b 100644 (file)
@@ -102,6 +102,7 @@ dhcp4_unittests_SOURCES += dhcp4to6_ipc_unittest.cc
 dhcp4_unittests_SOURCES += simple_parser4_unittest.cc
 dhcp4_unittests_SOURCES += get_config_unittest.cc get_config_unittest.h
 dhcp4_unittests_SOURCES += shared_network_unittest.cc
+dhcp4_unittests_SOURCES += host_unittest.cc
 
 nodist_dhcp4_unittests_SOURCES = marker_file.h test_libraries.h
 
diff --git a/src/bin/dhcp4/tests/host_unittest.cc b/src/bin/dhcp4/tests/host_unittest.cc
new file mode 100644 (file)
index 0000000..d05f229
--- /dev/null
@@ -0,0 +1,155 @@
+// Copyright (C) 2018 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+#include <asiolink/io_address.h>
+#include <cc/data.h>
+#include <dhcp/dhcp4.h>
+#include <dhcp/tests/iface_mgr_test_config.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/host.h>
+#include <dhcpsrv/host_mgr.h>
+#include <dhcpsrv/subnet_id.h>
+#include <dhcpsrv/testutils/cql_schema.h>
+#include <dhcpsrv/testutils/mysql_schema.h>
+#include <dhcpsrv/testutils/pgsql_schema.h>
+#include <dhcp4/tests/dhcp4_test_utils.h>
+#include <dhcp4/tests/dhcp4_client.h>
+#include <boost/shared_ptr.hpp>
+#include <stats/stats_mgr.h>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::data;
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+
+namespace {
+
+/// @brief Set of JSON configuration(s) used throughout the Host tests.
+///
+/// - Configuration 0:
+///   - Used for testing global host reservations 
+///   - 5 global reservations 
+///   - 1 subnet: 10.0.0.0/24
+const char* CONFIGS[] = {
+// Configuration 4
+    "{ \"interfaces-config\": {\n"
+        "      \"interfaces\": [ \"*\" ]\n"
+        "},\n"
+        "\"host-reservation-identifiers\": [ \"circuit-id\", \"hw-address\",\n"
+        "                                    \"duid\", \"client-id\" ],\n"
+        "\"reservations\": [ \n"
+        "{\n"
+        "   \"hw-address\": \"aa:bb:cc:dd:ee:ff\",\n"
+        "   \"hostname\": \"hw-host-dynamic\"\n"
+        "},\n"
+        "{\n"
+        "   \"hw-address\": \"01:02:03:04:05:06\",\n"
+        "   \"hostname\": \"hw-host-fixed\",\n"
+        "   \"ip-address\": \"10.0.0.7\"\n"
+        "},\n"
+        "{\n"
+        "   \"duid\": \"01:02:03:04:05\",\n"
+        "   \"hostname\": \"duid-host\"\n"
+        "},\n"
+        "{\n"
+        "   \"circuit-id\": \"'charter950'\",\n"
+        "   \"hostname\": \"circuit-id-host\"\n"
+        "},\n"
+        "{\n"
+        "   \"client-id\": \"01:11:22:33:44:55:66\",\n"
+        "   \"hostname\": \"client-id-host\"\n"
+        "}\n"
+        "],\n"
+        "\"valid-lifetime\": 600,\n"
+        "\"subnet4\": [ { \n"
+        "    \"subnet\": \"10.0.0.0/24\", \n"
+        "    \"pools\": [ { \"pool\": \"10.0.0.10-10.0.0.100\" } ]\n"
+        "} ]\n"
+    "}\n"
+};
+
+/// @brief Test fixture class for testing v4 exchanges.
+class HostTest : public Dhcpv4SrvTest {
+public:
+
+    /// @brief Constructor.
+    ///
+    /// Sets up fake interfaces.
+    HostTest()
+        : Dhcpv4SrvTest(),
+          iface_mgr_test_config_(true) {
+        IfaceMgr::instance().openSockets4();
+
+        // Let's wipe all existing statistics.
+        isc::stats::StatsMgr::instance().removeAll();
+    }
+
+    /// @brief Destructor.
+    ///
+    /// Cleans up statistics after the test.
+    ~HostTest() {
+        // Let's wipe all existing statistics.
+        isc::stats::StatsMgr::instance().removeAll();
+    }
+
+    /// @brief Interface Manager's fake configuration control.
+    IfaceMgrTestConfig iface_mgr_test_config_;
+
+    void runDoraTest(const std::string& config, Dhcp4Client& client, 
+                     const std::string& expected_host, 
+                     const std::string& expected_addr) {
+
+        // Configure DHCP server.
+        configure(config, *client.getServer());
+        client.requestOptions(DHO_HOST_NAME);
+
+        // Perform 4-way exchange with the server but to not request any
+        // specific address in the DHCPDISCOVER message.
+        ASSERT_NO_THROW(client.doDORA());
+
+        // Make sure that the server responded.
+        ASSERT_TRUE(client.getContext().response_);
+        Pkt4Ptr resp = client.getContext().response_;
+
+        // Make sure that the server has responded with DHCPACK.
+        ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+
+        // Fetch the hostname option
+        OptionStringPtr hostname = boost::dynamic_pointer_cast<
+                                   OptionString>(resp->getOption(DHO_HOST_NAME));
+
+        if (expected_host.empty()) {
+            ASSERT_FALSE(hostname);
+        } else {
+            ASSERT_TRUE(hostname);
+            EXPECT_EQ(expected_host, hostname->getValue());
+        }
+
+        EXPECT_EQ(client.config_.lease_.addr_.toText(), expected_addr);
+    }
+
+    
+};
+
+TEST_F(HostTest, globalHardwareDynamicAddress) {
+    Dhcp4Client client(Dhcp4Client::SELECTING);
+
+    client.setHWAddress("aa:bb:cc:dd:ee:ff");
+    runDoraTest(CONFIGS[0], client, "hw-host-dynamic", "10.0.0.10");
+}
+
+
+TEST_F(HostTest, globalHardwareFixedAddress) {
+    Dhcp4Client client(Dhcp4Client::SELECTING);
+
+    //client.includeClientId(clientid_a);
+    client.setHWAddress("01:02:03:04:05:06");
+    runDoraTest(CONFIGS[0], client, "hw-host-fixed", "10.0.0.7");
+}
+
+} // end of anonymous namespace
index c48f3857f54959a485f30503e796cd0752234082..90b9058de4f0fd0400938b48655940d65517cda8 100644 (file)
@@ -2628,6 +2628,14 @@ hasAddressReservation(AllocEngine::ClientContext4& ctx) {
 
     Subnet4Ptr subnet = ctx.subnet_;
     while (subnet) {
+        if (subnet->getHostReservationMode() == Network::HR_GLOBAL) {
+            auto host = ctx.hosts_.find(SUBNET_ID_GLOBAL);
+            return (host != ctx.hosts_.end() &&
+                    !(host->second->getIPv4Reservation().isV4Zero()));
+            // if we want global + other modes we would need to
+            // return only if true, else continue
+        }
+
         auto host = ctx.hosts_.find(subnet->getID());
         if ((host != ctx.hosts_.end()) &&
             !(host->second->getIPv4Reservation().isV4Zero()) &&
@@ -2811,7 +2819,10 @@ AllocEngine::ClientContext4::ClientContext4(const Subnet4Ptr& subnet,
 ConstHostPtr
 AllocEngine::ClientContext4::currentHost() const {
     if (subnet_) {
-        auto host = hosts_.find(subnet_->getID());
+        SubnetID id = (subnet_->getHostReservationMode() == Network::HR_GLOBAL ?
+                       SUBNET_ID_GLOBAL : subnet_->getID());
+
+        auto host = hosts_.find(id);
         if (host != hosts_.cend()) {
             return (host->second);
         }
@@ -2878,6 +2889,17 @@ AllocEngine::findReservation(ClientContext4& ctx) {
     SharedNetwork4Ptr network;
     subnet->getSharedNetwork(network);
 
+    if (subnet->getHostReservationMode() == Network::HR_GLOBAL) {
+        ConstHostPtr ghost = findGlobalReservation(ctx);
+        if (ghost) {
+            ctx.hosts_[SUBNET_ID_GLOBAL] = ghost;
+
+            // @todo In theory, to support global as part of HR_ALL,
+            //  we would just keep going, instead of returning.
+            return;
+        }
+    }
+
     // If the subnet belongs to a shared network it is usually going to be
     // more efficient to make a query for all reservations for a particular
     // client rather than a query for each subnet within this shared network.
@@ -2942,6 +2964,24 @@ AllocEngine::findReservation(ClientContext4& ctx) {
     }
 }
 
+ConstHostPtr
+AllocEngine::findGlobalReservation(ClientContext4& ctx) {
+    ConstHostPtr host;
+    BOOST_FOREACH(const IdentifierPair& id_pair, ctx.host_identifiers_) {
+        // Attempt to find a host using a specified identifier.
+        host = HostMgr::instance().get4(SUBNET_ID_GLOBAL, id_pair.first,
+                                        &id_pair.second[0], id_pair.second.size());
+
+        // If we found matching global host we're done.
+        if (host) {
+            break;
+        }
+    }
+
+    return (host);
+}
+
+
 Lease4Ptr
 AllocEngine::discoverLease4(AllocEngine::ClientContext4& ctx) {
     // Find an existing lease for this client. This function will return true
index 3a64eb91a4b868debd89cde6aa4d5c53dea9e68d..198e32c9cd93787eb1b7c78f16190abd30dd98aa 100644 (file)
@@ -1299,6 +1299,17 @@ public:
     /// @param ctx Client context holding various information about the client.
     static void findReservation(ClientContext4& ctx);
 
+    /// @brief Attempts to find the host reservation for the client.
+    ///
+    /// This method attempts to find a "global" host reservation matching the
+    /// client identifier.  It will return the first global reservation that matches
+    /// per the configured list of host identifiers, or an empty pointer if no
+    /// matches are found.
+    ///
+    /// @param ctx Client context holding various information about the client.
+    /// @return Pointer to the reservation found, or an empty pointer.
+    static ConstHostPtr findGlobalReservation(ClientContext4& ctx);
+
 private:
 
     /// @brief Offers the lease.
index 5b51732b4b6e72e0b479d5a8efbf160039cecd7d..ce253cb7580a0097a7fd91c54c15f23dc9a9ccb0 100644 (file)
@@ -96,11 +96,16 @@ public:
         /// dealing with with addresses that are in pool.
         HR_OUT_OF_POOL,
 
+        /// Only global reservations are allowed. This mode
+        /// instructs AllocEngine to only look at global reservations.
+        HR_GLOBAL,
+
         /// Both out-of-pool and in-pool reservations are allowed. This is the
         /// most flexible mode, where sysadmin have biggest liberty. However,
         /// there is a non-trivial performance penalty for it, as the
         /// AllocEngine code has to check whether there are reservations, even
         /// when dealing with reservations from within the dynamic pools.
+        /// @todo - should ALL include global?
         HR_ALL
     } HRMode;
 
index 6d8e2776a661ce2105e6b101900b421a05cc13ce..a898717244453f66dc1c394584bb692f613e05cb 100644 (file)
@@ -2486,6 +2486,207 @@ TEST_F(AllocEngine4Test, reservedAddressExistingLeaseStat) {
     EXPECT_FALSE(ctx.fake_allocation_);
 }
 
+// This test checks the behavior of the allocation engine in the following
+// scenario:
+// - Client has no lease in the database.
+// - Client has a global reservation.
+// - Client sends DISCOVER
+// - Client is allocated the reserved address.
+// - Lease is not added to the lease database
+TEST_F(AllocEngine4Test, globalReservationReservedAddressDiscover) {
+    // Create reservation for the client.
+    HostPtr host(new Host(&hwaddr_->hwaddr_[0], hwaddr_->hwaddr_.size(),
+                          Host::IDENT_HWADDR, SUBNET_ID_GLOBAL,
+                          SUBNET_ID_UNUSED, IOAddress("192.0.77.123")));
+    CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
+    CfgMgr::instance().commit();
+
+    AllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 0, false);
+
+    subnet_->setHostReservationMode(Network::HR_GLOBAL);
+
+    // Query allocation engine for the lease to be assigned to this
+    // client without specifying the address to be assigned.
+    AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_,
+                                    IOAddress("0.0.0.0"), false, false,
+                                    "", true);
+    ctx.query_.reset(new Pkt4(DHCPDISCOVER, 1234));
+
+    // Look up the host.
+    AllocEngine::findReservation(ctx);
+
+    // We should have the correct current host
+    EXPECT_TRUE(ctx.currentHost());
+    EXPECT_EQ(ctx.currentHost()->getHostname(), host->getHostname());
+    EXPECT_EQ(ctx.currentHost()->getIPv4Reservation(), host->getIPv4Reservation());
+
+    // We should allocate the reserverd address.
+    Lease4Ptr lease = engine.allocateLease4(ctx);
+    ASSERT_TRUE(lease);
+    EXPECT_EQ("192.0.77.123", lease->addr_.toText());
+
+    // This is a "fake" allocation so the returned lease should not be committed
+    // to the lease database.
+    EXPECT_FALSE(LeaseMgrFactory::instance().getLease4(lease->addr_));
+
+    // Client had no lease in the database, so the old lease returned should
+    // be NULL.
+    EXPECT_FALSE(ctx.old_lease_);
+}
+
+// This test checks the behavior of the allocation engine in the following
+// scenario:
+// - Client has no lease in the database.
+// - Client has a global reservation.
+// - Client sends REQUEST
+// - Client is allocated the reserved address.
+// - Lease is added to the lease database
+TEST_F(AllocEngine4Test, globalReservationReservedAddressRequest) {
+    // Create reservation for the client.
+    HostPtr host(new Host(&hwaddr_->hwaddr_[0], hwaddr_->hwaddr_.size(),
+                          Host::IDENT_HWADDR, SUBNET_ID_GLOBAL,
+                          SUBNET_ID_UNUSED, IOAddress("192.0.77.123")));
+    CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
+    CfgMgr::instance().commit();
+
+    AllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 0, false);
+
+    subnet_->setHostReservationMode(Network::HR_GLOBAL);
+
+    // Query allocation engine for the lease to be assigned to this
+    // client without specifying the address to be assigned.
+    AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_,
+                                    IOAddress("0.0.0.0"), false, false,
+                                    "", false);
+    ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234));
+
+    // Look up the host.
+    AllocEngine::findReservation(ctx);
+
+    // We should have the correct current host
+    EXPECT_TRUE(ctx.currentHost());
+    EXPECT_EQ(ctx.currentHost()->getHostname(), host->getHostname());
+    EXPECT_EQ(ctx.currentHost()->getIPv4Reservation(), host->getIPv4Reservation());
+
+    // We should allocate the reserverd address.
+    Lease4Ptr lease = engine.allocateLease4(ctx);
+    ASSERT_TRUE(lease);
+    EXPECT_EQ("192.0.77.123", lease->addr_.toText());
+
+    // Check that the lease is indeed in LeaseMgr
+    Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_);
+    ASSERT_TRUE(from_mgr);
+
+    // Now check that the lease in LeaseMgr has the same parameters
+    detailCompareLease(lease, from_mgr);
+
+    // Client had no lease in the database, so the old lease returned should
+    // be NULL.
+    EXPECT_FALSE(ctx.old_lease_);
+}
+
+
+// This test checks the behavior of the allocation engine in the following
+// scenario:
+// - Client has no lease in the database.
+// - Client has a global reservation.
+// - Client sends DISCOVER
+// - Client is allocated a dynamic address from matched subnet
+// - Lease is not added to the lease database
+TEST_F(AllocEngine4Test, globalReservationDynamicDiscover) {
+    // Create reservation for the client.
+    HostPtr host(new Host(&hwaddr_->hwaddr_[0], hwaddr_->hwaddr_.size(),
+                          Host::IDENT_HWADDR, SUBNET_ID_GLOBAL,
+                          SUBNET_ID_UNUSED, IOAddress::IPV4_ZERO_ADDRESS(),
+                          "foo.example.org"));
+    CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
+    CfgMgr::instance().commit();
+
+    AllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 0, false);
+
+    subnet_->setHostReservationMode(Network::HR_GLOBAL);
+
+    // Query allocation engine for the lease to be assigned to this
+    // client without specifying the address to be assigned.
+    AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_,
+                                    IOAddress("0.0.0.0"), false, false,
+                                    "", true);
+    ctx.query_.reset(new Pkt4(DHCPDISCOVER, 1234));
+
+    // Look up the host.
+    AllocEngine::findReservation(ctx);
+
+    // We should have the correct current host
+    EXPECT_TRUE(ctx.currentHost());
+    EXPECT_EQ(ctx.currentHost()->getHostname(), host->getHostname());
+    EXPECT_EQ(ctx.currentHost()->getIPv4Reservation(), host->getIPv4Reservation());
+
+    // We should allocate a dynamic address.
+    Lease4Ptr lease = engine.allocateLease4(ctx);
+    ASSERT_TRUE(lease);
+    EXPECT_EQ("192.0.2.100", lease->addr_.toText());
+
+    // This is a "fake" allocation so the returned lease should not be committed
+    // to the lease database.
+    EXPECT_FALSE(LeaseMgrFactory::instance().getLease4(lease->addr_));
+
+    // Client had no lease in the database, so the old lease returned should
+    // be NULL.
+    EXPECT_FALSE(ctx.old_lease_);
+}
+
+// This test checks the behavior of the allocation engine in the following
+// scenario:
+// - Client has no lease in the database.
+// - Client has a global reservation.
+// - Client sends REQUEST
+// - Client is allocated a dynamic address from matched subnet
+// - Lease is added to the lease database
+TEST_F(AllocEngine4Test, globalReservationDynamicRequest) {
+    // Create reservation for the client.
+    HostPtr host(new Host(&hwaddr_->hwaddr_[0], hwaddr_->hwaddr_.size(),
+                          Host::IDENT_HWADDR, SUBNET_ID_GLOBAL,
+                          SUBNET_ID_UNUSED, IOAddress::IPV4_ZERO_ADDRESS(),
+                          "foo.example.org"));
+    CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
+    CfgMgr::instance().commit();
+
+    AllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 0, false);
+
+    subnet_->setHostReservationMode(Network::HR_GLOBAL);
+
+    // Query allocation engine for the lease to be assigned to this
+    // client without specifying the address to be assigned.
+    AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_,
+                                    IOAddress("0.0.0.0"), false, false,
+                                    "", false);
+    ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234));
+
+    // Look up the host.
+    AllocEngine::findReservation(ctx);
+
+    // We should have the correct current host
+    EXPECT_TRUE(ctx.currentHost());
+    EXPECT_EQ(ctx.currentHost()->getHostname(), host->getHostname());
+    EXPECT_EQ(ctx.currentHost()->getIPv4Reservation(), host->getIPv4Reservation());
+
+    // We should allocate a dynamic address.
+    Lease4Ptr lease = engine.allocateLease4(ctx);
+    ASSERT_TRUE(lease);
+    EXPECT_EQ("192.0.2.100", lease->addr_.toText());
+
+    // Check that the lease is indeed in LeaseMgr
+    Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_);
+    ASSERT_TRUE(from_mgr);
+
+    // Now check that the lease in LeaseMgr has the same parameters
+    detailCompareLease(lease, from_mgr);
+
+    // Client had no lease in the database, so the old lease returned should
+    // be NULL.
+    EXPECT_FALSE(ctx.old_lease_);
+}
+
 }; // namespace test
 }; // namespace dhcp
 }; // namespace isc