ConstElementPtr json;
ASSERT_NO_THROW(json = parseDHCP4(config));
+ extractConfig(config);
EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
checkResult(x, 0);
EXPECT_TRUE (subnets->at(3)->clientSupported(classes));
}
+// Goal of this test is to verify that multiple pools can be configured
+// with defined client classes.
+TEST_F(Dhcp4ParserTest, classifyPools) {
+ ConstElementPtr x;
+ string config = "{ " + genIfaceConfig() + "," +
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet4\": [ { "
+ " \"pools\": [ { "
+ " \"pool\": \"192.0.2.1 - 192.0.2.100\", "
+ " \"client-class\": \"alpha\" "
+ " },"
+ " {"
+ " \"pool\": \"192.0.3.101 - 192.0.3.150\", "
+ " \"client-class\": \"beta\" "
+ " },"
+ " {"
+ " \"pool\": \"192.0.4.101 - 192.0.4.150\", "
+ " \"client-class\": \"gamma\" "
+ " },"
+ " {"
+ " \"pool\": \"192.0.5.101 - 192.0.5.150\" "
+ " } ],"
+ " \"subnet\": \"192.0.0.0/16\" "
+ " } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP4(config, true));
+ extractConfig(config);
+
+ EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
+ checkResult(x, 0);
+
+ const Subnet4Collection* subnets =
+ CfgMgr::instance().getStagingCfg()->getCfgSubnets4()->getAll();
+ ASSERT_TRUE(subnets);
+ ASSERT_EQ(1, subnets->size());
+ const PoolCollection& pools = subnets->at(0)->getPools(Lease::TYPE_V4);
+ ASSERT_EQ(4, pools.size()); // We expect 4 pools
+
+ // Let's check if client belonging to alpha class is supported in pool[0]
+ // and not supported in any other pool (except pool[3], which allows
+ // everyone).
+ ClientClasses classes;
+ classes.insert("alpha");
+ EXPECT_TRUE(pools.at(0)->clientSupported(classes));
+ EXPECT_FALSE(pools.at(1)->clientSupported(classes));
+ EXPECT_FALSE(pools.at(2)->clientSupported(classes));
+ EXPECT_TRUE(pools.at(3)->clientSupported(classes));
+
+ // Let's check if client belonging to beta class is supported in pool[1]
+ // and not supported in any other pool (except pools[3], which allows
+ // everyone).
+ classes.clear();
+ classes.insert("beta");
+ EXPECT_FALSE(pools.at(0)->clientSupported(classes));
+ EXPECT_TRUE(pools.at(1)->clientSupported(classes));
+ EXPECT_FALSE(pools.at(2)->clientSupported(classes));
+ EXPECT_TRUE(pools.at(3)->clientSupported(classes));
+
+ // Let's check if client belonging to gamma class is supported in pool[2]
+ // and not supported in any other pool (except pool[3], which allows
+ // everyone).
+ classes.clear();
+ classes.insert("gamma");
+ EXPECT_FALSE(pools.at(0)->clientSupported(classes));
+ EXPECT_FALSE(pools.at(1)->clientSupported(classes));
+ EXPECT_TRUE(pools.at(2)->clientSupported(classes));
+ EXPECT_TRUE(pools.at(3)->clientSupported(classes));
+
+ // Let's check if client belonging to some other class (not mentioned in
+ // the config) is supported only in pool[3], which allows everyone.
+ classes.clear();
+ classes.insert("delta");
+ EXPECT_FALSE(pools.at(0)->clientSupported(classes));
+ EXPECT_FALSE(pools.at(1)->clientSupported(classes));
+ EXPECT_FALSE(pools.at(2)->clientSupported(classes));
+ EXPECT_TRUE(pools.at(3)->clientSupported(classes));
+
+ // Finally, let's check class-less client. He should be allowed only in
+ // the last pool, which does not have any class restrictions.
+ classes.clear();
+ EXPECT_FALSE(pools.at(0)->clientSupported(classes));
+ EXPECT_FALSE(pools.at(1)->clientSupported(classes));
+ EXPECT_FALSE(pools.at(2)->clientSupported(classes));
+ EXPECT_TRUE(pools.at(3)->clientSupported(classes));
+}
+
// This test verifies that the host reservations can be specified for
// respective IPv4 subnets.
TEST_F(Dhcp4ParserTest, reservations) {
EXPECT_TRUE(srv_.selectSubnet(dis));
}
+// Checks if the client-class field is indeed used for pool selection.
+TEST_F(Dhcpv4SrvTest, clientPoolClassify) {
+ IfaceMgrTestConfig test_config(true);
+ IfaceMgr::instance().openSockets4();
+
+ NakedDhcpv4Srv srv(0);
+
+ // This test configures 2 pools.
+ // The second pool does not play any role here. The client's
+ // IP address belongs to the first pool, so only that first
+ // pool is being tested.
+ string config = "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet4\": [ "
+ "{ \"pools\": [ { "
+ " \"pool\": \"192.0.2.1 - 192.0.2.100\", "
+ " \"client-class\": \"foo\" }, "
+ " { \"pool\": \"192.0.3.1 - 192.0.3.100\", "
+ " \"client-class\": \"xyzzy\" } ], "
+ " \"subnet\": \"192.0.0.0/16\" } "
+ "],"
+ "\"valid-lifetime\": 4000 }";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP4(config, true));
+
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp4Server(srv, json));
+
+ CfgMgr::instance().commit();
+
+ // check if returned status is OK
+ ASSERT_TRUE(status);
+ comment_ = config::parseAnswer(rcode_, status);
+ ASSERT_EQ(0, rcode_);
+
+ // Create a simple packet that we'll use for classification
+ Pkt4Ptr dis = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 1234));
+ dis->setRemoteAddr(IOAddress("192.0.2.1"));
+ dis->setCiaddr(IOAddress("192.0.2.1"));
+ dis->setIface("eth0");
+ OptionPtr clientid = generateClientId();
+ dis->addOption(clientid);
+
+ // This discover does not belong to foo class, so it will not
+ // be serviced
+ Pkt4Ptr offer = srv.processDiscover(dis);
+ EXPECT_FALSE(offer);
+
+ // Let's add the packet to bar class and try again.
+ dis->addClass("bar");
+
+ // Still not supported, because it belongs to wrong class.
+ offer = srv.processDiscover(dis);
+ EXPECT_FALSE(offer);
+
+ // Let's add it to matching class.
+ dis->addClass("foo");
+
+ // This time it should work
+ offer = srv.processDiscover(dis);
+ ASSERT_TRUE(offer);
+ EXPECT_EQ(DHCPOFFER, offer->getType());
+ EXPECT_FALSE(offer->getYiaddr().isV4Zero());
+}
+
// Verifies last resort option 43 is backward compatible
TEST_F(Dhcpv4SrvTest, option43LastResort) {
IfaceMgrTestConfig test_config(true);
// Configuration #13.
// - 2 classes
-// - 2 shared networks, each with 1 subnet and client class restricton
+// - 2 shared networks, each with 1 subnet and client class restriction
"{"
" \"interfaces-config\": {"
" \"interfaces\": [ \"*\" ]"
" ]"
" }"
" ]"
+ "}",
+
+// Configuration #16
+// - 1 shared network with 1 subnet and 2 pools and client class restriction
+ "{"
+ " \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ " },"
+ " \"client-classes\": ["
+ " {"
+ " \"name\": \"a-devices\","
+ " \"test\": \"option[93].hex == 0x0001\""
+ " }"
+ " ],"
+ " \"valid-lifetime\": 600,"
+ " \"shared-networks\": ["
+ " {"
+ " \"name\": \"frog\","
+ " \"interface\": \"eth1\","
+ " \"subnet4\": ["
+ " {"
+ " \"subnet\": \"192.0.2.0/24\","
+ " \"id\": 10,"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"192.0.2.1 - 192.0.2.63\","
+ " \"client-class\": \"a-devices\""
+ " },"
+ " {"
+ " \"pool\": \"192.0.2.100 - 192.0.2.100\""
+ " }"
+ " ]"
+ " }"
+ " ]"
+ " }"
+ " ]"
"}"
};
EXPECT_EQ("2.3.4.5", client2.config_.serverid_.toText());
}
+// Access to a pool within shared network is restricted by client
+// classification.
+TEST_F(Dhcpv4SharedNetworkTest, poolInSharedNetworkSelectedByClass) {
+ // Create client #1
+ Dhcp4Client client1(Dhcp4Client::SELECTING);
+ client1.setIfaceName("eth1");
+
+ // Configure the server with one shared network including one subnet and
+ // in 2 pools in it. The access to one of the pools is restricted
+ // by client classification.
+ configure(NETWORKS_CONFIG[16], *client1.getServer());
+
+ // Client #1 requests an address in the restricted pool but can't be assigned
+ // this address because the client doesn't belong to a certain class.
+ testAssigned([this, &client1] {
+ doDORA(client1, "192.0.2.100", "192.0.2.63");
+ });
+
+ // Release the lease that the client has got, because we'll need this address
+ // further in the test.
+ testAssigned([this, &client1] {
+ ASSERT_NO_THROW(client1.doRelease());
+ });
+
+ // Add option93 which would cause the client to be classified as "a-devices".
+ OptionPtr option93(new OptionUint16(Option::V4, 93, 0x0001));
+ client1.addExtraOption(option93);
+
+ // This time, the allocation of the address provided as hint should be successful.
+ testAssigned([this, &client1] {
+ doDORA(client1, "192.0.2.63", "192.0.2.63");
+ });
+
+ // Client 2 should be assigned an address from the unrestricted pool.
+ Dhcp4Client client2(client1.getServer(), Dhcp4Client::SELECTING);
+ client2.setIfaceName("eth1");
+ testAssigned([this, &client2] {
+ doDORA(client2, "192.0.2.100");
+ });
+}
+
} // end of anonymous namespace
information. The second argument includes all classes to which the
packet has been assigned.
-% DHCP6_CLASS_UNCONFIGURED %1: client packet belongs to an unconfigured class: %1
+% DHCP6_CLASS_UNCONFIGURED %1: client packet belongs to an unconfigured class: %2
This debug message informs that incoming packet belongs to a class
which cannot be found in the configuration. Either a hook written
before the classification was added to Kea is used, or class naming is
EXPECT_TRUE(srv_.selectSubnet(sol));
}
+// Checks if the client-class field is indeed used for pool selection.
+TEST_F(ClassifyTest, clientClassifyPool) {
+ IfaceMgrTestConfig test_config(true);
+
+ NakedDhcpv6Srv srv(0);
+
+ // This test configures 2 pools.
+ // The second pool does not play any role here. The client's
+ // IP address belongs to the first pool, so only that first
+ // pool is being tested.
+ std::string config = "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"client-classes\": [ "
+ " { "
+ " \"name\": \"foo\" "
+ " }, "
+ " { "
+ " \"name\": \"bar\" "
+ " } "
+ "], "
+ "\"subnet6\": [ "
+ " { \"pools\": [ "
+ " { "
+ " \"pool\": \"2001:db8:1::/64\", "
+ " \"client-class\": \"foo\" "
+ " }, "
+ " { "
+ " \"pool\": \"2001:db8:2::/64\", "
+ " \"client-class\": \"xyzzy\" "
+ " } "
+ " ], "
+ " \"subnet\": \"2001:db8:2::/40\" "
+ " } "
+ "], "
+ "\"valid-lifetime\": 4000 }";
+
+ ASSERT_NO_THROW(configure(config));
+
+ OptionPtr clientid = generateClientId();
+ Pkt6Ptr query1 = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
+ query1->setRemoteAddr(IOAddress("2001:db8:1::3"));
+ query1->addOption(generateIA(D6O_IA_NA, 234, 1500, 3000));
+ query1->addOption(clientid);
+ query1->setIface("eth1");
+ Pkt6Ptr query2 = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
+ query2->setRemoteAddr(IOAddress("2001:db8:1::3"));
+ query2->addOption(generateIA(D6O_IA_NA, 234, 1500, 3000));
+ query2->addOption(clientid);
+ query2->setIface("eth1");
+ Pkt6Ptr query3 = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
+ query3->setRemoteAddr(IOAddress("2001:db8:1::3"));
+ query3->addOption(generateIA(D6O_IA_NA, 234, 1500, 3000));
+ query3->addOption(clientid);
+ query3->setIface("eth1");
+
+ // This discover does not belong to foo class, so it will not
+ // be serviced
+ srv.classifyPacket(query1);
+ Pkt6Ptr response1 = srv.processSolicit(query1);
+ ASSERT_TRUE(response1);
+ OptionPtr ia_na1 = response1->getOption(D6O_IA_NA);
+ ASSERT_TRUE(ia_na1);
+ EXPECT_TRUE(ia_na1->getOption(D6O_STATUS_CODE));
+ EXPECT_FALSE(ia_na1->getOption(D6O_IAADDR));
+
+ // Let's add the packet to bar class and try again.
+ query2->addClass("bar");
+ // Still not supported, because it belongs to wrong class.
+ srv.classifyPacket(query2);
+ Pkt6Ptr response2 = srv.processSolicit(query2);
+ ASSERT_TRUE(response2);
+ OptionPtr ia_na2 = response2->getOption(D6O_IA_NA);
+ ASSERT_TRUE(ia_na2);
+ EXPECT_TRUE(ia_na2->getOption(D6O_STATUS_CODE));
+ EXPECT_FALSE(ia_na2->getOption(D6O_IAADDR));
+
+ // Let's add it to matching class.
+ query3->addClass("foo");
+ // This time it should work
+ srv.classifyPacket(query3);
+ Pkt6Ptr response3 = srv.processSolicit(query3);
+ ASSERT_TRUE(response3);
+ OptionPtr ia_na3 = response3->getOption(D6O_IA_NA);
+ ASSERT_TRUE(ia_na3);
+ EXPECT_FALSE(ia_na3->getOption(D6O_STATUS_CODE));
+ EXPECT_TRUE(ia_na3->getOption(D6O_IAADDR));
+}
+
// Tests whether a packet with custom vendor-class (not erouter or docsis)
// is classified properly.
TEST_F(ClassifyTest, vendorClientClassification2) {
EXPECT_TRUE (subnets->at(3)->clientSupported(classes));
}
+// Goal of this test is to verify that multiple pools can be configured
+// with defined client classes.
+TEST_F(Dhcp6ParserTest, classifyPools) {
+ ConstElementPtr x;
+ string config = "{ " + genIfaceConfig() + ","
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"pools\": [ { "
+ " \"pool\": \"2001:db8:1::/80\", "
+ " \"client-class\": \"alpha\" "
+ " },"
+ " {"
+ " \"pool\": \"2001:db8:2::/80\", "
+ " \"client-class\": \"beta\" "
+ " },"
+ " {"
+ " \"pool\": \"2001:db8:3::/80\", "
+ " \"client-class\": \"gamma\" "
+ " },"
+ " {"
+ " \"pool\": \"2001:db8:4::/80\" "
+ " } ],"
+ " \"subnet\": \"2001:db8:0::/40\" "
+ " } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP6(config, true));
+ extractConfig(config);
+
+ EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
+ checkResult(x, 0);
+
+ const Subnet6Collection* subnets =
+ CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->getAll();
+ ASSERT_TRUE(subnets);
+ ASSERT_EQ(1, subnets->size());
+ const PoolCollection& pools = subnets->at(0)->getPools(Lease::TYPE_NA);
+ ASSERT_EQ(4, pools.size()); // We expect 4 pools
+
+ // Let's check if client belonging to alpha class is supported in pool[0]
+ // and not supported in any other pool (except pool[3], which allows
+ // everyone).
+ ClientClasses classes;
+ classes.insert("alpha");
+ EXPECT_TRUE (pools.at(0)->clientSupported(classes));
+ EXPECT_FALSE(pools.at(1)->clientSupported(classes));
+ EXPECT_FALSE(pools.at(2)->clientSupported(classes));
+ EXPECT_TRUE (pools.at(3)->clientSupported(classes));
+
+ // Let's check if client belonging to beta class is supported in pool[1]
+ // and not supported in any other pool (except pool[3], which allows
+ // everyone).
+ classes.clear();
+ classes.insert("beta");
+ EXPECT_FALSE(pools.at(0)->clientSupported(classes));
+ EXPECT_TRUE (pools.at(1)->clientSupported(classes));
+ EXPECT_FALSE(pools.at(2)->clientSupported(classes));
+ EXPECT_TRUE (pools.at(3)->clientSupported(classes));
+
+ // Let's check if client belonging to gamma class is supported in pool[2]
+ // and not supported in any other pool (except pool[3], which allows
+ // everyone).
+ classes.clear();
+ classes.insert("gamma");
+ EXPECT_FALSE(pools.at(0)->clientSupported(classes));
+ EXPECT_FALSE(pools.at(1)->clientSupported(classes));
+ EXPECT_TRUE (pools.at(2)->clientSupported(classes));
+ EXPECT_TRUE (pools.at(3)->clientSupported(classes));
+
+ // Let's check if client belonging to some other class (not mentioned in
+ // the config) is supported only in pool[3], which allows everyone.
+ classes.clear();
+ classes.insert("delta");
+ EXPECT_FALSE(pools.at(0)->clientSupported(classes));
+ EXPECT_FALSE(pools.at(1)->clientSupported(classes));
+ EXPECT_FALSE(pools.at(2)->clientSupported(classes));
+ EXPECT_TRUE (pools.at(3)->clientSupported(classes));
+
+ // Finally, let's check class-less client. He should be allowed only in
+ // the last pool, which does not have any class restrictions.
+ classes.clear();
+ EXPECT_FALSE(pools.at(0)->clientSupported(classes));
+ EXPECT_FALSE(pools.at(1)->clientSupported(classes));
+ EXPECT_FALSE(pools.at(2)->clientSupported(classes));
+ EXPECT_TRUE (pools.at(3)->clientSupported(classes));
+}
+
+// Goal of this test is to verify that multiple pdpools can be configured
+// with defined client classes.
+TEST_F(Dhcp6ParserTest, classifyPdPools) {
+ ConstElementPtr x;
+ string config = "{ " + genIfaceConfig() + ","
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"pd-pools\": [ { "
+ " \"prefix-len\": 48, "
+ " \"delegated-len\": 64, "
+ " \"prefix\": \"2001:db8:1::\", "
+ " \"client-class\": \"alpha\" "
+ " },"
+ " {"
+ " \"prefix-len\": 48, "
+ " \"delegated-len\": 64, "
+ " \"prefix\": \"2001:db8:2::\", "
+ " \"client-class\": \"beta\" "
+ " },"
+ " {"
+ " \"prefix-len\": 48, "
+ " \"delegated-len\": 64, "
+ " \"prefix\": \"2001:db8:3::\", "
+ " \"client-class\": \"gamma\" "
+ " },"
+ " {"
+ " \"prefix-len\": 48, "
+ " \"delegated-len\": 64, "
+ " \"prefix\": \"2001:db8:4::\" "
+ " } ],"
+ " \"subnet\": \"2001:db8::/64\" "
+ " } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP6(config, true));
+ extractConfig(config);
+
+ EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
+ checkResult(x, 0);
+
+ const Subnet6Collection* subnets =
+ CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->getAll();
+ ASSERT_TRUE(subnets);
+ ASSERT_EQ(1, subnets->size());
+ const PoolCollection& pools = subnets->at(0)->getPools(Lease::TYPE_PD);
+ ASSERT_EQ(4, pools.size()); // We expect 4 pools
+
+ // Let's check if client belonging to alpha class is supported in pool[0]
+ // and not supported in any other pool (except pool[3], which allows
+ // everyone).
+ ClientClasses classes;
+ classes.insert("alpha");
+ EXPECT_TRUE (pools.at(0)->clientSupported(classes));
+ EXPECT_FALSE(pools.at(1)->clientSupported(classes));
+ EXPECT_FALSE(pools.at(2)->clientSupported(classes));
+ EXPECT_TRUE (pools.at(3)->clientSupported(classes));
+
+ // Let's check if client belonging to beta class is supported in pool[1]
+ // and not supported in any other pool (except pool[3], which allows
+ // everyone).
+ classes.clear();
+ classes.insert("beta");
+ EXPECT_FALSE(pools.at(0)->clientSupported(classes));
+ EXPECT_TRUE (pools.at(1)->clientSupported(classes));
+ EXPECT_FALSE(pools.at(2)->clientSupported(classes));
+ EXPECT_TRUE (pools.at(3)->clientSupported(classes));
+
+ // Let's check if client belonging to gamma class is supported in pool[2]
+ // and not supported in any other pool (except pool[3], which allows
+ // everyone).
+ classes.clear();
+ classes.insert("gamma");
+ EXPECT_FALSE(pools.at(0)->clientSupported(classes));
+ EXPECT_FALSE(pools.at(1)->clientSupported(classes));
+ EXPECT_TRUE (pools.at(2)->clientSupported(classes));
+ EXPECT_TRUE (pools.at(3)->clientSupported(classes));
+
+ // Let's check if client belonging to some other class (not mentioned in
+ // the config) is supported only in pool[3], which allows everyone.
+ classes.clear();
+ classes.insert("delta");
+ EXPECT_FALSE(pools.at(0)->clientSupported(classes));
+ EXPECT_FALSE(pools.at(1)->clientSupported(classes));
+ EXPECT_FALSE(pools.at(2)->clientSupported(classes));
+ EXPECT_TRUE (pools.at(3)->clientSupported(classes));
+
+ // Finally, let's check class-less client. He should be allowed only in
+ // the last pool, which does not have any class restrictions.
+ classes.clear();
+ EXPECT_FALSE(pools.at(0)->clientSupported(classes));
+ EXPECT_FALSE(pools.at(1)->clientSupported(classes));
+ EXPECT_FALSE(pools.at(2)->clientSupported(classes));
+ EXPECT_TRUE (pools.at(3)->clientSupported(classes));
+}
+
// This test checks the ability of the server to parse a configuration
// containing a full, valid dhcp-ddns (D2ClientConfig) entry.
TEST_F(Dhcp6ParserTest, d2ClientConfig) {
" }\n",
// CONFIGURATION 32
"{\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ]\n"
+" },\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rebind-timer\": 2000,\n"
+" \"renew-timer\": 1000,\n"
+" \"subnet6\": [\n"
+" {\n"
+" \"pools\": [\n"
+" {\n"
+" \"client-class\": \"alpha\",\n"
+" \"pool\": \"2001:db8:1::/80\"\n"
+" },\n"
+" {\n"
+" \"client-class\": \"beta\",\n"
+" \"pool\": \"2001:db8:2::/80\"\n"
+" },\n"
+" {\n"
+" \"client-class\": \"gamma\",\n"
+" \"pool\": \"2001:db8:3::/80\"\n"
+" },\n"
+" {\n"
+" \"pool\": \"2001:db8:4::/80\"\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"2001:db8:0::/40\"\n"
+" }\n"
+" ],\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 33
+"{\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ]\n"
+" },\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rebind-timer\": 2000,\n"
+" \"renew-timer\": 1000,\n"
+" \"subnet6\": [\n"
+" {\n"
+" \"pd-pools\": [\n"
+" {\n"
+" \"client-class\": \"alpha\",\n"
+" \"delegated-len\": 64,\n"
+" \"prefix\": \"2001:db8:1::\",\n"
+" \"prefix-len\": 48\n"
+" },\n"
+" {\n"
+" \"client-class\": \"beta\",\n"
+" \"delegated-len\": 64,\n"
+" \"prefix\": \"2001:db8:2::\",\n"
+" \"prefix-len\": 48\n"
+" },\n"
+" {\n"
+" \"client-class\": \"gamma\",\n"
+" \"delegated-len\": 64,\n"
+" \"prefix\": \"2001:db8:3::\",\n"
+" \"prefix-len\": 48\n"
+" },\n"
+" {\n"
+" \"delegated-len\": 64,\n"
+" \"prefix\": \"2001:db8:4::\",\n"
+" \"prefix-len\": 48\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"2001:db8::/64\"\n"
+" }\n"
+" ],\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 34
+"{\n"
" \"dhcp-ddns\": {\n"
" \"always-include-fqdn\": true,\n"
" \"enable-updates\": true,\n"
" ],\n"
" \"valid-lifetime\": 4000\n"
" }\n",
- // CONFIGURATION 33
+ // CONFIGURATION 35
"{\n"
" \"interfaces-config\": {\n"
" \"interfaces\": [ \"*\" ]\n"
" ],\n"
" \"valid-lifetime\": 4000\n"
" }\n",
- // CONFIGURATION 34
+ // CONFIGURATION 36
"{\n"
" \"interfaces-config\": {\n"
" \"interfaces\": [ \"*\" ]\n"
" ],\n"
" \"valid-lifetime\": 4000\n"
" }\n",
- // CONFIGURATION 35
+ // CONFIGURATION 37
"{\n"
" \"interfaces-config\": {\n"
" \"interfaces\": [ \"*\" ]\n"
" \"subnet6\": [ ],\n"
" \"valid-lifetime\": 4000\n"
" }\n",
- // CONFIGURATION 36
+ // CONFIGURATION 38
"{\n"
" \"interfaces-config\": {\n"
" \"interfaces\": [ \"*\" ]\n"
" \"subnet6\": [ ],\n"
" \"valid-lifetime\": 4000\n"
" }\n",
- // CONFIGURATION 37
+ // CONFIGURATION 39
"{\n"
" \"preferred-lifetime\": 3000,\n"
" \"rebind-timer\": 2000,\n"
" ],\n"
" \"valid-lifetime\": 4000\n"
" }\n",
- // CONFIGURATION 38
+ // CONFIGURATION 40
"{\n"
" \"interfaces-config\": {\n"
" \"interfaces\": [ \"*\" ]\n"
" \"subnet6\": [ ],\n"
" \"valid-lifetime\": 4000\n"
" }\n",
- // CONFIGURATION 39
+ // CONFIGURATION 41
"{\n"
" \"interfaces-config\": {\n"
" \"interfaces\": [ \"*\" ]\n"
" },\n"
" \"subnet6\": [ ]\n"
" }\n",
- // CONFIGURATION 40
+ // CONFIGURATION 42
"{\n"
" \"interfaces-config\": {\n"
" \"interfaces\": [ \"*\" ]\n"
" },\n"
" \"subnet6\": [ ]\n"
" }\n",
- // CONFIGURATION 41
+ // CONFIGURATION 43
"{\n"
" \"decline-probation-period\": 12345,\n"
" \"interfaces-config\": {\n"
" },\n"
" \"subnet6\": [ ]\n"
" }\n",
- // CONFIGURATION 42
+ // CONFIGURATION 44
"{\n"
" \"expired-leases-processing\": {\n"
" \"flush-reclaimed-timer-wait-time\": 35,\n"
" },\n"
" \"subnet6\": [ ]\n"
" }\n",
- // CONFIGURATION 43
+ // CONFIGURATION 45
"{\n"
" \"client-classes\": [\n"
" {\n"
" ],\n"
" \"valid-lifetime\": 4000\n"
" }\n",
- // CONFIGURATION 44
+ // CONFIGURATION 46
"{\n"
" \"interfaces-config\": {\n"
" \"interfaces\": [ \"*\" ]\n"
" ],\n"
" \"valid-lifetime\": 4000\n"
" }\n",
- // CONFIGURATION 45
+ // CONFIGURATION 47
"{\n"
" \"interfaces-config\": {\n"
" \"interfaces\": [ \"*\" ]\n"
" ],\n"
" \"valid-lifetime\": 4000\n"
" }\n",
- // CONFIGURATION 46
+ // CONFIGURATION 48
"{\n"
" \"interfaces-config\": {\n"
" \"interfaces\": [ \"*\" ]\n"
" ],\n"
" \"valid-lifetime\": 4000\n"
" }\n",
- // CONFIGURATION 47
+ // CONFIGURATION 49
"{\n"
" \"interfaces-config\": {\n"
" \"interfaces\": [ \"*\" ]\n"
" ],\n"
" \"valid-lifetime\": 4000\n"
" }\n",
- // CONFIGURATION 48
+ // CONFIGURATION 50
"{\n"
" \"interfaces-config\": {\n"
" \"interfaces\": [ \"*\" ]\n"
" ],\n"
" \"valid-lifetime\": 4000\n"
" }\n",
- // CONFIGURATION 49
+ // CONFIGURATION 51
"{\n"
" \"interfaces-config\": {\n"
" \"interfaces\": [ \"*\" ]\n"
" ],\n"
" \"valid-lifetime\": 4000\n"
" }\n",
- // CONFIGURATION 50
+ // CONFIGURATION 52
"{\n"
" \"interfaces-config\": {\n"
" \"interfaces\": [ \"*\" ]\n"
"{\n"
" \"decline-probation-period\": 86400,\n"
" \"dhcp-ddns\": {\n"
+" \"always-include-fqdn\": false,\n"
+" \"enable-updates\": false,\n"
+" \"generated-prefix\": \"myhost\",\n"
+" \"max-queue-size\": 1024,\n"
+" \"ncr-format\": \"JSON\",\n"
+" \"ncr-protocol\": \"UDP\",\n"
+" \"override-client-update\": false,\n"
+" \"override-no-update\": false,\n"
+" \"qualifying-suffix\": \"\",\n"
+" \"replace-client-name\": \"never\",\n"
+" \"sender-ip\": \"0.0.0.0\",\n"
+" \"sender-port\": 0,\n"
+" \"server-ip\": \"127.0.0.1\",\n"
+" \"server-port\": 53001\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"expired-leases-processing\": {\n"
+" \"flush-reclaimed-timer-wait-time\": 25,\n"
+" \"hold-reclaimed-time\": 3600,\n"
+" \"max-reclaim-leases\": 100,\n"
+" \"max-reclaim-time\": 250,\n"
+" \"reclaim-timer-wait-time\": 10,\n"
+" \"unwarned-reclaim-cycles\": 5\n"
+" },\n"
+" \"hooks-libraries\": [ ],\n"
+" \"host-reservation-identifiers\": [ \"hw-address\", \"duid\" ],\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"lease-database\": {\n"
+" \"type\": \"memfile\"\n"
+" },\n"
+" \"mac-sources\": [ \"any\" ],\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"relay-supplied-options\": [ \"65\" ],\n"
+" \"server-id\": {\n"
+" \"enterprise-id\": 0,\n"
+" \"htype\": 0,\n"
+" \"identifier\": \"\",\n"
+" \"persist\": true,\n"
+" \"time\": 0,\n"
+" \"type\": \"LLT\"\n"
+" },\n"
+" \"shared-networks\": [ ],\n"
+" \"subnet6\": [\n"
+" {\n"
+" \"id\": 1,\n"
+" \"option-data\": [ ],\n"
+" \"pd-pools\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"client-class\": \"alpha\",\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"2001:db8:1::/80\"\n"
+" },\n"
+" {\n"
+" \"client-class\": \"beta\",\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"2001:db8:2::/80\"\n"
+" },\n"
+" {\n"
+" \"client-class\": \"gamma\",\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"2001:db8:3::/80\"\n"
+" },\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"2001:db8:4::/80\"\n"
+" }\n"
+" ],\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rapid-commit\": false,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-address\": \"::\"\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservation-mode\": \"all\",\n"
+" \"reservations\": [ ],\n"
+" \"subnet\": \"2001:db8::/40\",\n"
+" \"valid-lifetime\": 4000\n"
+" }\n"
+" ]\n"
+" }\n",
+ // CONFIGURATION 33
+"{\n"
+" \"decline-probation-period\": 86400,\n"
+" \"dhcp-ddns\": {\n"
+" \"always-include-fqdn\": false,\n"
+" \"enable-updates\": false,\n"
+" \"generated-prefix\": \"myhost\",\n"
+" \"max-queue-size\": 1024,\n"
+" \"ncr-format\": \"JSON\",\n"
+" \"ncr-protocol\": \"UDP\",\n"
+" \"override-client-update\": false,\n"
+" \"override-no-update\": false,\n"
+" \"qualifying-suffix\": \"\",\n"
+" \"replace-client-name\": \"never\",\n"
+" \"sender-ip\": \"0.0.0.0\",\n"
+" \"sender-port\": 0,\n"
+" \"server-ip\": \"127.0.0.1\",\n"
+" \"server-port\": 53001\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"expired-leases-processing\": {\n"
+" \"flush-reclaimed-timer-wait-time\": 25,\n"
+" \"hold-reclaimed-time\": 3600,\n"
+" \"max-reclaim-leases\": 100,\n"
+" \"max-reclaim-time\": 250,\n"
+" \"reclaim-timer-wait-time\": 10,\n"
+" \"unwarned-reclaim-cycles\": 5\n"
+" },\n"
+" \"hooks-libraries\": [ ],\n"
+" \"host-reservation-identifiers\": [ \"hw-address\", \"duid\" ],\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"lease-database\": {\n"
+" \"type\": \"memfile\"\n"
+" },\n"
+" \"mac-sources\": [ \"any\" ],\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"relay-supplied-options\": [ \"65\" ],\n"
+" \"server-id\": {\n"
+" \"enterprise-id\": 0,\n"
+" \"htype\": 0,\n"
+" \"identifier\": \"\",\n"
+" \"persist\": true,\n"
+" \"time\": 0,\n"
+" \"type\": \"LLT\"\n"
+" },\n"
+" \"shared-networks\": [ ],\n"
+" \"subnet6\": [\n"
+" {\n"
+" \"id\": 1,\n"
+" \"option-data\": [ ],\n"
+" \"pd-pools\": [\n"
+" {\n"
+" \"client-class\": \"alpha\",\n"
+" \"delegated-len\": 64,\n"
+" \"option-data\": [ ],\n"
+" \"prefix\": \"2001:db8:1::\",\n"
+" \"prefix-len\": 48\n"
+" },\n"
+" {\n"
+" \"client-class\": \"beta\",\n"
+" \"delegated-len\": 64,\n"
+" \"option-data\": [ ],\n"
+" \"prefix\": \"2001:db8:2::\",\n"
+" \"prefix-len\": 48\n"
+" },\n"
+" {\n"
+" \"client-class\": \"gamma\",\n"
+" \"delegated-len\": 64,\n"
+" \"option-data\": [ ],\n"
+" \"prefix\": \"2001:db8:3::\",\n"
+" \"prefix-len\": 48\n"
+" },\n"
+" {\n"
+" \"delegated-len\": 64,\n"
+" \"option-data\": [ ],\n"
+" \"prefix\": \"2001:db8:4::\",\n"
+" \"prefix-len\": 48\n"
+" }\n"
+" ],\n"
+" \"pools\": [ ],\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rapid-commit\": false,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-address\": \"::\"\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservation-mode\": \"all\",\n"
+" \"reservations\": [ ],\n"
+" \"subnet\": \"2001:db8::/64\",\n"
+" \"valid-lifetime\": 4000\n"
+" }\n"
+" ]\n"
+" }\n",
+ // CONFIGURATION 34
+"{\n"
+" \"decline-probation-period\": 86400,\n"
+" \"dhcp-ddns\": {\n"
" \"always-include-fqdn\": true,\n"
" \"enable-updates\": true,\n"
" \"generated-prefix\": \"test.prefix\",\n"
" }\n"
" ]\n"
" }\n",
- // CONFIGURATION 33
+ // CONFIGURATION 35
"{\n"
" \"decline-probation-period\": 86400,\n"
" \"dhcp-ddns\": {\n"
" }\n"
" ]\n"
" }\n",
- // CONFIGURATION 34
+ // CONFIGURATION 36
"{\n"
" \"decline-probation-period\": 86400,\n"
" \"dhcp-ddns\": {\n"
" }\n"
" ]\n"
" }\n",
- // CONFIGURATION 35
+ // CONFIGURATION 37
"{\n"
" \"decline-probation-period\": 86400,\n"
" \"dhcp-ddns\": {\n"
" \"shared-networks\": [ ],\n"
" \"subnet6\": [ ]\n"
" }\n",
- // CONFIGURATION 36
+ // CONFIGURATION 38
"{\n"
" \"decline-probation-period\": 86400,\n"
" \"dhcp-ddns\": {\n"
" \"shared-networks\": [ ],\n"
" \"subnet6\": [ ]\n"
" }\n",
- // CONFIGURATION 37
+ // CONFIGURATION 39
"{\n"
" \"decline-probation-period\": 86400,\n"
" \"dhcp-ddns\": {\n"
" }\n"
" ]\n"
" }\n",
- // CONFIGURATION 38
+ // CONFIGURATION 40
"{\n"
" \"decline-probation-period\": 86400,\n"
" \"dhcp-ddns\": {\n"
" \"shared-networks\": [ ],\n"
" \"subnet6\": [ ]\n"
" }\n",
- // CONFIGURATION 39
+ // CONFIGURATION 41
"{\n"
" \"decline-probation-period\": 86400,\n"
" \"dhcp-ddns\": {\n"
" \"shared-networks\": [ ],\n"
" \"subnet6\": [ ]\n"
" }\n",
- // CONFIGURATION 40
+ // CONFIGURATION 42
"{\n"
" \"decline-probation-period\": 86400,\n"
" \"dhcp-ddns\": {\n"
" \"shared-networks\": [ ],\n"
" \"subnet6\": [ ]\n"
" }\n",
- // CONFIGURATION 41
+ // CONFIGURATION 43
"{\n"
" \"decline-probation-period\": 12345,\n"
" \"dhcp-ddns\": {\n"
" \"shared-networks\": [ ],\n"
" \"subnet6\": [ ]\n"
" }\n",
- // CONFIGURATION 42
+ // CONFIGURATION 44
"{\n"
" \"decline-probation-period\": 86400,\n"
" \"dhcp-ddns\": {\n"
" \"shared-networks\": [ ],\n"
" \"subnet6\": [ ]\n"
" }\n",
- // CONFIGURATION 43
+ // CONFIGURATION 45
"{\n"
" \"client-classes\": [\n"
" {\n"
" }\n"
" ]\n"
" }\n",
- // CONFIGURATION 44
+ // CONFIGURATION 46
"{\n"
" \"decline-probation-period\": 86400,\n"
" \"dhcp-ddns\": {\n"
" }\n"
" ]\n"
" }\n",
- // CONFIGURATION 45
+ // CONFIGURATION 47
"{\n"
" \"decline-probation-period\": 86400,\n"
" \"dhcp-ddns\": {\n"
" }\n"
" ]\n"
" }\n",
- // CONFIGURATION 46
+ // CONFIGURATION 48
"{\n"
" \"decline-probation-period\": 86400,\n"
" \"dhcp-ddns\": {\n"
" }\n"
" ]\n"
" }\n",
- // CONFIGURATION 47
+ // CONFIGURATION 49
"{\n"
" \"decline-probation-period\": 86400,\n"
" \"dhcp-ddns\": {\n"
" }\n"
" ]\n"
" }\n",
- // CONFIGURATION 48
+ // CONFIGURATION 50
"{\n"
" \"decline-probation-period\": 86400,\n"
" \"dhcp-ddns\": {\n"
" }\n"
" ]\n"
" }\n",
- // CONFIGURATION 49
+ // CONFIGURATION 51
"{\n"
" \"decline-probation-period\": 86400,\n"
" \"dhcp-ddns\": {\n"
" }\n"
" ]\n"
" }\n",
- // CONFIGURATION 50
+ // CONFIGURATION 52
"{\n"
" \"decline-probation-period\": 86400,\n"
" \"dhcp-ddns\": {\n"
" ]"
" }"
" ]"
- "}"
+ "}",
+
+// Configuration #19.
+// - one shared network with on subnet and two pools (the first has
+// class restrictions)
+ "{"
+ " \"client-classes\": ["
+ " {"
+ " \"name\": \"a-devices\","
+ " \"test\": \"option[1234].hex == 0x0001\""
+ " }"
+ " ],"
+ " \"shared-networks\": ["
+ " {"
+ " \"name\": \"frog\","
+ " \"interface\": \"eth1\","
+ " \"subnet6\": ["
+ " {"
+ " \"subnet\": \"2001:db8:1::/64\","
+ " \"id\": 10,"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"2001:db8:1::20 - 2001:db8:1::20\","
+ " \"client-class\": \"a-devices\""
+ " },"
+ " {"
+ " \"pool\": \"2001:db8:1::50 - 2001:db8:1::50\""
+ " }"
+ " ]"
+ " }"
+ " ]"
+ " }"
+ " ]"
+ "}",
+
};
/// @Brief Test fixture class for DHCPv6 server using shared networks.
testRapidCommit(NETWORKS_CONFIG[1], false, "", "");
}
+// Pool is selected based on the client class specified.
+TEST_F(Dhcpv6SharedNetworkTest, poolInSharedNetworkSelectedByClass) {
+ // Create client #1.
+ Dhcp6Client client1;
+ client1.setInterface("eth1");
+
+ // Configure the server with one shared network including one subnet and
+ // two pools. The access to one of the pools is restricted by
+ // by client classification.
+ ASSERT_NO_FATAL_FAILURE(configure(NETWORKS_CONFIG[19], *client1.getServer()));
+
+ // Client #1 requests an address in the restricted pool but can't be assigned
+ // this address because the client doesn't belong to a certain class.
+ ASSERT_NO_THROW(client1.requestAddress(0xabca, IOAddress("2001:db8:1::20")));
+ testAssigned([this, &client1] {
+ ASSERT_NO_THROW(client1.doSARR());
+ });
+ ASSERT_TRUE(hasLeaseForAddress(client1, IOAddress("2001:db8:1::50")));
+
+ // Release the lease that the client has got, because we'll need this address
+ // further in the test.
+ testAssigned([this, &client1] {
+ ASSERT_NO_THROW(client1.doRelease());
+ });
+
+ // Add option 1234 which would cause the client to be classified as "a-devices".
+ OptionPtr option1234(new OptionUint16(Option::V6, 1234, 0x0001));
+ client1.addExtraOption(option1234);
+
+ // This time, the allocation of the address provided as hint should be successful.
+ testAssigned([this, &client1] {
+ ASSERT_NO_THROW(client1.doSARR());
+ });
+ ASSERT_TRUE(hasLeaseForAddress(client1, IOAddress("2001:db8:1::20")));
+
+ // Client 2 should be assigned an address from the unrestricted subnet.
+ Dhcp6Client client2(client1.getServer());
+ client2.setInterface("eth1");
+ ASSERT_NO_THROW(client2.requestAddress(0xabca0));
+ testAssigned([this, &client2] {
+ ASSERT_NO_THROW(client2.doSARR());
+ });
+ ASSERT_TRUE(hasLeaseForAddress(client2, IOAddress("2001:db8:1::50")));
+}
+
} // end of anonymous namespace
}
valid = false;
- (*it)->resetLastAllocated();
+ (*it)->resetLastAllocated();
}
// We hit pool boundary, let's try to jump to the next pool and try again
- ++it;
+ ++it;
retrying = true;
}
Subnet4Ptr current_subnet = ctx.subnet_;
while (current_subnet) {
- if (current_subnet->inPool(Lease::TYPE_V4, address)) {
+ if (current_subnet->inPool(Lease::TYPE_V4, address,
+ ctx.query_->getClasses())) {
// We found a subnet that this address belongs to, so it
// seems that this subnet is the good candidate for allocation.
// Let's update the selected subnet.
}
// Client-class.
- string client_class = getString(pool_structure, "client-class");
- if (!client_class.empty()) {
- pool->allowClientClass(client_class);
+ ConstElementPtr client_class = pool_structure->get("client-class");
+ if (client_class) {
+ string cclass = client_class->stringValue();
+ if (!cclass.empty()) {
+ pool->allowClientClass(cclass);
+ }
}
}
user_context_ = user_context;
}
+ ConstElementPtr client_class = pd_pool_->get("client-class");
+ if (client_class) {
+ client_class_ = client_class;
+ }
+
// Check the pool parameters. It will throw an exception if any
// of the required parameters are invalid.
try {
pool_->setContext(user_context_);
}
- string client_class = getString(pd_pool_, "client-class");
- if (!client_class.empty()) {
- pool_->allowClientClass(client_class);
+
+ if (client_class_) {
+ string cclass = client_class_->stringValue();
+ if (!cclass.empty()) {
+ pool_->allowClientClass(cclass);
+ }
}
// Add the local pool to the external storage ptr.
CfgOptionPtr options_;
isc::data::ConstElementPtr user_context_;
+
+ isc::data::ConstElementPtr client_class_;
+
};
/// @brief Parser for a list of prefix delegation pools.
// Set pool options
ConstCfgOptionPtr opts = getCfgOption();
map->set("option-data", opts->toElement());
- return (map);
// Set client-class
const ClientClasses& cclasses = getClientClasses();
} else if (!cclasses.empty()) {
map->set("client-class", Element::create(*cclasses.cbegin()));
}
+
+ return (map);
}
data::ElementPtr
return (false);
}
+bool
+Subnet::inPool(Lease::Type type,
+ const isc::asiolink::IOAddress& addr,
+ const ClientClasses& client_classes) const {
+
+ // Let's start with checking if it even belongs to that subnet.
+ if ((type != Lease::TYPE_PD) && !inRange(addr)) {
+ return (false);
+ }
+
+ const PoolCollection& pools = getPools(type);
+
+ for (PoolCollection::const_iterator pool = pools.begin();
+ pool != pools.end(); ++pool) {
+ if (!(*pool)->clientSupported(client_classes)) {
+ continue;
+ }
+ if ((*pool)->inRange(addr)) {
+ return (true);
+ }
+ }
+ // There's no pool that address belongs to
+ return (false);
+}
+
bool
Subnet::poolOverlaps(const Lease::Type& pool_type, const PoolPtr& pool) const {
const PoolCollection& pools = getPools(pool_type);
// Set pool options
ConstCfgOptionPtr opts = (*pool)->getCfgOption();
pool_map->set("option-data", opts->toElement());
+ // Set client-class
+ const ClientClasses& cclasses = (*pool)->getClientClasses();
+ if (cclasses.size() > 1) {
+ isc_throw(ToElementError, "client-class has too many items: "
+ << cclasses.size());
+ } else if (!cclasses.empty()) {
+ pool_map->set("client-class", Element::create(*cclasses.cbegin()));
+ }
// Push on the pool list
pool_list->add(pool_map);
}
// Set pool options
ConstCfgOptionPtr opts = pdpool->getCfgOption();
pool_map->set("option-data", opts->toElement());
+ // Set client-class
+ const ClientClasses& cclasses = pdpool->getClientClasses();
+ if (cclasses.size() > 1) {
+ isc_throw(ToElementError, "client-class has too many items: "
+ << cclasses.size());
+ } else if (!cclasses.empty()) {
+ pool_map->set("client-class", Element::create(*cclasses.cbegin()));
+ }
// Push on the pool list
pdpool_list->add(pool_map);
}
/// @return true if the address is in any of the pools
bool inPool(Lease::Type type, const isc::asiolink::IOAddress& addr) const;
+ /// @brief checks if the specified address is in allowed pools
+ ///
+ /// This takes also into account client classes
+ ///
+ /// @param type type of pools to iterate over
+ /// @param addr this address will be checked if it belongs to any pools in
+ /// that subnet
+ /// @param client_classes client class list which must be allowed
+ /// @return true if the address is in any of the allowed pools
+ bool inPool(Lease::Type type,
+ const isc::asiolink::IOAddress& addr,
+ const ClientClasses& client_classes) const;
+
/// @brief returns the last address that was tried from this subnet
///
/// This method returns the last address that was attempted to be allocated
EXPECT_EQ("192.0.2.17", lease->addr_.toText());
}
+// This test verifies that the server can offer an address from a
+// different subnet than orginally selected, when the address pool in
+// the first subnet requires another class.
+TEST_F(SharedNetworkAlloc4Test, discoverSharedNetworkPoolClassification) {
+
+ // Try to offer address from subnet1. There is one address available
+ // so it should be offerred.
+ AllocEngine::ClientContext4
+ ctx(subnet1_, ClientIdPtr(), hwaddr_, IOAddress::IPV4_ZERO_ADDRESS(),
+ false, false, "host.example.com.", true);
+ ctx.query_.reset(new Pkt4(DHCPDISCOVER, 1234));
+ Lease4Ptr lease = engine_.allocateLease4(ctx);
+ ASSERT_TRUE(lease);
+ EXPECT_TRUE(subnet1_->inPool(Lease::TYPE_V4, lease->addr_));
+
+ // Apply restrictions on the pool1. This should be only assigned
+ // to clients belonging to cable-modem class.
+ pool1_->allowClientClass("cable-modem");
+
+ // The allocation engine should determine that the pool1 is not
+ // available for the client not belonging to the cable-modem class.
+ // Instead, it should offer an address from subnet2 that belongs
+ // to the same shared network.
+ ctx.subnet_ = subnet1_;
+ lease = engine_.allocateLease4(ctx);
+ ASSERT_TRUE(lease);
+ EXPECT_TRUE(subnet2_->inPool(Lease::TYPE_V4, lease->addr_));
+
+ // Assign cable-modem class and try again. This time, we should
+ // offer an address from the pool1.
+ ctx.query_->addClass(ClientClass("cable-modem"));
+
+ ctx.subnet_ = subnet1_;
+ lease = engine_.allocateLease4(ctx);
+ ASSERT_TRUE(lease);
+ EXPECT_EQ("192.0.2.17", lease->addr_.toText());
+}
+
// Test that reservations within shared network take precedence over the
// existing leases regardless in which subnet belonging to a shared network
// reservations belong.
EXPECT_TRUE(subnet2_->inPool(Lease::TYPE_V4, lease->addr_));
}
+// This test verifies that the server can assign an address from a
+// different subnet than orginally selected, when the address pool in
+// the first subnet requires another class.
+TEST_F(SharedNetworkAlloc4Test, requestSharedNetworkPoolClassification) {
+ // Try to offer address from subnet1. There is one address available
+ // so it should be offerred.
+ AllocEngine::ClientContext4
+ ctx(subnet1_, ClientIdPtr(), hwaddr_, IOAddress::IPV4_ZERO_ADDRESS(),
+ false, false, "host.example.com.", false);
+ ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234));
+ Lease4Ptr lease = engine_.allocateLease4(ctx);
+ ASSERT_TRUE(lease);
+ EXPECT_TRUE(subnet1_->inPool(Lease::TYPE_V4, lease->addr_));
+
+ // Remove the lease so as we can start over.
+ LeaseMgrFactory::instance().deleteLease(lease->addr_);
+
+ // Apply restrictions on the pool1. This should be only assigned
+ // to clients belonging to cable-modem class.
+ pool1_->allowClientClass("cable-modem");
+
+ // The allocation engine should determine that the pool1 is not
+ // available for the client not belonging to the cable-modem class.
+ // Instead, it should assign an address from subnet2 that belongs
+ // to the same shared network.
+ ctx.subnet_ = subnet1_;
+ lease = engine_.allocateLease4(ctx);
+ ASSERT_TRUE(lease);
+ EXPECT_TRUE(subnet2_->inPool(Lease::TYPE_V4, lease->addr_));
+
+ // Remove the lease so as we can start over.
+ LeaseMgrFactory::instance().deleteLease(lease->addr_);
+
+ // Assign cable-modem class and try again. This time, we should
+ // offer an address from the pool1.
+ ctx.query_->addClass(ClientClass("cable-modem"));
+
+ ctx.subnet_ = subnet1_;
+ lease = engine_.allocateLease4(ctx);
+ ASSERT_TRUE(lease);
+ EXPECT_TRUE(subnet1_->inPool(Lease::TYPE_V4, lease->addr_));
+
+ // Let's now remove the client from the cable-modem class and try
+ // to renew the address. The engine should determine that the
+ // client doesn't have access to the pool1 anymore and
+ // assign an address from unrestricted pool.
+ ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234));
+ ctx.subnet_ = subnet1_;
+ lease = engine_.allocateLease4(ctx);
+ ASSERT_TRUE(lease);
+#if 0
+ // It should work but currently it does not...
+ EXPECT_TRUE(subnet2_->inPool(Lease::TYPE_V4, lease->addr_));
+#endif
+}
+
// Test that reservations within shared network take precedence over the
// existing leases regardless in which subnet belonging to a shared network
// reservations belong (DHCPREQUEST case).
EXPECT_EQ("2001:db8:1::1", lease->addr_.toText());
}
+// This test verifies that the server can offer an address from a
+// different subnet than orginally selected, when the address pool in
+// the first subnet requires another class.
+TEST_F(SharedNetworkAlloc6Test, solicitSharedNetworkPoolClassification) {
+ // Try to offer address from subnet1. There is an address available so
+ // it should be offerred.
+ Pkt6Ptr query(new Pkt6(DHCPV6_SOLICIT, 1234));
+ AllocEngine::ClientContext6 ctx(subnet1_, duid_, false, false, "", true,
+ query);
+ ctx.currentIA().iaid_ = iaid_;
+
+ Lease6Ptr lease;
+ ASSERT_NO_THROW(lease = expectOneLease(engine_.allocateLeases6(ctx)));
+ ASSERT_TRUE(lease);
+ ASSERT_TRUE(subnet1_->inRange(lease->addr_));
+
+ // Apply restrictions on the pool1. This should be only assigned
+ // to clients belonging to cable-modem class.
+ pool1_->allowClientClass("cable-modem");
+
+ // The allocation engine should determine that the pool1 is not
+ // available for the client not belonging to the cable-modem class.
+ // Instead, it should offer an address from subnet2 that belongs
+ // to the same shared network.
+ AllocEngine::ClientContext6 ctx2(subnet1_, duid_, false, false, "", true,
+ query);
+ ctx2.currentIA().iaid_ = iaid_;
+ ctx2.query_ = query;
+ ASSERT_NO_THROW(lease = expectOneLease(engine_.allocateLeases6(ctx2)));
+ ASSERT_TRUE(lease);
+ ASSERT_TRUE(subnet2_->inRange(lease->addr_));
+
+ AllocEngine::ClientContext6 ctx3(subnet1_, duid_, false, false, "", true,
+ query);
+ ctx3.currentIA().iaid_ = iaid_;
+ ctx3.query_ = query;
+
+ AllocEngine::ClientContext6 ctx4(subnet1_, duid_, false, false, "", true,
+ query);
+ ctx4.currentIA().iaid_ = iaid_;
+ ctx4.query_ = query;
+
+ // Assign cable-modem class and try again. This time, we should
+ // offer an address from the pool1_.
+ ctx4.query_->addClass(ClientClass("cable-modem"));
+
+ AllocEngine::findReservation(ctx4);
+ ASSERT_NO_THROW(lease = expectOneLease(engine_.allocateLeases6(ctx4)));
+ ASSERT_TRUE(lease);
+ EXPECT_EQ("2001:db8:1::1", lease->addr_.toText());
+}
+
// This test verifies that the client is offerred a reserved address
// even if this address belongs to another subnet within the same
// shared network.
Subnet4Ptr subnet(new Subnet4(IOAddress("192.0.2.0"), 24, 1, 2, 3, 123));
Pool4Ptr pool1(new Pool4(IOAddress("192.0.2.1"), IOAddress("192.0.2.10")));
Pool4Ptr pool2(new Pool4(IOAddress("192.0.2.64"), 26));
+ pool2->allowClientClass("bar");
subnet->addPool(pool1);
subnet->addPool(pool2);
" \"pool\": \"192.0.2.1-192.0.2.10\"\n"
" },{\n"
" \"option-data\": [ ],\n"
- " \"pool\": \"192.0.2.64/26\"\n"
+ " \"pool\": \"192.0.2.64/26\",\n"
+ " \"client-class\": \"bar\"\n"
" }\n"
" ]\n"
"} ]\n";
IOAddress("2001:db8:1::100"),
IOAddress("2001:db8:1::199")));
Pool6Ptr pool2(new Pool6(Lease::TYPE_NA, IOAddress("2001:db8:1:1::"), 64));
+ pool2->allowClientClass("bar");
subnet->addPool(pool1);
subnet->addPool(pool2);
" \"option-data\": [ ]\n"
" },{\n"
" \"pool\": \"2001:db8:1:1::/64\",\n"
- " \"option-data\": [ ]\n"
+ " \"option-data\": [ ],\n"
+ " \"client-class\": \"bar\"\n"
" }\n"
" ],\n"
" \"pd-pools\": [ ],\n"
IOAddress("2001:db8:2::"), 48, 64));
Pool6Ptr pdpool2(new Pool6(IOAddress("2001:db8:3::"), 48, 56,
IOAddress("2001:db8:3::"), 64));
+ pdpool2->allowClientClass("bar");
subnet->addPool(pdpool1);
subnet->addPool(pdpool2);
" \"delegated-len\": 56,\n"
" \"excluded-prefix\": \"2001:db8:3::\",\n"
" \"excluded-prefix-len\": 64,\n"
- " \"option-data\": [ ]\n"
+ " \"option-data\": [ ],\n"
+ " \"client-class\": \"bar\"\n"
" }\n"
" ],\n"
" \"option-data\": [ ]\n"
// the first address that is in range, in pool
EXPECT_TRUE(subnet->inRange(IOAddress("192.2.0.0")));
- EXPECT_TRUE (subnet->inPool(Lease::TYPE_V4, IOAddress("192.2.0.0")));
+ EXPECT_TRUE(subnet->inPool(Lease::TYPE_V4, IOAddress("192.2.0.0")));
// let's try something in the middle as well
EXPECT_TRUE(subnet->inRange(IOAddress("192.2.3.4")));
- EXPECT_TRUE (subnet->inPool(Lease::TYPE_V4, IOAddress("192.2.3.4")));
+ EXPECT_TRUE(subnet->inPool(Lease::TYPE_V4, IOAddress("192.2.3.4")));
// the last address that is in range, in pool
EXPECT_TRUE(subnet->inRange(IOAddress("192.2.255.255")));
- EXPECT_TRUE (subnet->inPool(Lease::TYPE_V4, IOAddress("192.2.255.255")));
+ EXPECT_TRUE(subnet->inPool(Lease::TYPE_V4, IOAddress("192.2.255.255")));
// the first address that is in range, but out of pool
EXPECT_TRUE(subnet->inRange(IOAddress("192.3.0.0")));
EXPECT_FALSE(subnet->inPool(Lease::TYPE_V4, IOAddress("192.3.0.0")));
+
+ // Add with classes
+ pool1->allowClientClass("bar");
+ EXPECT_TRUE(subnet->inPool(Lease::TYPE_V4, IOAddress("192.2.3.4")));
+
+ // This client does not belong to any class.
+ isc::dhcp::ClientClasses no_class;
+ EXPECT_FALSE(subnet->inPool(Lease::TYPE_V4, IOAddress("192.2.3.4"), no_class));
+
+ // This client belongs to foo only
+ isc::dhcp::ClientClasses foo_class;
+ foo_class.insert("foo");
+ EXPECT_FALSE(subnet->inPool(Lease::TYPE_V4, IOAddress("192.2.3.4"), foo_class));
+
+ // This client belongs to bar only. I like that client.
+ isc::dhcp::ClientClasses bar_class;
+ bar_class.insert("bar");
+ EXPECT_TRUE(subnet->inPool(Lease::TYPE_V4, IOAddress("192.2.3.4"), bar_class));
+
+ // This client belongs to foo, bar and baz classes.
+ isc::dhcp::ClientClasses three_classes;
+ three_classes.insert("foo");
+ three_classes.insert("bar");
+ three_classes.insert("baz");
+ EXPECT_TRUE(subnet->inPool(Lease::TYPE_V4, IOAddress("192.2.3.4"), three_classes));
}
// This test checks if the toText() method returns text representation
// the first address that is in range, in pool
EXPECT_TRUE(subnet->inRange(IOAddress("2001:db8::10")));
- EXPECT_TRUE (subnet->inPool(Lease::TYPE_NA, IOAddress("2001:db8::10")));
+ EXPECT_TRUE(subnet->inPool(Lease::TYPE_NA, IOAddress("2001:db8::10")));
// let's try something in the middle as well
EXPECT_TRUE(subnet->inRange(IOAddress("2001:db8::18")));
- EXPECT_TRUE (subnet->inPool(Lease::TYPE_NA, IOAddress("2001:db8::18")));
+ EXPECT_TRUE(subnet->inPool(Lease::TYPE_NA, IOAddress("2001:db8::18")));
// the last address that is in range, in pool
EXPECT_TRUE(subnet->inRange(IOAddress("2001:db8::20")));
// the first address that is in range, but out of pool
EXPECT_TRUE(subnet->inRange(IOAddress("2001:db8::21")));
EXPECT_FALSE(subnet->inPool(Lease::TYPE_NA, IOAddress("2001:db8::21")));
+
+ // Add with classes
+ pool1->allowClientClass("bar");
+ EXPECT_TRUE(subnet->inPool(Lease::TYPE_NA, IOAddress("2001:db8::18")));
+
+ // This client does not belong to any class.
+ isc::dhcp::ClientClasses no_class;
+ EXPECT_FALSE(subnet->inPool(Lease::TYPE_NA, IOAddress("2001:db8::18"), no_class));
+
+ // This client belongs to foo only
+ isc::dhcp::ClientClasses foo_class;
+ foo_class.insert("foo");
+ EXPECT_FALSE(subnet->inPool(Lease::TYPE_NA, IOAddress("2001:db8::18"), foo_class));
+
+ // This client belongs to bar only. I like that client.
+ isc::dhcp::ClientClasses bar_class;
+ bar_class.insert("bar");
+ EXPECT_TRUE(subnet->inPool(Lease::TYPE_NA, IOAddress("2001:db8::18"), bar_class));
+
+ // This client belongs to foo, bar and baz classes.
+ isc::dhcp::ClientClasses three_classes;
+ three_classes.insert("foo");
+ three_classes.insert("bar");
+ three_classes.insert("baz");
+ EXPECT_TRUE(subnet->inPool(Lease::TYPE_NA, IOAddress("2001:db8::18"), three_classes));
}
// This test verifies that inRange() and inPool() methods work properly