Kea 2.2.0
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
21using namespace isc::data;
22
23namespace isc {
24namespace dhcp {
25
27OptionDescriptor::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
36 return (boost::make_shared<OptionDescriptor>(persist));
37}
38
41 return (boost::make_shared<OptionDescriptor>(desc));
42}
43
44bool
46 return ((persistent_ == other.persistent_) &&
48 (space_name_ == other.space_name_) &&
49 option_->equals(other.option_));
50}
51
53}
54
55bool
57 return (options_.empty() && vendor_options_.empty());
58}
59
60bool
61CfgOption::equals(const CfgOption& other) const {
62 return (options_.equals(other.options_) &&
63 vendor_options_.equals(other.vendor_options_));
64}
65
66void
67CfgOption::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
77void
78CfgOption::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
95void
96CfgOption::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
120std::list<std::string>
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
134void
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
149void
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
164bool
165CfgOption::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.
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
229void
231 // Merge non-vendor options.
232 mergeInternal(options_, other.options_);
233 // Merge vendor options.
234 mergeInternal(vendor_options_, other.vendor_options_);
235}
236
237void
239 // Remove any existing data in the destination.
240 other.options_.clearItems();
241 other.vendor_options_.clearItems();
242 mergeTo(other);
243}
244
245void
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
253void
254CfgOption::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
264void
265CfgOption::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
288template <typename Selector>
289void
290CfgOption::mergeInternal(const OptionSpaceContainer<OptionContainer,
291 OptionDescriptor, Selector>& src_container,
292 OptionSpaceContainer<OptionContainer,
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
322CfgOption::getAll(const std::string& option_space) const {
323 return (options_.getItems(option_space));
324}
325
327CfgOption::getAll(const uint32_t vendor_id) const {
328 return (vendor_options_.getItems(vendor_id));
329}
330
331size_t
332CfgOption::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
368size_t
369CfgOption::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
381size_t
382CfgOption::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
419 return (toElementWithMetadata(false));
420}
421
423CfgOption::toElementWithMetadata(const bool include_metadata) const {
424 // option-data value is a list of maps
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)
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();
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
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 function is called in a prohibited way.
void setId(const uint64_t id)
Sets element's database identifier.
static ElementPtr create(const Position &pos=ZERO_POSITION())
Definition: data.cc:241
static ElementPtr createMap(const Position &pos=ZERO_POSITION())
Creates an empty MapElement type ElementPtr.
Definition: data.cc:291
static ElementPtr createList(const Position &pos=ZERO_POSITION())
Creates an empty ListElement type ElementPtr.
Definition: data.cc:286
Represents option data configuration for the DHCP server.
Definition: cfg_option.h:314
void encapsulate()
Appends encapsulated options to top-level options.
Definition: cfg_option.cc:246
void replace(const OptionDescriptor &desc, const std::string &option_space)
Replaces the instance of an option within this collection.
Definition: cfg_option.cc:96
static bool createDescriptorOption(CfgOptionDefPtr cfg_def, const std::string &space, OptionDescriptor &opt_desc)
Creates an option descriptor's option based on a set of option defs.
Definition: cfg_option.cc:165
virtual isc::data::ElementPtr toElement() const
Unparse a configuration object.
Definition: cfg_option.cc:418
void createOptions(CfgOptionDefPtr cfg_def)
Re-create the option in each descriptor based on given definitions.
Definition: cfg_option.cc:150
isc::data::ElementPtr toElementWithMetadata(const bool include_metadata) const
Unparse a configuration object with optionally including the metadata.
Definition: cfg_option.cc:423
size_t del(const std::string &option_space, const uint16_t option_code)
Deletes option for the specified option space and option code.
Definition: cfg_option.cc:332
std::list< std::string > getOptionSpaceNames() const
Returns a list of configured option space names.
Definition: cfg_option.h:613
bool empty() const
Indicates the object is empty.
Definition: cfg_option.cc:56
void mergeTo(CfgOption &other) const
Merges this configuration to another configuration.
Definition: cfg_option.cc:230
void copyTo(CfgOption &other) const
Copies this configuration to another configuration.
Definition: cfg_option.cc:238
CfgOption()
default constructor
Definition: cfg_option.cc:52
std::list< std::string > getVendorIdsSpaceNames() const
Returns a list of option space names for configured vendor ids.
Definition: cfg_option.cc:121
OptionContainerPtr getAll(const std::string &option_space) const
Returns all options for the specified option space.
Definition: cfg_option.cc:322
void merge(CfgOptionDefPtr cfg_def, CfgOption &other)
Merges another option configuration into this one.
Definition: cfg_option.cc:135
void add(const OptionPtr &option, const bool persistent, const std::string &option_space, const uint64_t id=0)
Adds instance of the option to the configuration.
Definition: cfg_option.cc:67
bool equals(const CfgOption &other) const
Check if configuration is equal to other configuration.
Definition: cfg_option.cc:61
std::list< uint32_t > getVendorIds() const
Returns a list of all configured vendor identifiers.
Definition: cfg_option.h:618
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 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:164
static uint32_t optionSpaceToVendorId(const std::string &option_space)
Converts option space name to vendor id.
Definition: libdhcp++.cc:1131
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:243
Option descriptor.
Definition: cfg_option.h:42
OptionPtr option_
Option instance.
Definition: cfg_option.h:45
std::string space_name_
Option space name.
Definition: cfg_option.h:77
bool equals(const OptionDescriptor &other) const
Checks if the one descriptor is equal to another.
Definition: cfg_option.cc:45
std::string formatted_value_
Option value in textual (CSV) format.
Definition: cfg_option.h:66
static OptionDescriptorPtr create(const OptionPtr &opt, bool persist, const std::string &formatted_value="", data::ConstElementPtr user_context=data::ConstElementPtr())
Factory function creating an instance of the OptionDescriptor.
Definition: cfg_option.cc:27
bool persistent_
Persistence flag.
Definition: cfg_option.h:51
uint64_t deleteItems(const uint64_t id)
Remove all options or option definitions with a given database identifier.
void addItem(const ItemType &item, const Selector &option_space)
Adds a new item to the option_space.
bool empty() const
Indicates the container is empty.
void clearItems()
Remove all items from the container.
std::list< Selector > getOptionSpaceNames() const
Get a list of existing option spaces.
ItemsContainerPtr getItems(const Selector &option_space) const
Get all items for the particular option space.
bool equals(const OptionSpaceContainer &other) const
Check if two containers are equal.
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
#define isc_throw(type, stream)
A shortcut macro to insert known values into exception arguments.
boost::shared_ptr< const Element > ConstElementPtr
Definition: data.h:27
boost::shared_ptr< Element > ElementPtr
Definition: data.h:24
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
OptionContainer::nth_index< 1 >::type OptionContainerTypeIndex
Type of the index #1 - option type.
Definition: cfg_option.h:274
boost::shared_ptr< CfgOptionDef > CfgOptionDefPtr
Non-const pointer.
boost::shared_ptr< OptionDefinition > OptionDefinitionPtr
Pointer to option definition object.
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
boost::shared_ptr< OptionDescriptor > OptionDescriptorPtr
A pointer to option descriptor.
Definition: cfg_option.h:31
boost::shared_ptr< OptionContainer > OptionContainerPtr
Pointer to the OptionContainer object.
Definition: cfg_option.h:272
boost::shared_ptr< Option > OptionPtr
Definition: option.h:36
string encodeHex(const vector< uint8_t > &binary)
Encode binary data in the base16 ('hex') format.
Definition: base_n.cc:469
Defines the logger used by the top-level component of kea-lfc.
#define DHCP4_OPTION_SPACE
global std option spaces
#define DHCP6_OPTION_SPACE