Kea  1.9.9-git
option_data_parser.cc
Go to the documentation of this file.
1 // Copyright (C) 2017-2020 Internet Systems Consortium, Inc. ("ISC")
2 //
3 // This Source Code Form is subject to the terms of the Mozilla Public
4 // License, v. 2.0. If a copy of the MPL was not distributed with this
5 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
6 
7 #include <config.h>
8 
10 #include <dhcp/dhcp4.h>
11 #include <dhcp/libdhcp++.h>
12 #include <dhcp/option_definition.h>
13 #include <dhcp/option_space.h>
14 #include <dhcpsrv/cfgmgr.h>
18 #include <util/encode/hex.h>
19 #include <util/strutil.h>
20 #include <boost/foreach.hpp>
21 #include <limits>
22 #include <vector>
23 
24 using namespace isc::data;
25 using namespace isc::util;
26 
27 namespace isc {
28 namespace dhcp {
29 
30 // **************************** OptionDataParser *************************
31 
32 OptionDataParser::OptionDataParser(const uint16_t address_family,
33  CfgOptionDefPtr cfg_option_def)
34  : address_family_(address_family), cfg_option_def_(cfg_option_def) {
35 }
36 
37 std::pair<OptionDescriptor, std::string>
39 
40  // Check parameters.
41  if (address_family_ == AF_INET) {
43  } else {
45  }
46 
47  // Try to create the option instance.
48  std::pair<OptionDescriptor, std::string> opt = createOption(single_option);
49 
50  if (!opt.first.option_) {
51  // Should never happen (@todo: update message)
53  "parser logic error: no option has been configured and"
54  " thus there is nothing to commit. Has build() been called?");
55  }
56 
57  return (opt);
58 }
59 
61 OptionDataParser::extractCode(ConstElementPtr parent) const {
62  uint32_t code;
63  try {
64  code = getInteger(parent, "code");
65 
66  } catch (const std::exception&) {
67  // The code parameter was not found. Return an unspecified
68  // value.
69  return (Optional<uint32_t>());
70  }
71 
72  if (address_family_ == AF_INET &&
73  code > std::numeric_limits<uint8_t>::max()) {
74  isc_throw(DhcpConfigError, "invalid option code '" << code
75  << "', it must not be greater than '"
76  << static_cast<int>(std::numeric_limits<uint8_t>::max())
77  << "' (" << getPosition("code", parent)
78  << ")");
79 
80  } else if (address_family_ == AF_INET6 &&
81  code > std::numeric_limits<uint16_t>::max()) {
82  isc_throw(DhcpConfigError, "invalid option code '" << code
83  << "', it must not exceed '"
84  << std::numeric_limits<uint16_t>::max()
85  << "' (" << getPosition("code", parent)
86  << ")");
87 
88  }
89 
90  return (Optional<uint32_t>(code));
91 }
92 
94 OptionDataParser::extractName(ConstElementPtr parent) const {
95  std::string name;
96  try {
97  name = getString(parent, "name");
98 
99  } catch (...) {
100  return (Optional<std::string>());
101  }
102 
103  if (name.find(" ") != std::string::npos) {
104  isc_throw(DhcpConfigError, "invalid option name '" << name
105  << "', space character is not allowed ("
106  << getPosition("name", parent) << ")");
107  }
108 
109  return (Optional<std::string>(name));
110 }
111 
112 std::string
113 OptionDataParser::extractData(ConstElementPtr parent) const {
114  std::string data;
115  try {
116  data = getString(parent, "data");
117 
118  } catch (...) {
119  // The "data" parameter was not found. Return an empty value.
120  return (data);
121  }
122 
123  return (data);
124 }
125 
127 OptionDataParser::extractCSVFormat(ConstElementPtr parent) const {
128  bool csv_format = true;
129  try {
130  csv_format = getBoolean(parent, "csv-format");
131 
132  } catch (...) {
133  return (Optional<bool>());
134  }
135 
136  return (Optional<bool>(csv_format));
137 }
138 
139 std::string
140 OptionDataParser::extractSpace(ConstElementPtr parent) const {
141  std::string space = address_family_ == AF_INET ?
143  try {
144  space = getString(parent, "space");
145 
146  } catch (...) {
147  return (space);
148  }
149 
150  try {
151  if (!OptionSpace::validateName(space)) {
152  isc_throw(DhcpConfigError, "invalid option space name '"
153  << space << "'");
154  }
155 
156  if ((space == DHCP4_OPTION_SPACE) && (address_family_ == AF_INET6)) {
157  isc_throw(DhcpConfigError, "'" << DHCP4_OPTION_SPACE
158  << "' option space name is reserved for DHCPv4 server");
159 
160  } else if ((space == DHCP6_OPTION_SPACE) &&
161  (address_family_ == AF_INET)) {
162  isc_throw(DhcpConfigError, "'" << DHCP6_OPTION_SPACE
163  << "' option space name is reserved for DHCPv6 server");
164  }
165 
166  } catch (const std::exception& ex) {
167  // Append position of the option space parameter.
168  isc_throw(DhcpConfigError, ex.what() << " ("
169  << getPosition("space", parent) << ")");
170  }
171 
172  return (space);
173 }
174 
176 OptionDataParser::extractPersistent(ConstElementPtr parent) const {
177  bool persist = false;
178  try {
179  persist = getBoolean(parent, "always-send");
180 
181  } catch (...) {
182  return (Optional<bool>());
183  }
184 
185  return (Optional<bool>(persist));
186 }
187 
188 template<typename SearchKey>
190 OptionDataParser::findOptionDefinition(const std::string& option_space,
191  const SearchKey& search_key) const {
193  if (cfg_option_def_) {
194  // Check if the definition was given in the constructor
195  def = cfg_option_def_->get(option_space, search_key);
196  }
197 
198  if (!def) {
199  // Check if this is a standard option.
200  def = LibDHCP::getOptionDef(option_space, search_key);
201  }
202 
203  if (!def) {
204  // Check if this is a vendor-option. If it is, get vendor-specific
205  // definition.
206  uint32_t vendor_id = LibDHCP::optionSpaceToVendorId(option_space);
207  if (vendor_id) {
208  const Option::Universe u = address_family_ == AF_INET ?
210  def = LibDHCP::getVendorOptionDef(u, vendor_id, search_key);
211  }
212  }
213 
214  if (!def) {
215  // Check if this is an option specified by a user. We used to
216  // check that in the staging configuration, but when the configuration
217  // changes are caused by a command the staging configuration doesn't
218  // exist. What is always available is the container holding runtime
219  // option definitions in LibDHCP. It holds option definitions from
220  // the staging configuration in case of the full reconfiguration or
221  // the definitions from the current configuration in case there is
222  // no staging configuration (after configuration commit). In other
223  // words, runtime options are always the ones that we need here.
224  def = LibDHCP::getRuntimeOptionDef(option_space, search_key);
225  }
226 
227  if (!def) {
228  // Finish by last resort definitions.
229  def = LibDHCP::getLastResortOptionDef(option_space, search_key);
230  }
231 
232  return (def);
233 }
234 
235 std::pair<OptionDescriptor, std::string>
236 OptionDataParser::createOption(ConstElementPtr option_data) {
237  const Option::Universe universe = address_family_ == AF_INET ?
239 
240  Optional<uint32_t> code_param = extractCode(option_data);
241  Optional<std::string> name_param = extractName(option_data);
242  Optional<bool> csv_format_param = extractCSVFormat(option_data);
243  Optional<bool> persist_param = extractPersistent(option_data);
244  std::string data_param = extractData(option_data);
245  std::string space_param = extractSpace(option_data);
246  ConstElementPtr user_context = option_data->get("user-context");
247 
248  // Require that option code or option name is specified.
249  if (code_param.unspecified() && name_param.unspecified()) {
250  isc_throw(DhcpConfigError, "option data configuration requires one of"
251  " 'code' or 'name' parameters to be specified"
252  << " (" << option_data->getPosition() << ")");
253  }
254 
255  // Try to find a corresponding option definition using option code or
256  // option name.
257  OptionDefinitionPtr def = code_param.unspecified() ?
258  findOptionDefinition(space_param, name_param) :
259  findOptionDefinition(space_param, code_param);
260 
261  // If there is no definition, the user must not explicitly enable the
262  // use of csv-format.
263  if (!def) {
264  // If explicitly requested that the CSV format is to be used,
265  // the option definition is a must.
266  if (!csv_format_param.unspecified() && csv_format_param) {
267  isc_throw(DhcpConfigError, "definition for the option '"
268  << space_param << "." << name_param
269  << "' having code '" << code_param
270  << "' does not exist ("
271  << getPosition("name", option_data)
272  << ")");
273 
274  // If there is no option definition and the option code is not specified
275  // we have no means to find the option code.
276  } else if (!name_param.unspecified() && code_param.unspecified()) {
277  isc_throw(DhcpConfigError, "definition for the option '"
278  << space_param << "." << name_param
279  << "' does not exist ("
280  << getPosition("name", option_data)
281  << ")");
282  }
283  }
284 
285  // Transform string of hexadecimal digits into binary format.
286  std::vector<uint8_t> binary;
287  std::vector<std::string> data_tokens;
288 
289  // If the definition is available and csv-format hasn't been explicitly
290  // disabled, we will parse the data as comma separated values.
291  if (def && (csv_format_param.unspecified() || csv_format_param)) {
292  // If the option data is specified as a string of comma
293  // separated values then we need to split this string into
294  // individual values - each value will be used to initialize
295  // one data field of an option.
296  // It is the only usage of the escape option: this allows
297  // to embed commas in individual values and to return
298  // for instance a string value with embedded commas.
299  data_tokens = isc::util::str::tokens(data_param, ",", true);
300 
301  } else {
302  // Try to convert the values in quotes into a vector of ASCII codes.
303  // If the identifier lacks opening and closing quote, this will return
304  // an empty value, in which case we'll try to decode it as a string of
305  // hexadecimal digits.
306  try {
307  binary = util::str::quotedStringToBinary(data_param);
308  if (binary.empty()) {
309  util::str::decodeFormattedHexString(data_param, binary);
310  }
311  } catch (...) {
312  isc_throw(DhcpConfigError, "option data is not a valid"
313  << " string of hexadecimal digits: " << data_param
314  << " ("
315  << getPosition("data", option_data)
316  << ")");
317  }
318  }
319 
320  OptionDescriptor desc(false);
321 
322  if (!def) {
323  // @todo We have a limited set of option definitions initialized at
324  // the moment. In the future we want to initialize option definitions
325  // for all options. Consequently an error will be issued if an option
326  // definition does not exist for a particular option code. For now it is
327  // ok to create generic option if definition does not exist.
328  OptionPtr option(new Option(universe, static_cast<uint16_t>(code_param),
329  binary));
330 
331  desc.option_ = option;
332  desc.persistent_ = !persist_param.unspecified() && persist_param;
333  } else {
334 
335  // Option name is specified it should match the name in the definition.
336  if (!name_param.unspecified() && (def->getName() != name_param.get())) {
337  isc_throw(DhcpConfigError, "specified option name '"
338  << name_param << "' does not match the "
339  << "option definition: '" << space_param
340  << "." << def->getName() << "' ("
341  << getPosition("name", option_data)
342  << ")");
343  }
344 
345  // Option definition has been found so let's use it to create
346  // an instance of our option.
347  try {
348  bool use_csv = csv_format_param.unspecified() || csv_format_param;
349  OptionPtr option = use_csv ?
350  def->optionFactory(universe, def->getCode(), data_tokens) :
351  def->optionFactory(universe, def->getCode(), binary);
352  desc.option_ = option;
353  desc.persistent_ = !persist_param.unspecified() && persist_param;
354  if (use_csv) {
355  desc.formatted_value_ = data_param;
356  }
357  } catch (const isc::Exception& ex) {
358  isc_throw(DhcpConfigError, "option data does not match"
359  << " option definition (space: " << space_param
360  << ", code: " << def->getCode() << "): "
361  << ex.what() << " ("
362  << getPosition("data", option_data)
363  << ")");
364  }
365  }
366 
367  // Check PAD and END in (and only in) dhcp4 space.
368  if (space_param == DHCP4_OPTION_SPACE) {
369  if (desc.option_->getType() == DHO_PAD) {
370  isc_throw(DhcpConfigError, "invalid option code '0': "
371  << "reserved for PAD ("
372  << option_data->getPosition() << ")");
373  } else if (desc.option_->getType() == DHO_END) {
374  isc_throw(DhcpConfigError, "invalid option code '255': "
375  << "reserved for END ("
376  << option_data->getPosition() << ")");
377  }
378  }
379 
380  // For dhcp6 space the value 0 is reserved.
381  if (space_param == DHCP6_OPTION_SPACE) {
382  if (desc.option_->getType() == 0) {
383  isc_throw(DhcpConfigError, "invalid option code '0': "
384  << "reserved value ("
385  << option_data->getPosition() << ")");
386  }
387  }
388 
389 
390  // Add user context
391  if (user_context) {
392  desc.setContext(user_context);
393  }
394 
395  // All went good, so we can set the option space name.
396  return make_pair(desc, space_param);
397 }
398 
399 // **************************** OptionDataListParser *************************
401  //const CfgOptionPtr& cfg,
402  const uint16_t address_family,
403  CfgOptionDefPtr cfg_option_def)
404  : address_family_(address_family), cfg_option_def_(cfg_option_def) {
405 }
406 
407 
409  isc::data::ConstElementPtr option_data_list) {
410  OptionDataParser option_parser(address_family_, cfg_option_def_);
411  BOOST_FOREACH(ConstElementPtr data, option_data_list->listValue()) {
412  std::pair<OptionDescriptor, std::string> option =
413  option_parser.parse(data);
414  // Use the option description to keep the formatted value
415  cfg->add(option.first, option.second);
416  cfg->encapsulate();
417  }
418 }
419 
420 } // end of namespace isc::dhcp
421 } // end of namespace isc
static bool getBoolean(isc::data::ConstElementPtr scope, const std::string &name)
Returns a boolean parameter from a scope.
static OptionDefinitionPtr getRuntimeOptionDef(const std::string &space, const uint16_t code)
Returns runtime (non-standard) option definition by space and option code.
Definition: libdhcp++.cc:185
void unspecified(bool unspecified)
Modifies the flag that indicates whether the value is specified or unspecified.
Definition: optional.h:121
static int64_t getInteger(isc::data::ConstElementPtr scope, const std::string &name)
Returns an integer parameter from a scope.
void parse(const CfgOptionPtr &cfg, isc::data::ConstElementPtr option_data_list)
Parses a list of options, instantiates them and stores in cfg.
boost::shared_ptr< CfgOption > CfgOptionPtr
Non-const pointer.
Definition: cfg_option.h:706
std::pair< OptionDescriptor, std::string > parse(isc::data::ConstElementPtr single_option)
Parses ElementPtr containing option definition.
static bool validateName(const std::string &name)
Checks that the provided option space name is valid.
Definition: option_space.cc:26
boost::shared_ptr< Option > OptionPtr
Definition: option.h:36
Universe
defines option universe DHCPv4 or DHCPv6
Definition: option.h:82
boost::shared_ptr< CfgOptionDef > CfgOptionDefPtr
Non-const pointer.
void decodeFormattedHexString(const std::string &hex_string, std::vector< uint8_t > &binary)
Converts a formatted string of hexadecimal digits into a vector.
Definition: strutil.cc:273
#define isc_throw(type, stream)
A shortcut macro to insert known values into exception arguments.
Definition: edns.h:19
boost::shared_ptr< const Element > ConstElementPtr
Definition: data.h:23
static std::string getString(isc::data::ConstElementPtr scope, const std::string &name)
Returns a string parameter from a scope.
vector< string > tokens(const std::string &text, const std::string &delim, bool escape)
Split String into Tokens.
Definition: strutil.cc:77
static OptionDefinitionPtr getOptionDef(const std::string &space, const uint16_t code)
Return the first option definition matching a particular option code.
Definition: libdhcp++.cc:122
virtual const char * what() const
Returns a C-style character string of the cause of the exception.
static const isc::data::SimpleKeywords OPTION6_PARAMETERS
This table defines all option parameters.
std::vector< uint8_t > quotedStringToBinary(const std::string &quoted_string)
Converts a string in quotes into vector.
Definition: strutil.cc:196
static const data::Element::Position & getPosition(const std::string &name, const data::ConstElementPtr parent)
Utility method that returns position of an element.
This is a base class for exceptions thrown from the DNS library module.
Defines the logger used by the top-level component of kea-dhcp-ddns.
T get() const
Retrieves the encapsulated value.
Definition: optional.h:112
#define DHCP6_OPTION_SPACE
A generic exception that is thrown if a function is called in a prohibited way.
#define DHCP4_OPTION_SPACE
global std option spaces
static void checkKeywords(const SimpleKeywords &keywords, isc::data::ConstElementPtr scope)
Checks acceptable keywords with their expected type.
Parser for option data value.
static OptionDefinitionPtr getLastResortOptionDef(const std::string &space, const uint16_t code)
Returns last resort option definition by space and option code.
Definition: libdhcp++.cc:245
boost::shared_ptr< OptionDefinition > OptionDefinitionPtr
Pointer to option definition object.
OptionDataListParser(const uint16_t address_family, CfgOptionDefPtr cfg_option_def=CfgOptionDefPtr())
Constructor.
static uint32_t optionSpaceToVendorId(const std::string &option_space)
Converts option space name to vendor id.
Definition: libdhcp++.cc:925
static const isc::data::SimpleKeywords OPTION4_PARAMETERS
This table defines all option parameters.
static OptionDefinitionPtr getVendorOptionDef(const Option::Universe u, const uint32_t vendor_id, const uint16_t code)
Returns vendor option definition for a given vendor-id and code.
Definition: libdhcp++.cc:164