diff options
Diffstat (limited to 'src/lib/dhcpsrv/parsers')
30 files changed, 7581 insertions, 0 deletions
diff --git a/src/lib/dhcpsrv/parsers/base_network_parser.cc b/src/lib/dhcpsrv/parsers/base_network_parser.cc new file mode 100644 index 0000000..9af4919 --- /dev/null +++ b/src/lib/dhcpsrv/parsers/base_network_parser.cc @@ -0,0 +1,271 @@ +// Copyright (C) 2019-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 <util/triplet.h> +#include <dhcpsrv/parsers/base_network_parser.h> +#include <util/optional.h> +#include <util/strutil.h> + +using namespace isc::data; +using namespace isc::util; + +namespace isc { +namespace dhcp { + +void +BaseNetworkParser::moveReservationMode(ElementPtr config) { + if (!config->contains("reservation-mode")) { + return; + } + if (config->contains("reservations-global") || + config->contains("reservations-in-subnet") || + config->contains("reservations-out-of-pool")) { + isc_throw(DhcpConfigError, "invalid use of both 'reservation-mode'" + " and one of 'reservations-global', 'reservations-in-subnet'" + " or 'reservations-out-of-pool' parameters"); + } + std::string hr_mode = getString(config, "reservation-mode"); + if ((hr_mode == "disabled") || (hr_mode == "off")) { + config->set("reservations-global", Element::create(false)); + config->set("reservations-in-subnet", Element::create(false)); + } else if (hr_mode == "out-of-pool") { + config->set("reservations-global", Element::create(false)); + config->set("reservations-in-subnet", Element::create(true)); + config->set("reservations-out-of-pool", Element::create(true)); + } else if (hr_mode == "global") { + config->set("reservations-global", Element::create(true)); + config->set("reservations-in-subnet", Element::create(false)); + } else if (hr_mode == "all") { + config->set("reservations-global", Element::create(false)); + config->set("reservations-in-subnet", Element::create(true)); + config->set("reservations-out-of-pool", Element::create(false)); + } else { + isc_throw(DhcpConfigError, "invalid reservation-mode parameter: '" + << hr_mode << "' (" + << getPosition("reservation-mode", config) << ")"); + } + config->remove("reservation-mode"); +} + +void +BaseNetworkParser::moveReservationMode(CfgGlobalsPtr config) { + if (!config->get(CfgGlobals::RESERVATION_MODE)) { + return; + } + if (config->get(CfgGlobals::RESERVATIONS_GLOBAL) || + config->get(CfgGlobals::RESERVATIONS_IN_SUBNET) || + config->get(CfgGlobals::RESERVATIONS_OUT_OF_POOL)) { + isc_throw(DhcpConfigError, "invalid use of both 'reservation-mode'" + " and one of 'reservations-global', 'reservations-in-subnet'" + " or 'reservations-out-of-pool' parameters"); + } + std::string hr_mode = config->get(CfgGlobals::RESERVATION_MODE)->stringValue(); + if ((hr_mode == "disabled") || (hr_mode == "off")) { + config->set(CfgGlobals::RESERVATIONS_GLOBAL, Element::create(false)); + config->set(CfgGlobals::RESERVATIONS_IN_SUBNET, Element::create(false)); + } else if (hr_mode == "out-of-pool") { + config->set(CfgGlobals::RESERVATIONS_GLOBAL, Element::create(false)); + config->set(CfgGlobals::RESERVATIONS_IN_SUBNET, Element::create(true)); + config->set(CfgGlobals::RESERVATIONS_OUT_OF_POOL, Element::create(true)); + } else if (hr_mode == "global") { + config->set(CfgGlobals::RESERVATIONS_GLOBAL, Element::create(true)); + config->set(CfgGlobals::RESERVATIONS_IN_SUBNET, Element::create(false)); + } else if (hr_mode == "all") { + config->set(CfgGlobals::RESERVATIONS_GLOBAL, Element::create(false)); + config->set(CfgGlobals::RESERVATIONS_IN_SUBNET, Element::create(true)); + config->set("reservations-out-of-pool", Element::create(false)); + } else { + isc_throw(DhcpConfigError, "invalid reservation-mode parameter: '" + << hr_mode << "' (" + << config->get(CfgGlobals::RESERVATION_MODE)->getPosition() + << ")"); + } + config->set(CfgGlobals::RESERVATION_MODE, ConstElementPtr()); +} + +void +BaseNetworkParser::parseCommon(const ConstElementPtr& network_data, + NetworkPtr& network) { + bool has_renew = network_data->contains("renew-timer"); + bool has_rebind = network_data->contains("rebind-timer"); + int64_t renew = -1; + int64_t rebind = -1; + + if (has_renew) { + renew = getInteger(network_data, "renew-timer"); + if (renew < 0) { + isc_throw(DhcpConfigError, "the value of renew-timer (" + << renew << ") must be a positive number"); + } + network->setT1(renew); + } + + if (has_rebind) { + rebind = getInteger(network_data, "rebind-timer"); + if (rebind < 0) { + isc_throw(DhcpConfigError, "the value of rebind-timer (" + << rebind << ") must be a positive number"); + } + network->setT2(rebind); + } + + if (has_renew && has_rebind && (renew > rebind)) { + isc_throw(DhcpConfigError, "the value of renew-timer (" << renew + << ") is greater than the value of rebind-timer (" + << rebind << ")"); + } + + network->setValid(parseIntTriplet(network_data, "valid-lifetime")); + + if (network_data->contains("store-extended-info")) { + network->setStoreExtendedInfo(getBoolean(network_data, + "store-extended-info")); + } + + if (network_data->contains("reservations-global")) { + network->setReservationsGlobal(getBoolean(network_data, + "reservations-global")); + } + + if (network_data->contains("reservations-in-subnet")) { + network->setReservationsInSubnet(getBoolean(network_data, + "reservations-in-subnet")); + } + + if (network_data->contains("reservations-out-of-pool")) { + network->setReservationsOutOfPool(getBoolean(network_data, + "reservations-out-of-pool")); + } +} + +void +BaseNetworkParser::parseTeePercents(const ConstElementPtr& network_data, + NetworkPtr& network) { + bool calculate_tee_times = network->getCalculateTeeTimes(); + if (network_data->contains("calculate-tee-times")) { + calculate_tee_times = getBoolean(network_data, "calculate-tee-times"); + network->setCalculateTeeTimes(calculate_tee_times); + } + + Optional<double> t2_percent; + if (network_data->contains("t2-percent")) { + t2_percent = getDouble(network_data, "t2-percent"); + } + + Optional<double> t1_percent; + if (network_data->contains("t1-percent")) { + t1_percent = getDouble(network_data, "t1-percent"); + } + if (calculate_tee_times) { + if (!t2_percent.unspecified() && ((t2_percent.get() <= 0.0) || + (t2_percent.get() >= 1.0))) { + isc_throw(DhcpConfigError, "t2-percent: " << t2_percent.get() + << " is invalid, it must be greater than 0.0 and less than 1.0"); + } + + if (!t1_percent.unspecified() && ((t1_percent.get() <= 0.0) || + (t1_percent.get() >= 1.0))) { + isc_throw(DhcpConfigError, "t1-percent: " << t1_percent.get() + << " is invalid it must be greater than 0.0 and less than 1.0"); + } + + if (!t1_percent.unspecified() && !t2_percent.unspecified() && + (t1_percent.get() >= t2_percent.get())) { + isc_throw(DhcpConfigError, "t1-percent: " << t1_percent.get() + << " is invalid, it must be less than t2-percent: " + << t2_percent.get()); + } + } + + network->setT2Percent(t2_percent); + network->setT1Percent(t1_percent); +} + +void +BaseNetworkParser::parseCacheParams(const ConstElementPtr& network_data, + NetworkPtr& network) { + if (network_data->contains("cache-threshold")) { + double cache_threshold = getDouble(network_data, "cache-threshold"); + if ((cache_threshold <= 0.0) || (cache_threshold >= 1.0)) { + isc_throw(DhcpConfigError, "cache-threshold: " << cache_threshold + << " is invalid, it must be greater than 0.0 and less than 1.0"); + } + network->setCacheThreshold(cache_threshold); + } + + if (network_data->contains("cache-max-age")) { + network->setCacheMaxAge(getInteger(network_data, "cache-max-age")); + } +} + +void +BaseNetworkParser::parseDdnsParams(const data::ConstElementPtr& network_data, + NetworkPtr& network) { + + if (network_data->contains("ddns-send-updates")) { + network->setDdnsSendUpdates(getBoolean(network_data, "ddns-send-updates")); + } + + if (network_data->contains("ddns-override-no-update")) { + network->setDdnsOverrideNoUpdate(getBoolean(network_data, "ddns-override-no-update")); + } + + if (network_data->contains("ddns-override-client-update")) { + network->setDdnsOverrideClientUpdate(getBoolean(network_data, "ddns-override-client-update")); + } + + if (network_data->contains("ddns-replace-client-name")) { + network->setDdnsReplaceClientNameMode(getAndConvert<D2ClientConfig::ReplaceClientNameMode, + D2ClientConfig::stringToReplaceClientNameMode> + (network_data, "ddns-replace-client-name", + "ReplaceClientName mode")); + } + + if (network_data->contains("ddns-generated-prefix")) { + network->setDdnsGeneratedPrefix(getString(network_data, "ddns-generated-prefix")); + } + + if (network_data->contains("ddns-qualifying-suffix")) { + network->setDdnsQualifyingSuffix(getString(network_data, "ddns-qualifying-suffix")); + } + + std::string hostname_char_set; + if (network_data->contains("hostname-char-set")) { + hostname_char_set = getString(network_data, "hostname-char-set"); + network->setHostnameCharSet(hostname_char_set); + } + + std::string hostname_char_replacement; + if (network_data->contains("hostname-char-replacement")) { + hostname_char_replacement = getString(network_data, "hostname-char-replacement"); + network->setHostnameCharReplacement(hostname_char_replacement); + } + + // We need to validate sanitizer values here so we can detect problems and + // cause a configuration. We don't retain the compilation because it's not + // something we can inherit. + if (!hostname_char_set.empty()) { + try { + str::StringSanitizerPtr sanitizer(new str::StringSanitizer(hostname_char_set, + hostname_char_replacement)); + } catch (const std::exception& ex) { + isc_throw(BadValue, "hostname-char-set '" << hostname_char_set + << "' is not a valid regular expression"); + } + } + + if (network_data->contains("ddns-update-on-renew")) { + network->setDdnsUpdateOnRenew(getBoolean(network_data, "ddns-update-on-renew")); + } + + if (network_data->contains("ddns-use-conflict-resolution")) { + network->setDdnsUseConflictResolution(getBoolean(network_data, "ddns-use-conflict-resolution")); + } +} + +} // end of namespace isc::dhcp +} // end of namespace isc diff --git a/src/lib/dhcpsrv/parsers/base_network_parser.h b/src/lib/dhcpsrv/parsers/base_network_parser.h new file mode 100644 index 0000000..ee4bef3 --- /dev/null +++ b/src/lib/dhcpsrv/parsers/base_network_parser.h @@ -0,0 +1,113 @@ +// Copyright (C) 2019-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/. + +#ifndef BASE_NETWORK_PARSER_H +#define BASE_NETWORK_PARSER_H + +#include <cc/data.h> +#include <cc/simple_parser.h> +#include <dhcpsrv/cfg_globals.h> +#include <dhcpsrv/network.h> + +namespace isc { +namespace dhcp { + +/// @brief Common configuration parser for shared networks +/// and subnets. +class BaseNetworkParser : public data::SimpleParser { +public: + + /// @brief Moves deprecated reservation-mode parameter to + /// new reservations flags. + /// + /// @param config [in/out] configuration to alter. + /// @throw DhcpConfigError on error e.g. when both reservation-mode + /// and a flag are specified. + static void moveReservationMode(isc::data::ElementPtr config); + + /// @brief Moves deprecated reservation-mode parameter to + /// new reservations flags. + /// + /// @param config [in/out] global parameters to alter. + /// @throw DhcpConfigError on error e.g. when both reservation-mode + /// and a flag are specified. + static void moveReservationMode(CfgGlobalsPtr config); + +protected: + + /// @brief Parses common parameters + /// + /// The parsed parameters are: + /// - renew-timer, + /// - rebind-timer, + /// - valid-lifetime, + /// - store-extended-info + /// - reservations-global + /// - reservations-in-subnet + /// - reservations-out-of-pool + /// + /// @param network_data Data element holding shared network + /// configuration to be parsed. + /// @param [out] network Pointer to a network in which parsed data is + /// to be stored. + void parseCommon(const data::ConstElementPtr& network_data, + NetworkPtr& network); + + /// @brief Parses parameters related to "percent" timers settings. + /// + /// The parsed parameters are: + /// - calculate-tee-times, + /// - t1-percent, + /// - t2-percent. + /// + /// @param network_data Data element holding network configuration + /// to be parsed. + /// @param [out] network Pointer to a network in which parsed data is + /// to be stored. + /// + /// @throw DhcpConfigError if configuration of these parameters is + /// invalid. + void parseTeePercents(const data::ConstElementPtr& network_data, + NetworkPtr& network); + + /// @brief Parses parameters related to lease cache settings. + /// + /// The parsed parameters are: + /// - cache-threshold, + /// - cache-max-age. + /// + /// @param network_data Data element holding network configuration + /// to be parsed. + /// @param [out] network Pointer to a network in which parsed data is + /// to be stored. + /// + /// @throw DhcpConfigError if configuration of these parameters is + /// invalid. + void parseCacheParams(const data::ConstElementPtr& network_data, + NetworkPtr& network); + + /// @brief Parses parameters pertaining to DDNS behavior. + /// + /// The parsed parameters are: + /// - ddns-send-updates + /// - ddns-override-no-update + /// - ddns-override-client-update + /// - ddns-replace-client-name + /// - ddns-generated-prefix + /// - ddns-qualifying-suffix + /// + /// @param network_data Data element holding shared network + /// configuration to be parsed. + /// @param [out] network Pointer to a network in which parsed data is + /// to be stored. + void parseDdnsParams(const data::ConstElementPtr& network_data, + NetworkPtr& network); +}; + +} // end of namespace isc::dhcp +} // end of namespace isc + +#endif 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 diff --git a/src/lib/dhcpsrv/parsers/client_class_def_parser.h b/src/lib/dhcpsrv/parsers/client_class_def_parser.h new file mode 100644 index 0000000..060ab10 --- /dev/null +++ b/src/lib/dhcpsrv/parsers/client_class_def_parser.h @@ -0,0 +1,174 @@ +// Copyright (C) 2015-2021 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/. + +#ifndef CLIENT_CLASS_DEF_PARSER_H +#define CLIENT_CLASS_DEF_PARSER_H + +#include <cc/data.h> +#include <cc/simple_parser.h> +#include <eval/eval_context.h> +#include <dhcpsrv/client_class_def.h> +#include <dhcpsrv/parsers/option_data_parser.h> +#include <functional> +#include <list> + +/// @file client_class_def_parser.h +/// +/// @brief Parsers for client class definitions +/// +/// These parsers are used to parse lists of client class definitions +/// into a ClientClassDictionary of ClientClassDef instances. Each +/// ClientClassDef consists of (at least) a name, an expression, option-def +/// and option-data. Currently only a not empty name is required. +/// +/// There parsers defined are: +/// +/// ClientClassDefListParser - creates a ClientClassDictionary from a list +/// of element maps, where each map contains the entries that specify a +/// single class. The names of the classes in the are expected to be +/// unique. Attempting to define a duplicate class will result in an +/// DhcpConfigError throw. At the end the dictionary is stored by the CfgMgr. +/// +/// ClientClassDefParser - creates a ClientClassDefinition from an element +/// map. The elements are as follows: +/// +/// -# "name" - a string containing the name of the class +/// +/// -# "test" - a string containing the logical expression used to determine +/// membership in the class. This is passed into the eval parser. +/// +/// -# "option-def" - a list which defines the options which processing +/// is deferred. This element is optional and parsed using the @ref +/// isc::dhcp::OptionDefParser. A check is done to verify definitions +/// are only for deferred processing option (DHCPv4 43 and 224-254). +/// +/// -# "option-data" - a list which defines the options that should be +/// assigned to remembers of the class. This element is optional and parsed +/// using the @ref isc::dhcp::OptionDataListParser. +/// +/// ExpressionParser - creates an eval::Expression from a string element, +/// using the Eval Parser. +/// +namespace isc { +namespace dhcp { + +/// @brief Parser for a logical expression +/// +/// This parser creates an instance of an Expression from a string. The +/// string is passed to the Eval Parser and the resultant Expression is +/// stored into the ExpressionPtr reference passed into the constructor. +class ExpressionParser : public isc::data::SimpleParser { +public: + + /// @brief Parses an expression configuration element into an Expression + /// + /// @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_defined a closure to check if a client class is defined. + /// + /// @throw DhcpConfigError if parsing was unsuccessful. + void parse(ExpressionPtr& expression, + isc::data::ConstElementPtr expression_cfg, + uint16_t family, + isc::eval::EvalContext::CheckDefined check_defined = + isc::eval::EvalContext::acceptAll); +}; + +/// @brief Parser for a single client class definition. +/// +/// This parser creates an instance of a client class definition. +class ClientClassDefParser : public isc::data::SimpleParser { +public: + + /// @brief Virtual destructor. + virtual ~ClientClassDefParser() { + } + + /// @brief Parses an entry that describes single client class definition. + /// + /// Attempts to add the new class directly into the given dictionary. + /// This done here to detect duplicate classes prior to commit(). + /// @param class_dictionary dictionary into which the class should be added + /// @param client_class_def a configuration entry to be parsed. + /// @param family the address family of the client class. + /// @param append_error_position Boolean flag indicating if position + /// of the parsed string within parsed JSON should be appended. The + /// default setting is to append it, but it is typically set to false + /// when this parser is used by hooks libraries. + /// @param check_dependencies indicates if the parser should evaluate an + /// expression to see if the referenced client classes exist. + /// + /// @throw DhcpConfigError if parsing was unsuccessful. + void parse(ClientClassDictionaryPtr& class_dictionary, + isc::data::ConstElementPtr client_class_def, + uint16_t family, + bool append_error_position = true, + bool check_dependencies = true); + + /// @brief Iterates over class parameters and checks if they are supported. + /// + /// This method should be called by hooks libraries which do not use Bison + /// to validate class syntax prior to parsing the client class information. + /// + /// @param class_def_cfg class configuration entry. + /// @param family the address family of the client class. + /// + /// @throw DhcpConfigError if any of the parameters is not supported. + void checkParametersSupported(const isc::data::ConstElementPtr& class_def_cfg, + const uint16_t family); + +protected: + + /// @brief Returns an instance of the @c OptionDataListParser to + /// be used in parsing the option-data structure. + /// + /// This function can be overridden in the child classes to supply + /// a custom parser for option data. + /// + /// @param address_family @c AF_INET (for DHCPv4) or @c AF_INET6 (for DHCPv6). + /// @param cfg_option_def structure holding option definitions. + /// + /// @return an instance of the @c OptionDataListParser. + virtual boost::shared_ptr<OptionDataListParser> + createOptionDataListParser(const uint16_t address_family, + CfgOptionDefPtr cfg_option_def) const; +}; + +/// @brief Defines a pointer to a ClientClassDefParser +typedef boost::shared_ptr<ClientClassDefParser> ClientClassDefParserPtr; + +/// @brief Parser for a list of client class definitions. +/// +/// This parser iterates over all configuration entries that define +/// client classes and creates ClientClassDef instances for each. +/// When the parsing successfully completes, the collection of +/// created definitions is given to the CfgMgr. +class ClientClassDefListParser : public isc::data::SimpleParser { +public: + + /// @brief Parse configuration entries. + /// + /// This function parses configuration entries, creates instances + /// of client class definitions and tries to adds them to the + /// local dictionary. At the end the dictionary is returned. + /// + /// @param class_def_list pointer to an element that holds entries + /// for client class definitions. + /// @param family the address family of the client class definitions. + /// @param check_dependencies indicates if the parser should evaluate an + /// expression to see if the referenced client classes exist. + /// @return a pointer to the filled dictionary + /// @throw DhcpConfigError if configuration parsing fails. + ClientClassDictionaryPtr + parse(isc::data::ConstElementPtr class_def_list, uint16_t family, + bool check_dependencies = true); +}; + +} // end of namespace isc::dhcp +} // end of namespace isc + +#endif // CLIENT_CLASS_DEF_PARSER_H diff --git a/src/lib/dhcpsrv/parsers/dhcp_parsers.cc b/src/lib/dhcpsrv/parsers/dhcp_parsers.cc new file mode 100644 index 0000000..d309d09 --- /dev/null +++ b/src/lib/dhcpsrv/parsers/dhcp_parsers.cc @@ -0,0 +1,1634 @@ +// Copyright (C) 2013-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/iface_mgr.h> +#include <dhcp/dhcp4.h> +#include <dhcp/libdhcp++.h> +#include <dhcpsrv/cfgmgr.h> +#include <dhcpsrv/cfg_option.h> +#include <dhcpsrv/dhcpsrv_log.h> +#include <dhcpsrv/parsers/dhcp_parsers.h> +#include <dhcpsrv/parsers/host_reservation_parser.h> +#include <dhcpsrv/parsers/host_reservations_list_parser.h> +#include <dhcpsrv/parsers/option_data_parser.h> +#include <dhcpsrv/parsers/simple_parser4.h> +#include <dhcpsrv/parsers/simple_parser6.h> +#include <dhcpsrv/cfg_mac_source.h> +#include <util/encode/hex.h> +#include <util/strutil.h> + +#include <boost/algorithm/string.hpp> +#include <boost/foreach.hpp> +#include <boost/lexical_cast.hpp> +#include <boost/make_shared.hpp> +#include <boost/scoped_ptr.hpp> + +#include <map> +#include <string> +#include <vector> +#include <iomanip> + +using namespace std; +using namespace isc::asiolink; +using namespace isc::data; +using namespace isc::util; + +namespace isc { +namespace dhcp { + +// ******************** MACSourcesListConfigParser ************************* + +void +MACSourcesListConfigParser::parse(CfgMACSource& mac_sources, ConstElementPtr value) { + uint32_t source = 0; + size_t cnt = 0; + + // By default, there's only one source defined: ANY. + // If user specified anything, we need to get rid of that default. + mac_sources.clear(); + + BOOST_FOREACH(ConstElementPtr source_elem, value->listValue()) { + std::string source_str = source_elem->stringValue(); + try { + source = CfgMACSource::MACSourceFromText(source_str); + mac_sources.add(source); + ++cnt; + } catch (const InvalidParameter& ex) { + isc_throw(DhcpConfigError, "The mac-sources value '" << source_str + << "' was specified twice (" << value->getPosition() << ")"); + } catch (const std::exception& ex) { + isc_throw(DhcpConfigError, "Failed to convert '" + << source_str << "' to any recognized MAC source:" + << ex.what() << " (" << value->getPosition() << ")"); + } + } + + if (!cnt) { + isc_throw(DhcpConfigError, "If specified, MAC Sources cannot be empty"); + } +} + +// ******************** ControlSocketParser ************************* +void ControlSocketParser::parse(SrvConfig& srv_cfg, isc::data::ConstElementPtr value) { + if (!value) { + // Sanity check: not supposed to fail. + isc_throw(DhcpConfigError, "Logic error: specified control-socket is null"); + } + + if (value->getType() != Element::map) { + // Sanity check: not supposed to fail. + isc_throw(DhcpConfigError, "Specified control-socket is expected to be a map" + ", i.e. a structure defined within { }"); + } + srv_cfg.setControlSocketInfo(value); +} + +// ******************************** OptionDefParser **************************** + +OptionDefParser::OptionDefParser(const uint16_t address_family) + : address_family_(address_family) { +} + +OptionDefinitionPtr +OptionDefParser::parse(ConstElementPtr option_def) { + + // Check parameters. + if (address_family_ == AF_INET) { + checkKeywords(SimpleParser4::OPTION4_DEF_PARAMETERS, option_def); + } else { + checkKeywords(SimpleParser6::OPTION6_DEF_PARAMETERS, option_def); + } + + // Get mandatory parameters. + std::string name = getString(option_def, "name"); + int64_t code64 = getInteger(option_def, "code"); + std::string type = getString(option_def, "type"); + + // Get optional parameters. Whoever called this parser, should have + // called SimpleParser::setDefaults first. + bool array_type = getBoolean(option_def, "array"); + std::string record_types = getString(option_def, "record-types"); + std::string space = getString(option_def, "space"); + std::string encapsulates = getString(option_def, "encapsulate"); + ConstElementPtr user_context = option_def->get("user-context"); + + // Check code value. + if (code64 < 0) { + isc_throw(DhcpConfigError, "option code must not be negative " + "(" << getPosition("code", option_def) << ")"); + } else if (address_family_ == AF_INET && + code64 > std::numeric_limits<uint8_t>::max()) { + isc_throw(DhcpConfigError, "invalid option code '" << code64 + << "', it must not be greater than '" + << static_cast<int>(std::numeric_limits<uint8_t>::max()) + << "' (" << getPosition("code", option_def) << ")"); + } else if (address_family_ == AF_INET6 && + code64 > std::numeric_limits<uint16_t>::max()) { + isc_throw(DhcpConfigError, "invalid option code '" << code64 + << "', it must not be greater than '" + << std::numeric_limits<uint16_t>::max() + << "' (" << getPosition("code", option_def) << ")"); + } + uint32_t code = static_cast<uint32_t>(code64); + + // Validate space name. + if (!OptionSpace::validateName(space)) { + isc_throw(DhcpConfigError, "invalid option space name '" + << space << "' (" + << getPosition("space", option_def) << ")"); + } + + // Protect against definition of options 0 (PAD) or 255 (END) + // in (and only in) the dhcp4 space. + if (space == DHCP4_OPTION_SPACE) { + if (code == DHO_PAD) { + isc_throw(DhcpConfigError, "invalid option code '0': " + << "reserved for PAD (" + << getPosition("code", option_def) << ")"); + } else if (code == DHO_END) { + isc_throw(DhcpConfigError, "invalid option code '255': " + << "reserved for END (" + << getPosition("code", option_def) << ")"); + } + } + + // For dhcp6 space the value 0 is reserved. + if (space == DHCP6_OPTION_SPACE) { + if (code == 0) { + isc_throw(DhcpConfigError, "invalid option code '0': " + << "reserved value (" + << getPosition("code", option_def) << ")"); + } + } + + // Create option definition. + OptionDefinitionPtr def; + // We need to check if user has set encapsulated option space + // name. If so, different constructor will be used. + if (!encapsulates.empty()) { + // Arrays can't be used together with sub-options. + if (array_type) { + isc_throw(DhcpConfigError, "option '" << space << "." + << name << "', comprising an array of data" + << " fields may not encapsulate any option space (" + << option_def->getPosition() << ")"); + + } else if (encapsulates == space) { + isc_throw(DhcpConfigError, "option must not encapsulate" + << " an option space it belongs to: '" + << space << "." << name << "' is set to" + << " encapsulate '" << space << "' (" + << option_def->getPosition() << ")"); + + } else { + def.reset(new OptionDefinition(name, code, space, type, + encapsulates.c_str())); + } + + } else { + def.reset(new OptionDefinition(name, code, space, type, array_type)); + + } + + if (user_context) { + def->setContext(user_context); + } + + // Split the list of record types into tokens. + std::vector<std::string> record_tokens = + isc::util::str::tokens(record_types, ","); + // Iterate over each token and add a record type into + // option definition. + BOOST_FOREACH(std::string record_type, record_tokens) { + try { + boost::trim(record_type); + if (!record_type.empty()) { + def->addRecordField(record_type); + } + } catch (const Exception& ex) { + isc_throw(DhcpConfigError, "invalid record type values" + << " specified for the option definition: " + << ex.what() << " (" + << getPosition("record-types", option_def) << ")"); + } + } + + // Validate the definition. + try { + def->validate(); + } catch (const std::exception& ex) { + isc_throw(DhcpConfigError, ex.what() + << " (" << option_def->getPosition() << ")"); + } + + // Option definition has been created successfully. + return (def); +} + +// ******************************** OptionDefListParser ************************ + +OptionDefListParser::OptionDefListParser(const uint16_t address_family) + : address_family_(address_family) { +} + +void +OptionDefListParser::parse(CfgOptionDefPtr storage, ConstElementPtr option_def_list) { + if (!option_def_list) { + // Sanity check: not supposed to fail. + isc_throw(DhcpConfigError, "parser error: a pointer to a list of" + << " option definitions is NULL (" + << option_def_list->getPosition() << ")"); + } + + OptionDefParser parser(address_family_); + BOOST_FOREACH(ConstElementPtr option_def, option_def_list->listValue()) { + OptionDefinitionPtr def = parser.parse(option_def); + try { + storage->add(def); + } catch (const std::exception& ex) { + // Append position if there is a failure. + isc_throw(DhcpConfigError, ex.what() << " (" + << option_def->getPosition() << ")"); + } + } + + // All definitions have been prepared. Put them as runtime options into + // the libdhcp++. + LibDHCP::setRuntimeOptionDefs(storage->getContainer()); +} + +//****************************** RelayInfoParser ******************************** +RelayInfoParser::RelayInfoParser(const Option::Universe& family) + : family_(family) { +}; + +void +RelayInfoParser::parse(const isc::dhcp::Network::RelayInfoPtr& relay_info, + ConstElementPtr relay_elem) { + + if (relay_elem->getType() != Element::map) { + isc_throw(DhcpConfigError, "relay must be a map"); + } + + ConstElementPtr address = relay_elem->get("ip-address"); + ConstElementPtr addresses = relay_elem->get("ip-addresses"); + + if (address && addresses) { + isc_throw(DhcpConfigError, + "specify either ip-address or ip-addresses, not both"); + } + + if (!address && !addresses) { + isc_throw(DhcpConfigError, "ip-addresses is required"); + } + + // Create our resultant RelayInfo structure + *relay_info = isc::dhcp::Network::RelayInfo(); + + if (address) { + addAddress("ip-address", getString(relay_elem, "ip-address"), + relay_elem, relay_info); + LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, + DHCPSRV_CFGMGR_RELAY_IP_ADDRESS_DEPRECATED) + .arg(getPosition("ip-address", relay_elem)); + return; + } + + if (addresses->getType() != Element::list) { + isc_throw(DhcpConfigError, "ip-addresses must be a list " + "(" << getPosition("ip-addresses", relay_elem) << ")"); + } + + BOOST_FOREACH(ConstElementPtr address_element, addresses->listValue()) { + addAddress("ip-addresses", address_element->stringValue(), + relay_elem, relay_info); + } +} + +void +RelayInfoParser::addAddress(const std::string& name, + const std::string& address_str, + ConstElementPtr relay_elem, + const isc::dhcp::Network::RelayInfoPtr& relay_info) { + boost::scoped_ptr<isc::asiolink::IOAddress> ip; + try { + ip.reset(new isc::asiolink::IOAddress(address_str)); + } catch (const std::exception& ex) { + isc_throw(DhcpConfigError, "address " << address_str + << " is not a valid: " + << (family_ == Option::V4 ? "IPv4" : "IPv6") + << "address" + << " (" << getPosition(name, relay_elem) << ")"); + } + + // Check if the address family matches. + if ((ip->isV4() && family_ != Option::V4) || + (ip->isV6() && family_ != Option::V6) ) { + isc_throw(DhcpConfigError, "address " << address_str + << " is not a: " + << (family_ == Option::V4 ? "IPv4" : "IPv6") + << "address" + << " (" << getPosition(name, relay_elem) << ")"); + } + + try { + relay_info->addAddress(*ip); + } catch (const std::exception& ex) { + isc_throw(DhcpConfigError, "cannot add address: " << address_str + << " to relay info: " << ex.what() + << " (" << getPosition(name, relay_elem) << ")"); + } +} + +//****************************** PoolParser ******************************** + +void +PoolParser::parse(PoolStoragePtr pools, + ConstElementPtr pool_structure, + const uint16_t address_family) { + + if (address_family == AF_INET) { + checkKeywords(SimpleParser4::POOL4_PARAMETERS, pool_structure); + } else { + checkKeywords(SimpleParser6::POOL6_PARAMETERS, pool_structure); + } + + ConstElementPtr text_pool = pool_structure->get("pool"); + + if (!text_pool) { + isc_throw(DhcpConfigError, "Mandatory 'pool' entry missing in " + "definition: (" << pool_structure->getPosition() << ")"); + } + + // That should be a single pool representation. It should contain + // text is form prefix/len or first - last. Note that spaces + // are allowed + string txt = text_pool->stringValue(); + + // first let's remove any whitespaces + boost::erase_all(txt, " "); // space + boost::erase_all(txt, "\t"); // tabulation + + PoolPtr pool; + + // Is this prefix/len notation? + size_t pos = txt.find("/"); + if (pos != string::npos) { + isc::asiolink::IOAddress addr("::"); + uint8_t len = 0; + try { + addr = isc::asiolink::IOAddress(txt.substr(0, pos)); + + // start with the first character after / + string prefix_len = txt.substr(pos + 1); + + // It is lexical cast to int and then downcast to uint8_t. + // Direct cast to uint8_t (which is really an unsigned char) + // will result in interpreting the first digit as output + // value and throwing exception if length is written on two + // digits (because there are extra characters left over). + + // No checks for values over 128. Range correctness will + // be checked in Pool4 constructor, here we only check + // the representation fits in an uint8_t as this can't + // be done by a direct lexical cast as explained... + int val_len = boost::lexical_cast<int>(prefix_len); + if ((val_len < std::numeric_limits<uint8_t>::min()) || + (val_len > std::numeric_limits<uint8_t>::max())) { + // This exception will be handled 4 line later! + isc_throw(OutOfRange, ""); + } + len = static_cast<uint8_t>(val_len); + } catch (...) { + isc_throw(DhcpConfigError, "Failed to parse pool " + "definition: " << txt << " (" + << text_pool->getPosition() << ")"); + } + + try { + pool = poolMaker(addr, len); + pools->push_back(pool); + } catch (const std::exception& ex) { + isc_throw(DhcpConfigError, "Failed to create pool defined by: " + << txt << " (" << text_pool->getPosition() << ")"); + } + + } else { + isc::asiolink::IOAddress min("::"); + isc::asiolink::IOAddress max("::"); + + // Is this min-max notation? + pos = txt.find("-"); + if (pos != string::npos) { + // using min-max notation + try { + min = isc::asiolink::IOAddress(txt.substr(0, pos)); + max = isc::asiolink::IOAddress(txt.substr(pos + 1)); + } catch (...) { + isc_throw(DhcpConfigError, "Failed to parse pool " + "definition: " << txt << " (" + << text_pool->getPosition() << ")"); + } + + try { + pool = poolMaker(min, max); + pools->push_back(pool); + } catch (const std::exception& ex) { + isc_throw(DhcpConfigError, "Failed to create pool defined by: " + << txt << " (" << text_pool->getPosition() << ")"); + } + } + } + + if (!pool) { + isc_throw(DhcpConfigError, "invalid pool definition: " + << text_pool->stringValue() << + ". There are two acceptable formats <min address-max address>" + " or <prefix/len> (" + << text_pool->getPosition() << ")"); + } + + // If there's user-context specified, store it. + ConstElementPtr user_context = pool_structure->get("user-context"); + if (user_context) { + // The grammar accepts only maps but still check it. + if (user_context->getType() != Element::map) { + isc_throw(isc::dhcp::DhcpConfigError, "User context has to be a map (" + << user_context->getPosition() << ")"); + } + pool->setContext(user_context); + } + + // Parser pool specific options. + ConstElementPtr option_data = pool_structure->get("option-data"); + if (option_data) { + try { + CfgOptionPtr cfg = pool->getCfgOption(); + auto option_parser = createOptionDataListParser(address_family); + option_parser->parse(cfg, option_data); + } catch (const std::exception& ex) { + isc_throw(isc::dhcp::DhcpConfigError, ex.what() + << " (" << option_data->getPosition() << ")"); + } + } + + // Client-class. + ConstElementPtr client_class = pool_structure->get("client-class"); + if (client_class) { + string cclass = client_class->stringValue(); + if (!cclass.empty()) { + pool->allowClientClass(cclass); + } + } + + // Try setting up required client classes. + ConstElementPtr class_list = pool_structure->get("require-client-classes"); + if (class_list) { + const std::vector<data::ElementPtr>& classes = class_list->listValue(); + for (auto cclass = classes.cbegin(); + cclass != classes.cend(); ++cclass) { + if (((*cclass)->getType() != Element::string) || + (*cclass)->stringValue().empty()) { + isc_throw(DhcpConfigError, "invalid class name (" + << (*cclass)->getPosition() << ")"); + } + pool->requireClientClass((*cclass)->stringValue()); + } + } +} + +boost::shared_ptr<OptionDataListParser> +PoolParser::createOptionDataListParser(const uint16_t address_family) const { + auto parser = boost::make_shared<OptionDataListParser>(address_family); + return (parser); +} + +//****************************** Pool4Parser ************************* + +PoolPtr +Pool4Parser::poolMaker (IOAddress &addr, uint32_t len, int32_t) { + return (PoolPtr(new Pool4(addr, len))); +} + +PoolPtr +Pool4Parser::poolMaker (IOAddress &min, IOAddress &max, int32_t) { + return (PoolPtr(new Pool4(min, max))); +} + +//****************************** Pools4ListParser ************************* + +void +Pools4ListParser::parse(PoolStoragePtr pools, ConstElementPtr pools_list) { + BOOST_FOREACH(ConstElementPtr pool, pools_list->listValue()) { + auto parser = createPoolConfigParser(); + parser->parse(pools, pool, AF_INET); + } +} + +boost::shared_ptr<PoolParser> +Pools4ListParser::createPoolConfigParser() const { + auto parser = boost::make_shared<Pool4Parser>(); + return (parser); +} + +//****************************** SubnetConfigParser ************************* + +SubnetConfigParser::SubnetConfigParser(uint16_t family, bool check_iface) + : pools_(new PoolStorage()), + address_family_(family), + options_(new CfgOption()), + check_iface_(check_iface) { + relay_info_.reset(new isc::dhcp::Network::RelayInfo()); +} + +SubnetPtr +SubnetConfigParser::parse(ConstElementPtr subnet) { + + ConstElementPtr options_params = subnet->get("option-data"); + if (options_params) { + auto opt_parser = createOptionDataListParser(); + opt_parser->parse(options_, options_params); + } + + ConstElementPtr relay_params = subnet->get("relay"); + if (relay_params) { + Option::Universe u = (address_family_ == AF_INET) ? Option::V4 : Option::V6; + RelayInfoParser parser(u); + parser.parse(relay_info_, relay_params); + } + + // Create a subnet. + try { + createSubnet(subnet); + } catch (const std::exception& ex) { + isc_throw(DhcpConfigError, + "subnet configuration failed: " << ex.what()); + } + + return (subnet_); +} + +void +SubnetConfigParser::createSubnet(ConstElementPtr params) { + std::string subnet_txt; + try { + subnet_txt = getString(params, "subnet"); + } catch (const DhcpConfigError &) { + // rethrow with precise error + isc_throw(DhcpConfigError, + "mandatory 'subnet' parameter is missing for a subnet being" + " configured (" << params->getPosition() << ")"); + } + + // Remove any spaces or tabs. + boost::erase_all(subnet_txt, " "); + boost::erase_all(subnet_txt, "\t"); + + // The subnet format is prefix/len. We are going to extract + // the prefix portion of a subnet string to create IOAddress + // object from it. IOAddress will be passed to the Subnet's + // constructor later on. In order to extract the prefix we + // need to get all characters preceding "/". + size_t pos = subnet_txt.find("/"); + if (pos == string::npos) { + ConstElementPtr elem = params->get("subnet"); + isc_throw(DhcpConfigError, + "Invalid subnet syntax (prefix/len expected):" << subnet_txt + << " (" << elem->getPosition() << ")"); + } + + // Try to create the address object. It also validates that + // the address syntax is ok. + isc::asiolink::IOAddress addr(subnet_txt.substr(0, pos)); + + // Now parse out the prefix length. + unsigned int len; + try { + len = boost::lexical_cast<unsigned int>(subnet_txt.substr(pos + 1)); + } catch (const boost::bad_lexical_cast&) { + ConstElementPtr elem = params->get("subnet"); + isc_throw(DhcpConfigError, "prefix length: '" << + subnet_txt.substr(pos+1) << "' is not an integer (" + << elem->getPosition() << ")"); + } + + // Sanity check the prefix length + if ((addr.isV6() && len > 128) || + (addr.isV4() && len > 32)) { + ConstElementPtr elem = params->get("subnet"); + isc_throw(BadValue, + "Invalid prefix length specified for subnet: " << len + << " (" << elem->getPosition() << ")"); + } + + // Call the subclass's method to instantiate the subnet + initSubnet(params, addr, len); + + // Add pools to it. + for (PoolStorage::iterator it = pools_->begin(); it != pools_->end(); + ++it) { + try { + subnet_->addPool(*it); + } catch (const BadValue& ex) { + // addPool() can throw BadValue if the pool is overlapping or + // is out of bounds for the subnet. + isc_throw(DhcpConfigError, + ex.what() << " (" << params->getPosition() << ")"); + } + } + // If there's user-context specified, store it. + ConstElementPtr user_context = params->get("user-context"); + if (user_context) { + // The grammar accepts only maps but still check it. + if (user_context->getType() != Element::map) { + isc_throw(isc::dhcp::DhcpConfigError, "User context has to be a map (" + << user_context->getPosition() << ")"); + } + subnet_->setContext(user_context); + } + + // In order to take advantage of the dynamic inheritance of global + // parameters to a subnet we need to set a callback function for each + // subnet to allow for fetching global parameters. + subnet_->setFetchGlobalsFn([]() -> ConstCfgGlobalsPtr { + return (CfgMgr::instance().getCurrentCfg()->getConfiguredGlobals()); + }); +} + +boost::shared_ptr<OptionDataListParser> +SubnetConfigParser::createOptionDataListParser() const { + auto parser = boost::make_shared<OptionDataListParser>(address_family_); + return (parser); +} + +//****************************** Subnet4ConfigParser ************************* + +Subnet4ConfigParser::Subnet4ConfigParser(bool check_iface) + : SubnetConfigParser(AF_INET, check_iface) { +} + +Subnet4Ptr +Subnet4ConfigParser::parse(ConstElementPtr subnet) { + // Check parameters. + checkKeywords(SimpleParser4::SUBNET4_PARAMETERS, subnet); + + /// Parse Pools first. + ConstElementPtr pools = subnet->get("pools"); + if (pools) { + auto parser = createPoolsListParser(); + parser->parse(pools_, pools); + } + + SubnetPtr generic = SubnetConfigParser::parse(subnet); + + if (!generic) { + // Sanity check: not supposed to fail. + isc_throw(DhcpConfigError, + "Failed to create an IPv4 subnet (" << + subnet->getPosition() << ")"); + } + + Subnet4Ptr sn4ptr = boost::dynamic_pointer_cast<Subnet4>(subnet_); + if (!sn4ptr) { + // If we hit this, it is a programming error. + isc_throw(Unexpected, + "Invalid Subnet4 cast in Subnet4ConfigParser::parse"); + } + + // Set relay information if it was parsed + if (relay_info_) { + sn4ptr->setRelayInfo(*relay_info_); + } + + // Parse Host Reservations for this subnet if any. + ConstElementPtr reservations = subnet->get("reservations"); + if (reservations) { + HostCollection hosts; + HostReservationsListParser<HostReservationParser4> parser; + parser.parse(subnet_->getID(), reservations, hosts); + for (auto h = hosts.begin(); h != hosts.end(); ++h) { + validateResv(sn4ptr, *h); + CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(*h); + } + } + + return (sn4ptr); +} + +void +Subnet4ConfigParser::initSubnet(data::ConstElementPtr params, + asiolink::IOAddress addr, uint8_t len) { + // Subnet ID is optional. If it is not supplied the value of 0 is used, + // which means autogenerate. The value was inserted earlier by calling + // SimpleParser4::setAllDefaults. + int64_t subnet_id_max = static_cast<int64_t>(SUBNET_ID_MAX); + SubnetID subnet_id = static_cast<SubnetID>(getInteger(params, "id", 0, + subnet_id_max)); + + Subnet4Ptr subnet4(new Subnet4(addr, len, Triplet<uint32_t>(), + Triplet<uint32_t>(), Triplet<uint32_t>(), + subnet_id)); + subnet_ = subnet4; + + // Move from reservation mode to new reservations flags. + ElementPtr mutable_params; + mutable_params = boost::const_pointer_cast<Element>(params); + // @todo add warning + BaseNetworkParser::moveReservationMode(mutable_params); + + // Parse parameters common to all Network derivations. + NetworkPtr network = boost::dynamic_pointer_cast<Network>(subnet4); + parseCommon(mutable_params, network); + + std::ostringstream output; + output << addr << "/" << static_cast<int>(len) << " with params: "; + + bool has_renew = !subnet4->getT1().unspecified(); + bool has_rebind = !subnet4->getT2().unspecified(); + int64_t renew = -1; + int64_t rebind = -1; + + // t1 and t2 are optional may be not specified. + if (has_renew) { + renew = subnet4->getT1().get(); + output << "t1=" << renew << ", "; + } + if (has_rebind) { + rebind = subnet4->getT2().get(); + output << "t2=" << rebind << ", "; + } + + if (has_renew && has_rebind && (renew > rebind)) { + isc_throw(DhcpConfigError, "the value of renew-timer (" << renew + << ") is greater than the value of rebind-timer (" + << rebind << ")"); + } + + if (!subnet4->getValid().unspecified()) { + output << "valid-lifetime=" << subnet4->getValid().get(); + } + + LOG_INFO(dhcpsrv_logger, DHCPSRV_CFGMGR_NEW_SUBNET4).arg(output.str()); + + // Set the match-client-id value for the subnet. + if (params->contains("match-client-id")) { + bool match_client_id = getBoolean(params, "match-client-id"); + subnet4->setMatchClientId(match_client_id); + } + + // Set the authoritative value for the subnet. + if (params->contains("authoritative")) { + bool authoritative = getBoolean(params, "authoritative"); + subnet4->setAuthoritative(authoritative); + } + + // Set next-server. The default value is 0.0.0.0. Nevertheless, the + // user could have messed that up by specifying incorrect value. + // To avoid using 0.0.0.0, user can specify "". + if (params->contains("next-server")) { + string next_server; + try { + next_server = getString(params, "next-server"); + if (!next_server.empty()) { + subnet4->setSiaddr(IOAddress(next_server)); + } + } catch (...) { + ConstElementPtr next = params->get("next-server"); + string pos; + if (next) { + pos = next->getPosition().str(); + } else { + pos = params->getPosition().str(); + } + isc_throw(DhcpConfigError, "invalid parameter next-server : " + << next_server << "(" << pos << ")"); + } + } + + // Set server-hostname. + if (params->contains("server-hostname")) { + std::string sname = getString(params, "server-hostname"); + if (!sname.empty()) { + if (sname.length() >= Pkt4::MAX_SNAME_LEN) { + ConstElementPtr error = params->get("server-hostname"); + isc_throw(DhcpConfigError, "server-hostname must be at most " + << Pkt4::MAX_SNAME_LEN - 1 << " bytes long, it is " + << sname.length() << " (" + << error->getPosition() << ")"); + } + subnet4->setSname(sname); + } + } + + // Set boot-file-name. + if (params->contains("boot-file-name")) { + std::string filename =getString(params, "boot-file-name"); + if (!filename.empty()) { + if (filename.length() > Pkt4::MAX_FILE_LEN) { + ConstElementPtr error = params->get("boot-file-name"); + isc_throw(DhcpConfigError, "boot-file-name must be at most " + << Pkt4::MAX_FILE_LEN - 1 << " bytes long, it is " + << filename.length() << " (" + << error->getPosition() << ")"); + } + subnet4->setFilename(filename); + } + } + + // Get interface name. If it is defined, then the subnet is available + // directly over specified network interface. + if (params->contains("interface")) { + std::string iface = getString(params, "interface"); + if (!iface.empty()) { + if (check_iface_ && !IfaceMgr::instance().getIface(iface)) { + ConstElementPtr error = params->get("interface"); + isc_throw(DhcpConfigError, "Specified network interface name " << iface + << " for subnet " << subnet4->toText() + << " is not present in the system (" + << error->getPosition() << ")"); + } + + subnet4->setIface(iface); + } + } + + // Try setting up client class. + if (params->contains("client-class")) { + string client_class = getString(params, "client-class"); + if (!client_class.empty()) { + subnet4->allowClientClass(client_class); + } + } + + // Try setting up required client classes. + ConstElementPtr class_list = params->get("require-client-classes"); + if (class_list) { + const std::vector<data::ElementPtr>& classes = class_list->listValue(); + for (auto cclass = classes.cbegin(); + cclass != classes.cend(); ++cclass) { + if (((*cclass)->getType() != Element::string) || + (*cclass)->stringValue().empty()) { + isc_throw(DhcpConfigError, "invalid class name (" + << (*cclass)->getPosition() << ")"); + } + subnet4->requireClientClass((*cclass)->stringValue()); + } + } + + // 4o6 specific parameter: 4o6-interface. + if (params->contains("4o6-interface")) { + string iface4o6 = getString(params, "4o6-interface"); + if (!iface4o6.empty()) { + subnet4->get4o6().setIface4o6(iface4o6); + subnet4->get4o6().enabled(true); + } + } + + // 4o6 specific parameter: 4o6-subnet. + if (params->contains("4o6-subnet")) { + string subnet4o6 = getString(params, "4o6-subnet"); + if (!subnet4o6.empty()) { + size_t slash = subnet4o6.find("/"); + if (slash == std::string::npos) { + isc_throw(DhcpConfigError, "Missing / in the 4o6-subnet parameter:" + << subnet4o6 << ", expected format: prefix6/length"); + } + string prefix = subnet4o6.substr(0, slash); + string lenstr = subnet4o6.substr(slash + 1); + + uint8_t len = 128; + try { + len = boost::lexical_cast<unsigned int>(lenstr.c_str()); + } catch (const boost::bad_lexical_cast &) { + isc_throw(DhcpConfigError, "Invalid prefix length specified in " + "4o6-subnet parameter: " << subnet4o6 << ", expected 0..128 value"); + } + subnet4->get4o6().setSubnet4o6(IOAddress(prefix), len); + subnet4->get4o6().enabled(true); + } + } + + // Try 4o6 specific parameter: 4o6-interface-id + if (params->contains("4o6-interface-id")) { + std::string ifaceid = getString(params, "4o6-interface-id"); + if (!ifaceid.empty()) { + OptionBuffer tmp(ifaceid.begin(), ifaceid.end()); + OptionPtr opt(new Option(Option::V6, D6O_INTERFACE_ID, tmp)); + subnet4->get4o6().setInterfaceId(opt); + subnet4->get4o6().enabled(true); + } + } + + /// client-class processing is now generic and handled in the common + /// code (see isc::data::SubnetConfigParser::createSubnet) + + // Here globally defined options were merged to the subnet specific + // options but this is no longer the case (they have a different + // and not consecutive priority). + + // Copy options to the subnet configuration. + options_->copyTo(*subnet4->getCfgOption()); + + // Parse t1-percent and t2-percent + parseTeePercents(params, network); + + // Parse DDNS parameters + parseDdnsParams(params, network); + + // Parse lease cache parameters + parseCacheParams(params, network); +} + +void +Subnet4ConfigParser::validateResv(const Subnet4Ptr& subnet, ConstHostPtr host) { + const IOAddress& address = host->getIPv4Reservation(); + if (!address.isV4Zero() && !subnet->inRange(address)) { + isc_throw(DhcpConfigError, "specified reservation '" << address + << "' is not within the IPv4 subnet '" + << subnet->toText() << "'"); + } +} + +boost::shared_ptr<PoolsListParser> +Subnet4ConfigParser::createPoolsListParser() const { + auto parser = boost::make_shared<Pools4ListParser>(); + return (parser); +} + +//**************************** Subnets4ListConfigParser ********************** + +Subnets4ListConfigParser::Subnets4ListConfigParser(bool check_iface) + : check_iface_(check_iface) { +} + +size_t +Subnets4ListConfigParser::parse(SrvConfigPtr cfg, + ConstElementPtr subnets_list) { + size_t cnt = 0; + BOOST_FOREACH(ConstElementPtr subnet_json, subnets_list->listValue()) { + + auto parser = createSubnetConfigParser(); + Subnet4Ptr subnet = parser->parse(subnet_json); + if (subnet) { + + // Adding a subnet to the Configuration Manager may fail if the + // subnet id is invalid (duplicate). Thus, we catch exceptions + // here to append a position in the configuration string. + try { + cfg->getCfgSubnets4()->add(subnet); + cnt++; + } catch (const std::exception& ex) { + isc_throw(DhcpConfigError, ex.what() << " (" + << subnet_json->getPosition() << ")"); + } + } + } + return (cnt); +} + +size_t +Subnets4ListConfigParser::parse(Subnet4Collection& subnets, + data::ConstElementPtr subnets_list) { + size_t cnt = 0; + BOOST_FOREACH(ConstElementPtr subnet_json, subnets_list->listValue()) { + + auto parser = createSubnetConfigParser(); + Subnet4Ptr subnet = parser->parse(subnet_json); + if (subnet) { + try { + auto ret = subnets.insert(subnet); + if (!ret.second) { + isc_throw(Unexpected, + "can't store subnet because of conflict"); + } + ++cnt; + } catch (const std::exception& ex) { + isc_throw(DhcpConfigError, ex.what() << " (" + << subnet_json->getPosition() << ")"); + } + } + } + return (cnt); +} + +boost::shared_ptr<Subnet4ConfigParser> +Subnets4ListConfigParser::createSubnetConfigParser() const { + auto parser = boost::make_shared<Subnet4ConfigParser>(check_iface_); + return (parser); +} + +//**************************** Pool6Parser ********************************* + +PoolPtr +Pool6Parser::poolMaker (IOAddress &addr, uint32_t len, int32_t ptype) +{ + return (PoolPtr(new Pool6(static_cast<isc::dhcp::Lease::Type> + (ptype), addr, len))); +} + +PoolPtr +Pool6Parser::poolMaker (IOAddress &min, IOAddress &max, int32_t ptype) +{ + return (PoolPtr(new Pool6(static_cast<isc::dhcp::Lease::Type> + (ptype), min, max))); +} + + +//**************************** Pool6ListParser *************************** + +void +Pools6ListParser::parse(PoolStoragePtr pools, ConstElementPtr pools_list) { + BOOST_FOREACH(ConstElementPtr pool, pools_list->listValue()) { + auto parser = createPoolConfigParser(); + parser->parse(pools, pool, AF_INET6); + } +} + +boost::shared_ptr<PoolParser> +Pools6ListParser::createPoolConfigParser() const { + auto parser = boost::make_shared<Pool6Parser>(); + return (parser); +} + +//**************************** PdPoolParser ****************************** + +PdPoolParser::PdPoolParser() : options_(new CfgOption()) { +} + +void +PdPoolParser::parse(PoolStoragePtr pools, ConstElementPtr pd_pool_) { + checkKeywords(SimpleParser6::PD_POOL6_PARAMETERS, pd_pool_); + + std::string addr_str = getString(pd_pool_, "prefix"); + + uint8_t prefix_len = getUint8(pd_pool_, "prefix-len"); + + uint8_t delegated_len = getUint8(pd_pool_, "delegated-len"); + + std::string excluded_prefix_str = "::"; + if (pd_pool_->contains("excluded-prefix")) { + excluded_prefix_str = getString(pd_pool_, "excluded-prefix"); + } + + uint8_t excluded_prefix_len = 0; + if (pd_pool_->contains("excluded-prefix-len")) { + excluded_prefix_len = getUint8(pd_pool_, "excluded-prefix-len"); + } + + ConstElementPtr option_data = pd_pool_->get("option-data"); + if (option_data) { + auto opts_parser = createOptionDataListParser(); + opts_parser->parse(options_, option_data); + } + + ConstElementPtr user_context = pd_pool_->get("user-context"); + if (user_context) { + user_context_ = user_context; + } + + ConstElementPtr client_class = pd_pool_->get("client-class"); + if (client_class) { + client_class_ = client_class; + } + + ConstElementPtr class_list = pd_pool_->get("require-client-classes"); + + // Check the pool parameters. It will throw an exception if any + // of the required parameters are invalid. + try { + // Attempt to construct the local pool. + pool_.reset(new Pool6(IOAddress(addr_str), + prefix_len, + delegated_len, + IOAddress(excluded_prefix_str), + excluded_prefix_len)); + // Merge options specified for a pool into pool configuration. + options_->copyTo(*pool_->getCfgOption()); + } catch (const std::exception& ex) { + // Some parameters don't exist or are invalid. Since we are not + // aware whether they don't exist or are invalid, let's append + // the position of the pool map element. + isc_throw(isc::dhcp::DhcpConfigError, ex.what() + << " (" << pd_pool_->getPosition() << ")"); + } + + if (user_context_) { + pool_->setContext(user_context_); + } + + if (client_class_) { + string cclass = client_class_->stringValue(); + if (!cclass.empty()) { + pool_->allowClientClass(cclass); + } + } + + if (class_list) { + const std::vector<data::ElementPtr>& classes = class_list->listValue(); + for (auto cclass = classes.cbegin(); + cclass != classes.cend(); ++cclass) { + if (((*cclass)->getType() != Element::string) || + (*cclass)->stringValue().empty()) { + isc_throw(DhcpConfigError, "invalid class name (" + << (*cclass)->getPosition() << ")"); + } + pool_->requireClientClass((*cclass)->stringValue()); + } + } + + // Add the local pool to the external storage ptr. + pools->push_back(pool_); +} + +boost::shared_ptr<OptionDataListParser> +PdPoolParser::createOptionDataListParser() const { + auto parser = boost::make_shared<OptionDataListParser>(AF_INET6); + return (parser); +} + +//**************************** PdPoolsListParser ************************ + +void +PdPoolsListParser::parse(PoolStoragePtr pools, ConstElementPtr pd_pool_list) { + // Loop through the list of pd pools. + BOOST_FOREACH(ConstElementPtr pd_pool, pd_pool_list->listValue()) { + auto parser = createPdPoolConfigParser(); + parser->parse(pools, pd_pool); + } +} + +boost::shared_ptr<PdPoolParser> +PdPoolsListParser::createPdPoolConfigParser() const { + auto parser = boost::make_shared<PdPoolParser>(); + return (parser); +} + +//**************************** Subnet6ConfigParser *********************** + +Subnet6ConfigParser::Subnet6ConfigParser(bool check_iface) + : SubnetConfigParser(AF_INET6, check_iface) { +} + +Subnet6Ptr +Subnet6ConfigParser::parse(ConstElementPtr subnet) { + // Check parameters. + checkKeywords(SimpleParser6::SUBNET6_PARAMETERS, subnet); + + /// Parse all pools first. + ConstElementPtr pools = subnet->get("pools"); + if (pools) { + auto parser = createPoolsListParser(); + parser->parse(pools_, pools); + } + ConstElementPtr pd_pools = subnet->get("pd-pools"); + if (pd_pools) { + auto parser = createPdPoolsListParser(); + parser->parse(pools_, pd_pools); + } + + SubnetPtr generic = SubnetConfigParser::parse(subnet); + + if (!generic) { + // Sanity check: not supposed to fail. + isc_throw(DhcpConfigError, + "Failed to create an IPv6 subnet (" << + subnet->getPosition() << ")"); + } + + Subnet6Ptr sn6ptr = boost::dynamic_pointer_cast<Subnet6>(subnet_); + if (!sn6ptr) { + // If we hit this, it is a programming error. + isc_throw(Unexpected, + "Invalid Subnet6 cast in Subnet6ConfigParser::parse"); + } + + // Set relay information if it was provided + if (relay_info_) { + sn6ptr->setRelayInfo(*relay_info_); + } + + // Parse Host Reservations for this subnet if any. + ConstElementPtr reservations = subnet->get("reservations"); + if (reservations) { + HostCollection hosts; + HostReservationsListParser<HostReservationParser6> parser; + parser.parse(subnet_->getID(), reservations, hosts); + for (auto h = hosts.begin(); h != hosts.end(); ++h) { + validateResvs(sn6ptr, *h); + CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(*h); + } + } + + return (sn6ptr); +} + +// Unused? +void +Subnet6ConfigParser::duplicate_option_warning(uint32_t code, + asiolink::IOAddress& addr) { + LOG_WARN(dhcpsrv_logger, DHCPSRV_CFGMGR_OPTION_DUPLICATE) + .arg(code).arg(addr.toText()); +} + +void +Subnet6ConfigParser::initSubnet(data::ConstElementPtr params, + asiolink::IOAddress addr, uint8_t len) { + // Subnet ID is optional. If it is not supplied the value of 0 is used, + // which means autogenerate. The value was inserted earlier by calling + // SimpleParser6::setAllDefaults. + int64_t subnet_id_max = static_cast<int64_t>(SUBNET_ID_MAX); + SubnetID subnet_id = static_cast<SubnetID>(getInteger(params, "id", 0, + subnet_id_max)); + + // We want to log whether rapid-commit is enabled, so we get this + // before the actual subnet creation. + Optional<bool> rapid_commit; + if (params->contains("rapid-commit")) { + rapid_commit = getBoolean(params, "rapid-commit"); + } + + // Parse preferred lifetime as it is not parsed by the common function. + Triplet<uint32_t> pref = parseIntTriplet(params, "preferred-lifetime"); + + // Create a new subnet. + Subnet6* subnet6 = new Subnet6(addr, len, Triplet<uint32_t>(), + Triplet<uint32_t>(), + pref, + Triplet<uint32_t>(), + subnet_id); + subnet_.reset(subnet6); + + // Move from reservation mode to new reservations flags. + ElementPtr mutable_params; + mutable_params = boost::const_pointer_cast<Element>(params); + // @todo add warning + BaseNetworkParser::moveReservationMode(mutable_params); + + // Parse parameters common to all Network derivations. + NetworkPtr network = boost::dynamic_pointer_cast<Network>(subnet_); + parseCommon(mutable_params, network); + + // Enable or disable Rapid Commit option support for the subnet. + if (!rapid_commit.unspecified()) { + subnet6->setRapidCommit(rapid_commit); + } + + std::ostringstream output; + output << addr << "/" << static_cast<int>(len) << " with params: "; + // t1 and t2 are optional may be not specified. + + bool has_renew = !subnet6->getT1().unspecified(); + bool has_rebind = !subnet6->getT2().unspecified(); + int64_t renew = -1; + int64_t rebind = -1; + + if (has_renew) { + renew = subnet6->getT1().get(); + output << "t1=" << renew << ", "; + } + if (has_rebind) { + rebind = subnet6->getT2().get(); + output << "t2=" << rebind << ", "; + } + + if (has_renew && has_rebind && (renew > rebind)) { + isc_throw(DhcpConfigError, "the value of renew-timer (" << renew + << ") is greater than the value of rebind-timer (" + << rebind << ")"); + } + + if (!subnet6->getPreferred().unspecified()) { + output << "preferred-lifetime=" << subnet6->getPreferred().get() << ", "; + } + if (!subnet6->getValid().unspecified()) { + output << "valid-lifetime=" << subnet6->getValid().get(); + } + if (!subnet6->getRapidCommit().unspecified()) { + output << ", rapid-commit is " + << boolalpha << subnet6->getRapidCommit().get(); + } + + LOG_INFO(dhcpsrv_logger, DHCPSRV_CFGMGR_NEW_SUBNET6).arg(output.str()); + + // Get interface-id option content. For now we support string + // representation only + Optional<std::string> ifaceid; + if (params->contains("interface-id")) { + ifaceid = getString(params, "interface-id"); + } + + Optional<std::string> iface; + if (params->contains("interface")) { + iface = getString(params, "interface"); + } + + // Specifying both interface for locally reachable subnets and + // interface id for relays is mutually exclusive. Need to test for + // this condition. + if (!ifaceid.unspecified() && !iface.unspecified() && !ifaceid.empty() && + !iface.empty()) { + isc_throw(isc::dhcp::DhcpConfigError, + "parser error: interface (defined for locally reachable " + "subnets) and interface-id (defined for subnets reachable" + " via relays) cannot be defined at the same time for " + "subnet " << addr << "/" << (int)len << "(" + << params->getPosition() << ")"); + } + + // Configure interface-id for remote interfaces, if defined + if (!ifaceid.unspecified() && !ifaceid.empty()) { + std::string ifaceid_value = ifaceid.get(); + OptionBuffer tmp(ifaceid_value.begin(), ifaceid_value.end()); + OptionPtr opt(new Option(Option::V6, D6O_INTERFACE_ID, tmp)); + subnet6->setInterfaceId(opt); + } + + // Get interface name. If it is defined, then the subnet is available + // directly over specified network interface. + if (!iface.unspecified() && !iface.empty()) { + if (check_iface_ && !IfaceMgr::instance().getIface(iface)) { + ConstElementPtr error = params->get("interface"); + isc_throw(DhcpConfigError, "Specified network interface name " << iface + << " for subnet " << subnet6->toText() + << " is not present in the system (" + << error->getPosition() << ")"); + } + + subnet6->setIface(iface); + } + + // Try setting up client class. + if (params->contains("client-class")) { + string client_class = getString(params, "client-class"); + if (!client_class.empty()) { + subnet6->allowClientClass(client_class); + } + } + + if (params->contains("require-client-classes")) { + // Try setting up required client classes. + ConstElementPtr class_list = params->get("require-client-classes"); + if (class_list) { + const std::vector<data::ElementPtr>& classes = class_list->listValue(); + for (auto cclass = classes.cbegin(); + cclass != classes.cend(); ++cclass) { + if (((*cclass)->getType() != Element::string) || + (*cclass)->stringValue().empty()) { + isc_throw(DhcpConfigError, "invalid class name (" + << (*cclass)->getPosition() << ")"); + } + subnet6->requireClientClass((*cclass)->stringValue()); + } + } + } + + /// client-class processing is now generic and handled in the common + /// code (see isc::data::SubnetConfigParser::createSubnet) + + // Copy options to the subnet configuration. + options_->copyTo(*subnet6->getCfgOption()); + + // Parse t1-percent and t2-percent + parseTeePercents(params, network); + + // Parse DDNS parameters + parseDdnsParams(params, network); + + // Parse lease cache parameters + parseCacheParams(params, network); +} + +void +Subnet6ConfigParser::validateResvs(const Subnet6Ptr& subnet, ConstHostPtr host) { + IPv6ResrvRange range = host->getIPv6Reservations(IPv6Resrv::TYPE_NA); + for (auto it = range.first; it != range.second; ++it) { + const IOAddress& address = it->second.getPrefix(); + if (!subnet->inRange(address)) { + isc_throw(DhcpConfigError, "specified reservation '" << address + << "' is not within the IPv6 subnet '" + << subnet->toText() << "'"); + } + } +} + +boost::shared_ptr<PoolsListParser> +Subnet6ConfigParser::createPoolsListParser() const { + auto parser = boost::make_shared<Pools6ListParser>(); + return (parser); +} + +boost::shared_ptr<PdPoolsListParser> +Subnet6ConfigParser::createPdPoolsListParser() const { + auto parser = boost::make_shared<PdPoolsListParser>(); + return (parser); +} + +//**************************** Subnet6ListConfigParser ******************** + +Subnets6ListConfigParser::Subnets6ListConfigParser(bool check_iface) + : check_iface_(check_iface) { +} + +size_t +Subnets6ListConfigParser::parse(SrvConfigPtr cfg, + ConstElementPtr subnets_list) { + size_t cnt = 0; + BOOST_FOREACH(ConstElementPtr subnet_json, subnets_list->listValue()) { + + auto parser = createSubnetConfigParser(); + Subnet6Ptr subnet = parser->parse(subnet_json); + + // Adding a subnet to the Configuration Manager may fail if the + // subnet id is invalid (duplicate). Thus, we catch exceptions + // here to append a position in the configuration string. + try { + cfg->getCfgSubnets6()->add(subnet); + cnt++; + } catch (const std::exception& ex) { + isc_throw(DhcpConfigError, ex.what() << " (" + << subnet_json->getPosition() << ")"); + } + } + return (cnt); +} + +size_t +Subnets6ListConfigParser::parse(Subnet6Collection& subnets, + ConstElementPtr subnets_list) { + size_t cnt = 0; + BOOST_FOREACH(ConstElementPtr subnet_json, subnets_list->listValue()) { + + auto parser = createSubnetConfigParser(); + Subnet6Ptr subnet = parser->parse(subnet_json); + if (subnet) { + try { + auto ret = subnets.insert(subnet); + if (!ret.second) { + isc_throw(Unexpected, + "can't store subnet because of conflict"); + } + ++cnt; + } catch (const std::exception& ex) { + isc_throw(DhcpConfigError, ex.what() << " (" + << subnet_json->getPosition() << ")"); + } + } + } + return (cnt); +} + +boost::shared_ptr<Subnet6ConfigParser> +Subnets6ListConfigParser::createSubnetConfigParser() const { + auto parser = boost::make_shared<Subnet6ConfigParser>(check_iface_); + return (parser); +} + +//**************************** D2ClientConfigParser ********************** + +dhcp_ddns::NameChangeProtocol +D2ClientConfigParser::getProtocol(ConstElementPtr scope, + const std::string& name) { + return (getAndConvert<dhcp_ddns::NameChangeProtocol, + dhcp_ddns::stringToNcrProtocol> + (scope, name, "NameChangeRequest protocol")); +} + +dhcp_ddns::NameChangeFormat +D2ClientConfigParser::getFormat(ConstElementPtr scope, + const std::string& name) { + return (getAndConvert<dhcp_ddns::NameChangeFormat, + dhcp_ddns::stringToNcrFormat> + (scope, name, "NameChangeRequest format")); +} + +D2ClientConfig::ReplaceClientNameMode +D2ClientConfigParser::getMode(ConstElementPtr scope, + const std::string& name) { + return (getAndConvert<D2ClientConfig::ReplaceClientNameMode, + D2ClientConfig::stringToReplaceClientNameMode> + (scope, name, "ReplaceClientName mode")); +} + +D2ClientConfigPtr +D2ClientConfigParser::parse(isc::data::ConstElementPtr client_config) { + D2ClientConfigPtr new_config; + + // Get all parameters that are needed to create the D2ClientConfig. + bool enable_updates = getBoolean(client_config, "enable-updates"); + + IOAddress server_ip = getAddress(client_config, "server-ip"); + + uint32_t server_port = getUint32(client_config, "server-port"); + + std::string sender_ip_str = getString(client_config, "sender-ip"); + + uint32_t sender_port = getUint32(client_config, "sender-port"); + + uint32_t max_queue_size = getUint32(client_config, "max-queue-size"); + + dhcp_ddns::NameChangeProtocol ncr_protocol = + getProtocol(client_config, "ncr-protocol"); + + dhcp_ddns::NameChangeFormat ncr_format = + getFormat(client_config, "ncr-format"); + + IOAddress sender_ip(0); + if (sender_ip_str.empty()) { + // The default sender IP depends on the server IP family + sender_ip = (server_ip.isV4() ? IOAddress::IPV4_ZERO_ADDRESS() : + IOAddress::IPV6_ZERO_ADDRESS()); + } else { + try { + sender_ip = IOAddress(sender_ip_str); + } catch (const std::exception& ex) { + isc_throw(DhcpConfigError, "invalid address (" << sender_ip_str + << ") specified for parameter 'sender-ip' (" + << getPosition("sender-ip", client_config) << ")"); + } + } + + // Now we check for logical errors. This repeats what is done in + // D2ClientConfig::validate(), but doing it here permits us to + // emit meaningful parameter position info in the error. + if (ncr_format != dhcp_ddns::FMT_JSON) { + isc_throw(D2ClientError, "D2ClientConfig error: NCR Format: " + << dhcp_ddns::ncrFormatToString(ncr_format) + << " is not supported. (" + << getPosition("ncr-format", client_config) << ")"); + } + + if (ncr_protocol != dhcp_ddns::NCR_UDP) { + isc_throw(D2ClientError, "D2ClientConfig error: NCR Protocol: " + << dhcp_ddns::ncrProtocolToString(ncr_protocol) + << " is not supported. (" + << getPosition("ncr-protocol", client_config) << ")"); + } + + if (sender_ip.getFamily() != server_ip.getFamily()) { + isc_throw(D2ClientError, + "D2ClientConfig error: address family mismatch: " + << "server-ip: " << server_ip.toText() + << " is: " << (server_ip.isV4() ? "IPv4" : "IPv6") + << " while sender-ip: " << sender_ip.toText() + << " is: " << (sender_ip.isV4() ? "IPv4" : "IPv6") + << " (" << getPosition("sender-ip", client_config) << ")"); + } + + if (server_ip == sender_ip && server_port == sender_port) { + isc_throw(D2ClientError, + "D2ClientConfig error: server and sender cannot" + " share the exact same IP address/port: " + << server_ip.toText() << "/" << server_port + << " (" << getPosition("sender-ip", client_config) << ")"); + } + + try { + // Attempt to create the new client config. + new_config.reset(new D2ClientConfig(enable_updates, + server_ip, + server_port, + sender_ip, + sender_port, + max_queue_size, + ncr_protocol, + ncr_format)); + } catch (const std::exception& ex) { + isc_throw(DhcpConfigError, ex.what() << " (" + << client_config->getPosition() << ")"); + } + + // Add user context + ConstElementPtr user_context = client_config->get("user-context"); + if (user_context) { + new_config->setContext(user_context); + } + + return (new_config); +} + +/// @brief This table defines default values for D2 client configuration +const SimpleDefaults D2ClientConfigParser::D2_CLIENT_CONFIG_DEFAULTS = { + // enable-updates is unconditionally required + { "server-ip", Element::string, "127.0.0.1" }, + { "server-port", Element::integer, "53001" }, + // default sender-ip depends on server-ip family, so we leave default blank + // parser knows to use the appropriate ZERO address based on server-ip + { "sender-ip", Element::string, "" }, + { "sender-port", Element::integer, "0" }, + { "max-queue-size", Element::integer, "1024" }, + { "ncr-protocol", Element::string, "UDP" }, + { "ncr-format", Element::string, "JSON" } +}; + +size_t +D2ClientConfigParser::setAllDefaults(isc::data::ConstElementPtr d2_config) { + ElementPtr mutable_d2 = boost::const_pointer_cast<Element>(d2_config); + return (SimpleParser::setDefaults(mutable_d2, D2_CLIENT_CONFIG_DEFAULTS)); +} + +} // namespace dhcp +} // namespace isc diff --git a/src/lib/dhcpsrv/parsers/dhcp_parsers.h b/src/lib/dhcpsrv/parsers/dhcp_parsers.h new file mode 100644 index 0000000..9c60883 --- /dev/null +++ b/src/lib/dhcpsrv/parsers/dhcp_parsers.h @@ -0,0 +1,1051 @@ +// Copyright (C) 2013-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/. + +#ifndef DHCP_PARSERS_H +#define DHCP_PARSERS_H + +#include <asiolink/io_address.h> +#include <cc/data.h> +#include <dhcp/option_definition.h> +#include <dhcp/option_space_container.h> +#include <dhcpsrv/d2_client_cfg.h> +#include <dhcpsrv/cfg_iface.h> +#include <dhcpsrv/cfg_option.h> +#include <dhcpsrv/network.h> +#include <dhcpsrv/subnet.h> +#include <dhcpsrv/cfg_option_def.h> +#include <dhcpsrv/cfg_mac_source.h> +#include <dhcpsrv/srv_config.h> +#include <dhcpsrv/parsers/base_network_parser.h> +#include <dhcpsrv/parsers/option_data_parser.h> +#include <cc/simple_parser.h> +#include <exceptions/exceptions.h> +#include <util/optional.h> + +#include <boost/shared_ptr.hpp> + +#include <stdint.h> +#include <string> +#include <vector> + +namespace isc { +namespace dhcp { + +/// Collection of containers holding option spaces. Each container within +/// a particular option space holds so-called option descriptors. +typedef OptionSpaceContainer<OptionContainer, OptionDescriptor, + std::string> OptionStorage; +/// @brief Shared pointer to option storage. +typedef boost::shared_ptr<OptionStorage> OptionStoragePtr; + +/// @brief A template class that stores named elements of a given data type. +/// +/// This template class is provides data value storage for configuration +/// parameters of a given data type. The values are stored by parameter name +/// and as instances of type "ValueType". Each value held in the storage has +/// a corresponding position within a configuration string (file) specified +/// as a: file name, line number and position within the line. The position +/// information is used for logging when the particular configuration value +/// causes a configuration error. +/// +/// @tparam ValueType is the data type of the elements to store. +template<typename ValueType> +class ValueStorage { +public: + /// @brief Stores the parameter, its value and the position in the + /// store. + /// + /// If the parameter does not exist in the store, then it will be added, + /// otherwise its data value and the position will be updated with the + /// given values. + /// + /// @param name is the name of the parameter to store. + /// @param value is the data value to store. + /// @param position is the position of the data element within a + /// configuration string (file). + void setParam(const std::string& name, const ValueType& value, + const data::Element::Position& position) { + values_[name] = value; + positions_[name] = position; + } + + /// @brief Returns the data value for the given parameter. + /// + /// Finds and returns the data value for the given parameter. + /// @param name is the name of the parameter for which the data + /// value is desired. + /// + /// @return The parameter's data value of type @c ValueType. + /// @throw DhcpConfigError if the parameter is not found. + ValueType getParam(const std::string& name) const { + typename std::map<std::string, ValueType>::const_iterator param + = values_.find(name); + + if (param == values_.end()) { + isc_throw(DhcpConfigError, "Missing parameter '" + << name << "'"); + } + + return (param->second); + } + + /// @brief Returns position of the data element in the configuration string. + /// + /// The returned object comprises file name, line number and the position + /// within the particular line of the configuration string where the data + /// element holding a particular value is located. + /// + /// @param name is the name of the parameter which position is desired. + /// @param parent Pointer to a data element which position should be + /// returned when position of the specified parameter is not found. + /// + /// @return Position of the data element or the position holding empty + /// file name and two zeros if the position hasn't been specified for the + /// particular value. + const data::Element::Position& + getPosition(const std::string& name, const data::ConstElementPtr parent = + data::ConstElementPtr()) const { + typename std::map<std::string, data::Element::Position>::const_iterator + pos = positions_.find(name); + if (pos == positions_.end()) { + return (parent ? parent->getPosition() : + data::Element::ZERO_POSITION()); + } + + return (pos->second); + } + + /// @brief Returns the data value for an optional parameter. + /// + /// Finds and returns the data value for the given parameter or + /// a supplied default value if it is not found. + /// + /// @param name is the name of the parameter for which the data + /// value is desired. + /// @param default_value value to use the default + /// + /// @return The parameter's data value of type @c ValueType. + ValueType getOptionalParam(const std::string& name, + const ValueType& default_value) const { + typename std::map<std::string, ValueType>::const_iterator param + = values_.find(name); + + if (param == values_.end()) { + return (default_value); + } + + return (param->second); + } + + /// @brief Remove the parameter from the store. + /// + /// Deletes the entry for the given parameter from the store if it + /// exists. + /// + /// @param name is the name of the parameter to delete. + void delParam(const std::string& name) { + values_.erase(name); + positions_.erase(name); + } + + /// @brief Deletes all of the entries from the store. + /// + void clear() { + values_.clear(); + positions_.clear(); + } + +private: + /// @brief An std::map of the data values, keyed by parameter names. + std::map<std::string, ValueType> values_; + + /// @brief An std::map holding positions of the data elements in the + /// configuration, which values are held in @c values_. + /// + /// The position is used for logging, when the particular value + /// causes a configuration error. + std::map<std::string, data::Element::Position> positions_; + +}; + +/// @brief Combination of parameter name and configuration contents +typedef std::pair<std::string, isc::data::ConstElementPtr> ConfigPair; + +/// @brief a collection of elements that store uint32 values +typedef ValueStorage<uint32_t> Uint32Storage; +typedef boost::shared_ptr<Uint32Storage> Uint32StoragePtr; + +/// @brief a collection of elements that store string values +typedef ValueStorage<std::string> StringStorage; +typedef boost::shared_ptr<StringStorage> StringStoragePtr; + +/// @brief Storage for parsed boolean values. +typedef ValueStorage<bool> BooleanStorage; +typedef boost::shared_ptr<BooleanStorage> BooleanStoragePtr; + +/// @brief parser for MAC/hardware acquisition sources +/// +/// This parser handles Dhcp6/mac-sources entry. +/// It contains a list of MAC/hardware acquisition source, i.e. methods how +/// MAC address can possibly by obtained in DHCPv6. For a currently supported +/// methods, see @ref isc::dhcp::Pkt::getMAC. +class MACSourcesListConfigParser : public isc::data::SimpleParser { +public: + /// @brief parses parameters value + /// + /// Parses configuration entry (list of sources) and adds each element + /// to the sources list. + /// + /// @param value pointer to the content of parsed values + /// @param mac_sources parsed sources will be stored here + void parse(CfgMACSource& mac_sources, isc::data::ConstElementPtr value); +}; + +/// @brief Parser for the control-socket structure +/// +/// It does not parse anything, simply stores the element in +/// the staging config. +class ControlSocketParser : public isc::data::SimpleParser { +public: + /// @brief "Parses" control-socket structure + /// + /// Since the SrvConfig structure takes the socket definition + /// as ConstElementPtr, there's really nothing to parse here. + /// It only does basic sanity checks and throws DhcpConfigError + /// if the value is null or is not a map. + /// + /// @param srv_cfg parsed values will be stored here + /// @param value pointer to the content of parsed values + void parse(SrvConfig& srv_cfg, isc::data::ConstElementPtr value); +}; + +/// @brief Parser for a single option definition. +/// +/// This parser creates an instance of a single option definition. +class OptionDefParser : public isc::data::SimpleParser { +public: + /// @brief Constructor. + /// + /// @param address_family Address family: @c AF_INET or @c AF_INET6. + OptionDefParser(const uint16_t address_family); + + /// @brief Parses an entry that describes single option definition. + /// + /// @param option_def a configuration entry to be parsed. + /// @return option definition of the parsed structure. + /// + /// @throw DhcpConfigError if parsing was unsuccessful. + OptionDefinitionPtr parse(isc::data::ConstElementPtr option_def); + +private: + /// @brief Address family: @c AF_INET or @c AF_INET6. + uint16_t address_family_; +}; + +/// @brief Parser for a list of option definitions. +/// +/// This parser iterates over all configuration entries that define +/// option definitions and creates instances of these definitions. +/// If the parsing is successful, the collection of created definitions +/// is put into the provided storage. +class OptionDefListParser : public isc::data::SimpleParser { +public: + /// @brief Constructor. + /// + /// @param address_family Address family: @c AF_INET or @c AF_INET6. + OptionDefListParser(const uint16_t address_family); + + /// @brief Parses a list of option definitions, create them and store in cfg + /// + /// This method iterates over def_list, which is a JSON list of option definitions, + /// then creates corresponding option definitions and store them in the + /// configuration structure. + /// + /// @param def_list JSON list describing option definitions + /// @param cfg parsed option definitions will be stored here + void parse(CfgOptionDefPtr cfg, isc::data::ConstElementPtr def_list); + +private: + /// @brief Address family: @c AF_INET or @c AF_INET6. + uint16_t address_family_; +}; + +/// @brief a collection of pools +/// +/// That type is used as intermediate storage, when pools are parsed, but there is +/// no subnet object created yet to store them. +typedef std::vector<PoolPtr> PoolStorage; +typedef boost::shared_ptr<PoolStorage> PoolStoragePtr; + +/// @brief parser for a single pool definition +/// +/// This abstract parser handles pool definitions, i.e. a list of entries of one +/// of two syntaxes: min-max and prefix/len. Pool objects are created +/// and stored in chosen PoolStorage container. +/// +/// It is useful for parsing Dhcp<4/6>/subnet<4/6>[X]/pools[X] structure. +class PoolParser : public isc::data::SimpleParser { +public: + + /// @brief destructor. + virtual ~PoolParser() { + } + + /// @brief parses the actual structure + /// + /// This method parses the actual list of interfaces. + /// No validation is done at this stage, everything is interpreted as + /// interface name. + /// @param pools is the storage in which to store the parsed pool + /// @param pool_structure a single entry on a list of pools + /// @param address_family AF_INET (for DHCPv4) or AF_INET6 (for DHCPv6). + /// @throw isc::dhcp::DhcpConfigError when pool parsing fails + virtual void parse(PoolStoragePtr pools, + isc::data::ConstElementPtr pool_structure, + const uint16_t address_family); + +protected: + /// @brief Creates a Pool object given a IPv4 prefix and the prefix length. + /// + /// @param addr is the IP prefix of the pool. + /// @param len is the prefix length. + /// @param ptype is the type of pool to create. + /// @return returns a PoolPtr to the new Pool object. + virtual PoolPtr poolMaker(isc::asiolink::IOAddress &addr, uint32_t len, + int32_t ptype = 0) = 0; + + /// @brief Creates a Pool object given starting and ending IP addresses. + /// + /// @param min is the first IP address in the pool. + /// @param max is the last IP address in the pool. + /// @param ptype is the type of pool to create (not used by all derivations) + /// @return returns a PoolPtr to the new Pool object. + virtual PoolPtr poolMaker(isc::asiolink::IOAddress &min, + isc::asiolink::IOAddress &max, + int32_t ptype = 0) = 0; + + /// @brief Returns an instance of the @c OptionDataListParser to + /// be used in parsing the option-data structure. + /// + /// This function can be overridden in the child classes to supply + /// a custom parser for option data. + /// + /// @param address_family AF_INET (for DHCPv4) or AF_INET6 (for DHCPv6). + /// + /// @return an instance of the @c OptionDataListParser. + virtual boost::shared_ptr<OptionDataListParser> + createOptionDataListParser(const uint16_t address_family) const; +}; + +/// @brief Parser for IPv4 pool definitions. +/// +/// This is the IPv4 derivation of the PoolParser class and handles pool +/// definitions, i.e. a list of entries of one of two syntaxes: min-max and +/// prefix/len for IPv4 pools. Pool4 objects are created and stored in chosen +/// PoolStorage container. +/// +/// It is useful for parsing Dhcp4/subnet4[X]/pool parameters. +class Pool4Parser : public PoolParser { +protected: + /// @brief Creates a Pool4 object given a IPv4 prefix and the prefix length. + /// + /// @param addr is the IPv4 prefix of the pool. + /// @param len is the prefix length. + /// @param ignored dummy parameter to provide symmetry between the + /// PoolParser derivations. The V6 derivation requires a third value. + /// @return returns a PoolPtr to the new Pool4 object. + PoolPtr poolMaker (asiolink::IOAddress &addr, uint32_t len, + int32_t ignored); + + /// @brief Creates a Pool4 object given starting and ending IPv4 addresses. + /// + /// @param min is the first IPv4 address in the pool. + /// @param max is the last IPv4 address in the pool. + /// @param ignored dummy parameter to provide symmetry between the + /// PoolParser derivations. The V6 derivation requires a third value. + /// @return returns a PoolPtr to the new Pool4 object. + PoolPtr poolMaker (asiolink::IOAddress &min, asiolink::IOAddress &max, + int32_t ignored); +}; + +/// @brief Parser for a list of pools +/// +/// This parser parses a list pools. Each element on that list gets its own +/// parser, created with poolParserMaker() method. That method must be specified +/// for each protocol family (v4 or v6) separately. +class PoolsListParser : public isc::data::SimpleParser { +public: + + /// @brief destructor. + virtual ~PoolsListParser() { + } + + /// @brief parses the actual structure + /// + /// This method parses the actual list of pools. + /// + /// @param pools is the storage in which to store the parsed pools. + /// @param pools_list a list of pool structures + /// @throw isc::dhcp::DhcpConfigError when pool parsing fails + virtual void parse(PoolStoragePtr pools, + isc::data::ConstElementPtr pools_list) = 0; + +protected: + + /// @brief Returns an instance of the @c PoolParser to be used in + /// parsing the address pools. + /// + /// This function can be overridden in the child classes to supply + /// a custom parser for the pools. + /// + /// @return an instance of the @c PoolParser. + virtual boost::shared_ptr<PoolParser> createPoolConfigParser() const = 0; +}; + +/// @brief Specialization of the pool list parser for DHCPv4 +class Pools4ListParser : public PoolsListParser { +public: + + /// @brief parses the actual structure + /// + /// This method parses the actual list of pools. + /// + /// @param pools storage container in which to store the parsed pool. + /// @param pools_list a list of pool structures + /// @throw isc::dhcp::DhcpConfigError when pool parsing fails + void parse(PoolStoragePtr pools, data::ConstElementPtr pools_list); + +protected: + + /// @brief Returns an instance of the @c Pool4Parser to be used in + /// parsing the address pools. + /// + /// This function can be overridden in the child classes to supply + /// a custom parser for the pools. + /// + /// @return an instance of the @c Pool4Parser. + virtual boost::shared_ptr<PoolParser> createPoolConfigParser() const; +}; + +/// @brief parser for additional relay information +/// +/// This concrete parser handles RelayInfo structure definitions. +/// So far that structure holds only relay IP (v4 or v6) address, but it +/// is expected that the number of parameters will increase over time. +/// +/// It is useful for parsing Dhcp<4/6>/subnet<4/6>[x]/relay parameters. +class RelayInfoParser : public isc::data::SimpleParser { +public: + + /// @brief constructor + /// @param family specifies protocol family (IPv4 or IPv6) + explicit RelayInfoParser(const isc::dhcp::Option::Universe& family); + + /// @brief parses the actual relay parameters + /// + /// The elements currently supported are: + /// -# ip-address + /// -# ip-addresses + /// + /// Note that ip-address and ip-addresses are mutually exclusive, with + /// former being deprecated. The use of ip-address will cause an debug + /// log to be emitted, reminded users to switch. + /// + /// @param relay_info configuration will be stored here + /// @param relay_elem Element tree containing the relay and its members + /// @throw isc::dhcp::DhcpConfigError if both or neither of ip-address + /// and ip-addresses are specified. + void parse(const isc::dhcp::Network::RelayInfoPtr& relay_info, + isc::data::ConstElementPtr relay_elem); + + /// @brief Attempts to add an IP address to list of relay addresses + /// + /// @param name name of the element supplying the address string, (either + /// "ip-address" or "ip-addresses") + /// @param address_str string form of the IP address to add + /// @param relay_elem parent relay element (needed for position info) + /// @param relay_info RelayInfo to which the address should be added + /// @throw isc::dhcp::DhcpConfigError if the address string is not a valid + /// IP address, is an address of the wrong family, or is already in the + /// relay address list + void addAddress(const std::string& name, const std::string& address_str, + isc::data::ConstElementPtr relay_elem, + const isc::dhcp::Network::RelayInfoPtr& relay_info); +private: + + /// Protocol family (IPv4 or IPv6) + Option::Universe family_; +}; + +/// @brief this class parses a single subnet +/// +/// There are dedicated @ref Subnet4ConfigParser and @ref Subnet6ConfigParser +/// classes. They provide specialized parse() methods that return Subnet4Ptr +/// or Subnet6Ptr. +/// +/// This class parses the whole subnet definition. This class attempts to +/// unify the code between v4 and v6 as much as possible. As a result, the flow +/// is somewhat complex and it looks as follows: +/// +/// ------- Base class +/// / +/// | /----- Derived class +/// 1. * SubnetXConfigParser::parse() is called. +/// 2. * SubnetConfigParser::parse() is called. +/// 3. * SubnetConfigParser::createSubnet() is called. +/// 4. * SubnetXConfigParser::initSubnet() is called (Subnet4 or Subnet6 is +/// instantiated here and family specific parameters are set) +/// 5. Control returns to createSubnet() (step 3) and common parameters +/// are set. +class SubnetConfigParser : public BaseNetworkParser { +public: + + /// @brief constructor + /// + /// @param family address family: @c AF_INET or @c AF_INET6 + /// @param check_iface Check if the specified interface exists in + /// the system. + explicit SubnetConfigParser(uint16_t family, bool check_iface = true); + + /// @brief virtual destructor (does nothing) + virtual ~SubnetConfigParser() { } + +protected: + /// @brief parses a subnet description and returns Subnet{4,6} structure + /// + /// This method is called from specialized (Subnet4ConfigParser or + /// Subnet6ConfigParser) classes. + /// + /// @param subnet pointer to the content of subnet definition + /// @return a pointer to newly created subnet + /// + /// @throw isc::DhcpConfigError if subnet configuration parsing failed. + SubnetPtr parse(isc::data::ConstElementPtr subnet); + + /// @brief Instantiates the subnet based on a given IP prefix and prefix + /// length. + /// + /// @param params configuration parameters for that subnet + /// @param addr is the IP prefix of the subnet. + /// @param len is the prefix length + virtual void initSubnet(isc::data::ConstElementPtr params, + isc::asiolink::IOAddress addr, uint8_t len) = 0; + +protected: + + /// @brief Create a new subnet using a data from child parsers. + /// + /// @param data Element map that describes the subnet + /// @throw isc::dhcp::DhcpConfigError if subnet configuration parsing + /// failed. + void createSubnet(isc::data::ConstElementPtr data); + + /// @brief Returns an instance of the @c OptionDataListParser to + /// be used in parsing the option-data structure. + /// + /// This function can be overridden in the child classes to supply + /// a custom parser for option data. + /// + /// @return an instance of the @c OptionDataListParser. + virtual boost::shared_ptr<OptionDataListParser> createOptionDataListParser() const; + + /// @brief Returns an instance of the @c PoolsListParser to be used + /// in parsing the address pools. + /// + /// This function can be overridden in the child classes to supply + /// a custom parser for the pools. + /// + /// @return an instance of the @c PoolsListParser. + virtual boost::shared_ptr<PoolsListParser> + createPoolsListParser() const = 0; + + /// Storage for pools belonging to this subnet. + PoolStoragePtr pools_; + + /// Pointer to the created subnet object. + isc::dhcp::SubnetPtr subnet_; + + /// @brief Address family: @c AF_INET or @c AF_INET6 + uint16_t address_family_; + + /// Pointer to relay information + isc::dhcp::Network::RelayInfoPtr relay_info_; + + /// Pointer to the options configuration. + CfgOptionPtr options_; + + /// Check if the specified interface exists in the system. + bool check_iface_; +}; + +/// @anchor Subnet4ConfigParser +/// @brief This class parses a single IPv4 subnet. +/// +/// This is the IPv4 derivation of the SubnetConfigParser class and it parses +/// the whole subnet definition. It creates parsersfor received configuration +/// parameters as needed. +class Subnet4ConfigParser : public SubnetConfigParser { +public: + /// @brief Constructor + /// + /// stores global scope parameters, options, option definitions. + /// + /// @param check_iface Check if the specified interface exists in + /// the system. + Subnet4ConfigParser(bool check_iface = true); + + /// @brief Parses a single IPv4 subnet configuration and adds to the + /// Configuration Manager. + /// + /// @param subnet A new subnet being configured. + /// @return a pointer to created Subnet4 object + Subnet4Ptr parse(data::ConstElementPtr subnet); + +protected: + + /// @brief Instantiates the IPv4 Subnet based on a given IPv4 address + /// and prefix length. + /// + /// @param params Data structure describing a subnet. + /// @param addr is IPv4 address of the subnet. + /// @param len is the prefix length + void initSubnet(data::ConstElementPtr params, + asiolink::IOAddress addr, uint8_t len); + + /// @brief Verifies the host reservation address is in the subnet range + /// + /// @param subnet pointer to the subnet + /// @param host pointer to the host reservation + /// @throw DhcpConfigError when the address is not in the subnet range. + void validateResv(const Subnet4Ptr& subnet, ConstHostPtr host); + + /// @brief Returns an instance of the @c Pools4ListParser to be used + /// in parsing the address pools. + /// + /// This function can be overridden in the child classes to supply + /// a custom parser for the pools. + /// + /// @return an instance of the @c Pools4ListParser. + virtual boost::shared_ptr<PoolsListParser> + createPoolsListParser() const; +}; + +/// @brief this class parses list of DHCP4 subnets +/// +/// This is a wrapper parser that handles the whole list of Subnet4 +/// definitions. It iterates over all entries and creates Subnet4ConfigParser +/// for each entry. +class Subnets4ListConfigParser : public isc::data::SimpleParser { +public: + + /// @brief constructor + /// + /// @param check_iface Check if the specified interface exists in + /// the system. + Subnets4ListConfigParser(bool check_iface = true); + + /// @brief Virtual destructor. + virtual ~Subnets4ListConfigParser() { + } + + /// @brief parses contents of the list + /// + /// Iterates over all entries on the list, parses its content + /// (by instantiating Subnet6ConfigParser) and adds to specified + /// configuration. + /// + /// @param cfg Pointer to server configuration. + /// @param subnets_list pointer to a list of IPv4 subnets + /// @return number of subnets created + size_t parse(SrvConfigPtr cfg, data::ConstElementPtr subnets_list); + + /// @brief Parses contents of the subnet4 list. + /// + /// @param [out] subnets Container where parsed subnets will be stored. + /// @param subnets_list pointer to a list of IPv4 subnets + /// @return Number of subnets created. + size_t parse(Subnet4Collection& subnets, + data::ConstElementPtr subnets_list); + +protected: + + /// @brief Returns an instance of the @c Subnet4ConfigParser to be + /// used in parsing the subnets. + /// + /// This function can be overridden in the child classes to supply + /// a custom parser for the subnets. + /// + /// @return an instance of the @c Subnet4ConfigParser. + virtual boost::shared_ptr<Subnet4ConfigParser> createSubnetConfigParser() const; + + /// Check if the specified interface exists in the system. + bool check_iface_; +}; + +/// @brief Parser for IPv6 pool definitions. +/// +/// This is the IPv6 derivation of the PoolParser class and handles pool +/// definitions, i.e. a list of entries of one of two syntaxes: min-max and +/// prefix/len for IPv6 pools. Pool6 objects are created and stored in chosen +/// PoolStorage container. +/// +/// It is useful for parsing Dhcp6/subnet6[X]/pool parameters. +class Pool6Parser : public PoolParser { +protected: + /// @brief Creates a Pool6 object given a IPv6 prefix and the prefix length. + /// + /// @param addr is the IPv6 prefix of the pool. + /// @param len is the prefix length. + /// @param ptype is the type of IPv6 pool (Pool::PoolType). Note this is + /// passed in as an int32_t and cast to PoolType to accommodate a + /// polymorphic interface. + /// @return returns a PoolPtr to the new Pool4 object. + PoolPtr poolMaker (asiolink::IOAddress &addr, uint32_t len, int32_t ptype); + + /// @brief Creates a Pool6 object given starting and ending IPv6 addresses. + /// + /// @param min is the first IPv6 address in the pool. + /// @param max is the last IPv6 address in the pool. + /// @param ptype is the type of IPv6 pool (Pool::PoolType). Note this is + /// passed in as an int32_t and cast to PoolType to accommodate a + /// polymorphic interface. + /// @return returns a PoolPtr to the new Pool4 object. + PoolPtr poolMaker (asiolink::IOAddress &min, asiolink::IOAddress &max, + int32_t ptype); +}; + +/// @brief Specialization of the pool list parser for DHCPv6 +class Pools6ListParser : public PoolsListParser { +public: + + /// @brief parses the actual structure + /// + /// This method parses the actual list of pools. + /// + /// @param pools storage container in which to store the parsed pool. + /// @param pools_list a list of pool structures + /// @throw isc::dhcp::DhcpConfigError when pool parsing fails + void parse(PoolStoragePtr pools, data::ConstElementPtr pools_list); + +protected: + + /// @brief Returns an instance of the @c Pool6Parser to be used in + /// parsing the address pools. + /// + /// This function can be overridden in the child classes to supply + /// a custom parser for the pools. + /// + /// @return an instance of the @c Pool6Parser. + virtual boost::shared_ptr<PoolParser> createPoolConfigParser() const; +}; + +/// @brief Parser for IPv6 prefix delegation definitions. +/// +/// This class handles prefix delegation pool definitions for IPv6 subnets +/// Pool6 objects are created and stored in the given PoolStorage container. +/// +/// PdPool definitions currently support three elements: prefix, prefix-len, +/// and delegated-len, as shown in the example JSON text below: +/// +/// @code +/// +/// { +/// "prefix": "2001:db8:1::", +/// "prefix-len": 64, +/// "delegated-len": 128 +/// } +/// @endcode +/// +class PdPoolParser : public isc::data::SimpleParser { +public: + + /// @brief Constructor. + /// + PdPoolParser(); + + /// @brief Virtual destructor. + virtual ~PdPoolParser() { + } + + /// @brief Builds a prefix delegation pool from the given configuration + /// + /// This function parses configuration entries and creates an instance + /// of a dhcp::Pool6 configured for prefix delegation. + /// + /// @param pools storage container in which to store the parsed pool. + /// @param pd_pool_ pointer to an element that holds configuration entries + /// that define a prefix delegation pool. + /// + /// @throw DhcpConfigError if configuration parsing fails. + void parse(PoolStoragePtr pools, data::ConstElementPtr pd_pool_); + +protected: + + /// @brief Returns an instance of the @c OptionDataListParser to + /// be used in parsing the option-data structure. + /// + /// This function can be overridden in the child classes to supply + /// a custom parser for option data. + /// + /// @return an instance of the @c OptionDataListParser. + virtual boost::shared_ptr<OptionDataListParser> + createOptionDataListParser() const; + + /// Pointer to the created pool object. + isc::dhcp::Pool6Ptr pool_; + + /// A storage for pool specific option values. + CfgOptionPtr options_; + + /// @brief User context (optional, may be null) + /// + /// User context is arbitrary user data, to be used by hooks. + isc::data::ConstElementPtr user_context_; + + /// @brief Client class (a client has to belong to to use this pd-pool) + /// + /// If null, everyone is allowed. + isc::data::ConstElementPtr client_class_; +}; + +/// @brief Parser for a list of prefix delegation pools. +/// +/// This parser iterates over a list of prefix delegation pool entries and +/// creates pool instances for each one. If the parsing is successful, the +/// collection of pools is committed to the provided storage. +class PdPoolsListParser : public isc::data::SimpleParser { +public: + + /// @brief Virtual destructor. + virtual ~PdPoolsListParser() { + } + + /// @brief Parse configuration entries. + /// + /// This function parses configuration entries and creates instances + /// of prefix delegation pools . + /// + /// @param pools is the pool storage in which to store the parsed + /// @param pd_pool_list pointer to an element that holds entries + /// that define a prefix delegation pool. + /// + /// @throw DhcpConfigError if configuration parsing fails. + void parse(PoolStoragePtr pools, data::ConstElementPtr pd_pool_list); + +protected: + + /// @brief Returns an instance of the @c PdPoolParser to be used in + /// parsing the prefix delegation pools. + /// + /// This function can be overridden in the child classes to supply + /// a custom parser for the pools. + /// + /// @return an instance of the @c PdPool6Parser. + virtual boost::shared_ptr<PdPoolParser> + createPdPoolConfigParser() const; +}; + +/// @anchor Subnet6ConfigParser +/// @brief This class parses a single IPv6 subnet. +/// +/// This is the IPv6 derivation of the SubnetConfigParser class and it parses +/// the whole subnet definition. It creates parsersfor received configuration +/// parameters as needed. +class Subnet6ConfigParser : public SubnetConfigParser { +public: + + /// @brief Constructor + /// + /// stores global scope parameters, options, option definitions. + /// + /// @param check_iface Check if the specified interface exists in + /// the system. + Subnet6ConfigParser(bool check_iface = true); + + /// @brief Parses a single IPv6 subnet configuration and adds to the + /// Configuration Manager. + /// + /// @param subnet A new subnet being configured. + /// @return a pointer to created Subnet6 object + Subnet6Ptr parse(data::ConstElementPtr subnet); + +protected: + /// @brief Issues a DHCP6 server specific warning regarding duplicate subnet + /// options. + /// + /// @param code is the numeric option code of the duplicate option + /// @param addr is the subnet address + /// @todo A means to know the correct logger and perhaps a common + /// message would allow this message to be emitted by the base class. + virtual void duplicate_option_warning(uint32_t code, + asiolink::IOAddress& addr); + + /// @brief Instantiates the IPv6 Subnet based on a given IPv6 address + /// and prefix length. + /// + /// @param params Data structure describing a subnet. + /// @param addr is IPv6 prefix of the subnet. + /// @param len is the prefix length + void initSubnet(isc::data::ConstElementPtr params, + isc::asiolink::IOAddress addr, uint8_t len); + + /// @brief Verifies host reservation addresses are in the subnet range + /// + /// @param subnet pointer to the subnet + /// @param host pointer to the host reservation + /// @throw DhcpConfigError when an address is not in the subnet range. + void validateResvs(const Subnet6Ptr& subnet, ConstHostPtr host); + + /// @brief Returns an instance of the @c Pools6ListParser to be used + /// in parsing the address pools. + /// + /// This function can be overridden in the child classes to supply + /// a custom parser for the pools. + /// + /// @return an instance of the @c Pools6ListParser. + virtual boost::shared_ptr<PoolsListParser> + createPoolsListParser() const; + + /// @brief Returns an instance of the @c PdPools6ListParser to be used + /// in parsing the prefix delegation pools. + /// + /// This function can be overridden in the child classes to supply + /// a custom parser for the pools. + /// + /// @return an instance of the @c PdPools6ListParser. + virtual boost::shared_ptr<PdPoolsListParser> + createPdPoolsListParser() const; +}; + + +/// @brief this class parses a list of DHCP6 subnets +/// +/// This is a wrapper parser that handles the whole list of Subnet6 +/// definitions. It iterates over all entries and creates Subnet6ConfigParser +/// for each entry. +class Subnets6ListConfigParser : public isc::data::SimpleParser { +public: + + /// @brief constructor + /// + /// @param check_iface Check if the specified interface exists in + /// the system. + Subnets6ListConfigParser(bool check_iface = true); + + /// @brief Virtual destructor. + virtual ~Subnets6ListConfigParser() { + } + + /// @brief parses contents of the list + /// + /// Iterates over all entries on the list, parses its content + /// (by instantiating Subnet6ConfigParser) and adds to specified + /// configuration. + /// + /// @param cfg configuration (parsed subnets will be stored here) + /// @param subnets_list pointer to a list of IPv6 subnets + /// @throw DhcpConfigError if CfgMgr rejects the subnet (e.g. subnet-id is a duplicate) + size_t parse(SrvConfigPtr cfg, data::ConstElementPtr subnets_list); + + /// @brief Parses contents of the subnet6 list. + /// + /// @param [out] subnets Container where parsed subnets will be stored. + /// @param subnets_list pointer to a list of IPv6 subnets + /// @return Number of subnets created. + size_t parse(Subnet6Collection& subnets, + data::ConstElementPtr subnets_list); + +protected: + + /// @brief Returns an instance of the @c Subnet6ConfigParser to be + /// used in parsing the subnets. + /// + /// This function can be overridden in the child classes to supply + /// a custom parser for the subnets. + /// + /// @return an instance of the @c Subnet6ConfigParser. + virtual boost::shared_ptr<Subnet6ConfigParser> createSubnetConfigParser() const; + + /// Check if the specified interface exists in the system. + bool check_iface_; +}; + +/// @brief Parser for D2ClientConfig +/// +/// This class parses the configuration element "dhcp-ddns" common to the +/// config files for both dhcp4 and dhcp6. It creates an instance of a +/// D2ClientConfig. +class D2ClientConfigParser : public isc::data::SimpleParser { +public: + + /// @brief Parses a given dhcp-ddns element into D2ClientConfig. + /// + /// @param d2_client_cfg is the "dhcp-ddns" configuration to parse + /// + /// The elements currently supported are (see isc::dhcp::D2ClientConfig + /// for details on each): + /// -# enable-updates + /// -# server-ip + /// -# server-port + /// -# sender-ip + /// -# sender-port + /// -# max-queue-size + /// -# ncr-protocol + /// -# ncr-format + /// + /// @return returns a pointer to newly created D2ClientConfig. + D2ClientConfigPtr parse(isc::data::ConstElementPtr d2_client_cfg); + + /// @brief Defaults for the D2 client configuration. + static const isc::data::SimpleDefaults D2_CLIENT_CONFIG_DEFAULTS; + + /// @brief Sets all defaults for D2 client configuration. + /// + /// This method sets defaults value. It must not be called + /// before the short cut disabled updates condition was checked. + /// + /// @param d2_config d2 client configuration (will be const cast + // to ElementPtr) + /// @return number of parameters inserted + static size_t setAllDefaults(isc::data::ConstElementPtr d2_config); + +private: + + /// @brief Returns a value converted to NameChangeProtocol + /// + /// Instantiation of getAndConvert() to NameChangeProtocol + /// + /// @param scope specified parameter will be extracted from this scope + /// @param name name of the parameter + /// @return a NameChangeProtocol value + dhcp_ddns::NameChangeProtocol + getProtocol(isc::data::ConstElementPtr scope, const std::string& name); + + /// @brief Returns a value converted to NameChangeFormat + /// + /// Instantiation of getAndConvert() to NameChangeFormat + /// + /// @param scope specified parameter will be extracted from this scope + /// @param name name of the parameter + /// @return a NameChangeFormat value + dhcp_ddns::NameChangeFormat + getFormat(isc::data::ConstElementPtr scope, const std::string& name); + + /// @brief Returns a value converted to ReplaceClientNameMode + /// + /// Instantiation of getAndConvert() to ReplaceClientNameMode + /// + /// @param scope specified parameter will be extracted from this scope + /// @param name name of the parameter + /// @return a NameChangeFormat value + D2ClientConfig::ReplaceClientNameMode + getMode(isc::data::ConstElementPtr scope, const std::string& name); +}; + +} // end of isc::dhcp namespace +} // end of isc namespace + +#endif // DHCP_PARSERS_H diff --git a/src/lib/dhcpsrv/parsers/dhcp_queue_control_parser.cc b/src/lib/dhcpsrv/parsers/dhcp_queue_control_parser.cc new file mode 100644 index 0000000..a07e1e2 --- /dev/null +++ b/src/lib/dhcpsrv/parsers/dhcp_queue_control_parser.cc @@ -0,0 +1,59 @@ +// Copyright (C) 2015-2020 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 <cc/data.h> +#include <dhcpsrv/cfgmgr.h> +#include <dhcpsrv/dhcpsrv_log.h> +#include <dhcpsrv/parsers/dhcp_queue_control_parser.h> +#include <util/multi_threading_mgr.h> +#include <boost/foreach.hpp> +#include <string> +#include <sys/types.h> + +using namespace isc::data; +using namespace isc::util; + +namespace isc { +namespace dhcp { + +ElementPtr +DHCPQueueControlParser::parse(const ConstElementPtr& control_elem) { + // All we really do here is verify that it is a map that + // contains at least queue-type. All other content depends + // on the packet queue implementation of that type. + if (control_elem->getType() != Element::map) { + isc_throw(DhcpConfigError, "dhcp-queue-control must be a map"); + } + + // enable-queue is mandatory. + bool enable_queue = getBoolean(control_elem, "enable-queue"); + + if (enable_queue) { + ConstElementPtr elem = control_elem->get("queue-type"); + if (!elem) { + isc_throw(DhcpConfigError, "when queue is enabled, queue-type is required"); + } else { + if (elem->getType() != Element::string) { + isc_throw(DhcpConfigError, "queue-type must be a string"); + } + } + } + + // Return a copy of it. + ElementPtr result = data::copy(control_elem); + + // Currently not compatible with multi-threading. + if (MultiThreadingMgr::instance().getMode()) { + // Silently disable it. + result->set("enable-queue", Element::create(false)); + } + + return (result); +} + +} // end of namespace isc::dhcp +} // end of namespace isc diff --git a/src/lib/dhcpsrv/parsers/dhcp_queue_control_parser.h b/src/lib/dhcpsrv/parsers/dhcp_queue_control_parser.h new file mode 100644 index 0000000..6638749 --- /dev/null +++ b/src/lib/dhcpsrv/parsers/dhcp_queue_control_parser.h @@ -0,0 +1,57 @@ +// Copyright (C) 2018 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/. + +#ifndef DHCP_QUEUE_CONTROL_PARSER_H +#define DHCP_QUEUE_CONTROL_PARSER_H + +#include <cc/data.h> +#include <cc/simple_parser.h> +#include <dhcpsrv/parsers/dhcp_parsers.h> + +namespace isc { +namespace dhcp { + +/// @brief Parser for the configuration of DHCP packet queue controls +/// +/// This parser parses the "dhcp-queue-control" parameter which holds the +/// the configurable parameters that tailor DHCP packet queue behavior. +/// In order to provide wide latitude to packet queue implementators, +/// 'dhcp-queue-control' is mostly treated as a map of arbitrary values. +/// There is only mandatory value, 'enable-queue', which enables/disables +/// DHCP packet queueing. If this value is true, then the content must +/// also include a value for 'queue-type'. Beyond these values, the +/// map may contain any combination of valid JSON elements. +/// +/// Unlike most other parsers, this parser primarily serves to validate +/// the aforementioned rules, and rather than instantiate an object as +/// a result, it simply returns a copy original map of elements. +/// +/// This parser is used in both DHCPv4 and DHCPv6. Derived parsers +/// are not needed. +class DHCPQueueControlParser : public isc::data::SimpleParser { +public: + + /// @brief Constructor + /// + DHCPQueueControlParser(){}; + + /// @brief Parses content of the "dhcp-queue-control". + /// + /// @param control_elem MapElement containing the queue control values to + /// parse + /// + /// @return A copy of the of the input MapElement. + /// + /// @throw DhcpConfigError if any of the values are invalid. + data::ElementPtr parse(const isc::data::ConstElementPtr& control_elem); + +private: +}; + +} +} // end of namespace isc + +#endif // DHCP_QUEUE_CONTROL_PARSER_H diff --git a/src/lib/dhcpsrv/parsers/duid_config_parser.cc b/src/lib/dhcpsrv/parsers/duid_config_parser.cc new file mode 100644 index 0000000..5817baf --- /dev/null +++ b/src/lib/dhcpsrv/parsers/duid_config_parser.cc @@ -0,0 +1,95 @@ +// Copyright (C) 2015,2017 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 <cc/data.h> +#include <dhcp/duid.h> +#include <dhcpsrv/cfg_duid.h> +#include <dhcpsrv/cfgmgr.h> +#include <dhcpsrv/dhcpsrv_log.h> +#include <dhcpsrv/parsers/duid_config_parser.h> +#include <dhcpsrv/parsers/dhcp_parsers.h> +#include <exceptions/exceptions.h> +#include <boost/foreach.hpp> +#include <boost/lexical_cast.hpp> +#include <limits> +#include <string> + +using namespace isc::data; + +namespace isc { +namespace dhcp { + +void +DUIDConfigParser::parse(const CfgDUIDPtr& cfg, + isc::data::ConstElementPtr duid_configuration) { + if (!cfg) { + // Sanity check + isc_throw(DhcpConfigError, "Must provide valid pointer to cfg when parsing duid"); + } + + std::string param; + try { + param = "type"; + std::string duid_type = getString(duid_configuration, "type"); + // Map DUID type represented as text into numeric value. + DUID::DUIDType numeric_type = DUID::DUID_UNKNOWN; + if (duid_type == "LLT") { + numeric_type = DUID::DUID_LLT; + } else if (duid_type == "EN") { + numeric_type = DUID::DUID_EN; + } else if (duid_type == "LL") { + numeric_type = DUID::DUID_LL; + } else { + isc_throw(BadValue, "unsupported DUID type '" + << duid_type << "'. Expected: LLT, EN or LL"); + } + + cfg->setType(static_cast<DUID::DUIDType>(numeric_type)); + + param = "identifier"; + if (duid_configuration->contains(param)) { + cfg->setIdentifier(getString(duid_configuration, param)); + } + + param = "htype"; + if (duid_configuration->contains(param)) { + cfg->setHType(getUint16(duid_configuration, param)); + } + + param = "time"; + if (duid_configuration->contains(param)) { + cfg->setTime(getUint32(duid_configuration, param)); + } + + param = "enterprise-id"; + if (duid_configuration->contains(param)) { + cfg->setEnterpriseId(getUint32(duid_configuration, param)); + } + + param = "persist"; + if (duid_configuration->contains(param)) { + cfg->setPersist(getBoolean(duid_configuration, param)); + } + + param = "user-context"; + ConstElementPtr user_context = duid_configuration->get("user-context"); + if (user_context) { + cfg->setContext(user_context); + } + } catch (const DhcpConfigError&) { + throw; + } catch (const std::exception& ex) { + // Append position. + isc_throw(DhcpConfigError, ex.what() << " (" + << getPosition(param, duid_configuration) << ")"); + } + + LOG_WARN(dhcpsrv_logger, DHCPSRV_CFGMGR_CONFIGURE_SERVERID); +} + +} // end of namespace isc::dhcp +} // end of namespace isc diff --git a/src/lib/dhcpsrv/parsers/duid_config_parser.h b/src/lib/dhcpsrv/parsers/duid_config_parser.h new file mode 100644 index 0000000..8780031 --- /dev/null +++ b/src/lib/dhcpsrv/parsers/duid_config_parser.h @@ -0,0 +1,41 @@ +// Copyright (C) 2015,2017 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/. + +#ifndef DUID_CONFIG_PARSER_H +#define DUID_CONFIG_PARSER_H + +#include <cc/data.h> +#include <cc/simple_parser.h> +#include <stdint.h> +#include <string> + +namespace isc { +namespace dhcp { + +/// @brief Parser for server DUID configuration. +/// +/// This parser currently supports the following DUID types: +/// - DUID-LLT, +/// - DUID-EN +/// - DUID-LL +/// +/// @todo Add support for DUID-UUID in the parser. +class DUIDConfigParser : public isc::data::SimpleParser { +public: + /// @brief Parses DUID configuration. + /// + /// @param cfg parsed DUID configuration will be stored here + /// @param duid_configuration Data element holding a map representing + /// DUID configuration. + /// + /// @throw DhcpConfigError If the configuration is invalid. + void parse(const CfgDUIDPtr& cfg, isc::data::ConstElementPtr duid_configuration); +}; + +} +} // end of namespace isc + +#endif // DUID_CONFIG_PARSER_H diff --git a/src/lib/dhcpsrv/parsers/expiration_config_parser.cc b/src/lib/dhcpsrv/parsers/expiration_config_parser.cc new file mode 100644 index 0000000..3bbe333 --- /dev/null +++ b/src/lib/dhcpsrv/parsers/expiration_config_parser.cc @@ -0,0 +1,69 @@ +// Copyright (C) 2015-2019 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 <cc/data.h> +#include <dhcpsrv/cfg_expiration.h> +#include <dhcpsrv/cfgmgr.h> +#include <dhcpsrv/parsers/expiration_config_parser.h> +#include <dhcpsrv/parsers/dhcp_parsers.h> +#include <boost/foreach.hpp> + +using namespace isc::data; + +namespace isc { +namespace dhcp { + +void +ExpirationConfigParser::parse(ConstElementPtr expiration_config) { + CfgExpirationPtr cfg = CfgMgr::instance().getStagingCfg()->getCfgExpiration(); + + std::string param; + + try { + param = "reclaim-timer-wait-time"; + if (expiration_config->contains(param)) { + cfg->setReclaimTimerWaitTime(getInteger(expiration_config, param)); + } + + param = "flush-reclaimed-timer-wait-time"; + if (expiration_config->contains(param)) { + cfg->setFlushReclaimedTimerWaitTime(getInteger(expiration_config, + param)); + } + + param = "hold-reclaimed-time"; + if (expiration_config->contains(param)) { + cfg->setHoldReclaimedTime(getInteger(expiration_config, param)); + } + + param = "max-reclaim-leases"; + if (expiration_config->contains(param)) { + cfg->setMaxReclaimLeases(getInteger(expiration_config, param)); + } + + param = "max-reclaim-time"; + if (expiration_config->contains(param)) { + cfg->setMaxReclaimTime(getInteger(expiration_config, param)); + } + + param = "unwarned-reclaim-cycles"; + if (expiration_config->contains(param)) { + cfg->setUnwarnedReclaimCycles( + getInteger(expiration_config, param)); + } + } catch (const DhcpConfigError&) { + throw; + } catch (const std::exception& ex) { + // Append position of the configuration parameter to the error message. + isc_throw(DhcpConfigError, ex.what() << " (" + << getPosition(param, expiration_config) << ")"); + } +} + +} // end of namespace isc::dhcp +} // end of namespace isc diff --git a/src/lib/dhcpsrv/parsers/expiration_config_parser.h b/src/lib/dhcpsrv/parsers/expiration_config_parser.h new file mode 100644 index 0000000..44ba77d --- /dev/null +++ b/src/lib/dhcpsrv/parsers/expiration_config_parser.h @@ -0,0 +1,59 @@ +// Copyright (C) 2015,2017 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/. + +#ifndef EXPIRATION_CONFIG_PARSER_H +#define EXPIRATION_CONFIG_PARSER_H + +#include <cc/data.h> +#include <cc/simple_parser.h> + +namespace isc { +namespace dhcp { + + +/// @brief Parser for the configuration parameters pertaining to the +/// processing of expired leases. +/// +/// This parser iterates over parameters stored in the map and tries to +/// set the appropriate values in the @c CfgExpiration object of the +/// Configuration Manager. +/// +/// Currently supported parameters are: +/// - reclaim-timer-wait-time, +/// - flush-reclaimed-timer-wait-time, +/// - hold-reclaimed-time, +/// - max-reclaim-leases, +/// - max-reclaim-time, +/// - unwarned-reclaim-cycles. +/// +/// These parameters are optional and the default values are used for +/// those that aren't specified. +/// +/// The parser checks if the values of the specified parameters are within +/// the allowed ranges and throws exception if they aren't. Each parameter +/// has a corresponding maximum value defined in the @c CfgExpiration class. +/// None of them may be negative. +class ExpirationConfigParser : public isc::data::SimpleParser { +public: + + /// @brief Destructor. + virtual ~ExpirationConfigParser() { } + + /// @brief Parses parameters in the JSON map, pertaining to the processing + /// of the expired leases. + /// + /// @param expiration_config pointer to the content of parsed values + /// + /// @throw DhcpConfigError if unknown parameter specified or the + /// parameter contains invalid value.. + void parse(isc::data::ConstElementPtr expiration_config); + +}; + +} // end of namespace isc::dhcp +} // end of namespace isc + +#endif // EXPIRATION_CONFIG_PARSER_H diff --git a/src/lib/dhcpsrv/parsers/host_reservation_parser.cc b/src/lib/dhcpsrv/parsers/host_reservation_parser.cc new file mode 100644 index 0000000..38a8915 --- /dev/null +++ b/src/lib/dhcpsrv/parsers/host_reservation_parser.cc @@ -0,0 +1,437 @@ +// Copyright (C) 2014-2018 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 <asiolink/io_address.h> +#include <dhcpsrv/cfgmgr.h> +#include <dhcpsrv/parsers/host_reservation_parser.h> +#include <dhcpsrv/parsers/option_data_parser.h> +#include <boost/foreach.hpp> +#include <boost/lexical_cast.hpp> +#include <algorithm> +#include <sys/socket.h> +#include <sstream> +#include <string> + +using namespace isc::asiolink; +using namespace isc::data; + +namespace { + +/// @brief Returns set of the supported parameters for DHCPv4. +/// +/// This function returns the set of supported parameters for +/// host reservation in DHCPv4. +/// +/// @param identifiers_only Indicates if the function should only +/// return supported host identifiers (if true) or all supported +/// parameters (if false). +const std::set<std::string>& +getSupportedParams4(const bool identifiers_only = false) { + // Holds set of host identifiers. + static std::set<std::string> identifiers_set; + // Holds set of all supported parameters, including identifiers. + static std::set<std::string> params_set; + // If this is first execution of this function, we need + // to initialize the set. + if (identifiers_set.empty()) { + identifiers_set.insert("hw-address"); + identifiers_set.insert("duid"); + identifiers_set.insert("circuit-id"); + identifiers_set.insert("client-id"); + identifiers_set.insert("flex-id"); + } + // Copy identifiers and add all other parameters. + if (params_set.empty()) { + params_set = identifiers_set; + params_set.insert("hostname"); + params_set.insert("ip-address"); + params_set.insert("option-data"); + params_set.insert("next-server"); + params_set.insert("server-hostname"); + params_set.insert("boot-file-name"); + params_set.insert("client-classes"); + params_set.insert("user-context"); + } + return (identifiers_only ? identifiers_set : params_set); +} + +/// @brief Returns set of the supported parameters for DHCPv6. +/// +/// This function returns the set of supported parameters for +/// host reservation in DHCPv6. +/// +/// @param identifiers_only Indicates if the function should only +/// return supported host identifiers (if true) or all supported +/// parameters (if false). +const std::set<std::string>& +getSupportedParams6(const bool identifiers_only = false) { + // Holds set of host identifiers. + static std::set<std::string> identifiers_set; + // Holds set of all supported parameters, including identifiers. + static std::set<std::string> params_set; + // If this is first execution of this function, we need + // to initialize the set. + if (identifiers_set.empty()) { + identifiers_set.insert("hw-address"); + identifiers_set.insert("duid"); + identifiers_set.insert("flex-id"); + } + // Copy identifiers and add all other parameters. + if (params_set.empty()) { + params_set = identifiers_set; + params_set.insert("hostname"); + params_set.insert("ip-addresses"); + params_set.insert("prefixes"); + params_set.insert("option-data"); + params_set.insert("client-classes"); + params_set.insert("user-context"); + } + return (identifiers_only ? identifiers_set : params_set); +} + +} + +namespace isc { +namespace dhcp { + +HostPtr +HostReservationParser::parse(const SubnetID& subnet_id, + isc::data::ConstElementPtr reservation_data) { + return (parseInternal(subnet_id, reservation_data)); +} + +HostPtr +HostReservationParser::parseInternal(const SubnetID&, + isc::data::ConstElementPtr reservation_data) { + std::string identifier; + std::string identifier_name; + std::string hostname; + ConstElementPtr user_context; + HostPtr host; + + try { + // Gather those parameters that are common for both IPv4 and IPv6 + // reservations. + BOOST_FOREACH(auto element, reservation_data->mapValue()) { + // Check if we support this parameter. + if (!isSupportedParameter(element.first)) { + isc_throw(DhcpConfigError, "unsupported configuration" + " parameter '" << element.first << "'"); + } + + if (isIdentifierParameter(element.first)) { + if (!identifier.empty()) { + isc_throw(DhcpConfigError, "the '" << element.first + << "' and '" << identifier_name + << "' are mutually exclusive"); + } + identifier = element.second->stringValue(); + identifier_name = element.first; + + } else if (element.first == "hostname") { + hostname = element.second->stringValue(); + } else if (element.first == "user-context") { + user_context = element.second; + } + } + + // Host identifier is a must. + if (identifier_name.empty()) { + // If there is no identifier specified, we have to display an + // error message and include the information what identifiers + // are supported. + std::ostringstream s; + BOOST_FOREACH(std::string param_name, getSupportedParameters(true)) { + if (s.tellp() != std::streampos(0)) { + s << ", "; + } + s << param_name; + } + isc_throw(DhcpConfigError, "one of the supported identifiers must" + " be specified for host reservation: " + << s.str()); + + } + + // Create a host object from the basic parameters we already parsed. + host.reset(new Host(identifier, identifier_name, SUBNET_ID_UNUSED, + SUBNET_ID_UNUSED, IOAddress("0.0.0.0"), hostname)); + + // Add user context + if (user_context) { + host->setContext(user_context); + } + } catch (const std::exception& ex) { + // Append line number where the error occurred. + isc_throw(DhcpConfigError, ex.what() << " (" + << reservation_data->getPosition() << ")"); + } + + return (host); +} + +bool +HostReservationParser::isIdentifierParameter(const std::string& param_name) const { + return (getSupportedParameters(true).count(param_name) > 0); +} + +bool +HostReservationParser::isSupportedParameter(const std::string& param_name) const { + return (getSupportedParameters(false).count(param_name) > 0); +} + +HostPtr +HostReservationParser4::parseInternal(const SubnetID& subnet_id, + isc::data::ConstElementPtr reservation_data) { + HostPtr host = HostReservationParser::parseInternal(subnet_id, reservation_data); + + host->setIPv4SubnetID(subnet_id); + + BOOST_FOREACH(auto element, reservation_data->mapValue()) { + // For 'option-data' element we will use another parser which + // already returns errors with position appended, so don't + // surround it with try-catch. + if (element.first == "option-data") { + CfgOptionPtr cfg_option = host->getCfgOption4(); + + // This parser is converted to SimpleParser already. It + // parses the Element structure immediately, there's no need + // to go through build/commit phases. + OptionDataListParser parser(AF_INET); + parser.parse(cfg_option, element.second); + + // Everything else should be surrounded with try-catch to append + // position. + } else { + try { + if (element.first == "ip-address") { + host->setIPv4Reservation(IOAddress(element.second-> + stringValue())); + } else if (element.first == "next-server") { + host->setNextServer(IOAddress(element.second->stringValue())); + + } else if (element.first == "server-hostname") { + host->setServerHostname(element.second->stringValue()); + + } else if (element.first == "boot-file-name") { + host->setBootFileName(element.second->stringValue()); + + } else if (element.first == "client-classes") { + BOOST_FOREACH(ConstElementPtr class_element, + element.second->listValue()) { + host->addClientClass4(class_element->stringValue()); + } + } + + } catch (const std::exception& ex) { + // Append line number where the error occurred. + isc_throw(DhcpConfigError, ex.what() << " (" + << element.second->getPosition() << ")"); + } + } + } + + return (host); +} + +const std::set<std::string>& +HostReservationParser4::getSupportedParameters(const bool identifiers_only) const { + return (getSupportedParams4(identifiers_only)); +} + +HostPtr +HostReservationParser6::parseInternal(const SubnetID& subnet_id, + isc::data::ConstElementPtr reservation_data) { + HostPtr host = HostReservationParser::parseInternal(subnet_id, reservation_data); + + host->setIPv6SubnetID(subnet_id); + + BOOST_FOREACH(auto element, reservation_data->mapValue()) { + // Parse option values. Note that the configuration option parser + // returns errors with position information appended, so there is no + // need to surround it with try-clause (and rethrow with position + // appended). + if (element.first == "option-data") { + CfgOptionPtr cfg_option = host->getCfgOption6(); + + // This parser is converted to SimpleParser already. It + // parses the Element structure immediately, there's no need + // to go through build/commit phases. + OptionDataListParser parser(AF_INET6); + parser.parse(cfg_option, element.second); + + } else if (element.first == "ip-addresses" || element.first == "prefixes") { + BOOST_FOREACH(ConstElementPtr prefix_element, + element.second->listValue()) { + try { + // For the IPv6 address the prefix length is 128 and the + // value specified in the list is a reserved address. + IPv6Resrv::Type resrv_type = IPv6Resrv::TYPE_NA; + std::string prefix = prefix_element->stringValue(); + uint8_t prefix_len = 128; + + // If we're dealing with prefixes, instead of addresses, + // we will have to extract the prefix length from the value + // specified in the following format: 2001:db8:2000::/64. + if (element.first == "prefixes") { + // The slash is mandatory for prefixes. If there is no + // slash, return an error. + size_t len_pos = prefix.find('/'); + if (len_pos == std::string::npos) { + isc_throw(DhcpConfigError, "prefix reservation" + " requires prefix length be specified" + " in '" << prefix << "'"); + + // If there is nothing after the slash, we should also + // report an error. + } else if (len_pos >= prefix.length() - 1) { + isc_throw(DhcpConfigError, "prefix '" << prefix + << "' requires length after '/'"); + + } + + // Convert the prefix length from the string to the + // number. Note, that we don't use the uint8_t type + // as the lexical cast would expect a character, e.g. + // 'a', instead of prefix length, e.g. '64'. + try { + prefix_len = boost::lexical_cast< + unsigned int>(prefix.substr(len_pos + 1)); + + } catch (const boost::bad_lexical_cast&) { + isc_throw(DhcpConfigError, "prefix length value '" + << prefix.substr(len_pos + 1) + << "' is invalid"); + } + + // Remove the slash character and the prefix length + // from the parsed value. + prefix.erase(len_pos); + + // Finally, set the reservation type. + resrv_type = IPv6Resrv::TYPE_PD; + } + + // Create a reservation for an address or prefix. + host->addReservation(IPv6Resrv(resrv_type, + IOAddress(prefix), + prefix_len)); + + } catch (const std::exception& ex) { + // Append line number where the error occurred. + isc_throw(DhcpConfigError, ex.what() << " (" + << prefix_element->getPosition() << ")"); + } + } + + + } else if (element.first == "client-classes") { + try { + BOOST_FOREACH(ConstElementPtr class_element, + element.second->listValue()) { + host->addClientClass6(class_element->stringValue()); + } + } catch (const std::exception& ex) { + // Append line number where the error occurred. + isc_throw(DhcpConfigError, ex.what() << " (" + << element.second->getPosition() << ")"); + } + } + } + + return (host); +} + +const std::set<std::string>& +HostReservationParser6::getSupportedParameters(const bool identifiers_only) const { + return (getSupportedParams6(identifiers_only)); +} + +HostReservationIdsParser::HostReservationIdsParser() + : staging_cfg_() { +} + +void +HostReservationIdsParser::parse(isc::data::ConstElementPtr ids_list) { + parseInternal(ids_list); +} + +void +HostReservationIdsParser::parseInternal(isc::data::ConstElementPtr ids_list) { + // Remove existing identifier types. + staging_cfg_->clearIdentifierTypes(); + + BOOST_FOREACH(ConstElementPtr element, ids_list->listValue()) { + std::string id_name = element->stringValue(); + try { + if (id_name != "auto") { + if (!isSupportedIdentifier(id_name)) { + isc_throw(isc::BadValue, "unsupported identifier '" + << id_name << "'"); + } + staging_cfg_->addIdentifierType(id_name); + + } else { + // 'auto' is mutually exclusive with other values. If there + // are any values in the configuration already it means that + // some other values have already been specified. + if (!staging_cfg_->getIdentifierTypes().empty()) { + isc_throw(isc::BadValue, "if 'auto' keyword is used," + " no other values can be specified within '" + "host-reservation-identifiers' list"); + } + // Iterate over all identifier types and for those supported + // in a given context (DHCPv4 or DHCPv6) add the identifier type + // to the configuration. + for (unsigned int i = 0; + i <= static_cast<unsigned int>(Host::LAST_IDENTIFIER_TYPE); + ++i) { + std::string supported_id_name = + Host::getIdentifierName(static_cast<Host::IdentifierType>(i)); + if (isSupportedIdentifier(supported_id_name)) { + staging_cfg_->addIdentifierType(supported_id_name); + } + } + } + + } catch (const std::exception& ex) { + // Append line number where the error occurred. + isc_throw(DhcpConfigError, ex.what() << " (" + << element->getPosition() << ")"); + } + } + + // The parsed list must not be empty. + if (staging_cfg_->getIdentifierTypes().empty()) { + isc_throw(DhcpConfigError, "'host-reservation-identifiers' list must not" + " be empty (" << ids_list->getPosition() << ")"); + } + +} + +HostReservationIdsParser4::HostReservationIdsParser4() + : HostReservationIdsParser() { + staging_cfg_ = CfgMgr::instance().getStagingCfg()->getCfgHostOperations4(); +} + +bool +HostReservationIdsParser4::isSupportedIdentifier(const std::string& id_name) const { + return (getSupportedParams4(true).count(id_name) > 0); +} + +HostReservationIdsParser6::HostReservationIdsParser6() + : HostReservationIdsParser() { + staging_cfg_ = CfgMgr::instance().getStagingCfg()->getCfgHostOperations6(); +} + +bool +HostReservationIdsParser6::isSupportedIdentifier(const std::string& id_name) const { + return (getSupportedParams6(true).count(id_name) > 0); +} + +} // end of namespace isc::dhcp +} // end of namespace isc diff --git a/src/lib/dhcpsrv/parsers/host_reservation_parser.h b/src/lib/dhcpsrv/parsers/host_reservation_parser.h new file mode 100644 index 0000000..b6542c4 --- /dev/null +++ b/src/lib/dhcpsrv/parsers/host_reservation_parser.h @@ -0,0 +1,231 @@ +// Copyright (C) 2014-2017 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/. + +#ifndef HOST_RESERVATION_PARSER_H +#define HOST_RESERVATION_PARSER_H + +#include <cc/data.h> +#include <cc/simple_parser.h> +#include <dhcpsrv/host.h> + +namespace isc { +namespace dhcp { + +/// @brief Parser for a single host reservation entry. +class HostReservationParser : public isc::data::SimpleParser { +public: + + /// @brief Destructor. + virtual ~HostReservationParser() { } + + /// @brief Parses a single entry for host reservation. + /// + /// @param subnet_id Identifier of the subnet that the host is + /// connected to. + /// @param reservation_data Data element holding map with a host + /// reservation configuration. + /// + /// @return Pointer to the object representing parsed host. + /// @throw DhcpConfigError If the configuration is invalid. + virtual HostPtr + parse(const SubnetID& subnet_id, + isc::data::ConstElementPtr reservation_data) final; + +protected: + + /// @brief Parses a single entry for host reservation. + /// + /// This method is called by @ref parse and it can be overridden in the + /// derived classes to provide class specific parsing logic. + /// + /// @param subnet_id Identifier of the subnet that the host is + /// connected to. + /// @param reservation_data Data element holding map with a host + /// reservation configuration. + /// + /// @return Pointer to the object representing parsed host. + /// @throw DhcpConfigError If the configuration is invalid. + virtual HostPtr parseInternal(const SubnetID& subnet_id, + isc::data::ConstElementPtr reservation_data); + + /// @brief Checks if the specified parameter is a host identifier. + /// + /// @param param_name Parameter name. + /// + /// @return true if the parameter specifies host identifier, false + /// otherwise. + virtual bool isIdentifierParameter(const std::string& param_name) const; + + /// @brief Checks if the specified parameter is supported by the parser. + /// + /// @param param_name Parameter name. + /// + /// @return true if the parameter is supported, false otherwise. + virtual bool isSupportedParameter(const std::string& param_name) const; + + /// @brief Returns set of the supported parameters. + /// + /// @param identifiers_only Indicates if the function should only + /// return supported host identifiers (if true) or all supported + /// parameters (if false). + /// + /// @return Set of supported parameter names. + virtual const std::set<std::string>& + getSupportedParameters(const bool identifiers_only) const = 0; +}; + +/// @brief Parser for a single host reservation for DHCPv4. +class HostReservationParser4 : public HostReservationParser { +protected: + + /// @brief Parses a single host reservation for DHCPv4. + /// + /// @param subnet_id Identifier of the subnet that the host is + /// connected to. + /// @param reservation_data Data element holding map with a host + /// reservation configuration. + /// + /// @return Pointer to the object representing parsed host. + /// @throw DhcpConfigError If the configuration is invalid. + virtual HostPtr parseInternal(const SubnetID& subnet_id, + isc::data::ConstElementPtr reservation_data); + + /// @brief Returns set of the supported parameters for DHCPv4. + /// + /// @param identifiers_only Indicates if the function should only + /// return supported host identifiers (if true) or all supported + /// parameters (if false). + /// + /// @return Set of supported parameter names. + virtual const std::set<std::string>& + getSupportedParameters(const bool identifiers_only) const; +}; + +/// @brief Parser for a single host reservation for DHCPv6. +class HostReservationParser6 : public HostReservationParser { +protected: + + /// @brief Parses a single host reservation for DHCPv6. + /// + /// @param subnet_id Identifier of the subnet that the host is + /// connected to. + /// @param reservation_data Data element holding map with a host + /// reservation configuration. + /// + /// @return Pointer to the object representing parsed host. + /// @throw DhcpConfigError If the configuration is invalid. + virtual HostPtr parseInternal(const SubnetID& subnet_id, + isc::data::ConstElementPtr reservation_data); + + /// @brief Returns set of the supported parameters for DHCPv6. + /// + /// @param identifiers_only Indicates if the function should only + /// return supported host identifiers (if true) or all supported + /// parameters (if false). + /// + /// @return Set of supported parameter names. + virtual const std::set<std::string>& + getSupportedParameters(const bool identifiers_only) const; + +}; + +/// @brief Parser for a list of host identifiers. +/// +/// This is a parent parser class for parsing "host-reservation-identifiers" +/// global configuration parameter. The DHCPv4 and DHCPv6 specific parsers +/// derive from this class. +class HostReservationIdsParser : public isc::data::SimpleParser { +public: + + /// @brief Constructor. + HostReservationIdsParser(); + + /// @brief Destructor. + virtual ~HostReservationIdsParser() { } + + /// @brief Parses a list of host identifiers. + /// + /// @param ids_list Data element pointing to an ordered list of host + /// identifier names. + /// + /// @throw DhcpConfigError If specified configuration is invalid. + void parse(isc::data::ConstElementPtr ids_list); + +protected: + + /// @brief Parses a list of host identifiers. + /// + /// This method is called by @ref parse and it can be overridden in the + /// derived classes to provide class specific parsing logic. + /// + /// @param ids_list Data element pointing to an ordered list of host + /// identifier names. + /// + /// @throw DhcpConfigError If specified configuration is invalid. + virtual void parseInternal(isc::data::ConstElementPtr ids_list); + + /// @brief Checks if specified identifier name is supported in the + /// context of the parser. + /// + /// This is abstract method which must be implemented in the derived + /// parser classes for DHCPv4 and DHCPv6. + /// + /// @param id_name Identifier name. + /// @return true if the specified identifier is supported, false + /// otherwise. + virtual bool isSupportedIdentifier(const std::string& id_name) const = 0; + + /// @brief Pointer to the object holding configuration. + CfgHostOperationsPtr staging_cfg_; + +}; + +/// @brief Parser for a list of host identifiers for DHCPv4. +class HostReservationIdsParser4 : public HostReservationIdsParser { +public: + + /// @brief Constructor. + /// + /// Initializes staging configuration pointer to the one used for DHCPv4 + /// configuration. + HostReservationIdsParser4(); + +protected: + + /// @brief Checks if specified identifier name is supported for DHCPv4. + /// + /// @param id_name Identifier name. + /// @return true if the specified identifier is supported, false + /// otherwise. + virtual bool isSupportedIdentifier(const std::string& id_name) const; + +}; + +/// @brief Parser for a list of host identifiers for DHCPv6. +class HostReservationIdsParser6 : public HostReservationIdsParser { +public: + + /// @brief Constructor. + /// + /// Initializes staging configuration pointer to the one used for DHCPv6 + /// configuration. + HostReservationIdsParser6(); + +protected: + + /// @brief Checks if specified identifier name is supported for DHCPv6. + /// + /// @param id_name Identifier name. + /// @return true if the specified identifier is supported, false + /// otherwise. + virtual bool isSupportedIdentifier(const std::string& id_name) const; +}; + + +} +} // end of namespace isc + +#endif // HOST_RESERVATION_PARSER_H diff --git a/src/lib/dhcpsrv/parsers/host_reservations_list_parser.h b/src/lib/dhcpsrv/parsers/host_reservations_list_parser.h new file mode 100644 index 0000000..9f6ce2f --- /dev/null +++ b/src/lib/dhcpsrv/parsers/host_reservations_list_parser.h @@ -0,0 +1,53 @@ +// Copyright (C) 2014-2017 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/. + +#ifndef HOST_RESERVATIONS_LIST_PARSER_H +#define HOST_RESERVATIONS_LIST_PARSER_H + +#include <cc/data.h> +#include <cc/simple_parser.h> +#include <dhcpsrv/host.h> +#include <dhcpsrv/subnet_id.h> +#include <boost/foreach.hpp> + +namespace isc { +namespace dhcp { + +/// @brief Parser for a list of host reservations for a subnet. +/// +/// @tparam HostReservationParserType Host reservation parser to be used to +/// parse individual reservations: @c HostReservationParser4 or +/// @c HostReservationParser6. +template<typename HostReservationParserType> +class HostReservationsListParser : public isc::data::SimpleParser { +public: + + /// @brief Parses a list of host reservation entries for a subnet. + /// + /// @param subnet_id Identifier of the subnet to which the reservations + /// belong. + /// @param hr_list Data element holding a list of host reservations. + /// Each host reservation is described by a map object. + /// @param [out] hosts_list Hosts representing parsed reservations are stored + /// in this list. + /// + /// @throw DhcpConfigError If the configuration if any of the reservations + /// is invalid. + void parse(const SubnetID& subnet_id, isc::data::ConstElementPtr hr_list, + HostCollection& hosts_list) { + HostCollection hosts; + BOOST_FOREACH(data::ConstElementPtr reservation, hr_list->listValue()) { + HostReservationParserType parser; + hosts.push_back(parser.parse(subnet_id, reservation)); + } + hosts_list.swap(hosts); + } +}; + +} +} + +#endif // HOST_RESERVATIONS_LIST_PARSER_H diff --git a/src/lib/dhcpsrv/parsers/ifaces_config_parser.cc b/src/lib/dhcpsrv/parsers/ifaces_config_parser.cc new file mode 100644 index 0000000..adc821f --- /dev/null +++ b/src/lib/dhcpsrv/parsers/ifaces_config_parser.cc @@ -0,0 +1,131 @@ +// 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 <cc/data.h> +#include <dhcpsrv/cfgmgr.h> +#include <dhcpsrv/dhcpsrv_log.h> +#include <dhcpsrv/parsers/ifaces_config_parser.h> +#include <boost/foreach.hpp> +#include <string> +#include <sys/types.h> + +using namespace isc::data; + +namespace isc { +namespace dhcp { + +void +IfacesConfigParser::parseInterfacesList(const CfgIfacePtr& cfg_iface, + ConstElementPtr ifaces_list) { + BOOST_FOREACH(ConstElementPtr iface, ifaces_list->listValue()) { + std::string iface_name = iface->stringValue(); + try { + cfg_iface->use(protocol_, iface_name); + + } catch (const std::exception& ex) { + isc_throw(DhcpConfigError, "Failed to select interface: " + << ex.what() << " (" << iface->getPosition() << ")"); + } + } +} + +IfacesConfigParser::IfacesConfigParser(const uint16_t protocol, bool test_mode) + : protocol_(protocol), test_mode_(test_mode) { +} + +void +IfacesConfigParser::parse(const CfgIfacePtr& cfg, + const isc::data::ConstElementPtr& ifaces_config) { + + // Check for re-detect before calling parseInterfacesList() + bool re_detect = getBoolean(ifaces_config, "re-detect"); + cfg->setReDetect(re_detect); + if (re_detect && !test_mode_) { + IfaceMgr::instance().clearIfaces(); + IfaceMgr::instance().detectIfaces(); + } + + bool socket_type_specified = false; + BOOST_FOREACH(ConfigPair element, ifaces_config->mapValue()) { + try { + if (element.first == "re-detect") { + continue; + } + + if (element.first == "interfaces") { + parseInterfacesList(cfg, element.second); + continue; + + } + + if (element.first == "dhcp-socket-type") { + if (protocol_ == AF_INET) { + cfg->useSocketType(AF_INET, element.second->stringValue()); + socket_type_specified = true; + continue; + } else { + isc_throw(DhcpConfigError, + "dhcp-socket-type is not supported in DHCPv6"); + } + } + + if (element.first == "outbound-interface") { + if (protocol_ == AF_INET) { + CfgIface::OutboundIface type = + CfgIface::textToOutboundIface(element.second->stringValue()); + cfg->setOutboundIface(type); + continue; + } else { + isc_throw(DhcpConfigError, + "outbound-interface is not supported in DHCPv6"); + } + } + + if (element.first == "service-sockets-require-all") { + cfg->setServiceSocketsRequireAll(element.second->boolValue()); + continue; + } + + if (element.first == "service-sockets-retry-wait-time") { + cfg->setServiceSocketsRetryWaitTime(static_cast<uint32_t>(element.second->intValue())); + continue; + } + + if (element.first == "service-sockets-max-retries") { + cfg->setServiceSocketsMaxRetries(static_cast<uint32_t>(element.second->intValue())); + continue; + } + + if (element.first == "user-context") { + cfg->setContext(element.second); + continue; + } + + // This should never happen as the input produced by the parser + // see (src/bin/dhcpX/dhcpX_parser.yy) should not produce any + // other parameter, so this case is only to catch bugs in + // the parser. + isc_throw(DhcpConfigError, "unsupported parameter '" + << element.first << "'"); + } catch (const std::exception& ex) { + // Append line number where the error occurred. + isc_throw(DhcpConfigError, ex.what() << " (" + << element.second->getPosition() << ")"); + } + } + + // User hasn't specified the socket type. Log that we are using + // the default type. Log it only if this is DHCPv4. (DHCPv6 does not use + // raw sockets). + if (!socket_type_specified && (protocol_ == AF_INET) ) { + LOG_INFO(dhcpsrv_logger, DHCPSRV_CFGMGR_SOCKET_TYPE_DEFAULT) + .arg(cfg->socketTypeToText()); + } +} + +} // end of namespace isc::dhcp +} // end of namespace isc diff --git a/src/lib/dhcpsrv/parsers/ifaces_config_parser.h b/src/lib/dhcpsrv/parsers/ifaces_config_parser.h new file mode 100644 index 0000000..9eb25cb --- /dev/null +++ b/src/lib/dhcpsrv/parsers/ifaces_config_parser.h @@ -0,0 +1,70 @@ +// Copyright (C) 2015-2020 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/. + +#ifndef IFACES_CONFIG_PARSER_H +#define IFACES_CONFIG_PARSER_H + +#include <cc/data.h> +#include <cc/simple_parser.h> +#include <dhcpsrv/cfg_iface.h> +#include <dhcpsrv/parsers/dhcp_parsers.h> + +namespace isc { +namespace dhcp { + +/// @brief Parser for the configuration of interfaces. +/// +/// This parser parses the "interfaces-config" parameter which holds the +/// full configuration of the DHCP server with respect to the use of +/// interfaces, DHCP traffic sockets and alike. +/// +/// This parser is used in both DHCPv4 and DHCPv6. Derived parsers +/// are not needed. +class IfacesConfigParser : public isc::data::SimpleParser { +public: + + /// @brief Constructor + /// + /// In test mode only the configuration is checked. In particular + /// sockets are not opened or closed. + /// + /// @param protocol AF_INET for DHCPv4 and AF_INET6 for DHCPv6. + /// @param test_mode True if in test mode, False if not. + IfacesConfigParser(const uint16_t protocol, bool test_mode); + + /// @brief Parses content of the "interfaces-config". + /// + /// @param config parsed structures will be stored here + /// @param values pointer to the content of parsed values + /// + /// @throw DhcpConfigError if the interface names and/or addresses + /// are invalid. + void parse(const CfgIfacePtr& config, const isc::data::ConstElementPtr& values); + +private: + /// @brief parses interfaces-list structure + /// + /// This method goes through all the interfaces-specified in + /// 'interfaces-list' and enabled them in the specified configuration + /// structure + /// + /// @param cfg_iface parsed interfaces will be specified here + /// @param ifaces_list interfaces-list to be parsed + /// @throw DhcpConfigError if the interface names are invalid. + void parseInterfacesList(const CfgIfacePtr& cfg_iface, + isc::data::ConstElementPtr ifaces_list); + + /// @brief AF_INET for DHCPv4 and AF_INET6 for DHCPv6. + int protocol_; + + /// @brief Test mode. + bool test_mode_; +}; + +} +} // end of namespace isc + +#endif // IFACES_CONFIG_PARSER_H diff --git a/src/lib/dhcpsrv/parsers/multi_threading_config_parser.cc b/src/lib/dhcpsrv/parsers/multi_threading_config_parser.cc new file mode 100644 index 0000000..1194618 --- /dev/null +++ b/src/lib/dhcpsrv/parsers/multi_threading_config_parser.cc @@ -0,0 +1,72 @@ +// Copyright (C) 2020 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 <cc/data.h> +#include <dhcpsrv/srv_config.h> +#include <dhcpsrv/parsers/multi_threading_config_parser.h> +#include <util/multi_threading_mgr.h> + +using namespace isc::data; +using namespace isc::util; + +namespace isc { +namespace dhcp { + +void +MultiThreadingConfigParser::parse(SrvConfig& srv_cfg, + const ConstElementPtr& value) { + if (!value) { + return; + } + if (value->getType() != Element::map) { + isc_throw(DhcpConfigError, "multi-threading is supposed to be a map"); + } + + // enable-multi-threading is mandatory + auto enabled = getBoolean(value, "enable-multi-threading"); + + // thread-pool-size is not mandatory + if (value->get("thread-pool-size")) { + auto thread_pool_size = getInteger(value, "thread-pool-size"); + uint32_t max_size = std::numeric_limits<uint16_t>::max(); + if (thread_pool_size < 0) { + isc_throw(DhcpConfigError, + "thread pool size code must not be negative (" + << getPosition("thread-pool-size", value) << ")"); + } + if (thread_pool_size > max_size) { + isc_throw(DhcpConfigError, "invalid thread pool size '" + << thread_pool_size << "', it must not be greater than '" + << max_size << "' (" + << getPosition("thread-pool-size", value) << ")"); + } + } + + // packet-queue-size is not mandatory + if (value->get("packet-queue-size")) { + auto packet_queue_size = getInteger(value, "packet-queue-size"); + uint32_t max_size = std::numeric_limits<uint16_t>::max(); + if (packet_queue_size < 0) { + isc_throw(DhcpConfigError, + "packet queue size code must not be negative (" + << getPosition("packet-queue-size", value) << ")"); + } + if (packet_queue_size > max_size) { + isc_throw(DhcpConfigError, "invalid packet queue size '" + << packet_queue_size << "', it must not be greater than '" + << max_size << "' (" + << getPosition("packet-queue-size", value) << ")"); + } + } + + srv_cfg.setDHCPMultiThreading(value); + MultiThreadingMgr::instance().setMode(enabled); +} + +} // namespace dhcp +} // namespace isc diff --git a/src/lib/dhcpsrv/parsers/multi_threading_config_parser.h b/src/lib/dhcpsrv/parsers/multi_threading_config_parser.h new file mode 100644 index 0000000..d099a05 --- /dev/null +++ b/src/lib/dhcpsrv/parsers/multi_threading_config_parser.h @@ -0,0 +1,34 @@ +// Copyright (C) 2020 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/. + +#ifndef MULTI_THREADING_CONFIG_PARSER_H +#define MULTI_THREADING_CONFIG_PARSER_H + +#include <cc/simple_parser.h> +#include <dhcpsrv/srv_config.h> + +namespace isc { +namespace dhcp { + +/// @brief Simple parser for multi-threading structure +class MultiThreadingConfigParser : public isc::data::SimpleParser { +public: + + /// @brief parses JSON structure. + /// + /// This function stores the 'multi-threading' settings in the server + /// configuration and updates the MT mode so that is can be checked when + /// parsing 'hooks-libraries'. + /// + /// @param srv_cfg parsed value will be stored here. + /// @param value a JSON map that contains multi-threading parameters. + void parse(SrvConfig& srv_cfg, const isc::data::ConstElementPtr& value); +}; + +} // namespace dhcp +} // namespace isc + +#endif /* MULTI_THREADING_CONFIG_PARSER_H */ diff --git a/src/lib/dhcpsrv/parsers/option_data_parser.cc b/src/lib/dhcpsrv/parsers/option_data_parser.cc new file mode 100644 index 0000000..e26e1c5 --- /dev/null +++ b/src/lib/dhcpsrv/parsers/option_data_parser.cc @@ -0,0 +1,446 @@ +// Copyright (C) 2017-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 <exceptions/exceptions.h> +#include <dhcp/dhcp4.h> +#include <dhcp/libdhcp++.h> +#include <dhcp/option_definition.h> +#include <dhcp/option_space.h> +#include <dhcpsrv/cfgmgr.h> +#include <dhcpsrv/parsers/option_data_parser.h> +#include <dhcpsrv/parsers/simple_parser4.h> +#include <dhcpsrv/parsers/simple_parser6.h> +#include <util/encode/hex.h> +#include <util/strutil.h> +#include <boost/foreach.hpp> +#include <boost/make_shared.hpp> +#include <limits> +#include <vector> + +using namespace isc::data; +using namespace isc::util; + +namespace isc { +namespace dhcp { + +// **************************** OptionDataParser ************************* + +OptionDataParser::OptionDataParser(const uint16_t address_family, + CfgOptionDefPtr cfg_option_def) + : address_family_(address_family), cfg_option_def_(cfg_option_def) { +} + +std::pair<OptionDescriptor, std::string> +OptionDataParser::parse(isc::data::ConstElementPtr single_option) { + + // Check parameters. + if (address_family_ == AF_INET) { + checkKeywords(SimpleParser4::OPTION4_PARAMETERS, single_option); + } else { + checkKeywords(SimpleParser6::OPTION6_PARAMETERS, single_option); + } + + // Try to create the option instance. + std::pair<OptionDescriptor, std::string> opt = createOption(single_option); + + if (!opt.first.option_) { + // Should never happen (@todo: update message) + isc_throw(isc::InvalidOperation, + "parser logic error: no option has been configured and" + " thus there is nothing to commit. Has build() been called?"); + } + + return (opt); +} + +Optional<uint32_t> +OptionDataParser::extractCode(ConstElementPtr parent) const { + uint32_t code; + try { + code = getInteger(parent, "code"); + + } catch (const std::exception&) { + // The code parameter was not found. Return an unspecified + // value. + return (Optional<uint32_t>()); + } + + if (address_family_ == AF_INET && + code > std::numeric_limits<uint8_t>::max()) { + isc_throw(DhcpConfigError, "invalid option code '" << code + << "', it must not be greater than '" + << static_cast<int>(std::numeric_limits<uint8_t>::max()) + << "' (" << getPosition("code", parent) + << ")"); + + } else if (address_family_ == AF_INET6 && + code > std::numeric_limits<uint16_t>::max()) { + isc_throw(DhcpConfigError, "invalid option code '" << code + << "', it must not exceed '" + << std::numeric_limits<uint16_t>::max() + << "' (" << getPosition("code", parent) + << ")"); + + } + + return (Optional<uint32_t>(code)); +} + +Optional<std::string> +OptionDataParser::extractName(ConstElementPtr parent) const { + std::string name; + try { + name = getString(parent, "name"); + + } catch (...) { + return (Optional<std::string>()); + } + + if (name.find(" ") != std::string::npos) { + isc_throw(DhcpConfigError, "invalid option name '" << name + << "', space character is not allowed (" + << getPosition("name", parent) << ")"); + } + + return (Optional<std::string>(name)); +} + +std::string +OptionDataParser::extractData(ConstElementPtr parent) const { + std::string data; + try { + data = getString(parent, "data"); + + } catch (...) { + // The "data" parameter was not found. Return an empty value. + return (data); + } + + return (data); +} + +Optional<bool> +OptionDataParser::extractCSVFormat(ConstElementPtr parent) const { + bool csv_format = true; + try { + csv_format = getBoolean(parent, "csv-format"); + + } catch (...) { + return (Optional<bool>()); + } + + return (Optional<bool>(csv_format)); +} + +std::string +OptionDataParser::extractSpace(ConstElementPtr parent) const { + std::string space = address_family_ == AF_INET ? + DHCP4_OPTION_SPACE : DHCP6_OPTION_SPACE; + try { + space = getString(parent, "space"); + + } catch (...) { + return (space); + } + + try { + if (!OptionSpace::validateName(space)) { + isc_throw(DhcpConfigError, "invalid option space name '" + << space << "'"); + } + + if ((space == DHCP4_OPTION_SPACE) && (address_family_ == AF_INET6)) { + isc_throw(DhcpConfigError, "'" << DHCP4_OPTION_SPACE + << "' option space name is reserved for DHCPv4 server"); + + } else if ((space == DHCP6_OPTION_SPACE) && + (address_family_ == AF_INET)) { + isc_throw(DhcpConfigError, "'" << DHCP6_OPTION_SPACE + << "' option space name is reserved for DHCPv6 server"); + } + + } catch (const std::exception& ex) { + // Append position of the option space parameter. + isc_throw(DhcpConfigError, ex.what() << " (" + << getPosition("space", parent) << ")"); + } + + return (space); +} + +Optional<bool> +OptionDataParser::extractPersistent(ConstElementPtr parent) const { + bool persist = false; + try { + persist = getBoolean(parent, "always-send"); + + } catch (...) { + return (Optional<bool>()); + } + + return (Optional<bool>(persist)); +} + +OptionDefinitionPtr +OptionDataParser::findOptionDefinition(const std::string& option_space, + const Optional<uint32_t>& option_code, + const Optional<std::string>& option_name) const { + OptionDefinitionPtr def; + if (cfg_option_def_) { + // Check if the definition was given in the constructor + if (option_code.unspecified()) { + def = cfg_option_def_->get(option_space, option_name); + } else { + def = cfg_option_def_->get(option_space, option_code); + } + } + + if (!def) { + // Check if this is a standard option. + if (option_code.unspecified()) { + def = LibDHCP::getOptionDef(option_space, option_name); + } else { + def = LibDHCP::getOptionDef(option_space, option_code); + } + } + + if (!def) { + // Check if this is a vendor-option. If it is, get vendor-specific + // definition. + uint32_t vendor_id = LibDHCP::optionSpaceToVendorId(option_space); + if (vendor_id) { + const Option::Universe u = address_family_ == AF_INET ? + Option::V4 : Option::V6; + if (option_code.unspecified()) { + def = LibDHCP::getVendorOptionDef(u, vendor_id, option_name); + } else { + def = LibDHCP::getVendorOptionDef(u, vendor_id, option_code); + } + } + } + + if (!def) { + // Check if this is an option specified by a user. We used to + // check that in the staging configuration, but when the configuration + // changes are caused by a command the staging configuration doesn't + // exist. What is always available is the container holding runtime + // option definitions in LibDHCP. It holds option definitions from + // the staging configuration in case of the full reconfiguration or + // the definitions from the current configuration in case there is + // no staging configuration (after configuration commit). In other + // words, runtime options are always the ones that we need here. + if (option_code.unspecified()) { + def = LibDHCP::getRuntimeOptionDef(option_space, option_name); + } else { + def = LibDHCP::getRuntimeOptionDef(option_space, option_code); + } + } + + if (!def) { + // Finish by last resort definitions. + if (option_code.unspecified()) { + def = LibDHCP::getLastResortOptionDef(option_space, option_name); + } else { + def = LibDHCP::getLastResortOptionDef(option_space, option_code); + } + } + + return (def); +} + +std::pair<OptionDescriptor, std::string> +OptionDataParser::createOption(ConstElementPtr option_data) { + const Option::Universe universe = address_family_ == AF_INET ? + Option::V4 : Option::V6; + + Optional<uint32_t> code_param = extractCode(option_data); + Optional<std::string> name_param = extractName(option_data); + Optional<bool> csv_format_param = extractCSVFormat(option_data); + Optional<bool> persist_param = extractPersistent(option_data); + std::string data_param = extractData(option_data); + std::string space_param = extractSpace(option_data); + ConstElementPtr user_context = option_data->get("user-context"); + + // Require that option code or option name is specified. + if (code_param.unspecified() && name_param.unspecified()) { + isc_throw(DhcpConfigError, "option data configuration requires one of" + " 'code' or 'name' parameters to be specified" + << " (" << option_data->getPosition() << ")"); + } + + // Try to find a corresponding option definition using option code or + // option name. + OptionDefinitionPtr def = findOptionDefinition(space_param, code_param, name_param); + + // If there is no definition, the user must not explicitly enable the + // use of csv-format. + if (!def) { + // If explicitly requested that the CSV format is to be used, + // the option definition is a must. + if (!csv_format_param.unspecified() && csv_format_param) { + isc_throw(DhcpConfigError, "definition for the option '" + << space_param << "." << name_param + << "' having code '" << code_param + << "' does not exist (" + << getPosition("name", option_data) + << ")"); + + // If there is no option definition and the option code is not specified + // we have no means to find the option code. + } else if (!name_param.unspecified() && code_param.unspecified()) { + isc_throw(DhcpConfigError, "definition for the option '" + << space_param << "." << name_param + << "' does not exist (" + << getPosition("name", option_data) + << ")"); + } + } + + // Transform string of hexadecimal digits into binary format. + std::vector<uint8_t> binary; + std::vector<std::string> data_tokens; + + // If the definition is available and csv-format hasn't been explicitly + // disabled, we will parse the data as comma separated values. + if (def && (csv_format_param.unspecified() || csv_format_param)) { + // If the option data is specified as a string of comma + // separated values then we need to split this string into + // individual values - each value will be used to initialize + // one data field of an option. + // It is the only usage of the escape option: this allows + // to embed commas in individual values and to return + // for instance a string value with embedded commas. + data_tokens = isc::util::str::tokens(data_param, ",", true); + + } else { + // Try to convert the values in quotes into a vector of ASCII codes. + // If the identifier lacks opening and closing quote, this will return + // an empty value, in which case we'll try to decode it as a string of + // hexadecimal digits. + try { + binary = util::str::quotedStringToBinary(data_param); + if (binary.empty()) { + util::str::decodeFormattedHexString(data_param, binary); + } + } catch (...) { + isc_throw(DhcpConfigError, "option data is not a valid" + << " string of hexadecimal digits: " << data_param + << " (" + << getPosition("data", option_data) + << ")"); + } + } + + OptionDescriptor desc(false); + + if (!def) { + // @todo We have a limited set of option definitions initialized at + // the moment. In the future we want to initialize option definitions + // for all options. Consequently an error will be issued if an option + // definition does not exist for a particular option code. For now it is + // ok to create generic option if definition does not exist. + OptionPtr option(new Option(universe, static_cast<uint16_t>(code_param), + binary)); + + desc.option_ = option; + desc.persistent_ = !persist_param.unspecified() && persist_param; + } else { + + // Option name is specified it should match the name in the definition. + if (!name_param.unspecified() && (def->getName() != name_param.get())) { + isc_throw(DhcpConfigError, "specified option name '" + << name_param << "' does not match the " + << "option definition: '" << space_param + << "." << def->getName() << "' (" + << getPosition("name", option_data) + << ")"); + } + + // Option definition has been found so let's use it to create + // an instance of our option. + try { + bool use_csv = csv_format_param.unspecified() || csv_format_param; + OptionPtr option = use_csv ? + def->optionFactory(universe, def->getCode(), data_tokens) : + def->optionFactory(universe, def->getCode(), binary); + desc.option_ = option; + desc.persistent_ = !persist_param.unspecified() && persist_param; + if (use_csv) { + desc.formatted_value_ = data_param; + } + } catch (const isc::Exception& ex) { + isc_throw(DhcpConfigError, "option data does not match" + << " option definition (space: " << space_param + << ", code: " << def->getCode() << "): " + << ex.what() << " (" + << getPosition("data", option_data) + << ")"); + } + } + + // Check PAD and END in (and only in) dhcp4 space. + if (space_param == DHCP4_OPTION_SPACE) { + if (desc.option_->getType() == DHO_PAD) { + isc_throw(DhcpConfigError, "invalid option code '0': " + << "reserved for PAD (" + << option_data->getPosition() << ")"); + } else if (desc.option_->getType() == DHO_END) { + isc_throw(DhcpConfigError, "invalid option code '255': " + << "reserved for END (" + << option_data->getPosition() << ")"); + } + } + + // For dhcp6 space the value 0 is reserved. + if (space_param == DHCP6_OPTION_SPACE) { + if (desc.option_->getType() == 0) { + isc_throw(DhcpConfigError, "invalid option code '0': " + << "reserved value (" + << option_data->getPosition() << ")"); + } + } + + + // Add user context + if (user_context) { + desc.setContext(user_context); + } + + // All went good, so we can set the option space name. + return make_pair(desc, space_param); +} + +// **************************** OptionDataListParser ************************* +OptionDataListParser::OptionDataListParser(//const std::string&, + //const CfgOptionPtr& cfg, + const uint16_t address_family, + CfgOptionDefPtr cfg_option_def) + : address_family_(address_family), cfg_option_def_(cfg_option_def) { +} + + +void OptionDataListParser::parse(const CfgOptionPtr& cfg, + isc::data::ConstElementPtr option_data_list) { + auto option_parser = createOptionDataParser(); + BOOST_FOREACH(ConstElementPtr data, option_data_list->listValue()) { + std::pair<OptionDescriptor, std::string> option = + option_parser->parse(data); + // Use the option description to keep the formatted value + cfg->add(option.first, option.second); + cfg->encapsulate(); + } +} + +boost::shared_ptr<OptionDataParser> +OptionDataListParser::createOptionDataParser() const { + auto parser = boost::make_shared<OptionDataParser>(address_family_, cfg_option_def_); + return (parser); +} + +} // end of namespace isc::dhcp +} // end of namespace isc diff --git a/src/lib/dhcpsrv/parsers/option_data_parser.h b/src/lib/dhcpsrv/parsers/option_data_parser.h new file mode 100644 index 0000000..a21cdf2 --- /dev/null +++ b/src/lib/dhcpsrv/parsers/option_data_parser.h @@ -0,0 +1,230 @@ +// Copyright (C) 2017-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/. + +#ifndef OPTION_DATA_PARSER_H +#define OPTION_DATA_PARSER_H + +#include <cc/data.h> +#include <cc/simple_parser.h> +#include <dhcp/option_definition.h> +#include <dhcpsrv/cfg_option.h> +#include <dhcpsrv/cfg_option_def.h> +#include <boost/shared_ptr.hpp> +#include <util/optional.h> +#include <cstdint> +#include <string> +#include <utility> + +namespace isc { +namespace dhcp { + +/// @brief Parser for option data value. +/// +/// This parser parses configuration entries that specify value of +/// a single option. These entries include option name, option code +/// and data carried by the option. The option data can be specified +/// in one of the two available formats: binary value represented as +/// a string of hexadecimal digits or a list of comma separated values. +/// The format being used is controlled by csv-format configuration +/// parameter. When setting this value to True, the latter format is +/// used. The subsequent values in the CSV format apply to relevant +/// option data fields in the configured option. For example the +/// configuration: "data" : "192.168.2.0, 56, hello world" can be +/// used to set values for the option comprising IPv4 address, +/// integer and string data field. Note that order matters. If the +/// order of values does not match the order of data fields within +/// an option the configuration will not be accepted. If parsing +/// is successful then an instance of an option is created and +/// added to the storage provided by the calling class. +class OptionDataParser : public isc::data::SimpleParser { +public: + /// @brief Constructor. + /// + /// @param address_family Address family: @c AF_INET or @c AF_INET6. + /// @param cfg_option_def Config option definitions (optional) + OptionDataParser(const uint16_t address_family, + CfgOptionDefPtr cfg_option_def = CfgOptionDefPtr()); + + /// @brief Virtual destructor. + virtual ~OptionDataParser() { + } + + /// @brief Parses ElementPtr containing option definition + /// + /// This method parses ElementPtr containing the option definition, + /// instantiates the option for it and then returns a pair + /// of option descriptor (that holds that new option) and + /// a string that specifies the option space. + /// + /// Note: ElementPtr is expected to contain all fields. If your + /// ElementPtr does not have them, please use + /// @ref isc::data::SimpleParser::setDefaults to fill the missing fields + /// with default values. + /// + /// @param single_option ElementPtr containing option definition + /// @return Option object wrapped in option description and an option + /// space + std::pair<OptionDescriptor, std::string> + parse(isc::data::ConstElementPtr single_option); + +protected: + + /// @brief Finds an option definition within an option space + /// + /// Given an option space and an option code, find the corresponding + /// option definition within the option definition storage. + /// + /// @param option_space name of the parameter option space + /// @param option_code option code to be used to find the option + /// definition, if the option name is unspecified. + /// @param option_name option name to be used to lookup the option + /// definition. + /// + /// @return OptionDefinitionPtr of the option definition or an + /// empty OptionDefinitionPtr if not found. + /// @throw DhcpConfigError if the option space requested is not valid + /// for this server. + virtual OptionDefinitionPtr + findOptionDefinition(const std::string& option_space, + const util::Optional<uint32_t>& option_code, + const util::Optional<std::string>& option_name) const; + + /// @brief Create option instance. + /// + /// Creates an instance of an option and adds it to the provided + /// options storage. If the option data parsed by createOption function + /// is invalid or insufficient this function emits an exception. + /// + /// If the option data is given as a string containing a hexadecimal + /// literal, then it is converted into binary format. These literals + /// may contain upper and lower case digits. They may be octets + /// delimited by colons or spaces (octets may be 1 or 2 digits) + /// If not delimited octets then they must be a continuous string of + /// digits with or without a "0x" prefix. Examples: + /// + /// -# ab:cd:ef - colon delimited + /// -# ab cd ef - space delimited + /// -# 0xabcdef - 0x prefixed (no delimiters) + /// -# abcdef - no prefix or delimiters + /// + /// A leading zero is assumed for odd number of digits + /// in an octet or continuous string. + /// + /// @param option_data An element holding data for a single option being + /// created. + /// + /// @return created option descriptor + /// + /// @throw DhcpConfigError if parameters provided in the configuration + /// are invalid. + std::pair<OptionDescriptor, std::string> + createOption(isc::data::ConstElementPtr option_data); + + /// @brief Retrieves parsed option code as an optional value. + /// + /// @param parent A data element holding full option data configuration. + /// + /// @return Option code, possibly unspecified. + /// @throw DhcpConfigError if option code is invalid. + util::Optional<uint32_t> + extractCode(data::ConstElementPtr parent) const; + + /// @brief Retrieves parsed option name as an optional value. + /// + /// @param parent A data element holding full option data configuration. + /// + /// @return Option name, possibly unspecified. + /// @throw DhcpConfigError if option name is invalid. + util::Optional<std::string> + extractName(data::ConstElementPtr parent) const; + + /// @brief Retrieves csv-format parameter as an optional value. + /// + /// @return Value of the csv-format parameter, possibly unspecified. + util::Optional<bool> extractCSVFormat(data::ConstElementPtr parent) const; + + /// @brief Retrieves option data as a string. + /// + /// @param parent A data element holding full option data configuration. + /// @return Option data as a string. It will return empty string if + /// option data is unspecified. + std::string extractData(data::ConstElementPtr parent) const; + + /// @brief Retrieves option space name. + /// + /// If option space name is not specified in the configuration the + /// 'dhcp4' or 'dhcp6' option space name is returned, depending on + /// the universe specified in the parser context. + /// + /// @param parent A data element holding full option data configuration. + /// + /// @return Option space name. + std::string extractSpace(data::ConstElementPtr parent) const; + + /// @brief Retrieves persistent/always-send parameter as an optional value. + /// + /// @return Value of the persistent parameter, possibly unspecified. + util::Optional<bool> extractPersistent(data::ConstElementPtr parent) const; + + /// @brief Address family: @c AF_INET or @c AF_INET6. + uint16_t address_family_; + + /// @brief Config option definitions + CfgOptionDefPtr cfg_option_def_; +}; + +/// @brief Parser for option data values within a subnet. +/// +/// This parser iterates over all entries that define options +/// data for a particular subnet and creates a collection of options. +/// If parsing is successful, all these options are added to the Subnet +/// object. +class OptionDataListParser : public isc::data::SimpleParser { +public: + /// @brief Constructor. + /// + /// @param address_family Address family: @c AF_INET or AF_INET6 + /// @param cfg_option_def Config option definitions (optional) + OptionDataListParser(const uint16_t address_family, + CfgOptionDefPtr cfg_option_def = CfgOptionDefPtr()); + + /// @brief Virtual destructor. + virtual ~OptionDataListParser() { + } + + /// @brief Parses a list of options, instantiates them and stores in cfg + /// + /// This method expects to get a list of options in option_data_list, + /// iterates over them, creates option objects, wraps them with + /// option descriptor and stores in specified cfg. + /// + /// @param cfg created options will be stored here + /// @param option_data_list configuration that describes the options + void parse(const CfgOptionPtr& cfg, + isc::data::ConstElementPtr option_data_list); +protected: + + /// @brief Returns an instance of the @c OptionDataListParser to + /// be used in parsing options. + /// + /// This function can be overridden in the child classes to supply + /// a custom parser for option data. + /// + /// @return an instance of the @c OptionDataListParser. + virtual boost::shared_ptr<OptionDataParser> createOptionDataParser() const; + + /// @brief Address family: @c AF_INET or @c AF_INET6 + uint16_t address_family_; + + /// @brief Config option definitions + CfgOptionDefPtr cfg_option_def_; +}; + + +} // end of namespace isc::dhcp +} // end of namespace isc + +#endif // OPTION_DATA_PARSER_H diff --git a/src/lib/dhcpsrv/parsers/sanity_checks_parser.cc b/src/lib/dhcpsrv/parsers/sanity_checks_parser.cc new file mode 100644 index 0000000..6210857 --- /dev/null +++ b/src/lib/dhcpsrv/parsers/sanity_checks_parser.cc @@ -0,0 +1,55 @@ +// Copyright (C) 2018 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 <dhcpsrv/parsers/sanity_checks_parser.h> +#include <dhcpsrv/cfg_consistency.h> +#include <cc/data.h> + +using namespace isc::data; + +namespace isc { +namespace dhcp { + +void +SanityChecksParser::parse(SrvConfig& cfg, const ConstElementPtr& sanity_checks) { + + if (!sanity_checks) { + return; + } + if (sanity_checks->getType() != Element::map) { + isc_throw(DhcpConfigError, "sanity-checks is supposed to be a map"); + } + + ConstElementPtr lease_checks = sanity_checks->get("lease-checks"); + if (lease_checks) { + if (lease_checks->getType() != Element::string) { + isc_throw(DhcpConfigError, "lease-checks must be a string"); + } + std::string lc = lease_checks->stringValue(); + CfgConsistency::LeaseSanity check; + if (lc == "none") { + check = CfgConsistency::LEASE_CHECK_NONE; + } else if (lc == "warn") { + check = CfgConsistency::LEASE_CHECK_WARN; + } else if (lc == "fix") { + check = CfgConsistency::LEASE_CHECK_FIX; + } else if (lc == "fix-del") { + check = CfgConsistency::LEASE_CHECK_FIX_DEL; + } else if (lc == "del") { + check = CfgConsistency::LEASE_CHECK_DEL; + } else { + isc_throw(DhcpConfigError, "Unsupported lease-checks value: " << lc + << ", supported values are: none, warn, fix, fix-del, del"); + } + cfg.getConsistency()->setLeaseSanityCheck(check); + } + + // Additional sanity check fields will come in later here. +} + +}; +}; diff --git a/src/lib/dhcpsrv/parsers/sanity_checks_parser.h b/src/lib/dhcpsrv/parsers/sanity_checks_parser.h new file mode 100644 index 0000000..a9dab5e --- /dev/null +++ b/src/lib/dhcpsrv/parsers/sanity_checks_parser.h @@ -0,0 +1,32 @@ +// Copyright (C) 2018 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/. + +#ifndef SANITY_CHECKS_PARSER_H +#define SANITY_CHECKS_PARSER_H + +#include <cc/simple_parser.h> +#include <dhcpsrv/srv_config.h> + +namespace isc { +namespace dhcp { + +/// @brief Simple parser for sanity-checks structure +/// +/// Currently parses only one parameter: +/// - lease-checks. Allowed values: none, warn, fix, fix-del, del +class SanityChecksParser : public isc::data::SimpleParser { + public: + /// @brief parses JSON structure + /// + /// @param srv_cfg parsed value will be stored here + /// @param value a JSON map that contains lease-checks parameter. + void parse(SrvConfig& srv_cfg, const isc::data::ConstElementPtr& value); +}; + +}; +}; + +#endif /* SANITY_CHECKS_PARSER_H */ diff --git a/src/lib/dhcpsrv/parsers/shared_network_parser.cc b/src/lib/dhcpsrv/parsers/shared_network_parser.cc new file mode 100644 index 0000000..c063fa6 --- /dev/null +++ b/src/lib/dhcpsrv/parsers/shared_network_parser.cc @@ -0,0 +1,408 @@ +// Copyright (C) 2017-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 <asiolink/io_address.h> +#include <cc/data.h> +#include <dhcpsrv/cfgmgr.h> +#include <dhcpsrv/cfg_option.h> +#include <dhcpsrv/parsers/option_data_parser.h> +#include <dhcpsrv/parsers/shared_network_parser.h> +#include <dhcpsrv/parsers/simple_parser4.h> +#include <dhcpsrv/parsers/simple_parser6.h> +#include <dhcpsrv/shared_network.h> +#include <boost/make_shared.hpp> +#include <boost/pointer_cast.hpp> +#include <string> + +using namespace isc::asiolink; +using namespace isc::data; +using namespace isc::util; + +namespace isc { +namespace dhcp { + +SharedNetwork4Parser::SharedNetwork4Parser(bool check_iface) + : check_iface_(check_iface) { +} + +SharedNetwork4Ptr +SharedNetwork4Parser::parse(const data::ConstElementPtr& shared_network_data) { + SharedNetwork4Ptr shared_network; + try { + + // Check parameters. + checkKeywords(SimpleParser4::SHARED_NETWORK4_PARAMETERS, + shared_network_data); + + // Make sure that the network name has been specified. The name is required + // to create a SharedNetwork4 object. + std::string name = getString(shared_network_data, "name"); + shared_network.reset(new SharedNetwork4(name)); + + // Move from reservation mode to new reservations flags. + ElementPtr mutable_params; + mutable_params = boost::const_pointer_cast<Element>(shared_network_data); + BaseNetworkParser::moveReservationMode(mutable_params); + + // Parse parameters common to all Network derivations. + NetworkPtr network = boost::dynamic_pointer_cast<Network>(shared_network); + parseCommon(mutable_params, network); + + // interface is an optional parameter + if (shared_network_data->contains("interface")) { + std::string iface = getString(shared_network_data, "interface"); + if (!iface.empty()) { + if (check_iface_ && !IfaceMgr::instance().getIface(iface)) { + ConstElementPtr error = + shared_network_data->get("interface"); + isc_throw(DhcpConfigError, + "Specified network interface name " << iface + << " for shared network " << name + << " is not present in the system (" + << error->getPosition() << ")"); + } + shared_network->setIface(iface); + } + } + + if (shared_network_data->contains("option-data")) { + auto json = shared_network_data->get("option-data"); + // Create parser instance for option-data. + CfgOptionPtr cfg_option = shared_network->getCfgOption(); + auto parser = createOptionDataListParser(); + parser->parse(cfg_option, json); + } + + if (shared_network_data->contains("subnet4")) { + auto json = shared_network_data->get("subnet4"); + + // Create parser instance of subnet4. + auto parser = createSubnetsListParser(); + Subnet4Collection subnets; + parser->parse(subnets, json); + + // Add all returned subnets into shared network. + for (auto subnet = subnets.cbegin(); subnet != subnets.cend(); + ++subnet) { + shared_network->add(*subnet); + } + } + + if (shared_network_data->contains("match-client-id")) { + shared_network->setMatchClientId(getBoolean(shared_network_data, + "match-client-id")); + } + + if (shared_network_data->contains("authoritative")) { + shared_network->setAuthoritative(getBoolean(shared_network_data, + "authoritative")); + } + + // Set next-server + if (shared_network_data->contains("next-server")) { + std::string next_server; + try { + next_server = getString(shared_network_data, "next-server"); + if (!next_server.empty()) { + shared_network->setSiaddr(IOAddress(next_server)); + } + } catch (...) { + ConstElementPtr next = shared_network_data->get("next-server"); + std::string pos; + if (next) { + pos = next->getPosition().str(); + } else { + pos = shared_network_data->getPosition().str(); + } + isc_throw(DhcpConfigError, "invalid parameter next-server : " + << next_server << "(" << pos << ")"); + } + } + + // Set server-hostname. + if (shared_network_data->contains("server-hostname")) { + std::string sname = getString(shared_network_data, "server-hostname"); + if (!sname.empty()) { + if (sname.length() >= Pkt4::MAX_SNAME_LEN) { + ConstElementPtr error = shared_network_data->get("server-hostname"); + isc_throw(DhcpConfigError, "server-hostname must be at most " + << Pkt4::MAX_SNAME_LEN - 1 << " bytes long, it is " + << sname.length() << " (" + << error->getPosition() << ")"); + } + shared_network->setSname(sname); + } + } + + // Set boot-file-name. + if (shared_network_data->contains("boot-file-name")) { + std::string filename = getString(shared_network_data, "boot-file-name"); + if (!filename.empty()) { + if (filename.length() > Pkt4::MAX_FILE_LEN) { + ConstElementPtr error = shared_network_data->get("boot-file-name"); + isc_throw(DhcpConfigError, "boot-file-name must be at most " + << Pkt4::MAX_FILE_LEN - 1 << " bytes long, it is " + << filename.length() << " (" + << error->getPosition() << ")"); + } + shared_network->setFilename(filename); + } + } + + if (shared_network_data->contains("client-class")) { + std::string client_class = getString(shared_network_data, "client-class"); + if (!client_class.empty()) { + shared_network->allowClientClass(client_class); + } + } + + ConstElementPtr user_context = shared_network_data->get("user-context"); + if (user_context) { + shared_network->setContext(user_context); + } + + if (shared_network_data->contains("require-client-classes")) { + const std::vector<data::ElementPtr>& class_list = + shared_network_data->get("require-client-classes")->listValue(); + for (auto cclass = class_list.cbegin(); + cclass != class_list.cend(); ++cclass) { + if (((*cclass)->getType() != Element::string) || + (*cclass)->stringValue().empty()) { + isc_throw(DhcpConfigError, "invalid class name (" + << (*cclass)->getPosition() << ")"); + } + shared_network->requireClientClass((*cclass)->stringValue()); + } + } + + if (shared_network_data->contains("relay")) { + auto relay_parms = shared_network_data->get("relay"); + if (relay_parms) { + RelayInfoParser parser(Option::V4); + Network::RelayInfoPtr relay_info(new Network::RelayInfo()); + parser.parse(relay_info, relay_parms); + shared_network->setRelayInfo(*relay_info); + } + } + + parseTeePercents(shared_network_data, network); + + // Parse DDNS parameters + parseDdnsParams(shared_network_data, network); + + // Parse lease cache parameters + parseCacheParams(shared_network_data, network); + } catch (const DhcpConfigError&) { + // Position was already added + throw; + } catch (const std::exception& ex) { + isc_throw(DhcpConfigError, ex.what() << " (" + << shared_network_data->getPosition() << ")"); + } + + // In order to take advantage of the dynamic inheritance of global + // parameters to a shared network we need to set a callback function + // for each shared network to allow for fetching global parameters. + shared_network->setFetchGlobalsFn([]() -> ConstCfgGlobalsPtr { + return (CfgMgr::instance().getCurrentCfg()->getConfiguredGlobals()); + }); + + return (shared_network); +} + +boost::shared_ptr<OptionDataListParser> +SharedNetwork4Parser::createOptionDataListParser() const { + auto parser = boost::make_shared<OptionDataListParser>(AF_INET); + return (parser); +} + +boost::shared_ptr<Subnets4ListConfigParser> +SharedNetwork4Parser::createSubnetsListParser() const { + auto parser = boost::make_shared<Subnets4ListConfigParser>(check_iface_); + return (parser); +} + +SharedNetwork6Parser::SharedNetwork6Parser(bool check_iface) + : check_iface_(check_iface) { +} + +SharedNetwork6Ptr +SharedNetwork6Parser::parse(const data::ConstElementPtr& shared_network_data) { + SharedNetwork6Ptr shared_network; + std::string name; + try { + // Check parameters. + checkKeywords(SimpleParser6::SHARED_NETWORK6_PARAMETERS, + shared_network_data); + + // Make sure that the network name has been specified. The name is required + // to create a SharedNetwork6 object. + std::string name = getString(shared_network_data, "name"); + shared_network.reset(new SharedNetwork6(name)); + + // Move from reservation mode to new reservations flags. + ElementPtr mutable_params; + mutable_params = boost::const_pointer_cast<Element>(shared_network_data); + BaseNetworkParser::moveReservationMode(mutable_params); + + // Parse parameters common to all Network derivations. + NetworkPtr network = boost::dynamic_pointer_cast<Network>(shared_network); + parseCommon(mutable_params, network); + + // preferred-lifetime + shared_network->setPreferred(parseIntTriplet(shared_network_data, + "preferred-lifetime")); + + // Get interface-id option content. For now we support string + // representation only + Optional<std::string> ifaceid; + if (shared_network_data->contains("interface-id")) { + ifaceid = getString(shared_network_data, "interface-id"); + } + + // Interface is an optional parameter + Optional<std::string> iface; + if (shared_network_data->contains("interface")) { + iface = getString(shared_network_data, "interface"); + } + + // Specifying both interface for locally reachable subnets and + // interface id for relays is mutually exclusive. Need to test for + // this condition. + if (!ifaceid.unspecified() && !iface.unspecified() && !ifaceid.empty() && + !iface.empty()) { + isc_throw(isc::dhcp::DhcpConfigError, + "parser error: interface (defined for locally reachable " + "subnets) and interface-id (defined for subnets reachable" + " via relays) cannot be defined at the same time for " + "shared network " << name << "(" + << shared_network_data->getPosition() << ")"); + } + + // Configure interface-id for remote interfaces, if defined + if (!ifaceid.unspecified() && !ifaceid.empty()) { + std::string ifaceid_value = ifaceid.get(); + OptionBuffer tmp(ifaceid_value.begin(), ifaceid_value.end()); + OptionPtr opt(new Option(Option::V6, D6O_INTERFACE_ID, tmp)); + shared_network->setInterfaceId(opt); + } + + // Set interface name. If it is defined, then subnets are available + // directly over specified network interface. + if (!iface.unspecified() && !iface.empty()) { + if (check_iface_ && !IfaceMgr::instance().getIface(iface)) { + ConstElementPtr error = shared_network_data->get("interface"); + isc_throw(DhcpConfigError, + "Specified network interface name " << iface + << " for shared network " << name + << " is not present in the system (" + << error->getPosition() << ")"); + } + shared_network->setIface(iface); + } + + if (shared_network_data->contains("rapid-commit")) { + shared_network->setRapidCommit(getBoolean(shared_network_data, + "rapid-commit")); + } + + if (shared_network_data->contains("option-data")) { + auto json = shared_network_data->get("option-data"); + // Create parser instance for option-data. + CfgOptionPtr cfg_option = shared_network->getCfgOption(); + auto parser = createOptionDataListParser(); + parser->parse(cfg_option, json); + } + + if (shared_network_data->contains("client-class")) { + std::string client_class = getString(shared_network_data, "client-class"); + if (!client_class.empty()) { + shared_network->allowClientClass(client_class); + } + } + + ConstElementPtr user_context = shared_network_data->get("user-context"); + if (user_context) { + shared_network->setContext(user_context); + } + + if (shared_network_data->contains("require-client-classes")) { + const std::vector<data::ElementPtr>& class_list = + shared_network_data->get("require-client-classes")->listValue(); + for (auto cclass = class_list.cbegin(); + cclass != class_list.cend(); ++cclass) { + if (((*cclass)->getType() != Element::string) || + (*cclass)->stringValue().empty()) { + isc_throw(DhcpConfigError, "invalid class name (" + << (*cclass)->getPosition() << ")"); + } + shared_network->requireClientClass((*cclass)->stringValue()); + } + } + + if (shared_network_data->contains("subnet6")) { + auto json = shared_network_data->get("subnet6"); + + // Create parser instance of subnet6. + auto parser = createSubnetsListParser(); + Subnet6Collection subnets; + parser->parse(subnets, json); + + // Add all returned subnets into shared network. + for (auto subnet = subnets.cbegin(); subnet != subnets.cend(); + ++subnet) { + shared_network->add(*subnet); + } + } + + if (shared_network_data->contains("relay")) { + auto relay_parms = shared_network_data->get("relay"); + if (relay_parms) { + RelayInfoParser parser(Option::V6); + Network::RelayInfoPtr relay_info(new Network::RelayInfo()); + parser.parse(relay_info, relay_parms); + shared_network->setRelayInfo(*relay_info); + } + } + + parseTeePercents(shared_network_data, network); + + // Parse DDNS parameters + parseDdnsParams(shared_network_data, network); + + // Parse lease cache parameters + parseCacheParams(shared_network_data, network); + } catch (const std::exception& ex) { + isc_throw(DhcpConfigError, ex.what() << " (" + << shared_network_data->getPosition() << ")"); + } + + // In order to take advantage of the dynamic inheritance of global + // parameters to a shared network we need to set a callback function + // for each shared network which can be used to fetch global parameters. + shared_network->setFetchGlobalsFn([]() -> ConstCfgGlobalsPtr { + return (CfgMgr::instance().getCurrentCfg()->getConfiguredGlobals()); + }); + + return (shared_network); +} + +boost::shared_ptr<OptionDataListParser> +SharedNetwork6Parser::createOptionDataListParser() const { + auto parser = boost::make_shared<OptionDataListParser>(AF_INET6); + return (parser); +} + +boost::shared_ptr<Subnets6ListConfigParser> +SharedNetwork6Parser::createSubnetsListParser() const { + auto parser = boost::make_shared<Subnets6ListConfigParser>(check_iface_); + return (parser); +} + +} // end of namespace isc::dhcp +} // end of namespace isc diff --git a/src/lib/dhcpsrv/parsers/shared_network_parser.h b/src/lib/dhcpsrv/parsers/shared_network_parser.h new file mode 100644 index 0000000..8b4ea01 --- /dev/null +++ b/src/lib/dhcpsrv/parsers/shared_network_parser.h @@ -0,0 +1,120 @@ +// Copyright (C) 2017-2021 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/. + +#ifndef SHARED_SUBNET_PARSER_H +#define SHARED_SUBNET_PARSER_H + +#include <cc/data.h> +#include <cc/simple_parser.h> +#include <dhcpsrv/cfg_subnets4.h> +#include <dhcpsrv/cfg_subnets6.h> +#include <dhcpsrv/shared_network.h> +#include <dhcpsrv/parsers/base_network_parser.h> +#include <dhcpsrv/parsers/dhcp_parsers.h> +#include <dhcpsrv/parsers/option_data_parser.h> +#include <boost/shared_ptr.hpp> + +namespace isc { +namespace dhcp { + +/// @brief Implements parser for IPv4 shared networks. +class SharedNetwork4Parser : public BaseNetworkParser { +public: + /// @brief Constructor. + /// + /// @param check_iface Check if the specified interface exists in + /// the system. + SharedNetwork4Parser(bool check_iface = true); + + /// @brief Virtual destructor. + virtual ~SharedNetwork4Parser() { + } + + /// @brief Parses shared configuration information for IPv4 shared network. + /// + /// @param shared_network_data Data element holding shared network + /// configuration to be parsed. + /// + /// @return Pointer to an object representing shared network. + /// @throw DhcpConfigError when shared network configuration is invalid. + SharedNetwork4Ptr + parse(const data::ConstElementPtr& shared_network_data); + +protected: + + /// @brief Returns an instance of the @c OptionDataListParser to + /// be used in parsing the option-data structure. + /// + /// This function can be overridden in the child classes to supply + /// a custom parser for option data. + /// + /// @return an instance of the @c OptionDataListParser(AF_INET). + virtual boost::shared_ptr<OptionDataListParser> createOptionDataListParser() const; + + /// @brief Returns an instance of the @c Subnets4ListConfigParser + /// to be used for parsing the subnets within the shared network. + /// + /// This function can be overridden in the child classes to supply + /// a custom parser for the subnets. + /// + /// @return an instance of the @c Subnets4ListConfigParser. + virtual boost::shared_ptr<Subnets4ListConfigParser> createSubnetsListParser() const; + + /// Check if the specified interface exists in the system. + bool check_iface_; +}; + +/// @brief Implements parser for IPv6 shared networks. +class SharedNetwork6Parser : public BaseNetworkParser { +public: + /// @brief Constructor. + /// + /// @param check_iface Check if the specified interface exists in + /// the system. + SharedNetwork6Parser(bool check_iface = true); + + /// @brief Virtual destructor. + virtual ~SharedNetwork6Parser() { + } + + /// @brief Parses shared configuration information for IPv6 shared network. + /// + /// @param shared_network_data Data element holding shared network + /// configuration to be parsed. + /// + /// @return Pointer to an object representing shared network. + /// @throw DhcpConfigError when shared network configuration is invalid. + SharedNetwork6Ptr + parse(const data::ConstElementPtr& shared_network_data); + +protected: + + /// @brief Returns an instance of the @c OptionDataListParser to + /// be used in parsing the option-data structure. + /// + /// This function can be overridden in the child classes to supply + /// a custom parser for option data. + /// + /// @return an instance of the @c OptionDataListParser(AF_INET6). + virtual boost::shared_ptr<OptionDataListParser> createOptionDataListParser() const; + + /// @brief Returns an instance of the @c Subnets6ListConfigParser + /// to be used for parsing the subnets within the shared network. + /// + /// This function can be overridden in the child classes to supply + /// a custom parser for the subnets. + /// + /// @return an instance of the @c Subnets6ListConfigParser. + virtual boost::shared_ptr<Subnets6ListConfigParser> createSubnetsListParser() const; + + /// Check if the specified interface exists in the system. + bool check_iface_; +}; + +} // enf of namespace isc::dhcp +} // end of namespace isc + +#endif // SHARED_SUBNET_PARSER_H diff --git a/src/lib/dhcpsrv/parsers/shared_networks_list_parser.h b/src/lib/dhcpsrv/parsers/shared_networks_list_parser.h new file mode 100644 index 0000000..5f49c8b --- /dev/null +++ b/src/lib/dhcpsrv/parsers/shared_networks_list_parser.h @@ -0,0 +1,95 @@ +// Copyright (C) 2017-2019 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/. + +#ifndef SHARED_NETWORKS_LIST_PARSER_H +#define SHARED_NETWORKS_LIST_PARSER_H + +#include <cc/data.h> +#include <cc/dhcp_config_error.h> +#include <cc/simple_parser.h> +#include <exceptions/exceptions.h> +#include <dhcpsrv/subnet.h> +#include <dhcpsrv/parsers/shared_network_parser.h> +#include <vector> + +namespace isc { +namespace dhcp { + +/// @brief Parser for a list of shared networks. +/// +/// This is a generic parser for a list of IPv4 or IPv6 shared networks. +/// +/// @tparam SharedNetworkParserType Type of the parser to be used for +/// parsing shared network, i.e. @ref SharedNetwork4Parser or +/// @ref SharedNetwork6Parser. +template<typename SharedNetworkParserType> +class SharedNetworksListParser : public data::SimpleParser { +public: + + /// @brief Constructor. + /// + /// @param check_iface Check if the specified interface exists in + /// the system. + SharedNetworksListParser(bool check_iface = true) + : check_iface_(check_iface) { + } + + /// @brief Parses a list of shared networks. + /// + /// @tparam CfgSharedNetworksTypePtr Type of the configuration structure + /// into which the result will be stored, i.e. @ref CfgSharedNetworks4 + /// or @ref CfgSharedNetworks6. + /// @param [out] cfg Shared networks configuration structure into which + /// the data should be parsed. + /// @param shared_networks_list_data List element holding a list of + /// shared networks. + /// + /// @throw DhcpConfigError when error has occurred, e.g. when networks + /// with duplicated names have been specified. + template<typename CfgSharedNetworksTypePtr> + void parse(CfgSharedNetworksTypePtr& cfg, + const data::ConstElementPtr& shared_networks_list_data) { + try { + // Get the C++ vector holding networks. + const std::vector<data::ElementPtr>& networks_list = + shared_networks_list_data->listValue(); + // Iterate over all networks and do the parsing. + for (auto network_element = networks_list.cbegin(); + network_element != networks_list.cend(); ++network_element) { + SharedNetworkParserType parser(check_iface_); + auto network = parser.parse(*network_element); + cfg->add(network); + } + } catch (const DhcpConfigError&) { + // Such exceptions are emitted by the lower level parsers and + // errors should already include element's positions. So, we + // simply rethrow. + throw; + + } catch (const std::exception& ex) { + // Other exceptions don't include positions of the elements, so + // we should append one. + isc_throw(DhcpConfigError, ex.what() << " (" + << shared_networks_list_data->getPosition() << ")"); + } + } + +protected: + /// Check if the specified interface exists in the system. + bool check_iface_; +}; + +/// @brief Type of the shared networks list parser for IPv4. +typedef SharedNetworksListParser<SharedNetwork4Parser> SharedNetworks4ListParser; + +/// @brief Type of the shared networks list parser for IPv6. +typedef SharedNetworksListParser<SharedNetwork6Parser> SharedNetworks6ListParser; + + +} // end of namespace isc::dhcp +} // end of namespace isc + +#endif // SHARED_NETWORKS_LIST_PARSER_H diff --git a/src/lib/dhcpsrv/parsers/simple_parser4.cc b/src/lib/dhcpsrv/parsers/simple_parser4.cc new file mode 100644 index 0000000..ba6a809 --- /dev/null +++ b/src/lib/dhcpsrv/parsers/simple_parser4.cc @@ -0,0 +1,533 @@ +// Copyright (C) 2016-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 <dhcpsrv/parsers/simple_parser4.h> +#include <cc/data.h> +#include <boost/foreach.hpp> +#include <iostream> + +using namespace isc::data; + +namespace isc { +namespace dhcp { +/// @brief This sets of arrays define the default values and +/// values inherited (derived) between various scopes. +/// +/// Each of those is documented in @file simple_parser4.cc. This +/// is different than most other comments in Kea code. The reason +/// for placing those in .cc rather than .h file is that it +/// is expected to be one centralized place to look at for +/// the default values. This is expected to be looked at also by +/// people who are not skilled in C or C++, so they may be +/// confused with the differences between declaration and definition. +/// As such, there's one file to look at that hopefully is readable +/// without any C or C++ skills. +/// +/// @{ + +/// @brief This table defines all global parameters in DHCPv4. +/// +/// Boolean, integer, real and string types are for scalar parameters, +/// list and map types for entries. +/// Order follows global_param rule in bison grammar. +const SimpleKeywords SimpleParser4::GLOBAL4_PARAMETERS = { + { "valid-lifetime", Element::integer }, + { "min-valid-lifetime", Element::integer }, + { "max-valid-lifetime", Element::integer }, + { "renew-timer", Element::integer }, + { "rebind-timer", Element::integer }, + { "decline-probation-period", Element::integer }, + { "subnet4", Element::list }, + { "shared-networks", Element::list }, + { "interfaces-config", Element::map }, + { "lease-database", Element::map }, + { "hosts-database", Element::map }, + { "hosts-databases", Element::list }, + { "host-reservation-identifiers", Element::list }, + { "client-classes", Element::list }, + { "option-def", Element::list }, + { "option-data", Element::list }, + { "hooks-libraries", Element::list }, + { "expired-leases-processing", Element::map }, + { "dhcp4o6-port", Element::integer }, + { "control-socket", Element::map }, + { "dhcp-queue-control", Element::map }, + { "dhcp-ddns", Element::map }, + { "echo-client-id", Element::boolean }, + { "match-client-id", Element::boolean }, + { "authoritative", Element::boolean }, + { "next-server", Element::string }, + { "server-hostname", Element::string }, + { "boot-file-name", Element::string }, + { "user-context", Element::map }, + { "comment", Element::string }, + { "sanity-checks", Element::map }, + { "reservations", Element::list }, + { "config-control", Element::map }, + { "server-tag", Element::string }, + { "reservation-mode", Element::string }, + { "reservations-global", Element::boolean }, + { "reservations-in-subnet", Element::boolean }, + { "reservations-out-of-pool", Element::boolean }, + { "calculate-tee-times", Element::boolean }, + { "t1-percent", Element::real }, + { "t2-percent", Element::real }, + { "loggers", Element::list }, + { "hostname-char-set", Element::string }, + { "hostname-char-replacement", Element::string }, + { "ddns-send-updates", Element::boolean }, + { "ddns-override-no-update", Element::boolean }, + { "ddns-override-client-update", Element::boolean }, + { "ddns-replace-client-name", Element::string }, + { "ddns-generated-prefix", Element::string }, + { "ddns-qualifying-suffix", Element::string }, + { "store-extended-info", Element::boolean }, + { "statistic-default-sample-count", Element::integer }, + { "statistic-default-sample-age", Element::integer }, + { "multi-threading", Element::map }, + { "cache-threshold", Element::real }, + { "cache-max-age", Element::integer }, + { "early-global-reservations-lookup", Element::boolean }, + { "ip-reservations-unique", Element::boolean }, + { "reservations-lookup-first", Element::boolean }, + { "ddns-update-on-renew", Element::boolean }, + { "ddns-use-conflict-resolution", Element::boolean }, + { "compatibility", Element::map }, + { "parked-packet-limit", Element::integer }, +}; + +/// @brief This table defines default global values for DHCPv4 +/// +/// Some of the global parameters defined in the global scope (i.e. directly +/// in Dhcp4) are optional. If not defined, the following values will be +/// used. +const SimpleDefaults SimpleParser4::GLOBAL4_DEFAULTS = { + { "valid-lifetime", Element::integer, "7200" }, + { "decline-probation-period", Element::integer, "86400" }, // 24h + { "dhcp4o6-port", Element::integer, "0" }, + { "echo-client-id", Element::boolean, "true" }, + { "match-client-id", Element::boolean, "true" }, + { "authoritative", Element::boolean, "false" }, + { "next-server", Element::string, "0.0.0.0" }, + { "server-hostname", Element::string, "" }, + { "boot-file-name", Element::string, "" }, + { "server-tag", Element::string, "" }, + { "reservations-global", Element::boolean, "false" }, + { "reservations-in-subnet", Element::boolean, "true" }, + { "reservations-out-of-pool", Element::boolean, "false" }, + { "calculate-tee-times", Element::boolean, "false" }, + { "t1-percent", Element::real, ".50" }, + { "t2-percent", Element::real, ".875" }, + { "ddns-send-updates", Element::boolean, "true" }, + { "ddns-override-no-update", Element::boolean, "false" }, + { "ddns-override-client-update", Element::boolean, "false" }, + { "ddns-replace-client-name", Element::string, "never" }, + { "ddns-generated-prefix", Element::string, "myhost" }, + { "ddns-qualifying-suffix", Element::string, "" }, + { "hostname-char-set", Element::string, "[^A-Za-z0-9.-]" }, + { "hostname-char-replacement", Element::string, "" }, + { "store-extended-info", Element::boolean, "false" }, + { "statistic-default-sample-count", Element::integer, "20" }, + { "statistic-default-sample-age", Element::integer, "0" }, + { "early-global-reservations-lookup", Element::boolean, "false" }, + { "ip-reservations-unique", Element::boolean, "true" }, + { "reservations-lookup-first", Element::boolean, "false" }, + { "ddns-update-on-renew", Element::boolean, "false" }, + { "ddns-use-conflict-resolution", Element::boolean, "true" }, + { "parked-packet-limit", Element::integer, "256" }, +}; + +/// @brief This table defines all option definition parameters. +/// +/// Boolean, integer, real and string types are for scalar parameters, +/// list and map types for entries. +/// Order follows option_def_param rules in bison grammar. +const SimpleKeywords SimpleParser4::OPTION4_DEF_PARAMETERS = { + { "name", Element::string }, + { "code", Element::integer }, + { "type", Element::string }, + { "record-types", Element::string }, + { "space", Element::string }, + { "encapsulate", Element::string }, + { "array", Element::boolean, }, + { "user-context", Element::map }, + { "comment", Element::string }, + { "metadata", Element::map } +}; + +/// @brief This table defines default values for option definitions in DHCPv4. +/// +/// Dhcp4 may contain an array called option-def that enumerates new option +/// definitions. This array lists default values for those option definitions. +const SimpleDefaults SimpleParser4::OPTION4_DEF_DEFAULTS = { + { "record-types", Element::string, ""}, + { "space", Element::string, "dhcp4"}, // DHCP4_OPTION_SPACE + { "array", Element::boolean, "false"}, + { "encapsulate", Element::string, "" } +}; + +/// @brief This table defines all option parameters. +/// +/// Boolean, integer, real and string types are for scalar parameters, +/// list and map types for entries. +/// Order follows option_param rules in bison grammar. +const SimpleKeywords SimpleParser4::OPTION4_PARAMETERS = { + { "name", Element::string }, + { "data", Element::string }, + { "code", Element::integer }, + { "space", Element::string }, + { "csv-format", Element::boolean }, + { "always-send", Element::boolean }, + { "user-context", Element::map }, + { "comment", Element::string }, + { "metadata", Element::map } +}; + +/// @brief This table defines default values for options in DHCPv4. +/// +/// Dhcp4 usually contains option values (option-data) defined in global, +/// subnet, class or host reservations scopes. This array lists default values +/// for those option-data declarations. +const SimpleDefaults SimpleParser4::OPTION4_DEFAULTS = { + { "space", Element::string, "dhcp4"}, // DHCP4_OPTION_SPACE + { "csv-format", Element::boolean, "true"}, + { "always-send", Element::boolean, "false"} +}; + +/// @brief This table defines all subnet parameters for DHCPv4. +/// +/// Boolean, integer, real and string types are for scalar parameters, +/// list and map types for entries. +/// Order follows subnet4_param rule in bison grammar. +const SimpleKeywords SimpleParser4::SUBNET4_PARAMETERS = { + { "valid-lifetime", Element::integer }, + { "min-valid-lifetime", Element::integer }, + { "max-valid-lifetime", Element::integer }, + { "renew-timer", Element::integer }, + { "rebind-timer", Element::integer }, + { "option-data", Element::list }, + { "pools", Element::list }, + { "subnet", Element::string }, + { "interface", Element::string }, + { "id", Element::integer }, + { "client-class", Element::string }, + { "require-client-classes", Element::list }, + { "reservations", Element::list }, + { "reservation-mode", Element::string }, + { "reservations-global", Element::boolean }, + { "reservations-in-subnet", Element::boolean }, + { "reservations-out-of-pool", Element::boolean }, + { "relay", Element::map }, + { "match-client-id", Element::boolean }, + { "authoritative", Element::boolean }, + { "next-server", Element::string }, + { "server-hostname", Element::string }, + { "boot-file-name", Element::string }, + { "4o6-interface", Element::string }, + { "4o6-interface-id", Element::string }, + { "4o6-subnet", Element::string }, + { "user-context", Element::map }, + { "comment", Element::string }, + { "calculate-tee-times", Element::boolean }, + { "t1-percent", Element::real }, + { "t2-percent", Element::real }, + { "ddns-send-updates", Element::boolean }, + { "ddns-override-no-update", Element::boolean }, + { "ddns-override-client-update", Element::boolean }, + { "ddns-replace-client-name", Element::string }, + { "ddns-generated-prefix", Element::string }, + { "ddns-qualifying-suffix", Element::string }, + { "hostname-char-set", Element::string }, + { "hostname-char-replacement", Element::string }, + { "store-extended-info", Element::boolean }, + { "metadata", Element::map }, + { "cache-threshold", Element::real }, + { "cache-max-age", Element::integer }, + { "ddns-update-on-renew", Element::boolean }, + { "ddns-use-conflict-resolution", Element::boolean } +}; + +/// @brief This table defines default values for each IPv4 subnet. +/// +/// Note: When updating this array, please also update SHARED_SUBNET4_DEFAULTS +/// below. In most cases, those two should be kept in sync, except cases +/// where a parameter can be derived from shared-networks, but is not +/// defined on global level. Currently there are two such parameters: +/// interface and reservation-mode +const SimpleDefaults SimpleParser4::SUBNET4_DEFAULTS = { + { "id", Element::integer, "0" }, // 0 means autogenerate + { "interface", Element::string, "" }, + { "client-class", Element::string, "" }, + { "4o6-interface", Element::string, "" }, + { "4o6-interface-id", Element::string, "" }, + { "4o6-subnet", Element::string, "" }, +}; + +/// @brief This table defines default values for each IPv4 subnet that is +/// part of a shared network +/// +/// This is mostly the same as @ref SUBNET4_DEFAULTS, except two parameters +/// that can be derived from shared-network, but cannot from global scope. +/// Those are: interface and reservation-mode. +const SimpleDefaults SimpleParser4::SHARED_SUBNET4_DEFAULTS = { + { "id", Element::integer, "0" }, // 0 means autogenerate + { "4o6-interface", Element::string, "" }, + { "4o6-interface-id", Element::string, "" }, + { "4o6-subnet", Element::string, "" }, +}; + +/// @brief List of parameters that can be inherited to subnet4 scope. +/// +/// Some parameters may be defined on both global (directly in Dhcp4) and +/// subnet (Dhcp4/subnet4/...) scope. If not defined in the subnet scope, +/// the value is being inherited (derived) from the global scope. This +/// array lists all of such parameters. +/// +/// This list is also used for inheriting from global to shared networks +/// and from shared networks to subnets within it. +const ParamsList SimpleParser4::INHERIT_TO_SUBNET4 = { + "rebind-timer", + "relay", + "renew-timer", + "valid-lifetime", + "min-valid-lifetime", + "max-valid-lifetime", + "calculate-tee-times", + "t1-percent", + "t2-percent", + "store-extended-info", + "cache-threshold", + "cache-max-age" +}; + +/// @brief This table defines all pool parameters. +/// +/// Boolean, integer, real and string types are for scalar parameters, +/// list and map types for entries. +/// Order follows pool_param rules in bison grammar. +const SimpleKeywords SimpleParser4::POOL4_PARAMETERS = { + { "pool", Element::string }, + { "option-data", Element::list }, + { "client-class", Element::string }, + { "require-client-classes", Element::list }, + { "user-context", Element::map }, + { "comment", Element::string }, + { "metadata", Element::map } +}; + +/// @brief This table defines all shared network parameters for DHCPv4. +/// +/// Boolean, integer, real and string types are for scalar parameters, +/// list and map types for entries. +/// Order follows shared_network_param rule in bison grammar. +const SimpleKeywords SimpleParser4::SHARED_NETWORK4_PARAMETERS = { + { "name", Element::string }, + { "subnet4", Element::list }, + { "interface", Element::string }, + { "renew-timer", Element::integer }, + { "rebind-timer", Element::integer }, + { "option-data", Element::list }, + { "match-client-id", Element::boolean }, + { "authoritative", Element::boolean }, + { "next-server", Element::string }, + { "server-hostname", Element::string }, + { "boot-file-name", Element::string }, + { "relay", Element::map }, + { "reservation-mode", Element::string }, + { "reservations-global", Element::boolean }, + { "reservations-in-subnet", Element::boolean }, + { "reservations-out-of-pool", Element::boolean }, + { "client-class", Element::string }, + { "require-client-classes", Element::list }, + { "valid-lifetime", Element::integer }, + { "min-valid-lifetime", Element::integer }, + { "max-valid-lifetime", Element::integer }, + { "user-context", Element::map }, + { "comment", Element::string }, + { "calculate-tee-times", Element::boolean }, + { "t1-percent", Element::real }, + { "t2-percent", Element::real }, + { "ddns-send-updates", Element::boolean }, + { "ddns-override-no-update", Element::boolean }, + { "ddns-override-client-update", Element::boolean }, + { "ddns-replace-client-name", Element::string }, + { "ddns-generated-prefix", Element::string }, + { "ddns-qualifying-suffix", Element::string }, + { "hostname-char-set", Element::string }, + { "hostname-char-replacement", Element::string }, + { "store-extended-info", Element::boolean }, + { "metadata", Element::map }, + { "cache-threshold", Element::real }, + { "cache-max-age", Element::integer }, + { "ddns-update-on-renew", Element::boolean }, + { "ddns-use-conflict-resolution", Element::boolean } +}; + +/// @brief This table defines default values for each IPv4 shared network. +const SimpleDefaults SimpleParser4::SHARED_NETWORK4_DEFAULTS = { + { "client-class", Element::string, "" }, + { "interface", Element::string, "" } +}; + +/// @brief This table defines default values for interfaces for DHCPv4. +const SimpleDefaults SimpleParser4::IFACE4_DEFAULTS = { + { "re-detect", Element::boolean, "true" } +}; + +/// @brief This table defines default values for dhcp-queue-control in DHCPv4. +const SimpleDefaults SimpleParser4::DHCP_QUEUE_CONTROL4_DEFAULTS = { + { "enable-queue", Element::boolean, "false"}, + { "queue-type", Element::string, "kea-ring4"}, + { "capacity", Element::integer, "64"} +}; + +/// @brief This table defines default values for multi-threading in DHCPv4. +const SimpleDefaults SimpleParser4::DHCP_MULTI_THREADING4_DEFAULTS = { + { "enable-multi-threading", Element::boolean, "false" }, + { "thread-pool-size", Element::integer, "0" }, + { "packet-queue-size", Element::integer, "64" } +}; + +/// @brief This defines default values for sanity checking for DHCPv4. +const SimpleDefaults SimpleParser4::SANITY_CHECKS4_DEFAULTS = { + { "lease-checks", Element::string, "warn" } +}; + +/// @} + +/// --------------------------------------------------------------------------- +/// --- end of default values ------------------------------------------------- +/// --------------------------------------------------------------------------- + +size_t SimpleParser4::setAllDefaults(ElementPtr global) { + size_t cnt = 0; + + // Set global defaults first. + cnt = setDefaults(global, GLOBAL4_DEFAULTS); + + // Now set option definition defaults for each specified option definition + ConstElementPtr option_defs = global->get("option-def"); + if (option_defs) { + BOOST_FOREACH(ElementPtr option_def, option_defs->listValue()) { + cnt += SimpleParser::setDefaults(option_def, OPTION4_DEF_DEFAULTS); + } + } + + // Set the defaults for option data + ConstElementPtr options = global->get("option-data"); + if (options) { + cnt += setListDefaults(options, OPTION4_DEFAULTS); + } + + // Now set the defaults for defined subnets + ConstElementPtr subnets = global->get("subnet4"); + if (subnets) { + cnt += setListDefaults(subnets, SUBNET4_DEFAULTS); + } + + // Set the defaults for interfaces config + ConstElementPtr ifaces_cfg = global->get("interfaces-config"); + if (ifaces_cfg) { + ElementPtr mutable_cfg = boost::const_pointer_cast<Element>(ifaces_cfg); + cnt += setDefaults(mutable_cfg, IFACE4_DEFAULTS); + } + + // Set defaults for shared networks + ConstElementPtr shared = global->get("shared-networks"); + if (shared) { + BOOST_FOREACH(ElementPtr net, shared->listValue()) { + + cnt += setDefaults(net, SHARED_NETWORK4_DEFAULTS); + + ConstElementPtr subs = net->get("subnet4"); + if (subs) { + cnt += setListDefaults(subs, SHARED_SUBNET4_DEFAULTS); + } + } + } + + // Set the defaults for dhcp-queue-control. If the element isn't + // there we'll add it. + ConstElementPtr queue_control = global->get("dhcp-queue-control"); + ElementPtr mutable_cfg; + if (queue_control) { + mutable_cfg = boost::const_pointer_cast<Element>(queue_control); + } else { + mutable_cfg = Element::createMap(); + global->set("dhcp-queue-control", mutable_cfg); + } + + cnt += setDefaults(mutable_cfg, DHCP_QUEUE_CONTROL4_DEFAULTS); + + // Set the defaults for multi-threading. If the element isn't there + // we'll add it. + ConstElementPtr multi_threading = global->get("multi-threading"); + if (multi_threading) { + mutable_cfg = boost::const_pointer_cast<Element>(multi_threading); + } else { + mutable_cfg = Element::createMap(); + global->set("multi-threading", mutable_cfg); + } + + cnt += setDefaults(mutable_cfg, DHCP_MULTI_THREADING4_DEFAULTS); + + // Set the defaults for sanity-checks. If the element isn't + // there we'll add it. + ConstElementPtr sanity_checks = global->get("sanity-checks"); + if (sanity_checks) { + mutable_cfg = boost::const_pointer_cast<Element>(sanity_checks); + } else { + mutable_cfg = Element::createMap(); + global->set("sanity-checks", mutable_cfg); + } + + cnt += setDefaults(mutable_cfg, SANITY_CHECKS4_DEFAULTS); + + return (cnt); +} + +size_t SimpleParser4::deriveParameters(ElementPtr global) { + size_t cnt = 0; + + // Now derive global parameters into subnets. + ConstElementPtr subnets = global->get("subnet4"); + if (subnets) { + BOOST_FOREACH(ElementPtr single_subnet, subnets->listValue()) { + cnt += SimpleParser::deriveParams(global, single_subnet, + INHERIT_TO_SUBNET4); + } + } + + // Deriving parameters for shared networks is a bit more involved. + // First, the shared-network level derives from global, and then + // subnets within derive from it. + ConstElementPtr shared = global->get("shared-networks"); + if (shared) { + BOOST_FOREACH(ElementPtr net, shared->listValue()) { + // First try to inherit the parameters from shared network, + // if defined there. + // Then try to inherit them from global. + cnt += SimpleParser::deriveParams(global, net, + INHERIT_TO_SUBNET4); + + // Now we need to go thrugh all the subnets in this net. + subnets = net->get("subnet4"); + if (subnets) { + BOOST_FOREACH(ElementPtr single_subnet, subnets->listValue()) { + cnt += SimpleParser::deriveParams(net, single_subnet, + INHERIT_TO_SUBNET4); + } + } + } + } + + return (cnt); +} + +} // namespace dhcp +} // namespace isc diff --git a/src/lib/dhcpsrv/parsers/simple_parser4.h b/src/lib/dhcpsrv/parsers/simple_parser4.h new file mode 100644 index 0000000..e209ab4 --- /dev/null +++ b/src/lib/dhcpsrv/parsers/simple_parser4.h @@ -0,0 +1,69 @@ +// Copyright (C) 2016-2020 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/. + +#ifndef SIMPLE_PARSER4_H +#define SIMPLE_PARSER4_H + +#include <cc/simple_parser.h> + +namespace isc { +namespace dhcp { + +/// @brief SimpleParser specialized for DHCPv4 +/// +/// This class is a @ref isc::data::SimpleParser dedicated to DHCPv4 parser. +/// In particular, it contains all the default values and names of the +/// parameters that are to be derived (inherited) between scopes. +/// For the actual values, see @file simple_parser4.cc +class SimpleParser4 : public isc::data::SimpleParser { +public: + + /// @brief Sets all defaults for DHCPv4 configuration + /// + /// This method sets global, option data and option definitions defaults. + /// + /// @param global scope to be filled in with defaults. + /// @return number of default values added + static size_t setAllDefaults(isc::data::ElementPtr global); + + /// @brief Derives (inherits) all parameters from global to more specific scopes. + /// + /// This method currently does the following: + /// - derives global parameters to subnets (lifetimes for now) + /// @param global scope to be modified if needed (subnet4 will be extracted) + /// @return number of default values derived + static size_t deriveParameters(isc::data::ElementPtr global); + + // see simple_parser4.cc for comments for those parameters + static const isc::data::SimpleKeywords GLOBAL4_PARAMETERS; + static const isc::data::SimpleDefaults GLOBAL4_DEFAULTS; + + static const isc::data::SimpleKeywords OPTION4_DEF_PARAMETERS; + static const isc::data::SimpleDefaults OPTION4_DEF_DEFAULTS; + + static const isc::data::SimpleKeywords OPTION4_PARAMETERS; + static const isc::data::SimpleDefaults OPTION4_DEFAULTS; + + static const isc::data::SimpleKeywords SUBNET4_PARAMETERS; + static const isc::data::SimpleDefaults SUBNET4_DEFAULTS; + static const isc::data::SimpleDefaults SHARED_SUBNET4_DEFAULTS; + static const isc::data::ParamsList INHERIT_TO_SUBNET4; + + static const isc::data::SimpleKeywords POOL4_PARAMETERS; + + static const isc::data::SimpleKeywords SHARED_NETWORK4_PARAMETERS; + static const isc::data::SimpleDefaults SHARED_NETWORK4_DEFAULTS; + + static const isc::data::SimpleDefaults IFACE4_DEFAULTS; + static const isc::data::SimpleDefaults DHCP_QUEUE_CONTROL4_DEFAULTS; + static const isc::data::SimpleDefaults DHCP_MULTI_THREADING4_DEFAULTS; + static const isc::data::SimpleDefaults SANITY_CHECKS4_DEFAULTS; +}; + +} // namespace dhcp +} // namespace isc + +#endif diff --git a/src/lib/dhcpsrv/parsers/simple_parser6.cc b/src/lib/dhcpsrv/parsers/simple_parser6.cc new file mode 100644 index 0000000..6ed05ff --- /dev/null +++ b/src/lib/dhcpsrv/parsers/simple_parser6.cc @@ -0,0 +1,547 @@ +// Copyright (C) 2016-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 <cc/data.h> +#include <dhcpsrv/parsers/simple_parser6.h> + +#include <boost/foreach.hpp> + +using namespace isc::data; + +namespace isc { +namespace dhcp { +/// @brief This sets of arrays define the default values and +/// values inherited (derived) between various scopes. +/// +/// Each of those is documented in @file simple_parser6.cc. This +/// is different than most other comments in Kea code. The reason +/// for placing those in .cc rather than .h file is that it +/// is expected to be one centralized place to look at for +/// the default values. This is expected to be looked at also by +/// people who are not skilled in C or C++, so they may be +/// confused with the differences between declaration and definition. +/// As such, there's one file to look at that hopefully is readable +/// without any C or C++ skills. +/// +/// @{ + +/// @brief This table defines all global parameters in DHCPv6. +/// +/// Boolean, integer, real and string types are for scalar parameters, +/// list and map types for entries. +/// Order follows global_param rule in bison grammar. +const SimpleKeywords SimpleParser6::GLOBAL6_PARAMETERS = { + { "data-directory", Element::string }, + { "preferred-lifetime", Element::integer }, + { "min-preferred-lifetime", Element::integer }, + { "max-preferred-lifetime", Element::integer }, + { "valid-lifetime", Element::integer }, + { "min-valid-lifetime", Element::integer }, + { "max-valid-lifetime", Element::integer }, + { "renew-timer", Element::integer }, + { "rebind-timer", Element::integer }, + { "decline-probation-period", Element::integer }, + { "subnet6", Element::list }, + { "shared-networks", Element::list }, + { "interfaces-config", Element::map }, + { "lease-database", Element::map }, + { "hosts-database", Element::map }, + { "hosts-databases", Element::list }, + { "mac-sources", Element::list }, + { "relay-supplied-options", Element::list }, + { "host-reservation-identifiers", Element::list }, + { "client-classes", Element::list }, + { "option-def", Element::list }, + { "option-data", Element::list }, + { "hooks-libraries", Element::list }, + { "expired-leases-processing", Element::map }, + { "server-id", Element::map }, + { "dhcp4o6-port", Element::integer }, + { "control-socket", Element::map }, + { "dhcp-queue-control", Element::map }, + { "dhcp-ddns", Element::map }, + { "user-context", Element::map }, + { "comment", Element::string }, + { "sanity-checks", Element::map }, + { "reservations", Element::list }, + { "config-control", Element::map }, + { "server-tag", Element::string }, + { "reservation-mode", Element::string }, + { "reservations-global", Element::boolean }, + { "reservations-in-subnet", Element::boolean }, + { "reservations-out-of-pool", Element::boolean }, + { "calculate-tee-times", Element::boolean }, + { "t1-percent", Element::real }, + { "t2-percent", Element::real }, + { "loggers", Element::list }, + { "hostname-char-set", Element::string }, + { "hostname-char-replacement", Element::string }, + { "ddns-send-updates", Element::boolean }, + { "ddns-override-no-update", Element::boolean }, + { "ddns-override-client-update", Element::boolean }, + { "ddns-replace-client-name", Element::string }, + { "ddns-generated-prefix", Element::string }, + { "ddns-qualifying-suffix", Element::string }, + { "store-extended-info", Element::boolean }, + { "statistic-default-sample-count", Element::integer }, + { "statistic-default-sample-age", Element::integer }, + { "multi-threading", Element::map }, + { "cache-threshold", Element::real }, + { "cache-max-age", Element::integer }, + { "early-global-reservations-lookup", Element::boolean }, + { "ip-reservations-unique", Element::boolean }, + { "reservations-lookup-first", Element::boolean }, + { "ddns-update-on-renew", Element::boolean }, + { "ddns-use-conflict-resolution", Element::boolean }, + { "compatibility", Element::map }, + { "parked-packet-limit", Element::integer }, +}; + +/// @brief This table defines default global values for DHCPv6 +/// +/// Some of the global parameters defined in the global scope (i.e. directly +/// in Dhcp6) are optional. If not defined, the following values will be +/// used. +const SimpleDefaults SimpleParser6::GLOBAL6_DEFAULTS = { + { "preferred-lifetime", Element::integer, "3600" }, + { "valid-lifetime", Element::integer, "7200" }, + { "decline-probation-period", Element::integer, "86400" }, // 24h + { "dhcp4o6-port", Element::integer, "0" }, + { "server-tag", Element::string, "" }, + { "reservations-global", Element::boolean, "false" }, + { "reservations-in-subnet", Element::boolean, "true" }, + { "reservations-out-of-pool", Element::boolean, "false" }, + { "calculate-tee-times", Element::boolean, "true" }, + { "t1-percent", Element::real, ".50" }, + { "t2-percent", Element::real, ".80" }, + { "ddns-send-updates", Element::boolean, "true" }, + { "ddns-override-no-update", Element::boolean, "false" }, + { "ddns-override-client-update", Element::boolean, "false" }, + { "ddns-replace-client-name", Element::string, "never" }, + { "ddns-generated-prefix", Element::string, "myhost" }, + { "ddns-qualifying-suffix", Element::string, "" }, + { "hostname-char-set", Element::string, "[^A-Za-z0-9.-]" }, + { "hostname-char-replacement", Element::string, "" }, + { "store-extended-info", Element::boolean, "false" }, + { "statistic-default-sample-count", Element::integer, "20" }, + { "statistic-default-sample-age", Element::integer, "0" }, + { "early-global-reservations-lookup", Element::boolean, "false" }, + { "ip-reservations-unique", Element::boolean, "true" }, + { "reservations-lookup-first", Element::boolean, "false" }, + { "ddns-update-on-renew", Element::boolean, "false" }, + { "ddns-use-conflict-resolution", Element::boolean, "true" }, + { "parked-packet-limit", Element::integer, "256" } +}; + +/// @brief This table defines all option definition parameters. +/// +/// Boolean, integer, real and string types are for scalar parameters, +/// list and map types for entries. +/// Order follows option_def_param rules in bison grammar. +const SimpleKeywords SimpleParser6::OPTION6_DEF_PARAMETERS = { + { "name", Element::string }, + { "code", Element::integer }, + { "type", Element::string }, + { "record-types", Element::string }, + { "space", Element::string }, + { "encapsulate", Element::string }, + { "array", Element::boolean, }, + { "user-context", Element::map }, + { "comment", Element::string }, + { "metadata", Element::map } +}; + +/// @brief This table defines default values for option definitions in DHCPv6. +/// +/// Dhcp6 may contain an array called option-def that enumerates new option +/// definitions. This array lists default values for those option definitions. +const SimpleDefaults SimpleParser6::OPTION6_DEF_DEFAULTS = { + { "record-types", Element::string, ""}, + { "space", Element::string, "dhcp6"}, // DHCP6_OPTION_SPACE + { "array", Element::boolean, "false"}, + { "encapsulate", Element::string, "" } +}; + +/// @brief This table defines all option parameters. +/// +/// Boolean, integer, real and string types are for scalar parameters, +/// list and map types for entries. +/// Order follows option_param rules in bison grammar. +const SimpleKeywords SimpleParser6::OPTION6_PARAMETERS = { + { "name", Element::string }, + { "data", Element::string }, + { "code", Element::integer }, + { "space", Element::string }, + { "csv-format", Element::boolean }, + { "always-send", Element::boolean }, + { "user-context", Element::map }, + { "comment", Element::string }, + { "metadata", Element::map } +}; + +/// @brief This table defines default values for options in DHCPv6. +/// +/// Dhcp6 usually contains option values (option-data) defined in global, +/// subnet, class or host reservations scopes. This array lists default values +/// for those option-data declarations. +const SimpleDefaults SimpleParser6::OPTION6_DEFAULTS = { + { "space", Element::string, "dhcp6"}, // DHCP6_OPTION_SPACE + { "csv-format", Element::boolean, "true"}, + { "always-send", Element::boolean, "false"} +}; + +/// @brief This table defines all subnet parameters for DHCPv6. +/// +/// Boolean, integer, real and string types are for scalar parameters, +/// list and map types for entries. +/// Order follows subnet6_param rule in bison grammar. +const SimpleKeywords SimpleParser6::SUBNET6_PARAMETERS = { + { "preferred-lifetime", Element::integer }, + { "min-preferred-lifetime", Element::integer }, + { "max-preferred-lifetime", Element::integer }, + { "valid-lifetime", Element::integer }, + { "min-valid-lifetime", Element::integer }, + { "max-valid-lifetime", Element::integer }, + { "renew-timer", Element::integer }, + { "rebind-timer", Element::integer }, + { "option-data", Element::list }, + { "pools", Element::list }, + { "pd-pools", Element::list }, + { "subnet", Element::string }, + { "interface", Element::string }, + { "interface-id", Element::string }, + { "id", Element::integer }, + { "rapid-commit", Element::boolean }, + { "client-class", Element::string }, + { "require-client-classes", Element::list }, + { "reservations", Element::list }, + { "reservation-mode", Element::string }, + { "reservations-global", Element::boolean }, + { "reservations-in-subnet", Element::boolean }, + { "reservations-out-of-pool", Element::boolean }, + { "relay", Element::map }, + { "user-context", Element::map }, + { "comment", Element::string }, + { "calculate-tee-times", Element::boolean }, + { "t1-percent", Element::real }, + { "t2-percent", Element::real }, + { "ddns-send-updates", Element::boolean }, + { "ddns-override-no-update", Element::boolean }, + { "ddns-override-client-update", Element::boolean }, + { "ddns-replace-client-name", Element::string }, + { "ddns-generated-prefix", Element::string }, + { "ddns-qualifying-suffix", Element::string }, + { "hostname-char-set", Element::string }, + { "hostname-char-replacement", Element::string }, + { "store-extended-info", Element::boolean }, + { "metadata", Element::map }, + { "cache-threshold", Element::real }, + { "cache-max-age", Element::integer }, + { "ddns-update-on-renew", Element::boolean }, + { "ddns-use-conflict-resolution", Element::boolean } +}; + +/// @brief This table defines default values for each IPv6 subnet. +/// +/// Note: When updating this array, please also update SHARED_SUBNET6_DEFAULTS +/// below. In most cases, those two should be kept in sync, except cases +/// where a parameter can be derived from shared-networks, but is not +/// defined on global level. +const SimpleDefaults SimpleParser6::SUBNET6_DEFAULTS = { + { "id", Element::integer, "0" }, // 0 means autogenerate + { "interface", Element::string, "" }, + { "client-class", Element::string, "" }, + { "rapid-commit", Element::boolean, "false" }, // rapid-commit disabled by default + { "interface-id", Element::string, "" } +}; + +/// @brief This table defines default values for each IPv6 subnet that is +/// part of a shared network +/// +/// This is mostly the same as @ref SUBNET6_DEFAULTS, except the parameters +/// that can be derived from shared-network, but cannot from global scope. +const SimpleDefaults SimpleParser6::SHARED_NETWORK6_DEFAULTS = { + { "client-class", Element::string, "" }, + { "interface", Element::string, "" }, + { "interface-id", Element::string, "" }, + { "rapid-commit", Element::boolean, "false" } // rapid-commit disabled by default +}; + +/// @brief List of parameters that can be inherited from the global to subnet6 scope. +/// +/// Some parameters may be defined on both global (directly in Dhcp6) and +/// subnet (Dhcp6/subnet6/...) scope. If not defined in the subnet scope, +/// the value is being inherited (derived) from the global scope. This +/// array lists all of such parameters. +/// +/// This list is also used for inheriting from global to shared networks +/// and from shared networks to subnets within it. +const ParamsList SimpleParser6::INHERIT_TO_SUBNET6 = { + "preferred-lifetime", + "min-preferred-lifetime", + "max-preferred-lifetime", + "rebind-timer", + "relay", + "renew-timer", + "valid-lifetime", + "min-valid-lifetime", + "max-valid-lifetime", + "calculate-tee-times", + "t1-percent", + "t2-percent", + "store-extended-info", + "cache-threshold", + "cache-max-age" +}; + +/// @brief This table defines all pool parameters. +/// +/// Boolean, integer, real and string types are for scalar parameters, +/// list and map types for entries. +/// Order follows pool_param rules in bison grammar. +const SimpleKeywords SimpleParser6::POOL6_PARAMETERS = { + { "pool", Element::string }, + { "option-data", Element::list }, + { "client-class", Element::string }, + { "require-client-classes", Element::list }, + { "user-context", Element::map }, + { "comment", Element::string }, + { "metadata", Element::map } +}; + +/// @brief This table defines all prefix delegation pool parameters. +/// +/// Boolean, integer, real and string types are for scalar parameters, +/// list and map types for entries. +/// Order follows pd_pool_param rules in bison grammar. +const SimpleKeywords SimpleParser6::PD_POOL6_PARAMETERS = { + { "prefix", Element::string }, + { "prefix-len", Element::integer }, + { "delegated-len", Element::integer }, + { "option-data", Element::list }, + { "client-class", Element::string }, + { "require-client-classes", Element::list }, + { "excluded-prefix", Element::string }, + { "excluded-prefix-len", Element::integer }, + { "user-context", Element::map }, + { "comment", Element::string }, + { "metadata", Element::map } +}; + +/// @brief This table defines all shared network parameters for DHCPv6. +/// +/// Boolean, integer, real and string types are for scalar parameters, +/// list and map types for entries. +/// Order follows shared_network_param rule in bison grammar. +const SimpleKeywords SimpleParser6::SHARED_NETWORK6_PARAMETERS = { + { "name", Element::string }, + { "subnet6", Element::list }, + { "interface", Element::string }, + { "interface-id", Element::string }, + { "renew-timer", Element::integer }, + { "rebind-timer", Element::integer }, + { "option-data", Element::list }, + { "relay", Element::map }, + { "reservation-mode", Element::string }, + { "reservations-global", Element::boolean }, + { "reservations-in-subnet", Element::boolean }, + { "reservations-out-of-pool", Element::boolean }, + { "client-class", Element::string }, + { "require-client-classes", Element::list }, + { "preferred-lifetime", Element::integer }, + { "min-preferred-lifetime", Element::integer }, + { "max-preferred-lifetime", Element::integer }, + { "rapid-commit", Element::boolean }, + { "valid-lifetime", Element::integer }, + { "min-valid-lifetime", Element::integer }, + { "max-valid-lifetime", Element::integer }, + { "user-context", Element::map }, + { "comment", Element::string }, + { "calculate-tee-times", Element::boolean }, + { "t1-percent", Element::real }, + { "t2-percent", Element::real }, + { "ddns-send-updates", Element::boolean }, + { "ddns-override-no-update", Element::boolean }, + { "ddns-override-client-update", Element::boolean }, + { "ddns-replace-client-name", Element::string }, + { "ddns-generated-prefix", Element::string }, + { "ddns-qualifying-suffix", Element::string }, + { "hostname-char-set", Element::string }, + { "hostname-char-replacement", Element::string }, + { "store-extended-info", Element::boolean }, + { "metadata", Element::map }, + { "cache-threshold", Element::real }, + { "cache-max-age", Element::integer }, + { "ddns-update-on-renew", Element::boolean }, + { "ddns-use-conflict-resolution", Element::boolean } +}; + +/// @brief This table defines default values for each IPv6 subnet. +const SimpleDefaults SimpleParser6::SHARED_SUBNET6_DEFAULTS = { + { "id", Element::integer, "0" } // 0 means autogenerate +}; + +/// @brief This table defines default values for interfaces for DHCPv6. +const SimpleDefaults SimpleParser6::IFACE6_DEFAULTS = { + { "re-detect", Element::boolean, "true" } +}; + +/// @brief This table defines default values for dhcp-queue-control in DHCPv6. +const SimpleDefaults SimpleParser6::DHCP_QUEUE_CONTROL6_DEFAULTS = { + { "enable-queue", Element::boolean, "false"}, + { "queue-type", Element::string, "kea-ring6"}, + { "capacity", Element::integer, "64"} +}; + +/// @brief This table defines default values for multi-threading in DHCPv6. +const SimpleDefaults SimpleParser6::DHCP_MULTI_THREADING6_DEFAULTS = { + { "enable-multi-threading", Element::boolean, "false" }, + { "thread-pool-size", Element::integer, "0" }, + { "packet-queue-size", Element::integer, "64" } +}; + +/// @brief This defines default values for sanity checking for DHCPv6. +const SimpleDefaults SimpleParser6::SANITY_CHECKS6_DEFAULTS = { + { "lease-checks", Element::string, "warn" } +}; + +/// @} + +/// --------------------------------------------------------------------------- +/// --- end of default values ------------------------------------------------- +/// --------------------------------------------------------------------------- + +size_t SimpleParser6::setAllDefaults(ElementPtr global) { + size_t cnt = 0; + + // Set global defaults first. + cnt = setDefaults(global, GLOBAL6_DEFAULTS); + + // Now set the defaults for each specified option definition + ConstElementPtr option_defs = global->get("option-def"); + if (option_defs) { + BOOST_FOREACH(ElementPtr option_def, option_defs->listValue()) { + cnt += SimpleParser::setDefaults(option_def, OPTION6_DEF_DEFAULTS); + } + } + + // Set the defaults for option data + ConstElementPtr options = global->get("option-data"); + if (options) { + BOOST_FOREACH(ElementPtr single_option, options->listValue()) { + cnt += SimpleParser::setDefaults(single_option, OPTION6_DEFAULTS); + } + } + + // Now set the defaults for defined subnets + ConstElementPtr subnets = global->get("subnet6"); + if (subnets) { + cnt += setListDefaults(subnets, SUBNET6_DEFAULTS); + } + + // Set the defaults for interfaces config + ConstElementPtr ifaces_cfg = global->get("interfaces-config"); + if (ifaces_cfg) { + ElementPtr mutable_cfg = boost::const_pointer_cast<Element>(ifaces_cfg); + cnt += setDefaults(mutable_cfg, IFACE6_DEFAULTS); + } + + // Set defaults for shared networks + ConstElementPtr shared = global->get("shared-networks"); + if (shared) { + BOOST_FOREACH(ElementPtr net, shared->listValue()) { + + cnt += setDefaults(net, SHARED_NETWORK6_DEFAULTS); + + ConstElementPtr subs = net->get("subnet6"); + if (subs) { + cnt += setListDefaults(subs, SHARED_SUBNET6_DEFAULTS); + } + } + } + + // Set the defaults for dhcp-queue-control. If the element isn't there + // we'll add it. + ConstElementPtr queue_control = global->get("dhcp-queue-control"); + ElementPtr mutable_cfg; + if (queue_control) { + mutable_cfg = boost::const_pointer_cast<Element>(queue_control); + } else { + mutable_cfg = Element::createMap(); + global->set("dhcp-queue-control", mutable_cfg); + } + + cnt += setDefaults(mutable_cfg, DHCP_QUEUE_CONTROL6_DEFAULTS); + + // Set the defaults for multi-threading. If the element isn't there + // we'll add it. + ConstElementPtr multi_threading = global->get("multi-threading"); + if (multi_threading) { + mutable_cfg = boost::const_pointer_cast<Element>(multi_threading); + } else { + mutable_cfg = Element::createMap(); + global->set("multi-threading", mutable_cfg); + } + + cnt += setDefaults(mutable_cfg, DHCP_MULTI_THREADING6_DEFAULTS); + + // Set the defaults for sanity-checks. If the element isn't + // there we'll add it. + ConstElementPtr sanity_checks = global->get("sanity-checks"); + if (sanity_checks) { + mutable_cfg = boost::const_pointer_cast<Element>(sanity_checks); + } else { + mutable_cfg = Element::createMap(); + global->set("sanity-checks", mutable_cfg); + } + + cnt += setDefaults(mutable_cfg, SANITY_CHECKS6_DEFAULTS); + + return (cnt); +} + +size_t SimpleParser6::deriveParameters(ElementPtr global) { + size_t cnt = 0; + + // Now derive global parameters into subnets. + ConstElementPtr subnets = global->get("subnet6"); + if (subnets) { + BOOST_FOREACH(ElementPtr single_subnet, subnets->listValue()) { + cnt += SimpleParser::deriveParams(global, single_subnet, + INHERIT_TO_SUBNET6); + } + } + + // Deriving parameters for shared networks is a bit more involved. + // First, the shared-network level derives from global, and then + // subnets within derive from it. + ConstElementPtr shared = global->get("shared-networks"); + if (shared) { + BOOST_FOREACH(ElementPtr net, shared->listValue()) { + // First try to inherit the parameters from shared network, + // if defined there. + // Then try to inherit them from global. + cnt += SimpleParser::deriveParams(global, net, + INHERIT_TO_SUBNET6); + + // Now we need to go thrugh all the subnets in this net. + subnets = net->get("subnet6"); + if (subnets) { + BOOST_FOREACH(ElementPtr single_subnet, subnets->listValue()) { + cnt += SimpleParser::deriveParams(net, single_subnet, + INHERIT_TO_SUBNET6); + } + } + } + } + + return (cnt); +} + +} // namespace dhcp +} // namespace isc diff --git a/src/lib/dhcpsrv/parsers/simple_parser6.h b/src/lib/dhcpsrv/parsers/simple_parser6.h new file mode 100644 index 0000000..8eae3bf --- /dev/null +++ b/src/lib/dhcpsrv/parsers/simple_parser6.h @@ -0,0 +1,70 @@ +// Copyright (C) 2016-2020 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/. + +#ifndef SIMPLE_PARSER6_H +#define SIMPLE_PARSER6_H + +#include <cc/simple_parser.h> + +namespace isc { +namespace dhcp { + +/// @brief SimpleParser specialized for DHCPv6 +/// +/// This class is a @ref isc::data::SimpleParser dedicated to DHCPv6 parser. +/// In particular, it contains all the default values and names of the +/// parameters that are to be derived (inherited) between scopes. +/// For the actual values, see @file simple_parser6.cc +class SimpleParser6 : public isc::data::SimpleParser { +public: + + /// @brief Sets all defaults for DHCPv6 configuration + /// + /// This method sets global, option data and option definitions defaults. + /// + /// @param global scope to be filled in with defaults. + /// @return number of default values added + static size_t setAllDefaults(isc::data::ElementPtr global); + + /// @brief Derives (inherits) all parameters from global to more specific scopes. + /// + /// This method currently does the following: + /// - derives global parameters to subnets (lifetimes for now) + /// @param global scope to be modified if needed (subnet6 will be extracted) + /// @return number of default values derived + static size_t deriveParameters(isc::data::ElementPtr global); + + // see simple_parser6.cc for comments for those parameters + static const isc::data::SimpleKeywords GLOBAL6_PARAMETERS; + static const isc::data::SimpleDefaults GLOBAL6_DEFAULTS; + + static const isc::data::SimpleKeywords OPTION6_DEF_PARAMETERS; + static const isc::data::SimpleDefaults OPTION6_DEF_DEFAULTS; + + static const isc::data::SimpleKeywords OPTION6_PARAMETERS; + static const isc::data::SimpleDefaults OPTION6_DEFAULTS; + + static const isc::data::SimpleKeywords SUBNET6_PARAMETERS; + static const isc::data::SimpleDefaults SUBNET6_DEFAULTS; + static const isc::data::SimpleDefaults SHARED_SUBNET6_DEFAULTS; + static const isc::data::ParamsList INHERIT_TO_SUBNET6; + + static const isc::data::SimpleKeywords POOL6_PARAMETERS; + static const isc::data::SimpleKeywords PD_POOL6_PARAMETERS; + + static const isc::data::SimpleKeywords SHARED_NETWORK6_PARAMETERS; + static const isc::data::SimpleDefaults SHARED_NETWORK6_DEFAULTS; + + static const isc::data::SimpleDefaults IFACE6_DEFAULTS; + static const isc::data::SimpleDefaults DHCP_QUEUE_CONTROL6_DEFAULTS; + static const isc::data::SimpleDefaults DHCP_MULTI_THREADING6_DEFAULTS; + static const isc::data::SimpleDefaults SANITY_CHECKS6_DEFAULTS; +}; + +} // namespace dhcp +} // namespace isc + +#endif |