diff options
author | Thomas Markwalder <tmark@isc.org> | 2024-09-23 16:51:50 +0200 |
---|---|---|
committer | Thomas Markwalder <tmark@isc.org> | 2024-10-15 19:51:56 +0200 |
commit | 74a37d39e083035bce54e747c0701f81bcafc020 (patch) | |
tree | 3693bfb64b2c5c32dbc16a35c225b5b18e42f850 /src/lib | |
parent | [#3552] Addressed comments (diff) | |
download | kea-74a37d39e083035bce54e747c0701f81bcafc020.tar.xz kea-74a37d39e083035bce54e747c0701f81bcafc020.zip |
[#3583] Added client-class to OptionDescriptor
option-data.client-classes can be parsed and
stored in OptionDescriptor.
/src/bin/dhcp4/dhcp4_lexer.ll
/src/bin/dhcp4/dhcp4_parser.yy
/src/bin/dhcp6/dhcp6_lexer.ll
/src/bin/dhcp6/dhcp6_parser.yy
/src/lib/dhcp/classify.*
Added ClientClasses copy and equality operators
/src/lib/dhcp/tests/classify_unittest.cc
Udpated unit tests
/src/lib/dhcpsrv/cfg_option.cc
Added OptionDecription::client_classes_ and supporting funcs
/src/lib/dhcpsrv/parsers/option_data_parser.cc
OptionDataParser::createOption() - modified to parse 'client-classes'
/src/lib/dhcpsrv/parsers/simple_parser4.cc
Added 'client-classes' to OPTION4_PARAMETERS
/src/lib/dhcpsrv/parsers/simple_parser6.cc
Added 'client-classes' to OPTION6_PARAMETERS
/src/lib/dhcpsrv/tests/cfg_option_unittest.cc
Updated tests
/src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc
TEST_F(ParseConfigTest, optionDataClientClasses4)
TEST_F(ParseConfigTest, optionDataClientClasses6)
TEST_F(ParseConfigTest, optionDataClientClassesEmpty4)
TEST_F(ParseConfigTest, optionDataClientClassesEmpty6)
- new tests
Diffstat (limited to 'src/lib')
-rw-r--r-- | src/lib/dhcp/classify.cc | 21 | ||||
-rw-r--r-- | src/lib/dhcp/classify.h | 27 | ||||
-rw-r--r-- | src/lib/dhcp/tests/classify_unittest.cc | 27 | ||||
-rw-r--r-- | src/lib/dhcpsrv/cfg_option.cc | 20 | ||||
-rw-r--r-- | src/lib/dhcpsrv/cfg_option.h | 14 | ||||
-rw-r--r-- | src/lib/dhcpsrv/parsers/option_data_parser.cc | 7 | ||||
-rw-r--r-- | src/lib/dhcpsrv/parsers/simple_parser4.cc | 21 | ||||
-rw-r--r-- | src/lib/dhcpsrv/parsers/simple_parser6.cc | 21 | ||||
-rw-r--r-- | src/lib/dhcpsrv/tests/cfg_option_unittest.cc | 33 | ||||
-rw-r--r-- | src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc | 175 |
10 files changed, 336 insertions, 30 deletions
diff --git a/src/lib/dhcp/classify.cc b/src/lib/dhcp/classify.cc index f42a9e3710..784cc4a895 100644 --- a/src/lib/dhcp/classify.cc +++ b/src/lib/dhcp/classify.cc @@ -36,6 +36,12 @@ ClientClasses::ClientClasses(const std::string& class_names) } } +ClientClasses::ClientClasses(const ClientClasses& other) { + for (auto const& cclass : other) { + insert(cclass); + } +} + void ClientClasses::erase(const ClientClass& class_name) { auto& idx = container_.get<ClassNameTag>(); @@ -75,5 +81,20 @@ ClientClasses::toElement() const { return (result); } +bool +ClientClasses::equals(const ClientClasses& other) const { + return ((size() == other.size()) && std::equal(cbegin(), cend(), other.cbegin())); +} + +ClientClasses& +ClientClasses::operator=(const ClientClasses& other) { + clear(); + for (auto const& cclass : other) { + insert(cclass); + } + + return (*this); +} + } // end of namespace isc::dhcp } // end of namespace isc diff --git a/src/lib/dhcp/classify.h b/src/lib/dhcp/classify.h index 34977a5989..cb5340dfe5 100644 --- a/src/lib/dhcp/classify.h +++ b/src/lib/dhcp/classify.h @@ -122,6 +122,33 @@ namespace dhcp { /// with commas. The class names are trimmed before insertion to the set. ClientClasses(const std::string& class_names); + /// @brief Copy constructor. + /// + /// @param ClientClasses object to be copied. + ClientClasses(const ClientClasses& other); + + /// @brief Assigns the contents of on container to another + ClientClasses& operator=(const ClientClasses& other); + + /// @brief Compares two ClientClasses container for equality + /// + /// @return True if the two containers are equal, false otherwise. + bool equals(const ClientClasses& other) const; + + /// @brief Compares two ClientClasses container for equality + /// + /// @return True if the two containers are equal, false otherwise. + bool operator==(const ClientClasses& other) const { + return(equals(other)); + } + + /// @brief Compares two ClientClasses container for inequality + /// + /// @return True if the two containers are not equal, false otherwise. + bool operator!=(const ClientClasses& other) const { + return(!equals(other)); + } + /// @brief Insert an element. /// /// @param class_name The name of the class to insert diff --git a/src/lib/dhcp/tests/classify_unittest.cc b/src/lib/dhcp/tests/classify_unittest.cc index 4cf7037e36..49bfc9c48f 100644 --- a/src/lib/dhcp/tests/classify_unittest.cc +++ b/src/lib/dhcp/tests/classify_unittest.cc @@ -28,7 +28,7 @@ TEST(ClassifyTest, ClientClasses) { EXPECT_FALSE(classes.contains("")); EXPECT_FALSE(classes.contains("alpha")); EXPECT_FALSE(classes.contains("beta")); - EXPECT_FALSE(classes.contains("gamma")); + EXPECT_FALSE(classes.contains("gamma")); classes.insert("beta"); EXPECT_FALSE(classes.contains("")); EXPECT_FALSE(classes.contains("alpha")); @@ -40,6 +40,31 @@ TEST(ClassifyTest, ClientClasses) { EXPECT_TRUE (classes.contains("alpha")); EXPECT_TRUE (classes.contains("beta")); EXPECT_TRUE (classes.contains("gamma")); + + // Copy Constructor and equality operators. + ClientClasses classes2(classes); + EXPECT_EQ(classes, classes2); + + // A bigger than B + classes.insert("zeta"); + EXPECT_NE(classes, classes2); + + // A and B equal again. + classes2.insert("zeta"); + EXPECT_EQ(classes, classes2); + + // B bigger than A. + classes2.insert("chi"); + EXPECT_NE(classes, classes2); + + // Order matters. + ClientClasses classes3; + classes3.insert("alpha"); + classes3.insert("gamma"); + classes3.insert("beta"); + classes3.insert("zeta"); + EXPECT_EQ(classes3.size(), classes.size()); + EXPECT_NE(classes3, classes); } // Check if ClientClasses object can be created from the string of comma diff --git a/src/lib/dhcpsrv/cfg_option.cc b/src/lib/dhcpsrv/cfg_option.cc index 61137ed5eb..a25341ec8b 100644 --- a/src/lib/dhcpsrv/cfg_option.cc +++ b/src/lib/dhcpsrv/cfg_option.cc @@ -51,6 +51,14 @@ OptionDescriptor::equals(const OptionDescriptor& other) const { option_->equals(other.option_)); } +void +OptionDescriptor::addClientClass(const std::string& class_name) { + std::string trimmed = util::str::trim(class_name); + if (!trimmed.empty()) { + client_classes_.insert(ClientClass(trimmed)); + } +} + CfgOption::CfgOption() : encapsulated_(false) { } @@ -502,6 +510,11 @@ CfgOption::toElementWithMetadata(const bool include_metadata) const { map->set("metadata", opt.getMetadata()); } + // Include client-classes if not empty. + if (!opt.client_classes_.empty()) { + map->set("client-classes", opt.client_classes_.toElement()); + } + // Push on the list result->add(map); } @@ -557,10 +570,17 @@ CfgOption::toElementWithMetadata(const bool include_metadata) const { map->set("always-send", Element::create(opt.persistent_)); // Set the cancellation flag map->set("never-send", Element::create(opt.cancelled_)); + + // Include client-classes if not empty. + if (!opt.client_classes_.empty()) { + map->set("client-classes", opt.client_classes_.toElement()); + } + // Push on the list result->add(map); } } + return (result); } diff --git a/src/lib/dhcpsrv/cfg_option.h b/src/lib/dhcpsrv/cfg_option.h index 884e679c14..e3e9b52bb9 100644 --- a/src/lib/dhcpsrv/cfg_option.h +++ b/src/lib/dhcpsrv/cfg_option.h @@ -8,6 +8,7 @@ #define CFG_OPTION_H #include <dhcp/option.h> +#include <dhcp/classify.h> #include <dhcp/option_space_container.h> #include <cc/cfg_to_element.h> #include <cc/stamped_element.h> @@ -89,6 +90,10 @@ public: /// won't be set. std::string space_name_; + /// @brief Collection of classes for the which option is allowed. + /// An empty list means no class restrictions. + ClientClasses client_classes_; + /// @brief Constructor. /// /// @param opt option instance. @@ -122,7 +127,8 @@ public: persistent_(desc.persistent_), cancelled_(desc.cancelled_), formatted_value_(desc.formatted_value_), - space_name_(desc.space_name_) { + space_name_(desc.space_name_), + client_classes_(desc.client_classes_) { setContext(desc.getContext()); }; @@ -138,6 +144,7 @@ public: cancelled_ = other.cancelled_; formatted_value_ = other.formatted_value_; space_name_ = other.space_name_; + client_classes_ = other.client_classes_; setContext(other.getContext()); } return (*this); @@ -198,6 +205,11 @@ public: bool operator!=(const OptionDescriptor& other) const { return (!equals(other)); } + + /// @brief Adds new client class for which the option is allowed. + /// + /// @param class_name Class name. + void addClientClass(const std::string& class_name); }; /// @brief Multi index container for DHCP option descriptors. diff --git a/src/lib/dhcpsrv/parsers/option_data_parser.cc b/src/lib/dhcpsrv/parsers/option_data_parser.cc index cd544d5833..2710ffe624 100644 --- a/src/lib/dhcpsrv/parsers/option_data_parser.cc +++ b/src/lib/dhcpsrv/parsers/option_data_parser.cc @@ -278,6 +278,7 @@ OptionDataParser::createOption(ConstElementPtr option_data) { Optional<std::string> data_param = extractData(option_data); std::string space_param = extractSpace(option_data); ConstElementPtr user_context = option_data->get("user-context"); + ConstElementPtr client_classes = option_data->get("client-classes"); // Require that option code or option name is specified. if (code_param.unspecified() && name_param.unspecified()) { @@ -440,6 +441,12 @@ OptionDataParser::createOption(ConstElementPtr option_data) { desc.setContext(user_context); } + if (client_classes) { + for (auto const& class_element : client_classes->listValue()) { + desc.addClientClass(class_element->stringValue()); + } + } + // All went good, so we can set the option space name. return (make_pair(desc, space_param)); } diff --git a/src/lib/dhcpsrv/parsers/simple_parser4.cc b/src/lib/dhcpsrv/parsers/simple_parser4.cc index 1666419441..de0f7a63ba 100644 --- a/src/lib/dhcpsrv/parsers/simple_parser4.cc +++ b/src/lib/dhcpsrv/parsers/simple_parser4.cc @@ -181,16 +181,17 @@ const SimpleDefaults SimpleParser4::OPTION4_DEF_DEFAULTS = { /// list and map types for entries. /// Order follows option_param rules in bison grammar. const SimpleKeywords SimpleParser4::OPTION4_PARAMETERS = { - { "name", Element::string }, - { "data", Element::string }, - { "code", Element::integer }, - { "space", Element::string }, - { "csv-format", Element::boolean }, - { "always-send", Element::boolean }, - { "never-send", Element::boolean }, - { "user-context", Element::map }, - { "comment", Element::string }, - { "metadata", Element::map } + { "name", Element::string }, + { "data", Element::string }, + { "code", Element::integer }, + { "space", Element::string }, + { "csv-format", Element::boolean }, + { "always-send", Element::boolean }, + { "never-send", Element::boolean }, + { "user-context", Element::map }, + { "comment", Element::string }, + { "metadata", Element::map }, + { "client-classes", Element::list } }; /// @brief This table defines default values for options in DHCPv4. diff --git a/src/lib/dhcpsrv/parsers/simple_parser6.cc b/src/lib/dhcpsrv/parsers/simple_parser6.cc index c0a2d349ec..f58ceecb31 100644 --- a/src/lib/dhcpsrv/parsers/simple_parser6.cc +++ b/src/lib/dhcpsrv/parsers/simple_parser6.cc @@ -175,16 +175,17 @@ const SimpleDefaults SimpleParser6::OPTION6_DEF_DEFAULTS = { /// list and map types for entries. /// Order follows option_param rules in bison grammar. const SimpleKeywords SimpleParser6::OPTION6_PARAMETERS = { - { "name", Element::string }, - { "data", Element::string }, - { "code", Element::integer }, - { "space", Element::string }, - { "csv-format", Element::boolean }, - { "always-send", Element::boolean }, - { "never-send", Element::boolean }, - { "user-context", Element::map }, - { "comment", Element::string }, - { "metadata", Element::map } + { "name", Element::string }, + { "data", Element::string }, + { "code", Element::integer }, + { "space", Element::string }, + { "csv-format", Element::boolean }, + { "always-send", Element::boolean }, + { "never-send", Element::boolean }, + { "user-context", Element::map }, + { "comment", Element::string }, + { "metadata", Element::map }, + { "client-classes", Element::list } }; /// @brief This table defines default values for options in DHCPv6. diff --git a/src/lib/dhcpsrv/tests/cfg_option_unittest.cc b/src/lib/dhcpsrv/tests/cfg_option_unittest.cc index 2fa42ae065..09b43c9f29 100644 --- a/src/lib/dhcpsrv/tests/cfg_option_unittest.cc +++ b/src/lib/dhcpsrv/tests/cfg_option_unittest.cc @@ -1256,6 +1256,8 @@ TEST_F(CfgOptionTest, unparse) { OptionDescriptor desc2(opt2, false, true, "12, 12, 12, 12"); std::string ctx = "{ \"comment\": \"foo\", \"bar\": 1 }"; desc2.setContext(data::Element::fromJSON(ctx)); + desc2.addClientClass("water"); + desc2.addClientClass("melon"); cfg.add(desc2, "dns"); OptionPtr opt3(new Option(Option::V6, D6O_STATUS_CODE, OptionBuffer(2, 0))); cfg.add(opt3, false, false, DHCP6_OPTION_SPACE); @@ -1264,9 +1266,23 @@ TEST_F(CfgOptionTest, unparse) { OptionPtr opt5(new Option(Option::V6, 111)); cfg.add(opt5, false, true, "vendor-5678"); + OptionPtr opt6(new Option(Option::V6, 112)); + OptionDescriptor desc6(opt6, false, false); + desc6.addClientClass("foo"); + desc6.addClientClass("bar"); + cfg.add(desc6, "vendor-9999"); + // Unparse std::string expected = "[\n" "{\n" + " \"code\": 13,\n" + " \"name\": \"status-code\",\n" + " \"space\": \"dhcp6\",\n" + " \"csv-format\": false,\n" + " \"data\": \"0000\",\n" + " \"always-send\": false,\n" + " \"never-send\": false\n" + "},{\n" " \"code\": 100,\n" " \"space\": \"dns\",\n" " \"csv-format\": false,\n" @@ -1274,6 +1290,7 @@ TEST_F(CfgOptionTest, unparse) { " \"always-send\": false,\n" " \"never-send\": true\n" "},{\n" + " \"client-classes\": [ \"water\", \"melon\" ],\n" " \"code\": 101,\n" " \"space\": \"dns\",\n" " \"csv-format\": true,\n" @@ -1282,14 +1299,6 @@ TEST_F(CfgOptionTest, unparse) { " \"never-send\": true,\n" " \"user-context\": { \"comment\": \"foo\", \"bar\": 1 }\n" "},{\n" - " \"code\": 13,\n" - " \"name\": \"status-code\",\n" - " \"space\": \"dhcp6\",\n" - " \"csv-format\": false,\n" - " \"data\": \"0000\",\n" - " \"always-send\": false,\n" - " \"never-send\": false\n" - "},{\n" " \"code\": 100,\n" " \"space\": \"vendor-1234\",\n" " \"csv-format\": false,\n" @@ -1301,6 +1310,14 @@ TEST_F(CfgOptionTest, unparse) { " \"space\": \"vendor-5678\",\n" " \"always-send\": false,\n" " \"never-send\": true\n" + "},{\n" + " \"always-send\": false,\n" + " \"client-classes\": [ \"foo\", \"bar\" ],\n" + " \"code\": 112,\n" + " \"csv-format\": false,\n" + " \"data\": \"\",\n" + " \"never-send\": false\n," + " \"space\": \"vendor-9999\"\n" "}]\n"; isc::test::runToElementTest<CfgOption>(expected, cfg); } diff --git a/src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc b/src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc index a715ae93f5..6af3945a31 100644 --- a/src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc +++ b/src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc @@ -8,6 +8,7 @@ #include <cc/command_interpreter.h> #include <cc/data.h> #include <cc/simple_parser.h> +#include <dhcp/classify.h> #include <dhcp/option.h> #include <dhcp/option_custom.h> #include <dhcp/option_int.h> @@ -461,6 +462,22 @@ public: return (option_ptr); } + /// @brief Find the OptionDescriptor for a given space and code within the parser + /// context. + /// @param space is the space name of the desired option. + /// @param code is the numeric "type" of the desired option. + /// @return an OptionDecriptorPtr to the descriptor found or an empty ptr + OptionDescriptorPtr getOptionDescriptor(std::string space, uint32_t code) { + OptionDescriptorPtr od_ptr; + const auto &cfg_options = CfgMgr::instance().getStagingCfg()->getCfgOption(); + auto od = cfg_options->get(space, code); + if (od.option_) { + od_ptr = OptionDescriptor::create(od); + } + + return (od_ptr); + } + /// @brief Wipes the contents of the context to allowing another parsing /// during a given test if needed. /// @param family protocol family to use during the test, defaults @@ -1881,6 +1898,164 @@ setHooksLibrariesConfig(const char* lib1 = NULL, const char* lib2 = NULL, return (config); } +/// @brief Check parsing of a v4 option with a client-class list. +TEST_F(ParseConfigTest, optionDataClientClasses4) { + family_ = AF_INET; + // Configuration string. + std::string config = + "{ \"option-def\": [ {" + " \"name\": \"foo\"," + " \"code\": 0," + " \"type\": \"ipv4-address\"," + " \"space\": \"isc\"" + " } ], " + " \"option-data\": [ {" + " \"name\": \"foo\"," + " \"space\": \"isc\"," + " \"code\": 0," + " \"data\": \"192.0.2.0\"," + " \"csv-format\": true," + " \"always-send\": false," + " \"client-classes\": [ \"water\", \"melon\" ]" + " } ]" + "}"; + + // Verify that the configuration string parses. + int rcode = parseConfiguration(config); + ASSERT_EQ(0, rcode); + + // Verify that the option can be retrieved. + OptionPtr opt_ptr = getOptionPtr("isc", 0); + ASSERT_TRUE(opt_ptr); + + // Verify the option descriptor's client-classes lists is correct. + ClientClasses exp_cc("water, melon"); + OptionDescriptorPtr od = getOptionDescriptor("isc", 0); + ASSERT_TRUE(od); + EXPECT_EQ(od->client_classes_, exp_cc); + + // Check if it can be unparsed. + CfgOptionsTest cfg(CfgMgr::instance().getStagingCfg()); + cfg.runCfgOptionsTest(family_, config); +} + +/// @brief Check parsing of a v4 option with a client-class list. +TEST_F(ParseConfigTest, optionDataClientClassesEmpty4) { + family_ = AF_INET; + // Configuration string. + std::string config = + "{ \"option-def\": [ {" + " \"name\": \"foo\"," + " \"code\": 0," + " \"type\": \"ipv4-address\"," + " \"space\": \"isc\"" + " } ], " + " \"option-data\": [ {" + " \"name\": \"foo\"," + " \"space\": \"isc\"," + " \"code\": 0," + " \"data\": \"192.0.2.0\"," + " \"csv-format\": true," + " \"always-send\": false," + " \"client-classes\": []" + " } ]" + "}"; + + // Verify that the configuration string parses. + int rcode = parseConfiguration(config); + ASSERT_EQ(0, rcode); + + // Verify that the option can be retrieved. + OptionPtr opt_ptr = getOptionPtr("isc", 0); + ASSERT_TRUE(opt_ptr); + + // Verify the option descriptor's client-classes list is empty. + OptionDescriptorPtr od = getOptionDescriptor("isc", 0); + ASSERT_TRUE(od); + EXPECT_TRUE(od->client_classes_.empty()); + + // We skip unparse test because client-classes is only emitited if not empty. +} + +/// @brief Check parsing of a v6 option with a client-class list. +TEST_F(ParseConfigTest, optionDataClientClasses6) { + family_ = AF_INET6; + // Configuration string. + std::string config = + "{ \"option-def\": [ {" + " \"name\": \"foo\"," + " \"code\": 0," + " \"type\": \"ipv4-address\"," + " \"space\": \"isc\"" + " } ], " + " \"option-data\": [ {" + " \"name\": \"foo\"," + " \"space\": \"isc\"," + " \"code\": 0," + " \"data\": \"192.0.2.0\"," + " \"csv-format\": true," + " \"always-send\": false," + " \"client-classes\": [ \"water\", \"melon\" ]" + " } ]" + "}"; + + // Verify that the configuration string parses. + int rcode = parseConfiguration(config); + ASSERT_EQ(0, rcode); + + // Verify that the option can be retrieved. + OptionPtr opt_ptr = getOptionPtr("isc", 0); + ASSERT_TRUE(opt_ptr); + + // Verify the option descriptor's client-classes lists is correct. + ClientClasses exp_cc("water, melon"); + OptionDescriptorPtr od = getOptionDescriptor("isc", 0); + ASSERT_TRUE(od); + EXPECT_EQ(od->client_classes_, exp_cc); + + // Check if it can be unparsed. + CfgOptionsTest cfg(CfgMgr::instance().getStagingCfg()); + cfg.runCfgOptionsTest(family_, config); +} + +/// @brief Check parsing of a v4 option with a client-class list. +TEST_F(ParseConfigTest, optionDataClientClassesEmpty6) { + family_ = AF_INET6; + // Configuration string. + std::string config = + "{ \"option-def\": [ {" + " \"name\": \"foo\"," + " \"code\": 0," + " \"type\": \"ipv4-address\"," + " \"space\": \"isc\"" + " } ], " + " \"option-data\": [ {" + " \"name\": \"foo\"," + " \"space\": \"isc\"," + " \"code\": 0," + " \"data\": \"192.0.2.0\"," + " \"csv-format\": true," + " \"always-send\": false," + " \"client-classes\": []" + " } ]" + "}"; + + // Verify that the configuration string parses. + int rcode = parseConfiguration(config); + ASSERT_EQ(0, rcode); + + // Verify that the option can be retrieved. + OptionPtr opt_ptr = getOptionPtr("isc", 0); + ASSERT_TRUE(opt_ptr); + + // Verify the option descriptor's client-classes list is empty. + OptionDescriptorPtr od = getOptionDescriptor("isc", 0); + ASSERT_TRUE(od); + EXPECT_TRUE(od->client_classes_.empty()); + + // We skip unparse test because client-classes is only emitited if not empty. +} + // hooks-libraries element that does not contain anything. TEST_F(ParseConfigTest, noHooksLibraries) { // Check that no libraries are currently loaded |