]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[5374] Checkpoint: added member token, syntax and tests todo
authorFrancis Dupont <fdupont@isc.org>
Fri, 24 Nov 2017 08:21:20 +0000 (09:21 +0100)
committerFrancis Dupont <fdupont@isc.org>
Fri, 24 Nov 2017 08:21:20 +0000 (09:21 +0100)
src/lib/dhcpsrv/parsers/client_class_def_parser.cc
src/lib/dhcpsrv/parsers/client_class_def_parser.h
src/lib/eval/eval.dox
src/lib/eval/eval_context.cc
src/lib/eval/eval_context.h
src/lib/eval/eval_messages.mes
src/lib/eval/tests/token_unittest.cc
src/lib/eval/token.cc
src/lib/eval/token.h

index 582939334264410010f67287118f46918ea3ab66..6f1cfa1927e0997adcc4b2df5fba2bcad54902ed 100644 (file)
@@ -18,6 +18,7 @@
 #include <asiolink/io_error.h>
 
 #include <boost/foreach.hpp>
+#include <algorithm>
 
 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<bool(const ClientClass&)> 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<std::string>
+ClientClassDefParser::builtinPrefixes = {
+    "VENDOR_CLASS_", "AFTER_", "EXTERNAL_"
+};
+
+bool
+ClientClassDefParser::isClientClassKnown(ClientClassDictionaryPtr& class_dictionary,
+                                         const ClientClass& client_class) {
+    // First check built-in prefixes
+    for (std::list<std::string>::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
index 576ca4d63c6906d5303e9f905418c41d4f913381..95a0a592c6a2b50bc2fb3be0dcde50552bb66099 100644 (file)
@@ -9,7 +9,10 @@
 
 #include <cc/data.h>
 #include <cc/simple_parser.h>
+#include <eval/eval_context.h>
 #include <dhcpsrv/client_class_def.h>
+#include <functional>
+#include <list>
 
 /// @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<bool(const ClientClass&)> 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<std::string> 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
index 80e7c61078a1f7b6d08a167bcd5c65fc205b210b..db29baa9aa19db30b130dccbe72dbc1c60195937 100644 (file)
   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
index 562a67481ba8fc212f103150f901b96933ad90bd..72570dffb05ff88590b60c6a6aa15181d86e88a2 100644 (file)
 #include <fstream>
 #include <limits>
 
-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<bool(const ClientClass&)> 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)
 {
index e2eb816bc5cec7ad4df783b3bd045e3208b416f7..7690db15047ad47fdf20779ffa9b7ad0903f1d26 100644 (file)
@@ -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<bool(const ClientClass&)> 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<bool(const ClientClass&)> check_known_;
+
 };
 
 }; // end of isc::eval namespace
index 2a43435ec2f44be5932981e9d833215a36f36bab..c70b7aeb3fb7124f4624cef5028849fead252e4b 100644 (file)
@@ -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
index f9fee2ccffe7e8f08072c0d55b20421058b9a996..f74c13d3b5b7e22fe605201a1399bbe8e75e7050 100644 (file)
@@ -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
index 6356cffd34efde0eb5006aa7e475c1523b33b2ce..8968a15e42a75ff167b7dfeee06ab40705e9fd01 100644 (file)
@@ -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),
index 08c267e5b5388ab623e2f5779bf8eb6293fbb013..630a11cd107d8c916a40ebb066d46a03075a4e1f 100644 (file)
@@ -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)