Kea  2.3.2-git
libdhcp++.cc
Go to the documentation of this file.
1 // Copyright (C) 2011-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/dhcp4.h>
10 #include <dhcp/dhcp6.h>
11 #include <dhcp/libdhcp++.h>
12 #include <dhcp/option.h>
13 #include <dhcp/option_vendor.h>
14 #include <dhcp/option6_ia.h>
15 #include <dhcp/option6_iaaddr.h>
16 #include <dhcp/option_definition.h>
17 #include <dhcp/option_int_array.h>
18 #include <dhcp/std_option_defs.h>
20 #include <exceptions/exceptions.h>
21 #include <exceptions/isc_assert.h>
22 #include <util/buffer.h>
23 
24 #include <boost/lexical_cast.hpp>
25 #include <boost/shared_array.hpp>
26 #include <boost/shared_ptr.hpp>
27 
28 #include <limits>
29 #include <list>
30 
31 using namespace std;
32 using namespace isc::dhcp;
33 using namespace isc::util;
34 
35 namespace isc {
36 namespace dhcp {
37 
38 namespace {
39 
43 const OptionDefParamsEncapsulation OPTION_DEF_PARAMS[] = {
44  { STANDARD_V4_OPTION_DEFINITIONS, STANDARD_V4_OPTION_DEFINITIONS_SIZE, DHCP4_OPTION_SPACE },
45  { STANDARD_V6_OPTION_DEFINITIONS, STANDARD_V6_OPTION_DEFINITIONS_SIZE, DHCP6_OPTION_SPACE },
48  { ISC_V6_OPTION_DEFINITIONS, ISC_V6_OPTION_DEFINITIONS_SIZE, ISC_V6_OPTION_SPACE },
49  { MAPE_V6_OPTION_DEFINITIONS, MAPE_V6_OPTION_DEFINITIONS_SIZE, MAPE_V6_OPTION_SPACE },
50  { MAPT_V6_OPTION_DEFINITIONS, MAPT_V6_OPTION_DEFINITIONS_SIZE, MAPT_V6_OPTION_SPACE },
51  { LW_V6_OPTION_DEFINITIONS, LW_V6_OPTION_DEFINITIONS_SIZE, LW_V6_OPTION_SPACE },
52  { V4V6_RULE_OPTION_DEFINITIONS, V4V6_RULE_OPTION_DEFINITIONS_SIZE, V4V6_RULE_OPTION_SPACE },
53  { V4V6_BIND_OPTION_DEFINITIONS, V4V6_BIND_OPTION_DEFINITIONS_SIZE, V4V6_BIND_OPTION_SPACE },
54  { LAST_RESORT_V4_OPTION_DEFINITIONS, LAST_RESORT_V4_OPTION_DEFINITIONS_SIZE, LAST_RESORT_V4_OPTION_SPACE },
55  { NULL, 0, "" }
56 };
57 
58 } // namespace
59 
60 } // namespace dhcp
61 } // namespace isc
62 
63 // static array with factories for options
64 std::map<unsigned short, Option::Factory*> LibDHCP::v4factories_;
65 
66 // static array with factories for options
67 std::map<unsigned short, Option::Factory*> LibDHCP::v6factories_;
68 
69 // Static container with option definitions grouped by option space.
70 OptionDefContainers LibDHCP::option_defs_;
71 
72 // Static container with option definitions created in runtime.
73 StagedValue<OptionDefSpaceContainer> LibDHCP::runtime_option_defs_;
74 
75 // Null container.
77 
78 // Those two vendor classes are used for cable modems:
79 
81 const char* isc::dhcp::DOCSIS3_CLASS_MODEM = "docsis3.0";
82 
84 const char* isc::dhcp::DOCSIS3_CLASS_EROUTER = "eRouter1.0";
85 
86 // Let's keep it in .cc file. Moving it to .h would require including optionDefParams
87 // definitions there
89  const OptionDefParams* params,
90  size_t params_size);
91 
92 bool LibDHCP::initialized_ = LibDHCP::initOptionDefs();
93 
95 LibDHCP::getOptionDefs(const std::string& space) {
96  auto const& container = option_defs_.find(space);
97  if (container != option_defs_.end()) {
98  return (container->second);
99  }
100 
102 }
103 
105 LibDHCP::getVendorOptionDefs(const Option::Universe u, const uint32_t vendor_id) {
106  if (Option::V4 == u) {
107  if (VENDOR_ID_CABLE_LABS == vendor_id) {
108  return getOptionDefs(DOCSIS3_V4_OPTION_SPACE);
109  }
110  } else if (Option::V6 == u) {
111  if (VENDOR_ID_CABLE_LABS == vendor_id) {
112  return getOptionDefs(DOCSIS3_V6_OPTION_SPACE);
113  } else if (ENTERPRISE_ID_ISC == vendor_id) {
114  return getOptionDefs(ISC_V6_OPTION_SPACE);
115  }
116  }
117 
119 }
120 
122 LibDHCP::getOptionDef(const std::string& space, const uint16_t code) {
123  const OptionDefContainerPtr& defs = getOptionDefs(space);
124  const OptionDefContainerTypeIndex& idx = defs->get<1>();
125  const OptionDefContainerTypeRange& range = idx.equal_range(code);
126  if (range.first != range.second) {
127  return (*range.first);
128  }
129 
130  return (OptionDefinitionPtr());
131 }
132 
134 LibDHCP::getOptionDef(const std::string& space, const std::string& name) {
135  const OptionDefContainerPtr& defs = getOptionDefs(space);
136  const OptionDefContainerNameIndex& idx = defs->get<2>();
137  const OptionDefContainerNameRange& range = idx.equal_range(name);
138  if (range.first != range.second) {
139  return (*range.first);
140  }
141 
142  return (OptionDefinitionPtr());
143 }
144 
146 LibDHCP::getVendorOptionDef(const Option::Universe u, const uint32_t vendor_id,
147  const std::string& name) {
148  const OptionDefContainerPtr& defs = getVendorOptionDefs(u, vendor_id);
149 
150  if (!defs) {
151  return (OptionDefinitionPtr());
152  }
153 
154  const OptionDefContainerNameIndex& idx = defs->get<2>();
155  const OptionDefContainerNameRange& range = idx.equal_range(name);
156  if (range.first != range.second) {
157  return (*range.first);
158  }
159 
160  return (OptionDefinitionPtr());
161 }
162 
164 LibDHCP::getVendorOptionDef(const Option::Universe u, const uint32_t vendor_id,
165  const uint16_t code) {
166  const OptionDefContainerPtr& defs = getVendorOptionDefs(u, vendor_id);
167 
168  if (!defs) {
169  // Weird universe or unknown vendor_id. We don't care. No definitions
170  // one way or another
171  // What is it anyway?
172  return (OptionDefinitionPtr());
173  }
174 
175  const OptionDefContainerTypeIndex& idx = defs->get<1>();
176  const OptionDefContainerTypeRange& range = idx.equal_range(code);
177  if (range.first != range.second) {
178  return (*range.first);
179  }
180 
181  return (OptionDefinitionPtr());
182 }
183 
185 LibDHCP::getRuntimeOptionDef(const std::string& space, const uint16_t code) {
186  OptionDefContainerPtr container = runtime_option_defs_.getValue().getItems(space);
187  const OptionDefContainerTypeIndex& index = container->get<1>();
188  const OptionDefContainerTypeRange& range = index.equal_range(code);
189  if (range.first != range.second) {
190  return (*range.first);
191  }
192 
193  return (OptionDefinitionPtr());
194 }
195 
197 LibDHCP::getRuntimeOptionDef(const std::string& space, const std::string& name) {
198  OptionDefContainerPtr container = runtime_option_defs_.getValue().getItems(space);
199  const OptionDefContainerNameIndex& index = container->get<2>();
200  const OptionDefContainerNameRange& range = index.equal_range(name);
201  if (range.first != range.second) {
202  return (*range.first);
203  }
204 
205  return (OptionDefinitionPtr());
206 }
207 
209 LibDHCP::getRuntimeOptionDefs(const std::string& space) {
210  return (runtime_option_defs_.getValue().getItems(space));
211 }
212 
213 void
214 LibDHCP::setRuntimeOptionDefs(const OptionDefSpaceContainer& defs) {
215  OptionDefSpaceContainer defs_copy;
216  std::list<std::string> option_space_names = defs.getOptionSpaceNames();
217  for (auto const& name : option_space_names) {
218  OptionDefContainerPtr container = defs.getItems(name);
219  for (auto const& def : *container) {
220  OptionDefinitionPtr def_copy(new OptionDefinition(*def));
221  defs_copy.addItem(def_copy);
222  }
223  }
224  runtime_option_defs_ = defs_copy;
225 }
226 
227 void
228 LibDHCP::clearRuntimeOptionDefs() {
229  runtime_option_defs_.reset();
230 }
231 
232 void
233 LibDHCP::revertRuntimeOptionDefs() {
234  runtime_option_defs_.revert();
235 }
236 
237 void
238 LibDHCP::commitRuntimeOptionDefs() {
239  runtime_option_defs_.commit();
240 }
241 
243 LibDHCP::getLastResortOptionDef(const std::string& space, const uint16_t code) {
244  OptionDefContainerPtr container = getLastResortOptionDefs(space);
245  const OptionDefContainerTypeIndex& index = container->get<1>();
246  const OptionDefContainerTypeRange& range = index.equal_range(code);
247  if (range.first != range.second) {
248  return (*range.first);
249  }
250 
251  return (OptionDefinitionPtr());
252 }
253 
255 LibDHCP::getLastResortOptionDef(const std::string& space, const std::string& name) {
256  OptionDefContainerPtr container = getLastResortOptionDefs(space);
257  const OptionDefContainerNameIndex& index = container->get<2>();
258  const OptionDefContainerNameRange& range = index.equal_range(name);
259  if (range.first != range.second) {
260  return (*range.first);
261  }
262 
263  return (OptionDefinitionPtr());
264 }
265 
267 LibDHCP::getLastResortOptionDefs(const std::string& space) {
268  if (space == DHCP4_OPTION_SPACE) {
269  return getOptionDefs(LAST_RESORT_V4_OPTION_SPACE);
270  }
271 
273 }
274 
275 bool
276 LibDHCP::shouldDeferOptionUnpack(const std::string& space, const uint16_t code) {
277  return ((space == DHCP4_OPTION_SPACE) &&
278  ((code == DHO_VENDOR_ENCAPSULATED_OPTIONS) ||
279  ((code >= 224) && (code <= 254))));
280 }
281 
282 OptionPtr
283 LibDHCP::optionFactory(Option::Universe u,
284  uint16_t type,
285  const OptionBuffer& buf) {
286  FactoryMap::iterator it;
287  if (u == Option::V4) {
288  it = v4factories_.find(type);
289  if (it == v4factories_.end()) {
290  isc_throw(BadValue, "factory function not registered "
291  "for DHCP v4 option type " << type);
292  }
293  } else if (u == Option::V6) {
294  it = v6factories_.find(type);
295  if (it == v6factories_.end()) {
296  isc_throw(BadValue, "factory function not registered "
297  "for DHCPv6 option type " << type);
298  }
299  } else {
300  isc_throw(BadValue, "invalid universe specified (expected "
301  "Option::V4 or Option::V6");
302  }
303  return (it->second(u, type, buf));
304 }
305 
306 
307 size_t
308 LibDHCP::unpackOptions6(const OptionBuffer& buf,
309  const std::string& option_space,
311  size_t* relay_msg_offset /* = 0 */,
312  size_t* relay_msg_len /* = 0 */) {
313  size_t offset = 0;
314  size_t length = buf.size();
315  size_t last_offset = 0;
316 
317  // Get the list of standard option definitions.
318  const OptionDefContainerPtr& option_defs = LibDHCP::getOptionDefs(option_space);
319  // Runtime option definitions for non standard option space and if
320  // the definition doesn't exist within the standard option definitions.
321  const OptionDefContainerPtr& runtime_option_defs = LibDHCP::getRuntimeOptionDefs(option_space);
322 
323  // @todo Once we implement other option spaces we should add else clause
324  // here and gather option definitions for them. For now leaving option_defs
325  // empty will imply creation of generic Option.
326 
327  // Get the search indexes #1. It allows to search for option definitions
328  // using option code.
329  const OptionDefContainerTypeIndex& idx = option_defs->get<1>();
330  const OptionDefContainerTypeIndex& runtime_idx = runtime_option_defs->get<1>();
331 
332  // The buffer being read comprises a set of options, each starting with
333  // a two-byte type code and a two-byte length field.
334  while (offset < length) {
335  // Save the current offset for backtracking
336  last_offset = offset;
337 
338  // Check if there is room for another option
339  if (offset + 4 > length) {
340  // Still something but smaller than an option
341  return (last_offset);
342  }
343 
344  // Parse the option header
345  uint16_t opt_type = isc::util::readUint16(&buf[offset], 2);
346  offset += 2;
347 
348  uint16_t opt_len = isc::util::readUint16(&buf[offset], 2);
349  offset += 2;
350 
351  if (offset + opt_len > length) {
352  // We peeked at the option header of the next option, but
353  // discovered that it would end up beyond buffer end, so
354  // the option is truncated. Hence we can't parse
355  // it. Therefore we revert back by those bytes (as if
356  // we never parsed them).
357  //
358  // @note it is the responsibility of the caller to throw
359  // an exception on partial parsing
360  return (last_offset);
361  }
362 
363  if (opt_type == D6O_RELAY_MSG && relay_msg_offset && relay_msg_len) {
364  // remember offset of the beginning of the relay-msg option
365  *relay_msg_offset = offset;
366  *relay_msg_len = opt_len;
367 
368  // do not create that relay-msg option
369  offset += opt_len;
370  continue;
371  }
372 
373  if (opt_type == D6O_VENDOR_OPTS) {
374  if (offset + 4 > length) {
375  // Truncated vendor-option. We expect at least
376  // 4 bytes for the enterprise-id field. Let's roll back
377  // option code + option length (4 bytes) and return.
378  return (last_offset);
379  }
380 
381  // Parse this as vendor option
382  OptionPtr vendor_opt(new OptionVendor(Option::V6, buf.begin() + offset,
383  buf.begin() + offset + opt_len));
384  options.insert(std::make_pair(opt_type, vendor_opt));
385 
386  offset += opt_len;
387  continue;
388  }
389 
390  // Get all definitions with the particular option code. Note
391  // that option code is non-unique within this container
392  // however at this point we expect to get one option
393  // definition with the particular code. If more are returned
394  // we report an error.
396  // Number of option definitions returned.
397  size_t num_defs = 0;
398 
399  // We previously did the lookup only for dhcp6 option space, but with the
400  // addition of S46 options, we now do it for every space.
401  range = idx.equal_range(opt_type);
402  num_defs = std::distance(range.first, range.second);
403 
404  // Standard option definitions do not include the definition for
405  // our option or we're searching for non-standard option. Try to
406  // find the definition among runtime option definitions.
407  if (num_defs == 0) {
408  range = runtime_idx.equal_range(opt_type);
409  num_defs = std::distance(range.first, range.second);
410  }
411 
412  OptionPtr opt;
413  if (num_defs > 1) {
414  // Multiple options of the same code are not supported right now!
415  isc_throw(isc::Unexpected, "Internal error: multiple option"
416  " definitions for option type " << opt_type <<
417  " returned. Currently it is not supported to initialize"
418  " multiple option definitions for the same option code."
419  " This will be supported once support for option spaces"
420  " is implemented");
421  } else if (num_defs == 0) {
422  // @todo Don't crash if definition does not exist because
423  // only a few option definitions are initialized right
424  // now. In the future we will initialize definitions for
425  // all options and we will remove this elseif. For now,
426  // return generic option.
427  opt = OptionPtr(new Option(Option::V6, opt_type,
428  buf.begin() + offset,
429  buf.begin() + offset + opt_len));
430  } else {
431  try {
432  // The option definition has been found. Use it to create
433  // the option instance from the provided buffer chunk.
434  const OptionDefinitionPtr& def = *(range.first);
435  isc_throw_assert(def);
436  opt = def->optionFactory(Option::V6, opt_type,
437  buf.begin() + offset,
438  buf.begin() + offset + opt_len);
439  } catch (const SkipThisOptionError&) {
440  opt.reset();
441  }
442  }
443 
444  // add option to options
445  if (opt) {
446  options.insert(std::make_pair(opt_type, opt));
447  }
448 
449  offset += opt_len;
450  }
451 
452  last_offset = offset;
453  return (last_offset);
454 }
455 
456 size_t
457 LibDHCP::unpackOptions4(const OptionBuffer& buf,
458  const std::string& option_space,
460  std::list<uint16_t>& deferred,
461  bool check) {
462  size_t offset = 0;
463  size_t last_offset = 0;
464 
465  // Special case when option_space is dhcp4.
466  bool space_is_dhcp4 = (option_space == DHCP4_OPTION_SPACE);
467 
468  // Get the list of standard option definitions.
469  const OptionDefContainerPtr& option_defs = LibDHCP::getOptionDefs(option_space);
470  // Runtime option definitions for non standard option space and if
471  // the definition doesn't exist within the standard option definitions.
472  const OptionDefContainerPtr& runtime_option_defs = LibDHCP::getRuntimeOptionDefs(option_space);
473 
474  // Get the search indexes #1. It allows to search for option definitions
475  // using option code.
476  const OptionDefContainerTypeIndex& idx = option_defs->get<1>();
477  const OptionDefContainerTypeIndex& runtime_idx = runtime_option_defs->get<1>();
478 
479  // Flexible PAD and END parsing.
480  bool flex_pad = (check && (runtime_idx.count(DHO_PAD) == 0));
481  bool flex_end = (check && (runtime_idx.count(DHO_END) == 0));
482 
483  // The buffer being read comprises a set of options, each starting with
484  // a one-byte type code and a one-byte length field.
485  while (offset < buf.size()) {
486  // Save the current offset for backtracking
487  last_offset = offset;
488 
489  // Get the option type
490  uint8_t opt_type = buf[offset++];
491 
492  // DHO_END is a special, one octet long option
493  // Valid in dhcp4 space or when check is true and
494  // there is a sub-option configured for this code.
495  if ((opt_type == DHO_END) && (space_is_dhcp4 || flex_end)) {
496  // just return. Don't need to add DHO_END option
497  // Don't return offset because it makes this condition
498  // and partial parsing impossible to recognize.
499  return (last_offset);
500  }
501 
502  // DHO_PAD is just a padding after DHO_END. Let's continue parsing
503  // in case we receive a message without DHO_END.
504  // Valid in dhcp4 space or when check is true and
505  // there is a sub-option configured for this code.
506  if ((opt_type == DHO_PAD) && (space_is_dhcp4 || flex_pad)) {
507  continue;
508  }
509 
510  if (offset + 1 > buf.size()) {
511  // We peeked at the option header of the next option, but
512  // discovered that it would end up beyond buffer end, so
513  // the option is truncated. Hence we can't parse
514  // it. Therefore we revert back (as if we never parsed it).
515  //
516  // @note it is the responsibility of the caller to throw
517  // an exception on partial parsing
518  return (last_offset);
519  }
520 
521  uint8_t opt_len = buf[offset++];
522  if (offset + opt_len > buf.size()) {
523  // We peeked at the option header of the next option, but
524  // discovered that it would end up beyond buffer end, so
525  // the option is truncated. Hence we can't parse
526  // it. Therefore we revert back (as if we never parsed it).
527  return (last_offset);
528  }
529 
530  // While an empty Host Name option is non-RFC compliant, some clients
531  // do send it. In the spirit of being liberal, we'll just drop it,
532  // rather than the dropping the whole packet. We do not have a
533  // way to log this from here but meh... a PCAP will show it arriving,
534  // and we know we drop it.
535  if (space_is_dhcp4 && opt_len == 0 && opt_type == DHO_HOST_NAME) {
536  continue;
537  }
538 
539  // Get all definitions with the particular option code. Note
540  // that option code is non-unique within this container
541  // however at this point we expect to get one option
542  // definition with the particular code. If more are returned
543  // we report an error.
545  // Number of option definitions returned.
546  size_t num_defs = 0;
547 
548  // Previously we did the lookup only for "dhcp4" option space, but there
549  // may be standard options in other spaces (e.g. radius). So we now do
550  // the lookup for every space.
551  range = idx.equal_range(opt_type);
552  num_defs = std::distance(range.first, range.second);
553 
554  // Standard option definitions do not include the definition for
555  // our option or we're searching for non-standard option. Try to
556  // find the definition among runtime option definitions.
557  if (num_defs == 0) {
558  range = runtime_idx.equal_range(opt_type);
559  num_defs = std::distance(range.first, range.second);
560  }
561 
562  // Check if option unpacking must be deferred
563  if (shouldDeferOptionUnpack(option_space, opt_type)) {
564  num_defs = 0;
565  // Store deferred option only once.
566  bool found = false;
567  for (auto const& existing : deferred) {
568  if (existing == opt_type) {
569  found = true;
570  break;
571  }
572  }
573  if (!found) {
574  deferred.push_back(opt_type);
575  }
576  }
577 
578  OptionPtr opt;
579  if (num_defs > 1) {
580  // Multiple options of the same code are not supported right now!
581  isc_throw(isc::Unexpected, "Internal error: multiple option"
582  " definitions for option type " <<
583  static_cast<int>(opt_type) <<
584  " returned. Currently it is not supported to initialize"
585  " multiple option definitions for the same option code."
586  " This will be supported once support for option spaces"
587  " is implemented");
588  } else if (num_defs == 0) {
589  opt = OptionPtr(new Option(Option::V4, opt_type,
590  buf.begin() + offset,
591  buf.begin() + offset + opt_len));
592  opt->setEncapsulatedSpace(DHCP4_OPTION_SPACE);
593  } else {
594  try {
595  // The option definition has been found. Use it to create
596  // the option instance from the provided buffer chunk.
597  const OptionDefinitionPtr& def = *(range.first);
598  isc_throw_assert(def);
599  opt = def->optionFactory(Option::V4, opt_type,
600  buf.begin() + offset,
601  buf.begin() + offset + opt_len);
602  } catch (const SkipThisOptionError&) {
603  opt.reset();
604  }
605  }
606 
607  // If we have the option, insert it
608  if (opt) {
609  options.insert(std::make_pair(opt_type, opt));
610  }
611 
612  offset += opt_len;
613  }
614  last_offset = offset;
615  return (last_offset);
616 }
617 
618 bool
619 LibDHCP::fuseOptions4(OptionCollection& options) {
620  bool result = false;
621  // We need to loop until all options have been fused.
622  for (;;) {
623  uint32_t found = 0;
624  bool found_suboptions = false;
625  // Iterate over all options in the container.
626  for (auto const& option : options) {
627  OptionPtr candidate = option.second;
628  OptionCollection& sub_options = candidate->getMutableOptions();
629  // Fuse suboptions recursively, if any.
630  if (sub_options.size()) {
631  // Fusing suboptions might result in new options with multiple
632  // options having the same code, so we need to iterate again
633  // until no option needs fusing.
634  found_suboptions = LibDHCP::fuseOptions4(sub_options);
635  if (found_suboptions) {
636  result = true;
637  }
638  }
639  OptionBuffer data;
640  OptionCollection suboptions;
641  // Make a copy of the options so we can safely iterate over the
642  // old container.
643  OptionCollection copy = options;
644  for (auto const& old_option : copy) {
645  if (old_option.first == option.first) {
646  // Copy the option data to the buffer.
647  data.insert(data.end(), old_option.second->getData().begin(),
648  old_option.second->getData().end());
649  suboptions.insert(old_option.second->getOptions().begin(),
650  old_option.second->getOptions().end());
651  // Other options might need fusing, so we need to iterate
652  // again until no options needs fusing.
653  found++;
654  }
655  }
656  if (found > 1) {
657  result = true;
658  // Erase the old options from the new container so that only the
659  // new option is present.
660  copy.erase(option.first);
661  // Create new option with entire data.
662  OptionPtr new_option(new Option(candidate->getUniverse(),
663  candidate->getType(), data));
664  // Recreate suboptions container.
665  new_option->getMutableOptions() = suboptions;
666  // Add the new option to the new container.
667  copy.insert(make_pair(candidate->getType(), new_option));
668  // After all options have been fused and new option added,
669  // update the option container with the new container.
670  options = copy;
671  break;
672  } else {
673  found = 0;
674  }
675  }
676  // No option needs fusing, so we can exit the loop.
677  if ((found <= 1) && !found_suboptions) {
678  break;
679  }
680  }
681  return (result);
682 }
683 
684 size_t
685 LibDHCP::unpackVendorOptions6(const uint32_t vendor_id,
686  const OptionBuffer& buf,
687  isc::dhcp::OptionCollection& options) {
688  size_t offset = 0;
689  size_t length = buf.size();
690 
691  // Get the list of option definitions for this particular vendor-id
692  const OptionDefContainerPtr& option_defs =
693  LibDHCP::getVendorOptionDefs(Option::V6, vendor_id);
694 
695  // Get the search index #1. It allows to search for option definitions
696  // using option code. If there's no such vendor-id space, we're out of luck
697  // anyway.
698  const OptionDefContainerTypeIndex* idx = NULL;
699  if (option_defs) {
700  idx = &(option_defs->get<1>());
701  }
702 
703  // The buffer being read comprises a set of options, each starting with
704  // a two-byte type code and a two-byte length field.
705  while (offset < length) {
706  if (offset + 4 > length) {
708  "Vendor option parse failed: truncated header");
709  }
710 
711  uint16_t opt_type = isc::util::readUint16(&buf[offset], 2);
712  offset += 2;
713 
714  uint16_t opt_len = isc::util::readUint16(&buf[offset], 2);
715  offset += 2;
716 
717  if (offset + opt_len > length) {
719  "Vendor option parse failed. Tried to parse "
720  << offset + opt_len << " bytes from " << length
721  << "-byte long buffer.");
722  }
723 
724  OptionPtr opt;
725  opt.reset();
726 
727  // If there is a definition for such a vendor option...
728  if (idx) {
729  // Get all definitions with the particular option
730  // code. Note that option code is non-unique within this
731  // container however at this point we expect to get one
732  // option definition with the particular code. If more are
733  // returned we report an error.
734  const OptionDefContainerTypeRange& range =
735  idx->equal_range(opt_type);
736  // Get the number of returned option definitions for the
737  // option code.
738  size_t num_defs = std::distance(range.first, range.second);
739 
740  if (num_defs > 1) {
741  // Multiple options of the same code are not supported
742  // right now!
743  isc_throw(isc::Unexpected, "Internal error: multiple option"
744  " definitions for option type " << opt_type <<
745  " returned. Currently it is not supported to"
746  " initialize multiple option definitions for the"
747  " same option code. This will be supported once"
748  " support for option spaces is implemented");
749  } else if (num_defs == 1) {
750  // The option definition has been found. Use it to create
751  // the option instance from the provided buffer chunk.
752  const OptionDefinitionPtr& def = *(range.first);
753  isc_throw_assert(def);
754  opt = def->optionFactory(Option::V6, opt_type,
755  buf.begin() + offset,
756  buf.begin() + offset + opt_len);
757  }
758  }
759 
760  // This can happen in one of 2 cases:
761  // 1. we do not have definitions for that vendor-space
762  // 2. we do have definitions, but that particular option was
763  // not defined
764 
765  if (!opt) {
766  opt = OptionPtr(new Option(Option::V6, opt_type,
767  buf.begin() + offset,
768  buf.begin() + offset + opt_len));
769  }
770 
771  // add option to options
772  if (opt) {
773  options.insert(std::make_pair(opt_type, opt));
774  }
775  offset += opt_len;
776  }
777 
778  return (offset);
779 }
780 
781 size_t
782 LibDHCP::unpackVendorOptions4(const uint32_t vendor_id, const OptionBuffer& buf,
783  isc::dhcp::OptionCollection& options) {
784  size_t offset = 0;
785 
786  // Get the list of standard option definitions.
787  const OptionDefContainerPtr& option_defs =
788  LibDHCP::getVendorOptionDefs(Option::V4, vendor_id);
789  // Get the search index #1. It allows to search for option definitions
790  // using option code.
791  const OptionDefContainerTypeIndex* idx = NULL;
792  if (option_defs) {
793  idx = &(option_defs->get<1>());
794  }
795 
796  // The buffer being read comprises a set of options, each starting with
797  // a one-byte type code and a one-byte length field.
798  while (offset < buf.size()) {
799  // Note that Vendor-Specific info option (RFC3925) has a
800  // different option format than Vendor-Spec info for
801  // DHCPv6. (there's additional layer of data-length)
802  uint8_t data_len = buf[offset++];
803 
804  if (offset + data_len > buf.size()) {
805  // The option is truncated.
807  "Attempt to parse truncated vendor option");
808  }
809 
810  uint8_t offset_end = offset + data_len;
811 
812  // beginning of data-chunk parser
813  while (offset < offset_end) {
814  uint8_t opt_type = buf[offset++];
815 
816  // No DHO_END or DHO_PAD in vendor options
817 
818  if (offset + 1 > offset_end) {
819  // opt_type must be cast to integer so as it is not
820  // treated as unsigned char value (a number is
821  // presented in error message).
823  "Attempt to parse truncated vendor option "
824  << static_cast<int>(opt_type));
825  }
826 
827  uint8_t opt_len = buf[offset++];
828  if (offset + opt_len > offset_end) {
830  "Option parse failed. Tried to parse "
831  << offset + opt_len << " bytes from " << buf.size()
832  << "-byte long buffer.");
833  }
834 
835  OptionPtr opt;
836  opt.reset();
837 
838  if (idx) {
839  // Get all definitions with the particular option
840  // code. Note that option code is non-unique within
841  // this container however at this point we expect to
842  // get one option definition with the particular
843  // code. If more are returned we report an error.
844  const OptionDefContainerTypeRange& range =
845  idx->equal_range(opt_type);
846  // Get the number of returned option definitions for
847  // the option code.
848  size_t num_defs = std::distance(range.first, range.second);
849 
850  if (num_defs > 1) {
851  // Multiple options of the same code are not
852  // supported right now!
853  isc_throw(isc::Unexpected, "Internal error: multiple"
854  " option definitions for option type "
855  << opt_type << " returned. Currently it is"
856  " not supported to initialize multiple option"
857  " definitions for the same option code."
858  " This will be supported once support for"
859  " option spaces is implemented");
860  } else if (num_defs == 1) {
861  // The option definition has been found. Use it to create
862  // the option instance from the provided buffer chunk.
863  const OptionDefinitionPtr& def = *(range.first);
864  isc_throw_assert(def);
865  opt = def->optionFactory(Option::V4, opt_type,
866  buf.begin() + offset,
867  buf.begin() + offset + opt_len);
868  }
869  }
870 
871  if (!opt) {
872  opt = OptionPtr(new Option(Option::V4, opt_type,
873  buf.begin() + offset,
874  buf.begin() + offset + opt_len));
875  }
876 
877  options.insert(std::make_pair(opt_type, opt));
878  offset += opt_len;
879 
880  } // end of data-chunk
881 
882  break; // end of the vendor block.
883  }
884  return (offset);
885 }
886 
887 void
888 LibDHCP::packOptions4(isc::util::OutputBuffer& buf,
889  const OptionCollection& options,
890  bool top, bool check) {
891  OptionCollection agent;
892  OptionPtr end;
893 
894  // We only look for type when we're the top level
895  // call that starts packing for options for a packet.
896  // This way we avoid doing type logic in all ensuing
897  // recursive calls.
898  if (top) {
899  auto x = options.find(DHO_DHCP_MESSAGE_TYPE);
900  if (x != options.end()) {
901  x->second->pack(buf, check);
902  }
903  }
904 
905  for (auto const& option : options) {
906  // TYPE is already done, RAI and END options must be last.
907  switch (option.first) {
909  break;
911  agent.insert(make_pair(DHO_DHCP_AGENT_OPTIONS, option.second));
912  break;
913  case DHO_END:
914  end = option.second;
915  break;
916  default:
917  option.second->pack(buf, check);
918  break;
919  }
920  }
921 
922  // Add the RAI option if it exists.
923  for (auto const& option : agent) {
924  option.second->pack(buf, check);
925  }
926 
927  // And at the end the END option.
928  if (end) {
929  end->pack(buf, check);
930  }
931 }
932 
933 bool
934 LibDHCP::splitOptions4(OptionCollection& options,
935  ScopedOptionsCopyContainer& scoped_options,
936  uint32_t used) {
937  bool result = false;
938  // We need to loop until all options have been split.
939  for (;;) {
940  bool found = false;
941  // Make a copy of the options so we can safely iterate over the
942  // old container.
943  OptionCollection copy = options;
944  // Iterate over all options in the container.
945  for (auto const& option : options) {
946  OptionPtr candidate = option.second;
947  OptionCollection& sub_options = candidate->getMutableOptions();
948  // Split suboptions recursively, if any.
949  OptionCollection distinct_options;
950  bool updated = false;
951  bool found_suboptions = false;
952  if (sub_options.size()) {
953  ScopedOptionsCopyPtr candidate_scoped_options(new ScopedSubOptionsCopy(candidate));
954  found_suboptions = LibDHCP::splitOptions4(sub_options, scoped_options,
955  used + candidate->getHeaderLen());
956  // Also split if the overflow is caused by adding the suboptions
957  // to the option data.
958  if (found_suboptions || candidate->len() > 255) {
959  updated = true;
960  scoped_options.push_back(candidate_scoped_options);
961  // Erase the old options from the new container so that only
962  // the new options are present.
963  copy.erase(option.first);
964  result = true;
965  // If there are suboptions which have been split, one parent
966  // option will be created for each of the chunk of the
967  // suboptions. If the suboptions have not been split,
968  // but they cause overflow when added to the option data,
969  // one parent option will contain the option data and one
970  // parent option will be created for each suboption.
971  // This will guarantee that none of the options plus
972  // suboptions will have more than 255 bytes.
973  for (auto sub_option : candidate->getMutableOptions()) {
974  OptionPtr data_sub_option(new Option(candidate->getUniverse(),
975  candidate->getType(),
976  OptionBuffer(0)));
977  data_sub_option->addOption(sub_option.second);
978  distinct_options.insert(make_pair(candidate->getType(), data_sub_option));
979  }
980  }
981  }
982  // Create a new option containing only data that needs to be split
983  // and no suboptions (which are inserted in completely separate
984  // options which are added at the end).
985  OptionPtr data_option(new Option(candidate->getUniverse(),
986  candidate->getType(),
987  OptionBuffer(candidate->getData().begin(),
988  candidate->getData().end())));
989  OutputBuffer buf(0);
990  data_option->pack(buf, false);
991  uint32_t header_len = candidate->getHeaderLen();
992  // At least 1 + header length bytes must be available.
993  if (used >= 255 - header_len) {
994  isc_throw(BadValue, "there is no space left to split option "
995  << candidate->getType() << " after parent already used "
996  << used);
997  }
998  // Maximum option buffer size is 255 - header size - buffer size
999  // already used by parent options.
1000  uint8_t len = 255 - header_len - used;
1001  // Current option size after split is the sum of the data and the
1002  // header size. The suboptions are serialized in separate options.
1003  // The header is duplicated in all new options, but the rest of the
1004  // data must be split and serialized.
1005  uint32_t size = buf.getLength() - header_len;
1006  // Only split if data does not fit in the current option.
1007  if (size > len) {
1008  // Erase the old option from the new container so that only new
1009  // options are present.
1010  if (!updated) {
1011  updated = true;
1012  // Erase the old options from the new container so that only
1013  // the new options are present.
1014  copy.erase(option.first);
1015  result = true;
1016  }
1017  uint32_t offset = 0;
1018  // Drain the option buffer in multiple new options until all
1019  // data is serialized.
1020  for (; offset != size;) {
1021  // Adjust the data length of the new option if remaining
1022  // data is less than the 255 - header size (for the last
1023  // option).
1024  if (size - offset < len) {
1025  len = size - offset;
1026  }
1027  // Create new option with data starting from offset and
1028  // containing truncated length.
1029  const uint8_t* data = static_cast<const uint8_t*>(buf.getData());
1030  data += header_len;
1031  OptionPtr new_option(new Option(candidate->getUniverse(),
1032  candidate->getType(),
1033  OptionBuffer(data + offset,
1034  data + offset + len)));
1035  // Adjust the offset for remaining data to be written to the
1036  // next new option.
1037  offset += len;
1038  // Add the new option to the new container.
1039  copy.insert(make_pair(candidate->getType(), new_option));
1040  }
1041  } else if (candidate->len() > 255 && size) {
1042  // Also split if the overflow is caused by adding the suboptions
1043  // to the option data (which should be of non zero size).
1044  // Add the new option to the new container.
1045  copy.insert(make_pair(candidate->getType(), data_option));
1046  }
1047  if (updated) {
1048  // Add the new options containing the split suboptions, if any,
1049  // to the new container.
1050  copy.insert(distinct_options.begin(), distinct_options.end());
1051  // After all new options have been split and added, update the
1052  // option container with the new container.
1053  options = copy;
1054  // Other options might need splitting, so we need to iterate
1055  // again until no option needs splitting.
1056  found = true;
1057  break;
1058  }
1059  }
1060  // No option needs splitting, so we can exit the loop.
1061  if (!found) {
1062  break;
1063  }
1064  }
1065  return (result);
1066 }
1067 
1068 void
1069 LibDHCP::packOptions6(isc::util::OutputBuffer& buf,
1070  const OptionCollection& options) {
1071  for (auto const& option : options) {
1072  option.second->pack(buf);
1073  }
1074 }
1075 
1076 void
1077 LibDHCP::OptionFactoryRegister(Option::Universe u,
1078  uint16_t opt_type,
1079  Option::Factory* factory) {
1080  switch (u) {
1081  case Option::V6:
1082  {
1083  if (v6factories_.find(opt_type) != v6factories_.end()) {
1084  isc_throw(BadValue, "There is already DHCPv6 factory registered "
1085  << "for option type " << opt_type);
1086  }
1087  v6factories_[opt_type] = factory;
1088  return;
1089  }
1090  case Option::V4:
1091  {
1092  // Option 0 is special (a one octet-long, equal 0) PAD option. It is never
1093  // instantiated as an Option object, but rather consumed during packet parsing.
1094  if (opt_type == 0) {
1095  isc_throw(BadValue, "Cannot redefine PAD option (code=0)");
1096  }
1097  // Option 255 is never instantiated as an option object. It is special
1098  // (a one-octet equal 255) option that is added at the end of all options
1099  // during packet assembly. It is also silently consumed during packet parsing.
1100  if (opt_type > 254) {
1101  isc_throw(BadValue, "Too big option type for DHCPv4, only 0-254 allowed.");
1102  }
1103  if (v4factories_.find(opt_type) != v4factories_.end()) {
1104  isc_throw(BadValue, "There is already DHCPv4 factory registered "
1105  << "for option type " << opt_type);
1106  }
1107  v4factories_[opt_type] = factory;
1108  return;
1109  }
1110  default:
1111  isc_throw(BadValue, "Invalid universe type specified.");
1112  }
1113 
1114  return;
1115 }
1116 
1117 bool
1118 LibDHCP::initOptionDefs() {
1119  for (uint32_t i = 0; OPTION_DEF_PARAMS[i].optionDefParams; ++i) {
1120  std::string space = OPTION_DEF_PARAMS[i].space;
1121  option_defs_[space] = OptionDefContainerPtr(new OptionDefContainer);
1122  initOptionSpace(option_defs_[space],
1123  OPTION_DEF_PARAMS[i].optionDefParams,
1124  OPTION_DEF_PARAMS[i].size);
1125  }
1126 
1127  return (true);
1128 }
1129 
1130 uint32_t
1131 LibDHCP::optionSpaceToVendorId(const std::string& option_space) {
1132  // 8 is a minimal length of "vendor-X" format
1133  if ((option_space.size() < 8) || (option_space.substr(0,7) != "vendor-")) {
1134  return (0);
1135  }
1136 
1137  int64_t check;
1138  try {
1139  // text after "vendor-", supposedly numbers only
1140  std::string x = option_space.substr(7);
1141 
1142  check = boost::lexical_cast<int64_t>(x);
1143  } catch (const boost::bad_lexical_cast &) {
1144  return (0);
1145  }
1146 
1147  if ((check < 0) || (check > std::numeric_limits<uint32_t>::max())) {
1148  return (0);
1149  }
1150 
1151  // value is small enough to fit
1152  return (static_cast<uint32_t>(check));
1153 }
1154 
1155 void
1157  const OptionDefParams* params,
1158  size_t params_size) {
1159  // Container holding vendor options is typically not initialized, as it
1160  // is held in map of null pointers. We need to initialize here in this
1161  // case.
1162  if (!defs) {
1163  defs.reset(new OptionDefContainer());
1164  } else {
1165  defs->clear();
1166  }
1167 
1168  for (size_t i = 0; i < params_size; ++i) {
1169  std::string encapsulates(params[i].encapsulates);
1170  if (!encapsulates.empty() && params[i].array) {
1171  isc_throw(isc::BadValue, "invalid standard option definition: "
1172  << "option with code '" << params[i].code
1173  << "' may not encapsulate option space '"
1174  << encapsulates << "' because the definition"
1175  << " indicates that this option comprises an array"
1176  << " of values");
1177  }
1178 
1179  // Depending whether an option encapsulates an option space or not
1180  // we pick different constructor to create an instance of the option
1181  // definition.
1182  OptionDefinitionPtr definition;
1183  if (encapsulates.empty()) {
1184  // Option does not encapsulate any option space.
1185  definition.reset(new OptionDefinition(params[i].name,
1186  params[i].code,
1187  params[i].space,
1188  params[i].type,
1189  params[i].array));
1190  } else {
1191  // Option does encapsulate an option space.
1192  definition.reset(new OptionDefinition(params[i].name,
1193  params[i].code,
1194  params[i].space,
1195  params[i].type,
1196  params[i].encapsulates));
1197 
1198  }
1199 
1200  for (size_t rec = 0; rec < params[i].records_size; ++rec) {
1201  definition->addRecordField(params[i].records[rec]);
1202  }
1203 
1204  try {
1205  definition->validate();
1206  } catch (const isc::Exception&) {
1207  // This is unlikely event that validation fails and may
1208  // be only caused by programming error. To guarantee the
1209  // data consistency we clear all option definitions that
1210  // have been added so far and pass the exception forward.
1211  defs->clear();
1212  throw;
1213  }
1214 
1215  // option_defs is a multi-index container with no unique indexes
1216  // so push_back can't fail).
1217  static_cast<void>(defs->push_back(definition));
1218  }
1219 }
Encapsulation of option definition parameters and the structure size.
#define isc_throw_assert(expr)
Replacement for assert() that throws if the expression is false.
Definition: isc_assert.h:18
Base class representing a DHCP option definition.
void addItem(const OptionDefinitionPtr &def)
Adds a new option definition to the container.
ItemsContainerPtr getItems(const Selector &option_space) const
Get all items for the particular option space.
#define V4V6_BIND_OPTION_SPACE
Exception thrown during option unpacking This exception is thrown when an error has occurred...
Definition: option.h:52
#define V4V6_RULE_OPTION_SPACE
std::list< Selector > getOptionSpaceNames() const
Get a list of existing option spaces.
OptionDefContainer::nth_index< 1 >::type OptionDefContainerTypeIndex
Type of the index #1 - option type.
boost::shared_ptr< Option > OptionPtr
Definition: option.h:36
Universe
defines option universe DHCPv4 or DHCPv6
Definition: option.h:83
STL namespace.
Parameters being used to make up an option definition.
#define DOCSIS3_V4_OPTION_SPACE
global docsis3 option spaces
std::vector< ScopedOptionsCopyPtr > ScopedOptionsCopyContainer
A container of ScopedOptionsCopyPtr objects.
Definition: libdhcp++.h:32
boost::multi_index_container< OptionDefinitionPtr, boost::multi_index::indexed_by< boost::multi_index::sequenced<>, boost::multi_index::hashed_non_unique< boost::multi_index::const_mem_fun< OptionDefinition, uint16_t, &OptionDefinition::getCode > >, boost::multi_index::hashed_non_unique< boost::multi_index::const_mem_fun< OptionDefinition, std::string, &OptionDefinition::getName > >, boost::multi_index::ordered_non_unique< boost::multi_index::const_mem_fun< data::BaseStampedElement, boost::posix_time::ptime, &data::StampedElement::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 > > >> OptionDefContainer
Multi index container for DHCP option definitions.
std::vector< uint8_t > OptionBuffer
buffer types used in DHCP code.
Definition: option.h:24
std::pair< OptionDefContainerTypeIndex::const_iterator, OptionDefContainerTypeIndex::const_iterator > OptionDefContainerTypeRange
Pair of iterators to represent the range of options definitions having the same option type value...
#define LW_V6_OPTION_SPACE
Class of option definition space container.
const OptionDefParams DOCSIS3_V6_OPTION_DEFINITIONS[]
Definitions of standard DHCPv6 options.
#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...
#define MAPT_V6_OPTION_SPACE
Definition: edns.h:19
#define MAPE_V6_OPTION_SPACE
ElementPtr copy(ConstElementPtr from, int level)
Copy the data up to a nesting level.
Definition: data.cc:1360
const char * DOCSIS3_CLASS_MODEM
DOCSIS3.0 compatible cable modem.
Definition: libdhcp++.cc:81
A generic exception that is thrown when an unexpected error condition occurs.
#define ISC_V6_OPTION_SPACE
const char * DOCSIS3_CLASS_EROUTER
The class as specified in vendor-class option by the devices.
Definition: libdhcp++.cc:84
#define LAST_RESORT_V4_OPTION_SPACE
boost::shared_ptr< OptionDefContainer > OptionDefContainerPtr
Pointer to an option definition container.
std::multimap< unsigned int, OptionPtr > OptionCollection
A collection of DHCP (v4 or v6) options.
Definition: option.h:40
The OutputBuffer class is a buffer abstraction for manipulating mutable data.
Definition: buffer.h:294
const OptionDefParams DOCSIS3_V4_OPTION_DEFINITIONS[]
Definitions of standard DHCPv4 options.
std::map< std::string, OptionDefContainerPtr > OptionDefContainers
Container that holds option definitions for various option spaces.
This is a base class for exceptions thrown from the DNS library module.
Defines the logger used by the top-level component of kea-lfc.
const int DOCSIS3_V6_OPTION_DEFINITIONS_SIZE
Number of option definitions defined.
uint16_t readUint16(const void *buffer, size_t length)
Read Unsigned 16-Bit Integer from Buffer.
Definition: io_utilities.h:28
#define DHCP6_OPTION_SPACE
#define DHCP4_OPTION_SPACE
global std option spaces
std::shared_ptr< ScopedSubOptionsCopy > ScopedOptionsCopyPtr
A pointer to a ScopedSubOptionsCopy object.
Definition: libdhcp++.h:30
boost::shared_ptr< OptionDefinition > OptionDefinitionPtr
Pointer to option definition object.
Exception thrown during option unpacking This exception is thrown when an error has occurred unpackin...
Definition: option.h:67
#define DOCSIS3_V6_OPTION_SPACE
RAII object enabling duplication of the stored options and restoring the original options on destruct...
Definition: pkt.h:811
const OptionDefContainerPtr null_option_def_container_(new OptionDefContainer())
#define VENDOR_ID_CABLE_LABS
This class implements set/commit mechanism for a single object.
Definition: staged_value.h:32
This class represents vendor-specific information option.
Definition: option_vendor.h:30
void initOptionSpace(OptionDefContainerPtr &defs, const OptionDefParams *params, size_t params_size)
Definition: libdhcp++.cc:1156
OptionDefContainer::nth_index< 2 >::type OptionDefContainerNameIndex
Type of the index #2 - option name.
const int DOCSIS3_V4_OPTION_DEFINITIONS_SIZE
Number of option definitions defined.
std::pair< OptionDefContainerNameIndex::const_iterator, OptionDefContainerNameIndex::const_iterator > OptionDefContainerNameRange
Pair of iterators to represent the range of options definitions having the same option name...