#include <asiolink/io_error.h>
#include <boost/foreach.hpp>
+#include <algorithm>
using namespace isc::data;
using namespace isc::asiolink;
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 ("
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;
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();
}
}
}
+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
#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
///
/// @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.
/// @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
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
#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)
{
}
{
}
+bool
+EvalContext::acceptAll(const ClientClass&) {
+ return (true);
+}
+
bool
EvalContext::parseString(const std::string& str, ParserType type)
{
return (tmp);
}
+bool
+EvalContext::isClientClassKnown(const ClientClass& client_class) {
+ return (check_known_(client_class));
+}
+
void
EvalContext::fatal (const std::string& what)
{
/// @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;
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_;
/// 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
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
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
.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),
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)