From: Francis Dupont Date: Fri, 24 Nov 2017 08:21:20 +0000 (+0100) Subject: [5374] Checkpoint: added member token, syntax and tests todo X-Git-Tag: trac5458a_base~14^2~30 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=0af25b87112b02bfd5ea824de9e20125b438a236;p=thirdparty%2Fkea.git [5374] Checkpoint: added member token, syntax and tests todo --- diff --git a/src/lib/dhcpsrv/parsers/client_class_def_parser.cc b/src/lib/dhcpsrv/parsers/client_class_def_parser.cc index 5829393342..6f1cfa1927 100644 --- a/src/lib/dhcpsrv/parsers/client_class_def_parser.cc +++ b/src/lib/dhcpsrv/parsers/client_class_def_parser.cc @@ -18,6 +18,7 @@ #include #include +#include using namespace isc::data; using namespace isc::asiolink; @@ -35,7 +36,8 @@ namespace dhcp { void ExpressionParser::parse(ExpressionPtr& expression, ConstElementPtr expression_cfg, - uint16_t family) { + uint16_t family, + std::function check_known) { if (expression_cfg->getType() != Element::string) { isc_throw(DhcpConfigError, "expression [" << expression_cfg->str() << "] must be a string, at (" @@ -47,7 +49,8 @@ ExpressionParser::parse(ExpressionPtr& expression, std::string value; expression_cfg->getValue(value); try { - EvalContext eval_ctx(family == AF_INET ? Option::V4 : Option::V6); + EvalContext eval_ctx(family == AF_INET ? Option::V4 : Option::V6, + check_known); eval_ctx.parseString(value); expression.reset(new Expression()); *expression = eval_ctx.expression; @@ -80,7 +83,9 @@ ClientClassDefParser::parse(ClientClassDictionaryPtr& class_dictionary, std::string test; if (test_cfg) { ExpressionParser parser; - parser.parse(match_expr, test_cfg, family); + using std::placeholders::_1; + auto check_known = std::bind(isClientClassKnown, class_dictionary, _1); + parser.parse(match_expr, test_cfg, family, check_known); test = test_cfg->stringValue(); } @@ -190,6 +195,36 @@ ClientClassDefParser::parse(ClientClassDictionaryPtr& class_dictionary, } } +std::list +ClientClassDefParser::builtinPrefixes = { + "VENDOR_CLASS_", "AFTER_", "EXTERNAL_" +}; + +bool +ClientClassDefParser::isClientClassKnown(ClientClassDictionaryPtr& class_dictionary, + const ClientClass& client_class) { + // First check built-in prefixes + for (std::list::const_iterator bt = builtinPrefixes.cbegin(); + bt != builtinPrefixes.cend(); ++bt) { + if (client_class.size() <= bt->size()) { + continue; + } + auto mis = std::mismatch(bt->cbegin(), bt->cend(), client_class.cbegin()); + if (mis.first == bt->cend()) { + return true; + } + } + + // Second check already defined, i.e. in the dictionary + ClientClassDefPtr def = class_dictionary->findClass(client_class); + if (def) { + return (true); + } + + // Unknown... + return (false); +} + // ****************** ClientClassDefListParser ************************ ClientClassDictionaryPtr diff --git a/src/lib/dhcpsrv/parsers/client_class_def_parser.h b/src/lib/dhcpsrv/parsers/client_class_def_parser.h index 576ca4d63c..95a0a592c6 100644 --- a/src/lib/dhcpsrv/parsers/client_class_def_parser.h +++ b/src/lib/dhcpsrv/parsers/client_class_def_parser.h @@ -9,7 +9,10 @@ #include #include +#include #include +#include +#include /// @file client_class_def_parser.h /// @@ -64,10 +67,14 @@ public: /// @param expression variable in which to store the new expression /// @param expression_cfg the configuration entry to be parsed. /// @param family the address family of the expression. + /// @param check_known a closure to check if a client class is known. /// /// @throw DhcpConfigError if parsing was unsuccessful. void parse(ExpressionPtr& expression, - isc::data::ConstElementPtr expression_cfg, uint16_t family); + isc::data::ConstElementPtr expression_cfg, + uint16_t family, + std::function check_known = + isc::eval::EvalContext::acceptAll); }; /// @brief Parser for a single client class definition. @@ -87,6 +94,20 @@ public: /// @throw DhcpConfigError if parsing was unsuccessful. void parse(ClientClassDictionaryPtr& class_dictionary, isc::data::ConstElementPtr client_class_def, uint16_t family); + + /// @brief List of built-in client class prefixes + /// i.e. VENDOR_CLASS_, AFTER_ and EXTERNAL_. + static std::list builtinPrefixes; + + /// @brief Check if a client class name is already known, + /// i.e. beginning by a built-in prefix or in the dictionary, + /// + /// @param class_dictionary A class dictionary where to look for. + /// @param client_class A client class name to look for. + /// @return true if known or built-in, false if not. + static bool + isClientClassKnown(ClientClassDictionaryPtr& class_dictionary, + const ClientClass& client_class); }; /// @brief Defines a pointer to a ClientClassDefParser diff --git a/src/lib/eval/eval.dox b/src/lib/eval/eval.dox index 80e7c61078..db29baa9aa 100644 --- a/src/lib/eval/eval.dox +++ b/src/lib/eval/eval.dox @@ -21,6 +21,13 @@ tokens that are stored in Reverse Polish Notation in EvalContext::expression. + Parameters to the @ref isc::eval::EvalContext class constructor are + the universe to choose between DHCPv4 and DHCPv6 for dependent expressions, + and a closure which checks if a client class is already known used + by the parser to accept only already known or built-in client + class names in client class membership expressions. This closure defaults + to accept all client class names. + Internally, the parser code is generated by flex and bison. These two tools convert lexer.ll and parser.yy files into a number of .cc and .hh files. To avoid a build of Kea depending on the presence of flex and bison, the diff --git a/src/lib/eval/eval_context.cc b/src/lib/eval/eval_context.cc index 562a67481b..72570dffb0 100644 --- a/src/lib/eval/eval_context.cc +++ b/src/lib/eval/eval_context.cc @@ -16,9 +16,10 @@ #include #include -EvalContext::EvalContext(const Option::Universe& option_universe) - : trace_scanning_(false), trace_parsing_(false), - option_universe_(option_universe) +EvalContext::EvalContext(const Option::Universe& option_universe, + std::function check_known) + : trace_scanning_(false), trace_parsing_(false), + option_universe_(option_universe), check_known_(check_known) { } @@ -26,6 +27,11 @@ EvalContext::~EvalContext() { } +bool +EvalContext::acceptAll(const ClientClass&) { + return (true); +} + bool EvalContext::parseString(const std::string& str, ParserType type) { @@ -183,6 +189,11 @@ EvalContext::fromUint32(const uint32_t integer) { return (tmp); } +bool +EvalContext::isClientClassKnown(const ClientClass& client_class) { + return (check_known_(client_class)); +} + void EvalContext::fatal (const std::string& what) { diff --git a/src/lib/eval/eval_context.h b/src/lib/eval/eval_context.h index e2eb816bc5..7690db1504 100644 --- a/src/lib/eval/eval_context.h +++ b/src/lib/eval/eval_context.h @@ -47,11 +47,21 @@ public: /// @param option_universe Option universe: DHCPv4 or DHCPv6. This is used /// by the parser to determine which option definitions set should be used /// to map option names to option codes. - EvalContext(const Option::Universe& option_universe); + /// @param check_known A closure called to check if a client class + /// used for membership is already known. If it is not the parser + /// will fail: only backward or built-in references are accepted. + EvalContext(const Option::Universe& option_universe, + std::function check_known = acceptAll); /// @brief destructor virtual ~EvalContext(); + /// @brief Accept all client class names + /// + /// @param client_class (unused) + /// @return true + static bool acceptAll(const ClientClass& client_class); + /// @brief Parsed expression (output tokens are stored here) isc::dhcp::Expression expression; @@ -169,6 +179,12 @@ public: return (option_universe_); } + /// @brief Check if a client class is already known + /// + /// @param client_class the client class name to check + /// @return true if the client class is known, false if not + bool isClientClassKnown(const ClientClass& client_class); + private: /// @brief Flag determining scanner debugging. bool trace_scanning_; @@ -182,6 +198,9 @@ public: /// set should be used to map option name to option code. Option::Universe option_universe_; + /// @brief Closure to check if a client class is already known + std::function check_known_; + }; }; // end of isc::eval namespace diff --git a/src/lib/eval/eval_messages.mes b/src/lib/eval/eval_messages.mes index 2a43435ec2..c70b7aeb3f 100644 --- a/src/lib/eval/eval_messages.mes +++ b/src/lib/eval/eval_messages.mes @@ -53,6 +53,12 @@ This debug message indicates that the given binary string is being pushed onto the value stack. This represents either an IPv4 or IPv6 address. The string is displayed in hex. +# For use with TokenMember + +% EVAL_DEBUG_MEMBER Checking membership of '%1', pushing result %2 +This debug message indicates that the membership of the packet for +the client class was checked. + # For use with TokenNot % EVAL_DEBUG_NOT Popping %1 pushing %2 diff --git a/src/lib/eval/tests/token_unittest.cc b/src/lib/eval/tests/token_unittest.cc index f9fee2ccff..f74c13d3b5 100644 --- a/src/lib/eval/tests/token_unittest.cc +++ b/src/lib/eval/tests/token_unittest.cc @@ -2193,6 +2193,36 @@ TEST_F(TokenTest, operatorOrTrue) { EXPECT_TRUE(checkFile()); } +// This test verifies client class membership +TEST_F(TokenTest, member) { + + ASSERT_NO_THROW(t_.reset(new TokenMember("foo"))); + + EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_)); + + // the packet has no classes so false was left on the stack + ASSERT_EQ(1, values_.size()); + EXPECT_EQ("false", values_.top()); + values_.pop(); + + // Add bar and retry + pkt4_->addClass("bar"); + EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_)); + + // the packet has a class but it is not foo + ASSERT_EQ(1, values_.size()); + EXPECT_EQ("false", values_.top()); + values_.pop(); + + // Add foo and retry + pkt4_->addClass("foo"); + EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_)); + + // Now the packet is in the foo class + ASSERT_EQ(1, values_.size()); + EXPECT_EQ("true", values_.top()); +} + // This test verifies if expression vendor[4491].exists works properly in DHCPv4. TEST_F(TokenTest, vendor4SpecificVendorExists) { // Case 1: no option, should evaluate to false diff --git a/src/lib/eval/token.cc b/src/lib/eval/token.cc index 6356cffd34..8968a15e42 100644 --- a/src/lib/eval/token.cc +++ b/src/lib/eval/token.cc @@ -706,6 +706,20 @@ TokenOr::evaluate(Pkt& /*pkt*/, ValueStack& values) { .arg('\'' + values.top() + '\''); } +void +TokenMember::evaluate(Pkt& pkt, ValueStack& values) { + if (pkt.inClass(client_class_)) { + values.push("true"); + } else { + values.push("false"); + } + + // Log what we pushed + LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_MEMBER) + .arg(client_class_) + .arg('\'' + values.top() + '\''); +} + TokenVendor::TokenVendor(Option::Universe u, uint32_t vendor_id, RepresentationType repr, uint16_t option_code) :TokenOption(option_code, repr), universe_(u), vendor_id_(vendor_id), diff --git a/src/lib/eval/token.h b/src/lib/eval/token.h index 08c267e5b5..630a11cd10 100644 --- a/src/lib/eval/token.h +++ b/src/lib/eval/token.h @@ -800,6 +800,30 @@ public: void evaluate(Pkt& pkt, ValueStack& values); }; +/// @brief Token that represents client class membership +/// +/// For example "not member('foo')" is the complement of class foo +class TokenMember : public Token { +public: + /// @brief Constructor + /// + /// @param client_class client class name + TokenMember(const std::string& client_class) + :client_class_(client_class){ + } + + /// @brief Token evaluation (check if client_class_ was added to + /// packet client classes) + /// + /// @param pkt the class name will be check from this packet's client classes + /// @param values true (if found) or false (if not found) will be pushed here + void evaluate(Pkt& pkt, ValueStack& values); + +protected: + /// @brief The client class name + ClientClass client_class_; +}; + /// @brief Token that represents vendor options in DHCPv4 and DHCPv6. /// /// It covers vendor independent vendor information option (125, DHCPv4)