Kea 2.2.0
option_data_parser.cc
Go to the documentation of this file.
1// Copyright (C) 2017-2022 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>
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
25using namespace isc::data;
26using namespace isc::util;
27
28namespace isc {
29namespace dhcp {
30
31// **************************** OptionDataParser *************************
32
33OptionDataParser::OptionDataParser(const uint16_t address_family,
34 CfgOptionDefPtr cfg_option_def)
35 : address_family_(address_family), cfg_option_def_(cfg_option_def) {
36}
37
38std::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
113std::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
140std::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
190OptionDataParser::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
256std::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
439boost::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
This is a base class for exceptions thrown from the DNS library module.
virtual const char * what() const
Returns a C-style character string of the cause of the exception.
A generic exception that is thrown if a function is called in a prohibited way.
static void checkKeywords(const SimpleKeywords &keywords, isc::data::ConstElementPtr scope)
Checks acceptable keywords with their expected type.
static const data::Element::Position & getPosition(const std::string &name, const data::ConstElementPtr parent)
Utility method that returns position of an element.
static std::string getString(isc::data::ConstElementPtr scope, const std::string &name)
Returns a string parameter from a scope.
static bool getBoolean(isc::data::ConstElementPtr scope, const std::string &name)
Returns a boolean parameter from a scope.
static int64_t getInteger(isc::data::ConstElementPtr scope, const std::string &name)
Returns an integer parameter from a scope.
To be removed. Please use ConfigError instead.
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 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
static uint32_t optionSpaceToVendorId(const std::string &option_space)
Converts option space name to vendor id.
Definition: libdhcp++.cc:1131
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
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
CfgOptionDefPtr cfg_option_def_
Config option definitions.
virtual boost::shared_ptr< OptionDataParser > createOptionDataParser() const
Returns an instance of the OptionDataListParser to be used in parsing options.
OptionDataListParser(const uint16_t address_family, CfgOptionDefPtr cfg_option_def=CfgOptionDefPtr())
Constructor.
uint16_t address_family_
Address family: AF_INET or AF_INET6.
void parse(const CfgOptionPtr &cfg, isc::data::ConstElementPtr option_data_list)
Parses a list of options, instantiates them and stores in cfg.
std::pair< OptionDescriptor, std::string > parse(isc::data::ConstElementPtr single_option)
Parses ElementPtr containing option definition.
uint16_t address_family_
Address family: AF_INET or AF_INET6.
std::string extractSpace(data::ConstElementPtr parent) const
Retrieves option space name.
util::Optional< bool > extractPersistent(data::ConstElementPtr parent) const
Retrieves persistent/always-send parameter as an optional value.
std::pair< OptionDescriptor, std::string > createOption(isc::data::ConstElementPtr option_data)
Create option instance.
util::Optional< bool > extractCSVFormat(data::ConstElementPtr parent) const
Retrieves csv-format parameter as an optional value.
util::Optional< uint32_t > extractCode(data::ConstElementPtr parent) const
Retrieves parsed option code as an optional value.
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.
std::string extractData(data::ConstElementPtr parent) const
Retrieves option data as a string.
CfgOptionDefPtr cfg_option_def_
Config option definitions.
util::Optional< std::string > extractName(data::ConstElementPtr parent) const
Retrieves parsed option name as an optional value.
OptionDataParser(const uint16_t address_family, CfgOptionDefPtr cfg_option_def=CfgOptionDefPtr())
Constructor.
Option descriptor.
Definition: cfg_option.h:42
OptionPtr option_
Option instance.
Definition: cfg_option.h:45
std::string formatted_value_
Option value in textual (CSV) format.
Definition: cfg_option.h:66
bool persistent_
Persistence flag.
Definition: cfg_option.h:51
static bool validateName(const std::string &name)
Checks that the provided option space name is valid.
Definition: option_space.cc:26
Universe
defines option universe DHCPv4 or DHCPv6
Definition: option.h:83
static const isc::data::SimpleKeywords OPTION4_PARAMETERS
This table defines all option parameters.
static const isc::data::SimpleKeywords OPTION6_PARAMETERS
This table defines all option parameters.
T get() const
Retrieves the encapsulated value.
Definition: optional.h:114
void unspecified(bool unspecified)
Modifies the flag that indicates whether the value is specified or unspecified.
Definition: optional.h:136
#define isc_throw(type, stream)
A shortcut macro to insert known values into exception arguments.
boost::shared_ptr< const Element > ConstElementPtr
Definition: data.h:27
boost::shared_ptr< CfgOption > CfgOptionPtr
Non-const pointer.
Definition: cfg_option.h:706
@ DHO_END
Definition: dhcp4.h:228
@ DHO_PAD
Definition: dhcp4.h:69
boost::shared_ptr< CfgOptionDef > CfgOptionDefPtr
Non-const pointer.
boost::shared_ptr< OptionDefinition > OptionDefinitionPtr
Pointer to option definition object.
boost::shared_ptr< Option > OptionPtr
Definition: option.h:36
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
std::vector< uint8_t > quotedStringToBinary(const std::string &quoted_string)
Converts a string in quotes into vector.
Definition: strutil.cc:196
vector< string > tokens(const std::string &text, const std::string &delim, bool escape)
Split String into Tokens.
Definition: strutil.cc:77
Definition: edns.h:19
Defines the logger used by the top-level component of kea-lfc.
#define DHCP4_OPTION_SPACE
global std option spaces
#define DHCP6_OPTION_SPACE
void setContext(const data::ConstElementPtr &ctx)
Sets user context.
Definition: user_context.h:30