summaryrefslogtreecommitdiffstats
path: root/src/lib/dhcpsrv/parsers/client_class_def_parser.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/dhcpsrv/parsers/client_class_def_parser.cc')
-rw-r--r--src/lib/dhcpsrv/parsers/client_class_def_parser.cc325
1 files changed, 325 insertions, 0 deletions
diff --git a/src/lib/dhcpsrv/parsers/client_class_def_parser.cc b/src/lib/dhcpsrv/parsers/client_class_def_parser.cc
new file mode 100644
index 0000000..59fd475
--- /dev/null
+++ b/src/lib/dhcpsrv/parsers/client_class_def_parser.cc
@@ -0,0 +1,325 @@
+// Copyright (C) 2015-2022 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+#include <dhcp/libdhcp++.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/client_class_def.h>
+#include <dhcpsrv/parsers/dhcp_parsers.h>
+#include <dhcpsrv/parsers/client_class_def_parser.h>
+#include <dhcpsrv/parsers/simple_parser4.h>
+#include <dhcpsrv/parsers/simple_parser6.h>
+#include <eval/eval_context.h>
+#include <asiolink/io_address.h>
+#include <asiolink/io_error.h>
+
+#include <boost/foreach.hpp>
+#include <algorithm>
+#include <sstream>
+
+using namespace isc::data;
+using namespace isc::asiolink;
+using namespace isc::util;
+using namespace std;
+
+/// @file client_class_def_parser.cc
+///
+/// @brief Method implementations for client class definition parsing
+
+namespace isc {
+namespace dhcp {
+
+// ********************** ExpressionParser ****************************
+
+void
+ExpressionParser::parse(ExpressionPtr& expression,
+ ConstElementPtr expression_cfg,
+ uint16_t family,
+ EvalContext::CheckDefined check_defined) {
+ if (expression_cfg->getType() != Element::string) {
+ isc_throw(DhcpConfigError, "expression ["
+ << expression_cfg->str() << "] must be a string, at ("
+ << expression_cfg->getPosition() << ")");
+ }
+
+ // Get the expression's text via getValue() as the text returned
+ // by str() enclosed in quotes.
+ std::string value;
+ expression_cfg->getValue(value);
+ try {
+ EvalContext eval_ctx(family == AF_INET ? Option::V4 : Option::V6,
+ check_defined);
+ eval_ctx.parseString(value);
+ expression.reset(new Expression());
+ *expression = eval_ctx.expression;
+ } catch (const std::exception& ex) {
+ // Append position if there is a failure.
+ isc_throw(DhcpConfigError,
+ "expression: [" << value
+ << "] error: " << ex.what() << " at ("
+ << expression_cfg->getPosition() << ")");
+ }
+}
+
+// ********************** ClientClassDefParser ****************************
+
+void
+ClientClassDefParser::parse(ClientClassDictionaryPtr& class_dictionary,
+ ConstElementPtr class_def_cfg,
+ uint16_t family,
+ bool append_error_position,
+ bool check_dependencies) {
+ // name is now mandatory, so let's deal with it first.
+ std::string name = getString(class_def_cfg, "name");
+ if (name.empty()) {
+ isc_throw(DhcpConfigError,
+ "not empty parameter 'name' is required "
+ << getPosition("name", class_def_cfg) << ")");
+ }
+
+ // Parse matching expression
+ ExpressionPtr match_expr;
+ ConstElementPtr test_cfg = class_def_cfg->get("test");
+ std::string test;
+ bool depend_on_known = false;
+ if (test_cfg) {
+ ExpressionParser parser;
+ auto check_defined =
+ [&class_dictionary, &depend_on_known, check_dependencies]
+ (const ClientClass& cclass) {
+ return (!check_dependencies || isClientClassDefined(class_dictionary,
+ depend_on_known,
+ cclass));
+ };
+ parser.parse(match_expr, test_cfg, family, check_defined);
+ test = test_cfg->stringValue();
+ }
+
+ // Parse option def
+ CfgOptionDefPtr defs(new CfgOptionDef());
+ ConstElementPtr option_defs = class_def_cfg->get("option-def");
+ if (option_defs) {
+ // Apply defaults
+ SimpleParser::setListDefaults(option_defs,
+ family == AF_INET ?
+ SimpleParser4::OPTION4_DEF_DEFAULTS :
+ SimpleParser6::OPTION6_DEF_DEFAULTS);
+
+ OptionDefParser parser(family);
+ BOOST_FOREACH(ConstElementPtr option_def, option_defs->listValue()) {
+ OptionDefinitionPtr def = parser.parse(option_def);
+
+ // Verify if the definition is for an option which is in a deferred
+ // processing list.
+ if (!LibDHCP::shouldDeferOptionUnpack(def->getOptionSpaceName(),
+ def->getCode())) {
+ isc_throw(DhcpConfigError,
+ "Not allowed option definition for code '"
+ << def->getCode() << "' in space '"
+ << def->getOptionSpaceName() << "' at ("
+ << option_def->getPosition() << ")");
+ }
+ try {
+ defs->add(def);
+ } catch (const std::exception& ex) {
+ // Sanity check: it should never happen
+ isc_throw(DhcpConfigError, ex.what() << " ("
+ << option_def->getPosition() << ")");
+ }
+ }
+ }
+
+ // Parse option data
+ CfgOptionPtr options(new CfgOption());
+ ConstElementPtr option_data = class_def_cfg->get("option-data");
+ if (option_data) {
+ auto opts_parser = createOptionDataListParser(family, defs);
+ opts_parser->parse(options, option_data);
+ }
+
+ // Parse user context
+ ConstElementPtr user_context = class_def_cfg->get("user-context");
+ if (user_context) {
+ if (user_context->getType() != Element::map) {
+ isc_throw(isc::dhcp::DhcpConfigError, "User context has to be a map ("
+ << user_context->getPosition() << ")");
+ }
+ }
+
+ // Let's try to parse the only-if-required flag
+ bool required = false;
+ if (class_def_cfg->contains("only-if-required")) {
+ required = getBoolean(class_def_cfg, "only-if-required");
+ }
+
+ // Let's try to parse the next-server field
+ IOAddress next_server("0.0.0.0");
+ if (class_def_cfg->contains("next-server")) {
+ std::string next_server_txt = getString(class_def_cfg, "next-server");
+ try {
+ next_server = IOAddress(next_server_txt);
+ } catch (const IOError& ex) {
+ isc_throw(DhcpConfigError,
+ "Invalid next-server value specified: '"
+ << next_server_txt << "' ("
+ << getPosition("next-server", class_def_cfg) << ")");
+ }
+
+ if (next_server.getFamily() != AF_INET) {
+ isc_throw(DhcpConfigError, "Invalid next-server value: '"
+ << next_server_txt << "', must be IPv4 address ("
+ << getPosition("next-server", class_def_cfg) << ")");
+ }
+
+ if (next_server.isV4Bcast()) {
+ isc_throw(DhcpConfigError, "Invalid next-server value: '"
+ << next_server_txt << "', must not be a broadcast ("
+ << getPosition("next-server", class_def_cfg) << ")");
+ }
+ }
+
+ // Let's try to parse server-hostname
+ std::string sname;
+ if (class_def_cfg->contains("server-hostname")) {
+ sname = getString(class_def_cfg, "server-hostname");
+
+ if (sname.length() >= Pkt4::MAX_SNAME_LEN) {
+ isc_throw(DhcpConfigError, "server-hostname must be at most "
+ << Pkt4::MAX_SNAME_LEN - 1 << " bytes long, it is "
+ << sname.length() << " ("
+ << getPosition("server-hostname", class_def_cfg) << ")");
+ }
+ }
+
+ // Let's try to parse boot-file-name
+ std::string filename;
+ if (class_def_cfg->contains("boot-file-name")) {
+ filename = getString(class_def_cfg, "boot-file-name");
+
+ if (filename.length() > Pkt4::MAX_FILE_LEN) {
+ isc_throw(DhcpConfigError, "boot-file-name must be at most "
+ << Pkt4::MAX_FILE_LEN - 1 << " bytes long, it is "
+ << filename.length() << " ("
+ << getPosition("boot-file-name", class_def_cfg) << ")");
+ }
+
+ }
+
+ // Parse valid lifetime triplet.
+ Triplet<uint32_t> valid_lft = parseIntTriplet(class_def_cfg, "valid-lifetime");
+
+ Triplet<uint32_t> preferred_lft;
+ if (family != AF_INET) {
+ // Parse preferred lifetime triplet.
+ preferred_lft = parseIntTriplet(class_def_cfg, "preferred-lifetime");
+ }
+
+ // Sanity checks on built-in classes
+ for (auto bn : builtinNames) {
+ if (name == bn) {
+ if (required) {
+ isc_throw(DhcpConfigError, "built-in class '" << name
+ << "' only-if-required flag must be false");
+ }
+ if (!test.empty()) {
+ isc_throw(DhcpConfigError, "built-in class '" << name
+ << "' test expression must be empty");
+ }
+ }
+ }
+
+ // Sanity checks on DROP
+ if (name == "DROP") {
+ if (required) {
+ isc_throw(DhcpConfigError, "special class '" << name
+ << "' only-if-required flag must be false");
+ }
+ // depend_on_known is now allowed
+ }
+
+ // Add the client class definition
+ try {
+ class_dictionary->addClass(name, match_expr, test, required,
+ depend_on_known, options, defs,
+ user_context, next_server, sname, filename,
+ valid_lft, preferred_lft);
+ } catch (const std::exception& ex) {
+ std::ostringstream s;
+ s << "Can't add class: " << ex.what();
+ // Append position of the error in JSON string if required.
+ if (append_error_position) {
+ s << " (" << class_def_cfg->getPosition() << ")";
+ }
+ isc_throw(DhcpConfigError, s.str());
+ }
+}
+
+void
+ClientClassDefParser::checkParametersSupported(const ConstElementPtr& class_def_cfg,
+ const uint16_t family) {
+ // Make sure that the client class definition is stored in a map.
+ if (!class_def_cfg || (class_def_cfg->getType() != Element::map)) {
+ isc_throw(DhcpConfigError, "client class definition is not a map");
+ }
+
+ // Common v4 and v6 parameters supported for the client class.
+ static std::set<std::string> supported_params = { "name",
+ "test",
+ "option-data",
+ "user-context",
+ "only-if-required",
+ "valid-lifetime",
+ "min-valid-lifetime",
+ "max-valid-lifetime" };
+
+
+ // The v4 client class supports additional parameters.
+ static std::set<std::string> supported_params_v4 = { "option-def",
+ "next-server",
+ "server-hostname",
+ "boot-file-name" };
+
+ // The v6 client class supports additional parameters.
+ static std::set<std::string> supported_params_v6 = { "preferred-lifetime",
+ "min-preferred-lifetime",
+ "max-preferred-lifetime" };
+
+ // Iterate over the specified parameters and check if they are all supported.
+ for (auto name_value_pair : class_def_cfg->mapValue()) {
+ if ((supported_params.count(name_value_pair.first) > 0) ||
+ ((family == AF_INET) && (supported_params_v4.count(name_value_pair.first) > 0)) ||
+ ((family != AF_INET) && (supported_params_v6.count(name_value_pair.first) > 0))) {
+ continue;
+ } else {
+ isc_throw(DhcpConfigError, "unsupported client class parameter '"
+ << name_value_pair.first << "'");
+ }
+ }
+}
+
+boost::shared_ptr<OptionDataListParser>
+ClientClassDefParser::createOptionDataListParser(const uint16_t address_family,
+ CfgOptionDefPtr cfg_option_def) const {
+ auto parser = boost::make_shared<OptionDataListParser>(address_family, cfg_option_def);
+ return (parser);
+}
+
+// ****************** ClientClassDefListParser ************************
+
+ClientClassDictionaryPtr
+ClientClassDefListParser::parse(ConstElementPtr client_class_def_list,
+ uint16_t family, bool check_dependencies) {
+ ClientClassDictionaryPtr dictionary(new ClientClassDictionary());
+ BOOST_FOREACH(ConstElementPtr client_class_def,
+ client_class_def_list->listValue()) {
+ ClientClassDefParser parser;
+ parser.parse(dictionary, client_class_def, family, true, check_dependencies);
+ }
+ return (dictionary);
+}
+
+} // end of namespace isc::dhcp
+} // end of namespace isc