--- /dev/null
+// 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
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()) &&
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);
}
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.
}
}
+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
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