Kea 2.0.1
flex_option.cc
Go to the documentation of this file.
1// Copyright (C) 2019-2020 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 <dhcpsrv/cfgmgr.h>
18#include <eval/eval_context.h>
19
20using namespace isc;
21using namespace isc::data;
22using namespace isc::dhcp;
23using namespace isc::eval;
24using namespace isc::flex_option;
25using namespace isc::log;
26using namespace isc::util;
27using namespace std;
28
29namespace {
30
38void
39parseAction(ConstElementPtr option,
41 Option::Universe universe,
42 const string& name,
44 EvalContext::ParserType parser_type) {
45 ConstElementPtr elem = option->get(name);
46 if (elem) {
47 if (elem->getType() != Element::string) {
48 isc_throw(BadValue, "'" << name << "' must be a string: "
49 << elem->str());
50 }
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
79 : code_(code), def_(def), action_(NONE) {
80}
81
83}
84
86}
87
89 option_config_map_.clear();
90}
91
92void
94 if (!options) {
95 isc_throw(BadValue, "'options' parameter is mandatory");
96 }
97 if (options->getType() != Element::list) {
98 isc_throw(BadValue, "'options' parameter must be a list");
99 }
100 if (options->empty()) {
101 return;
102 }
103 for (auto option : options->listValue()) {
104 parseOptionConfig(option);
105 }
106}
107
108void
109FlexOptionImpl::parseOptionConfig(ConstElementPtr option) {
110 uint16_t family = CfgMgr::instance().getFamily();
111 if (!option) {
112 isc_throw(BadValue, "null option element");
113 }
114 if (option->getType() != Element::map) {
115 isc_throw(BadValue, "option element is not a map");
116 }
117 ConstElementPtr code_elem = option->get("code");
118 ConstElementPtr name_elem = option->get("name");
119 ConstElementPtr csv_format_elem = option->get("csv-format");
121 if (!code_elem && !name_elem) {
122 isc_throw(BadValue, "'code' or 'name' must be specified: "
123 << option->str());
124 }
125 string space;
126 Option::Universe universe;
127 if (family == AF_INET) {
128 space = DHCP4_OPTION_SPACE;
129 universe = Option::V4;
130 } else {
131 space = DHCP6_OPTION_SPACE;
132 universe = Option::V6;
133 }
134 uint16_t code;
135 if (code_elem) {
136 if (code_elem->getType() != Element::integer) {
137 isc_throw(BadValue, "'code' must be an integer: "
138 << code_elem->str());
139 }
140 int64_t value = code_elem->intValue();
141 int64_t max_code;
142 if (family == AF_INET) {
143 max_code = numeric_limits<uint8_t>::max();
144 } else {
145 max_code = numeric_limits<uint16_t>::max();
146 }
147 if ((value < 0) || (value > max_code)) {
148 isc_throw(OutOfRange, "invalid 'code' value " << value
149 << " not in [0.." << max_code << "]");
150 }
151 if (family == AF_INET) {
152 if (value == DHO_PAD) {
154 "invalid 'code' value 0: reserved for PAD");
155 } else if (value == DHO_END) {
157 "invalid 'code' value 255: reserved for END");
158 }
159 } else {
160 if (value == 0) {
161 isc_throw(BadValue, "invalid 'code' value 0: reserved");
162 }
163 }
164 code = static_cast<uint16_t>(value);
165 }
166 if (name_elem) {
167 if (name_elem->getType() != Element::string) {
168 isc_throw(BadValue, "'name' must be a string: "
169 << name_elem->str());
170 }
171 string name = name_elem->stringValue();
172 if (name.empty()) {
173 isc_throw(BadValue, "'name' must not be empty");
174 }
175 def = LibDHCP::getOptionDef(space, name);
176 if (!def) {
177 def = LibDHCP::getRuntimeOptionDef(space, name);
178 }
179 if (!def) {
180 def = LibDHCP::getLastResortOptionDef(space, name);
181 }
182 if (!def) {
183 isc_throw(BadValue, "no known '" << name << "' option in '"
184 << space << "' space");
185 }
186 if (code_elem && (def->getCode() != code)) {
187 isc_throw(BadValue, "option '" << name << "' is defined as code: "
188 << def->getCode() << ", not the specified code: "
189 << code);
190 }
191 code = def->getCode();
192 }
193 if (option_config_map_.count(code)) {
194 isc_throw(BadValue, "option " << code << " was already specified");
195 }
196
197 bool csv_format = false;
198 if (csv_format_elem) {
199 if (csv_format_elem->getType() != Element::boolean) {
200 isc_throw(BadValue, "'csv-format' must be a boolean: "
201 << csv_format_elem->str());
202 }
203 csv_format = csv_format_elem->boolValue();
204 }
205
206 if (!csv_format) {
207 // No definition means no csv format.
208 if (def) {
209 def.reset();
210 }
211 } else if (!def) {
212 // Definition is required with csv format.
213 def = isc::dhcp::LibDHCP::getOptionDef(space, code);
214 if (!def) {
216 }
217 if (!def) {
219 }
220 if (!def) {
221 isc_throw(BadValue, "no known option with code '" << code
222 << "' in '" << space << "' space");
223 }
224 }
225
226 OptionConfigPtr opt_cfg(new OptionConfig(code, def));
227
228 // opt_cfg initial action is NONE.
229 parseAction(option, opt_cfg, universe,
230 "add", ADD, EvalContext::PARSER_STRING);
231 parseAction(option, opt_cfg, universe,
232 "supersede", SUPERSEDE, EvalContext::PARSER_STRING);
233 parseAction(option, opt_cfg, universe,
234 "remove", REMOVE, EvalContext::PARSER_BOOL);
235
236 if (opt_cfg->getAction() == NONE) {
237 isc_throw(BadValue, "no action: " << option->str());
238 }
239
240 option_config_map_[code] = opt_cfg;
241}
242
243void
244FlexOptionImpl::logAction(Action action, uint16_t code,
245 const string& value) const {
246 if (action == NONE) {
247 return;
248 }
249 if (action == REMOVE) {
252 .arg(code);
253 return;
254 }
255 ostringstream repr;
256 if (str::isPrintable(value)) {
257 repr << "'" << value << "'";
258 } else {
259 repr << "0x" << hex;
260 for (const char& ch : value) {
261 repr << setw(2) << setfill('0') << static_cast<unsigned>(ch);
262 }
263 }
264 if (action == SUPERSEDE) {
267 .arg(code)
268 .arg(repr.str());
269 } else {
272 .arg(code)
273 .arg(repr.str());
274 }
275}
276
277} // end of namespace flex_option
278} // 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...
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:245
Universe
defines option universe DHCPv4 or DHCPv6
Definition: option.h:82
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:77
void configure(isc::data::ConstElementPtr options)
Configure the Flex Option implementation.
Definition: flex_option.cc:93
boost::shared_ptr< OptionConfig > OptionConfigPtr
The type of shared pointers to option config.
Definition: flex_option.h:139
void logAction(Action action, uint16_t code, const std::string &value) const
Log the action.
Definition: flex_option.cc:244
#define isc_throw(type, stream)
A shortcut macro to insert known values into exception arguments.
const isc::log::MessageID FLEX_OPTION_PROCESS_ADD
const isc::log::MessageID FLEX_OPTION_PROCESS_REMOVE
const isc::log::MessageID FLEX_OPTION_PROCESS_SUPERSEDE
#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
@ DHO_END
Definition: dhcp4.h:224
@ DHO_PAD
Definition: dhcp4.h:69
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
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:355
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