Kea  2.1.7-git
cfg_option.cc
Go to the documentation of this file.
1 // Copyright (C) 2014-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 <dhcp/libdhcp++.h>
10 #include <dhcpsrv/cfg_option.h>
11 #include <dhcp/dhcp6.h>
12 #include <dhcp/option_space.h>
13 #include <util/encode/hex.h>
14 #include <boost/algorithm/string/split.hpp>
15 #include <boost/algorithm/string/classification.hpp>
16 #include <boost/make_shared.hpp>
17 #include <string>
18 #include <sstream>
19 #include <vector>
20 
21 using namespace isc::data;
22 
23 namespace isc {
24 namespace dhcp {
25 
27 OptionDescriptor::create(const OptionPtr& opt, bool persist,
28  const std::string& formatted_value,
29  ConstElementPtr user_context) {
30  return (boost::make_shared<OptionDescriptor>(opt, persist, formatted_value,
31  user_context));
32 }
33 
35 OptionDescriptor::create(bool persist) {
36  return (boost::make_shared<OptionDescriptor>(persist));
37 }
38 
40 OptionDescriptor::create(const OptionDescriptor& desc) {
41  return (boost::make_shared<OptionDescriptor>(desc));
42 }
43 
44 bool
45 OptionDescriptor::equals(const OptionDescriptor& other) const {
46  return ((persistent_ == other.persistent_) &&
47  (formatted_value_ == other.formatted_value_) &&
48  (space_name_ == other.space_name_) &&
49  option_->equals(other.option_));
50 }
51 
52 CfgOption::CfgOption() {
53 }
54 
55 bool
56 CfgOption::empty() const {
57  return (options_.empty() && vendor_options_.empty());
58 }
59 
60 bool
61 CfgOption::equals(const CfgOption& other) const {
62  return (options_.equals(other.options_) &&
63  vendor_options_.equals(other.vendor_options_));
64 }
65 
66 void
67 CfgOption::add(const OptionPtr& option, const bool persistent,
68  const std::string& option_space,
69  const uint64_t id) {
70  OptionDescriptor desc(option, persistent);
71  if (id > 0) {
72  desc.setId(id);
73  }
74  add(desc, option_space);
75 }
76 
77 void
78 CfgOption::add(const OptionDescriptor& desc, const std::string& option_space) {
79  if (!desc.option_) {
80  isc_throw(isc::BadValue, "option being configured must not be NULL");
81 
82  } else if (!OptionSpace::validateName(option_space)) {
83  isc_throw(isc::BadValue, "invalid option space name: '"
84  << option_space << "'");
85  }
86 
87  const uint32_t vendor_id = LibDHCP::optionSpaceToVendorId(option_space);
88  if (vendor_id) {
89  vendor_options_.addItem(desc, vendor_id);
90  } else {
91  options_.addItem(desc, option_space);
92  }
93 }
94 
95 void
96 CfgOption::replace(const OptionDescriptor& desc, const std::string& option_space) {
97  if (!desc.option_) {
98  isc_throw(isc::BadValue, "option being replaced must not be NULL");
99  }
100 
101  // Check for presence of options.
102  OptionContainerPtr options = getAll(option_space);
103  if (!options) {
104  isc_throw(isc::BadValue, "option space " << option_space
105  << " does not exist");
106  }
107 
108  // Find the option we want to replace.
109  OptionContainerTypeIndex& idx = options->get<1>();
110  auto const& od_itr = idx.find(desc.option_->getType());
111  if (od_itr == idx.end()) {
112  isc_throw(isc::BadValue, "cannot replace option: "
113  << option_space << ":" << desc.option_->getType()
114  << ", it does not exist");
115  }
116 
117  idx.replace(od_itr, desc);
118 }
119 
120 std::list<std::string>
121 CfgOption::getVendorIdsSpaceNames() const {
122  std::list<uint32_t> ids = getVendorIds();
123  std::list<std::string> names;
124  for (auto const& id : ids) {
125  std::ostringstream s;
126  // Vendor space name is constructed as "vendor-XYZ" where XYZ is an
127  // uint32_t value, without leading zeros.
128  s << "vendor-" << id;
129  names.push_back(s.str());
130  }
131  return (names);
132 }
133 
134 void
136  // First we merge our options into other.
137  // This adds my options that are not
138  // in other, to other (i.e we skip over
139  // duplicates).
140  mergeTo(other);
141 
142  // Create option instances based on the given definitions.
143  other.createOptions(cfg_def);
144 
145  // Next we copy "other" on top of ourself.
146  other.copyTo(*this);
147 }
148 
149 void
150 CfgOption::createOptions(CfgOptionDefPtr cfg_def) {
151  // Iterate over all the option descriptors in
152  // all the spaces and instantiate the options
153  // based on the given definitions.
154  for (auto space : getOptionSpaceNames()) {
155  for (auto opt_desc : *(getAll(space))) {
156  if (createDescriptorOption(cfg_def, space, opt_desc)) {
157  // Option was recreated, let's replace the descriptor.
158  replace(opt_desc,space);
159  }
160  }
161  }
162 }
163 
164 bool
165 CfgOption::createDescriptorOption(CfgOptionDefPtr cfg_def, const std::string& space,
166  OptionDescriptor& opt_desc) {
167  if (!opt_desc.option_) {
169  "validateCreateOption: descriptor has no option instance");
170  }
171 
172  Option::Universe universe = opt_desc.option_->getUniverse();
173  uint16_t code = opt_desc.option_->getType();
174 
175  // Find the option's defintion, if it has one.
176  // First, check for a standard definition.
177  OptionDefinitionPtr def = LibDHCP::getOptionDef(space, code);
178 
179  // If there is no standard definition but the option is vendor specific,
180  // we should search the definition within the vendor option space.
181  if (!def && (space != DHCP4_OPTION_SPACE) && (space != DHCP6_OPTION_SPACE)) {
182  uint32_t vendor_id = LibDHCP::optionSpaceToVendorId(space);
183  if (vendor_id > 0) {
184  def = LibDHCP::getVendorOptionDef(universe, vendor_id, code);
185  }
186  }
187 
188  // Still haven't found the definition, so look for custom
189  // definition in the given set of configured definitions
190  if (!def) {
191  def = cfg_def->get(space, code);
192  }
193 
194  std::string& formatted_value = opt_desc.formatted_value_;
195  if (!def) {
196  if (!formatted_value.empty()) {
197  isc_throw(InvalidOperation, "option: " << space << "." << code
198  << " has a formatted value: '" << formatted_value
199  << "' but no option definition");
200  }
201 
202  // If there's no definition and no formatted string, we'll
203  // settle for the generic option already in the descriptor.
204  // Indicate no-change by returning false.
205  return (false);
206  }
207 
208  try {
209  // Definition found. Let's replace the generic option in
210  // the descriptor with one created based on definition's factory.
211  if (formatted_value.empty()) {
212  // No formatted value, use data stored in the generic option.
213  opt_desc.option_ = def->optionFactory(universe, code, opt_desc.option_->getData());
214  } else {
215  // Spit the value specified in comma separated values format.
216  std::vector<std::string> split_vec;
217  boost::split(split_vec, formatted_value, boost::is_any_of(","));
218  opt_desc.option_ = def->optionFactory(universe, code, split_vec);
219  }
220  } catch (const std::exception& ex) {
221  isc_throw(InvalidOperation, "could not create option: " << space << "." << code
222  << " from data specified, reason: " << ex.what());
223  }
224 
225  // Indicate we replaced the definition.
226  return(true);
227 }
228 
229 void
230 CfgOption::mergeTo(CfgOption& other) const {
231  // Merge non-vendor options.
232  mergeInternal(options_, other.options_);
233  // Merge vendor options.
234  mergeInternal(vendor_options_, other.vendor_options_);
235 }
236 
237 void
238 CfgOption::copyTo(CfgOption& other) const {
239  // Remove any existing data in the destination.
240  other.options_.clearItems();
241  other.vendor_options_.clearItems();
242  mergeTo(other);
243 }
244 
245 void
246 CfgOption::encapsulate() {
247  // Append sub-options to the top level "dhcp4" option space.
248  encapsulateInternal(DHCP4_OPTION_SPACE);
249  // Append sub-options to the top level "dhcp6" option space.
250  encapsulateInternal(DHCP6_OPTION_SPACE);
251 }
252 
253 void
254 CfgOption::encapsulateInternal(const std::string& option_space) {
255  // Get all options for the particular option space.
256  OptionContainerPtr options = getAll(option_space);
257  // For each option in the option space we will append sub-options
258  // from the option spaces they encapsulate.
259  for (auto const& opt : *options) {
260  encapsulateInternal(opt.option_);
261  }
262 }
263 
264 void
265 CfgOption::encapsulateInternal(const OptionPtr& option) {
266  // Get encapsulated option space for the option.
267  const std::string& encap_space = option->getEncapsulatedSpace();
268  // Empty value means that no option space is encapsulated.
269  if (!encap_space.empty()) {
270  // Retrieve all options from the encapsulated option space.
271  OptionContainerPtr encap_options = getAll(encap_space);
272  for (auto const& encap_opt : *encap_options) {
273  // Add sub-option if there isn't one added already.
274  if (!option->getOption(encap_opt.option_->getType())) {
275  option->addOption(encap_opt.option_);
276  }
277  // This is a workaround for preventing infinite recursion when
278  // trying to encapsulate options created with default global option
279  // spaces.
280  if (encap_space != DHCP4_OPTION_SPACE &&
281  encap_space != DHCP6_OPTION_SPACE) {
282  encapsulateInternal(encap_opt.option_);
283  }
284  }
285  }
286 }
287 
288 template <typename Selector>
289 void
290 CfgOption::mergeInternal(const OptionSpaceContainer<OptionContainer,
291  OptionDescriptor, Selector>& src_container,
293  OptionDescriptor, Selector>& dest_container) const {
294  // Get all option spaces used in source container.
295  std::list<Selector> selectors = src_container.getOptionSpaceNames();
296 
297  // For each space in the source container retrieve the actual options and
298  // match them with the options held in the destination container under
299  // the same space.
300  for (auto const& it : selectors) {
301  // Get all options in the destination container for the particular
302  // option space.
303  OptionContainerPtr dest_all = dest_container.getItems(it);
304  OptionContainerPtr src_all = src_container.getItems(it);
305  // For each option under this option space check if there is a
306  // corresponding option in the destination container. If not,
307  // add one.
308  for (auto const& src_opt : *src_all) {
309  const OptionContainerTypeIndex& idx = dest_all->get<1>();
310  const OptionContainerTypeRange& range =
311  idx.equal_range(src_opt.option_->getType());
312  // If there is no such option in the destination container,
313  // add one.
314  if (std::distance(range.first, range.second) == 0) {
315  dest_container.addItem(OptionDescriptor(src_opt), it);
316  }
317  }
318  }
319 }
320 
322 CfgOption::getAll(const std::string& option_space) const {
323  return (options_.getItems(option_space));
324 }
325 
327 CfgOption::getAll(const uint32_t vendor_id) const {
328  return (vendor_options_.getItems(vendor_id));
329 }
330 
331 size_t
332 CfgOption::del(const std::string& option_space, const uint16_t option_code) {
333  // Check for presence of options.
334  OptionContainerPtr options = getAll(option_space);
335  if (!options || options->empty()) {
336  // There are no options, so there is nothing to do.
337  return (0);
338  }
339 
340  // If this is not top level option we may also need to delete the
341  // option instance from options encapsulating the particular option
342  // space.
343  if ((option_space != DHCP4_OPTION_SPACE) &&
344  (option_space != DHCP6_OPTION_SPACE)) {
345  // For each option space name iterate over the existing options.
346  auto option_space_names = getOptionSpaceNames();
347  for (auto option_space_from_list : option_space_names) {
348  // Get all options within the particular option space.
349  auto options_in_space = getAll(option_space_from_list);
350  for (auto option_it = options_in_space->begin();
351  option_it != options_in_space->end();
352  ++option_it) {
353 
354  // Check if the option encapsulates our option space and
355  // it does, try to delete our option.
356  if (option_it->option_ &&
357  (option_it->option_->getEncapsulatedSpace() == option_space)) {
358  option_it->option_->delOption(option_code);
359  }
360  }
361  }
362  }
363 
364  auto& idx = options->get<1>();
365  return (idx.erase(option_code));
366 }
367 
368 size_t
369 CfgOption::del(const uint32_t vendor_id, const uint16_t option_code) {
370  // Check for presence of options.
371  OptionContainerPtr vendor_options = getAll(vendor_id);
372  if (!vendor_options || vendor_options->empty()) {
373  // There are no options, so there is nothing to do.
374  return (0);
375  }
376 
377  auto& idx = vendor_options->get<1>();
378  return (idx.erase(option_code));
379 }
380 
381 size_t
382 CfgOption::del(const uint64_t id) {
383  // Hierarchical nature of the options configuration requires that
384  // we go over all options and decapsulate them before removing
385  // any of them. Let's walk over the existing option spaces.
386  for (auto space_name : getOptionSpaceNames()) {
387  // Get all options for the option space.
388  auto options = getAll(space_name);
389  for (auto option_it = options->begin(); option_it != options->end();
390  ++option_it) {
391  if (!option_it->option_) {
392  continue;
393  }
394 
395  // For each option within the option space we need to dereference
396  // any existing sub options.
397  auto sub_options = option_it->option_->getOptions();
398  for (auto sub = sub_options.begin(); sub != sub_options.end();
399  ++sub) {
400  // Dereference sub option.
401  option_it->option_->delOption(sub->second->getType());
402  }
403  }
404  }
405 
406  // Now that we got rid of dependencies between the instances of the options
407  // we can delete all options having a specified id.
408  size_t num_deleted = options_.deleteItems(id) + vendor_options_.deleteItems(id);
409 
410  // Let's encapsulate those options that remain in the configuration.
411  encapsulate();
412 
413  // Return the number of deleted options.
414  return (num_deleted);
415 }
416 
418 CfgOption::toElement() const {
419  return (toElementWithMetadata(false));
420 }
421 
423 CfgOption::toElementWithMetadata(const bool include_metadata) const {
424  // option-data value is a list of maps
425  ElementPtr result = Element::createList();
426  // Iterate first on options using space names
427  const std::list<std::string>& names = options_.getOptionSpaceNames();
428  for (auto const& name : names) {
429  OptionContainerPtr opts = getAll(name);
430  for (auto const& opt : *opts) {
431  // Get and fill the map for this option
433  // Set user context
434  opt.contextToElement(map);
435  // Set space from parent iterator
436  map->set("space", Element::create(name));
437  // Set the code
438  uint16_t code = opt.option_->getType();
439  map->set("code", Element::create(code));
440  // Set the name (always for standard options else when asked for)
441  OptionDefinitionPtr def = LibDHCP::getOptionDef(name, code);
442  if (!def) {
443  def = LibDHCP::getRuntimeOptionDef(name, code);
444  }
445  if (!def) {
446  def = LibDHCP::getLastResortOptionDef(name, code);
447  }
448  if (def) {
449  map->set("name", Element::create(def->getName()));
450  }
451  // Set the data item
452  if (!opt.formatted_value_.empty()) {
453  map->set("csv-format", Element::create(true));
454  map->set("data", Element::create(opt.formatted_value_));
455  } else {
456  map->set("csv-format", Element::create(false));
457  std::vector<uint8_t> bin = opt.option_->toBinary();
458  std::string repr = util::encode::encodeHex(bin);
459  map->set("data", Element::create(repr));
460  }
461  // Set the persistency flag
462  map->set("always-send", Element::create(opt.persistent_));
463 
464  // Include metadata if requested.
465  if (include_metadata) {
466  map->set("metadata", opt.getMetadata());
467  }
468 
469  // Push on the list
470  result->add(map);
471  }
472  }
473  // Iterate first on vendor_options using vendor ids
474  const std::list<uint32_t>& ids = vendor_options_.getOptionSpaceNames();
475  for (auto const& id : ids) {
476  OptionContainerPtr opts = getAll(id);
477  for (auto const& opt : *opts) {
478  // Get and fill the map for this option
480  // Set user context
481  opt.contextToElement(map);
482  // Set space from parent iterator
483  std::ostringstream oss;
484  oss << "vendor-" << id;
485  map->set("space", Element::create(oss.str()));
486  // Set the code
487  uint16_t code = opt.option_->getType();
488  map->set("code", Element::create(code));
489  // Set the name
490  Option::Universe universe = opt.option_->getUniverse();
491  OptionDefinitionPtr def =
492  LibDHCP::getVendorOptionDef(universe, id, code);
493  if (!def) {
494  // vendor-XXX space is in oss
495  def = LibDHCP::getRuntimeOptionDef(oss.str(), code);
496  }
497  if (def) {
498  map->set("name", Element::create(def->getName()));
499  }
500  // Set the data item
501  if (!opt.formatted_value_.empty()) {
502  map->set("csv-format", Element::create(true));
503  map->set("data", Element::create(opt.formatted_value_));
504  } else {
505  map->set("csv-format", Element::create(false));
506  std::vector<uint8_t> bin = opt.option_->toBinary();
507  std::string repr = util::encode::encodeHex(bin);
508  map->set("data", Element::create(repr));
509  }
510  // Set the persistency flag
511  map->set("always-send", Element::create(opt.persistent_));
512  // Push on the list
513  result->add(map);
514  }
515  }
516  return (result);
517 }
518 
519 } // namespace dhcp
520 } // namespace isc
Simple container for option spaces holding various items.
Option descriptor.
Definition: cfg_option.h:42
OptionContainer::nth_index< 1 >::type OptionContainerTypeIndex
Type of the index #1 - option type.
Definition: cfg_option.h:274
void copyTo(CfgOption &other) const
Copies this configuration to another configuration.
Definition: cfg_option.cc:238
boost::shared_ptr< OptionDescriptor > OptionDescriptorPtr
A pointer to option descriptor.
Definition: cfg_option.h:31
boost::shared_ptr< Option > OptionPtr
Definition: option.h:36
Universe
defines option universe DHCPv4 or DHCPv6
Definition: option.h:83
boost::shared_ptr< Element > ElementPtr
Definition: data.h:24
boost::shared_ptr< CfgOptionDef > CfgOptionDefPtr
Non-const pointer.
static ElementPtr createMap(const Position &pos=ZERO_POSITION())
Creates an empty MapElement type ElementPtr.
Definition: data.cc:286
static ElementPtr createList(const Position &pos=ZERO_POSITION())
Creates an empty ListElement type ElementPtr.
Definition: data.cc:281
virtual const char * what() const
Returns a C-style character string of the cause of the exception.
std::pair< OptionContainerTypeIndex::const_iterator, OptionContainerTypeIndex::const_iterator > OptionContainerTypeRange
Pair of iterators to represent the range of options having the same option type value.
Definition: cfg_option.h:279
#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...
std::string formatted_value_
Option value in textual (CSV) format.
Definition: cfg_option.h:66
Represents option data configuration for the DHCP server.
Definition: cfg_option.h:314
bool persistent_
Persistence flag.
Definition: cfg_option.h:51
boost::shared_ptr< const Element > ConstElementPtr
Definition: data.h:27
void createOptions(CfgOptionDefPtr cfg_def)
Re-create the option in each descriptor based on given definitions.
Definition: cfg_option.cc:150
std::string space_name_
Option space name.
Definition: cfg_option.h:77
OptionPtr option_
Option instance.
Definition: cfg_option.h:45
void clearItems()
Remove all items from the container.
Defines the logger used by the top-level component of kea-lfc.
string encodeHex(const vector< uint8_t > &binary)
Encode binary data in the base16 (&#39;hex&#39;) format.
Definition: base_n.cc:469
void merge(ElementPtr element, ConstElementPtr other)
Merges the data from other into element.
Definition: data.cc:1134
static ElementPtr create(const Position &pos=ZERO_POSITION())
Definition: data.cc:241
void setId(const uint64_t id)
Sets element&#39;s database identifier.
#define DHCP6_OPTION_SPACE
boost::multi_index_container< OptionDescriptor, boost::multi_index::indexed_by< boost::multi_index::sequenced<>, boost::multi_index::hashed_non_unique< KeyFromKeyExtractor< boost::multi_index::const_mem_fun< Option, uint16_t, &Option::getType >, boost::multi_index::member< OptionDescriptor, OptionPtr, &OptionDescriptor::option_ > > >, boost::multi_index::hashed_non_unique< boost::multi_index::member< OptionDescriptor, bool, &OptionDescriptor::persistent_ > >, boost::multi_index::ordered_non_unique< boost::multi_index::const_mem_fun< data::BaseStampedElement, boost::posix_time::ptime, &data::BaseStampedElement::getModificationTime > >, boost::multi_index::hashed_non_unique< boost::multi_index::tag< OptionIdIndexTag >, boost::multi_index::const_mem_fun< data::BaseStampedElement, uint64_t, &data::BaseStampedElement::getId > > >> OptionContainer
Multi index container for DHCP option descriptors.
Definition: cfg_option.h:269
A generic exception that is thrown if a function is called in a prohibited way.
#define DHCP4_OPTION_SPACE
global std option spaces
boost::shared_ptr< OptionContainer > OptionContainerPtr
Pointer to the OptionContainer object.
Definition: cfg_option.h:272
boost::shared_ptr< OptionDefinition > OptionDefinitionPtr
Pointer to option definition object.