From: Marcin Siodelski Date: Sat, 4 Sep 2021 15:49:29 +0000 (+0200) Subject: [#2077] Init match expressions X-Git-Tag: Kea-2.0.0~125 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=733a34a843d449bc190ce4d4bef505a80afc76ca;p=thirdparty%2Fkea.git [#2077] Init match expressions Match expressions are now initialized for client classes fetched from the configuration backend. --- diff --git a/src/lib/dhcpsrv/cb_ctl_dhcp4.cc b/src/lib/dhcpsrv/cb_ctl_dhcp4.cc index 6b3c36773e..3c53d05223 100644 --- a/src/lib/dhcpsrv/cb_ctl_dhcp4.cc +++ b/src/lib/dhcpsrv/cb_ctl_dhcp4.cc @@ -203,6 +203,9 @@ CBControlDHCPv4::databaseConfigApply(const BackendSelector& backend_selector, if (audit_entries.empty() || !updated_entries.empty()) { ClientClassDictionary client_classes = getMgr().getPool()->getAllClientClasses4(backend_selector, server_selector); + // Match expressions are not initialized for classes returned from the config backend. + // We have to ensure to initialize them before they can be used by the server. + client_classes.initMatchExpr(AF_INET); external_cfg->setClientClassDictionary(boost::make_shared(client_classes)); } diff --git a/src/lib/dhcpsrv/cb_ctl_dhcp6.cc b/src/lib/dhcpsrv/cb_ctl_dhcp6.cc index d2096045f3..2713db2201 100644 --- a/src/lib/dhcpsrv/cb_ctl_dhcp6.cc +++ b/src/lib/dhcpsrv/cb_ctl_dhcp6.cc @@ -202,6 +202,9 @@ CBControlDHCPv6::databaseConfigApply(const db::BackendSelector& backend_selector if (audit_entries.empty() || !updated_entries.empty()) { ClientClassDictionary client_classes = getMgr().getPool()->getAllClientClasses6(backend_selector, server_selector); + // Match expressions are not initialized for classes returned from the config backend. + // We have to ensure to initialize them before they can be used by the server. + client_classes.initMatchExpr(AF_INET6); external_cfg->setClientClassDictionary(boost::make_shared(client_classes)); } diff --git a/src/lib/dhcpsrv/client_class_def.cc b/src/lib/dhcpsrv/client_class_def.cc index 6a3b3c10cd..8666f9432a 100644 --- a/src/lib/dhcpsrv/client_class_def.cc +++ b/src/lib/dhcpsrv/client_class_def.cc @@ -9,8 +9,11 @@ #include #include #include +#include #include +#include + using namespace isc::data; namespace isc { @@ -384,6 +387,25 @@ ClientClassDictionary::equals(const ClientClassDictionary& other) const { return (true); } +void +ClientClassDictionary::initMatchExpr(uint16_t family) { + std::queue expressions; + for (auto c : *list_) { + ExpressionPtr match_expr = boost::make_shared(); + if (!c->getTest().empty()) { + ExpressionParser parser; + parser.parse(match_expr, Element::create(c->getTest()), family); + } + expressions.push(match_expr); + } + // All expressions successfully initialied. Let's set them for the + // client classes in the dictionary. + for (auto c : *list_) { + c->setMatchExpr(expressions.front()); + expressions.pop(); + } +} + ElementPtr ClientClassDictionary::toElement() const { ElementPtr result = Element::createList(); diff --git a/src/lib/dhcpsrv/client_class_def.h b/src/lib/dhcpsrv/client_class_def.h index 89542b0294..012f3e9c39 100644 --- a/src/lib/dhcpsrv/client_class_def.h +++ b/src/lib/dhcpsrv/client_class_def.h @@ -397,6 +397,12 @@ public: /// @return true if descriptors equal, false otherwise. bool equals(const ClientClassDictionary& other) const; + /// @brief Iterates over the classes in the dictionary and ensures that + /// that match expressions are initialized. + /// + /// @param family Class universe, e.g. AF_INET or AF_INET6. + void initMatchExpr(uint16_t family); + /// @brief Equality operator. /// /// @param other Other client class dictionary to compare to. diff --git a/src/lib/dhcpsrv/tests/cb_ctl_dhcp_unittest.cc b/src/lib/dhcpsrv/tests/cb_ctl_dhcp_unittest.cc index 8bd10b8082..e6318b1336 100644 --- a/src/lib/dhcpsrv/tests/cb_ctl_dhcp_unittest.cc +++ b/src/lib/dhcpsrv/tests/cb_ctl_dhcp_unittest.cc @@ -382,6 +382,7 @@ public: // Insert client classes into the database. auto expression = boost::make_shared(); ClientClassDefPtr client_class = boost::make_shared("first-class", expression); + client_class->setTest("substring(option[1].hex, 0, 8) == 'my-value'"); client_class->setId(1); client_class->setModificationTime(getTimestamp("dhcp4_client_class")); mgr.getPool()->createUpdateClientClass4(BackendSelector::UNSPEC(), ServerSelector::ALL(), @@ -592,6 +593,8 @@ public: if (hasConfigElement("dhcp4_client_class") && (getTimestamp("dhcp4_client_class") > lb_modification_time)) { ASSERT_TRUE(found_class); + ASSERT_TRUE(found_class->getMatchExpr()); + EXPECT_GT(found_class->getMatchExpr()->size(), 0); EXPECT_EQ("first-class", found_class->getName()); } else { @@ -1175,6 +1178,7 @@ public: // Insert client classes into the database. auto expression = boost::make_shared(); ClientClassDefPtr client_class = boost::make_shared("first-class", expression); + client_class->setTest("substring(option[1].hex, 0, 8) == 'my-value'"); client_class->setId(1); client_class->setModificationTime(getTimestamp("dhcp6_client_class")); mgr.getPool()->createUpdateClientClass6(BackendSelector::UNSPEC(), ServerSelector::ALL(), @@ -1385,6 +1389,8 @@ public: if (hasConfigElement("dhcp6_client_class") && (getTimestamp("dhcp6_client_class") > lb_modification_time)) { ASSERT_TRUE(found_class); + ASSERT_TRUE(found_class->getMatchExpr()); + EXPECT_GT(found_class->getMatchExpr()->size(), 0); EXPECT_EQ("first-class", found_class->getName()); } else { diff --git a/src/lib/dhcpsrv/tests/client_class_def_unittest.cc b/src/lib/dhcpsrv/tests/client_class_def_unittest.cc index 5532dd9050..b93c7198ea 100644 --- a/src/lib/dhcpsrv/tests/client_class_def_unittest.cc +++ b/src/lib/dhcpsrv/tests/client_class_def_unittest.cc @@ -542,6 +542,62 @@ TEST(ClientClassDictionary, dependency) { EXPECT_EQ("cc4", depend); } +// Tests that match expressions are set for all client classes in the +// dictionary. +TEST(ClientClassDictionary, initMatchExpr) { + ClientClassDictionaryPtr dictionary(new ClientClassDictionary()); + ExpressionPtr expr; + CfgOptionPtr cfg_option; + + // Add several classes. + ASSERT_NO_THROW(dictionary->addClass("foo", expr, "", false, + false, cfg_option)); + ASSERT_NO_THROW(dictionary->addClass("bar", expr, "member('KNOWN') or member('foo')", false, + false, cfg_option)); + ASSERT_NO_THROW(dictionary->addClass("baz", expr, "substring(option[61].hex,0,3) == 'foo'", false, + false, cfg_option)); + + // Create match expressions for all of them. + ASSERT_NO_THROW(dictionary->initMatchExpr(AF_INET)); + + // Ensure that the expressions were created. + auto classes = *(dictionary->getClasses()); + EXPECT_TRUE(classes[0]->getMatchExpr()); + EXPECT_EQ(0, classes[0]->getMatchExpr()->size()); + + EXPECT_TRUE(classes[1]->getMatchExpr()); + EXPECT_EQ(3, classes[1]->getMatchExpr()->size()); + + EXPECT_TRUE(classes[2]->getMatchExpr()); + EXPECT_EQ(6, classes[2]->getMatchExpr()->size()); +} + +// Tests that an error is returned when any of the test expressions is +// invalid, and that no expressions are initialized if there is an error +// for a single expresion. +TEST(ClientClassDictionary, initMatchExprError) { + ClientClassDictionaryPtr dictionary(new ClientClassDictionary()); + ExpressionPtr expr; + CfgOptionPtr cfg_option; + + // Add several classes. One of them has invalid test expression. + ASSERT_NO_THROW(dictionary->addClass("foo", expr, "member('KNOWN')", false, + false, cfg_option)); + ASSERT_NO_THROW(dictionary->addClass("bar", expr, "wrong expression", false, + false, cfg_option)); + ASSERT_NO_THROW(dictionary->addClass("baz", expr, "substring(option[61].hex,0,3) == 'foo'", false, + false, cfg_option)); + + // An attempt to initialize match expressions should fail because the + // test expression for the second class is invalid. + ASSERT_THROW(dictionary->initMatchExpr(AF_INET), std::exception); + + // Ensure that no classes have their match expressions modified. + for (auto c : (*dictionary->getClasses())) { + EXPECT_FALSE(c->getMatchExpr()); + } +} + // Tests the default constructor regarding fixed fields TEST(ClientClassDef, fixedFieldsDefaults) { boost::scoped_ptr cclass;