From: Marcin Siodelski Date: Mon, 3 Oct 2016 09:56:20 +0000 (+0200) Subject: [5022] DHCPv6 server now supports specifying options on pool level. X-Git-Tag: trac4631a_base~3^2~7 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=0a6024687c70a2bfa3959eab83f44db66ed2104f;p=thirdparty%2Fkea.git [5022] DHCPv6 server now supports specifying options on pool level. --- diff --git a/src/bin/dhcp6/dhcp6_srv.cc b/src/bin/dhcp6/dhcp6_srv.cc index 201511eef9..85205c0b77 100644 --- a/src/bin/dhcp6/dhcp6_srv.cc +++ b/src/bin/dhcp6/dhcp6_srv.cc @@ -820,6 +820,24 @@ Dhcpv6Srv::buildCfgOptionList(const Pkt6Ptr& question, co_list.push_back(ctx.host_->getCfgOption6()); } + // Secondly, pool specific options. Pools are defined within a subnet, so + // if there is no subnet, there is nothing to do. + if (ctx.subnet_) { + BOOST_FOREACH(const AllocEngine::ResourceType& resource, + ctx.allocated_resources_) { + /// @todo This is has significant performance implications. We + /// are performing full scan of pools within this subnet to + /// find the one we're interested in. We need to implement the + /// Patricia trie based storage for pools. + PoolPtr pool = ctx.subnet_->getPool(resource.second == 128 ? + Lease::TYPE_NA : Lease::TYPE_PD, + resource.first, false); + if (pool && !pool->getCfgOption()->empty()) { + co_list.push_back(pool->getCfgOption()); + } + } + }; + // Next, subnet configured options. if (ctx.subnet_ && !ctx.subnet_->getCfgOption()->empty()) { co_list.push_back(ctx.subnet_->getCfgOption()); @@ -2316,6 +2334,9 @@ Dhcpv6Srv::processSolicit(const Pkt6Ptr& solicit) { } } + processClientFqdn(solicit, response, ctx); + assignLeases(solicit, response, ctx); + copyClientOptions(solicit, response); CfgOptionList co_list; buildCfgOptionList(solicit, ctx, co_list); @@ -2323,9 +2344,6 @@ Dhcpv6Srv::processSolicit(const Pkt6Ptr& solicit) { appendRequestedOptions(solicit, response, co_list); appendRequestedVendorOptions(solicit, response, ctx, co_list); - processClientFqdn(solicit, response, ctx); - assignLeases(solicit, response, ctx); - // Only generate name change requests if sending a Reply as a result // of receiving Rapid Commit option. if (response->getType() == DHCPV6_REPLY) { @@ -2347,6 +2365,9 @@ Dhcpv6Srv::processRequest(const Pkt6Ptr& request) { Pkt6Ptr reply(new Pkt6(DHCPV6_REPLY, request->getTransid())); + processClientFqdn(request, reply, ctx); + assignLeases(request, reply, ctx); + copyClientOptions(request, reply); CfgOptionList co_list; buildCfgOptionList(request, ctx, co_list); @@ -2354,8 +2375,6 @@ Dhcpv6Srv::processRequest(const Pkt6Ptr& request) { appendRequestedOptions(request, reply, co_list); appendRequestedVendorOptions(request, reply, ctx, co_list); - processClientFqdn(request, reply, ctx); - assignLeases(request, reply, ctx); generateFqdn(reply); createNameChangeRequests(reply, ctx); @@ -2374,6 +2393,9 @@ Dhcpv6Srv::processRenew(const Pkt6Ptr& renew) { Pkt6Ptr reply(new Pkt6(DHCPV6_REPLY, renew->getTransid())); + processClientFqdn(renew, reply, ctx); + extendLeases(renew, reply, ctx); + copyClientOptions(renew, reply); CfgOptionList co_list; buildCfgOptionList(renew, ctx, co_list); @@ -2381,8 +2403,6 @@ Dhcpv6Srv::processRenew(const Pkt6Ptr& renew) { appendRequestedOptions(renew, reply, co_list); appendRequestedVendorOptions(renew, reply, ctx, co_list); - processClientFqdn(renew, reply, ctx); - extendLeases(renew, reply, ctx); generateFqdn(reply); createNameChangeRequests(reply, ctx); @@ -2401,6 +2421,9 @@ Dhcpv6Srv::processRebind(const Pkt6Ptr& rebind) { Pkt6Ptr reply(new Pkt6(DHCPV6_REPLY, rebind->getTransid())); + processClientFqdn(rebind, reply, ctx); + extendLeases(rebind, reply, ctx); + copyClientOptions(rebind, reply); CfgOptionList co_list; buildCfgOptionList(rebind, ctx, co_list); @@ -2408,8 +2431,6 @@ Dhcpv6Srv::processRebind(const Pkt6Ptr& rebind) { appendRequestedOptions(rebind, reply, co_list); appendRequestedVendorOptions(rebind, reply, ctx, co_list); - processClientFqdn(rebind, reply, ctx); - extendLeases(rebind, reply, ctx); generateFqdn(reply); createNameChangeRequests(reply, ctx); diff --git a/src/bin/dhcp6/json_config_parser.cc b/src/bin/dhcp6/json_config_parser.cc index 3f8ae624bb..9939de3b6f 100644 --- a/src/bin/dhcp6/json_config_parser.cc +++ b/src/bin/dhcp6/json_config_parser.cc @@ -206,6 +206,8 @@ public: // Attempt to construct the local pool. pool_.reset(new Pool6(Lease::TYPE_PD, IOAddress(addr_str), prefix_len, delegated_len)); + // Merge options specified for a pool into pool configuration. + options_->copyTo(*pool_->getCfgOption()); } catch (const std::exception& ex) { // Some parameters don't exist or are invalid. Since we are not // aware whether they don't exist or are invalid, let's append diff --git a/src/bin/dhcp6/tests/config_parser_unittest.cc b/src/bin/dhcp6/tests/config_parser_unittest.cc index f47de6d369..909d01f780 100644 --- a/src/bin/dhcp6/tests/config_parser_unittest.cc +++ b/src/bin/dhcp6/tests/config_parser_unittest.cc @@ -2556,7 +2556,9 @@ TEST_F(Dhcp6ParserTest, optionDataInMultipleSubnets) { sizeof(user_class_expected)); } -TEST_F(Dhcp6ParserTest, optionDataInMultiplePools) { +// This test verifies that it is possible to specify options on +// pool levels. +TEST_F(Dhcp6ParserTest, optionDataMultiplePools) { ConstElementPtr x; string config = "{ " + genIfaceConfig() + "," "\"preferred-lifetime\": 3000," @@ -2564,20 +2566,20 @@ TEST_F(Dhcp6ParserTest, optionDataInMultiplePools) { "\"renew-timer\": 1000, " "\"subnet6\": [ { " " \"pools\": [ { " - " \"pool\": \"2001:db8:1::10 - 2001:db8:1::100\"" -/* " \"option-data\": [ {" + " \"pool\": \"2001:db8:1::10 - 2001:db8:1::100\"," + " \"option-data\": [ {" " \"name\": \"subscriber-id\"," " \"data\": \"0102030405060708090A\"," " \"csv-format\": False" - " } ]" */ + " } ]" " }," " {" - " \"pool\": \"2001:db8:1::300 - 2001:db8:1::400\"" -/* " \"option-data\": [ {" + " \"pool\": \"2001:db8:1::300 - 2001:db8:1::400\"," + " \"option-data\": [ {" " \"name\": \"user-class\"," " \"data\": \"FFFEFDFCFB\"," " \"csv-format\": False" - " } ]" */ + " } ]" " } ]," " \"pd-pools\": [ { " " \"prefix\": \"3000::\"," @@ -2615,7 +2617,7 @@ TEST_F(Dhcp6ParserTest, optionDataInMultiplePools) { PoolPtr pool = subnet->getPool(Lease::TYPE_PD, IOAddress("3000::"), false); ASSERT_TRUE(pool); Pool6Ptr pool6 = boost::dynamic_pointer_cast(pool); - + ASSERT_TRUE(pool6); OptionContainerPtr options1 = pool6->getCfgOption()->getAll("dhcp6"); ASSERT_EQ(1, options1->size()); @@ -2629,21 +2631,22 @@ TEST_F(Dhcp6ParserTest, optionDataInMultiplePools) { std::pair range1 = idx1.equal_range(D6O_SUBSCRIBER_ID); - // Expect single option with the code equal to 38. + // Expect a single Subscriber ID option instance. ASSERT_EQ(1, std::distance(range1.first, range1.second)); - const uint8_t subid_expected[] = { - 0x01, 0x02, 0x03, 0x04, 0x05, - 0x06, 0x07, 0x08, 0x09, 0x0A + const uint8_t subscriber_id_expected[] = { + 0x11, 0x22, 0x33, 0x44, 0x55, 0x66 }; // Check if option is valid in terms of code and carried data. - testOption(*range1.first, D6O_SUBSCRIBER_ID, subid_expected, - sizeof(subid_expected)); + testOption(*range1.first, D6O_SUBSCRIBER_ID, subscriber_id_expected, + sizeof(subscriber_id_expected)); -/* // Test another subnet in the same way. - Subnet6Ptr subnet2 = CfgMgr::instance().getStagingCfg()->getCfgSubnets6()-> - selectSubnet(IOAddress("2001:db8:2::4"), classify_); - ASSERT_TRUE(subnet2); - OptionContainerPtr options2 = subnet2->getCfgOption()->getAll("dhcp6"); + // Test another pool in the same way. + pool = subnet->getPool(Lease::TYPE_PD, IOAddress("3001::"), false); + ASSERT_TRUE(pool); + pool6 = boost::dynamic_pointer_cast(pool); + ASSERT_TRUE(pool6); + + OptionContainerPtr options2 = pool6->getCfgOption()->getAll("dhcp6"); ASSERT_EQ(1, options2->size()); const OptionContainerTypeIndex& idx2 = options2->get<1>(); @@ -2653,11 +2656,52 @@ TEST_F(Dhcp6ParserTest, optionDataInMultiplePools) { ASSERT_EQ(1, std::distance(range2.first, range2.second)); const uint8_t user_class_expected[] = { - 0xFF, 0xFE, 0xFD, 0xFC, 0xFB + 0xAA, 0xBB, 0xCC, 0xDD, 0xEE }; testOption(*range2.first, D6O_USER_CLASS, user_class_expected, - sizeof(user_class_expected)); */ + sizeof(user_class_expected)); + + // Test options in NA pools. + pool = subnet->getPool(Lease::TYPE_NA, IOAddress("2001:db8:1::10")); + ASSERT_TRUE(pool); + pool6 = boost::dynamic_pointer_cast(pool); + ASSERT_TRUE(pool6); + + OptionContainerPtr options3 = pool6->getCfgOption()->getAll("dhcp6"); + ASSERT_EQ(1, options3->size()); + + const OptionContainerTypeIndex& idx3 = options3->get<1>(); + std::pair range3 = + idx3.equal_range(D6O_SUBSCRIBER_ID); + ASSERT_EQ(1, std::distance(range3.first, range3.second)); + + const uint8_t subscriber_id_expected2[] = { + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A + }; + testOption(*range3.first, D6O_SUBSCRIBER_ID, subscriber_id_expected2, + sizeof(subscriber_id_expected2)); + + pool = subnet->getPool(Lease::TYPE_NA, IOAddress("2001:db8:1::300")); + ASSERT_TRUE(pool); + pool6 = boost::dynamic_pointer_cast(pool); + ASSERT_TRUE(pool6); + + OptionContainerPtr options4 = pool6->getCfgOption()->getAll("dhcp6"); + ASSERT_EQ(1, options4->size()); + + const OptionContainerTypeIndex& idx4 = options4->get<1>(); + std::pair range4 = + idx4.equal_range(D6O_USER_CLASS); + ASSERT_EQ(1, std::distance(range4.first, range4.second)); + + const uint8_t user_class_expected2[] = { + 0xFF, 0xFE, 0xFD, 0xFC, 0xFB + }; + testOption(*range4.first, D6O_USER_CLASS, user_class_expected2, + sizeof(user_class_expected2)); } // The goal of this test is to check that the option carrying a boolean diff --git a/src/bin/dhcp6/tests/dhcp6_client.cc b/src/bin/dhcp6/tests/dhcp6_client.cc index e19d5dab31..922baa28b5 100644 --- a/src/bin/dhcp6/tests/dhcp6_client.cc +++ b/src/bin/dhcp6/tests/dhcp6_client.cc @@ -8,11 +8,12 @@ #include #include #include +#include +#include +#include #include #include #include -#include -#include #include #include #include @@ -20,6 +21,7 @@ #include #include #include +#include #include #include @@ -803,6 +805,20 @@ Dhcp6Client::hasLeaseWithZeroLifetimeForPrefix(const asiolink::IOAddress& prefix return (false); } +bool +Dhcp6Client::hasOptionWithAddress(const uint16_t option_type, + const std::string& expected_address) const { + Option6AddrLstPtr opt = boost::dynamic_pointer_cast< + Option6AddrLst>(config_.findOption(option_type)); + if (opt) { + Option6AddrLst::AddressContainer addrs = opt->getAddresses(); + if (!addrs.empty()) { + return (std::find(addrs.begin(), addrs.end(), + IOAddress(expected_address)) != addrs.end()); + } + } + return (false); +} uint16_t Dhcp6Client::getStatusCode(const uint32_t iaid) const { diff --git a/src/bin/dhcp6/tests/dhcp6_client.h b/src/bin/dhcp6/tests/dhcp6_client.h index 4e40c94ef1..d2791a3977 100644 --- a/src/bin/dhcp6/tests/dhcp6_client.h +++ b/src/bin/dhcp6/tests/dhcp6_client.h @@ -468,6 +468,19 @@ public: bool hasLeaseWithZeroLifetimeForPrefix(const asiolink::IOAddress& prefix, const uint8_t prefix_len) const; + /// @brief Checks that specified option exists and contains a desired + /// address. + /// + /// The option must cast to the @ref Option6AddrLst type. The function + /// expects that this option contains at least one address and checks + /// first address for equality with @ref expected_address. + /// + /// @param option_type Option type. + /// @param expected_address Desired address. + /// @param config Configuration obtained from the server. + bool hasOptionWithAddress(const uint16_t option_type, + const std::string& expected_address) const; + /// @brief Returns the value of the global status code for the last /// transaction. uint16_t getStatusCode() const { diff --git a/src/bin/dhcp6/tests/dhcp6_test_utils.h b/src/bin/dhcp6/tests/dhcp6_test_utils.h index ab04d0e3f0..ca7fb3d6ed 100644 --- a/src/bin/dhcp6/tests/dhcp6_test_utils.h +++ b/src/bin/dhcp6/tests/dhcp6_test_utils.h @@ -624,6 +624,7 @@ public: /// @param stat_name this statistic is expected to be set to 1 void testReceiveStats(uint8_t pkt_type, const std::string& stat_name); + /// A subnet used in most tests isc::dhcp::Subnet6Ptr subnet_; diff --git a/src/bin/dhcp6/tests/rebind_unittest.cc b/src/bin/dhcp6/tests/rebind_unittest.cc index 801db22867..d5f190a458 100644 --- a/src/bin/dhcp6/tests/rebind_unittest.cc +++ b/src/bin/dhcp6/tests/rebind_unittest.cc @@ -69,6 +69,15 @@ namespace { /// - 1 subnet for eth0 and 1 subnet for eth1 /// - DOCSIS vendor config file sub-option /// +/// - Configuration 8: +/// - single subnet 3000::/32, +/// - two options specified in the subnet scope, +/// - one option specified at the global scope, +/// - two address pools: 3000::10-3000::20, 3000::40-3000::50, +/// - two prefix pools: 2001:db8:3::/64 and 2001:db8:4::/64, +/// - an option with unique value specified for each pool, so as it is +/// possible to test that pool specific options can be assigned. +/// const char* REBIND_CONFIGS[] = { // Configuration 0 "{ \"interfaces-config\": {" @@ -256,7 +265,69 @@ const char* REBIND_CONFIGS[] = { " \"interface-id\": \"\"," " \"interface\": \"eth1\"" " } ]," - "\"valid-lifetime\": 4000 }" + "\"valid-lifetime\": 4000 }", + +// Configuration 8 + "{ \"interfaces-config\": {" + " \"interfaces\": [ \"*\" ]" + "}," + "\"preferred-lifetime\": 3000," + "\"rebind-timer\": 2000, " + "\"renew-timer\": 1000, " + "\"option-data\": [ {" + " \"name\": \"dns-servers\"," + " \"data\": \"3000:1::234\"" + "}," + "{" + " \"name\": \"sntp-servers\"," + " \"data\": \"3000:2::1\"" + "} ]," + "\"subnet6\": [ { " + " \"option-data\": [ {" + " \"name\": \"dns-servers\"," + " \"data\": \"3000:1::567\"" + " }," + " {" + " \"name\": \"sntp-servers\"," + " \"data\": \"3000:2::1\"" + " } ]," + " \"pools\": [ { " + " \"pool\": \"3000::10 - 3000::20\"," + " \"option-data\": [ {" + " \"name\": \"sntp-servers\"," + " \"data\": \"3000:2::2\"" + " } ]" + " }," + " {" + " \"pool\": \"3000::40 - 3000::50\"," + " \"option-data\": [ {" + " \"name\": \"nisp-servers\"," + " \"data\": \"3000:2::3\"" + " } ]" + " } ]," + " \"pd-pools\": [ { " + " \"prefix\": \"2001:db8:3::\"," + " \"prefix-len\": 64," + " \"delegated-len\": 64," + " \"option-data\": [ {" + " \"name\": \"dns-servers\"," + " \"data\": \"3000:1::678\"" + " } ]" + " }," + " {" + " \"prefix\": \"2001:db8:4::\"," + " \"prefix-len\": 64," + " \"delegated-len\": 64," + " \"option-data\": [ {" + " \"name\": \"nis-servers\"," + " \"data\": \"3000:1::789\"" + " } ]" + " } ]," + " \"subnet\": \"3000::/32\", " + " \"interface\": \"eth0\"" + " } ]," + "\"valid-lifetime\": 4000" + "}" }; /// @brief Test fixture class for testing Rebind. @@ -1004,4 +1075,74 @@ TEST_F(RebindTest, docsisORO) { EXPECT_EQ("normal_erouter_v6.cm", config_file->getValue()); } +// This test verifies that the same options can be specified on the global +// level, subnet level and pool level. The options associated with pools +// are used when the lease is handed out from these pools. +TEST_F(RebindTest, optionsInheritance) { + Dhcp6Client client; + // Request a single address and single prefix. + ASSERT_NO_THROW(client.requestPrefix(0xabac, 64, IOAddress("2001:db8:4::"))); + ASSERT_NO_THROW(client.requestAddress(0xabca, IOAddress("3000::45"))); + // Request two options configured for the pools from which the client may get + // a lease. + client.requestOption(D6O_NAME_SERVERS); + client.requestOption(D6O_NIS_SERVERS); + client.requestOption(D6O_NISP_SERVERS); + client.requestOption(D6O_SNTP_SERVERS); + ASSERT_NO_FATAL_FAILURE(configure(REBIND_CONFIGS[8], *client.getServer())); + // Make sure we ended-up having expected number of subnets configured. + const Subnet6Collection* subnets = CfgMgr::instance().getCurrentCfg()-> + getCfgSubnets6()->getAll(); + ASSERT_EQ(1, subnets->size()); + // Perform 4-way exchange. + ASSERT_NO_THROW(client.doSARR()); + + // Simulate aging of leases. + client.fastFwdTime(1000); + + // Send Rebind message to the server. + ASSERT_NO_THROW(client.doRebind()); + + // We have provided hints so we should get leases appropriate + // for the hints we provided. + ASSERT_TRUE(client.hasLeaseForPrefix(IOAddress("2001:db8:4::"), 64)); + ASSERT_TRUE(client.hasLeaseForAddress(IOAddress("3000::45"))); + // We shouldn't have leases for the prefix and address which we didn't + // request. + ASSERT_FALSE(client.hasLeaseForPrefix(IOAddress("2001:db8:3::"), 64)); + ASSERT_FALSE(client.hasLeaseForAddress(IOAddress("3000::11"))); + + // We should have received options associated with a prefix pool and + // address pool from which we have requested the leases. We should not + // have received options associated with the remaining pools. Instead, + // we should have received options associated with a subnet. + ASSERT_TRUE(client.hasOptionWithAddress(D6O_NAME_SERVERS, "3000:1::567")); + ASSERT_TRUE(client.hasOptionWithAddress(D6O_NIS_SERVERS, "3000:1::789")); + ASSERT_TRUE(client.hasOptionWithAddress(D6O_NISP_SERVERS, "3000:2::3")); + ASSERT_TRUE(client.hasOptionWithAddress(D6O_SNTP_SERVERS, "3000:2::1")); + + // Let's now also request a prefix and an address from the remaining pools. + ASSERT_NO_THROW(client.requestPrefix(0x6806, 64, IOAddress("2001:db8:3::"))); + ASSERT_NO_THROW(client.requestAddress(0x6860, IOAddress("3000::11"))); + + client.fastFwdTime(1000); + + // Send another Rebind. + ASSERT_NO_THROW(client.doRebind()); + + // We should now have two prefixes from two distinct pools. + ASSERT_TRUE(client.hasLeaseForPrefix(IOAddress("2001:db8:3::"), 64)); + ASSERT_TRUE(client.hasLeaseForPrefix(IOAddress("2001:db8:4::"), 64)); + // We should also have two addresses from two distinct pools. + ASSERT_TRUE(client.hasLeaseForAddress(IOAddress("3000::45"))); + ASSERT_TRUE(client.hasLeaseForAddress(IOAddress("3000::11"))); + + // This time, options from all pools should have been assigned. + ASSERT_TRUE(client.hasOptionWithAddress(D6O_NAME_SERVERS, "3000:1::678")); + ASSERT_TRUE(client.hasOptionWithAddress(D6O_NIS_SERVERS, "3000:1::789")); + ASSERT_TRUE(client.hasOptionWithAddress(D6O_NISP_SERVERS, "3000:2::3")); + ASSERT_TRUE(client.hasOptionWithAddress(D6O_SNTP_SERVERS, "3000:2::2")); +} + + } // end of anonymous namespace diff --git a/src/bin/dhcp6/tests/renew_unittest.cc b/src/bin/dhcp6/tests/renew_unittest.cc index 3413d659ca..c83850804d 100644 --- a/src/bin/dhcp6/tests/renew_unittest.cc +++ b/src/bin/dhcp6/tests/renew_unittest.cc @@ -44,6 +44,15 @@ namespace { /// - 1 subnet with 2001:db8:1::/64 pool /// - DOCSIS vendor config file sub-option /// +/// - Configuration 4: +/// - single subnet 3000::/32, +/// - two options specified in the subnet scope, +/// - one option specified at the global scope, +/// - two address pools: 3000::10-3000::20, 3000::40-3000::50, +/// - two prefix pools: 2001:db8:3::/64 and 2001:db8:4::/64, +/// - an option with unique value specified for each pool, so as it is +/// possible to test that pool specific options can be assigned. +/// const char* RENEW_CONFIGS[] = { // Configuration 0 "{ \"interfaces-config\": {" @@ -117,8 +126,69 @@ const char* RENEW_CONFIGS[] = { " \"interface-id\": \"\"," " \"interface\": \"eth0\"" " } ]," - "\"valid-lifetime\": 4000 }" + "\"valid-lifetime\": 4000 }", +// Configuration 4 + "{ \"interfaces-config\": {" + " \"interfaces\": [ \"*\" ]" + "}," + "\"preferred-lifetime\": 3000," + "\"rebind-timer\": 2000, " + "\"renew-timer\": 1000, " + "\"option-data\": [ {" + " \"name\": \"dns-servers\"," + " \"data\": \"3000:1::234\"" + "}," + "{" + " \"name\": \"sntp-servers\"," + " \"data\": \"3000:2::1\"" + "} ]," + "\"subnet6\": [ { " + " \"option-data\": [ {" + " \"name\": \"dns-servers\"," + " \"data\": \"3000:1::567\"" + " }," + " {" + " \"name\": \"sntp-servers\"," + " \"data\": \"3000:2::1\"" + " } ]," + " \"pools\": [ { " + " \"pool\": \"3000::10 - 3000::20\"," + " \"option-data\": [ {" + " \"name\": \"sntp-servers\"," + " \"data\": \"3000:2::2\"" + " } ]" + " }," + " {" + " \"pool\": \"3000::40 - 3000::50\"," + " \"option-data\": [ {" + " \"name\": \"nisp-servers\"," + " \"data\": \"3000:2::3\"" + " } ]" + " } ]," + " \"pd-pools\": [ { " + " \"prefix\": \"2001:db8:3::\"," + " \"prefix-len\": 64," + " \"delegated-len\": 64," + " \"option-data\": [ {" + " \"name\": \"dns-servers\"," + " \"data\": \"3000:1::678\"" + " } ]" + " }," + " {" + " \"prefix\": \"2001:db8:4::\"," + " \"prefix-len\": 64," + " \"delegated-len\": 64," + " \"option-data\": [ {" + " \"name\": \"nis-servers\"," + " \"data\": \"3000:1::789\"" + " } ]" + " } ]," + " \"subnet\": \"3000::/32\", " + " \"interface\": \"eth0\"" + " } ]," + "\"valid-lifetime\": 4000" + "}" }; /// @brief Test fixture class for testing Renew. @@ -472,4 +542,73 @@ TEST_F(RenewTest, docsisORO) { EXPECT_EQ("normal_erouter_v6.cm", config_file->getValue()); } +// This test verifies that the same options can be specified on the global +// level, subnet level and pool level. The options associated with pools +// are used when the lease is handed out from these pools. +TEST_F(RenewTest, optionsInheritance) { + Dhcp6Client client; + // Request a single address and single prefix. + ASSERT_NO_THROW(client.requestPrefix(0xabac, 64, IOAddress("2001:db8:4::"))); + ASSERT_NO_THROW(client.requestAddress(0xabca, IOAddress("3000::45"))); + // Request two options configured for the pools from which the client may get + // a lease. + client.requestOption(D6O_NAME_SERVERS); + client.requestOption(D6O_NIS_SERVERS); + client.requestOption(D6O_NISP_SERVERS); + client.requestOption(D6O_SNTP_SERVERS); + ASSERT_NO_FATAL_FAILURE(configure(RENEW_CONFIGS[4], *client.getServer())); + // Make sure we ended-up having expected number of subnets configured. + const Subnet6Collection* subnets = CfgMgr::instance().getCurrentCfg()-> + getCfgSubnets6()->getAll(); + ASSERT_EQ(1, subnets->size()); + // Perform 4-way exchange. + ASSERT_NO_THROW(client.doSARR()); + + // Simulate aging of leases. + client.fastFwdTime(1000); + + // Send Renew message to the server. + ASSERT_NO_THROW(client.doRenew()); + + // We have provided hints so we should get leases appropriate + // for the hints we provided. + ASSERT_TRUE(client.hasLeaseForPrefix(IOAddress("2001:db8:4::"), 64)); + ASSERT_TRUE(client.hasLeaseForAddress(IOAddress("3000::45"))); + // We shouldn't have leases for the prefix and address which we didn't + // request. + ASSERT_FALSE(client.hasLeaseForPrefix(IOAddress("2001:db8:3::"), 64)); + ASSERT_FALSE(client.hasLeaseForAddress(IOAddress("3000::11"))); + + // We should have received options associated with a prefix pool and + // address pool from which we have requested the leases. We should not + // have received options associated with the remaining pools. Instead, + // we should have received options associated with a subnet. + ASSERT_TRUE(client.hasOptionWithAddress(D6O_NAME_SERVERS, "3000:1::567")); + ASSERT_TRUE(client.hasOptionWithAddress(D6O_NIS_SERVERS, "3000:1::789")); + ASSERT_TRUE(client.hasOptionWithAddress(D6O_NISP_SERVERS, "3000:2::3")); + ASSERT_TRUE(client.hasOptionWithAddress(D6O_SNTP_SERVERS, "3000:2::1")); + + // Let's now also request a prefix and an address from the remaining pools. + ASSERT_NO_THROW(client.requestPrefix(0x6806, 64, IOAddress("2001:db8:3::"))); + ASSERT_NO_THROW(client.requestAddress(0x6860, IOAddress("3000::11"))); + + client.fastFwdTime(1000); + + // Send another Renew. + ASSERT_NO_THROW(client.doRenew()); + + // We should now have two prefixes from two distinct pools. + ASSERT_TRUE(client.hasLeaseForPrefix(IOAddress("2001:db8:3::"), 64)); + ASSERT_TRUE(client.hasLeaseForPrefix(IOAddress("2001:db8:4::"), 64)); + // We should also have two addresses from two distinct pools. + ASSERT_TRUE(client.hasLeaseForAddress(IOAddress("3000::45"))); + ASSERT_TRUE(client.hasLeaseForAddress(IOAddress("3000::11"))); + + // This time, options from all pools should have been assigned. + ASSERT_TRUE(client.hasOptionWithAddress(D6O_NAME_SERVERS, "3000:1::678")); + ASSERT_TRUE(client.hasOptionWithAddress(D6O_NIS_SERVERS, "3000:1::789")); + ASSERT_TRUE(client.hasOptionWithAddress(D6O_NISP_SERVERS, "3000:2::3")); + ASSERT_TRUE(client.hasOptionWithAddress(D6O_SNTP_SERVERS, "3000:2::2")); +} + } // end of anonymous namespace diff --git a/src/bin/dhcp6/tests/sarr_unittest.cc b/src/bin/dhcp6/tests/sarr_unittest.cc index 3a500dfebd..228105ac20 100644 --- a/src/bin/dhcp6/tests/sarr_unittest.cc +++ b/src/bin/dhcp6/tests/sarr_unittest.cc @@ -39,6 +39,15 @@ namespace { /// one /// - DNS updates enabled /// +/// - Configuration 2: +/// - single subnet 3000::/32, +/// - two options specified in the subnet scope, +/// - one option specified at the global scope, +/// - two address pools: 3000::10-3000::20, 3000::40-3000::50, +/// - two prefix pools: 2001:db8:3::/64 and 2001:db8:4::/64, +/// - an option with unique value specified for each pool, so as it is +/// possible to test that pool specific options can be assigned. +/// const char* CONFIGS[] = { // Configuration 0 "{ \"interfaces-config\": {" @@ -82,6 +91,68 @@ const char* CONFIGS[] = { " \"dhcp-ddns\" : {" " \"enable-updates\" : True, " " \"qualifying-suffix\" : \"example.com\" }" + "}", + +// Configuration 2 + "{ \"interfaces-config\": {" + " \"interfaces\": [ \"*\" ]" + "}," + "\"preferred-lifetime\": 3000," + "\"rebind-timer\": 2000, " + "\"renew-timer\": 1000, " + "\"option-data\": [ {" + " \"name\": \"dns-servers\"," + " \"data\": \"3000:1::234\"" + "}," + "{" + " \"name\": \"sntp-servers\"," + " \"data\": \"3000:2::1\"" + "} ]," + "\"subnet6\": [ { " + " \"option-data\": [ {" + " \"name\": \"dns-servers\"," + " \"data\": \"3000:1::567\"" + " }," + " {" + " \"name\": \"sntp-servers\"," + " \"data\": \"3000:2::1\"" + " } ]," + " \"pools\": [ { " + " \"pool\": \"3000::10 - 3000::20\"," + " \"option-data\": [ {" + " \"name\": \"sntp-servers\"," + " \"data\": \"3000:2::2\"" + " } ]" + " }," + " {" + " \"pool\": \"3000::40 - 3000::50\"," + " \"option-data\": [ {" + " \"name\": \"nisp-servers\"," + " \"data\": \"3000:2::3\"" + " } ]" + " } ]," + " \"pd-pools\": [ { " + " \"prefix\": \"2001:db8:3::\"," + " \"prefix-len\": 64," + " \"delegated-len\": 64," + " \"option-data\": [ {" + " \"name\": \"dns-servers\"," + " \"data\": \"3000:1::678\"" + " } ]" + " }," + " {" + " \"prefix\": \"2001:db8:4::\"," + " \"prefix-len\": 64," + " \"delegated-len\": 64," + " \"option-data\": [ {" + " \"name\": \"nis-servers\"," + " \"data\": \"3000:1::789\"" + " } ]" + " } ]," + " \"subnet\": \"3000::/32\", " + " \"interface\": \"eth0\"" + " } ]," + "\"valid-lifetime\": 4000" "}" }; @@ -173,6 +244,67 @@ TEST_F(SARRTest, directClientPrefixHint) { ASSERT_TRUE(lease_server); } +// This test verifies that the same options can be specified on the global +// level, subnet level and pool level. The options associated with pools +// are used when the lease is handed out from these pools. +TEST_F(SARRTest, optionsInheritance) { + Dhcp6Client client; + // Request a single address and single prefix. + ASSERT_NO_THROW(client.requestPrefix(0xabac, 64, IOAddress("2001:db8:4::"))); + ASSERT_NO_THROW(client.requestAddress(0xabca, IOAddress("3000::45"))); + // Request two options configured for the pools from which the client may get + // a lease. + client.requestOption(D6O_NAME_SERVERS); + client.requestOption(D6O_NIS_SERVERS); + client.requestOption(D6O_NISP_SERVERS); + client.requestOption(D6O_SNTP_SERVERS); + ASSERT_NO_FATAL_FAILURE(configure(CONFIGS[2], *client.getServer())); + // Make sure we ended-up having expected number of subnets configured. + const Subnet6Collection* subnets = CfgMgr::instance().getCurrentCfg()-> + getCfgSubnets6()->getAll(); + ASSERT_EQ(1, subnets->size()); + // Perform 4-way exchange. + ASSERT_NO_THROW(client.doSARR()); + + // We have provided hints so we should get leases appropriate + // for the hints we provided. + ASSERT_TRUE(client.hasLeaseForPrefix(IOAddress("2001:db8:4::"), 64)); + ASSERT_TRUE(client.hasLeaseForAddress(IOAddress("3000::45"))); + // We shouldn't have leases for the prefix and address which we didn't + // request. + ASSERT_FALSE(client.hasLeaseForPrefix(IOAddress("2001:db8:3::"), 64)); + ASSERT_FALSE(client.hasLeaseForAddress(IOAddress("3000::11"))); + + // We should have received options associated with a prefix pool and + // address pool from which we have requested the leases. We should not + // have received options associated with the remaining pools. Instead, + // we should have received options associated with a subnet. + ASSERT_TRUE(client.hasOptionWithAddress(D6O_NAME_SERVERS, "3000:1::567")); + ASSERT_TRUE(client.hasOptionWithAddress(D6O_NIS_SERVERS, "3000:1::789")); + ASSERT_TRUE(client.hasOptionWithAddress(D6O_NISP_SERVERS, "3000:2::3")); + ASSERT_TRUE(client.hasOptionWithAddress(D6O_SNTP_SERVERS, "3000:2::1")); + + // Let's now also request a prefix and an address from the remaining pools. + ASSERT_NO_THROW(client.requestPrefix(0x6806, 64, IOAddress("2001:db8:3::"))); + ASSERT_NO_THROW(client.requestAddress(0x6860, IOAddress("3000::11"))); + + // Perform 4-way exchange again. + ASSERT_NO_THROW(client.doSARR()); + + // We should now have two prefixes from two distinct pools. + ASSERT_TRUE(client.hasLeaseForPrefix(IOAddress("2001:db8:3::"), 64)); + ASSERT_TRUE(client.hasLeaseForPrefix(IOAddress("2001:db8:4::"), 64)); + // We should also have two addresses from two distinct pools. + ASSERT_TRUE(client.hasLeaseForAddress(IOAddress("3000::45"))); + ASSERT_TRUE(client.hasLeaseForAddress(IOAddress("3000::11"))); + + // This time, options from all pools should have been assigned. + ASSERT_TRUE(client.hasOptionWithAddress(D6O_NAME_SERVERS, "3000:1::678")); + ASSERT_TRUE(client.hasOptionWithAddress(D6O_NIS_SERVERS, "3000:1::789")); + ASSERT_TRUE(client.hasOptionWithAddress(D6O_NISP_SERVERS, "3000:2::3")); + ASSERT_TRUE(client.hasOptionWithAddress(D6O_SNTP_SERVERS, "3000:2::2")); +} + // Check that when the client includes the Rapid Commit option in its // Solicit, the server responds with Reply and commits the lease. TEST_F(SARRTest, rapidCommitEnable) { diff --git a/src/lib/dhcpsrv/parsers/dhcp_parsers.cc b/src/lib/dhcpsrv/parsers/dhcp_parsers.cc index 8c6ab3684f..efd7e23e72 100644 --- a/src/lib/dhcpsrv/parsers/dhcp_parsers.cc +++ b/src/lib/dhcpsrv/parsers/dhcp_parsers.cc @@ -1039,8 +1039,8 @@ void PoolsListParser::commit() { } //****************************** PoolParser ******************************** -PoolParser::PoolParser(const std::string&, PoolStoragePtr pools) - :pools_(pools) { +PoolParser::PoolParser(const std::string&, PoolStoragePtr pools) + :pools_(pools), options_(new CfgOption()) { if (!pools_) { isc_throw(isc::dhcp::DhcpConfigError, "parser logic error: " @@ -1067,6 +1067,8 @@ PoolParser::build(ConstElementPtr pool_structure) { boost::erase_all(txt, " "); // space boost::erase_all(txt, "\t"); // tabulation + PoolPtr pool; + // Is this prefix/len notation? size_t pos = txt.find("/"); if (pos != string::npos) { @@ -1093,28 +1095,47 @@ PoolParser::build(ConstElementPtr pool_structure) { << " (" << text_pool->getPosition() << ")"); } - PoolPtr pool(poolMaker(addr, len)); + pool = poolMaker(addr, len); local_pools_.push_back(pool); - return; - } - // Is this min-max notation? - pos = txt.find("-"); - if (pos != string::npos) { - // using min-max notation - isc::asiolink::IOAddress min(txt.substr(0,pos)); - isc::asiolink::IOAddress max(txt.substr(pos + 1)); + } else { - PoolPtr pool(poolMaker(min, max)); - local_pools_.push_back(pool); - return; + // Is this min-max notation? + pos = txt.find("-"); + if (pos != string::npos) { + // using min-max notation + isc::asiolink::IOAddress min(txt.substr(0,pos)); + isc::asiolink::IOAddress max(txt.substr(pos + 1)); + + pool = poolMaker(min, max); + local_pools_.push_back(pool); + } + } + + if (!pool) { + isc_throw(DhcpConfigError, "invalid pool definition: " + << text_pool->stringValue() << + ". There are two acceptable formats " + " or (" + << text_pool->getPosition() << ")"); } - isc_throw(DhcpConfigError, "invalid pool definition: " - << text_pool->stringValue() << - ". There are two acceptable formats " - " or (" - << text_pool->getPosition() << ")"); + // Parser pool specific options. + ConstElementPtr option_data = pool_structure->get("option-data"); + if (option_data) { + try { + OptionDataListParserPtr option_parser(new OptionDataListParser("option-data", + options_, + AF_INET6)); + option_parser->build(option_data); + option_parser->commit(); + options_->copyTo(*pool->getCfgOption());; + + } catch (const std::exception& ex) { + isc_throw(isc::dhcp::DhcpConfigError, ex.what() + << " (" << option_data->getPosition() << ")"); + } + } } void diff --git a/src/lib/dhcpsrv/parsers/dhcp_parsers.h b/src/lib/dhcpsrv/parsers/dhcp_parsers.h index 8535b8eab4..4092ccb97f 100644 --- a/src/lib/dhcpsrv/parsers/dhcp_parsers.h +++ b/src/lib/dhcpsrv/parsers/dhcp_parsers.h @@ -879,6 +879,9 @@ protected: /// A temporary storage for pools configuration. It is a /// storage where pools are stored by build function. PoolStorage local_pools_; + + /// A storage for pool specific option values. + CfgOptionPtr options_; }; /// @brief Parser for a list of pools