From: Francis Dupont Date: Sun, 22 Nov 2015 08:06:29 +0000 (+0100) Subject: [4097a] Finished the DHCPv6 part X-Git-Tag: trac4204fd_base~2^2~14 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=c50a482e47be087a57e3cff6521dba035c0fa44b;p=thirdparty%2Fkea.git [4097a] Finished the DHCPv6 part --- diff --git a/src/bin/dhcp4/tests/config_parser_unittest.cc b/src/bin/dhcp4/tests/config_parser_unittest.cc index 2f5938516c..6ba213d238 100644 --- a/src/bin/dhcp4/tests/config_parser_unittest.cc +++ b/src/bin/dhcp4/tests/config_parser_unittest.cc @@ -1826,10 +1826,8 @@ TEST_F(Dhcp4ParserTest, optionStandardDefOverride) { } -// Goal of this test is to verify that global option -// data is configured for the subnet if the subnet -// configuration does not include options configuration. -TEST_F(Dhcp4ParserTest, optionDataDefaults) { +// Goal of this test is to verify that global option data is configured +TEST_F(Dhcp4ParserTest, optionDataDefaultsGlobal) { ConstElementPtr x; string config = "{ " + genIfaceConfig() + "," + "\"rebind-timer\": 2000," @@ -1855,10 +1853,78 @@ TEST_F(Dhcp4ParserTest, optionDataDefaults) { EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json)); checkResult(x, 0); + // These options are global Subnet4Ptr subnet = CfgMgr::instance().getStagingCfg()-> getCfgSubnets4()->selectSubnet(IOAddress("192.0.2.200")); ASSERT_TRUE(subnet); OptionContainerPtr options = subnet->getCfgOption()->getAll("dhcp4"); + ASSERT_EQ(0, options->size()); + + options = CfgMgr::instance().getStagingCfg()->getCfgOption()->getAll("dhcp4"); + ASSERT_EQ(2, options->size()); + + // Get the search index. Index #1 is to search using option code. + const OptionContainerTypeIndex& idx = options->get<1>(); + + // Get the options for specified index. Expecting one option to be + // returned but in theory we may have multiple options with the same + // code so we get the range. + std::pair range = + idx.equal_range(56); + // Expect single option with the code equal to 56. + ASSERT_EQ(1, std::distance(range.first, range.second)); + const uint8_t foo_expected[] = { + 0xAB, 0xCD, 0xEF, 0x01, 0x05 + }; + // Check if option is valid in terms of code and carried data. + testOption(*range.first, 56, foo_expected, sizeof(foo_expected)); + + range = idx.equal_range(23); + ASSERT_EQ(1, std::distance(range.first, range.second)); + // Do another round of testing with second option. + const uint8_t foo2_expected[] = { + 0x01 + }; + testOption(*range.first, 23, foo2_expected, sizeof(foo2_expected)); +} + +// Goal of this test is to verify that subnet option data is configured +TEST_F(Dhcp4ParserTest, optionDataDefaultsSubnet) { + ConstElementPtr x; + string config = "{ " + genIfaceConfig() + "," + + "\"rebind-timer\": 2000," + "\"renew-timer\": 1000," + "\"subnet4\": [ { " + " \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ]," + " \"subnet\": \"192.0.2.0/24\"," + " \"option-data\": [ {" + " \"name\": \"dhcp-message\"," + " \"data\": \"ABCDEF0105\"," + " \"csv-format\": False" + " }," + " {" + " \"name\": \"default-ip-ttl\"," + " \"data\": \"01\"," + " \"csv-format\": False" + " } ]" + " } ]," + "\"valid-lifetime\": 4000 }"; + + ElementPtr json = Element::fromJSON(config); + + EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json)); + checkResult(x, 0); + + // These options are subnet options + OptionContainerPtr options = + CfgMgr::instance().getStagingCfg()->getCfgOption()->getAll("dhcp4"); + ASSERT_EQ(0, options->size()); + + Subnet4Ptr subnet = CfgMgr::instance().getStagingCfg()-> + getCfgSubnets4()->selectSubnet(IOAddress("192.0.2.200")); + ASSERT_TRUE(subnet); + options = subnet->getCfgOption()->getAll("dhcp4"); ASSERT_EQ(2, options->size()); // Get the search index. Index #1 is to search using option code. @@ -1933,21 +1999,21 @@ TEST_F(Dhcp4ParserTest, optionDataTwoSpaces) { ASSERT_TRUE(status); checkResult(status, 0); - // Options should be now available for the subnet. - Subnet4Ptr subnet = CfgMgr::instance().getStagingCfg()-> - getCfgSubnets4()->selectSubnet(IOAddress("192.0.2.200")); - ASSERT_TRUE(subnet); + // Options should be now available // Try to get the option from the space dhcp4. - OptionDescriptor desc1 = subnet->getCfgOption()->get("dhcp4", 56); + OptionDescriptor desc1 = + CfgMgr::instance().getStagingCfg()->getCfgOption()->get("dhcp4", 56); ASSERT_TRUE(desc1.option_); EXPECT_EQ(56, desc1.option_->getType()); // Try to get the option from the space isc. - OptionDescriptor desc2 = subnet->getCfgOption()->get("isc", 56); + OptionDescriptor desc2 = + CfgMgr::instance().getStagingCfg()->getCfgOption()->get("isc", 56); ASSERT_TRUE(desc2.option_); EXPECT_EQ(56, desc1.option_->getType()); // Try to get the non-existing option from the non-existing // option space and expect that option is not returned. - OptionDescriptor desc3 = subnet->getCfgOption()->get("non-existing", 56); + OptionDescriptor desc3 = + CfgMgr::instance().getStagingCfg()->getCfgOption()->get("non-existing", 56); ASSERT_FALSE(desc3.option_); } @@ -1960,8 +2026,8 @@ TEST_F(Dhcp4ParserTest, optionDataTwoSpaces) { TEST_F(Dhcp4ParserTest, optionDataEncapsulate) { // @todo DHCP configurations has many dependencies between - // parameters. First of all, configuration for subnet is - // inherited from the global values. Thus subnet has to be + // parameters. First of all, configuration for subnet was + // inherited from the global values. Thus subnet had to be // configured when all global values have been configured. // Also, an option can encapsulate another option only // if the latter has been configured. For this reason in this @@ -2011,7 +2077,7 @@ TEST_F(Dhcp4ParserTest, optionDataEncapsulate) { CfgMgr::instance().clear(); // Stage 2. Configure base option and a subnet. Please note that - // the configuration from the stage 2 is repeated because BIND + // the configuration from the stage 2 is repeated because Kea // configuration manager sends whole configuration for the lists // where at least one element is being modified or added. config = "{ " + genIfaceConfig() + "," + @@ -2063,18 +2129,15 @@ TEST_F(Dhcp4ParserTest, optionDataEncapsulate) { ASSERT_TRUE(status); checkResult(status, 0); - // Get the subnet. - Subnet4Ptr subnet = CfgMgr::instance().getStagingCfg()-> - getCfgSubnets4()->selectSubnet(IOAddress("192.0.2.5")); - ASSERT_TRUE(subnet); - // We should have one option available. - OptionContainerPtr options = subnet->getCfgOption()->getAll("dhcp4"); + OptionContainerPtr options = + CfgMgr::instance().getStagingCfg()->getCfgOption()->getAll("dhcp4"); ASSERT_TRUE(options); ASSERT_EQ(1, options->size()); // Get the option. - OptionDescriptor desc = subnet->getCfgOption()->get("dhcp4", 222); + OptionDescriptor desc = + CfgMgr::instance().getStagingCfg()->getCfgOption()->get("dhcp4", 222); EXPECT_TRUE(desc.option_); EXPECT_EQ(222, desc.option_->getType()); @@ -2605,19 +2668,15 @@ TEST_F(Dhcp4ParserTest, stdOptionDataEncapsulate) { ASSERT_TRUE(status); checkResult(status, 0); - // Get the subnet. - Subnet4Ptr subnet = CfgMgr::instance().getStagingCfg()-> - getCfgSubnets4()->selectSubnet(IOAddress("192.0.2.5")); - ASSERT_TRUE(subnet); - // We should have one option available. - OptionContainerPtr options = subnet->getCfgOption()->getAll("dhcp4"); + OptionContainerPtr options = + CfgMgr::instance().getStagingCfg()->getCfgOption()->getAll("dhcp4"); ASSERT_TRUE(options); ASSERT_EQ(1, options->size()); // Get the option. - OptionDescriptor desc = - subnet->getCfgOption()->get("dhcp4", DHO_VENDOR_ENCAPSULATED_OPTIONS); + OptionDescriptor desc = CfgMgr::instance().getStagingCfg()-> + getCfgOption()->get("dhcp4", DHO_VENDOR_ENCAPSULATED_OPTIONS); EXPECT_TRUE(desc.option_); EXPECT_EQ(DHO_VENDOR_ENCAPSULATED_OPTIONS, desc.option_->getType()); @@ -2651,7 +2710,7 @@ TEST_F(Dhcp4ParserTest, stdOptionDataEncapsulate) { } // This test checks if vendor options can be specified in the config file -// (in hex format), and later retrieved from configured subnet +// (in hex format), and later retrieved TEST_F(Dhcp4ParserTest, vendorOptionsHex) { // This configuration string is to configure two options @@ -2689,28 +2748,26 @@ TEST_F(Dhcp4ParserTest, vendorOptionsHex) { ASSERT_TRUE(status); checkResult(status, 0); - // Options should be now available for the subnet. - Subnet4Ptr subnet = CfgMgr::instance().getStagingCfg()-> - getCfgSubnets4()->selectSubnet(IOAddress("192.0.2.5")); - ASSERT_TRUE(subnet); - // Try to get the option from the vendor space 4491 - OptionDescriptor desc1 = subnet->getCfgOption()->get(VENDOR_ID_CABLE_LABS, 100); + OptionDescriptor desc1 = CfgMgr::instance().getStagingCfg()-> + getCfgOption()->get(VENDOR_ID_CABLE_LABS, 100); ASSERT_TRUE(desc1.option_); EXPECT_EQ(100, desc1.option_->getType()); // Try to get the option from the vendor space 1234 - OptionDescriptor desc2 = subnet->getCfgOption()->get(1234, 100); + OptionDescriptor desc2 = + CfgMgr::instance().getStagingCfg()->getCfgOption()->get(1234, 100); ASSERT_TRUE(desc2.option_); EXPECT_EQ(100, desc1.option_->getType()); // Try to get the non-existing option from the non-existing // option space and expect that option is not returned. - OptionDescriptor desc3 = subnet->getCfgOption()->get(5678, 100); + OptionDescriptor desc3 = + CfgMgr::instance().getStagingCfg()->getCfgOption()->get(5678, 100); ASSERT_FALSE(desc3.option_); } // This test checks if vendor options can be specified in the config file, -// (in csv format), and later retrieved from configured subnet +// (in csv format), and later retrieved TEST_F(Dhcp4ParserTest, vendorOptionsCsv) { // This configuration string is to configure two options @@ -2746,19 +2803,16 @@ TEST_F(Dhcp4ParserTest, vendorOptionsCsv) { ASSERT_TRUE(status); checkResult(status, 0); - // Options should be now available for the subnet. - Subnet4Ptr subnet = CfgMgr::instance().getStagingCfg()-> - getCfgSubnets4()->selectSubnet(IOAddress("192.0.2.5")); - ASSERT_TRUE(subnet); - // Try to get the option from the vendor space 4491 - OptionDescriptor desc1 = subnet->getCfgOption()->get(VENDOR_ID_CABLE_LABS, 100); + OptionDescriptor desc1 = CfgMgr::instance().getStagingCfg()-> + getCfgOption()->get(VENDOR_ID_CABLE_LABS, 100); ASSERT_TRUE(desc1.option_); EXPECT_EQ(100, desc1.option_->getType()); // Try to get the non-existing option from the non-existing // option space and expect that option is not returned. - OptionDescriptor desc2 = subnet->getCfgOption()->get(5678, 100); + OptionDescriptor desc2 = + CfgMgr::instance().getStagingCfg()->getCfgOption()->get(5678, 100); ASSERT_FALSE(desc2.option_); } diff --git a/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc b/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc index 18108bafe9..477be2ec56 100644 --- a/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc +++ b/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc @@ -1844,8 +1844,10 @@ TEST_F(Dhcpv4SrvTest, subnetClassPriority) { } // Checks class options have the priority over global options -// Note it is not currently the case, cf #4205 TEST_F(Dhcpv4SrvTest, classGlobalPriority) { + IfaceMgrTestConfig test_config(true); + IfaceMgr::instance().openSockets4(); + NakedDhcpv4Srv srv(0); // A global ip-forwarding option is set in the response. @@ -1914,13 +1916,13 @@ TEST_F(Dhcpv4SrvTest, classGlobalPriority) { ASSERT_TRUE(opt); ASSERT_GT(opt->len(), opt->getHeaderLen()); // Classification sets the value to true/1, global to false/0 - // Here class should have the priority but hasn't, cf #4205 - EXPECT_EQ(0, opt->getUint8()); + // Here class has the priority + EXPECT_NE(0, opt->getUint8()); } // Checks if the client-class field is indeed used for subnet selection. // Note that packet classification is already checked in Dhcpv4SrvTest -// .*clientClassification above. +// .*Classification above. TEST_F(Dhcpv4SrvTest, clientClassify) { // This test configures 2 subnets. We actually only need the diff --git a/src/bin/dhcp6/dhcp6_srv.cc b/src/bin/dhcp6/dhcp6_srv.cc index 6d2bb42920..99fb06e7b8 100644 --- a/src/bin/dhcp6/dhcp6_srv.cc +++ b/src/bin/dhcp6/dhcp6_srv.cc @@ -884,16 +884,16 @@ Dhcpv6Srv::copyClientOptions(const Pkt6Ptr& question, Pkt6Ptr& answer) { } void -Dhcpv6Srv::appendDefaultOptions(const Pkt6Ptr&, Pkt6Ptr& answer) { +Dhcpv6Srv::appendDefaultOptions(const Pkt6Ptr&, Pkt6Ptr& answer, + const CfgOptionList&) { // add server-id answer->addOption(getServerID()); } void Dhcpv6Srv::buildCfgOptionList(const Pkt6Ptr& question, - AllocEngine::ClientContext6& ctx) { - CfgOptionList& co_list = getCfgOptionList(); - + AllocEngine::ClientContext6& ctx, + CfgOptionList& co_list) { // First subnet configured options if (ctx.subnet_) { co_list.push_back(ctx.subnet_->getCfgOption()); @@ -921,7 +921,8 @@ Dhcpv6Srv::buildCfgOptionList(const Pkt6Ptr& question, void Dhcpv6Srv::appendRequestedOptions(const Pkt6Ptr& question, Pkt6Ptr& answer, - AllocEngine::ClientContext6& ctx) { + AllocEngine::ClientContext6& ctx, + const CfgOptionList& co_list) { // Client requests some options using ORO option. Try to // get this option from client's message. @@ -938,7 +939,6 @@ Dhcpv6Srv::appendRequestedOptions(const Pkt6Ptr& question, Pkt6Ptr& answer, const std::vector& requested_opts = option_oro->getValues(); BOOST_FOREACH(uint16_t opt, requested_opts) { // Iterate on the configured option list - const CfgOptionList& co_list = getCfgOptionList(); for (CfgOptionList::const_iterator copts = co_list.begin(); copts != co_list.end(); ++copts) { OptionDescriptor desc = (*copts)->get("dhcp6", opt); @@ -952,8 +952,10 @@ Dhcpv6Srv::appendRequestedOptions(const Pkt6Ptr& question, Pkt6Ptr& answer, } void -Dhcpv6Srv::appendRequestedVendorOptions(const Pkt6Ptr& question, Pkt6Ptr& answer, - AllocEngine::ClientContext6& ctx) { +Dhcpv6Srv::appendRequestedVendorOptions(const Pkt6Ptr& question, + Pkt6Ptr& answer, + AllocEngine::ClientContext6& ctx, + const CfgOptionList& co_list) { // Leave if there is no subnet matching the incoming packet. // There is no need to log the error message here because @@ -990,7 +992,6 @@ Dhcpv6Srv::appendRequestedVendorOptions(const Pkt6Ptr& question, Pkt6Ptr& answer bool added = false; const std::vector& requested_opts = oro->getValues(); BOOST_FOREACH(uint16_t opt, requested_opts) { - const CfgOptionList& co_list = getCfgOptionList(); for (CfgOptionList::const_iterator copts = co_list.begin(); copts != co_list.end(); ++copts) { OptionDescriptor desc = (*copts)->get(vendor_id, opt); @@ -2320,10 +2321,11 @@ Dhcpv6Srv::processSolicit(const Pkt6Ptr& solicit) { } copyClientOptions(solicit, response); - buildCfgOptionList(solicit, ctx); - appendDefaultOptions(solicit, response); - appendRequestedOptions(solicit, response, ctx); - appendRequestedVendorOptions(solicit, response, ctx); + CfgOptionList co_list; + buildCfgOptionList(solicit, ctx, co_list); + appendDefaultOptions(solicit, response, co_list); + appendRequestedOptions(solicit, response, ctx, co_list); + appendRequestedVendorOptions(solicit, response, ctx, co_list); processClientFqdn(solicit, response, ctx); assignLeases(solicit, response, ctx); @@ -2348,10 +2350,11 @@ Dhcpv6Srv::processRequest(const Pkt6Ptr& request) { Pkt6Ptr reply(new Pkt6(DHCPV6_REPLY, request->getTransid())); copyClientOptions(request, reply); - buildCfgOptionList(request, ctx); - appendDefaultOptions(request, reply); - appendRequestedOptions(request, reply, ctx); - appendRequestedVendorOptions(request, reply, ctx); + CfgOptionList co_list; + buildCfgOptionList(request, ctx, co_list); + appendDefaultOptions(request, reply, co_list); + appendRequestedOptions(request, reply, ctx, co_list); + appendRequestedVendorOptions(request, reply, ctx, co_list); processClientFqdn(request, reply, ctx); assignLeases(request, reply, ctx); @@ -2372,9 +2375,10 @@ Dhcpv6Srv::processRenew(const Pkt6Ptr& renew) { Pkt6Ptr reply(new Pkt6(DHCPV6_REPLY, renew->getTransid())); copyClientOptions(renew, reply); - buildCfgOptionList(renew, ctx); - appendDefaultOptions(renew, reply); - appendRequestedOptions(renew, reply, ctx); + CfgOptionList co_list; + buildCfgOptionList(renew, ctx, co_list); + appendDefaultOptions(renew, reply, co_list); + appendRequestedOptions(renew, reply, ctx, co_list); processClientFqdn(renew, reply, ctx); extendLeases(renew, reply, ctx); @@ -2395,9 +2399,10 @@ Dhcpv6Srv::processRebind(const Pkt6Ptr& rebind) { Pkt6Ptr reply(new Pkt6(DHCPV6_REPLY, rebind->getTransid())); copyClientOptions(rebind, reply); - buildCfgOptionList(rebind, ctx); - appendDefaultOptions(rebind, reply); - appendRequestedOptions(rebind, reply, ctx); + CfgOptionList co_list; + buildCfgOptionList(rebind, ctx, co_list); + appendDefaultOptions(rebind, reply, co_list); + appendRequestedOptions(rebind, reply, ctx, co_list); processClientFqdn(rebind, reply, ctx); extendLeases(rebind, reply, ctx); @@ -2426,9 +2431,10 @@ Dhcpv6Srv::processConfirm(const Pkt6Ptr& confirm) { Pkt6Ptr reply(new Pkt6(DHCPV6_REPLY, confirm->getTransid())); // Make sure that the necessary options are included. copyClientOptions(confirm, reply); - buildCfgOptionList(confirm, ctx); - appendDefaultOptions(confirm, reply); - appendRequestedOptions(confirm, reply, ctx); + CfgOptionList co_list; + buildCfgOptionList(confirm, ctx, co_list); + appendDefaultOptions(confirm, reply, co_list); + appendRequestedOptions(confirm, reply, ctx, co_list); // Indicates if at least one address has been verified. If no addresses // are verified it means that the client has sent no IA_NA options // or no IAAddr options and that client's message has to be discarded. @@ -2507,7 +2513,9 @@ Dhcpv6Srv::processRelease(const Pkt6Ptr& release) { Pkt6Ptr reply(new Pkt6(DHCPV6_REPLY, release->getTransid())); copyClientOptions(release, reply); - appendDefaultOptions(release, reply); + CfgOptionList co_list; + // buildCfgOptionList(release, ctx, co_list); + appendDefaultOptions(release, reply, co_list); releaseLeases(release, reply, ctx); @@ -2532,8 +2540,12 @@ Dhcpv6Srv::processDecline(const Pkt6Ptr& decline) { // Copy client options (client-id, also relay information if present) copyClientOptions(decline, reply); + // Get the configured option list + CfgOptionList co_list; + buildCfgOptionList(decline, ctx, co_list); + // Include server-id - appendDefaultOptions(decline, reply); + appendDefaultOptions(decline, reply, co_list); if (declineLeases(decline, reply, ctx)) { return (reply); @@ -2808,15 +2820,16 @@ Dhcpv6Srv::processInfRequest(const Pkt6Ptr& inf_request) { copyClientOptions(inf_request, reply); // Build the configured option list for append methods - buildCfgOptionList(inf_request, ctx); + CfgOptionList co_list; + buildCfgOptionList(inf_request, ctx, co_list); // Append default options, i.e. options that the server is supposed // to put in all messages it sends (server-id for now, but possibly other // options once we start supporting authentication) - appendDefaultOptions(inf_request, reply); + appendDefaultOptions(inf_request, reply, co_list); // Try to assign options that were requested by the client. - appendRequestedOptions(inf_request, reply, ctx); + appendRequestedOptions(inf_request, reply, ctx, co_list); return (reply); } diff --git a/src/bin/dhcp6/dhcp6_srv.h b/src/bin/dhcp6/dhcp6_srv.h index d8bb024b06..ad5c1e00b4 100644 --- a/src/bin/dhcp6/dhcp6_srv.h +++ b/src/bin/dhcp6/dhcp6_srv.h @@ -429,24 +429,17 @@ protected: /// @param answer server's message (options will be copied here) void copyClientOptions(const Pkt6Ptr& question, Pkt6Ptr& answer); - /// @brief Returns the configured option list - CfgOptionList& getCfgOptionList() { - return (cfg_option_list_); - } - - /// @brief Returns the configured option list - const CfgOptionList& getCfgOptionList() const { - return (cfg_option_list_); - } - /// @brief Build the configured option list /// /// @note The configured option list is an *ordered* list of /// @c CfgOption objects used to append options to the response. /// - /// @param ex The exchange where the configured option list is cached + /// @param question client's message + /// @param ctx client context (for the subnet) + /// @param co_list configured option list to build void buildCfgOptionList(const Pkt6Ptr& question, - AllocEngine::ClientContext6& ctx); + AllocEngine::ClientContext6& ctx, + CfgOptionList& co_list); /// @brief Appends default options to server's answer. /// @@ -456,7 +449,9 @@ protected: /// /// @param question client's message /// @param answer server's message (options will be added here) - void appendDefaultOptions(const Pkt6Ptr& question, Pkt6Ptr& answer); + /// @param co_list configured option list (currently unused) + void appendDefaultOptions(const Pkt6Ptr& question, Pkt6Ptr& answer, + const CfgOptionList& co_list); /// @brief Appends requested options to server's answer. /// @@ -465,8 +460,10 @@ protected: /// @param question client's message /// @param answer server's message (options will be added here) /// @param ctx client context (contains subnet, duid and other parameters) + /// @param co_list configured option list void appendRequestedOptions(const Pkt6Ptr& question, Pkt6Ptr& answer, - AllocEngine::ClientContext6& ctx); + AllocEngine::ClientContext6& ctx, + const CfgOptionList& co_list); /// @brief Appends requested vendor options to server's answer. /// @@ -476,8 +473,10 @@ protected: /// @param question client's message /// @param answer server's message (vendor options will be added here) /// @param ctx client context (contains subnet, duid and other parameters) + /// @param co_list configured option list void appendRequestedVendorOptions(const Pkt6Ptr& question, Pkt6Ptr& answer, - AllocEngine::ClientContext6& ctx); + AllocEngine::ClientContext6& ctx, + const CfgOptionList& co_list); /// @brief Assigns leases. /// @@ -825,9 +824,6 @@ private: /// UDP port number on which server listens. uint16_t port_; - /// @brief Configured option list for appending otions. - CfgOptionList cfg_option_list_; - protected: /// Indicates if shutdown is in progress. Setting it to true will diff --git a/src/bin/dhcp6/tests/config_parser_unittest.cc b/src/bin/dhcp6/tests/config_parser_unittest.cc index d08ee4a0c0..5da2d23409 100644 --- a/src/bin/dhcp6/tests/config_parser_unittest.cc +++ b/src/bin/dhcp6/tests/config_parser_unittest.cc @@ -2057,10 +2057,8 @@ TEST_F(Dhcp6ParserTest, optionStandardDefOverride) { EXPECT_FALSE(def->getArrayType()); } -// Goal of this test is to verify that global option -// data is configured for the subnet if the subnet -// configuration does not include options configuration. -TEST_F(Dhcp6ParserTest, optionDataDefaults) { +// Goal of this test is to verify that global option data is configured +TEST_F(Dhcp6ParserTest, optionDataDefaultsGlobal) { ConstElementPtr x; string config = "{ " + genIfaceConfig() + "," "\"preferred-lifetime\": 3000," @@ -2086,10 +2084,86 @@ TEST_F(Dhcp6ParserTest, optionDataDefaults) { EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json)); checkResult(x, 0); + // These options are global Subnet6Ptr subnet = CfgMgr::instance().getStagingCfg()->getCfgSubnets6()-> selectSubnet(IOAddress("2001:db8:1::5"), classify_); ASSERT_TRUE(subnet); OptionContainerPtr options = subnet->getCfgOption()->getAll("dhcp6"); + ASSERT_EQ(0, options->size()); + + options = CfgMgr::instance().getStagingCfg()->getCfgOption()->getAll("dhcp6"); + ASSERT_EQ(2, options->size()); + + // Get the search index. Index #1 is to search using option code. + const OptionContainerTypeIndex& idx = options->get<1>(); + + // Get the options for specified index. Expecting one option to be + // returned but in theory we may have multiple options with the same + // code so we get the range. + std::pair range = + idx.equal_range(D6O_SUBSCRIBER_ID); + // Expect single option with the code equal to 38. + ASSERT_EQ(1, std::distance(range.first, range.second)); + const uint8_t subid_expected[] = { + 0xAB, 0xCD, 0xEF, 0x01, 0x05 + }; + // Check if option is valid in terms of code and carried data. + testOption(*range.first, D6O_SUBSCRIBER_ID, subid_expected, + sizeof(subid_expected)); + + range = idx.equal_range(D6O_PREFERENCE); + ASSERT_EQ(1, std::distance(range.first, range.second)); + // Do another round of testing with second option. + const uint8_t pref_expected[] = { + 0x01 + }; + testOption(*range.first, D6O_PREFERENCE, pref_expected, + sizeof(pref_expected)); + + // Check that options with other option codes are not returned. + for (uint16_t code = 47; code < 57; ++code) { + range = idx.equal_range(code); + EXPECT_EQ(0, std::distance(range.first, range.second)); + } +} + +// Goal of this test is to verify that subnet option data is configured +TEST_F(Dhcp6ParserTest, optionDataDefaultsSubnet) { + ConstElementPtr x; + string config = "{ " + genIfaceConfig() + "," + "\"preferred-lifetime\": 3000," + "\"rebind-timer\": 2000," + "\"renew-timer\": 1000," + "\"subnet6\": [ { " + " \"pools\": [ { \"pool\": \"2001:db8:1::/80\" } ]," + " \"subnet\": \"2001:db8:1::/64\"," + " \"option-data\": [ {" + " \"name\": \"subscriber-id\"," + " \"data\": \"ABCDEF0105\"," + " \"csv-format\": False" + " }," + " {" + " \"name\": \"preference\"," + " \"data\": \"01\"" + " } ]" + " } ]," + "\"valid-lifetime\": 4000 }"; + + ElementPtr json = Element::fromJSON(config); + + EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json)); + checkResult(x, 0); + + // These options are subnet options + OptionContainerPtr options = + CfgMgr::instance().getStagingCfg()->getCfgOption()->getAll("dhcp6"); + ASSERT_EQ(0, options->size()); + + Subnet6Ptr subnet = CfgMgr::instance().getStagingCfg()->getCfgSubnets6()-> + selectSubnet(IOAddress("2001:db8:1::5"), classify_); + ASSERT_TRUE(subnet); + options = subnet->getCfgOption()->getAll("dhcp6"); ASSERT_EQ(2, options->size()); // Get the search index. Index #1 is to search using option code. @@ -2173,21 +2247,21 @@ TEST_F(Dhcp6ParserTest, optionDataTwoSpaces) { ASSERT_TRUE(status); checkResult(status, 0); - // Options should be now available for the subnet. - Subnet6Ptr subnet = CfgMgr::instance().getStagingCfg()->getCfgSubnets6()-> - selectSubnet(IOAddress("2001:db8:1::5"), classify_); - ASSERT_TRUE(subnet); + // Options should be now available // Try to get the option from the space dhcp6. - OptionDescriptor desc1 = subnet->getCfgOption()->get("dhcp6", 38); + OptionDescriptor desc1 = + CfgMgr::instance().getStagingCfg()->getCfgOption()->get("dhcp6", 38); ASSERT_TRUE(desc1.option_); EXPECT_EQ(38, desc1.option_->getType()); // Try to get the option from the space isc. - OptionDescriptor desc2 = subnet->getCfgOption()->get("isc", 38); + OptionDescriptor desc2 = + CfgMgr::instance().getStagingCfg()->getCfgOption()->get("isc", 38); ASSERT_TRUE(desc2.option_); EXPECT_EQ(38, desc1.option_->getType()); // Try to get the non-existing option from the non-existing // option space and expect that option is not returned. - OptionDescriptor desc3 = subnet->getCfgOption()->get("non-existing", 38); + OptionDescriptor desc3 = CfgMgr::instance().getStagingCfg()-> + getCfgOption()->get("non-existing", 38); ASSERT_FALSE(desc3.option_); } @@ -2306,18 +2380,15 @@ TEST_F(Dhcp6ParserTest, optionDataEncapsulate) { ASSERT_TRUE(status); checkResult(status, 0); - // Get the subnet. - Subnet6Ptr subnet = CfgMgr::instance().getStagingCfg()->getCfgSubnets6()-> - selectSubnet(IOAddress("2001:db8:1::5"), classify_); - ASSERT_TRUE(subnet); - // We should have one option available. - OptionContainerPtr options = subnet->getCfgOption()->getAll("dhcp6"); + OptionContainerPtr options = + CfgMgr::instance().getStagingCfg()->getCfgOption()->getAll("dhcp6"); ASSERT_TRUE(options); ASSERT_EQ(1, options->size()); // Get the option. - OptionDescriptor desc = subnet->getCfgOption()->get("dhcp6", 100); + OptionDescriptor desc = + CfgMgr::instance().getStagingCfg()->getCfgOption()->get("dhcp6", 100); EXPECT_TRUE(desc.option_); EXPECT_EQ(100, desc.option_->getType()); @@ -2681,23 +2752,22 @@ TEST_F(Dhcp6ParserTest, vendorOptionsHex) { ASSERT_TRUE(status); checkResult(status, 0); - // Options should be now available for the subnet. - Subnet6Ptr subnet = CfgMgr::instance().getStagingCfg()->getCfgSubnets6()-> - selectSubnet(IOAddress("2001:db8:1::5"), classify_); - ASSERT_TRUE(subnet); - + // Options should be now available // Try to get the option from the vendor space 4491 - OptionDescriptor desc1 = subnet->getCfgOption()->get(4491, 100); + OptionDescriptor desc1 = + CfgMgr::instance().getStagingCfg()->getCfgOption()->get(4491, 100); ASSERT_TRUE(desc1.option_); EXPECT_EQ(100, desc1.option_->getType()); // Try to get the option from the vendor space 1234 - OptionDescriptor desc2 = subnet->getCfgOption()->get(1234, 100); + OptionDescriptor desc2 = + CfgMgr::instance().getStagingCfg()->getCfgOption()->get(1234, 100); ASSERT_TRUE(desc2.option_); EXPECT_EQ(100, desc1.option_->getType()); // Try to get the non-existing option from the non-existing // option space and expect that option is not returned. - OptionDescriptor desc3 = subnet->getCfgOption()->get(5678, 38); + OptionDescriptor desc3 = + CfgMgr::instance().getStagingCfg()->getCfgOption()->get(5678, 38); ASSERT_FALSE(desc3.option_); } @@ -2739,19 +2809,17 @@ TEST_F(Dhcp6ParserTest, vendorOptionsCsv) { ASSERT_TRUE(status); checkResult(status, 0); - // Options should be now available for the subnet. - Subnet6Ptr subnet = CfgMgr::instance().getStagingCfg()->getCfgSubnets6()-> - selectSubnet(IOAddress("2001:db8:1::5"), classify_); - ASSERT_TRUE(subnet); - + // Options should be now available. // Try to get the option from the vendor space 4491 - OptionDescriptor desc1 = subnet->getCfgOption()->get(4491, 100); + OptionDescriptor desc1 = + CfgMgr::instance().getStagingCfg()->getCfgOption()->get(4491, 100); ASSERT_TRUE(desc1.option_); EXPECT_EQ(100, desc1.option_->getType()); // Try to get the non-existing option from the non-existing // option space and expect that option is not returned. - OptionDescriptor desc2 = subnet->getCfgOption()->get(5678, 100); + OptionDescriptor desc2 = + CfgMgr::instance().getStagingCfg()->getCfgOption()->get(5678, 100); ASSERT_FALSE(desc2.option_); } diff --git a/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc b/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc index a5d1c1519c..cfd4c6726f 100644 --- a/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc +++ b/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc @@ -1807,8 +1807,8 @@ TEST_F(Dhcpv6SrvTest, unpackOptions) { EXPECT_EQ(0x0, option_bar->getValue()); } -// Checks if client packets are classified properly -TEST_F(Dhcpv6SrvTest, clientClassification) { +// Checks if DOCSIS client packets are classified properly +TEST_F(Dhcpv6SrvTest, docsisClientClassification) { NakedDhcpv6Srv srv(0); @@ -1836,10 +1836,252 @@ TEST_F(Dhcpv6SrvTest, clientClassification) { EXPECT_FALSE(sol2->inClass(srv.VENDOR_CLASS_PREFIX + "docsis3.0")); } +// Checks if client packets are classified properly using match expressions. +TEST_F(Dhcpv6SrvTest, matchClassification) { + IfaceMgrTestConfig test_config(true); + + NakedDhcpv6Srv srv(0); + + // The router class matches incoming packets with foo in a host-name + // option (code 1234) and sets an ipv6-forwarding option in the response. + string config = "{ \"interfaces-config\": {" + " \"interfaces\": [ \"*\" ] }, " + "\"preferred-lifetime\": 3000," + "\"rebind-timer\": 2000, " + "\"renew-timer\": 1000, " + "\"valid-lifetime\": 4000, " + "\"option-def\": [ " + "{ \"name\": \"host-name\"," + " \"code\": 1234," + " \"type\": \"string\" }," + "{ \"name\": \"ipv6-forwarding\"," + " \"code\": 2345," + " \"type\": \"boolean\" }]," + "\"subnet6\": [ " + "{ \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ], " + " \"subnet\": \"2001:db8:1::/48\", " + " \"interface\": \"eth1\" } ]," + "\"client-classes\": [ " + "{ \"name\": \"router\", " + " \"option-data\": [" + " { \"name\": \"ipv6-forwarding\", " + " \"data\": \"true\" } ], " + " \"test\": \"option[1234] == 'foo'\" } ] }"; + ASSERT_NO_THROW(configure(config)); + + // Create packets with enough to select the subnet + OptionPtr clientid = generateClientId(); + Pkt6Ptr query1(new Pkt6(DHCPV6_SOLICIT, 1234)); + query1->setRemoteAddr(IOAddress("fe80::abcd")); + query1->addOption(clientid); + query1->setIface("eth1"); + query1->addOption(generateIA(D6O_IA_NA, 123, 1500, 3000)); + Pkt6Ptr query2(new Pkt6(DHCPDISCOVER, 1234)); + query2->setRemoteAddr(IOAddress("fe80::abcd")); + query2->addOption(clientid); + query2->setIface("eth1"); + query2->addOption(generateIA(D6O_IA_NA, 234, 1500, 3000)); + Pkt6Ptr query3(new Pkt6(DHCPDISCOVER, 1234)); + query3->setRemoteAddr(IOAddress("fe80::abcd")); + query3->addOption(clientid); + query3->setIface("eth1"); + query3->addOption(generateIA(D6O_IA_NA, 345, 1500, 3000)); + + // Create and add an ORO option to the first 2 queries + OptionUint16ArrayPtr oro(new OptionUint16Array(Option::V6, D6O_ORO)); + ASSERT_TRUE(oro); + oro->addValue(2345); + query1->addOption(oro); + query2->addOption(oro); + + // Create and add a host-name option to the first and last queries + OptionStringPtr hostname(new OptionString(Option::V6, 1234, "foo")); + ASSERT_TRUE(hostname); + query1->addOption(hostname); + query3->addOption(hostname); + + // Classify packets + srv.classifyPacket(query1); + srv.classifyPacket(query2); + srv.classifyPacket(query3); + + // Packets at the exception of the second should be in the router class + EXPECT_TRUE(query1->inClass("router")); + EXPECT_FALSE(query2->inClass("router")); + EXPECT_TRUE(query3->inClass("router")); + + // Process queries + Pkt6Ptr response1 = srv.processSolicit(query1); + Pkt6Ptr response2 = srv.processSolicit(query2); + Pkt6Ptr response3 = srv.processSolicit(query3); + + // Classification processing should add an ip-forwarding option + OptionPtr opt1 = response1->getOption(2345); + EXPECT_TRUE(opt1); + + // But only for the first exchange: second was not classified + OptionPtr opt2 = response2->getOption(2345); + EXPECT_FALSE(opt2); + + // But only for the first exchange: third has no ORO + OptionPtr opt3 = response3->getOption(2345); + EXPECT_FALSE(opt3); +} + +// Checks subnet options have the priority over class options +TEST_F(Dhcpv6SrvTest, subnetClassPriority) { + IfaceMgrTestConfig test_config(true); + + NakedDhcpv6Srv srv(0); + + // Subnet sets an ipv6-forwarding option in the response. + // The router class matches incoming packets with foo in a host-name + // option (code 1234) and sets an ipv6-forwarding option in the response. + string config = "{ \"interfaces-config\": {" + " \"interfaces\": [ \"*\" ] }, " + "\"preferred-lifetime\": 3000," + "\"rebind-timer\": 2000, " + "\"renew-timer\": 1000, " + "\"valid-lifetime\": 4000, " + "\"option-def\": [ " + "{ \"name\": \"host-name\"," + " \"code\": 1234," + " \"type\": \"string\" }," + "{ \"name\": \"ipv6-forwarding\"," + " \"code\": 2345," + " \"type\": \"boolean\" }]," + "\"subnet6\": [ " + "{ \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ], " + " \"subnet\": \"2001:db8:1::/48\", " + " \"interface\": \"eth1\", " + " \"option-data\": [" + " { \"name\": \"ipv6-forwarding\", " + " \"data\": \"false\" } ] } ], " + "\"client-classes\": [ " + "{ \"name\": \"router\"," + " \"option-data\": [" + " { \"name\": \"ipv6-forwarding\", " + " \"data\": \"true\" } ], " + " \"test\": \"option[1234] == 'foo'\" } ] }"; + ASSERT_NO_THROW(configure(config)); + + // Create a packet with enough to select the subnet and go through + // the SOLICIT processing + Pkt6Ptr query(new Pkt6(DHCPV6_SOLICIT, 1234)); + query->setRemoteAddr(IOAddress("fe80::abcd")); + OptionPtr clientid = generateClientId(); + query->addOption(clientid); + query->setIface("eth1"); + query->addOption(generateIA(D6O_IA_NA, 123, 1500, 3000)); + + // Create and add a ORO option to the query + OptionUint16ArrayPtr oro(new OptionUint16Array(Option::V6, D6O_ORO)); + ASSERT_TRUE(oro); + oro->addValue(2345); + query->addOption(oro); + + // Create and add a host-name option to the query + OptionStringPtr hostname(new OptionString(Option::V6, 1234, "foo")); + ASSERT_TRUE(hostname); + query->addOption(hostname); + + // Classify the packet + srv.classifyPacket(query); + + // The packet should be in the router class + EXPECT_TRUE(query->inClass("router")); + + // Process the query + Pkt6Ptr response = srv.processSolicit(query); + + // A processing should add an ip-forwarding option + OptionPtr opt = response->getOption(2345); + ASSERT_TRUE(opt); + ASSERT_GT(opt->len(), opt->getHeaderLen()); + // Classification sets the value to true/1, subnet to false/0 + // Here subnet has the priority + EXPECT_EQ(0, opt->getUint8()); +} + +// Checks class options have the priority over global options +TEST_F(Dhcpv6SrvTest, classGlobalPriority) { + IfaceMgrTestConfig test_config(true); + + NakedDhcpv6Srv srv(0); + + // A global ipv6-forwarding option is set in the response. + // The router class matches incoming packets with foo in a host-name + // option (code 1234) and sets an ipv6-forwarding option in the response. + string config = "{ \"interfaces-config\": {" + " \"interfaces\": [ \"*\" ] }, " + "\"preferred-lifetime\": 3000," + "\"rebind-timer\": 2000, " + "\"renew-timer\": 1000, " + "\"valid-lifetime\": 4000, " + "\"option-def\": [ " + "{ \"name\": \"host-name\"," + " \"code\": 1234," + " \"type\": \"string\" }," + "{ \"name\": \"ipv6-forwarding\"," + " \"code\": 2345," + " \"type\": \"boolean\" }]," + "\"subnet6\": [ " + "{ \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ], " + " \"subnet\": \"2001:db8:1::/48\", " + " \"interface\": \"eth1\" } ]," + "\"option-data\": [" + " { \"name\": \"ipv6-forwarding\", " + " \"data\": \"false\" } ], " + "\"client-classes\": [ " + "{ \"name\": \"router\"," + " \"option-data\": [" + " { \"name\": \"ipv6-forwarding\", " + " \"data\": \"true\" } ], " + " \"test\": \"option[1234] == 'foo'\" } ] }"; + ASSERT_NO_THROW(configure(config)); + + // Create a packet with enough to select the subnet and go through + // the SOLICIT processing + Pkt6Ptr query(new Pkt6(DHCPV6_SOLICIT, 1234)); + query->setRemoteAddr(IOAddress("fe80::abcd")); + OptionPtr clientid = generateClientId(); + query->addOption(clientid); + query->setIface("eth1"); + query->addOption(generateIA(D6O_IA_NA, 123, 1500, 3000)); + + // Create and add a ORO option to the query + OptionUint16ArrayPtr oro(new OptionUint16Array(Option::V6, D6O_ORO)); + ASSERT_TRUE(oro); + oro->addValue(2345); + query->addOption(oro); + + // Create and add a host-name option to the query + OptionStringPtr hostname(new OptionString(Option::V6, 1234, "foo")); + ASSERT_TRUE(hostname); + query->addOption(hostname); + + // Classify the packet + srv.classifyPacket(query); + + // The packet should be in the router class + EXPECT_TRUE(query->inClass("router")); + + // Process the query + Pkt6Ptr response = srv.processSolicit(query); + + // A processing should add an ip-forwarding option + OptionPtr opt = response->getOption(2345); + ASSERT_TRUE(opt); + ASSERT_GT(opt->len(), opt->getHeaderLen()); + // Classification sets the value to true/1, global to false/0 + // Here class has the priority + EXPECT_NE(0, opt->getUint8()); +} + // Checks if the client-class field is indeed used for subnet selection. // Note that packet classification is already checked in Dhcpv6SrvTest -// .clientClassification above. -TEST_F(Dhcpv6SrvTest, clientClassify2) { +// .*Classification above. +TEST_F(Dhcpv6SrvTest, clientClassifySubnet) { // This test configures 2 subnets. We actually only need the // first one, but since there's still this ugly hack that picks @@ -1895,7 +2137,7 @@ TEST_F(Dhcpv6SrvTest, clientClassify2) { // Tests whether a packet with custom vendor-class (not erouter or docsis) // is classified properly. -TEST_F(Dhcpv6SrvTest, clientClassification3) { +TEST_F(Dhcpv6SrvTest, vendorClientClassification2) { NakedDhcpv6Srv srv(0); // Let's create a SOLICIT. diff --git a/src/lib/dhcpsrv/parsers/dhcp_parsers.cc b/src/lib/dhcpsrv/parsers/dhcp_parsers.cc index 18d2cc846e..8a581f8212 100644 --- a/src/lib/dhcpsrv/parsers/dhcp_parsers.cc +++ b/src/lib/dhcpsrv/parsers/dhcp_parsers.cc @@ -730,6 +730,12 @@ OptionDataListParser::commit() { BOOST_FOREACH(ParserPtr parser, parsers_) { parser->commit(); } + // Append suboptions to the top-level options + if (cfg_) { + cfg_->encapsulate(); + } else { + CfgMgr::instance().getStagingCfg()->getCfgOption()->encapsulate(); + } } // ******************************** OptionDefParser **************************** @@ -1260,10 +1266,8 @@ SubnetConfigParser::createSubnet() { // options but it is no longer the case (they have a different // and not consecutive priority). - // Copy all options to the subnet configuration. + // Copy options to the subnet configuration. options_->copyTo(*subnet_->getCfgOption()); - // Append suboptions to the top-level options. - subnet_->getCfgOption()->encapsulate(); } isc::dhcp::Triplet diff --git a/src/lib/dhcpsrv/parsers/dhcp_parsers.h b/src/lib/dhcpsrv/parsers/dhcp_parsers.h index 5513d72503..f16584e172 100644 --- a/src/lib/dhcpsrv/parsers/dhcp_parsers.h +++ b/src/lib/dhcpsrv/parsers/dhcp_parsers.h @@ -718,7 +718,8 @@ public: /// @brief Commit all option values. /// - /// This function invokes commit for all option values. + /// This function invokes commit for all option values + /// and append suboptions to the top-level options. void commit(); private: