Kea 2.7.6
option_data_parser.cc
Go to the documentation of this file.
1// Copyright (C) 2017-2024 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/encode.h>
19#include <util/str.h>
20#include <boost/make_shared.hpp>
21#include <limits>
22#include <vector>
23
24using namespace isc::data;
25using namespace isc::util;
26
27namespace isc {
28namespace dhcp {
29
30// **************************** OptionDataParser *************************
31
32OptionDataParser::OptionDataParser(const uint16_t address_family,
33 CfgOptionDefPtr cfg_option_def)
34 : address_family_(address_family), cfg_option_def_(cfg_option_def) {
35}
36
37std::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
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
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
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 (Optional<std::string>());
121 }
122
123 return (Optional<std::string>(data));
124}
125
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
139std::string
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)) {
158 << "' option space name is reserved for DHCPv4 server");
159
160 } else if ((space == DHCP6_OPTION_SPACE) &&
161 (address_family_ == AF_INET)) {
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
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
190 bool cancel = false;
191 try {
192 cancel = getBoolean(parent, "never-send");
193
194 } catch (...) {
195 return (Optional<bool>());
196 }
197
198 return (Optional<bool>(cancel));
199}
200
202OptionDataParser::findOptionDefinition(const std::string& option_space,
203 const Optional<uint32_t>& option_code,
204 const Optional<std::string>& option_name) const {
206 if (cfg_option_def_) {
207 // Check if the definition was given in the constructor
208 if (option_code.unspecified()) {
209 def = cfg_option_def_->get(option_space, option_name);
210 } else {
211 def = cfg_option_def_->get(option_space, option_code);
212 }
213 }
214
215 if (!def) {
216 // Check if this is a standard option.
217 if (option_code.unspecified()) {
218 def = LibDHCP::getOptionDef(option_space, option_name);
219 } else {
220 def = LibDHCP::getOptionDef(option_space, option_code);
221 }
222 }
223
224 if (!def) {
225 // Check if this is a vendor-option. If it is, get vendor-specific
226 // definition.
227 uint32_t vendor_id = LibDHCP::optionSpaceToVendorId(option_space);
228 if (vendor_id) {
229 const Option::Universe u = address_family_ == AF_INET ?
231 if (option_code.unspecified()) {
232 def = LibDHCP::getVendorOptionDef(u, vendor_id, option_name);
233 } else {
234 def = LibDHCP::getVendorOptionDef(u, vendor_id, option_code);
235 }
236 }
237 }
238
239 if (!def) {
240 // Check if this is an option specified by a user. We used to
241 // check that in the staging configuration, but when the configuration
242 // changes are caused by a command the staging configuration doesn't
243 // exist. What is always available is the container holding runtime
244 // option definitions in LibDHCP. It holds option definitions from
245 // the staging configuration in case of the full reconfiguration or
246 // the definitions from the current configuration in case there is
247 // no staging configuration (after configuration commit). In other
248 // words, runtime options are always the ones that we need here.
249 if (option_code.unspecified()) {
250 def = LibDHCP::getRuntimeOptionDef(option_space, option_name);
251 } else {
252 def = LibDHCP::getRuntimeOptionDef(option_space, option_code);
253 }
254 }
255
256 if (!def) {
257 // Finish by last resort definitions.
258 if (option_code.unspecified()) {
259 def = LibDHCP::getLastResortOptionDef(option_space, option_name);
260 } else {
261 def = LibDHCP::getLastResortOptionDef(option_space, option_code);
262 }
263 }
264
265 return (def);
266}
267
268std::pair<OptionDescriptor, std::string>
270 const Option::Universe universe = address_family_ == AF_INET ?
272
273 Optional<uint32_t> code_param = extractCode(option_data);
274 Optional<std::string> name_param = extractName(option_data);
275 Optional<bool> csv_format_param = extractCSVFormat(option_data);
276 Optional<bool> persist_param = extractPersistent(option_data);
277 Optional<bool> cancel_param = extractCancelled(option_data);
278 Optional<std::string> data_param = extractData(option_data);
279 std::string space_param = extractSpace(option_data);
280 ConstElementPtr user_context = option_data->get("user-context");
281 ConstElementPtr client_classes = option_data->get("client-classes");
282
283 // Require that option code or option name is specified.
284 if (code_param.unspecified() && name_param.unspecified()) {
285 isc_throw(DhcpConfigError, "option data configuration requires one of"
286 " 'code' or 'name' parameters to be specified"
287 << " (" << option_data->getPosition() << ")");
288 }
289
290 // Try to find a corresponding option definition using option code or
291 // option name.
292 OptionDefinitionPtr def = findOptionDefinition(space_param, code_param, name_param);
293
294 // If there is no definition, the user must not explicitly enable the
295 // use of csv-format.
296 if (!def) {
297 // If explicitly requested that the CSV format is to be used,
298 // the option definition is a must.
299 if (!csv_format_param.unspecified() && csv_format_param) {
300 isc_throw(DhcpConfigError, "definition for the option '"
301 << space_param << "." << name_param
302 << "' having code '" << code_param
303 << "' does not exist ("
304 << getPosition("name", option_data)
305 << ")");
306
307 // If there is no option definition and the option code is not specified
308 // we have no means to find the option code.
309 } else if (!name_param.unspecified() && code_param.unspecified()) {
310 isc_throw(DhcpConfigError, "definition for the option '"
311 << space_param << "." << name_param
312 << "' does not exist ("
313 << getPosition("name", option_data)
314 << ")");
315 }
316 } else {
317 // Option name is specified it should match the name in the definition.
318 if (!name_param.unspecified() && (def->getName() != name_param.get())) {
319 isc_throw(DhcpConfigError, "specified option name '"
320 << name_param << "' does not match the "
321 << "option definition: '" << space_param
322 << "." << def->getName() << "' ("
323 << getPosition("name", option_data)
324 << ")");
325 }
326 }
327
328 // No data and cancelled is a supported special case.
329 if (!cancel_param.unspecified() && cancel_param &&
330 data_param.unspecified()) {
331 uint16_t code;
332 if (def) {
333 code = def->getCode();
334 } else {
335 code = static_cast<uint16_t>(code_param);
336 }
337 OptionPtr option(new Option(universe, code));
338 bool persistent = !persist_param.unspecified() && persist_param;
339 OptionDescriptor desc(option, persistent, true, "", user_context);
340 return (make_pair(desc, space_param));
341 }
342
343 // Transform string of hexadecimal digits into binary format.
344 std::vector<uint8_t> binary;
345 std::vector<std::string> data_tokens;
346
347 // If the definition is available and csv-format hasn't been explicitly
348 // disabled, we will parse the data as comma separated values.
349 if (def && (csv_format_param.unspecified() || csv_format_param)) {
350 // If the option data is specified as a string of comma
351 // separated values then we need to split this string into
352 // individual values - each value will be used to initialize
353 // one data field of an option.
354 // It is the only usage of the escape option: this allows
355 // to embed commas in individual values and to return
356 // for instance a string value with embedded commas.
357 data_tokens = isc::util::str::tokens(data_param, ",", true);
358
359 } else {
360 // Try to convert the values in quotes into a vector of ASCII codes.
361 // If the identifier lacks opening and closing quote, this will return
362 // an empty value, in which case we'll try to decode it as a string of
363 // hexadecimal digits.
364 try {
365 binary = util::str::quotedStringToBinary(data_param);
366 if (binary.empty()) {
367 util::str::decodeFormattedHexString(data_param, binary);
368 }
369 } catch (...) {
370 isc_throw(DhcpConfigError, "option data is not a valid"
371 << " string of hexadecimal digits: " << data_param
372 << " ("
373 << getPosition("data", option_data)
374 << ")");
375 }
376 }
377
378 OptionDescriptor desc(false, false);
379
380 if (!def) {
381 // @todo We have a limited set of option definitions initialized at
382 // the moment. In the future we want to initialize option definitions
383 // for all options. Consequently an error will be issued if an option
384 // definition does not exist for a particular option code. For now it is
385 // ok to create generic option if definition does not exist.
386 OptionPtr option(new Option(universe, static_cast<uint16_t>(code_param),
387 binary));
388
389 desc.option_ = option;
390 desc.persistent_ = !persist_param.unspecified() && persist_param;
391 desc.cancelled_ = !cancel_param.unspecified() && cancel_param;
392 } else {
393 // Option definition has been found so let's use it to create
394 // an instance of our option.
395 try {
396 bool use_csv = csv_format_param.unspecified() || csv_format_param;
397 OptionPtr option = use_csv ?
398 def->optionFactory(universe, def->getCode(), data_tokens) :
399 def->optionFactory(universe, def->getCode(), binary);
400 desc.option_ = option;
401 desc.persistent_ = !persist_param.unspecified() && persist_param;
402 desc.cancelled_ = !cancel_param.unspecified() && cancel_param;
403 if (use_csv) {
404 desc.formatted_value_ = data_param;
405 }
406 } catch (const isc::Exception& ex) {
407 isc_throw(DhcpConfigError, "option data does not match"
408 << " option definition (space: " << space_param
409 << ", code: " << def->getCode() << "): "
410 << ex.what() << " ("
411 << getPosition("data", option_data)
412 << ")");
413 }
414 }
415
416 // Check PAD and END in (and only in) dhcp4 space.
417 if (space_param == DHCP4_OPTION_SPACE) {
418 if (desc.option_->getType() == DHO_PAD) {
419 isc_throw(DhcpConfigError, "invalid option code '0': "
420 << "reserved for PAD ("
421 << option_data->getPosition() << ")");
422 } else if (desc.option_->getType() == DHO_END) {
423 isc_throw(DhcpConfigError, "invalid option code '255': "
424 << "reserved for END ("
425 << option_data->getPosition() << ")");
426 }
427 }
428
429 // For dhcp6 space the value 0 is reserved.
430 if (space_param == DHCP6_OPTION_SPACE) {
431 if (desc.option_->getType() == 0) {
432 isc_throw(DhcpConfigError, "invalid option code '0': "
433 << "reserved value ("
434 << option_data->getPosition() << ")");
435 }
436 }
437
438
439 // Add user context
440 if (user_context) {
441 desc.setContext(user_context);
442 }
443
444#if 1
445 desc.client_classes_.fromElement(client_classes);
446#else
447 if (client_classes) {
448 for (auto const& class_element : client_classes->listValue()) {
449 desc.addClientClass(class_element->stringValue());
450 }
451 }
452#endif
453
454 // All went good, so we can set the option space name.
455 return (make_pair(desc, space_param));
456}
457
458// **************************** OptionDataListParser *************************
460 //const CfgOptionPtr& cfg,
461 const uint16_t address_family,
462 CfgOptionDefPtr cfg_option_def)
463 : address_family_(address_family), cfg_option_def_(cfg_option_def) {
464}
465
466
468 isc::data::ConstElementPtr option_data_list,
469 bool encapsulate) {
470 auto option_parser = createOptionDataParser();
471 for (auto const& data : option_data_list->listValue()) {
472 std::pair<OptionDescriptor, std::string> option =
473 option_parser->parse(data);
474 // Use the option description to keep the formatted value
475 cfg->add(option.first, option.second);
476 if (encapsulate) {
477 cfg->encapsulate();
478 }
479 }
480}
481
482boost::shared_ptr<OptionDataParser>
484 auto parser = boost::make_shared<OptionDataParser>(address_family_, cfg_option_def_);
485 return (parser);
486}
487
488} // end of namespace isc::dhcp
489} // 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.
void fromElement(isc::data::ConstElementPtr list)
Sets contents from a ListElement.
Definition classify.cc:104
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:132
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:174
static uint32_t optionSpaceToVendorId(const std::string &option_space)
Converts option space name to vendor id.
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:195
static OptionDefinitionPtr getLastResortOptionDef(const std::string &space, const uint16_t code)
Returns last resort option definition by space and option code.
Definition libdhcp++.cc:253
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.
void parse(const CfgOptionPtr &cfg, isc::data::ConstElementPtr option_data_list, bool encapsulate=true)
Parses a list of options, instantiates them and stores in cfg.
OptionDataListParser(const uint16_t address_family, CfgOptionDefPtr cfg_option_def=CfgOptionDefPtr())
Constructor.
uint16_t address_family_
Address family: AF_INET or AF_INET6.
util::Optional< std::string > extractData(data::ConstElementPtr parent) const
Retrieves option data as a string.
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.
CfgOptionDefPtr cfg_option_def_
Config option definitions.
util::Optional< std::string > extractName(data::ConstElementPtr parent) const
Retrieves parsed option name as an optional value.
util::Optional< bool > extractCancelled(data::ConstElementPtr parent) const
Retrieves cancelled/never-send parameter as an optional value.
OptionDataParser(const uint16_t address_family, CfgOptionDefPtr cfg_option_def=CfgOptionDefPtr())
Constructor.
Option descriptor.
Definition cfg_option.h:48
OptionPtr option_
Option instance.
Definition cfg_option.h:51
void addClientClass(const std::string &class_name)
Adds new client class for which the option is allowed.
Definition cfg_option.cc:55
bool cancelled_
Cancelled flag.
Definition cfg_option.h:65
std::string formatted_value_
Option value in textual (CSV) format.
Definition cfg_option.h:80
ClientClasses client_classes_
Collection of classes for the which option is allowed.
Definition cfg_option.h:95
bool persistent_
Persistence flag.
Definition cfg_option.h:57
static bool validateName(const std::string &name)
Checks that the provided option space name is valid.
Universe
defines option universe DHCPv4 or DHCPv6
Definition option.h:90
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.
A template representing an optional value.
Definition optional.h:36
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:29
boost::shared_ptr< CfgOption > CfgOptionPtr
Non-const pointer.
Definition cfg_option.h:832
@ DHO_END
Definition dhcp4.h:229
@ 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:37
void decodeFormattedHexString(const string &hex_string, vector< uint8_t > &binary)
Converts a formatted string of hexadecimal digits into a vector.
Definition str.cc:212
vector< string > tokens(const string &text, const string &delim, bool escape)
Split string into tokens.
Definition str.cc:52
vector< uint8_t > quotedStringToBinary(const string &quoted_string)
Converts a string in quotes into vector.
Definition str.cc:139
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.