Kea  1.5.0
option_data_parser.cc
Go to the documentation of this file.
1 // Copyright (C) 2017-2018 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/libdhcp++.h>
11 #include <dhcp/option_definition.h>
12 #include <dhcp/option_space.h>
13 #include <dhcpsrv/cfgmgr.h>
15 #include <util/encode/hex.h>
16 #include <util/strutil.h>
17 #include <boost/foreach.hpp>
18 #include <limits>
19 #include <vector>
20 
21 using namespace isc::data;
22 using namespace isc::util;
23 
24 namespace isc {
25 namespace dhcp {
26 
27 // **************************** OptionDataParser *************************
28 
29 OptionDataParser::OptionDataParser(const uint16_t address_family,
30  CfgOptionDefPtr cfg_option_def)
31  : address_family_(address_family), cfg_option_def_(cfg_option_def) {
32 }
33 
34 std::pair<OptionDescriptor, std::string>
36 
37  // Try to create the option instance.
38  std::pair<OptionDescriptor, std::string> opt = createOption(single_option);
39 
40  if (!opt.first.option_) {
41  // Should never happen (@todo: update message)
43  "parser logic error: no option has been configured and"
44  " thus there is nothing to commit. Has build() been called?");
45  }
46 
47  return (opt);
48 }
49 
51 OptionDataParser::extractCode(ConstElementPtr parent) const {
52  uint32_t code;
53  try {
54  code = getInteger(parent, "code");
55 
56  } catch (const std::exception&) {
57  // The code parameter was not found. Return an unspecified
58  // value.
59  return (OptionalValue<uint32_t>());
60  }
61 
62  if (code == 0) {
63  isc_throw(DhcpConfigError, "option code must not be zero "
64  "(" << getPosition("code", parent) << ")");
65 
66  } else if (address_family_ == AF_INET &&
67  code > std::numeric_limits<uint8_t>::max()) {
68  isc_throw(DhcpConfigError, "invalid option code '" << code
69  << "', it must not be greater than '"
70  << static_cast<int>(std::numeric_limits<uint8_t>::max())
71  << "' (" << getPosition("code", parent)
72  << ")");
73 
74  } else if (address_family_ == AF_INET6 &&
75  code > std::numeric_limits<uint16_t>::max()) {
76  isc_throw(DhcpConfigError, "invalid option code '" << code
77  << "', it must not exceed '"
78  << std::numeric_limits<uint16_t>::max()
79  << "' (" << getPosition("code", parent)
80  << ")");
81 
82  }
83 
84  return (OptionalValue<uint32_t>(code, OptionalValueState(true)));
85 }
86 
88 OptionDataParser::extractName(ConstElementPtr parent) const {
89  std::string name;
90  try {
91  name = getString(parent, "name");
92 
93  } catch (...) {
94  return (OptionalValue<std::string>());
95  }
96 
97  if (name.find(" ") != std::string::npos) {
98  isc_throw(DhcpConfigError, "invalid option name '" << name
99  << "', space character is not allowed ("
100  << getPosition("name", parent) << ")");
101  }
102 
103  return (OptionalValue<std::string>(name, OptionalValueState(true)));
104 }
105 
106 std::string
107 OptionDataParser::extractData(ConstElementPtr parent) const {
108  std::string data;
109  try {
110  data = getString(parent, "data");
111 
112  } catch (...) {
113  // The "data" parameter was not found. Return an empty value.
114  return (data);
115  }
116 
117  return (data);
118 }
119 
121 OptionDataParser::extractCSVFormat(ConstElementPtr parent) const {
122  bool csv_format = true;
123  try {
124  csv_format = getBoolean(parent, "csv-format");
125 
126  } catch (...) {
127  return (OptionalValue<bool>(csv_format));
128  }
129 
130  return (OptionalValue<bool>(csv_format, OptionalValueState(true)));
131 }
132 
133 std::string
134 OptionDataParser::extractSpace(ConstElementPtr parent) const {
135  std::string space = address_family_ == AF_INET ?
137  try {
138  space = getString(parent, "space");
139 
140  } catch (...) {
141  return (space);
142  }
143 
144  try {
145  if (!OptionSpace::validateName(space)) {
146  isc_throw(DhcpConfigError, "invalid option space name '"
147  << space << "'");
148  }
149 
150  if ((space == DHCP4_OPTION_SPACE) && (address_family_ == AF_INET6)) {
151  isc_throw(DhcpConfigError, "'" << DHCP4_OPTION_SPACE
152  << "' option space name is reserved for DHCPv4 server");
153 
154  } else if ((space == DHCP6_OPTION_SPACE) &&
155  (address_family_ == AF_INET)) {
156  isc_throw(DhcpConfigError, "'" << DHCP6_OPTION_SPACE
157  << "' option space name is reserved for DHCPv6 server");
158  }
159 
160  } catch (std::exception& ex) {
161  // Append position of the option space parameter.
162  isc_throw(DhcpConfigError, ex.what() << " ("
163  << getPosition("space", parent) << ")");
164  }
165 
166  return (space);
167 }
168 
170 OptionDataParser::extractPersistent(ConstElementPtr parent) const {
171  bool persist = false;
172  try {
173  persist = getBoolean(parent, "always-send");
174 
175  } catch (...) {
176  return (OptionalValue<bool>(persist));
177  }
178 
179  return (OptionalValue<bool>(persist, OptionalValueState(true)));
180 }
181 
182 template<typename SearchKey>
184 OptionDataParser::findOptionDefinition(const std::string& option_space,
185  const SearchKey& search_key) const {
187  if (cfg_option_def_) {
188  // Check if the definition was given in the constructor
189  def = cfg_option_def_->get(option_space, search_key);
190  }
191 
192  if (!def) {
193  // Check if this is a standard option.
194  def = LibDHCP::getOptionDef(option_space, search_key);
195  }
196 
197  if (!def) {
198  // Check if this is a vendor-option. If it is, get vendor-specific
199  // definition.
200  uint32_t vendor_id = LibDHCP::optionSpaceToVendorId(option_space);
201  if (vendor_id) {
202  const Option::Universe u = address_family_ == AF_INET ?
204  def = LibDHCP::getVendorOptionDef(u, vendor_id, search_key);
205  }
206  }
207 
208  if (!def) {
209  // Check if this is an option specified by a user. We used to
210  // check that in the staging configuration, but when the configuration
211  // changes are caused by a command the staging configuration doesn't
212  // exist. What is always available is the container holding runtime
213  // option definitions in LibDHCP. It holds option definitions from
214  // the staging configuration in case of the full reconfiguration or
215  // the definitions from the current configuration in case there is
216  // no staging configuration (after configuration commit). In other
217  // words, runtime options are always the ones that we need here.
218  def = LibDHCP::getRuntimeOptionDef(option_space, search_key);
219  }
220 
221  if (!def) {
222  // Finish by last resort definitions.
223  def = LibDHCP::getLastResortOptionDef(option_space, search_key);
224  }
225 
226  return (def);
227 }
228 
229 std::pair<OptionDescriptor, std::string>
230 OptionDataParser::createOption(ConstElementPtr option_data) {
231  const Option::Universe universe = address_family_ == AF_INET ?
233 
234  OptionalValue<uint32_t> code_param = extractCode(option_data);
235  OptionalValue<std::string> name_param = extractName(option_data);
236  OptionalValue<bool> csv_format_param = extractCSVFormat(option_data);
237  OptionalValue<bool> persist_param = extractPersistent(option_data);
238  std::string data_param = extractData(option_data);
239  std::string space_param = extractSpace(option_data);
240  ConstElementPtr user_context = option_data->get("user-context");
241 
242  // Require that option code or option name is specified.
243  if (!code_param.isSpecified() && !name_param.isSpecified()) {
244  isc_throw(DhcpConfigError, "option data configuration requires one of"
245  " 'code' or 'name' parameters to be specified"
246  << " (" << option_data->getPosition() << ")");
247  }
248 
249  // Try to find a corresponding option definition using option code or
250  // option name.
251  OptionDefinitionPtr def = code_param.isSpecified() ?
252  findOptionDefinition(space_param, code_param) :
253  findOptionDefinition(space_param, name_param);
254 
255  // If there is no definition, the user must not explicitly enable the
256  // use of csv-format.
257  if (!def) {
258  // If explicitly requested that the CSV format is to be used,
259  // the option definition is a must.
260  if (csv_format_param.isSpecified() && csv_format_param) {
261  isc_throw(DhcpConfigError, "definition for the option '"
262  << space_param << "." << name_param
263  << "' having code '" << code_param
264  << "' does not exist ("
265  << getPosition("name", option_data)
266  << ")");
267 
268  // If there is no option definition and the option code is not specified
269  // we have no means to find the option code.
270  } else if (name_param.isSpecified() && !code_param.isSpecified()) {
271  isc_throw(DhcpConfigError, "definition for the option '"
272  << space_param << "." << name_param
273  << "' does not exist ("
274  << getPosition("name", option_data)
275  << ")");
276  }
277  }
278 
279  // Transform string of hexadecimal digits into binary format.
280  std::vector<uint8_t> binary;
281  std::vector<std::string> data_tokens;
282 
283  // If the definition is available and csv-format hasn't been explicitly
284  // disabled, we will parse the data as comma separated values.
285  if (def && (!csv_format_param.isSpecified() || csv_format_param)) {
286  // If the option data is specified as a string of comma
287  // separated values then we need to split this string into
288  // individual values - each value will be used to initialize
289  // one data field of an option.
290  // It is the only usage of the escape option: this allows
291  // to embed commas in individual values and to return
292  // for instance a string value with embedded commas.
293  data_tokens = isc::util::str::tokens(data_param, ",", true);
294 
295  } else {
296  // Otherwise, the option data is specified as a string of
297  // hexadecimal digits that we have to turn into binary format.
298  try {
299  // The decodeHex function expects that the string contains an
300  // even number of digits. If we don't meet this requirement,
301  // we have to insert a leading 0.
302  if (!data_param.empty() && ((data_param.length() % 2) != 0)) {
303  data_param = data_param.insert(0, "0");
304  }
305  util::encode::decodeHex(data_param, binary);
306  } catch (...) {
307  isc_throw(DhcpConfigError, "option data is not a valid"
308  << " string of hexadecimal digits: " << data_param
309  << " ("
310  << getPosition("data", option_data)
311  << ")");
312  }
313  }
314 
315  OptionPtr option;
316  OptionDescriptor desc(false);
317 
318  if (!def) {
319  // @todo We have a limited set of option definitions initialized at
320  // the moment. In the future we want to initialize option definitions
321  // for all options. Consequently an error will be issued if an option
322  // definition does not exist for a particular option code. For now it is
323  // ok to create generic option if definition does not exist.
324  OptionPtr option(new Option(universe, static_cast<uint16_t>(code_param),
325  binary));
326 
327  desc.option_ = option;
328  desc.persistent_ = persist_param.isSpecified() && persist_param;
329  } else {
330 
331  // Option name is specified it should match the name in the definition.
332  if (name_param.isSpecified() && (def->getName() != name_param.get())) {
333  isc_throw(DhcpConfigError, "specified option name '"
334  << name_param << "' does not match the "
335  << "option definition: '" << space_param
336  << "." << def->getName() << "' ("
337  << getPosition("name", option_data)
338  << ")");
339  }
340 
341  // Option definition has been found so let's use it to create
342  // an instance of our option.
343  try {
344  bool use_csv = !csv_format_param.isSpecified() || csv_format_param;
345  OptionPtr option = use_csv ?
346  def->optionFactory(universe, def->getCode(), data_tokens) :
347  def->optionFactory(universe, def->getCode(), binary);
348  desc.option_ = option;
349  desc.persistent_ = persist_param.isSpecified() && persist_param;
350  if (use_csv) {
351  desc.formatted_value_ = data_param;
352  }
353  } catch (const isc::Exception& ex) {
354  isc_throw(DhcpConfigError, "option data does not match"
355  << " option definition (space: " << space_param
356  << ", code: " << def->getCode() << "): "
357  << ex.what() << " ("
358  << getPosition("data", option_data)
359  << ")");
360  }
361  }
362 
363  // Add user context
364  if (user_context) {
365  desc.setContext(user_context);
366  }
367 
368  // All went good, so we can set the option space name.
369  return make_pair(desc, space_param);
370 }
371 
372 // **************************** OptionDataListParser *************************
374  //const CfgOptionPtr& cfg,
375  const uint16_t address_family,
376  CfgOptionDefPtr cfg_option_def)
377  : address_family_(address_family), cfg_option_def_(cfg_option_def) {
378 }
379 
380 
382  isc::data::ConstElementPtr option_data_list) {
383  OptionDataParser option_parser(address_family_, cfg_option_def_);
384  BOOST_FOREACH(ConstElementPtr data, option_data_list->listValue()) {
385  std::pair<OptionDescriptor, std::string> option =
386  option_parser.parse(data);
387  // Use the option description to keep the formatted value
388  cfg->add(option.first, option.second);
389  cfg->encapsulate();
390  }
391 }
392 
393 } // end of namespace isc::dhcp
394 } // 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:205
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.
Simple class representing an optional value.
boost::shared_ptr< CfgOption > CfgOptionPtr
Non-const pointer.
Definition: cfg_option.h:497
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
#define DHCP6_OPTION_SPACE
Definition: option_space.h:17
boost::shared_ptr< Option > OptionPtr
Definition: option.h:37
Universe
defines option universe DHCPv4 or DHCPv6
Definition: option.h:67
boost::shared_ptr< CfgOptionDef > CfgOptionDefPtr
Non-const pointer.
virtual const char * what() const
Returns a C-style character string of the cause of the exception.
#define isc_throw(type, stream)
A shortcut macro to insert known values into exception arguments.
Definition: edns.h:19
Indicate if an OptionalValue is is specified or not.
void decodeHex(const string &input, vector< uint8_t > &result)
Decode a text encoded in the base16 ('hex') format into the original data.
Definition: base_n.cc:466
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.
T get() const
Retrieves the actual value.
vector< string > tokens(const std::string &text, const std::string &delim, bool escape)
Split String into Tokens.
Definition: strutil.cc:77
#define DHCP4_OPTION_SPACE
Definition: option_space.h:16
static OptionDefinitionPtr getOptionDef(const std::string &space, const uint16_t code)
Return the first option definition matching a particular option code.
Definition: libdhcp++.cc:144
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.
bool isSpecified() const
Checks if the value is specified or unspecified.
A generic exception that is thrown if a function is called in a prohibited way.
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:265
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:916
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:184