Kea 2.2.0
flex_option.cc
Go to the documentation of this file.
1// Copyright (C) 2019-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
9#include <flex_option.h>
10#include <flex_option_log.h>
11#include <util/strutil.h>
12#include <cc/simple_parser.h>
13#include <dhcp/dhcp4.h>
14#include <dhcp/libdhcp++.h>
16#include <dhcp/option_space.h>
17#include <dhcp/option_vendor.h>
18#include <dhcpsrv/cfgmgr.h>
19#include <eval/eval_context.h>
20
21using namespace isc;
22using namespace isc::data;
23using namespace isc::dhcp;
24using namespace isc::eval;
25using namespace isc::flex_option;
26using namespace isc::log;
27using namespace isc::util;
28using namespace std;
29
30namespace {
31
42void
43parseAction(ConstElementPtr option,
45 Option::Universe universe,
46 const string& name,
48 EvalContext::ParserType parser_type) {
49 ConstElementPtr elem = option->get(name);
50 if (elem) {
51 string expr_text = elem->stringValue();
52 if (expr_text.empty()) {
53 isc_throw(BadValue, "'" << name << "' must not be empty");
54 }
55 if (opt_cfg->getAction() != FlexOptionImpl::NONE) {
56 isc_throw(BadValue, "multiple actions: " << option->str());
57 }
58 opt_cfg->setAction(action);
59 opt_cfg->setText(expr_text);
60 try {
61 EvalContext eval_ctx(universe);
62 eval_ctx.parseString(expr_text, parser_type);
63 ExpressionPtr expr(new Expression(eval_ctx.expression));
64 opt_cfg->setExpr(expr);
65 } catch (const std::exception& ex) {
66 isc_throw(BadValue, "can't parse " << name << " expression ["
67 << expr_text << "] error: " << ex.what());
68 }
69 }
70}
71
72} // end of anonymous namespace
73
74namespace isc {
75namespace flex_option {
76
77const SimpleKeywords FlexOptionImpl::OPTION_PARAMETERS = {
78 { "code", Element::integer },
79 { "name", Element::string },
80 { "space", Element::string },
81 { "csv-format", Element::boolean },
82 { "add", Element::string },
83 { "supersede", Element::string },
84 { "remove", Element::string },
85 { "sub-options", Element::list },
86 { "client-class", Element::string },
87 { "comment", Element::string }
88};
89
90const SimpleKeywords FlexOptionImpl::SUB_OPTION_PARAMETERS = {
91 { "code", Element::integer },
92 { "name", Element::string },
93 { "space", Element::string },
94 { "csv-format", Element::boolean },
95 { "add", Element::string },
96 { "supersede", Element::string },
97 { "remove", Element::string },
98 { "container-add", Element::boolean },
99 { "container-remove", Element::boolean },
100 { "client-class", Element::string },
101 { "comment", Element::string }
102};
103
106 : code_(code), def_(def), action_(NONE), class_("") {
107}
108
110}
111
114 OptionConfigPtr container)
115 : OptionConfig(code, def), container_(container), vendor_id_(0),
116 container_action_(NONE) {
117 if (!container) {
118 isc_throw(Unexpected, "null container?");
119 }
120}
121
123}
124
126}
127
129 sub_option_config_map_.clear();
130 option_config_map_.clear();
131}
132
133void
135 if (!options) {
136 isc_throw(BadValue, "'options' parameter is mandatory");
137 }
138 if (options->getType() != Element::list) {
139 isc_throw(BadValue, "'options' parameter must be a list");
140 }
141 if (options->empty()) {
142 return;
143 }
144 for (auto option : options->listValue()) {
145 parseOptionConfig(option);
146 }
147}
148
149void
150FlexOptionImpl::parseOptionConfig(ConstElementPtr option) {
151 uint16_t family = CfgMgr::instance().getFamily();
152 if (!option) {
153 isc_throw(BadValue, "null option element");
154 }
155 if (option->getType() != Element::map) {
156 isc_throw(BadValue, "option element is not a map");
157 }
158 // See SimpleParser::checkKeywords
159 for (auto entry : option->mapValue()) {
160 if (OPTION_PARAMETERS.count(entry.first) == 0) {
161 isc_throw(BadValue, "unknown parameter '" << entry.first << "'");
162 }
163 Element::types expected = OPTION_PARAMETERS.at(entry.first);
164 if (entry.second->getType() == expected) {
165 continue;
166 }
167 isc_throw(BadValue, "'" << entry.first << "' must be "
168 << (expected == Element::integer ? "an " : "a ")
169 << Element::typeToName(expected)
170 << ": " << entry.second->str());
171 }
172 ConstElementPtr code_elem = option->get("code");
173 ConstElementPtr name_elem = option->get("name");
174 ConstElementPtr space_elem = option->get("space");
175 ConstElementPtr csv_format_elem = option->get("csv-format");
176 ConstElementPtr class_elem = option->get("client-class");
177 ConstElementPtr sub_options = option->get("sub-options");
178 if (!code_elem && !name_elem) {
179 isc_throw(BadValue, "'code' or 'name' must be specified: "
180 << option->str());
181 }
182 string space;
183 Option::Universe universe;
184 if (family == AF_INET) {
185 space = DHCP4_OPTION_SPACE;
186 universe = Option::V4;
187 } else {
188 space = DHCP6_OPTION_SPACE;
189 universe = Option::V6;
190 }
191 if (space_elem) {
192 space = space_elem->stringValue();
193 if (!OptionSpace::validateName(space)) {
194 isc_throw(BadValue, "'" << space << "' is not a valid space name");
195 }
196 }
197 uint16_t code;
198 if (code_elem) {
199 int64_t value = code_elem->intValue();
200 int64_t max_code;
201 if (family == AF_INET) {
202 max_code = numeric_limits<uint8_t>::max();
203 } else {
204 max_code = numeric_limits<uint16_t>::max();
205 }
206 if ((value < 0) || (value > max_code)) {
207 isc_throw(OutOfRange, "invalid 'code' value " << value
208 << " not in [0.." << max_code << "]");
209 }
210 if (space == DHCP4_OPTION_SPACE) {
211 if (value == DHO_PAD) {
213 "invalid 'code' value 0: reserved for PAD");
214 } else if (value == DHO_END) {
216 "invalid 'code' value 255: reserved for END");
217 }
218 } else if (space == DHCP6_OPTION_SPACE) {
219 if (value == 0) {
220 isc_throw(BadValue, "invalid 'code' value 0: reserved");
221 }
222 }
223 code = static_cast<uint16_t>(value);
224 }
226 if (name_elem) {
227 string name = name_elem->stringValue();
228 if (name.empty()) {
229 isc_throw(BadValue, "'name' must not be empty");
230 }
231 def = LibDHCP::getOptionDef(space, name);
232 if (!def) {
233 def = LibDHCP::getRuntimeOptionDef(space, name);
234 }
235 if (!def) {
236 def = LibDHCP::getLastResortOptionDef(space, name);
237 }
238 if (!def) {
239 isc_throw(BadValue, "no known '" << name << "' option in '"
240 << space << "' space");
241 }
242 if (code_elem && (def->getCode() != code)) {
243 isc_throw(BadValue, "option '" << name << "' is defined as code: "
244 << def->getCode() << ", not the specified code: "
245 << code);
246 }
247 code = def->getCode();
248 }
249
250 bool csv_format = false;
251 if (csv_format_elem) {
252 csv_format = csv_format_elem->boolValue();
253 }
254
255 if (!csv_format && !sub_options) {
256 // No definition means no csv format.
257 if (def) {
258 def.reset();
259 }
260 } else if (!def) {
261 // Definition is required with csv format.
262 def = isc::dhcp::LibDHCP::getOptionDef(space, code);
263 if (!def) {
265 }
266 if (!def) {
268 }
269 if (!def && csv_format) {
270 isc_throw(BadValue, "no known option with code '" << code
271 << "' in '" << space << "' space");
272 }
273 }
274
275 OptionConfigPtr opt_cfg(new OptionConfig(code, def));
276 if (class_elem) {
277 opt_cfg->setClass(class_elem->stringValue());
278 }
279
280 // opt_cfg initial action is NONE.
281 if (sub_options) {
282 string action;
283 if (option->contains("add")) {
284 action = "add";
285 } else if (option->contains("supersede")) {
286 action = "supersede";
287 } else if (option->contains("remove")) {
288 action = "remove";
289 }
290 if (!action.empty()) {
291 isc_throw(BadValue, "'sub-options' and '" << action << "' are "
292 << "incompatible in the same entry");
293 }
294 parseSubOptions(sub_options, opt_cfg, universe);
295 } else {
296 parseAction(option, opt_cfg, universe,
297 "add", ADD, EvalContext::PARSER_STRING);
298 parseAction(option, opt_cfg, universe,
299 "supersede", SUPERSEDE, EvalContext::PARSER_STRING);
300 parseAction(option, opt_cfg, universe,
301 "remove", REMOVE, EvalContext::PARSER_BOOL);
302
303 if (opt_cfg->getAction() == NONE) {
304 isc_throw(BadValue, "no action: " << option->str());
305 }
306
307 // The [] operator creates the item if it does not exist before
308 // returning a reference to it.
309 OptionConfigList& opt_lst = option_config_map_[code];
310 opt_lst.push_back(opt_cfg);
311 }
312}
313
314void
315FlexOptionImpl::parseSubOptions(ConstElementPtr sub_options,
316 OptionConfigPtr opt_cfg,
317 Option::Universe universe) {
318 for (ConstElementPtr sub_option : sub_options->listValue()) {
319 parseSubOption(sub_option, opt_cfg, universe);
320 }
321}
322
323void
324FlexOptionImpl::parseSubOption(ConstElementPtr sub_option,
325 OptionConfigPtr opt_cfg,
326 Option::Universe universe) {
327 if (!sub_option) {
328 isc_throw(BadValue, "null sub-option element");
329 }
330 if (sub_option->getType() != Element::map) {
331 isc_throw(BadValue, "sub-option element is not a map");
332 }
333 // See SimpleParser::checkKeywords
334 for (auto entry : sub_option->mapValue()) {
335 if (SUB_OPTION_PARAMETERS.count(entry.first) == 0) {
336 isc_throw(BadValue, "unknown parameter '" << entry.first << "'");
337 }
338 Element::types expected = SUB_OPTION_PARAMETERS.at(entry.first);
339 if (entry.second->getType() == expected) {
340 continue;
341 }
342 isc_throw(BadValue, "'" << entry.first << "' must be "
343 << (expected == Element::integer ? "an " : "a ")
344 << Element::typeToName(expected)
345 << ": " << entry.second->str());
346 }
347 ConstElementPtr code_elem = sub_option->get("code");
348 ConstElementPtr name_elem = sub_option->get("name");
349 ConstElementPtr space_elem = sub_option->get("space");
350 ConstElementPtr csv_format_elem = sub_option->get("csv-format");
351 ConstElementPtr class_elem = sub_option->get("client-class");
352 if (!code_elem && !name_elem) {
353 isc_throw(BadValue, "'code' or 'name' must be specified: "
354 << sub_option->str());
355 }
356 string space;
357 if (space_elem) {
358 space = space_elem->stringValue();
359 if (!OptionSpace::validateName(space)) {
360 isc_throw(BadValue, "'" << space << "' is not a valid space name");
361 }
362 } else {
363 OptionDefinitionPtr opt_def = opt_cfg->getOptionDef();
364 if (!opt_def) {
365 isc_throw(BadValue, "container is not defined: can't get space");
366 }
367 space = opt_def->getEncapsulatedSpace();
368 if (space.empty()) {
369 isc_throw(BadValue, "container does not encapsulate a space");
370 }
371 }
372 uint32_t vendor_id = LibDHCP::optionSpaceToVendorId(space);
373 uint16_t code;
374 if (code_elem) {
375 int64_t value = code_elem->intValue();
376 int64_t max_code;
377 if (universe == Option::V4) {
378 max_code = numeric_limits<uint8_t>::max();
379 } else {
380 max_code = numeric_limits<uint16_t>::max();
381 }
382 if ((value < 0) || (value > max_code)) {
383 isc_throw(OutOfRange, "invalid 'code' value " << value
384 << " not in [0.." << max_code << "]");
385 }
386 code = static_cast<uint16_t>(value);
387 }
389 if (name_elem) {
390 string name = name_elem->stringValue();
391 if (name.empty()) {
392 isc_throw(BadValue, "'name' must not be empty");
393 }
394 def = LibDHCP::getOptionDef(space, name);
395 if (!def && vendor_id) {
396 def = LibDHCP::getVendorOptionDef(universe, vendor_id, name);
397 }
398 if (!def) {
399 def = LibDHCP::getRuntimeOptionDef(space, name);
400 }
401 if (!def) {
402 isc_throw(BadValue, "no known '" << name << "' sub-option in '"
403 << space << "' space");
404 }
405 if (code_elem && (def->getCode() != code)) {
406 isc_throw(BadValue, "sub-option '" << name
407 << "' is defined as code: " << def->getCode()
408 << ", not the specified code: " << code);
409 }
410 code = def->getCode();
411 }
412
413 bool csv_format = false;
414 if (csv_format_elem) {
415 csv_format = csv_format_elem->boolValue();
416 }
417
418 if (!csv_format) {
419 // No definition means no csv format.
420 if (def) {
421 def.reset();
422 }
423 } else if (!def) {
424 // Definition is required with csv format.
425 def = isc::dhcp::LibDHCP::getOptionDef(space, code);
426 if (!def && vendor_id) {
427 def = LibDHCP::getVendorOptionDef(universe, vendor_id, code);
428 }
429 if (!def) {
431 }
432 if (!def) {
433 isc_throw(BadValue, "no known sub-option with code '" << code
434 << "' in '" << space << "' space");
435 }
436 }
437
438 SubOptionConfigPtr sub_cfg(new SubOptionConfig(code, def, opt_cfg));
439 if (vendor_id) {
440 if (((universe == Option::V4) &&
441 (opt_cfg->getCode() == DHO_VIVSO_SUBOPTIONS)) ||
442 ((universe == Option::V6) &&
443 (opt_cfg->getCode() == D6O_VENDOR_OPTS))) {
444 sub_cfg->setVendorId(vendor_id);
445 }
446 }
447 if (class_elem) {
448 sub_cfg->setClass(class_elem->stringValue());
449 }
450
451 // sub_cfg initial action is NONE.
452 parseAction(sub_option, sub_cfg, universe,
453 "add", ADD, EvalContext::PARSER_STRING);
454 parseAction(sub_option, sub_cfg, universe,
455 "supersede", SUPERSEDE, EvalContext::PARSER_STRING);
456 parseAction(sub_option, sub_cfg, universe,
457 "remove", REMOVE, EvalContext::PARSER_BOOL);
458
459 if (sub_cfg->getAction() == NONE) {
460 isc_throw(BadValue, "no action: " << sub_option->str());
461 }
462
463 ConstElementPtr container_add = sub_option->get("container-add");
464 ConstElementPtr container_remove = sub_option->get("container-remove");
465 if ((sub_cfg->getAction() == ADD) || (sub_cfg->getAction() == SUPERSEDE)) {
466 sub_cfg->setContainerAction(ADD);
467 if (container_add && !container_add->boolValue()) {
468 sub_cfg->setContainerAction(NONE);
469 }
470 } else if (sub_cfg->getAction() == REMOVE) {
471 sub_cfg->setContainerAction(REMOVE);
472 if (container_remove && !container_remove->boolValue()) {
473 sub_cfg->setContainerAction(NONE);
474 }
475 }
476
477 // The [] operator creates the item if it does not exist before
478 // returning a reference to it.
479 uint16_t opt_code = opt_cfg->getCode();
480 SubOptionConfigMap& sub_map = sub_option_config_map_[opt_code];
481 if (sub_map.count(code)) {
482 isc_throw(BadValue, "sub-option " << code << " of option " << opt_code
483 << " was already specified");
484 }
485 sub_map[code] = sub_cfg;
486}
487
488void
489FlexOptionImpl::logClass(const ClientClass& client_class, uint16_t code) {
492 .arg(client_class)
493 .arg(code);
494 return;
495}
496
497void
498FlexOptionImpl::logAction(Action action, uint16_t code,
499 const string& value) {
500 if (action == NONE) {
501 return;
502 }
503 if (action == REMOVE) {
506 .arg(code);
507 return;
508 }
509 ostringstream repr;
510 if (str::isPrintable(value)) {
511 repr << "'" << value << "'";
512 } else {
513 repr << "0x" << hex;
514 for (const char& ch : value) {
515 repr << setw(2) << setfill('0') << static_cast<unsigned>(ch);
516 }
517 }
518 if (action == SUPERSEDE) {
521 .arg(code)
522 .arg(repr.str());
523 } else {
526 .arg(code)
527 .arg(repr.str());
528 }
529}
530
531void
532FlexOptionImpl::logAction(Action action, uint16_t code,
533 uint32_t vendor_id) {
534 if (action == SUPERSEDE) {
537 .arg(code)
538 .arg(vendor_id);
539 } else {
542 .arg(code)
543 .arg(vendor_id);
544 }
545}
546
547void
548FlexOptionImpl::logSubClass(const ClientClass& client_class, uint16_t code,
549 uint16_t container_code) {
552 .arg(client_class)
553 .arg(code)
554 .arg(container_code);
555 return;
556}
557
558void
560 uint16_t container_code,
561 const string& value) {
562 if (action == NONE) {
563 return;
564 }
565 if (action == REMOVE) {
568 .arg(code)
569 .arg(container_code);
570 return;
571 }
572 ostringstream repr;
573 if (str::isPrintable(value)) {
574 repr << "'" << value << "'";
575 } else {
576 repr << "0x" << hex;
577 for (const char& ch : value) {
578 repr << setw(2) << setfill('0') << static_cast<unsigned>(ch);
579 }
580 }
581 if (action == SUPERSEDE) {
584 .arg(code)
585 .arg(container_code)
586 .arg(repr.str());
587 } else {
590 .arg(code)
591 .arg(container_code)
592 .arg(repr.str());
593 }
594}
595
596bool
597FlexOptionImpl::checkVendor(OptionPtr opt, uint32_t vendor_id) {
598 OptionVendorPtr vendor = boost::dynamic_pointer_cast<OptionVendor>(opt);
599 bool ret = (!vendor || (vendor->getVendorId() == vendor_id));
600 if (!ret) {
603 .arg(opt->getType())
604 .arg(vendor->getVendorId())
605 .arg(vendor_id);
606 }
607 return (ret);
608}
609
610} // end of namespace flex_option
611} // end of namespace isc
A generic exception that is thrown if a parameter given to a method is considered invalid in that con...
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 parameter given to a method would refer to or modify out-of-r...
A generic exception that is thrown when an unexpected error condition occurs.
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 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
Universe
defines option universe DHCPv4 or DHCPv6
Definition: option.h:83
Evaluation context, an interface to the expression evaluation.
Definition: eval_context.h:34
ParserType
Specifies what type of expression the parser is expected to see.
Definition: eval_context.h:38
OptionConfig(uint16_t code, isc::dhcp::OptionDefinitionPtr def)
Constructor.
Definition: flex_option.cc:104
SubOptionConfig(uint16_t code, isc::dhcp::OptionDefinitionPtr def, OptionConfigPtr container)
Constructor.
Definition: flex_option.cc:112
void configure(isc::data::ConstElementPtr options)
Configure the Flex Option implementation.
Definition: flex_option.cc:134
boost::shared_ptr< OptionConfig > OptionConfigPtr
The type of shared pointers to option config.
Definition: flex_option.h:159
boost::shared_ptr< SubOptionConfig > SubOptionConfigPtr
The type of shared pointers to sub-option config.
Definition: flex_option.h:244
static void logAction(Action action, uint16_t code, const std::string &value)
Log the action for option.
Definition: flex_option.cc:498
static void logSubClass(const isc::dhcp::ClientClass &client_class, uint16_t code, uint16_t container_code)
Log the client class for sub-option.
Definition: flex_option.cc:548
static void logSubAction(Action action, uint16_t code, uint16_t container_code, const std::string &value)
Log the action for sub-option.
Definition: flex_option.cc:559
std::list< OptionConfigPtr > OptionConfigList
The type of lists of shared pointers to option config.
Definition: flex_option.h:162
static bool checkVendor(isc::dhcp::OptionPtr opt, uint32_t vendor_id)
Check vendor option vendor id mismatch.
Definition: flex_option.cc:597
std::map< uint16_t, SubOptionConfigPtr > SubOptionConfigMap
The type of the sub-option config map.
Definition: flex_option.h:248
static void logClass(const isc::dhcp::ClientClass &client_class, uint16_t code)
Log the client class for option.
Definition: flex_option.cc:489
@ D6O_VENDOR_OPTS
Definition: dhcp6.h:37
#define isc_throw(type, stream)
A shortcut macro to insert known values into exception arguments.
const isc::log::MessageID FLEX_OPTION_PROCESS_VENDOR_ID_MISMATCH
const isc::log::MessageID FLEX_OPTION_PROCESS_CLIENT_CLASS
const isc::log::MessageID FLEX_OPTION_PROCESS_ADD
const isc::log::MessageID FLEX_OPTION_PROCESS_SUB_SUPERSEDE
const isc::log::MessageID FLEX_OPTION_PROCESS_SUB_CLIENT_CLASS
const isc::log::MessageID FLEX_OPTION_PROCESS_REMOVE
const isc::log::MessageID FLEX_OPTION_PROCESS_SUPERSEDE
const isc::log::MessageID FLEX_OPTION_PROCESS_SUB_ADD
const isc::log::MessageID FLEX_OPTION_PROCESS_SUB_REMOVE
#define LOG_DEBUG(LOGGER, LEVEL, MESSAGE)
Macro to conveniently test debug output and log it.
Definition: macros.h:14
boost::shared_ptr< const Element > ConstElementPtr
Definition: data.h:27
std::map< std::string, isc::data::Element::types > SimpleKeywords
This specifies all accepted keywords with their types.
boost::shared_ptr< OptionVendor > OptionVendorPtr
Pointer to a vendor option.
std::string ClientClass
Defines a single class name.
Definition: classify.h:42
@ DHO_END
Definition: dhcp4.h:228
@ DHO_PAD
Definition: dhcp4.h:69
@ DHO_VIVSO_SUBOPTIONS
Definition: dhcp4.h:190
boost::shared_ptr< OptionDefinition > OptionDefinitionPtr
Pointer to option definition object.
boost::shared_ptr< Expression > ExpressionPtr
Definition: token.h:30
std::vector< TokenPtr > Expression
This is a structure that holds an expression converted to RPN.
Definition: token.h:28
boost::shared_ptr< Option > OptionPtr
Definition: option.h:36
isc::log::Logger flex_option_logger("flex-option-hooks")
const int DBGLVL_TRACE_BASIC
Trace basic operations.
Definition: log_dbglevels.h:69
bool isPrintable(const std::string &content)
Check if a string is printable.
Definition: strutil.h:366
Definition: edns.h:19
Defines the logger used by the top-level component of kea-lfc.
uint16_t code_
#define DHCP4_OPTION_SPACE
global std option spaces
#define DHCP6_OPTION_SPACE