summaryrefslogtreecommitdiffstats
path: root/src/lib/dhcpsrv/parsers/option_data_parser.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/dhcpsrv/parsers/option_data_parser.cc')
-rw-r--r--src/lib/dhcpsrv/parsers/option_data_parser.cc369
1 files changed, 369 insertions, 0 deletions
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 0000000000..ec989e0bfe
--- /dev/null
+++ b/src/lib/dhcpsrv/parsers/option_data_parser.cc
@@ -0,0 +1,369 @@
+// Copyright (C) 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 <exceptions/exceptions.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 <util/encode/hex.h>
+#include <util/strutil.h>
+#include <boost/foreach.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)
+ : address_family_(address_family) {
+}
+
+std::pair<OptionDescriptor, std::string>
+OptionDataParser::parse(isc::data::ConstElementPtr single_option) {
+
+ // Try to create the option instance.
+ std::pair<OptionDescriptor, std::string> opt = createOption(single_option);
+
+ if (!opt.first.option_) {
+ 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);
+}
+
+OptionalValue<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 (OptionalValue<uint32_t>());
+ }
+
+ if (code == 0) {
+ isc_throw(DhcpConfigError, "option code must not be zero "
+ "(" << getPosition("code", parent) << ")");
+
+ } else 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 (OptionalValue<uint32_t>(code, OptionalValueState(true)));
+}
+
+OptionalValue<std::string>
+OptionDataParser::extractName(ConstElementPtr parent) const {
+ std::string name;
+ try {
+ name = getString(parent, "name");
+
+ } catch (...) {
+ return (OptionalValue<std::string>());
+ }
+
+ if (name.find(" ") != std::string::npos) {
+ isc_throw(DhcpConfigError, "invalid option name '" << name
+ << "', space character is not allowed ("
+ << getPosition("name", parent) << ")");
+ }
+
+ return (OptionalValue<std::string>(name, OptionalValueState(true)));
+}
+
+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);
+}
+
+OptionalValue<bool>
+OptionDataParser::extractCSVFormat(ConstElementPtr parent) const {
+ bool csv_format = true;
+ try {
+ csv_format = getBoolean(parent, "csv-format");
+
+ } catch (...) {
+ return (OptionalValue<bool>(csv_format));
+ }
+
+ return (OptionalValue<bool>(csv_format, OptionalValueState(true)));
+}
+
+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 (std::exception& ex) {
+ // Append position of the option space parameter.
+ isc_throw(DhcpConfigError, ex.what() << " ("
+ << getPosition("space", parent) << ")");
+ }
+
+ return (space);
+}
+
+OptionalValue<bool>
+OptionDataParser::extractPersistent(ConstElementPtr parent) const {
+ bool persist = false;
+ try {
+ persist = getBoolean(parent, "always-send");
+
+ } catch (...) {
+ return (OptionalValue<bool>(persist));
+ }
+
+ return (OptionalValue<bool>(persist, OptionalValueState(true)));
+}
+
+template<typename SearchKey>
+OptionDefinitionPtr
+OptionDataParser::findOptionDefinition(const std::string& option_space,
+ const SearchKey& search_key) const {
+ OptionDefinitionPtr def = LibDHCP::getOptionDef(option_space, search_key);
+
+ 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;
+ def = LibDHCP::getVendorOptionDef(u, vendor_id, search_key);
+ }
+ }
+
+ 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.
+ def = LibDHCP::getRuntimeOptionDef(option_space, search_key);
+ }
+
+ return (def);
+}
+
+std::pair<OptionDescriptor, std::string>
+OptionDataParser::createOption(ConstElementPtr option_data) {
+ const Option::Universe universe = address_family_ == AF_INET ?
+ Option::V4 : Option::V6;
+
+ OptionalValue<uint32_t> code_param = extractCode(option_data);
+ OptionalValue<std::string> name_param = extractName(option_data);
+ OptionalValue<bool> csv_format_param = extractCSVFormat(option_data);
+ OptionalValue<bool> persist_param = extractPersistent(option_data);
+ std::string data_param = extractData(option_data);
+ std::string space_param = extractSpace(option_data);
+
+ // Require that option code or option name is specified.
+ if (!code_param.isSpecified() && !name_param.isSpecified()) {
+ 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 = code_param.isSpecified() ?
+ findOptionDefinition(space_param, code_param) :
+ findOptionDefinition(space_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.isSpecified() && 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.isSpecified() && !code_param.isSpecified()) {
+ 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.isSpecified() || 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 {
+ // Otherwise, the option data is specified as a string of
+ // hexadecimal digits that we have to turn into binary format.
+ try {
+ // The decodeHex function expects that the string contains an
+ // even number of digits. If we don't meet this requirement,
+ // we have to insert a leading 0.
+ if (!data_param.empty() && ((data_param.length() % 2) != 0)) {
+ data_param = data_param.insert(0, "0");
+ }
+ util::encode::decodeHex(data_param, binary);
+ } catch (...) {
+ isc_throw(DhcpConfigError, "option data is not a valid"
+ << " string of hexadecimal digits: " << data_param
+ << " ("
+ << getPosition("data", option_data)
+ << ")");
+ }
+ }
+
+ OptionPtr option;
+ 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.isSpecified() && persist_param;
+ } else {
+
+ // Option name is specified it should match the name in the definition.
+ if (name_param.isSpecified() && (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.isSpecified() || 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.isSpecified() && 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)
+ << ")");
+ }
+ }
+
+ // 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)
+ : address_family_(address_family) {
+}
+
+
+void OptionDataListParser::parse(const CfgOptionPtr& cfg,
+ isc::data::ConstElementPtr option_data_list) {
+ OptionDataParser option_parser(address_family_);
+ 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();
+ }
+}
+
+} // end of namespace isc::dhcp
+} // end of namespace isc