From bbdef9791d95f0aad3bcd85f64db919549b99981 Mon Sep 17 00:00:00 2001 From: Marcin Siodelski Date: Wed, 17 Sep 2014 17:49:08 +0200 Subject: [PATCH] [3589] Created an object to hold the option configuration. --- src/bin/dhcp4/dhcp4_srv.cc | 8 +- src/bin/dhcp4/tests/config_parser_unittest.cc | 90 +++--- src/bin/dhcp6/dhcp6_srv.cc | 4 +- src/bin/dhcp6/tests/config_parser_unittest.cc | 82 +++--- src/lib/dhcpsrv/Makefile.am | 1 + src/lib/dhcpsrv/cfg_option.cc | 98 +++++++ src/lib/dhcpsrv/cfg_option.h | 267 ++++++++++++++++++ src/lib/dhcpsrv/dhcp_parsers.cc | 21 +- src/lib/dhcpsrv/dhcp_parsers.h | 7 +- src/lib/dhcpsrv/subnet.cc | 8 +- src/lib/dhcpsrv/subnet.h | 124 +------- src/lib/dhcpsrv/tests/Makefile.am | 1 + src/lib/dhcpsrv/tests/cfg_option_unittest.cc | 243 ++++++++++++++++ .../dhcpsrv/tests/dhcp_parsers_unittest.cc | 18 +- src/lib/dhcpsrv/tests/subnet_unittest.cc | 40 +-- 15 files changed, 753 insertions(+), 259 deletions(-) create mode 100644 src/lib/dhcpsrv/cfg_option.cc create mode 100644 src/lib/dhcpsrv/cfg_option.h create mode 100644 src/lib/dhcpsrv/tests/cfg_option_unittest.cc diff --git a/src/bin/dhcp4/dhcp4_srv.cc b/src/bin/dhcp4/dhcp4_srv.cc index 6d2d3a7c6..115816856 100644 --- a/src/bin/dhcp4/dhcp4_srv.cc +++ b/src/bin/dhcp4/dhcp4_srv.cc @@ -599,7 +599,7 @@ Dhcpv4Srv::appendRequestedOptions(const Pkt4Ptr& question, Pkt4Ptr& msg) { for (std::vector::const_iterator opt = requested_opts.begin(); opt != requested_opts.end(); ++opt) { if (!msg->getOption(*opt)) { - Subnet::OptionDescriptor desc = + OptionDescriptor desc = subnet->getOptionDescriptor("dhcp4", *opt); if (desc.option && !msg->getOption(*opt)) { msg->addOption(desc.option); @@ -650,7 +650,7 @@ Dhcpv4Srv::appendRequestedVendorOptions(const Pkt4Ptr& question, Pkt4Ptr& answer for (std::vector::const_iterator code = requested_opts.begin(); code != requested_opts.end(); ++code) { if (!vendor_rsp->getOption(*code)) { - Subnet::OptionDescriptor desc = subnet->getVendorOptionDescriptor(vendor_id, + OptionDescriptor desc = subnet->getVendorOptionDescriptor(vendor_id, *code); if (desc.option) { vendor_rsp->addOption(desc.option); @@ -689,7 +689,7 @@ Dhcpv4Srv::appendBasicOptions(const Pkt4Ptr& question, Pkt4Ptr& msg) { OptionPtr opt = msg->getOption(required_options[i]); if (!opt) { // Check whether option has been configured. - Subnet::OptionDescriptor desc = + OptionDescriptor desc = subnet->getOptionDescriptor("dhcp4", required_options[i]); if (desc.option) { msg->addOption(desc.option); @@ -1949,7 +1949,7 @@ bool Dhcpv4Srv::classSpecificProcessing(const Pkt4Ptr& query, const Pkt4Ptr& rsp // Now try to set up file field in DHCPv4 packet. We will just copy // content of the boot-file option, which contains the same information. - Subnet::OptionDescriptor desc = + OptionDescriptor desc = subnet->getOptionDescriptor("dhcp4", DHO_BOOT_FILE_NAME); if (desc.option) { diff --git a/src/bin/dhcp4/tests/config_parser_unittest.cc b/src/bin/dhcp4/tests/config_parser_unittest.cc index bc1b8d2ce..138f25fa7 100644 --- a/src/bin/dhcp4/tests/config_parser_unittest.cc +++ b/src/bin/dhcp4/tests/config_parser_unittest.cc @@ -231,7 +231,7 @@ public: /// @return Descriptor of the option. If the descriptor holds a /// NULL option pointer, it means that there was no such option /// in the subnet. - Subnet::OptionDescriptor + OptionDescriptor getOptionFromSubnet(const IOAddress& subnet_address, const uint16_t option_code, const uint16_t expected_options_count = 1) { @@ -243,7 +243,7 @@ public: << subnet_address.toText() << "does not exist in Config Manager"; } - Subnet::OptionContainerPtr options = + OptionContainerPtr options = subnet->getOptionDescriptors("dhcp4"); if (expected_options_count != options->size()) { ADD_FAILURE() << "The number of options in the subnet '" @@ -253,13 +253,13 @@ public: } // Get the search index. Index #1 is to search using option code. - const Subnet::OptionContainerTypeIndex& idx = options->get<1>(); + 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 = + std::pair range = idx.equal_range(option_code); if (std::distance(range.first, range.second) > 1) { ADD_FAILURE() << "There is more than one option having the" @@ -267,7 +267,7 @@ public: << subnet_address.toText() << "'. Expected " " at most one option"; } else if (std::distance(range.first, range.second) == 0) { - return (Subnet::OptionDescriptor(OptionPtr(), false)); + return (OptionDescriptor(OptionPtr(), false)); } return (*range.first); @@ -319,7 +319,7 @@ public: /// @param expected_data_len length of the reference data. /// @param extra_data if true extra data is allowed in an option /// after tested data. - void testOption(const Subnet::OptionDescriptor& option_desc, + void testOption(const OptionDescriptor& option_desc, uint16_t expected_code, const uint8_t* expected_data, size_t expected_data_len, bool extra_data = false) { @@ -378,7 +378,7 @@ public: std::string config = createConfigWithOption(params); ASSERT_TRUE(executeConfiguration(config, "parse option configuration")); // The subnet should now hold one option with the specified option code. - Subnet::OptionDescriptor desc = + OptionDescriptor desc = getOptionFromSubnet(IOAddress("192.0.2.24"), option_code); ASSERT_TRUE(desc.option); testOption(desc, option_code, expected_data, expected_data_len); @@ -1797,17 +1797,17 @@ TEST_F(Dhcp4ParserTest, optionDataDefaults) { Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.200"), classify_); ASSERT_TRUE(subnet); - Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp4"); + OptionContainerPtr options = subnet->getOptionDescriptors("dhcp4"); ASSERT_EQ(2, options->size()); // Get the search index. Index #1 is to search using option code. - const Subnet::OptionContainerTypeIndex& idx = options->get<1>(); + 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 = + 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)); @@ -1884,16 +1884,16 @@ TEST_F(Dhcp4ParserTest, optionDataTwoSpaces) { classify_); ASSERT_TRUE(subnet); // Try to get the option from the space dhcp4. - Subnet::OptionDescriptor desc1 = subnet->getOptionDescriptor("dhcp4", 56); + OptionDescriptor desc1 = subnet->getOptionDescriptor("dhcp4", 56); ASSERT_TRUE(desc1.option); EXPECT_EQ(56, desc1.option->getType()); // Try to get the option from the space isc. - Subnet::OptionDescriptor desc2 = subnet->getOptionDescriptor("isc", 56); + OptionDescriptor desc2 = subnet->getOptionDescriptor("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. - Subnet::OptionDescriptor desc3 = subnet->getOptionDescriptor("non-existing", 56); + OptionDescriptor desc3 = subnet->getOptionDescriptor("non-existing", 56); ASSERT_FALSE(desc3.option); } @@ -2039,12 +2039,12 @@ TEST_F(Dhcp4ParserTest, optionDataEncapsulate) { ASSERT_TRUE(subnet); // We should have one option available. - Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp4"); + OptionContainerPtr options = subnet->getOptionDescriptors("dhcp4"); ASSERT_TRUE(options); ASSERT_EQ(1, options->size()); // Get the option. - Subnet::OptionDescriptor desc = subnet->getOptionDescriptor("dhcp4", 222); + OptionDescriptor desc = subnet->getOptionDescriptor("dhcp4", 222); EXPECT_TRUE(desc.option); EXPECT_EQ(222, desc.option->getType()); @@ -2104,17 +2104,17 @@ TEST_F(Dhcp4ParserTest, optionDataInSingleSubnet) { Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.24"), classify_); ASSERT_TRUE(subnet); - Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp4"); + OptionContainerPtr options = subnet->getOptionDescriptors("dhcp4"); ASSERT_EQ(2, options->size()); // Get the search index. Index #1 is to search using option code. - const Subnet::OptionContainerTypeIndex& idx = options->get<1>(); + 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 = + std::pair range = idx.equal_range(56); // Expect single option with the code equal to 100. ASSERT_EQ(1, std::distance(range.first, range.second)); @@ -2150,7 +2150,7 @@ TEST_F(Dhcp4ParserTest, optionDataBoolean) { " boolean value")); // The subnet should now hold one option with the code 19. - Subnet::OptionDescriptor desc = getOptionFromSubnet(IOAddress("192.0.2.24"), + OptionDescriptor desc = getOptionFromSubnet(IOAddress("192.0.2.24"), 19); ASSERT_TRUE(desc.option); @@ -2255,17 +2255,17 @@ TEST_F(Dhcp4ParserTest, optionDataInMultipleSubnets) { Subnet4Ptr subnet1 = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.100"), classify_); ASSERT_TRUE(subnet1); - Subnet::OptionContainerPtr options1 = subnet1->getOptionDescriptors("dhcp4"); + OptionContainerPtr options1 = subnet1->getOptionDescriptors("dhcp4"); ASSERT_EQ(1, options1->size()); // Get the search index. Index #1 is to search using option code. - const Subnet::OptionContainerTypeIndex& idx1 = options1->get<1>(); + const OptionContainerTypeIndex& idx1 = options1->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 range1 = + std::pair range1 = idx1.equal_range(56); // Expect single option with the code equal to 56. ASSERT_EQ(1, std::distance(range1.first, range1.second)); @@ -2280,12 +2280,12 @@ TEST_F(Dhcp4ParserTest, optionDataInMultipleSubnets) { Subnet4Ptr subnet2 = CfgMgr::instance().getSubnet4(IOAddress("192.0.3.102"), classify_); ASSERT_TRUE(subnet2); - Subnet::OptionContainerPtr options2 = subnet2->getOptionDescriptors("dhcp4"); + OptionContainerPtr options2 = subnet2->getOptionDescriptors("dhcp4"); ASSERT_EQ(1, options2->size()); - const Subnet::OptionContainerTypeIndex& idx2 = options2->get<1>(); - std::pair range2 = + const OptionContainerTypeIndex& idx2 = options2->get<1>(); + std::pair range2 = idx2.equal_range(23); ASSERT_EQ(1, std::distance(range2.first, range2.second)); @@ -2358,17 +2358,17 @@ TEST_F(Dhcp4ParserTest, optionDataLowerCase) { Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.5"), classify_); ASSERT_TRUE(subnet); - Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp4"); + OptionContainerPtr options = subnet->getOptionDescriptors("dhcp4"); ASSERT_EQ(1, options->size()); // Get the search index. Index #1 is to search using option code. - const Subnet::OptionContainerTypeIndex& idx = options->get<1>(); + 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 = + std::pair range = idx.equal_range(56); // Expect single option with the code equal to 100. ASSERT_EQ(1, std::distance(range.first, range.second)); @@ -2401,19 +2401,19 @@ TEST_F(Dhcp4ParserTest, stdOptionData) { Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.5"), classify_); ASSERT_TRUE(subnet); - Subnet::OptionContainerPtr options = + OptionContainerPtr options = subnet->getOptionDescriptors("dhcp4"); ASSERT_TRUE(options); ASSERT_EQ(1, options->size()); // Get the search index. Index #1 is to search using option code. - const Subnet::OptionContainerTypeIndex& idx = options->get<1>(); + 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 = + std::pair range = idx.equal_range(DHO_NIS_SERVERS); // Expect single option with the code equal to NIS_SERVERS option code. ASSERT_EQ(1, std::distance(range.first, range.second)); @@ -2611,12 +2611,12 @@ TEST_F(Dhcp4ParserTest, stdOptionDataEncapsulate) { ASSERT_TRUE(subnet); // We should have one option available. - Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp4"); + OptionContainerPtr options = subnet->getOptionDescriptors("dhcp4"); ASSERT_TRUE(options); ASSERT_EQ(1, options->size()); // Get the option. - Subnet::OptionDescriptor desc = + OptionDescriptor desc = subnet->getOptionDescriptor("dhcp4", DHO_VENDOR_ENCAPSULATED_OPTIONS); EXPECT_TRUE(desc.option); EXPECT_EQ(DHO_VENDOR_ENCAPSULATED_OPTIONS, desc.option->getType()); @@ -2695,17 +2695,17 @@ TEST_F(Dhcp4ParserTest, vendorOptionsHex) { ASSERT_TRUE(subnet); // Try to get the option from the vendor space 4491 - Subnet::OptionDescriptor desc1 = subnet->getVendorOptionDescriptor(VENDOR_ID_CABLE_LABS, 100); + OptionDescriptor desc1 = subnet->getVendorOptionDescriptor(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 - Subnet::OptionDescriptor desc2 = subnet->getVendorOptionDescriptor(1234, 100); + OptionDescriptor desc2 = subnet->getVendorOptionDescriptor(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. - Subnet::OptionDescriptor desc3 = subnet->getVendorOptionDescriptor(5678, 100); + OptionDescriptor desc3 = subnet->getVendorOptionDescriptor(5678, 100); ASSERT_FALSE(desc3.option); } @@ -2756,13 +2756,13 @@ TEST_F(Dhcp4ParserTest, vendorOptionsCsv) { ASSERT_TRUE(subnet); // Try to get the option from the vendor space 4491 - Subnet::OptionDescriptor desc1 = subnet->getVendorOptionDescriptor(VENDOR_ID_CABLE_LABS, 100); + OptionDescriptor desc1 = subnet->getVendorOptionDescriptor(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. - Subnet::OptionDescriptor desc2 = subnet->getVendorOptionDescriptor(5678, 100); + OptionDescriptor desc2 = subnet->getVendorOptionDescriptor(5678, 100); ASSERT_FALSE(desc2.option); } diff --git a/src/bin/dhcp6/dhcp6_srv.cc b/src/bin/dhcp6/dhcp6_srv.cc index 08e25ee9d..abed50f4f 100644 --- a/src/bin/dhcp6/dhcp6_srv.cc +++ b/src/bin/dhcp6/dhcp6_srv.cc @@ -731,7 +731,7 @@ Dhcpv6Srv::appendRequestedOptions(const Pkt6Ptr& question, Pkt6Ptr& answer) { // Get the list of options that client requested. const std::vector& requested_opts = option_oro->getValues(); BOOST_FOREACH(uint16_t opt, requested_opts) { - Subnet::OptionDescriptor desc = subnet->getOptionDescriptor("dhcp6", opt); + OptionDescriptor desc = subnet->getOptionDescriptor("dhcp6", opt); if (desc.option) { answer->addOption(desc.option); } @@ -777,7 +777,7 @@ Dhcpv6Srv::appendRequestedVendorOptions(const Pkt6Ptr& question, Pkt6Ptr& answer bool added = false; const std::vector& requested_opts = oro->getValues(); BOOST_FOREACH(uint16_t opt, requested_opts) { - Subnet::OptionDescriptor desc = subnet->getVendorOptionDescriptor(vendor_id, opt); + OptionDescriptor desc = subnet->getVendorOptionDescriptor(vendor_id, opt); if (desc.option) { vendor_rsp->addOption(desc.option); added = true; diff --git a/src/bin/dhcp6/tests/config_parser_unittest.cc b/src/bin/dhcp6/tests/config_parser_unittest.cc index 082cc7209..961451a4a 100644 --- a/src/bin/dhcp6/tests/config_parser_unittest.cc +++ b/src/bin/dhcp6/tests/config_parser_unittest.cc @@ -243,7 +243,7 @@ public: /// @return Descriptor of the option. If the descriptor holds a /// NULL option pointer, it means that there was no such option /// in the subnet. - Subnet::OptionDescriptor + OptionDescriptor getOptionFromSubnet(const IOAddress& subnet_address, const uint16_t option_code, const uint16_t expected_options_count = 1) { @@ -255,7 +255,7 @@ public: << subnet_address.toText() << "does not exist in Config Manager"; } - Subnet::OptionContainerPtr options = + OptionContainerPtr options = subnet->getOptionDescriptors("dhcp6"); if (expected_options_count != options->size()) { ADD_FAILURE() << "The number of options in the subnet '" @@ -265,13 +265,13 @@ public: } // Get the search index. Index #1 is to search using option code. - const Subnet::OptionContainerTypeIndex& idx = options->get<1>(); + 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 = + std::pair range = idx.equal_range(option_code); if (std::distance(range.first, range.second) > 1) { ADD_FAILURE() << "There is more than one option having the" @@ -279,7 +279,7 @@ public: << subnet_address.toText() << "'. Expected " " at most one option"; } else if (std::distance(range.first, range.second) == 0) { - return (Subnet::OptionDescriptor(OptionPtr(), false)); + return (OptionDescriptor(OptionPtr(), false)); } return (*range.first); @@ -413,7 +413,7 @@ public: /// @param expected_data_len length of the reference data. /// @param extra_data if true extra data is allowed in an option /// after tested data. - void testOption(const Subnet::OptionDescriptor& option_desc, + void testOption(const OptionDescriptor& option_desc, uint16_t expected_code, const uint8_t* expected_data, size_t expected_data_len, bool extra_data = false) { @@ -473,7 +473,7 @@ public: ASSERT_TRUE(executeConfiguration(config, "parse option configuration")); // The subnet should now hold one option with the specified code. - Subnet::OptionDescriptor desc = + OptionDescriptor desc = getOptionFromSubnet(IOAddress("2001:db8:1::5"), option_code); ASSERT_TRUE(desc.option); testOption(desc, option_code, expected_data, expected_data_len); @@ -2030,17 +2030,17 @@ TEST_F(Dhcp6ParserTest, optionDataDefaults) { Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"), classify_); ASSERT_TRUE(subnet); - Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp6"); + OptionContainerPtr options = subnet->getOptionDescriptors("dhcp6"); ASSERT_EQ(2, options->size()); // Get the search index. Index #1 is to search using option code. - const Subnet::OptionContainerTypeIndex& idx = options->get<1>(); + 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 = + 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)); @@ -2126,16 +2126,16 @@ TEST_F(Dhcp6ParserTest, optionDataTwoSpaces) { classify_); ASSERT_TRUE(subnet); // Try to get the option from the space dhcp6. - Subnet::OptionDescriptor desc1 = subnet->getOptionDescriptor("dhcp6", 38); + OptionDescriptor desc1 = subnet->getOptionDescriptor("dhcp6", 38); ASSERT_TRUE(desc1.option); EXPECT_EQ(38, desc1.option->getType()); // Try to get the option from the space isc. - Subnet::OptionDescriptor desc2 = subnet->getOptionDescriptor("isc", 38); + OptionDescriptor desc2 = subnet->getOptionDescriptor("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. - Subnet::OptionDescriptor desc3 = subnet->getOptionDescriptor("non-existing", 38); + OptionDescriptor desc3 = subnet->getOptionDescriptor("non-existing", 38); ASSERT_FALSE(desc3.option); } @@ -2283,12 +2283,12 @@ TEST_F(Dhcp6ParserTest, optionDataEncapsulate) { ASSERT_TRUE(subnet); // We should have one option available. - Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp6"); + OptionContainerPtr options = subnet->getOptionDescriptors("dhcp6"); ASSERT_TRUE(options); ASSERT_EQ(1, options->size()); // Get the option. - Subnet::OptionDescriptor desc = subnet->getOptionDescriptor("dhcp6", 100); + OptionDescriptor desc = subnet->getOptionDescriptor("dhcp6", 100); EXPECT_TRUE(desc.option); EXPECT_EQ(100, desc.option->getType()); @@ -2344,17 +2344,17 @@ TEST_F(Dhcp6ParserTest, optionDataInMultipleSubnets) { Subnet6Ptr subnet1 = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"), classify_); ASSERT_TRUE(subnet1); - Subnet::OptionContainerPtr options1 = subnet1->getOptionDescriptors("dhcp6"); + OptionContainerPtr options1 = subnet1->getOptionDescriptors("dhcp6"); ASSERT_EQ(1, options1->size()); // Get the search index. Index #1 is to search using option code. - const Subnet::OptionContainerTypeIndex& idx1 = options1->get<1>(); + const OptionContainerTypeIndex& idx1 = options1->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 range1 = + std::pair range1 = idx1.equal_range(D6O_SUBSCRIBER_ID); // Expect single option with the code equal to 38. ASSERT_EQ(1, std::distance(range1.first, range1.second)); @@ -2370,12 +2370,12 @@ TEST_F(Dhcp6ParserTest, optionDataInMultipleSubnets) { Subnet6Ptr subnet2 = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:2::4"), classify_); ASSERT_TRUE(subnet2); - Subnet::OptionContainerPtr options2 = subnet2->getOptionDescriptors("dhcp6"); + OptionContainerPtr options2 = subnet2->getOptionDescriptors("dhcp6"); ASSERT_EQ(1, options2->size()); - const Subnet::OptionContainerTypeIndex& idx2 = options2->get<1>(); - std::pair range2 = + const OptionContainerTypeIndex& idx2 = options2->get<1>(); + std::pair range2 = idx2.equal_range(D6O_USER_CLASS); ASSERT_EQ(1, std::distance(range2.first, range2.second)); @@ -2405,7 +2405,7 @@ TEST_F(Dhcp6ParserTest, optionDataBoolean) { CfgMgr::instance().commit(); // The subnet should now hold one option with the code 1000. - Subnet::OptionDescriptor desc = + OptionDescriptor desc = getOptionFromSubnet(IOAddress("2001:db8:1::5"), 1000); ASSERT_TRUE(desc.option); @@ -2541,17 +2541,17 @@ TEST_F(Dhcp6ParserTest, optionDataLowerCase) { Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"), classify_); ASSERT_TRUE(subnet); - Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp6"); + OptionContainerPtr options = subnet->getOptionDescriptors("dhcp6"); ASSERT_EQ(1, options->size()); // Get the search index. Index #1 is to search using option code. - const Subnet::OptionContainerTypeIndex& idx = options->get<1>(); + 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 = + 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)); @@ -2584,17 +2584,17 @@ TEST_F(Dhcp6ParserTest, stdOptionData) { Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"), classify_); ASSERT_TRUE(subnet); - Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp6"); + OptionContainerPtr options = subnet->getOptionDescriptors("dhcp6"); ASSERT_EQ(1, options->size()); // Get the search index. Index #1 is to search using option code. - const Subnet::OptionContainerTypeIndex& idx = options->get<1>(); + 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 = + std::pair range = idx.equal_range(D6O_IA_NA); // Expect single option with the code equal to IA_NA option code. ASSERT_EQ(1, std::distance(range.first, range.second)); @@ -2664,17 +2664,17 @@ TEST_F(Dhcp6ParserTest, vendorOptionsHex) { ASSERT_TRUE(subnet); // Try to get the option from the vendor space 4491 - Subnet::OptionDescriptor desc1 = subnet->getVendorOptionDescriptor(4491, 100); + OptionDescriptor desc1 = subnet->getVendorOptionDescriptor(4491, 100); ASSERT_TRUE(desc1.option); EXPECT_EQ(100, desc1.option->getType()); // Try to get the option from the vendor space 1234 - Subnet::OptionDescriptor desc2 = subnet->getVendorOptionDescriptor(1234, 100); + OptionDescriptor desc2 = subnet->getVendorOptionDescriptor(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. - Subnet::OptionDescriptor desc3 = subnet->getVendorOptionDescriptor(5678, 38); + OptionDescriptor desc3 = subnet->getVendorOptionDescriptor(5678, 38); ASSERT_FALSE(desc3.option); } @@ -2726,13 +2726,13 @@ TEST_F(Dhcp6ParserTest, vendorOptionsCsv) { ASSERT_TRUE(subnet); // Try to get the option from the vendor space 4491 - Subnet::OptionDescriptor desc1 = subnet->getVendorOptionDescriptor(4491, 100); + OptionDescriptor desc1 = subnet->getVendorOptionDescriptor(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. - Subnet::OptionDescriptor desc2 = subnet->getVendorOptionDescriptor(5678, 100); + OptionDescriptor desc2 = subnet->getVendorOptionDescriptor(5678, 100); ASSERT_FALSE(desc2.option); } @@ -2865,12 +2865,12 @@ TEST_F(Dhcp6ParserTest, stdOptionDataEncapsulate) { ASSERT_TRUE(subnet); // We should have one option available. - Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp6"); + OptionContainerPtr options = subnet->getOptionDescriptors("dhcp6"); ASSERT_TRUE(options); ASSERT_EQ(1, options->size()); // Get the option. - Subnet::OptionDescriptor desc = + OptionDescriptor desc = subnet->getOptionDescriptor("dhcp6", D6O_VENDOR_OPTS); EXPECT_TRUE(desc.option); EXPECT_EQ(D6O_VENDOR_OPTS, desc.option->getType()); diff --git a/src/lib/dhcpsrv/Makefile.am b/src/lib/dhcpsrv/Makefile.am index e035bee2d..ff49d64d6 100644 --- a/src/lib/dhcpsrv/Makefile.am +++ b/src/lib/dhcpsrv/Makefile.am @@ -46,6 +46,7 @@ libkea_dhcpsrv_la_SOURCES += addr_utilities.cc addr_utilities.h libkea_dhcpsrv_la_SOURCES += alloc_engine.cc alloc_engine.h libkea_dhcpsrv_la_SOURCES += callout_handle_store.h libkea_dhcpsrv_la_SOURCES += cfg_iface.cc cfg_iface.h +libkea_dhcpsrv_la_SOURCES += cfg_option.cc cfg_option.h libkea_dhcpsrv_la_SOURCES += cfg_option_def.cc cfg_option_def.h libkea_dhcpsrv_la_SOURCES += cfgmgr.cc cfgmgr.h libkea_dhcpsrv_la_SOURCES += csv_lease_file4.cc csv_lease_file4.h diff --git a/src/lib/dhcpsrv/cfg_option.cc b/src/lib/dhcpsrv/cfg_option.cc new file mode 100644 index 000000000..8273d6b4a --- /dev/null +++ b/src/lib/dhcpsrv/cfg_option.cc @@ -0,0 +1,98 @@ +// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC") +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE +// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +#include +#include +#include +#include +#include + +namespace { + +uint32_t +optionSpaceToVendorId(const std::string& option_space) { + if (option_space.size() < 8) { + // 8 is a minimal length of "vendor-X" format + return (0); + } + if (option_space.substr(0,7) != "vendor-") { + return (0); + } + + // text after "vendor-", supposedly numbers only + std::string x = option_space.substr(7); + + int64_t check; + try { + check = boost::lexical_cast(x); + } catch (const boost::bad_lexical_cast &) { + /// @todo: Should we throw here? + // isc_throw(BadValue, "Failed to parse vendor-X value (" << x + // << ") as unsigned 32-bit integer."); + return (0); + } + if (check > std::numeric_limits::max()) { + /// @todo: Should we throw here? + //isc_throw(BadValue, "Value " << x << "is too large" + // << " for unsigned 32-bit integer."); + return (0); + } + if (check < 0) { + /// @todo: Should we throw here? + // isc_throw(BadValue, "Value " << x << "is negative." + // << " Only 0 or larger are allowed for unsigned 32-bit integer."); + return (0); + } + + // value is small enough to fit + return (static_cast(check)); +} + +} + +namespace isc { +namespace dhcp { + +void +CfgOption::add(const OptionPtr& option, const bool persistent, + const std::string& option_space) { + if (!option) { + isc_throw(isc::BadValue, "option being configured must not be NULL"); + + } else if (!OptionSpace::validateName(option_space)) { + isc_throw(isc::BadValue, "invalid option space name: '" + << option_space << "'"); + } + + const uint32_t vendor_id = optionSpaceToVendorId(option_space); + if (vendor_id) { + vendor_options_.addItem(OptionDescriptor(option, persistent), + vendor_id); + } else { + options_.addItem(OptionDescriptor(option, persistent), option_space); + } +} + +OptionContainerPtr +CfgOption::getAll(const std::string& option_space) const { + return (options_.getItems(option_space)); +} + +OptionContainerPtr +CfgOption::getAll(const uint32_t vendor_id) const { + return (vendor_options_.getItems(vendor_id)); +} + +} // end of namespace isc::dhcp +} // end of namespace isc diff --git a/src/lib/dhcpsrv/cfg_option.h b/src/lib/dhcpsrv/cfg_option.h new file mode 100644 index 000000000..36733dd69 --- /dev/null +++ b/src/lib/dhcpsrv/cfg_option.h @@ -0,0 +1,267 @@ +// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC") +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE +// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +#ifndef CFG_OPTION_H +#define CFG_OPTION_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace isc { +namespace dhcp { + +/// @brief Option descriptor. +/// +/// Option descriptor holds instance of an option and additional information +/// for this option. This information comprises whether this option is sent +/// to DHCP client only on request (persistent = false) or always +/// (persistent = true). +struct OptionDescriptor { + /// Option instance. + OptionPtr option; + /// Persistent flag, if true option is always sent to the client, + /// if false option is sent to the client on request. + bool persistent; + + /// @brief Constructor. + /// + /// @param opt option + /// @param persist if true option is always sent. + OptionDescriptor(const OptionPtr& opt, bool persist) + : option(opt), persistent(persist) {}; + + /// @brief Constructor + /// + /// @param persist if true option is always sent. + OptionDescriptor(bool persist) + : option(OptionPtr()), persistent(persist) {}; +}; + +/// A pointer to option descriptor. +typedef boost::shared_ptr OptionDescriptorPtr; + +/// @brief Multi index container for DHCP option descriptors. +/// +/// This container comprises three indexes to access option +/// descriptors: +/// - sequenced index: used to access elements in the order they +/// have been added to the container, +/// - option type index: used to search option descriptors containing +/// options with specific option code (aka option type). +/// - persistency flag index: used to search option descriptors with +/// 'persistent' flag set to true. +/// +/// This container is the equivalent of three separate STL containers: +/// - std::list of all options, +/// - std::multimap of options with option code used as a multimap key, +/// - std::multimap of option descriptors with option persistency flag +/// used as a multimap key. +/// The major advantage of this container over 3 separate STL containers +/// is automatic synchronization of all indexes when elements are added, +/// removed or modified in the container. With separate containers, +/// the synchronization would have to be guaranteed by the Subnet class +/// code. This would increase code complexity and presumably it would +/// be much harder to add new search criteria (indexes). +/// +/// @todo we may want to search for options using option spaces when +/// they are implemented. +/// +/// @see http://www.boost.org/doc/libs/1_51_0/libs/multi_index/doc/index.html +typedef boost::multi_index_container< + // Container comprises elements of OptionDescriptor type. + OptionDescriptor, + // Here we start enumerating various indexes. + boost::multi_index::indexed_by< + // Sequenced index allows accessing elements in the same way + // as elements in std::list. + // Sequenced is an index #0. + boost::multi_index::sequenced<>, + // Start definition of index #1. + boost::multi_index::hashed_non_unique< + // KeyFromKeyExtractor is the index key extractor that allows + // accessing option type being held by the OptionPtr through + // OptionDescriptor structure. + KeyFromKeyExtractor< + // Use option type as the index key. The type is held + // in OptionPtr object so we have to call Option::getType + // to retrieve this key for each element. + boost::multi_index::const_mem_fun< + Option, + uint16_t, + &Option::getType + >, + // Indicate that OptionPtr is a member of + // OptionDescriptor structure. + boost::multi_index::member< + OptionDescriptor, + OptionPtr, + &OptionDescriptor::option + > + > + >, + // Start definition of index #2. + // Use 'persistent' struct member as a key. + boost::multi_index::hashed_non_unique< + boost::multi_index::member< + OptionDescriptor, + bool, + &OptionDescriptor::persistent + > + > + > +> OptionContainer; + +/// Pointer to the OptionContainer object. +typedef boost::shared_ptr OptionContainerPtr; +/// Type of the index #1 - option type. +typedef OptionContainer::nth_index<1>::type OptionContainerTypeIndex; +/// Pair of iterators to represent the range of options having the +/// same option type value. The first element in this pair represents +/// the beginning of the range, the second element represents the end. +typedef std::pair OptionContainerTypeRange; +/// Type of the index #2 - option persistency flag. +typedef OptionContainer::nth_index<2>::type OptionContainerPersistIndex; + +/// @brief Represents option data configuration for the DHCP server. +/// +/// This class holds a collection of options to be sent to a DHCP client. +/// Options are grouped by the option space or vendor identifier (for +/// vendor options). +/// +/// The server configuration allows for specifying two distinct collections +/// of options: global options and per-subnet options in which some options +/// may overlap. +/// +/// The collection of global options specify options being sent to the client +/// belonging to any subnets, i.e. global options are "inherited" by all +/// subnets. +/// +/// The per-subnet options are configured for a particular subnet and are sent +/// to clients which belong to this subnet. The values of the options specified +/// for a particular subnet override the values of the global options. +/// +/// This class represents a single collection of options (either global or +/// per-subnet). Each subnet holds its own object of the @c CfgOption type. The +/// @c CfgMgr holds a @c CfgOption object representing global options. +/// +/// Note that having a separate copy of the @c CfgOption to represent global +/// options is useful when the client requests stateless configuration from +/// the DHCP server and no subnet is selected for this client. This client +/// will only receive global options. +class CfgOption { +public: + + /// @brief Adds instance of the option to the configuration. + /// + /// There are two types of options which may be passed to this method: + /// - vendor options + /// - non-vendor options + /// + /// The non-vendor options are grouped by the name of the option space + /// (specified in textual format). The vendor options are grouped by the + /// vendor identifier, which is a 32-bit unsigned integer value. + /// + /// In order to add new vendor option to the list the option space name + /// (last argument of this method) should be specified as "vendor-X" where + /// "X" is a 32-bit unsigned integer, e.g. "vendor-1234". Options for which + /// the @c option_space argument doesn't follow this format are added as + /// non-vendor options. + /// + /// @param option Pointer to the option being added. + /// @param persistent Boolean value which specifies if the option should + /// be sent to the client regardless if requested (true), or nor (false) + /// @param option_space Option space name. + /// + /// @throw isc::BadValue if the option space is invalid. + void add(const OptionPtr& option, const bool persistent, + const std::string& option_space); + + /// @brief Returns all options for the specified option space. + /// + /// This method will not return vendor options, i.e. having option space + /// name in the format of "vendor-X" where X is 32-bit unsiged integer. + /// + /// @param option_space Name of the option space. + /// + /// @return Pointer to the container holding returned options. This + /// container is empty if no options have been found. + OptionContainerPtr getAll(const std::string& option_space) const; + + /// @brief Returns vendor options for the specified vendor id. + /// + /// @param vendor_id Vendor id for which options are to be returned. + /// + /// @return Pointer to the container holding returned options. This + /// container is empty if no options have been found. + OptionContainerPtr getAll(const uint32_t vendor_id) const; + + /// @brief Returns option for the specified key and option code. + /// + /// The key should be a string, in which case it specifies an option space + /// name, or an uint32_t value, in which case it specifies a vendor + /// identifier. + /// + /// @param key Option space name or vendor identifier. + /// @param option_code Code of the option to be returned. + /// @tparam T one of: @c std::string or @c uint32_t + /// + /// @return Descriptor of the option. If option hasn't been found, the + /// descriptor holds NULL option. + template + OptionDescriptor get(const T& key, const uint16_t option_code) const { + OptionContainerPtr options = getAll(key); + if (!options || options->empty()) { + return (OptionDescriptor(false)); + } + + const OptionContainerTypeIndex& idx = options->get<1>(); + const OptionContainerTypeRange& range = idx.equal_range(option_code); + if (std::distance(range.first, range.second) == 0) { + return (OptionDescriptor(false)); + } + + return (*range.first); + } + +private: + + /// @brief Type of the container holding options grouped by option space. + typedef OptionSpaceContainer OptionSpaceCollection; + /// @brief Container holding options grouped by option space. + OptionSpaceCollection options_; + + /// @brief Type of the container holding options grouped by vendor id. + typedef OptionSpaceContainer VendorOptionSpaceCollection; + /// @brief Container holding options grouped by vendor id. + VendorOptionSpaceCollection vendor_options_; + + + +}; + +} +} + +#endif // CFG_OPTION_H diff --git a/src/lib/dhcpsrv/dhcp_parsers.cc b/src/lib/dhcpsrv/dhcp_parsers.cc index 3173332fc..b618e6b8b 100644 --- a/src/lib/dhcpsrv/dhcp_parsers.cc +++ b/src/lib/dhcpsrv/dhcp_parsers.cc @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -345,16 +346,16 @@ OptionDataParser::commit() { } uint16_t opt_type = option_descriptor_.option->getType(); - Subnet::OptionContainerPtr options = options_->getItems(option_space_); + OptionContainerPtr options = options_->getItems(option_space_); // The getItems() should never return NULL pointer. If there are no // options configured for the particular option space a pointer // to an empty container should be returned. assert(options); - Subnet::OptionContainerTypeIndex& idx = options->get<1>(); + OptionContainerTypeIndex& idx = options->get<1>(); // Try to find options with the particular option code in the main // storage. If found, remove these options because they will be // replaced with new one. - Subnet::OptionContainerTypeRange range = idx.equal_range(opt_type); + OptionContainerTypeRange range = idx.equal_range(opt_type); if (std::distance(range.first, range.second) > 0) { idx.erase(range.first, range.second); } @@ -540,7 +541,7 @@ OptionDataParser::createOption(ConstElementPtr option_data) { code, data_tokens) : def->optionFactory(global_context_->universe_, code, binary); - Subnet::OptionDescriptor desc(option, false); + OptionDescriptor desc(option, false); option_descriptor_.option = option; option_descriptor_.persistent = false; } catch (const isc::Exception& ex) { @@ -1070,10 +1071,10 @@ SubnetConfigParser::appendSubOptions(const std::string& option_space, if (!encapsulated_space.empty()) { // Get the sub-options that belong to the encapsulated // option space. - const Subnet::OptionContainerPtr sub_opts = + const OptionContainerPtr sub_opts = global_context_->options_->getItems(encapsulated_space); // Append sub-options to the option. - BOOST_FOREACH(Subnet::OptionDescriptor desc, *sub_opts) { + BOOST_FOREACH(OptionDescriptor desc, *sub_opts) { if (desc.option) { option->addOption(desc.option); } @@ -1152,7 +1153,7 @@ SubnetConfigParser::createSubnet() { std::list space_names = options_->getOptionSpaceNames(); BOOST_FOREACH(std::string option_space, space_names) { // Get all options within a particular option space. - BOOST_FOREACH(Subnet::OptionDescriptor desc, + BOOST_FOREACH(OptionDescriptor desc, *options_->getItems(option_space)) { // The pointer should be non-NULL. The validation is expected // to be performed by the OptionDataParser before adding an @@ -1161,7 +1162,7 @@ SubnetConfigParser::createSubnet() { // We want to check whether an option with the particular // option code has been already added. If so, we want // to issue a warning. - Subnet::OptionDescriptor existing_desc = + OptionDescriptor existing_desc = subnet_->getOptionDescriptor("option_space", desc.option->getType()); if (existing_desc.option) { @@ -1189,7 +1190,7 @@ SubnetConfigParser::createSubnet() { space_names = global_context_->options_->getOptionSpaceNames(); BOOST_FOREACH(std::string option_space, space_names) { // Get all global options for the particular option space. - BOOST_FOREACH(Subnet::OptionDescriptor desc, + BOOST_FOREACH(OptionDescriptor desc, *(global_context_->options_->getItems(option_space))) { // The pointer should be non-NULL. The validation is expected // to be performed by the OptionDataParser before adding an @@ -1201,7 +1202,7 @@ SubnetConfigParser::createSubnet() { // subnet scope take precedence over globally configured // values we don't add option from the global storage // if there is one already. - Subnet::OptionDescriptor existing_desc = + OptionDescriptor existing_desc = subnet_->getOptionDescriptor(option_space, desc.option->getType()); if (!existing_desc.option) { diff --git a/src/lib/dhcpsrv/dhcp_parsers.h b/src/lib/dhcpsrv/dhcp_parsers.h index 08d30acd3..af3ddd212 100644 --- a/src/lib/dhcpsrv/dhcp_parsers.h +++ b/src/lib/dhcpsrv/dhcp_parsers.h @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -42,8 +43,8 @@ typedef boost::shared_ptr OptionDefStoragePtr; /// Collection of containers holding option spaces. Each container within /// a particular option space holds so-called option descriptors. -typedef OptionSpaceContainer OptionStorage; +typedef OptionSpaceContainer OptionStorage; /// @brief Shared pointer to option storage. typedef boost::shared_ptr OptionStoragePtr; @@ -600,7 +601,7 @@ private: OptionStoragePtr options_; /// Option descriptor holds newly configured option. - Subnet::OptionDescriptor option_descriptor_; + OptionDescriptor option_descriptor_; /// Option space name where the option belongs to. std::string option_space_; diff --git a/src/lib/dhcpsrv/subnet.cc b/src/lib/dhcpsrv/subnet.cc index ceba9af1e..17f265c1d 100644 --- a/src/lib/dhcpsrv/subnet.cc +++ b/src/lib/dhcpsrv/subnet.cc @@ -104,12 +104,12 @@ Subnet::delOptions() { option_spaces_.clearItems(); } -Subnet::OptionContainerPtr +OptionContainerPtr Subnet::getOptionDescriptors(const std::string& option_space) const { return (option_spaces_.getItems(option_space)); } -Subnet::OptionDescriptor +OptionDescriptor Subnet::getOptionDescriptor(const std::string& option_space, const uint16_t option_code) { OptionContainerPtr options = getOptionDescriptors(option_space); @@ -133,12 +133,12 @@ void Subnet::addVendorOption(const OptionPtr& option, bool persistent, vendor_option_spaces_.addItem(OptionDescriptor(option, persistent), vendor_id); } -Subnet::OptionContainerPtr +OptionContainerPtr Subnet::getVendorOptionDescriptors(uint32_t vendor_id) const { return (vendor_option_spaces_.getItems(vendor_id)); } -Subnet::OptionDescriptor +OptionDescriptor Subnet::getVendorOptionDescriptor(uint32_t vendor_id, uint16_t option_code) { OptionContainerPtr options = getVendorOptionDescriptors(vendor_id); if (!options || options->empty()) { diff --git a/src/lib/dhcpsrv/subnet.h b/src/lib/dhcpsrv/subnet.h index d0382fd17..648fd99e0 100644 --- a/src/lib/dhcpsrv/subnet.h +++ b/src/lib/dhcpsrv/subnet.h @@ -15,22 +15,17 @@ #ifndef SUBNET_H #define SUBNET_H -#include -#include -#include -#include -#include -#include - #include #include #include -#include +#include #include #include #include #include +#include + namespace isc { namespace dhcp { @@ -54,119 +49,6 @@ typedef uint32_t SubnetID; class Subnet { public: - /// @brief Option descriptor. - /// - /// Option descriptor holds information about option configured for - /// a particular subnet. This information comprises the actual option - /// instance and information whether this option is sent to DHCP client - /// only on request (persistent = false) or always (persistent = true). - struct OptionDescriptor { - /// Option instance. - OptionPtr option; - /// Persistent flag, if true option is always sent to the client, - /// if false option is sent to the client on request. - bool persistent; - - /// @brief Constructor. - /// - /// @param opt option - /// @param persist if true option is always sent. - OptionDescriptor(const OptionPtr& opt, bool persist) - : option(opt), persistent(persist) {}; - - /// @brief Constructor - /// - /// @param persist if true option is always sent. - OptionDescriptor(bool persist) - : option(OptionPtr()), persistent(persist) {}; - }; - - /// A pointer to option descriptor. - typedef boost::shared_ptr OptionDescriptorPtr; - - /// @brief Multi index container for DHCP option descriptors. - /// - /// This container comprises three indexes to access option - /// descriptors: - /// - sequenced index: used to access elements in the order they - /// have been added to the container, - /// - option type index: used to search option descriptors containing - /// options with specific option code (aka option type). - /// - persistency flag index: used to search option descriptors with - /// 'persistent' flag set to true. - /// - /// This container is the equivalent of three separate STL containers: - /// - std::list of all options, - /// - std::multimap of options with option code used as a multimap key, - /// - std::multimap of option descriptors with option persistency flag - /// used as a multimap key. - /// The major advantage of this container over 3 separate STL containers - /// is automatic synchronization of all indexes when elements are added, - /// removed or modified in the container. With separate containers, - /// the synchronization would have to be guaranteed by the Subnet class - /// code. This would increase code complexity and presumably it would - /// be much harder to add new search criteria (indexes). - /// - /// @todo we may want to search for options using option spaces when - /// they are implemented. - /// - /// @see http://www.boost.org/doc/libs/1_51_0/libs/multi_index/doc/index.html - typedef boost::multi_index_container< - // Container comprises elements of OptionDescriptor type. - OptionDescriptor, - // Here we start enumerating various indexes. - boost::multi_index::indexed_by< - // Sequenced index allows accessing elements in the same way - // as elements in std::list. - // Sequenced is an index #0. - boost::multi_index::sequenced<>, - // Start definition of index #1. - boost::multi_index::hashed_non_unique< - // KeyFromKeyExtractor is the index key extractor that allows - // accessing option type being held by the OptionPtr through - // OptionDescriptor structure. - KeyFromKeyExtractor< - // Use option type as the index key. The type is held - // in OptionPtr object so we have to call Option::getType - // to retrieve this key for each element. - boost::multi_index::const_mem_fun< - Option, - uint16_t, - &Option::getType - >, - // Indicate that OptionPtr is a member of - // OptionDescriptor structure. - boost::multi_index::member< - OptionDescriptor, - OptionPtr, - &OptionDescriptor::option - > - > - >, - // Start definition of index #2. - // Use 'persistent' struct member as a key. - boost::multi_index::hashed_non_unique< - boost::multi_index::member< - OptionDescriptor, - bool, - &OptionDescriptor::persistent - > - > - > - > OptionContainer; - - /// Pointer to the OptionContainer object. - typedef boost::shared_ptr OptionContainerPtr; - /// Type of the index #1 - option type. - typedef OptionContainer::nth_index<1>::type OptionContainerTypeIndex; - /// Pair of iterators to represent the range of options having the - /// same option type value. The first element in this pair represents - /// the beginning of the range, the second element represents the end. - typedef std::pair OptionContainerTypeRange; - /// Type of the index #2 - option persistency flag. - typedef OptionContainer::nth_index<2>::type OptionContainerPersistIndex; - /// @brief Holds optional information about relay. /// /// In some cases it is beneficial to have additional information about diff --git a/src/lib/dhcpsrv/tests/Makefile.am b/src/lib/dhcpsrv/tests/Makefile.am index 0f93ddd4f..be85605a9 100644 --- a/src/lib/dhcpsrv/tests/Makefile.am +++ b/src/lib/dhcpsrv/tests/Makefile.am @@ -56,6 +56,7 @@ libdhcpsrv_unittests_SOURCES += addr_utilities_unittest.cc libdhcpsrv_unittests_SOURCES += alloc_engine_unittest.cc libdhcpsrv_unittests_SOURCES += callout_handle_store_unittest.cc libdhcpsrv_unittests_SOURCES += cfg_iface_unittest.cc +libdhcpsrv_unittests_SOURCES += cfg_option_unittest.cc libdhcpsrv_unittests_SOURCES += cfg_option_def_unittest.cc libdhcpsrv_unittests_SOURCES += cfgmgr_unittest.cc libdhcpsrv_unittests_SOURCES += csv_lease_file4_unittest.cc diff --git a/src/lib/dhcpsrv/tests/cfg_option_unittest.cc b/src/lib/dhcpsrv/tests/cfg_option_unittest.cc new file mode 100644 index 000000000..fa34d77e2 --- /dev/null +++ b/src/lib/dhcpsrv/tests/cfg_option_unittest.cc @@ -0,0 +1,243 @@ +// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC") +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE +// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +#include +#include +#include +#include + +using namespace isc; +using namespace isc::dhcp; + +namespace { + +// This test verifies that multiple options can be added to the configuration +// and that they can be retrieved using the option space name. +TEST(CfgOptionTest, add) { + CfgOption cfg; + + // Differentiate options by their codes (100-109) + for (uint16_t code = 100; code < 110; ++code) { + OptionPtr option(new Option(Option::V6, code, OptionBuffer(10, 0xFF))); + ASSERT_NO_THROW(cfg.add(option, false, "dhcp6")); + } + + // Add 7 options to another option space. The option codes partially overlap + // with option codes that we have added to dhcp6 option space. + for (uint16_t code = 105; code < 112; ++code) { + OptionPtr option(new Option(Option::V6, code, OptionBuffer(10, 0xFF))); + ASSERT_NO_THROW(cfg.add(option, false, "isc")); + } + + // Get options from the Subnet and check if all 10 are there. + OptionContainerPtr options = cfg.getAll("dhcp6"); + ASSERT_TRUE(options); + ASSERT_EQ(10, options->size()); + + // Validate codes of options added to dhcp6 option space. + uint16_t expected_code = 100; + for (OptionContainer::const_iterator option_desc = options->begin(); + option_desc != options->end(); ++option_desc) { + ASSERT_TRUE(option_desc->option); + EXPECT_EQ(expected_code, option_desc->option->getType()); + ++expected_code; + } + + options = cfg.getAll("isc"); + ASSERT_TRUE(options); + ASSERT_EQ(7, options->size()); + + // Validate codes of options added to isc option space. + expected_code = 105; + for (OptionContainer::const_iterator option_desc = options->begin(); + option_desc != options->end(); ++option_desc) { + ASSERT_TRUE(option_desc->option); + EXPECT_EQ(expected_code, option_desc->option->getType()); + ++expected_code; + } + + // Try to get options from a non-existing option space. + options = cfg.getAll("abcd"); + ASSERT_TRUE(options); + EXPECT_TRUE(options->empty()); +} + +// This test verifies that single option can be retrieved from the configuration +// using option code and option space. +TEST(CfgOption, get) { + CfgOption cfg; + + // Add 10 options to a "dhcp6" option space in the subnet. + for (uint16_t code = 100; code < 110; ++code) { + OptionPtr option(new Option(Option::V6, code, OptionBuffer(10, 0xFF))); + ASSERT_NO_THROW(cfg.add(option, false, "dhcp6")); + } + + // Check that we can get each added option descriptor using + // individually. + for (uint16_t code = 100; code < 110; ++code) { + std::ostringstream stream; + // First, try the invalid option space name. + OptionDescriptor desc = cfg.get("isc", code); + // Returned descriptor should contain NULL option ptr. + EXPECT_FALSE(desc.option); + // Now, try the valid option space. + desc = cfg.get("dhcp6", code); + // Test that the option code matches the expected code. + ASSERT_TRUE(desc.option); + EXPECT_EQ(code, desc.option->getType()); + } +} + +// This test verifies that the same options can be added to the configuration +// under different option space. +TEST(CfgOptionTest, addNonUniqueOptions) { + CfgOption cfg; + + // Create a set of options with non-unique codes. + for (int i = 0; i < 2; ++i) { + // In the inner loop we create options with unique codes (100-109). + for (uint16_t code = 100; code < 110; ++code) { + OptionPtr option(new Option(Option::V6, code, OptionBuffer(10, 0xFF))); + ASSERT_NO_THROW(cfg.add(option, false, "dhcp6")); + } + } + + // Sanity check that all options are there. + OptionContainerPtr options = cfg.getAll("dhcp6"); + ASSERT_EQ(20, options->size()); + + // Use container index #1 to get the options by their codes. + OptionContainerTypeIndex& idx = options->get<1>(); + // Look for the codes 100-109. + for (uint16_t code = 100; code < 110; ++ code) { + // For each code we should get two instances of options-> + std::pair range = + idx.equal_range(code); + // Distance between iterators indicates how many options + // have been retured for the particular code. + ASSERT_EQ(2, distance(range.first, range.second)); + // Check that returned options actually have the expected option code. + for (OptionContainerTypeIndex::const_iterator option_desc = range.first; + option_desc != range.second; ++option_desc) { + ASSERT_TRUE(option_desc->option); + EXPECT_EQ(code, option_desc->option->getType()); + } + } + + // Let's try to find some non-exiting option. + const uint16_t non_existing_code = 150; + std::pair range = + idx.equal_range(non_existing_code); + // Empty set is expected. + EXPECT_EQ(0, distance(range.first, range.second)); +} + +// This test verifies that the option with the persistency flag can be +// added to the configuration and that options with the persistency flags +// can be retrieved. +TEST(Subnet6Test, addPersistentOption) { + CfgOption cfg; + + // Add 10 options to the subnet with option codes 100 - 109. + for (uint16_t code = 100; code < 110; ++code) { + OptionPtr option(new Option(Option::V6, code, OptionBuffer(10, 0xFF))); + // We create 10 options and want some of them to be flagged + // persistent and some non-persistent. Persistent options are + // those that server sends to clients regardless if they ask + // for them or not. We pick 3 out of 10 options and mark them + // non-persistent and 7 other options persistent. + // Code values: 102, 105 and 108 are divisible by 3 + // and options with these codes will be flagged non-persistent. + // Options with other codes will be flagged persistent. + bool persistent = (code % 3) ? true : false; + ASSERT_NO_THROW(cfg.add(option, persistent, "dhcp6")); + } + + // Get added options from the subnet. + OptionContainerPtr options = cfg.getAll("dhcp6"); + + // options->get<2> returns reference to container index #2. This + // index is used to access options by the 'persistent' flag. + OptionContainerPersistIndex& idx = options->get<2>(); + + // Get all persistent options-> + std::pair range_persistent = + idx.equal_range(true); + // 3 out of 10 options have been flagged persistent. + ASSERT_EQ(7, distance(range_persistent.first, range_persistent.second)); + + // Get all non-persistent options-> + std::pair range_non_persistent = + idx.equal_range(false); + // 7 out of 10 options have been flagged persistent. + ASSERT_EQ(3, distance(range_non_persistent.first, range_non_persistent.second)); +} + +// This test verifies that the vendor option can be added to the configuration. +TEST(CfgOptionTest, addVendorOptions) { + CfgOption cfg; + + // Differentiate options by their codes (100-109) + for (uint16_t code = 100; code < 110; ++code) { + OptionPtr option(new Option(Option::V6, code, OptionBuffer(10, 0xFF))); + ASSERT_NO_THROW(cfg.add(option, false, "vendor-12345678")); + } + + // Add 7 options to another option space. The option codes partially overlap + // with option codes that we have added to dhcp6 option space. + for (uint16_t code = 105; code < 112; ++code) { + OptionPtr option(new Option(Option::V6, code, OptionBuffer(10, 0xFF))); + ASSERT_NO_THROW(cfg.add(option, false, "vendor-87654321")); + } + + // Get options from the Subnet and check if all 10 are there. + OptionContainerPtr options = cfg.getAll(12345678); + ASSERT_TRUE(options); + ASSERT_EQ(10, options->size()); + + // Validate codes of options added to dhcp6 option space. + uint16_t expected_code = 100; + for (OptionContainer::const_iterator option_desc = options->begin(); + option_desc != options->end(); ++option_desc) { + ASSERT_TRUE(option_desc->option); + EXPECT_EQ(expected_code, option_desc->option->getType()); + ++expected_code; + } + + options = cfg.getAll(87654321); + ASSERT_TRUE(options); + ASSERT_EQ(7, options->size()); + + // Validate codes of options added to isc option space. + expected_code = 105; + for (OptionContainer::const_iterator option_desc = options->begin(); + option_desc != options->end(); ++option_desc) { + ASSERT_TRUE(option_desc->option); + EXPECT_EQ(expected_code, option_desc->option->getType()); + ++expected_code; + } + + // Try to get options from a non-existing option space. + options = cfg.getAll(1111111); + ASSERT_TRUE(options); + EXPECT_TRUE(options->empty()); +} + + +} // end of anonymous namespace diff --git a/src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc b/src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc index ed794cf4c..d15fb92e7 100644 --- a/src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc +++ b/src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc @@ -459,19 +459,19 @@ public: OptionPtr getOptionPtr(std::string space, uint32_t code) { OptionPtr option_ptr; - Subnet::OptionContainerPtr options = + OptionContainerPtr options = parser_context_->options_->getItems(space); // Should always be able to get options list even if it is empty. EXPECT_TRUE(options); if (options) { // Attempt to find desired option. - const Subnet::OptionContainerTypeIndex& idx = options->get<1>(); - const Subnet::OptionContainerTypeRange& range = + const OptionContainerTypeIndex& idx = options->get<1>(); + const OptionContainerTypeRange& range = idx.equal_range(code); int cnt = std::distance(range.first, range.second); EXPECT_EQ(1, cnt); if (cnt == 1) { - Subnet::OptionDescriptor desc = *(idx.begin()); + OptionDescriptor desc = *(idx.begin()); option_ptr = desc.option; EXPECT_TRUE(option_ptr); } @@ -1122,11 +1122,11 @@ public: /// @param ctx A pointer to a context. /// @param opt_type Expected option type. void checkOptionType(const ParserContext& ctx, const uint16_t opt_type) { - Subnet::OptionContainerPtr options = + OptionContainerPtr options = ctx.options_->getItems("option-space"); ASSERT_TRUE(options); - Subnet::OptionContainerTypeIndex& idx = options->get<1>(); - Subnet::OptionContainerTypeRange range = idx.equal_range(opt_type); + OptionContainerTypeIndex& idx = options->get<1>(); + OptionContainerTypeRange range = idx.equal_range(opt_type); ASSERT_EQ(1, std::distance(range.first, range.second)); } @@ -1217,7 +1217,7 @@ public: // Add new option, with option code 10, to the context. ASSERT_TRUE(ctx.options_); OptionPtr opt1(new Option(Option::V6, 10)); - Subnet::OptionDescriptor desc1(opt1, false); + OptionDescriptor desc1(opt1, false); std::string option_space = "option-space"; ASSERT_TRUE(desc1.option); ctx.options_->addItem(desc1, option_space); @@ -1430,7 +1430,7 @@ public: SCOPED_TRACE("Check that option remains the same in the new context" " when the option in the original context is changed"); ctx.options_->clearItems(); - Subnet::OptionDescriptor desc(OptionPtr(new Option(Option::V6, + OptionDescriptor desc(OptionPtr(new Option(Option::V6, 100)), false); diff --git a/src/lib/dhcpsrv/tests/subnet_unittest.cc b/src/lib/dhcpsrv/tests/subnet_unittest.cc index 99332e7f6..c54c671e0 100644 --- a/src/lib/dhcpsrv/tests/subnet_unittest.cc +++ b/src/lib/dhcpsrv/tests/subnet_unittest.cc @@ -657,13 +657,13 @@ TEST(Subnet6Test, addOptions) { } // Get options from the Subnet and check if all 10 are there. - Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp6"); + OptionContainerPtr options = subnet->getOptionDescriptors("dhcp6"); ASSERT_TRUE(options); ASSERT_EQ(10, options->size()); // Validate codes of options added to dhcp6 option space. uint16_t expected_code = 100; - for (Subnet::OptionContainer::const_iterator option_desc = options->begin(); + for (OptionContainer::const_iterator option_desc = options->begin(); option_desc != options->end(); ++option_desc) { ASSERT_TRUE(option_desc->option); EXPECT_EQ(expected_code, option_desc->option->getType()); @@ -676,7 +676,7 @@ TEST(Subnet6Test, addOptions) { // Validate codes of options added to isc option space. expected_code = 105; - for (Subnet::OptionContainer::const_iterator option_desc = options->begin(); + for (OptionContainer::const_iterator option_desc = options->begin(); option_desc != options->end(); ++option_desc) { ASSERT_TRUE(option_desc->option); EXPECT_EQ(expected_code, option_desc->option->getType()); @@ -715,22 +715,22 @@ TEST(Subnet6Test, addNonUniqueOptions) { } // Sanity check that all options are there. - Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp6"); + OptionContainerPtr options = subnet->getOptionDescriptors("dhcp6"); ASSERT_EQ(20, options->size()); // Use container index #1 to get the options by their codes. - Subnet::OptionContainerTypeIndex& idx = options->get<1>(); + OptionContainerTypeIndex& idx = options->get<1>(); // Look for the codes 100-109. for (uint16_t code = 100; code < 110; ++ code) { // For each code we should get two instances of options-> - std::pair range = + std::pair range = idx.equal_range(code); // Distance between iterators indicates how many options // have been retured for the particular code. ASSERT_EQ(2, distance(range.first, range.second)); // Check that returned options actually have the expected option code. - for (Subnet::OptionContainerTypeIndex::const_iterator option_desc = range.first; + for (OptionContainerTypeIndex::const_iterator option_desc = range.first; option_desc != range.second; ++option_desc) { ASSERT_TRUE(option_desc->option); EXPECT_EQ(code, option_desc->option->getType()); @@ -739,8 +739,8 @@ TEST(Subnet6Test, addNonUniqueOptions) { // Let's try to find some non-exiting option. const uint16_t non_existing_code = 150; - std::pair range = + std::pair range = idx.equal_range(non_existing_code); // Empty set is expected. EXPECT_EQ(0, distance(range.first, range.second)); @@ -789,22 +789,22 @@ TEST(Subnet6Test, addPersistentOption) { } // Get added options from the subnet. - Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp6"); + OptionContainerPtr options = subnet->getOptionDescriptors("dhcp6"); // options->get<2> returns reference to container index #2. This // index is used to access options by the 'persistent' flag. - Subnet::OptionContainerPersistIndex& idx = options->get<2>(); + OptionContainerPersistIndex& idx = options->get<2>(); // Get all persistent options-> - std::pair range_persistent = + std::pair range_persistent = idx.equal_range(true); // 3 out of 10 options have been flagged persistent. ASSERT_EQ(7, distance(range_persistent.first, range_persistent.second)); // Get all non-persistent options-> - std::pair range_non_persistent = + std::pair range_non_persistent = idx.equal_range(false); // 7 out of 10 options have been flagged persistent. ASSERT_EQ(3, distance(range_non_persistent.first, range_non_persistent.second)); @@ -829,7 +829,7 @@ TEST(Subnet6Test, getOptionDescriptor) { for (uint16_t code = 100; code < 110; ++code) { std::ostringstream stream; // First, try the invalid option space name. - Subnet::OptionDescriptor desc = subnet->getOptionDescriptor("isc", code); + OptionDescriptor desc = subnet->getOptionDescriptor("isc", code); // Returned descriptor should contain NULL option ptr. EXPECT_FALSE(desc.option); // Now, try the valid option space. @@ -864,13 +864,13 @@ TEST(Subnet6Test, addVendorOptions) { } // Get options from the Subnet and check if all 10 are there. - Subnet::OptionContainerPtr options = subnet->getVendorOptionDescriptors(vendor_id1); + OptionContainerPtr options = subnet->getVendorOptionDescriptors(vendor_id1); ASSERT_TRUE(options); ASSERT_EQ(10, options->size()); // Validate codes of options added to dhcp6 option space. uint16_t expected_code = 100; - for (Subnet::OptionContainer::const_iterator option_desc = options->begin(); + for (OptionContainer::const_iterator option_desc = options->begin(); option_desc != options->end(); ++option_desc) { ASSERT_TRUE(option_desc->option); EXPECT_EQ(expected_code, option_desc->option->getType()); @@ -883,7 +883,7 @@ TEST(Subnet6Test, addVendorOptions) { // Validate codes of options added to isc option space. expected_code = 105; - for (Subnet::OptionContainer::const_iterator option_desc = options->begin(); + for (OptionContainer::const_iterator option_desc = options->begin(); option_desc != options->end(); ++option_desc) { ASSERT_TRUE(option_desc->option); EXPECT_EQ(expected_code, option_desc->option->getType()); -- GitLab