Kea  1.9.9-git
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>
15 #include <dhcp/option_definition.h>
16 #include <dhcp/option_space.h>
17 #include <dhcpsrv/cfgmgr.h>
18 #include <eval/eval_context.h>
19 
20 using namespace isc;
21 using namespace isc::data;
22 using namespace isc::dhcp;
23 using namespace isc::eval;
24 using namespace isc::flex_option;
25 using namespace isc::log;
26 using namespace isc::util;
27 using namespace std;
28 
29 namespace {
30 
38 void
39 parseAction(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 
74 namespace isc {
75 namespace flex_option {
76 
77 FlexOptionImpl::OptionConfig::OptionConfig(uint16_t code,
79  : code_(code), def_(def), action_(NONE) {
80 }
81 
83 }
84 
86 }
87 
89  option_config_map_.clear();
90 }
91 
92 void
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 
108 void
109 FlexOptionImpl::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) {
215  def = isc::dhcp::LibDHCP::getRuntimeOptionDef(space, code);
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 
243 void
244 FlexOptionImpl::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
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
const isc::log::MessageID FLEX_OPTION_PROCESS_SUPERSEDE
const int DBGLVL_TRACE_BASIC
Trace basic operations.
Definition: log_dbglevels.h:65
isc::log::Logger flex_option_logger("flex-option-hooks")
const isc::log::MessageID FLEX_OPTION_PROCESS_ADD
Universe
defines option universe DHCPv4 or DHCPv6
Definition: option.h:82
STL namespace.
boost::shared_ptr< OptionConfig > OptionConfigPtr
The type of shared pointers to option config.
Definition: flex_option.h:139
#define isc_throw(type, stream)
A shortcut macro to insert known values into exception arguments.
A generic exception that is thrown if a parameter given to a method is considered invalid in that con...
Definition: edns.h:19
void configure(isc::data::ConstElementPtr options)
Configure the Flex Option implementation.
Definition: flex_option.cc:93
Evaluation context, an interface to the expression evaluation.
Definition: eval_context.h:34
boost::shared_ptr< const Element > ConstElementPtr
Definition: data.h:23
const isc::log::MessageID FLEX_OPTION_PROCESS_REMOVE
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
virtual const char * what() const
Returns a C-style character string of the cause of the exception.
Defines the logger used by the top-level component of kea-dhcp-ddns.
uint16_t code_
std::vector< TokenPtr > Expression
This is a structure that holds an expression converted to RPN.
Definition: token.h:28
#define DHCP6_OPTION_SPACE
#define DHCP4_OPTION_SPACE
global std option spaces
#define LOG_DEBUG(LOGGER, LEVEL, MESSAGE)
Macro to conveniently test debug output and log it.
Definition: macros.h:14
ParserType
Specifies what type of expression the parser is expected to see.
Definition: eval_context.h:38
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
A generic exception that is thrown if a parameter given to a method would refer to or modify out-of-r...
boost::shared_ptr< OptionDefinition > OptionDefinitionPtr
Pointer to option definition object.
void logAction(Action action, uint16_t code, const std::string &value) const
Log the action.
Definition: flex_option.cc:244
bool isPrintable(const std::string &content)
Check if a string is printable.
Definition: strutil.h:355
boost::shared_ptr< Expression > ExpressionPtr
Definition: token.h:30