From: Francis Dupont Date: Sun, 5 Nov 2017 10:12:27 +0000 (+0100) Subject: [5425] Checkpoint: added not alloc and service tests X-Git-Tag: trac5374_base~13 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=3c48a2ebb24e14bfc8ba6659d4262025d221d682;p=thirdparty%2Fkea.git [5425] Checkpoint: added not alloc and service tests --- diff --git a/src/bin/dhcp4/tests/config_parser_unittest.cc b/src/bin/dhcp4/tests/config_parser_unittest.cc index 8c00d545be..dcc3bf2377 100644 --- a/src/bin/dhcp4/tests/config_parser_unittest.cc +++ b/src/bin/dhcp4/tests/config_parser_unittest.cc @@ -4040,6 +4040,7 @@ TEST_F(Dhcp4ParserTest, classifySubnets) { ConstElementPtr json; ASSERT_NO_THROW(json = parseDHCP4(config)); + extractConfig(config); EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json)); checkResult(x, 0); @@ -4097,6 +4098,95 @@ TEST_F(Dhcp4ParserTest, classifySubnets) { 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) { diff --git a/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc b/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc index df1ae36fb9..f650d33867 100644 --- a/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc +++ b/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc @@ -2318,6 +2318,75 @@ TEST_F(Dhcpv4SrvTest, clientClassify) { 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); diff --git a/src/bin/dhcp4/tests/shared_network_unittest.cc b/src/bin/dhcp4/tests/shared_network_unittest.cc index 15cdf20973..bc50de5486 100644 --- a/src/bin/dhcp4/tests/shared_network_unittest.cc +++ b/src/bin/dhcp4/tests/shared_network_unittest.cc @@ -698,7 +698,7 @@ const char* NETWORKS_CONFIG[] = { // 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\": [ \"*\" ]" @@ -859,6 +859,42 @@ const char* NETWORKS_CONFIG[] = { " ]" " }" " ]" + "}", + +// 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\"" + " }" + " ]" + " }" + " ]" + " }" + " ]" "}" }; @@ -1779,4 +1815,45 @@ TEST_F(Dhcpv4SharedNetworkTest, customServerIdentifier) { 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 diff --git a/src/bin/dhcp6/dhcp6_messages.mes b/src/bin/dhcp6/dhcp6_messages.mes index 4fde8c77ed..4c8c115454 100644 --- a/src/bin/dhcp6/dhcp6_messages.mes +++ b/src/bin/dhcp6/dhcp6_messages.mes @@ -60,7 +60,7 @@ The first argument specifies the client and transaction identification 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 diff --git a/src/bin/dhcp6/tests/classify_unittests.cc b/src/bin/dhcp6/tests/classify_unittests.cc index 9b04d035c2..7a81310f92 100644 --- a/src/bin/dhcp6/tests/classify_unittests.cc +++ b/src/bin/dhcp6/tests/classify_unittests.cc @@ -643,6 +643,98 @@ TEST_F(ClassifyTest, clientClassifySubnet) { 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) { diff --git a/src/bin/dhcp6/tests/config_parser_unittest.cc b/src/bin/dhcp6/tests/config_parser_unittest.cc index 946ba5cb9c..b29fd5840f 100644 --- a/src/bin/dhcp6/tests/config_parser_unittest.cc +++ b/src/bin/dhcp6/tests/config_parser_unittest.cc @@ -4134,6 +4134,194 @@ TEST_F(Dhcp6ParserTest, classifySubnets) { 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) { diff --git a/src/bin/dhcp6/tests/get_config_unittest.cc b/src/bin/dhcp6/tests/get_config_unittest.cc index dae152ebc2..22f8b4d971 100644 --- a/src/bin/dhcp6/tests/get_config_unittest.cc +++ b/src/bin/dhcp6/tests/get_config_unittest.cc @@ -987,6 +987,78 @@ const char* EXTRACTED_CONFIGS[] = { " }\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" @@ -1021,7 +1093,7 @@ const char* EXTRACTED_CONFIGS[] = { " ],\n" " \"valid-lifetime\": 4000\n" " }\n", - // CONFIGURATION 33 + // CONFIGURATION 35 "{\n" " \"interfaces-config\": {\n" " \"interfaces\": [ \"*\" ]\n" @@ -1107,7 +1179,7 @@ const char* EXTRACTED_CONFIGS[] = { " ],\n" " \"valid-lifetime\": 4000\n" " }\n", - // CONFIGURATION 34 + // CONFIGURATION 36 "{\n" " \"interfaces-config\": {\n" " \"interfaces\": [ \"*\" ]\n" @@ -1146,7 +1218,7 @@ const char* EXTRACTED_CONFIGS[] = { " ],\n" " \"valid-lifetime\": 4000\n" " }\n", - // CONFIGURATION 35 + // CONFIGURATION 37 "{\n" " \"interfaces-config\": {\n" " \"interfaces\": [ \"*\" ]\n" @@ -1158,7 +1230,7 @@ const char* EXTRACTED_CONFIGS[] = { " \"subnet6\": [ ],\n" " \"valid-lifetime\": 4000\n" " }\n", - // CONFIGURATION 36 + // CONFIGURATION 38 "{\n" " \"interfaces-config\": {\n" " \"interfaces\": [ \"*\" ]\n" @@ -1170,7 +1242,7 @@ const char* EXTRACTED_CONFIGS[] = { " \"subnet6\": [ ],\n" " \"valid-lifetime\": 4000\n" " }\n", - // CONFIGURATION 37 + // CONFIGURATION 39 "{\n" " \"preferred-lifetime\": 3000,\n" " \"rebind-timer\": 2000,\n" @@ -1214,7 +1286,7 @@ const char* EXTRACTED_CONFIGS[] = { " ],\n" " \"valid-lifetime\": 4000\n" " }\n", - // CONFIGURATION 38 + // CONFIGURATION 40 "{\n" " \"interfaces-config\": {\n" " \"interfaces\": [ \"*\" ]\n" @@ -1226,21 +1298,21 @@ const char* EXTRACTED_CONFIGS[] = { " \"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" @@ -1248,7 +1320,7 @@ const char* EXTRACTED_CONFIGS[] = { " },\n" " \"subnet6\": [ ]\n" " }\n", - // CONFIGURATION 42 + // CONFIGURATION 44 "{\n" " \"expired-leases-processing\": {\n" " \"flush-reclaimed-timer-wait-time\": 35,\n" @@ -1263,7 +1335,7 @@ const char* EXTRACTED_CONFIGS[] = { " },\n" " \"subnet6\": [ ]\n" " }\n", - // CONFIGURATION 43 + // CONFIGURATION 45 "{\n" " \"client-classes\": [\n" " {\n" @@ -1294,7 +1366,7 @@ const char* EXTRACTED_CONFIGS[] = { " ],\n" " \"valid-lifetime\": 4000\n" " }\n", - // CONFIGURATION 44 + // CONFIGURATION 46 "{\n" " \"interfaces-config\": {\n" " \"interfaces\": [ \"*\" ]\n" @@ -1314,7 +1386,7 @@ const char* EXTRACTED_CONFIGS[] = { " ],\n" " \"valid-lifetime\": 4000\n" " }\n", - // CONFIGURATION 45 + // CONFIGURATION 47 "{\n" " \"interfaces-config\": {\n" " \"interfaces\": [ \"*\" ]\n" @@ -1335,7 +1407,7 @@ const char* EXTRACTED_CONFIGS[] = { " ],\n" " \"valid-lifetime\": 4000\n" " }\n", - // CONFIGURATION 46 + // CONFIGURATION 48 "{\n" " \"interfaces-config\": {\n" " \"interfaces\": [ \"*\" ]\n" @@ -1361,7 +1433,7 @@ const char* EXTRACTED_CONFIGS[] = { " ],\n" " \"valid-lifetime\": 4000\n" " }\n", - // CONFIGURATION 47 + // CONFIGURATION 49 "{\n" " \"interfaces-config\": {\n" " \"interfaces\": [ \"*\" ]\n" @@ -1387,7 +1459,7 @@ const char* EXTRACTED_CONFIGS[] = { " ],\n" " \"valid-lifetime\": 4000\n" " }\n", - // CONFIGURATION 48 + // CONFIGURATION 50 "{\n" " \"interfaces-config\": {\n" " \"interfaces\": [ \"*\" ]\n" @@ -1409,7 +1481,7 @@ const char* EXTRACTED_CONFIGS[] = { " ],\n" " \"valid-lifetime\": 4000\n" " }\n", - // CONFIGURATION 49 + // CONFIGURATION 51 "{\n" " \"interfaces-config\": {\n" " \"interfaces\": [ \"*\" ]\n" @@ -1432,7 +1504,7 @@ const char* EXTRACTED_CONFIGS[] = { " ],\n" " \"valid-lifetime\": 4000\n" " }\n", - // CONFIGURATION 50 + // CONFIGURATION 52 "{\n" " \"interfaces-config\": {\n" " \"interfaces\": [ \"*\" ]\n" @@ -4323,6 +4395,194 @@ const char* UNPARSED_CONFIGS[] = { "{\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" @@ -4394,7 +4654,7 @@ const char* UNPARSED_CONFIGS[] = { " }\n" " ]\n" " }\n", - // CONFIGURATION 33 + // CONFIGURATION 35 "{\n" " \"decline-probation-period\": 86400,\n" " \"dhcp-ddns\": {\n" @@ -4588,7 +4848,7 @@ const char* UNPARSED_CONFIGS[] = { " }\n" " ]\n" " }\n", - // CONFIGURATION 34 + // CONFIGURATION 36 "{\n" " \"decline-probation-period\": 86400,\n" " \"dhcp-ddns\": {\n" @@ -4686,7 +4946,7 @@ const char* UNPARSED_CONFIGS[] = { " }\n" " ]\n" " }\n", - // CONFIGURATION 35 + // CONFIGURATION 37 "{\n" " \"decline-probation-period\": 86400,\n" " \"dhcp-ddns\": {\n" @@ -4738,7 +4998,7 @@ const char* UNPARSED_CONFIGS[] = { " \"shared-networks\": [ ],\n" " \"subnet6\": [ ]\n" " }\n", - // CONFIGURATION 36 + // CONFIGURATION 38 "{\n" " \"decline-probation-period\": 86400,\n" " \"dhcp-ddns\": {\n" @@ -4790,7 +5050,7 @@ const char* UNPARSED_CONFIGS[] = { " \"shared-networks\": [ ],\n" " \"subnet6\": [ ]\n" " }\n", - // CONFIGURATION 37 + // CONFIGURATION 39 "{\n" " \"decline-probation-period\": 86400,\n" " \"dhcp-ddns\": {\n" @@ -4931,7 +5191,7 @@ const char* UNPARSED_CONFIGS[] = { " }\n" " ]\n" " }\n", - // CONFIGURATION 38 + // CONFIGURATION 40 "{\n" " \"decline-probation-period\": 86400,\n" " \"dhcp-ddns\": {\n" @@ -4983,7 +5243,7 @@ const char* UNPARSED_CONFIGS[] = { " \"shared-networks\": [ ],\n" " \"subnet6\": [ ]\n" " }\n", - // CONFIGURATION 39 + // CONFIGURATION 41 "{\n" " \"decline-probation-period\": 86400,\n" " \"dhcp-ddns\": {\n" @@ -5035,7 +5295,7 @@ const char* UNPARSED_CONFIGS[] = { " \"shared-networks\": [ ],\n" " \"subnet6\": [ ]\n" " }\n", - // CONFIGURATION 40 + // CONFIGURATION 42 "{\n" " \"decline-probation-period\": 86400,\n" " \"dhcp-ddns\": {\n" @@ -5087,7 +5347,7 @@ const char* UNPARSED_CONFIGS[] = { " \"shared-networks\": [ ],\n" " \"subnet6\": [ ]\n" " }\n", - // CONFIGURATION 41 + // CONFIGURATION 43 "{\n" " \"decline-probation-period\": 12345,\n" " \"dhcp-ddns\": {\n" @@ -5139,7 +5399,7 @@ const char* UNPARSED_CONFIGS[] = { " \"shared-networks\": [ ],\n" " \"subnet6\": [ ]\n" " }\n", - // CONFIGURATION 42 + // CONFIGURATION 44 "{\n" " \"decline-probation-period\": 86400,\n" " \"dhcp-ddns\": {\n" @@ -5191,7 +5451,7 @@ const char* UNPARSED_CONFIGS[] = { " \"shared-networks\": [ ],\n" " \"subnet6\": [ ]\n" " }\n", - // CONFIGURATION 43 + // CONFIGURATION 45 "{\n" " \"client-classes\": [\n" " {\n" @@ -5280,7 +5540,7 @@ const char* UNPARSED_CONFIGS[] = { " }\n" " ]\n" " }\n", - // CONFIGURATION 44 + // CONFIGURATION 46 "{\n" " \"decline-probation-period\": 86400,\n" " \"dhcp-ddns\": {\n" @@ -5355,7 +5615,7 @@ const char* UNPARSED_CONFIGS[] = { " }\n" " ]\n" " }\n", - // CONFIGURATION 45 + // CONFIGURATION 47 "{\n" " \"decline-probation-period\": 86400,\n" " \"dhcp-ddns\": {\n" @@ -5431,7 +5691,7 @@ const char* UNPARSED_CONFIGS[] = { " }\n" " ]\n" " }\n", - // CONFIGURATION 46 + // CONFIGURATION 48 "{\n" " \"decline-probation-period\": 86400,\n" " \"dhcp-ddns\": {\n" @@ -5512,7 +5772,7 @@ const char* UNPARSED_CONFIGS[] = { " }\n" " ]\n" " }\n", - // CONFIGURATION 47 + // CONFIGURATION 49 "{\n" " \"decline-probation-period\": 86400,\n" " \"dhcp-ddns\": {\n" @@ -5593,7 +5853,7 @@ const char* UNPARSED_CONFIGS[] = { " }\n" " ]\n" " }\n", - // CONFIGURATION 48 + // CONFIGURATION 50 "{\n" " \"decline-probation-period\": 86400,\n" " \"dhcp-ddns\": {\n" @@ -5670,7 +5930,7 @@ const char* UNPARSED_CONFIGS[] = { " }\n" " ]\n" " }\n", - // CONFIGURATION 49 + // CONFIGURATION 51 "{\n" " \"decline-probation-period\": 86400,\n" " \"dhcp-ddns\": {\n" @@ -5748,7 +6008,7 @@ const char* UNPARSED_CONFIGS[] = { " }\n" " ]\n" " }\n", - // CONFIGURATION 50 + // CONFIGURATION 52 "{\n" " \"decline-probation-period\": 86400,\n" " \"dhcp-ddns\": {\n" diff --git a/src/bin/dhcp6/tests/shared_network_unittest.cc b/src/bin/dhcp6/tests/shared_network_unittest.cc index 38d7d3cc3c..adeab7ed7e 100644 --- a/src/bin/dhcp6/tests/shared_network_unittest.cc +++ b/src/bin/dhcp6/tests/shared_network_unittest.cc @@ -954,7 +954,41 @@ const char* NETWORKS_CONFIG[] = { " ]" " }" " ]" - "}" + "}", + +// 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. @@ -2161,4 +2195,49 @@ TEST_F(Dhcpv6SharedNetworkTest, sharedNetworkRapidCommit3) { 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 diff --git a/src/lib/dhcpsrv/alloc_engine.cc b/src/lib/dhcpsrv/alloc_engine.cc index e6945c0aa7..b4f93d2733 100644 --- a/src/lib/dhcpsrv/alloc_engine.cc +++ b/src/lib/dhcpsrv/alloc_engine.cc @@ -262,10 +262,10 @@ AllocEngine::IterativeAllocator::pickAddress(const SubnetPtr& subnet, } 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; } @@ -2572,7 +2572,8 @@ inAllowedPool(AllocEngine::ClientContext4& ctx, const IOAddress& address) { 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. diff --git a/src/lib/dhcpsrv/parsers/dhcp_parsers.cc b/src/lib/dhcpsrv/parsers/dhcp_parsers.cc index d57b94a894..4499cbe4aa 100644 --- a/src/lib/dhcpsrv/parsers/dhcp_parsers.cc +++ b/src/lib/dhcpsrv/parsers/dhcp_parsers.cc @@ -380,9 +380,12 @@ PoolParser::parse(PoolStoragePtr pools, } // 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); + } } } @@ -853,6 +856,11 @@ PdPoolParser::parse(PoolStoragePtr pools, ConstElementPtr pd_pool_) { 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 { @@ -876,9 +884,12 @@ PdPoolParser::parse(PoolStoragePtr pools, ConstElementPtr pd_pool_) { 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. diff --git a/src/lib/dhcpsrv/parsers/dhcp_parsers.h b/src/lib/dhcpsrv/parsers/dhcp_parsers.h index 0941e70b46..e195cd4bad 100644 --- a/src/lib/dhcpsrv/parsers/dhcp_parsers.h +++ b/src/lib/dhcpsrv/parsers/dhcp_parsers.h @@ -652,6 +652,9 @@ private: CfgOptionPtr options_; isc::data::ConstElementPtr user_context_; + + isc::data::ConstElementPtr client_class_; + }; /// @brief Parser for a list of prefix delegation pools. diff --git a/src/lib/dhcpsrv/pool.cc b/src/lib/dhcpsrv/pool.cc index 1e3b622160..3e41426eac 100644 --- a/src/lib/dhcpsrv/pool.cc +++ b/src/lib/dhcpsrv/pool.cc @@ -111,7 +111,6 @@ Pool::toElement() const { // Set pool options ConstCfgOptionPtr opts = getCfgOption(); map->set("option-data", opts->toElement()); - return (map); // Set client-class const ClientClasses& cclasses = getClientClasses(); @@ -121,6 +120,8 @@ Pool::toElement() const { } else if (!cclasses.empty()) { map->set("client-class", Element::create(*cclasses.cbegin())); } + + return (map); } data::ElementPtr diff --git a/src/lib/dhcpsrv/subnet.cc b/src/lib/dhcpsrv/subnet.cc index 0e0f0baca7..a07a9c47c0 100644 --- a/src/lib/dhcpsrv/subnet.cc +++ b/src/lib/dhcpsrv/subnet.cc @@ -475,6 +475,31 @@ Subnet::inPool(Lease::Type type, const isc::asiolink::IOAddress& addr) const { 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); @@ -709,6 +734,14 @@ Subnet6::toElement() const { // 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); } @@ -763,6 +796,14 @@ Subnet6::toElement() const { // 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); } diff --git a/src/lib/dhcpsrv/subnet.h b/src/lib/dhcpsrv/subnet.h index 883aa229b6..dacdad8d05 100644 --- a/src/lib/dhcpsrv/subnet.h +++ b/src/lib/dhcpsrv/subnet.h @@ -54,6 +54,19 @@ public: /// @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 diff --git a/src/lib/dhcpsrv/tests/alloc_engine4_unittest.cc b/src/lib/dhcpsrv/tests/alloc_engine4_unittest.cc index ab02757247..8f92b2ffa6 100644 --- a/src/lib/dhcpsrv/tests/alloc_engine4_unittest.cc +++ b/src/lib/dhcpsrv/tests/alloc_engine4_unittest.cc @@ -690,6 +690,44 @@ TEST_F(SharedNetworkAlloc4Test, discoverSharedNetworkClassification) { 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. @@ -888,6 +926,62 @@ TEST_F(SharedNetworkAlloc4Test, requestSharedNetworkClassification) { 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). diff --git a/src/lib/dhcpsrv/tests/alloc_engine6_unittest.cc b/src/lib/dhcpsrv/tests/alloc_engine6_unittest.cc index a8d80109ea..97c1b69daa 100644 --- a/src/lib/dhcpsrv/tests/alloc_engine6_unittest.cc +++ b/src/lib/dhcpsrv/tests/alloc_engine6_unittest.cc @@ -2185,6 +2185,58 @@ TEST_F(SharedNetworkAlloc6Test, solicitSharedNetworkClassification) { 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. diff --git a/src/lib/dhcpsrv/tests/cfg_subnets4_unittest.cc b/src/lib/dhcpsrv/tests/cfg_subnets4_unittest.cc index c8083d0c84..0e5e0c59ed 100644 --- a/src/lib/dhcpsrv/tests/cfg_subnets4_unittest.cc +++ b/src/lib/dhcpsrv/tests/cfg_subnets4_unittest.cc @@ -812,6 +812,7 @@ TEST(CfgSubnets4Test, unparsePool) { 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); @@ -841,7 +842,8 @@ TEST(CfgSubnets4Test, unparsePool) { " \"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"; diff --git a/src/lib/dhcpsrv/tests/cfg_subnets6_unittest.cc b/src/lib/dhcpsrv/tests/cfg_subnets6_unittest.cc index 575a0b8e90..df09f3301f 100644 --- a/src/lib/dhcpsrv/tests/cfg_subnets6_unittest.cc +++ b/src/lib/dhcpsrv/tests/cfg_subnets6_unittest.cc @@ -503,6 +503,7 @@ TEST(CfgSubnets6Test, unparsePool) { 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); @@ -526,7 +527,8 @@ TEST(CfgSubnets6Test, unparsePool) { " \"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" @@ -547,6 +549,7 @@ TEST(CfgSubnets6Test, unparsePdPool) { 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); @@ -577,7 +580,8 @@ TEST(CfgSubnets6Test, unparsePdPool) { " \"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" diff --git a/src/lib/dhcpsrv/tests/subnet_unittest.cc b/src/lib/dhcpsrv/tests/subnet_unittest.cc index efb4cafe1d..4226bc2eea 100644 --- a/src/lib/dhcpsrv/tests/subnet_unittest.cc +++ b/src/lib/dhcpsrv/tests/subnet_unittest.cc @@ -521,19 +521,44 @@ TEST(Subnet4Test, inRangeinPool) { // 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 @@ -1351,11 +1376,11 @@ TEST(Subnet6Test, inRangeinPool) { // 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"))); @@ -1364,6 +1389,31 @@ TEST(Subnet6Test, inRangeinPool) { // 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