Kea 2.5.8
flex_option.cc
Go to the documentation of this file.
1// Copyright (C) 2019-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
9#include <flex_option.h>
10#include <flex_option_log.h>
11#include <util/str.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 const& 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 const& 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 // The code is always set below but some compilers can't see that...
198 uint16_t code = 0;
199 if (code_elem) {
200 int64_t value = code_elem->intValue();
201 int64_t max_code;
202 if (family == AF_INET) {
203 max_code = numeric_limits<uint8_t>::max();
204 } else {
205 max_code = numeric_limits<uint16_t>::max();
206 }
207 if ((value < 0) || (value > max_code)) {
208 isc_throw(OutOfRange, "invalid 'code' value " << value
209 << " not in [0.." << max_code << "]");
210 }
211 if (space == DHCP4_OPTION_SPACE) {
212 if (value == DHO_PAD) {
214 "invalid 'code' value 0: reserved for PAD");
215 } else if (value == DHO_END) {
217 "invalid 'code' value 255: reserved for END");
218 }
219 } else if (space == DHCP6_OPTION_SPACE) {
220 if (value == 0) {
221 isc_throw(BadValue, "invalid 'code' value 0: reserved");
222 }
223 }
224 code = static_cast<uint16_t>(value);
225 }
227 if (name_elem) {
228 string name = name_elem->stringValue();
229 if (name.empty()) {
230 isc_throw(BadValue, "'name' must not be empty");
231 }
232 def = LibDHCP::getOptionDef(space, name);
233 if (!def) {
234 def = LibDHCP::getRuntimeOptionDef(space, name);
235 }
236 if (!def) {
237 def = LibDHCP::getLastResortOptionDef(space, name);
238 }
239 if (!def) {
240 isc_throw(BadValue, "no known '" << name << "' option in '"
241 << space << "' space");
242 }
243 if (code_elem && (def->getCode() != code)) {
244 isc_throw(BadValue, "option '" << name << "' is defined as code: "
245 << def->getCode() << ", not the specified code: "
246 << code);
247 }
248 code = def->getCode();
249 }
250
251 bool csv_format = false;
252 if (csv_format_elem) {
253 csv_format = csv_format_elem->boolValue();
254 }
255
256 if (!csv_format && !sub_options) {
257 // No definition means no csv format.
258 if (def) {
259 def.reset();
260 }
261 } else if (!def) {
262 // Definition is required with csv format.
263 def = isc::dhcp::LibDHCP::getOptionDef(space, code);
264 if (!def) {
266 }
267 if (!def) {
269 }
270 if (!def && csv_format) {
271 isc_throw(BadValue, "no known option with code '" << code
272 << "' in '" << space << "' space");
273 }
274 }
275
276 OptionConfigPtr opt_cfg(new OptionConfig(code, def));
277 if (class_elem) {
278 opt_cfg->setClass(class_elem->stringValue());
279 }
280
281 // opt_cfg initial action is NONE.
282 if (sub_options) {
283 string action;
284 if (option->contains("add")) {
285 action = "add";
286 } else if (option->contains("supersede")) {
287 action = "supersede";
288 } else if (option->contains("remove")) {
289 action = "remove";
290 }
291 if (!action.empty()) {
292 isc_throw(BadValue, "'sub-options' and '" << action << "' are "
293 << "incompatible in the same entry");
294 }
295 parseSubOptions(sub_options, opt_cfg, universe);
296 } else {
297 parseAction(option, opt_cfg, universe,
299 parseAction(option, opt_cfg, universe,
301 parseAction(option, opt_cfg, universe,
303
304 if (opt_cfg->getAction() == NONE) {
305 isc_throw(BadValue, "no action: " << option->str());
306 }
307
308 // The [] operator creates the item if it does not exist before
309 // returning a reference to it.
310 OptionConfigList& opt_lst = option_config_map_[code];
311 opt_lst.push_back(opt_cfg);
312 }
313}
314
315void
316FlexOptionImpl::parseSubOptions(ConstElementPtr sub_options,
317 OptionConfigPtr opt_cfg,
318 Option::Universe universe) {
319 for (auto const& sub_option : sub_options->listValue()) {
320 parseSubOption(sub_option, opt_cfg, universe);
321 }
322}
323
324void
325FlexOptionImpl::parseSubOption(ConstElementPtr sub_option,
326 OptionConfigPtr opt_cfg,
327 Option::Universe universe) {
328 if (!sub_option) {
329 isc_throw(BadValue, "null sub-option element");
330 }
331 if (sub_option->getType() != Element::map) {
332 isc_throw(BadValue, "sub-option element is not a map");
333 }
334 // See SimpleParser::checkKeywords
335 for (auto const& entry : sub_option->mapValue()) {
336 if (SUB_OPTION_PARAMETERS.count(entry.first) == 0) {
337 isc_throw(BadValue, "unknown parameter '" << entry.first << "'");
338 }
339 Element::types expected = SUB_OPTION_PARAMETERS.at(entry.first);
340 if (entry.second->getType() == expected) {
341 continue;
342 }
343 isc_throw(BadValue, "'" << entry.first << "' must be "
344 << (expected == Element::integer ? "an " : "a ")
345 << Element::typeToName(expected)
346 << ": " << entry.second->str());
347 }
348 ConstElementPtr code_elem = sub_option->get("code");
349 ConstElementPtr name_elem = sub_option->get("name");
350 ConstElementPtr space_elem = sub_option->get("space");
351 ConstElementPtr csv_format_elem = sub_option->get("csv-format");
352 ConstElementPtr class_elem = sub_option->get("client-class");
353 if (!code_elem && !name_elem) {
354 isc_throw(BadValue, "'code' or 'name' must be specified: "
355 << sub_option->str());
356 }
357 string space;
358 if (space_elem) {
359 space = space_elem->stringValue();
360 if (!OptionSpace::validateName(space)) {
361 isc_throw(BadValue, "'" << space << "' is not a valid space name");
362 }
363 } else {
364 OptionDefinitionPtr opt_def = opt_cfg->getOptionDef();
365 if (!opt_def) {
366 isc_throw(BadValue, "container is not defined: can't get space");
367 }
368 space = opt_def->getEncapsulatedSpace();
369 if (space.empty()) {
370 isc_throw(BadValue, "container does not encapsulate a space");
371 }
372 }
373 uint32_t vendor_id = LibDHCP::optionSpaceToVendorId(space);
374 uint16_t code;
375 if (code_elem) {
376 int64_t value = code_elem->intValue();
377 int64_t max_code;
378 if (universe == Option::V4) {
379 max_code = numeric_limits<uint8_t>::max();
380 } else {
381 max_code = numeric_limits<uint16_t>::max();
382 }
383 if ((value < 0) || (value > max_code)) {
384 isc_throw(OutOfRange, "invalid 'code' value " << value
385 << " not in [0.." << max_code << "]");
386 }
387 code = static_cast<uint16_t>(value);
388 }
390 if (name_elem) {
391 string name = name_elem->stringValue();
392 if (name.empty()) {
393 isc_throw(BadValue, "'name' must not be empty");
394 }
395 def = LibDHCP::getOptionDef(space, name);
396 if (!def && vendor_id) {
397 def = LibDHCP::getVendorOptionDef(universe, vendor_id, name);
398 }
399 if (!def) {
400 def = LibDHCP::getRuntimeOptionDef(space, name);
401 }
402 if (!def) {
403 isc_throw(BadValue, "no known '" << name << "' sub-option in '"
404 << space << "' space");
405 }
406 if (code_elem && (def->getCode() != code)) {
407 isc_throw(BadValue, "sub-option '" << name
408 << "' is defined as code: " << def->getCode()
409 << ", not the specified code: " << code);
410 }
411 code = def->getCode();
412 }
413
414 bool csv_format = false;
415 if (csv_format_elem) {
416 csv_format = csv_format_elem->boolValue();
417 }
418
419 if (!csv_format) {
420 // No definition means no csv format.
421 if (def) {
422 def.reset();
423 }
424 } else if (!def) {
425 // Definition is required with csv format.
426 def = isc::dhcp::LibDHCP::getOptionDef(space, code);
427 if (!def && vendor_id) {
428 def = LibDHCP::getVendorOptionDef(universe, vendor_id, code);
429 }
430 if (!def) {
432 }
433 if (!def) {
434 isc_throw(BadValue, "no known sub-option with code '" << code
435 << "' in '" << space << "' space");
436 }
437 }
438
439 SubOptionConfigPtr sub_cfg(new SubOptionConfig(code, def, opt_cfg));
440 if (vendor_id) {
441 if (((universe == Option::V4) &&
442 (opt_cfg->getCode() == DHO_VIVSO_SUBOPTIONS)) ||
443 ((universe == Option::V6) &&
444 (opt_cfg->getCode() == D6O_VENDOR_OPTS))) {
445 sub_cfg->setVendorId(vendor_id);
446 }
447 }
448 if (class_elem) {
449 sub_cfg->setClass(class_elem->stringValue());
450 }
451
452 // sub_cfg initial action is NONE.
453 parseAction(sub_option, sub_cfg, universe,
455 parseAction(sub_option, sub_cfg, universe,
457 parseAction(sub_option, sub_cfg, universe,
459
460 if (sub_cfg->getAction() == NONE) {
461 isc_throw(BadValue, "no action: " << sub_option->str());
462 }
463
464 ConstElementPtr container_add = sub_option->get("container-add");
465 ConstElementPtr container_remove = sub_option->get("container-remove");
466 if ((sub_cfg->getAction() == ADD) || (sub_cfg->getAction() == SUPERSEDE)) {
467 sub_cfg->setContainerAction(ADD);
468 if (container_add && !container_add->boolValue()) {
469 sub_cfg->setContainerAction(NONE);
470 }
471 } else if (sub_cfg->getAction() == REMOVE) {
472 sub_cfg->setContainerAction(REMOVE);
473 if (container_remove && !container_remove->boolValue()) {
474 sub_cfg->setContainerAction(NONE);
475 }
476 }
477
478 // The [] operator creates the item if it does not exist before
479 // returning a reference to it.
480 uint16_t opt_code = opt_cfg->getCode();
481 SubOptionConfigMap& sub_map = sub_option_config_map_[opt_code];
482 if (sub_map.count(code)) {
483 isc_throw(BadValue, "sub-option " << code << " of option " << opt_code
484 << " was already specified");
485 }
486 sub_map[code] = sub_cfg;
487}
488
489void
490FlexOptionImpl::logClass(const ClientClass& client_class, uint16_t code) {
493 .arg(client_class)
494 .arg(code);
495 return;
496}
497
498void
499FlexOptionImpl::logAction(Action action, uint16_t code,
500 const string& value) {
501 if (action == NONE) {
502 return;
503 }
504 if (action == REMOVE) {
507 .arg(code);
508 return;
509 }
510 ostringstream repr;
511 if (str::isPrintable(value)) {
512 repr << "'" << value << "'";
513 } else {
514 repr << "0x" << hex;
515 for (const char& ch : value) {
516 repr << setw(2) << setfill('0') << static_cast<unsigned>(ch);
517 }
518 }
519 if (action == SUPERSEDE) {
522 .arg(code)
523 .arg(repr.str());
524 } else {
527 .arg(code)
528 .arg(repr.str());
529 }
530}
531
532void
533FlexOptionImpl::logAction(Action action, uint16_t code,
534 uint32_t vendor_id) {
535 if (action == SUPERSEDE) {
538 .arg(code)
539 .arg(vendor_id);
540 } else {
543 .arg(code)
544 .arg(vendor_id);
545 }
546}
547
548void
549FlexOptionImpl::logSubClass(const ClientClass& client_class, uint16_t code,
550 uint16_t container_code) {
553 .arg(client_class)
554 .arg(code)
555 .arg(container_code);
556 return;
557}
558
559void
561 uint16_t container_code,
562 const string& value) {
563 if (action == NONE) {
564 return;
565 }
566 if (action == REMOVE) {
569 .arg(code)
570 .arg(container_code);
571 return;
572 }
573 ostringstream repr;
574 if (str::isPrintable(value)) {
575 repr << "'" << value << "'";
576 } else {
577 repr << "0x" << hex;
578 for (const char& ch : value) {
579 repr << setw(2) << setfill('0') << static_cast<unsigned>(ch);
580 }
581 }
582 if (action == SUPERSEDE) {
585 .arg(code)
586 .arg(container_code)
587 .arg(repr.str());
588 } else {
591 .arg(code)
592 .arg(container_code)
593 .arg(repr.str());
594 }
595}
596
597bool
598FlexOptionImpl::checkVendor(OptionPtr opt, uint32_t vendor_id) {
599 OptionVendorPtr vendor = boost::dynamic_pointer_cast<OptionVendor>(opt);
600 bool ret = (!vendor || (vendor->getVendorId() == vendor_id));
601 if (!ret) {
604 .arg(opt->getType())
605 .arg(vendor->getVendorId())
606 .arg(vendor_id);
607 }
608 return (ret);
609}
610
611} // end of namespace flex_option
612} // 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.
types
The types that an Element can hold.
Definition: data.h:139
static std::string typeToName(Element::types type)
Returns the name of the given type as a string.
Definition: data.cc:651
uint16_t getFamily() const
Returns address family.
Definition: cfgmgr.h:280
static CfgMgr & instance()
returns a single instance of Configuration Manager
Definition: cfgmgr.cc:25
static OptionDefinitionPtr getOptionDef(const std::string &space, const uint16_t code)
Return the first option definition matching a particular option code.
Definition: libdhcp++.cc:126
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:168
static uint32_t optionSpaceToVendorId(const std::string &option_space)
Converts option space name to vendor id.
Definition: libdhcp++.cc:1298
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:189
static OptionDefinitionPtr getLastResortOptionDef(const std::string &space, const uint16_t code)
Returns last resort option definition by space and option code.
Definition: libdhcp++.cc:247
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
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
@ PARSER_BOOL
expression is expected to evaluate to bool
Definition: eval_context.h:39
@ PARSER_STRING
expression is expected to evaluate to string
Definition: eval_context.h:40
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:499
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:549
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:560
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:598
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:490
@ 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:29
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:230
@ 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:37
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 string &content)
Check if a string is printable.
Definition: str.cc:310
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