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