Kea  1.9.9-git
cfg_option.cc
Go to the documentation of this file.
1 // Copyright (C) 2014-2019 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  OptionContainerTypeIndex::const_iterator 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 
121 std::list<std::string>
122 CfgOption::getVendorIdsSpaceNames() const {
123  std::list<uint32_t> ids = getVendorIds();
124  std::list<std::string> names;
125  for (std::list<uint32_t>::const_iterator id = ids.begin();
126  id != ids.end(); ++id) {
127  std::ostringstream s;
128  // Vendor space name is constructed as "vendor-XYZ" where XYZ is an
129  // uint32_t value, without leading zeros.
130  s << "vendor-" << *id;
131  names.push_back(s.str());
132  }
133  return (names);
134 }
135 
136 void
138  // First we merge our options into other.
139  // This adds my options that are not
140  // in other, to other (i.e we skip over
141  // duplicates).
142  mergeTo(other);
143 
144  // Create option instances based on the given definitions.
145  other.createOptions(cfg_def);
146 
147  // Next we copy "other" on top of ourself.
148  other.copyTo(*this);
149 }
150 
151 void
152 CfgOption::createOptions(CfgOptionDefPtr cfg_def) {
153  // Iterate over all the option descriptors in
154  // all the spaces and instantiate the options
155  // based on the given definitions.
156  for (auto space : getOptionSpaceNames()) {
157  for (auto opt_desc : *(getAll(space))) {
158  if (createDescriptorOption(cfg_def, space, opt_desc)) {
159  // Option was recreated, let's replace the descriptor.
160  replace(opt_desc,space);
161  }
162  }
163  }
164 }
165 
166 bool
167 CfgOption::createDescriptorOption(CfgOptionDefPtr cfg_def, const std::string& space,
168  OptionDescriptor& opt_desc) {
169  if (!opt_desc.option_) {
171  "validateCreateOption: descriptor has no option instance");
172  }
173 
174  Option::Universe universe = opt_desc.option_->getUniverse();
175  uint16_t code = opt_desc.option_->getType();
176 
177  // Find the option's defintion, if it has one.
178  // First, check for a standard definition.
179  OptionDefinitionPtr def = LibDHCP::getOptionDef(space, code);
180 
181  // If there is no standard definition but the option is vendor specific,
182  // we should search the definition within the vendor option space.
183  if (!def && (space != DHCP4_OPTION_SPACE) && (space != DHCP6_OPTION_SPACE)) {
184  uint32_t vendor_id = LibDHCP::optionSpaceToVendorId(space);
185  if (vendor_id > 0) {
186  def = LibDHCP::getVendorOptionDef(universe, vendor_id, code);
187  }
188  }
189 
190  // Still haven't found the definition, so look for custom
191  // definition in the given set of configured definitions
192  if (!def) {
193  def = cfg_def->get(space, code);
194  }
195 
196  std::string& formatted_value = opt_desc.formatted_value_;
197  if (!def) {
198  if (!formatted_value.empty()) {
199  isc_throw(InvalidOperation, "option: " << space << "." << code
200  << " has a formatted value: '" << formatted_value
201  << "' but no option definition");
202  }
203 
204  // If there's no definition and no formatted string, we'll
205  // settle for the generic option already in the descriptor.
206  // Indicate no-change by returning false.
207  return (false);
208  }
209 
210  try {
211  // Definition found. Let's replace the generic option in
212  // the descriptor with one created based on definition's factory.
213  if (formatted_value.empty()) {
214  // No formatted value, use data stored in the generic option.
215  opt_desc.option_ = def->optionFactory(universe, code, opt_desc.option_->getData());
216  } else {
217  // Spit the value specified in comma separated values format.
218  std::vector<std::string> split_vec;
219  boost::split(split_vec, formatted_value, boost::is_any_of(","));
220  opt_desc.option_ = def->optionFactory(universe, code, split_vec);
221  }
222  } catch (const std::exception& ex) {
223  isc_throw(InvalidOperation, "could not create option: " << space << "." << code
224  << " from data specified, reason: " << ex.what());
225  }
226 
227  // Indicate we replaced the definition.
228  return(true);
229 }
230 
231 void
232 CfgOption::mergeTo(CfgOption& other) const {
233  // Merge non-vendor options.
234  mergeInternal(options_, other.options_);
235  // Merge vendor options.
236  mergeInternal(vendor_options_, other.vendor_options_);
237 }
238 
239 void
240 CfgOption::copyTo(CfgOption& other) const {
241  // Remove any existing data in the destination.
242  other.options_.clearItems();
243  other.vendor_options_.clearItems();
244  mergeTo(other);
245 }
246 
247 void
248 CfgOption::encapsulate() {
249  // Append sub-options to the top level "dhcp4" option space.
250  encapsulateInternal(DHCP4_OPTION_SPACE);
251  // Append sub-options to the top level "dhcp6" option space.
252  encapsulateInternal(DHCP6_OPTION_SPACE);
253 }
254 
255 void
256 CfgOption::encapsulateInternal(const std::string& option_space) {
257  // Get all options for the particular option space.
258  OptionContainerPtr options = getAll(option_space);
259  // For each option in the option space we will append sub-options
260  // from the option spaces they encapsulate.
261  for (OptionContainer::const_iterator opt = options->begin();
262  opt != options->end(); ++opt) {
263  encapsulateInternal(opt->option_);
264  }
265 }
266 
267 void
268 CfgOption::encapsulateInternal(const OptionPtr& option) {
269  // Get encapsulated option space for the option.
270  const std::string& encap_space = option->getEncapsulatedSpace();
271  // Empty value means that no option space is encapsulated.
272  if (!encap_space.empty()) {
273  // Retrieve all options from the encapsulated option space.
274  OptionContainerPtr encap_options = getAll(encap_space);
275  for (OptionContainer::const_iterator encap_opt =
276  encap_options->begin(); encap_opt != encap_options->end();
277  ++encap_opt) {
278  // Add sub-option if there isn't one added already.
279  if (!option->getOption(encap_opt->option_->getType())) {
280  option->addOption(encap_opt->option_);
281  }
282  // This is a workaround for preventing infinite recursion when
283  // trying to encapsulate options created with default global option
284  // spaces.
285  if (encap_space != DHCP4_OPTION_SPACE &&
286  encap_space != DHCP6_OPTION_SPACE) {
287  encapsulateInternal(encap_opt->option_);
288  }
289  }
290  }
291 }
292 
293 template <typename Selector>
294 void
295 CfgOption::mergeInternal(const OptionSpaceContainer<OptionContainer,
296  OptionDescriptor, Selector>& src_container,
297  OptionSpaceContainer<OptionContainer,
298  OptionDescriptor, Selector>& dest_container) const {
299  // Get all option spaces used in source container.
300  std::list<Selector> selectors = src_container.getOptionSpaceNames();
301 
302  // For each space in the source container retrieve the actual options and
303  // match them with the options held in the destination container under
304  // the same space.
305  for (typename std::list<Selector>::const_iterator it = selectors.begin();
306  it != selectors.end(); ++it) {
307  // Get all options in the destination container for the particular
308  // option space.
309  OptionContainerPtr dest_all = dest_container.getItems(*it);
310  OptionContainerPtr src_all = src_container.getItems(*it);
311  // For each option under this option space check if there is a
312  // corresponding option in the destination container. If not,
313  // add one.
314  for (OptionContainer::const_iterator src_opt = src_all->begin();
315  src_opt != src_all->end(); ++src_opt) {
316  const OptionContainerTypeIndex& idx = dest_all->get<1>();
317  const OptionContainerTypeRange& range =
318  idx.equal_range(src_opt->option_->getType());
319  // If there is no such option in the destination container,
320  // add one.
321  if (std::distance(range.first, range.second) == 0) {
322  dest_container.addItem(OptionDescriptor(*src_opt), *it);
323  }
324  }
325  }
326 }
327 
328 
330 CfgOption::getAll(const std::string& option_space) const {
331  return (options_.getItems(option_space));
332 }
333 
335 CfgOption::getAll(const uint32_t vendor_id) const {
336  return (vendor_options_.getItems(vendor_id));
337 }
338 
339 size_t
340 CfgOption::del(const std::string& option_space, const uint16_t option_code) {
341  // Check for presence of options.
342  OptionContainerPtr options = getAll(option_space);
343  if (!options || options->empty()) {
344  // There are no options, so there is nothing to do.
345  return (0);
346  }
347 
348  // If this is not top level option we may also need to delete the
349  // option instance from options encapsulating the particular option
350  // space.
351  if ((option_space != DHCP4_OPTION_SPACE) &&
352  (option_space != DHCP6_OPTION_SPACE)) {
353  // For each option space name iterate over the existing options.
354  auto option_space_names = getOptionSpaceNames();
355  for (auto option_space_from_list : option_space_names) {
356  // Get all options within the particular option space.
357  auto options_in_space = getAll(option_space_from_list);
358  for (auto option_it = options_in_space->begin();
359  option_it != options_in_space->end();
360  ++option_it) {
361 
362  // Check if the option encapsulates our option space and
363  // it does, try to delete our option.
364  if (option_it->option_ &&
365  (option_it->option_->getEncapsulatedSpace() == option_space)) {
366  option_it->option_->delOption(option_code);
367  }
368  }
369  }
370  }
371 
372  auto& idx = options->get<1>();
373  return (idx.erase(option_code));
374 }
375 
376 size_t
377 CfgOption::del(const uint32_t vendor_id, const uint16_t option_code) {
378  // Check for presence of options.
379  OptionContainerPtr vendor_options = getAll(vendor_id);
380  if (!vendor_options || vendor_options->empty()) {
381  // There are no options, so there is nothing to do.
382  return (0);
383  }
384 
385  auto& idx = vendor_options->get<1>();
386  return (idx.erase(option_code));
387 }
388 
389 size_t
390 CfgOption::del(const uint64_t id) {
391  // Hierarchical nature of the options configuration requires that
392  // we go over all options and decapsulate them before removing
393  // any of them. Let's walk over the existing option spaces.
394  for (auto space_name : getOptionSpaceNames()) {
395  // Get all options for the option space.
396  auto options = getAll(space_name);
397  for (auto option_it = options->begin(); option_it != options->end();
398  ++option_it) {
399  if (!option_it->option_) {
400  continue;
401  }
402 
403  // For each option within the option space we need to dereference
404  // any existing sub options.
405  auto sub_options = option_it->option_->getOptions();
406  for (auto sub = sub_options.begin(); sub != sub_options.end();
407  ++sub) {
408  // Dereference sub option.
409  option_it->option_->delOption(sub->second->getType());
410  }
411  }
412  }
413 
414  // Now that we got rid of dependencies between the instances of the options
415  // we can delete all options having a specified id.
416  size_t num_deleted = options_.deleteItems(id) + vendor_options_.deleteItems(id);
417 
418  // Let's encapsulate those options that remain in the configuration.
419  encapsulate();
420 
421  // Return the number of deleted options.
422  return (num_deleted);
423 }
424 
426 CfgOption::toElement() const {
427  return (toElementWithMetadata(false));
428 }
429 
431 CfgOption::toElementWithMetadata(const bool include_metadata) const {
432  // option-data value is a list of maps
433  ElementPtr result = Element::createList();
434  // Iterate first on options using space names
435  const std::list<std::string>& names = options_.getOptionSpaceNames();
436  for (std::list<std::string>::const_iterator name = names.begin();
437  name != names.end(); ++name) {
438  OptionContainerPtr opts = getAll(*name);
439  for (OptionContainer::const_iterator opt = opts->begin();
440  opt != opts->end(); ++opt) {
441  // Get and fill the map for this option
443  // Set user context
444  opt->contextToElement(map);
445  // Set space from parent iterator
446  map->set("space", Element::create(*name));
447  // Set the code
448  uint16_t code = opt->option_->getType();
449  map->set("code", Element::create(code));
450  // Set the name (always for standard options else when asked for)
451  OptionDefinitionPtr def = LibDHCP::getOptionDef(*name, code);
452  if (!def) {
453  def = LibDHCP::getRuntimeOptionDef(*name, code);
454  }
455  if (!def) {
456  def = LibDHCP::getLastResortOptionDef(*name, code);
457  }
458  if (def) {
459  map->set("name", Element::create(def->getName()));
460  }
461  // Set the data item
462  if (!opt->formatted_value_.empty()) {
463  map->set("csv-format", Element::create(true));
464  map->set("data", Element::create(opt->formatted_value_));
465  } else {
466  map->set("csv-format", Element::create(false));
467  std::vector<uint8_t> bin = opt->option_->toBinary();
468  std::string repr = util::encode::encodeHex(bin);
469  map->set("data", Element::create(repr));
470  }
471  // Set the persistency flag
472  map->set("always-send", Element::create(opt->persistent_));
473 
474  // Include metadata if requested.
475  if (include_metadata) {
476  map->set("metadata", opt->getMetadata());
477  }
478 
479  // Push on the list
480  result->add(map);
481  }
482  }
483  // Iterate first on vendor_options using vendor ids
484  const std::list<uint32_t>& ids = vendor_options_.getOptionSpaceNames();
485  for (std::list<uint32_t>::const_iterator id = ids.begin();
486  id != ids.end(); ++id) {
487  OptionContainerPtr opts = getAll(*id);
488  for (OptionContainer::const_iterator opt = opts->begin();
489  opt != opts->end(); ++opt) {
490  // Get and fill the map for this option
492  // Set user context
493  opt->contextToElement(map);
494  // Set space from parent iterator
495  std::ostringstream oss;
496  oss << "vendor-" << *id;
497  map->set("space", Element::create(oss.str()));
498  // Set the code
499  uint16_t code = opt->option_->getType();
500  map->set("code", Element::create(code));
501  // Set the name
502  Option::Universe universe = opt->option_->getUniverse();
503  OptionDefinitionPtr def =
504  LibDHCP::getVendorOptionDef(universe, *id, code);
505  if (!def) {
506  // vendor-XXX space is in oss
507  def = LibDHCP::getRuntimeOptionDef(oss.str(), code);
508  }
509  if (def) {
510  map->set("name", Element::create(def->getName()));
511  }
512  // Set the data item
513  if (!opt->formatted_value_.empty()) {
514  map->set("csv-format", Element::create(true));
515  map->set("data", Element::create(opt->formatted_value_));
516  } else {
517  map->set("csv-format", Element::create(false));
518  std::vector<uint8_t> bin = opt->option_->toBinary();
519  std::string repr = util::encode::encodeHex(bin);
520  map->set("data", Element::create(repr));
521  }
522  // Set the persistency flag
523  map->set("always-send", Element::create(opt->persistent_));
524  // Push on the list
525  result->add(map);
526  }
527  }
528  return (result);
529 }
530 
531 
532 } // namespace dhcp
533 } // namespace isc
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
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:82
boost::shared_ptr< Element > ElementPtr
Definition: data.h:20
void copyTo(CfgOption &other) const
Copies this configuration to another configuration.
Definition: cfg_option.cc:240
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:267
static ElementPtr createList(const Position &pos=ZERO_POSITION())
Creates an empty ListElement type ElementPtr.
Definition: data.cc:262
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:23
void createOptions(CfgOptionDefPtr cfg_def)
Re-create the option in each descriptor based on given definitions.
Definition: cfg_option.cc:152
virtual const char * what() const
Returns a C-style character string of the cause of the exception.
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-dhcp-ddns.
string encodeHex(const vector< uint8_t > &binary)
Encode binary data in the base16 ('hex') format.
Definition: base_n.cc:469
void merge(ElementPtr element, ConstElementPtr other)
Merges the data from other into element.
Definition: data.cc:1079
static ElementPtr create(const Position &pos=ZERO_POSITION())
Definition: data.cc:222
void setId(const uint64_t id)
Sets element'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.