+17xx. [bug]* fdupont
+ When a host reservation in a subnet reserves an address, the
+ address must in the subnet prefix. This check was previously
+ only done by the host command hook library. Note it does not
+ applies the prefix delegation.
+ (Gitlab #1254)
+
1787. [bug] razvan
The recount leases functions consider leases in 'declined' state as
'assigned' so that when the lease is reclaimed or reused, no negative
specify a hostname, host-specific options, or fields carried within the
DHCPv4 message such as siaddr, sname, or file.
+.. note::
+
+ Beginning with Kea 1.7.11 the reserved address must be in the subnet
+ prefix.
+
The following example shows how to reserve addresses for specific hosts
in a subnet:
addresses or prefixes may also be specified, and it is possible to
specify a hostname and DHCPv6 options for a given host.
+.. note::
+
+ Beginning with Kea 1.7.11 all reserved addresses must be in the subnet
+ prefix. This does not apply to reserved prefixes.
+
The following example shows how to reserve addresses and prefixes for
specific hosts:
" {\n"
" \"hw-address\": \"aa:bb:cc:dd:ee:ff\",\n"
" \"hostname\": \"subnet-10-host\",\n"
- " \"ip-address\": \"192.0.5.10\"\n"
+ " \"ip-address\": \"10.0.0.105\"\n"
" }]\n"
" }\n"
"]\n"
"\"subnet4\": [\n"
" {\n"
" \"subnet\": \"10.0.0.0/24\", \n"
- " \"id\": 10," " \"pools\": [ { \"pool\": \"10.0.0.10-10.0.0.100\" } ],\n"
+ " \"id\": 10,"
+ " \"pools\": [ { \"pool\": \"10.0.0.10-10.0.0.100\" } ],\n"
" \"interface\": \"eth0\",\n"
" \"reservation-mode\": \"all\","
" \"reservations\": [ \n"
" {\n"
" \"hw-address\": \"aa:bb:cc:dd:ee:ff\",\n"
" \"hostname\": \"subnet-10-host\",\n"
- " \"ip-address\": \"192.0.5.10\"\n"
+ " \"ip-address\": \"10.0.0.105\"\n"
" }]\n"
" }\n"
"]\n"
// Hardware address matches all reservations
client.setHWAddress("aa:bb:cc:dd:ee:ff");
- // Subnet 10 usses default HR mode(i.e. "in-pool"), so its
+ // Subnet 10 uses "out-of-pool" HR mode, so its
// reservation should be used, rather than global.
- runDoraTest(CONFIGS[2], client, "subnet-10-host", "192.0.5.10");
+ runDoraTest(CONFIGS[2], client, "subnet-10-host", "10.0.0.105");
}
// Verifies that when there are matching reservations at
// Hardware address matches all reservations
client.setHWAddress("aa:bb:cc:dd:ee:ff");
- // Subnet 10 usses default HR mode(i.e. "in-pool"), so its
+ // Subnet 10 uses default HR mode(i.e. "all"), so its
// reservation should be used, rather than global.
- runDoraTest(CONFIGS[3], client, "subnet-10-host", "192.0.5.10");
+ runDoraTest(CONFIGS[3], client, "subnet-10-host", "10.0.0.105");
}
// Verifies that client class specified in the global reservation
#include <dhcpsrv/subnet.h>
#include <dhcpsrv/subnet_id.h>
#include <dhcpsrv/subnet_selector.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/cfg_hosts.h>
#include <stats/stats_mgr.h>
#include <testutils/gtest_utils.h>
#include <testutils/test_to_element.h>
"reclaimed-leases"));
ASSERT_FALSE(observation);
}
+
+// This test verifies that in range host reservation works as expected.
+TEST(CfgSubnets4Test, host) {
+ // Create a configuration.
+ std::string json =
+ " {"
+ " \"id\": 1,\n"
+ " \"subnet\": \"10.1.2.0/24\", \n"
+ " \"reservations\": [ {\n"
+ " \"hw-address\": \"aa:bb:cc:dd:ee:ff\", \n"
+ " \"ip-address\": \"10.1.2.1\" } ]\n"
+ " }";
+
+ data::ElementPtr elems;
+ ASSERT_NO_THROW(elems = data::Element::fromJSON(json))
+ << "invalid JSON:" << json << "\n test is broken";
+
+ Subnet4ConfigParser parser;
+ Subnet4Ptr subnet;
+ EXPECT_NO_THROW(subnet = parser.parse(elems));
+ ASSERT_TRUE(subnet);
+ CfgHostsPtr cfg_hosts = CfgMgr::instance().getStagingCfg()->getCfgHosts();
+ ASSERT_TRUE(cfg_hosts);
+ HostCollection hosts = cfg_hosts->getAll4(SubnetID(1));
+ ASSERT_EQ(1, hosts.size());
+ ConstHostPtr host = hosts[0];
+ ASSERT_TRUE(host);
+ EXPECT_EQ(1, host->getIPv4SubnetID());
+ EXPECT_EQ("hwaddr=AABBCCDDEEFF", host->getIdentifierAsText());
+ EXPECT_EQ("10.1.2.1", host->getIPv4Reservation().toText());
+
+ CfgMgr::instance().clear();
+}
+
+// This test verifies that an out of range host reservation is rejected.
+TEST(CfgSubnets4Test, outOfRangeHost) {
+ // Create a configuration.
+ std::string json =
+ " {"
+ " \"id\": 1,\n"
+ " \"subnet\": \"10.1.2.0/24\", \n"
+ " \"reservations\": [ {\n"
+ " \"hw-address\": \"aa:bb:cc:dd:ee:ff\", \n"
+ " \"ip-address\": \"10.2.2.1\" } ]\n"
+ " }";
+
+ data::ElementPtr elems;
+ ASSERT_NO_THROW(elems = data::Element::fromJSON(json))
+ << "invalid JSON:" << json << "\n test is broken";
+
+ Subnet4ConfigParser parser;
+ std::string msg = "specified reservation '10.2.2.1' is not matching ";
+ msg += "the IPv4 subnet prefix '10.1.2.0/24'";
+ EXPECT_THROW_MSG(parser.parse(elems), DhcpConfigError, msg);
+}
} // end of anonymous namespace
#include <dhcpsrv/subnet.h>
#include <dhcpsrv/subnet_id.h>
#include <dhcpsrv/subnet_selector.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/cfg_hosts.h>
#include <stats/stats_mgr.h>
#include <testutils/gtest_utils.h>
#include <testutils/test_to_element.h>
ASSERT_FALSE(observation);
}
+// This test verifies that in range host reservation works as expected.
+TEST(CfgSubnets6Test, hostNA) {
+ // Create a configuration.
+ std::string json =
+ " {"
+ " \"id\": 1,\n"
+ " \"subnet\": \"2001:db8:1::/48\", \n"
+ " \"reservations\": [ {\n"
+ " \"hw-address\": \"aa:bb:cc:dd:ee:ff\", \n"
+ " \"ip-addresses\": [\"2001:db8:1::1\"] } ]\n"
+ " }";
+
+ data::ElementPtr elems;
+ ASSERT_NO_THROW(elems = data::Element::fromJSON(json))
+ << "invalid JSON:" << json << "\n test is broken";
+
+ Subnet6ConfigParser parser;
+ Subnet6Ptr subnet;
+ EXPECT_NO_THROW(subnet = parser.parse(elems));
+ ASSERT_TRUE(subnet);
+ CfgHostsPtr cfg_hosts = CfgMgr::instance().getStagingCfg()->getCfgHosts();
+ ASSERT_TRUE(cfg_hosts);
+ HostCollection hosts = cfg_hosts->getAll6(SubnetID(1));
+ ASSERT_EQ(1, hosts.size());
+ ConstHostPtr host = hosts[0];
+ ASSERT_TRUE(host);
+ EXPECT_EQ(1, host->getIPv6SubnetID());
+ EXPECT_EQ("hwaddr=AABBCCDDEEFF", host->getIdentifierAsText());
+ IPv6ResrvRange addresses = host->getIPv6Reservations(IPv6Resrv::TYPE_NA);
+ ASSERT_EQ(1, std::distance(addresses.first, addresses.second));
+ EXPECT_EQ("2001:db8:1::1", addresses.first->second.getPrefix().toText());
+
+ CfgMgr::instance().clear();
+}
+
+// This test verifies that an out of range host reservation is rejected.
+TEST(CfgSubnets6Test, outOfRangeHost) {
+ // Create a configuration.
+ std::string json =
+ " {"
+ " \"id\": 1,\n"
+ " \"subnet\": \"2001:db8:1::/48\", \n"
+ " \"reservations\": [ {\n"
+ " \"hw-address\": \"aa:bb:cc:dd:ee:ff\", \n"
+ " \"ip-addresses\": [\"2001:db8:1::1\", \n"
+ " \"2001:db8:2::1\"] } ]\n"
+ " }";
+
+ data::ElementPtr elems;
+ ASSERT_NO_THROW(elems = data::Element::fromJSON(json))
+ << "invalid JSON:" << json << "\n test is broken";
+
+ Subnet6ConfigParser parser;
+ std::string msg = "specified reservation '2001:db8:2::1' is ";
+ msg += "not matching the IPv6 subnet prefix '2001:db8:1::/48'";
+ EXPECT_THROW_MSG(parser.parse(elems), DhcpConfigError, msg);
+}
+
+// This test verifies that in range validation does not applies to prefixes.
+TEST(CfgSubnets6Test, hostPD) {
+ // Create a configuration.
+ std::string json =
+ " {"
+ " \"id\": 1,\n"
+ " \"subnet\": \"2001:db8:1::/48\", \n"
+ " \"reservations\": [ {\n"
+ " \"hw-address\": \"aa:bb:cc:dd:ee:ff\", \n"
+ " \"prefixes\": [\"2001:db8:2::/64\"] } ]\n"
+ " }";
+
+ data::ElementPtr elems;
+ ASSERT_NO_THROW(elems = data::Element::fromJSON(json))
+ << "invalid JSON:" << json << "\n test is broken";
+
+ Subnet6ConfigParser parser;
+ Subnet6Ptr subnet;
+ try {
+ subnet = parser.parse(elems);
+ } catch (const std::exception& ex) {
+ std::cerr << "parse fail with " << ex.what() << std::endl;
+ }
+ //EXPECT_NO_THROW(subnet = parser.parse(elems));
+ ASSERT_TRUE(subnet);
+ CfgHostsPtr cfg_hosts = CfgMgr::instance().getStagingCfg()->getCfgHosts();
+ ASSERT_TRUE(cfg_hosts);
+ HostCollection hosts = cfg_hosts->getAll6(SubnetID(1));
+ ASSERT_EQ(1, hosts.size());
+ ConstHostPtr host = hosts[0];
+ ASSERT_TRUE(host);
+ EXPECT_EQ(1, host->getIPv6SubnetID());
+ EXPECT_EQ("hwaddr=AABBCCDDEEFF", host->getIdentifierAsText());
+ IPv6ResrvRange prefixes = host->getIPv6Reservations(IPv6Resrv::TYPE_PD);
+ ASSERT_EQ(1, std::distance(prefixes.first, prefixes.second));
+ EXPECT_EQ("2001:db8:2::", prefixes.first->second.getPrefix().toText());
+ EXPECT_EQ(64, prefixes.first->second.getPrefixLen());
+
+ // Verify the prefix is not in the subnet range.
+ EXPECT_FALSE(subnet->inRange(prefixes.first->second.getPrefix()));
+
+ CfgMgr::instance().clear();
+}
+
} // end of anonymous namespace