summaryrefslogtreecommitdiffstats
path: root/src/lib/dhcp/libdhcp++.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/dhcp/libdhcp++.cc')
-rw-r--r--src/lib/dhcp/libdhcp++.cc1219
1 files changed, 1219 insertions, 0 deletions
diff --git a/src/lib/dhcp/libdhcp++.cc b/src/lib/dhcp/libdhcp++.cc
new file mode 100644
index 0000000..805925d
--- /dev/null
+++ b/src/lib/dhcp/libdhcp++.cc
@@ -0,0 +1,1219 @@
+// Copyright (C) 2011-2022 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <dhcp/dhcp4.h>
+#include <dhcp/dhcp6.h>
+#include <dhcp/libdhcp++.h>
+#include <dhcp/option.h>
+#include <dhcp/option_vendor.h>
+#include <dhcp/option6_ia.h>
+#include <dhcp/option6_iaaddr.h>
+#include <dhcp/option_definition.h>
+#include <dhcp/option_int_array.h>
+#include <dhcp/std_option_defs.h>
+#include <dhcp/docsis3_option_defs.h>
+#include <exceptions/exceptions.h>
+#include <exceptions/isc_assert.h>
+#include <util/buffer.h>
+
+#include <boost/lexical_cast.hpp>
+#include <boost/shared_array.hpp>
+#include <boost/shared_ptr.hpp>
+
+#include <limits>
+#include <list>
+
+using namespace std;
+using namespace isc::dhcp;
+using namespace isc::util;
+
+namespace isc {
+namespace dhcp {
+
+namespace {
+
+/// @brief the option definitions and the respective space mapping
+///
+/// used for easier initialization of option definitions by space name
+const OptionDefParamsEncapsulation OPTION_DEF_PARAMS[] = {
+ { STANDARD_V4_OPTION_DEFINITIONS, STANDARD_V4_OPTION_DEFINITIONS_SIZE, DHCP4_OPTION_SPACE },
+ { STANDARD_V6_OPTION_DEFINITIONS, STANDARD_V6_OPTION_DEFINITIONS_SIZE, DHCP6_OPTION_SPACE },
+ { DOCSIS3_V4_OPTION_DEFINITIONS, DOCSIS3_V4_OPTION_DEFINITIONS_SIZE, DOCSIS3_V4_OPTION_SPACE },
+ { DOCSIS3_V6_OPTION_DEFINITIONS, DOCSIS3_V6_OPTION_DEFINITIONS_SIZE, DOCSIS3_V6_OPTION_SPACE },
+ { ISC_V6_OPTION_DEFINITIONS, ISC_V6_OPTION_DEFINITIONS_SIZE, ISC_V6_OPTION_SPACE },
+ { MAPE_V6_OPTION_DEFINITIONS, MAPE_V6_OPTION_DEFINITIONS_SIZE, MAPE_V6_OPTION_SPACE },
+ { MAPT_V6_OPTION_DEFINITIONS, MAPT_V6_OPTION_DEFINITIONS_SIZE, MAPT_V6_OPTION_SPACE },
+ { LW_V6_OPTION_DEFINITIONS, LW_V6_OPTION_DEFINITIONS_SIZE, LW_V6_OPTION_SPACE },
+ { V4V6_RULE_OPTION_DEFINITIONS, V4V6_RULE_OPTION_DEFINITIONS_SIZE, V4V6_RULE_OPTION_SPACE },
+ { V4V6_BIND_OPTION_DEFINITIONS, V4V6_BIND_OPTION_DEFINITIONS_SIZE, V4V6_BIND_OPTION_SPACE },
+ { LAST_RESORT_V4_OPTION_DEFINITIONS, LAST_RESORT_V4_OPTION_DEFINITIONS_SIZE, LAST_RESORT_V4_OPTION_SPACE },
+ { NULL, 0, "" }
+};
+
+} // namespace
+
+} // namespace dhcp
+} // namespace isc
+
+// static array with factories for options
+std::map<unsigned short, Option::Factory*> LibDHCP::v4factories_;
+
+// static array with factories for options
+std::map<unsigned short, Option::Factory*> LibDHCP::v6factories_;
+
+// Static container with option definitions grouped by option space.
+OptionDefContainers LibDHCP::option_defs_;
+
+// Static container with option definitions created in runtime.
+StagedValue<OptionDefSpaceContainer> LibDHCP::runtime_option_defs_;
+
+// Null container.
+const OptionDefContainerPtr null_option_def_container_(new OptionDefContainer());
+
+// Those two vendor classes are used for cable modems:
+
+/// DOCSIS3.0 compatible cable modem
+const char* isc::dhcp::DOCSIS3_CLASS_MODEM = "docsis3.0";
+
+/// DOCSIS3.0 cable modem that has router built-in
+const char* isc::dhcp::DOCSIS3_CLASS_EROUTER = "eRouter1.0";
+
+// Let's keep it in .cc file. Moving it to .h would require including optionDefParams
+// definitions there
+void initOptionSpace(OptionDefContainerPtr& defs,
+ const OptionDefParams* params,
+ size_t params_size);
+
+bool LibDHCP::initialized_ = LibDHCP::initOptionDefs();
+
+const OptionDefContainerPtr
+LibDHCP::getOptionDefs(const std::string& space) {
+ auto const& container = option_defs_.find(space);
+ if (container != option_defs_.end()) {
+ return (container->second);
+ }
+
+ return (null_option_def_container_);
+}
+
+const OptionDefContainerPtr
+LibDHCP::getVendorOptionDefs(const Option::Universe u, const uint32_t vendor_id) {
+ if (Option::V4 == u) {
+ if (VENDOR_ID_CABLE_LABS == vendor_id) {
+ return getOptionDefs(DOCSIS3_V4_OPTION_SPACE);
+ }
+ } else if (Option::V6 == u) {
+ if (VENDOR_ID_CABLE_LABS == vendor_id) {
+ return getOptionDefs(DOCSIS3_V6_OPTION_SPACE);
+ } else if (ENTERPRISE_ID_ISC == vendor_id) {
+ return getOptionDefs(ISC_V6_OPTION_SPACE);
+ }
+ }
+
+ return (null_option_def_container_);
+}
+
+OptionDefinitionPtr
+LibDHCP::getOptionDef(const std::string& space, const uint16_t code) {
+ const OptionDefContainerPtr& defs = getOptionDefs(space);
+ const OptionDefContainerTypeIndex& idx = defs->get<1>();
+ const OptionDefContainerTypeRange& range = idx.equal_range(code);
+ if (range.first != range.second) {
+ return (*range.first);
+ }
+
+ return (OptionDefinitionPtr());
+}
+
+OptionDefinitionPtr
+LibDHCP::getOptionDef(const std::string& space, const std::string& name) {
+ const OptionDefContainerPtr& defs = getOptionDefs(space);
+ const OptionDefContainerNameIndex& idx = defs->get<2>();
+ const OptionDefContainerNameRange& range = idx.equal_range(name);
+ if (range.first != range.second) {
+ return (*range.first);
+ }
+
+ return (OptionDefinitionPtr());
+}
+
+OptionDefinitionPtr
+LibDHCP::getVendorOptionDef(const Option::Universe u, const uint32_t vendor_id,
+ const std::string& name) {
+ const OptionDefContainerPtr& defs = getVendorOptionDefs(u, vendor_id);
+
+ if (!defs) {
+ return (OptionDefinitionPtr());
+ }
+
+ const OptionDefContainerNameIndex& idx = defs->get<2>();
+ const OptionDefContainerNameRange& range = idx.equal_range(name);
+ if (range.first != range.second) {
+ return (*range.first);
+ }
+
+ return (OptionDefinitionPtr());
+}
+
+OptionDefinitionPtr
+LibDHCP::getVendorOptionDef(const Option::Universe u, const uint32_t vendor_id,
+ const uint16_t code) {
+ const OptionDefContainerPtr& defs = getVendorOptionDefs(u, vendor_id);
+
+ if (!defs) {
+ // Weird universe or unknown vendor_id. We don't care. No definitions
+ // one way or another
+ // What is it anyway?
+ return (OptionDefinitionPtr());
+ }
+
+ const OptionDefContainerTypeIndex& idx = defs->get<1>();
+ const OptionDefContainerTypeRange& range = idx.equal_range(code);
+ if (range.first != range.second) {
+ return (*range.first);
+ }
+
+ return (OptionDefinitionPtr());
+}
+
+OptionDefinitionPtr
+LibDHCP::getRuntimeOptionDef(const std::string& space, const uint16_t code) {
+ OptionDefContainerPtr container = runtime_option_defs_.getValue().getItems(space);
+ const OptionDefContainerTypeIndex& index = container->get<1>();
+ const OptionDefContainerTypeRange& range = index.equal_range(code);
+ if (range.first != range.second) {
+ return (*range.first);
+ }
+
+ return (OptionDefinitionPtr());
+}
+
+OptionDefinitionPtr
+LibDHCP::getRuntimeOptionDef(const std::string& space, const std::string& name) {
+ OptionDefContainerPtr container = runtime_option_defs_.getValue().getItems(space);
+ const OptionDefContainerNameIndex& index = container->get<2>();
+ const OptionDefContainerNameRange& range = index.equal_range(name);
+ if (range.first != range.second) {
+ return (*range.first);
+ }
+
+ return (OptionDefinitionPtr());
+}
+
+OptionDefContainerPtr
+LibDHCP::getRuntimeOptionDefs(const std::string& space) {
+ return (runtime_option_defs_.getValue().getItems(space));
+}
+
+void
+LibDHCP::setRuntimeOptionDefs(const OptionDefSpaceContainer& defs) {
+ OptionDefSpaceContainer defs_copy;
+ std::list<std::string> option_space_names = defs.getOptionSpaceNames();
+ for (auto const& name : option_space_names) {
+ OptionDefContainerPtr container = defs.getItems(name);
+ for (auto const& def : *container) {
+ OptionDefinitionPtr def_copy(new OptionDefinition(*def));
+ defs_copy.addItem(def_copy);
+ }
+ }
+ runtime_option_defs_ = defs_copy;
+}
+
+void
+LibDHCP::clearRuntimeOptionDefs() {
+ runtime_option_defs_.reset();
+}
+
+void
+LibDHCP::revertRuntimeOptionDefs() {
+ runtime_option_defs_.revert();
+}
+
+void
+LibDHCP::commitRuntimeOptionDefs() {
+ runtime_option_defs_.commit();
+}
+
+OptionDefinitionPtr
+LibDHCP::getLastResortOptionDef(const std::string& space, const uint16_t code) {
+ OptionDefContainerPtr container = getLastResortOptionDefs(space);
+ const OptionDefContainerTypeIndex& index = container->get<1>();
+ const OptionDefContainerTypeRange& range = index.equal_range(code);
+ if (range.first != range.second) {
+ return (*range.first);
+ }
+
+ return (OptionDefinitionPtr());
+}
+
+OptionDefinitionPtr
+LibDHCP::getLastResortOptionDef(const std::string& space, const std::string& name) {
+ OptionDefContainerPtr container = getLastResortOptionDefs(space);
+ const OptionDefContainerNameIndex& index = container->get<2>();
+ const OptionDefContainerNameRange& range = index.equal_range(name);
+ if (range.first != range.second) {
+ return (*range.first);
+ }
+
+ return (OptionDefinitionPtr());
+}
+
+OptionDefContainerPtr
+LibDHCP::getLastResortOptionDefs(const std::string& space) {
+ if (space == DHCP4_OPTION_SPACE) {
+ return getOptionDefs(LAST_RESORT_V4_OPTION_SPACE);
+ }
+
+ return (null_option_def_container_);
+}
+
+bool
+LibDHCP::shouldDeferOptionUnpack(const std::string& space, const uint16_t code) {
+ return ((space == DHCP4_OPTION_SPACE) &&
+ ((code == DHO_VENDOR_ENCAPSULATED_OPTIONS) ||
+ ((code >= 224) && (code <= 254))));
+}
+
+OptionPtr
+LibDHCP::optionFactory(Option::Universe u,
+ uint16_t type,
+ const OptionBuffer& buf) {
+ FactoryMap::iterator it;
+ if (u == Option::V4) {
+ it = v4factories_.find(type);
+ if (it == v4factories_.end()) {
+ isc_throw(BadValue, "factory function not registered "
+ "for DHCP v4 option type " << type);
+ }
+ } else if (u == Option::V6) {
+ it = v6factories_.find(type);
+ if (it == v6factories_.end()) {
+ isc_throw(BadValue, "factory function not registered "
+ "for DHCPv6 option type " << type);
+ }
+ } else {
+ isc_throw(BadValue, "invalid universe specified (expected "
+ "Option::V4 or Option::V6");
+ }
+ return (it->second(u, type, buf));
+}
+
+
+size_t
+LibDHCP::unpackOptions6(const OptionBuffer& buf,
+ const std::string& option_space,
+ isc::dhcp::OptionCollection& options,
+ size_t* relay_msg_offset /* = 0 */,
+ size_t* relay_msg_len /* = 0 */) {
+ size_t offset = 0;
+ size_t length = buf.size();
+ size_t last_offset = 0;
+
+ // Get the list of standard option definitions.
+ const OptionDefContainerPtr& option_defs = LibDHCP::getOptionDefs(option_space);
+ // Runtime option definitions for non standard option space and if
+ // the definition doesn't exist within the standard option definitions.
+ const OptionDefContainerPtr& runtime_option_defs = LibDHCP::getRuntimeOptionDefs(option_space);
+
+ // @todo Once we implement other option spaces we should add else clause
+ // here and gather option definitions for them. For now leaving option_defs
+ // empty will imply creation of generic Option.
+
+ // Get the search indexes #1. It allows to search for option definitions
+ // using option code.
+ const OptionDefContainerTypeIndex& idx = option_defs->get<1>();
+ const OptionDefContainerTypeIndex& runtime_idx = runtime_option_defs->get<1>();
+
+ // The buffer being read comprises a set of options, each starting with
+ // a two-byte type code and a two-byte length field.
+ while (offset < length) {
+ // Save the current offset for backtracking
+ last_offset = offset;
+
+ // Check if there is room for another option
+ if (offset + 4 > length) {
+ // Still something but smaller than an option
+ return (last_offset);
+ }
+
+ // Parse the option header
+ uint16_t opt_type = isc::util::readUint16(&buf[offset], 2);
+ offset += 2;
+
+ uint16_t opt_len = isc::util::readUint16(&buf[offset], 2);
+ offset += 2;
+
+ if (offset + opt_len > length) {
+ // We peeked at the option header of the next option, but
+ // discovered that it would end up beyond buffer end, so
+ // the option is truncated. Hence we can't parse
+ // it. Therefore we revert back by those bytes (as if
+ // we never parsed them).
+ //
+ // @note it is the responsibility of the caller to throw
+ // an exception on partial parsing
+ return (last_offset);
+ }
+
+ if (opt_type == D6O_RELAY_MSG && relay_msg_offset && relay_msg_len) {
+ // remember offset of the beginning of the relay-msg option
+ *relay_msg_offset = offset;
+ *relay_msg_len = opt_len;
+
+ // do not create that relay-msg option
+ offset += opt_len;
+ continue;
+ }
+
+ if (opt_type == D6O_VENDOR_OPTS) {
+ if (offset + 4 > length) {
+ // Truncated vendor-option. We expect at least
+ // 4 bytes for the enterprise-id field. Let's roll back
+ // option code + option length (4 bytes) and return.
+ return (last_offset);
+ }
+
+ // Parse this as vendor option
+ OptionPtr vendor_opt(new OptionVendor(Option::V6, buf.begin() + offset,
+ buf.begin() + offset + opt_len));
+ options.insert(std::make_pair(opt_type, vendor_opt));
+
+ offset += opt_len;
+ continue;
+ }
+
+ // Get all definitions with the particular option code. Note
+ // that option code is non-unique within this container
+ // however at this point we expect to get one option
+ // definition with the particular code. If more are returned
+ // we report an error.
+ OptionDefContainerTypeRange range;
+ // Number of option definitions returned.
+ size_t num_defs = 0;
+
+ // We previously did the lookup only for dhcp6 option space, but with the
+ // addition of S46 options, we now do it for every space.
+ range = idx.equal_range(opt_type);
+ num_defs = std::distance(range.first, range.second);
+
+ // Standard option definitions do not include the definition for
+ // our option or we're searching for non-standard option. Try to
+ // find the definition among runtime option definitions.
+ if (num_defs == 0) {
+ range = runtime_idx.equal_range(opt_type);
+ num_defs = std::distance(range.first, range.second);
+ }
+
+ OptionPtr opt;
+ if (num_defs > 1) {
+ // Multiple options of the same code are not supported right now!
+ isc_throw(isc::Unexpected, "Internal error: multiple option"
+ " definitions for option type " << opt_type <<
+ " returned. Currently it is not supported to initialize"
+ " multiple option definitions for the same option code."
+ " This will be supported once support for option spaces"
+ " is implemented");
+ } else if (num_defs == 0) {
+ // @todo Don't crash if definition does not exist because
+ // only a few option definitions are initialized right
+ // now. In the future we will initialize definitions for
+ // all options and we will remove this elseif. For now,
+ // return generic option.
+ opt = OptionPtr(new Option(Option::V6, opt_type,
+ buf.begin() + offset,
+ buf.begin() + offset + opt_len));
+ } else {
+ try {
+ // The option definition has been found. Use it to create
+ // the option instance from the provided buffer chunk.
+ const OptionDefinitionPtr& def = *(range.first);
+ isc_throw_assert(def);
+ opt = def->optionFactory(Option::V6, opt_type,
+ buf.begin() + offset,
+ buf.begin() + offset + opt_len);
+ } catch (const SkipThisOptionError&) {
+ opt.reset();
+ }
+ }
+
+ // add option to options
+ if (opt) {
+ options.insert(std::make_pair(opt_type, opt));
+ }
+
+ offset += opt_len;
+ }
+
+ last_offset = offset;
+ return (last_offset);
+}
+
+size_t
+LibDHCP::unpackOptions4(const OptionBuffer& buf,
+ const std::string& option_space,
+ isc::dhcp::OptionCollection& options,
+ std::list<uint16_t>& deferred,
+ bool check) {
+ size_t offset = 0;
+ size_t last_offset = 0;
+
+ // Special case when option_space is dhcp4.
+ bool space_is_dhcp4 = (option_space == DHCP4_OPTION_SPACE);
+
+ // Get the list of standard option definitions.
+ const OptionDefContainerPtr& option_defs = LibDHCP::getOptionDefs(option_space);
+ // Runtime option definitions for non standard option space and if
+ // the definition doesn't exist within the standard option definitions.
+ const OptionDefContainerPtr& runtime_option_defs = LibDHCP::getRuntimeOptionDefs(option_space);
+
+ // Get the search indexes #1. It allows to search for option definitions
+ // using option code.
+ const OptionDefContainerTypeIndex& idx = option_defs->get<1>();
+ const OptionDefContainerTypeIndex& runtime_idx = runtime_option_defs->get<1>();
+
+ // Flexible PAD and END parsing.
+ bool flex_pad = (check && (runtime_idx.count(DHO_PAD) == 0));
+ bool flex_end = (check && (runtime_idx.count(DHO_END) == 0));
+
+ // The buffer being read comprises a set of options, each starting with
+ // a one-byte type code and a one-byte length field.
+ while (offset < buf.size()) {
+ // Save the current offset for backtracking
+ last_offset = offset;
+
+ // Get the option type
+ uint8_t opt_type = buf[offset++];
+
+ // DHO_END is a special, one octet long option
+ // Valid in dhcp4 space or when check is true and
+ // there is a sub-option configured for this code.
+ if ((opt_type == DHO_END) && (space_is_dhcp4 || flex_end)) {
+ // just return. Don't need to add DHO_END option
+ // Don't return offset because it makes this condition
+ // and partial parsing impossible to recognize.
+ return (last_offset);
+ }
+
+ // DHO_PAD is just a padding after DHO_END. Let's continue parsing
+ // in case we receive a message without DHO_END.
+ // Valid in dhcp4 space or when check is true and
+ // there is a sub-option configured for this code.
+ if ((opt_type == DHO_PAD) && (space_is_dhcp4 || flex_pad)) {
+ continue;
+ }
+
+ if (offset + 1 > buf.size()) {
+ // We peeked at the option header of the next option, but
+ // discovered that it would end up beyond buffer end, so
+ // the option is truncated. Hence we can't parse
+ // it. Therefore we revert back (as if we never parsed it).
+ //
+ // @note it is the responsibility of the caller to throw
+ // an exception on partial parsing
+ return (last_offset);
+ }
+
+ uint8_t opt_len = buf[offset++];
+ if (offset + opt_len > buf.size()) {
+ // We peeked at the option header of the next option, but
+ // discovered that it would end up beyond buffer end, so
+ // the option is truncated. Hence we can't parse
+ // it. Therefore we revert back (as if we never parsed it).
+ return (last_offset);
+ }
+
+ // While an empty Host Name option is non-RFC compliant, some clients
+ // do send it. In the spirit of being liberal, we'll just drop it,
+ // rather than the dropping the whole packet. We do not have a
+ // way to log this from here but meh... a PCAP will show it arriving,
+ // and we know we drop it.
+ if (space_is_dhcp4 && opt_len == 0 && opt_type == DHO_HOST_NAME) {
+ continue;
+ }
+
+ // Get all definitions with the particular option code. Note
+ // that option code is non-unique within this container
+ // however at this point we expect to get one option
+ // definition with the particular code. If more are returned
+ // we report an error.
+ OptionDefContainerTypeRange range;
+ // Number of option definitions returned.
+ size_t num_defs = 0;
+
+ // Previously we did the lookup only for "dhcp4" option space, but there
+ // may be standard options in other spaces (e.g. radius). So we now do
+ // the lookup for every space.
+ range = idx.equal_range(opt_type);
+ num_defs = std::distance(range.first, range.second);
+
+ // Standard option definitions do not include the definition for
+ // our option or we're searching for non-standard option. Try to
+ // find the definition among runtime option definitions.
+ if (num_defs == 0) {
+ range = runtime_idx.equal_range(opt_type);
+ num_defs = std::distance(range.first, range.second);
+ }
+
+ // Check if option unpacking must be deferred
+ if (shouldDeferOptionUnpack(option_space, opt_type)) {
+ num_defs = 0;
+ // Store deferred option only once.
+ bool found = false;
+ for (auto const& existing : deferred) {
+ if (existing == opt_type) {
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ deferred.push_back(opt_type);
+ }
+ }
+
+ OptionPtr opt;
+ if (num_defs > 1) {
+ // Multiple options of the same code are not supported right now!
+ isc_throw(isc::Unexpected, "Internal error: multiple option"
+ " definitions for option type " <<
+ static_cast<int>(opt_type) <<
+ " returned. Currently it is not supported to initialize"
+ " multiple option definitions for the same option code."
+ " This will be supported once support for option spaces"
+ " is implemented");
+ } else if (num_defs == 0) {
+ opt = OptionPtr(new Option(Option::V4, opt_type,
+ buf.begin() + offset,
+ buf.begin() + offset + opt_len));
+ opt->setEncapsulatedSpace(DHCP4_OPTION_SPACE);
+ } else {
+ try {
+ // The option definition has been found. Use it to create
+ // the option instance from the provided buffer chunk.
+ const OptionDefinitionPtr& def = *(range.first);
+ isc_throw_assert(def);
+ opt = def->optionFactory(Option::V4, opt_type,
+ buf.begin() + offset,
+ buf.begin() + offset + opt_len);
+ } catch (const SkipThisOptionError&) {
+ opt.reset();
+ }
+ }
+
+ // If we have the option, insert it
+ if (opt) {
+ options.insert(std::make_pair(opt_type, opt));
+ }
+
+ offset += opt_len;
+ }
+ last_offset = offset;
+ return (last_offset);
+}
+
+bool
+LibDHCP::fuseOptions4(OptionCollection& options) {
+ bool result = false;
+ // We need to loop until all options have been fused.
+ for (;;) {
+ uint32_t found = 0;
+ bool found_suboptions = false;
+ // Iterate over all options in the container.
+ for (auto const& option : options) {
+ OptionPtr candidate = option.second;
+ OptionCollection& sub_options = candidate->getMutableOptions();
+ // Fuse suboptions recursively, if any.
+ if (sub_options.size()) {
+ // Fusing suboptions might result in new options with multiple
+ // options having the same code, so we need to iterate again
+ // until no option needs fusing.
+ found_suboptions = LibDHCP::fuseOptions4(sub_options);
+ if (found_suboptions) {
+ result = true;
+ }
+ }
+ OptionBuffer data;
+ OptionCollection suboptions;
+ // Make a copy of the options so we can safely iterate over the
+ // old container.
+ OptionCollection copy = options;
+ for (auto const& old_option : copy) {
+ if (old_option.first == option.first) {
+ // Copy the option data to the buffer.
+ data.insert(data.end(), old_option.second->getData().begin(),
+ old_option.second->getData().end());
+ suboptions.insert(old_option.second->getOptions().begin(),
+ old_option.second->getOptions().end());
+ // Other options might need fusing, so we need to iterate
+ // again until no options needs fusing.
+ found++;
+ }
+ }
+ if (found > 1) {
+ result = true;
+ // Erase the old options from the new container so that only the
+ // new option is present.
+ copy.erase(option.first);
+ // Create new option with entire data.
+ OptionPtr new_option(new Option(candidate->getUniverse(),
+ candidate->getType(), data));
+ // Recreate suboptions container.
+ new_option->getMutableOptions() = suboptions;
+ // Add the new option to the new container.
+ copy.insert(make_pair(candidate->getType(), new_option));
+ // After all options have been fused and new option added,
+ // update the option container with the new container.
+ options = copy;
+ break;
+ } else {
+ found = 0;
+ }
+ }
+ // No option needs fusing, so we can exit the loop.
+ if ((found <= 1) && !found_suboptions) {
+ break;
+ }
+ }
+ return (result);
+}
+
+size_t
+LibDHCP::unpackVendorOptions6(const uint32_t vendor_id,
+ const OptionBuffer& buf,
+ isc::dhcp::OptionCollection& options) {
+ size_t offset = 0;
+ size_t length = buf.size();
+
+ // Get the list of option definitions for this particular vendor-id
+ const OptionDefContainerPtr& option_defs =
+ LibDHCP::getVendorOptionDefs(Option::V6, vendor_id);
+
+ // Get the search index #1. It allows to search for option definitions
+ // using option code. If there's no such vendor-id space, we're out of luck
+ // anyway.
+ const OptionDefContainerTypeIndex* idx = NULL;
+ if (option_defs) {
+ idx = &(option_defs->get<1>());
+ }
+
+ // The buffer being read comprises a set of options, each starting with
+ // a two-byte type code and a two-byte length field.
+ while (offset < length) {
+ if (offset + 4 > length) {
+ isc_throw(SkipRemainingOptionsError,
+ "Vendor option parse failed: truncated header");
+ }
+
+ uint16_t opt_type = isc::util::readUint16(&buf[offset], 2);
+ offset += 2;
+
+ uint16_t opt_len = isc::util::readUint16(&buf[offset], 2);
+ offset += 2;
+
+ if (offset + opt_len > length) {
+ isc_throw(SkipRemainingOptionsError,
+ "Vendor option parse failed. Tried to parse "
+ << offset + opt_len << " bytes from " << length
+ << "-byte long buffer.");
+ }
+
+ OptionPtr opt;
+ opt.reset();
+
+ // If there is a definition for such a vendor option...
+ if (idx) {
+ // Get all definitions with the particular option
+ // code. Note that option code is non-unique within this
+ // container however at this point we expect to get one
+ // option definition with the particular code. If more are
+ // returned we report an error.
+ const OptionDefContainerTypeRange& range =
+ idx->equal_range(opt_type);
+ // Get the number of returned option definitions for the
+ // option code.
+ size_t num_defs = std::distance(range.first, range.second);
+
+ if (num_defs > 1) {
+ // Multiple options of the same code are not supported
+ // right now!
+ isc_throw(isc::Unexpected, "Internal error: multiple option"
+ " definitions for option type " << opt_type <<
+ " returned. Currently it is not supported to"
+ " initialize multiple option definitions for the"
+ " same option code. This will be supported once"
+ " support for option spaces is implemented");
+ } else if (num_defs == 1) {
+ // The option definition has been found. Use it to create
+ // the option instance from the provided buffer chunk.
+ const OptionDefinitionPtr& def = *(range.first);
+ isc_throw_assert(def);
+ opt = def->optionFactory(Option::V6, opt_type,
+ buf.begin() + offset,
+ buf.begin() + offset + opt_len);
+ }
+ }
+
+ // This can happen in one of 2 cases:
+ // 1. we do not have definitions for that vendor-space
+ // 2. we do have definitions, but that particular option was
+ // not defined
+
+ if (!opt) {
+ opt = OptionPtr(new Option(Option::V6, opt_type,
+ buf.begin() + offset,
+ buf.begin() + offset + opt_len));
+ }
+
+ // add option to options
+ if (opt) {
+ options.insert(std::make_pair(opt_type, opt));
+ }
+ offset += opt_len;
+ }
+
+ return (offset);
+}
+
+size_t
+LibDHCP::unpackVendorOptions4(const uint32_t vendor_id, const OptionBuffer& buf,
+ isc::dhcp::OptionCollection& options) {
+ size_t offset = 0;
+
+ // Get the list of standard option definitions.
+ const OptionDefContainerPtr& option_defs =
+ LibDHCP::getVendorOptionDefs(Option::V4, vendor_id);
+ // Get the search index #1. It allows to search for option definitions
+ // using option code.
+ const OptionDefContainerTypeIndex* idx = NULL;
+ if (option_defs) {
+ idx = &(option_defs->get<1>());
+ }
+
+ // The buffer being read comprises a set of options, each starting with
+ // a one-byte type code and a one-byte length field.
+ while (offset < buf.size()) {
+ // Note that Vendor-Specific info option (RFC3925) has a
+ // different option format than Vendor-Spec info for
+ // DHCPv6. (there's additional layer of data-length)
+ uint8_t data_len = buf[offset++];
+
+ if (offset + data_len > buf.size()) {
+ // The option is truncated.
+ isc_throw(SkipRemainingOptionsError,
+ "Attempt to parse truncated vendor option");
+ }
+
+ uint8_t offset_end = offset + data_len;
+
+ // beginning of data-chunk parser
+ while (offset < offset_end) {
+ uint8_t opt_type = buf[offset++];
+
+ // No DHO_END or DHO_PAD in vendor options
+
+ if (offset + 1 > offset_end) {
+ // opt_type must be cast to integer so as it is not
+ // treated as unsigned char value (a number is
+ // presented in error message).
+ isc_throw(SkipRemainingOptionsError,
+ "Attempt to parse truncated vendor option "
+ << static_cast<int>(opt_type));
+ }
+
+ uint8_t opt_len = buf[offset++];
+ if (offset + opt_len > offset_end) {
+ isc_throw(SkipRemainingOptionsError,
+ "Option parse failed. Tried to parse "
+ << offset + opt_len << " bytes from " << buf.size()
+ << "-byte long buffer.");
+ }
+
+ OptionPtr opt;
+ opt.reset();
+
+ if (idx) {
+ // Get all definitions with the particular option
+ // code. Note that option code is non-unique within
+ // this container however at this point we expect to
+ // get one option definition with the particular
+ // code. If more are returned we report an error.
+ const OptionDefContainerTypeRange& range =
+ idx->equal_range(opt_type);
+ // Get the number of returned option definitions for
+ // the option code.
+ size_t num_defs = std::distance(range.first, range.second);
+
+ if (num_defs > 1) {
+ // Multiple options of the same code are not
+ // supported right now!
+ isc_throw(isc::Unexpected, "Internal error: multiple"
+ " option definitions for option type "
+ << opt_type << " returned. Currently it is"
+ " not supported to initialize multiple option"
+ " definitions for the same option code."
+ " This will be supported once support for"
+ " option spaces is implemented");
+ } else if (num_defs == 1) {
+ // The option definition has been found. Use it to create
+ // the option instance from the provided buffer chunk.
+ const OptionDefinitionPtr& def = *(range.first);
+ isc_throw_assert(def);
+ opt = def->optionFactory(Option::V4, opt_type,
+ buf.begin() + offset,
+ buf.begin() + offset + opt_len);
+ }
+ }
+
+ if (!opt) {
+ opt = OptionPtr(new Option(Option::V4, opt_type,
+ buf.begin() + offset,
+ buf.begin() + offset + opt_len));
+ }
+
+ options.insert(std::make_pair(opt_type, opt));
+ offset += opt_len;
+
+ } // end of data-chunk
+
+ break; // end of the vendor block.
+ }
+ return (offset);
+}
+
+void
+LibDHCP::packOptions4(isc::util::OutputBuffer& buf,
+ const OptionCollection& options,
+ bool top, bool check) {
+ OptionCollection agent;
+ OptionPtr end;
+
+ // We only look for type when we're the top level
+ // call that starts packing for options for a packet.
+ // This way we avoid doing type logic in all ensuing
+ // recursive calls.
+ if (top) {
+ auto x = options.find(DHO_DHCP_MESSAGE_TYPE);
+ if (x != options.end()) {
+ x->second->pack(buf, check);
+ }
+ }
+
+ for (auto const& option : options) {
+ // TYPE is already done, RAI and END options must be last.
+ switch (option.first) {
+ case DHO_DHCP_MESSAGE_TYPE:
+ break;
+ case DHO_DHCP_AGENT_OPTIONS:
+ agent.insert(make_pair(DHO_DHCP_AGENT_OPTIONS, option.second));
+ break;
+ case DHO_END:
+ end = option.second;
+ break;
+ default:
+ option.second->pack(buf, check);
+ break;
+ }
+ }
+
+ // Add the RAI option if it exists.
+ for (auto const& option : agent) {
+ option.second->pack(buf, check);
+ }
+
+ // And at the end the END option.
+ if (end) {
+ end->pack(buf, check);
+ }
+}
+
+bool
+LibDHCP::splitOptions4(OptionCollection& options,
+ ScopedOptionsCopyContainer& scoped_options,
+ uint32_t used) {
+ bool result = false;
+ // We need to loop until all options have been split.
+ for (;;) {
+ bool found = false;
+ // Make a copy of the options so we can safely iterate over the
+ // old container.
+ OptionCollection copy = options;
+ // Iterate over all options in the container.
+ for (auto const& option : options) {
+ OptionPtr candidate = option.second;
+ OptionCollection& sub_options = candidate->getMutableOptions();
+ // Split suboptions recursively, if any.
+ OptionCollection distinct_options;
+ bool updated = false;
+ bool found_suboptions = false;
+ if (sub_options.size()) {
+ ScopedOptionsCopyPtr candidate_scoped_options(new ScopedSubOptionsCopy(candidate));
+ found_suboptions = LibDHCP::splitOptions4(sub_options, scoped_options,
+ used + candidate->getHeaderLen());
+ // Also split if the overflow is caused by adding the suboptions
+ // to the option data.
+ if (found_suboptions || candidate->len() > 255) {
+ updated = true;
+ scoped_options.push_back(candidate_scoped_options);
+ // Erase the old options from the new container so that only
+ // the new options are present.
+ copy.erase(option.first);
+ result = true;
+ // If there are suboptions which have been split, one parent
+ // option will be created for each of the chunk of the
+ // suboptions. If the suboptions have not been split,
+ // but they cause overflow when added to the option data,
+ // one parent option will contain the option data and one
+ // parent option will be created for each suboption.
+ // This will guarantee that none of the options plus
+ // suboptions will have more than 255 bytes.
+ for (auto sub_option : candidate->getMutableOptions()) {
+ OptionPtr data_sub_option(new Option(candidate->getUniverse(),
+ candidate->getType(),
+ OptionBuffer(0)));
+ data_sub_option->addOption(sub_option.second);
+ distinct_options.insert(make_pair(candidate->getType(), data_sub_option));
+ }
+ }
+ }
+ // Create a new option containing only data that needs to be split
+ // and no suboptions (which are inserted in completely separate
+ // options which are added at the end).
+ OptionPtr data_option(new Option(candidate->getUniverse(),
+ candidate->getType(),
+ OptionBuffer(candidate->getData().begin(),
+ candidate->getData().end())));
+ OutputBuffer buf(0);
+ data_option->pack(buf, false);
+ uint32_t header_len = candidate->getHeaderLen();
+ // At least 1 + header length bytes must be available.
+ if (used >= 255 - header_len) {
+ isc_throw(BadValue, "there is no space left to split option "
+ << candidate->getType() << " after parent already used "
+ << used);
+ }
+ // Maximum option buffer size is 255 - header size - buffer size
+ // already used by parent options.
+ uint8_t len = 255 - header_len - used;
+ // Current option size after split is the sum of the data and the
+ // header size. The suboptions are serialized in separate options.
+ // The header is duplicated in all new options, but the rest of the
+ // data must be split and serialized.
+ uint32_t size = buf.getLength() - header_len;
+ // Only split if data does not fit in the current option.
+ if (size > len) {
+ // Erase the old option from the new container so that only new
+ // options are present.
+ if (!updated) {
+ updated = true;
+ // Erase the old options from the new container so that only
+ // the new options are present.
+ copy.erase(option.first);
+ result = true;
+ }
+ uint32_t offset = 0;
+ // Drain the option buffer in multiple new options until all
+ // data is serialized.
+ for (; offset != size;) {
+ // Adjust the data length of the new option if remaining
+ // data is less than the 255 - header size (for the last
+ // option).
+ if (size - offset < len) {
+ len = size - offset;
+ }
+ // Create new option with data starting from offset and
+ // containing truncated length.
+ const uint8_t* data = static_cast<const uint8_t*>(buf.getData());
+ data += header_len;
+ OptionPtr new_option(new Option(candidate->getUniverse(),
+ candidate->getType(),
+ OptionBuffer(data + offset,
+ data + offset + len)));
+ // Adjust the offset for remaining data to be written to the
+ // next new option.
+ offset += len;
+ // Add the new option to the new container.
+ copy.insert(make_pair(candidate->getType(), new_option));
+ }
+ } else if (candidate->len() > 255 && size) {
+ // Also split if the overflow is caused by adding the suboptions
+ // to the option data (which should be of non zero size).
+ // Add the new option to the new container.
+ copy.insert(make_pair(candidate->getType(), data_option));
+ }
+ if (updated) {
+ // Add the new options containing the split suboptions, if any,
+ // to the new container.
+ copy.insert(distinct_options.begin(), distinct_options.end());
+ // After all new options have been split and added, update the
+ // option container with the new container.
+ options = copy;
+ // Other options might need splitting, so we need to iterate
+ // again until no option needs splitting.
+ found = true;
+ break;
+ }
+ }
+ // No option needs splitting, so we can exit the loop.
+ if (!found) {
+ break;
+ }
+ }
+ return (result);
+}
+
+void
+LibDHCP::packOptions6(isc::util::OutputBuffer& buf,
+ const OptionCollection& options) {
+ for (auto const& option : options) {
+ option.second->pack(buf);
+ }
+}
+
+void
+LibDHCP::OptionFactoryRegister(Option::Universe u,
+ uint16_t opt_type,
+ Option::Factory* factory) {
+ switch (u) {
+ case Option::V6:
+ {
+ if (v6factories_.find(opt_type) != v6factories_.end()) {
+ isc_throw(BadValue, "There is already DHCPv6 factory registered "
+ << "for option type " << opt_type);
+ }
+ v6factories_[opt_type] = factory;
+ return;
+ }
+ case Option::V4:
+ {
+ // Option 0 is special (a one octet-long, equal 0) PAD option. It is never
+ // instantiated as an Option object, but rather consumed during packet parsing.
+ if (opt_type == 0) {
+ isc_throw(BadValue, "Cannot redefine PAD option (code=0)");
+ }
+ // Option 255 is never instantiated as an option object. It is special
+ // (a one-octet equal 255) option that is added at the end of all options
+ // during packet assembly. It is also silently consumed during packet parsing.
+ if (opt_type > 254) {
+ isc_throw(BadValue, "Too big option type for DHCPv4, only 0-254 allowed.");
+ }
+ if (v4factories_.find(opt_type) != v4factories_.end()) {
+ isc_throw(BadValue, "There is already DHCPv4 factory registered "
+ << "for option type " << opt_type);
+ }
+ v4factories_[opt_type] = factory;
+ return;
+ }
+ default:
+ isc_throw(BadValue, "Invalid universe type specified.");
+ }
+
+ return;
+}
+
+bool
+LibDHCP::initOptionDefs() {
+ for (uint32_t i = 0; OPTION_DEF_PARAMS[i].optionDefParams; ++i) {
+ std::string space = OPTION_DEF_PARAMS[i].space;
+ option_defs_[space] = OptionDefContainerPtr(new OptionDefContainer);
+ initOptionSpace(option_defs_[space],
+ OPTION_DEF_PARAMS[i].optionDefParams,
+ OPTION_DEF_PARAMS[i].size);
+ }
+
+ return (true);
+}
+
+uint32_t
+LibDHCP::optionSpaceToVendorId(const std::string& option_space) {
+ // 8 is a minimal length of "vendor-X" format
+ if ((option_space.size() < 8) || (option_space.substr(0,7) != "vendor-")) {
+ return (0);
+ }
+
+ int64_t check;
+ try {
+ // text after "vendor-", supposedly numbers only
+ std::string x = option_space.substr(7);
+
+ check = boost::lexical_cast<int64_t>(x);
+ } catch (const boost::bad_lexical_cast &) {
+ return (0);
+ }
+
+ if ((check < 0) || (check > std::numeric_limits<uint32_t>::max())) {
+ return (0);
+ }
+
+ // value is small enough to fit
+ return (static_cast<uint32_t>(check));
+}
+
+void
+initOptionSpace(OptionDefContainerPtr& defs,
+ const OptionDefParams* params,
+ size_t params_size) {
+ // Container holding vendor options is typically not initialized, as it
+ // is held in map of null pointers. We need to initialize here in this
+ // case.
+ if (!defs) {
+ defs.reset(new OptionDefContainer());
+ } else {
+ defs->clear();
+ }
+
+ for (size_t i = 0; i < params_size; ++i) {
+ std::string encapsulates(params[i].encapsulates);
+ if (!encapsulates.empty() && params[i].array) {
+ isc_throw(isc::BadValue, "invalid standard option definition: "
+ << "option with code '" << params[i].code
+ << "' may not encapsulate option space '"
+ << encapsulates << "' because the definition"
+ << " indicates that this option comprises an array"
+ << " of values");
+ }
+
+ // Depending whether an option encapsulates an option space or not
+ // we pick different constructor to create an instance of the option
+ // definition.
+ OptionDefinitionPtr definition;
+ if (encapsulates.empty()) {
+ // Option does not encapsulate any option space.
+ definition.reset(new OptionDefinition(params[i].name,
+ params[i].code,
+ params[i].space,
+ params[i].type,
+ params[i].array));
+ } else {
+ // Option does encapsulate an option space.
+ definition.reset(new OptionDefinition(params[i].name,
+ params[i].code,
+ params[i].space,
+ params[i].type,
+ params[i].encapsulates));
+
+ }
+
+ for (size_t rec = 0; rec < params[i].records_size; ++rec) {
+ definition->addRecordField(params[i].records[rec]);
+ }
+
+ try {
+ definition->validate();
+ } catch (const isc::Exception&) {
+ // This is unlikely event that validation fails and may
+ // be only caused by programming error. To guarantee the
+ // data consistency we clear all option definitions that
+ // have been added so far and pass the exception forward.
+ defs->clear();
+ throw;
+ }
+
+ // option_defs is a multi-index container with no unique indexes
+ // so push_back can't fail).
+ static_cast<void>(defs->push_back(definition));
+ }
+}