// PERFORMANCE OF THIS SOFTWARE.
#include <config/ccsession.h>
-#include <dhcpsrv/cfgmgr.h>
#include <dhcp4/config_parser.h>
#include <dhcp4/dhcp4_log.h>
+#include <dhcp/libdhcp++.h>
+#include <dhcp/option_definition.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <util/encode/hex.h>
#include <boost/foreach.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/algorithm/string.hpp>
/// no subnet object created yet to store them.
typedef std::vector<Pool4Ptr> PoolStorage;
+/// @brief Collection of option descriptors. This container allows searching for
+/// options using the option code or persistency flag. This is useful when merging
+/// existing options with newly configured options.
+typedef Subnet::OptionContainer OptionStorage;
+
/// @brief Global uint32 parameters that will be used as defaults.
Uint32Storage uint32_defaults;
/// @brief global string parameters that will be used as defaults.
StringStorage string_defaults;
+/// @brief Global storage for options that will be used as defaults.
+OptionStorage option_defaults;
+
/// @brief a dummy configuration parser
///
/// It is a debugging parser. It does not configure anything,
PoolStorage* pools_;
};
+/// @brief Parser for option data value.
+///
+/// This parser parses configuration entries that specify value of
+/// a single option. These entries include option name, option code
+/// and data carried by the option. If parsing is successful than an
+/// instance of an option is created and added to the storage provided
+/// by the calling class.
+///
+/// @todo This class parses and validates the option name. However it is
+/// not used anywhere util support for option spaces is implemented
+/// (see tickets #2319, #2314). When option spaces are implemented
+/// there will be a way to reference the particular option using
+/// its type (code) or option name.
+class OptionDataParser : public Dhcp4ConfigParser {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// Class constructor.
+ OptionDataParser(const std::string&)
+ : options_(NULL),
+ // initialize option to NULL ptr
+ option_descriptor_(false) { }
+
+ /// @brief Parses the single option data.
+ ///
+ /// This method parses the data of a single option from the configuration.
+ /// The option data includes option name, option code and data being
+ /// carried by this option. Eventually it creates the instance of the
+ /// option.
+ ///
+ /// @warning setStorage must be called with valid storage pointer prior
+ /// to calling this method.
+ ///
+ /// @param option_data_entries collection of entries that define value
+ /// for a particular option.
+ /// @throw Dhcp4ConfigError if invalid parameter specified in
+ /// the configuration.
+ /// @throw isc::InvalidOperation if failed to set storage prior to
+ /// calling build.
+ /// @throw isc::BadValue if option data storage is invalid.
+ virtual void build(ConstElementPtr option_data_entries) {
+ if (options_ == NULL) {
+ isc_throw(isc::InvalidOperation, "Parser logic error: storage must be set before "
+ "parsing option data.");
+ }
+ BOOST_FOREACH(ConfigPair param, option_data_entries->mapValue()) {
+ ParserPtr parser;
+ if (param.first == "name") {
+ boost::shared_ptr<StringParser>
+ name_parser(dynamic_cast<StringParser*>(StringParser::Factory(param.first)));
+ if (name_parser) {
+ name_parser->setStorage(&string_values_);
+ parser = name_parser;
+ }
+ } else if (param.first == "code") {
+ boost::shared_ptr<Uint32Parser>
+ code_parser(dynamic_cast<Uint32Parser*>(Uint32Parser::Factory(param.first)));
+ if (code_parser) {
+ code_parser->setStorage(&uint32_values_);
+ parser = code_parser;
+ }
+ } else if (param.first == "data") {
+ boost::shared_ptr<StringParser>
+ value_parser(dynamic_cast<StringParser*>(StringParser::Factory(param.first)));
+ if (value_parser) {
+ value_parser->setStorage(&string_values_);
+ parser = value_parser;
+ }
+ } else {
+ isc_throw(Dhcp4ConfigError,
+ "Parser error: option-data parameter not supported: "
+ << param.first);
+ }
+ parser->build(param.second);
+ }
+ // Try to create the option instance.
+ createOption();
+ }
+
+ /// @brief Commits option value.
+ ///
+ /// This function adds a new option to the storage or replaces an existing option
+ /// with the same code.
+ ///
+ /// @throw isc::InvalidOperation if failed to set pointer to storage or failed
+ /// to call build() prior to commit. If that happens data in the storage
+ /// remain un-modified.
+ virtual void commit() {
+ if (options_ == NULL) {
+ isc_throw(isc::InvalidOperation, "Parser logic error: storage must be set before "
+ "commiting option data.");
+ } else if (!option_descriptor_.option) {
+ // Before we can commit the new option should be configured. If it is not
+ // than somebody must have called commit() before build().
+ isc_throw(isc::InvalidOperation, "Parser logic error: no option has been configured and"
+ " thus there is nothing to commit. Has build() been called?");
+ }
+ uint16_t opt_type = option_descriptor_.option->getType();
+ Subnet::OptionContainerTypeIndex& idx = options_->get<1>();
+ // Try to find options with the particular option code in the main
+ // storage. If found, remove these options because they will be
+ // replaced with new one.
+ Subnet::OptionContainerTypeRange range =
+ idx.equal_range(opt_type);
+ if (std::distance(range.first, range.second) > 0) {
+ idx.erase(range.first, range.second);
+ }
+ // Append new option to the main storage.
+ options_->push_back(option_descriptor_);
+ }
+
+ /// @brief Set storage for the parser.
+ ///
+ /// Sets storage for the parser. This storage points to the
+ /// vector of options and is used by multiple instances of
+ /// OptionDataParser. Each instance creates exactly one object
+ /// of dhcp::Option or derived type and appends it to this
+ /// storage.
+ ///
+ /// @param storage pointer to the options storage
+ void setStorage(OptionStorage* storage) {
+ options_ = storage;
+ }
+
+private:
+
+ /// @brief Create option instance.
+ ///
+ /// Creates an instance of an option and adds it to the provided
+ /// options storage. If the option data parsed by \ref build function
+ /// are invalid or insufficient this function emits an exception.
+ ///
+ /// @warning this function does not check if options_ storage pointer
+ /// is intitialized but this check is not needed here because it is done
+ /// in the \ref build function.
+ ///
+ /// @throw Dhcp4ConfigError if parameters provided in the configuration
+ /// are invalid.
+ void createOption() {
+ // Option code is held in the uint32_t storage but is supposed to
+ // be uint16_t value. We need to check that value in the configuration
+ // does not exceed range of uint16_t and is not zero.
+ uint32_t option_code = getUint32Param("code");
+ if (option_code == 0) {
+ isc_throw(Dhcp4ConfigError, "Parser error: value of 'code' must not"
+ << " be equal to zero. Option code '0' is reserved in"
+ << " DHCPv4.");
+ } else if (option_code > std::numeric_limits<uint16_t>::max()) {
+ isc_throw(Dhcp4ConfigError, "Parser error: value of 'code' must not"
+ << " exceed " << std::numeric_limits<uint16_t>::max());
+ }
+ // Check that the option name has been specified, is non-empty and does not
+ // contain spaces.
+ // @todo possibly some more restrictions apply here?
+ std::string option_name = getStringParam("name");
+ if (option_name.empty()) {
+ isc_throw(Dhcp4ConfigError, "Parser error: option name must not be"
+ << " empty");
+ } else if (option_name.find(" ") != std::string::npos) {
+ isc_throw(Dhcp4ConfigError, "Parser error: option name must not contain"
+ << " spaces");
+ }
+
+ // Get option data from the configuration database ('data' field).
+ // Option data is specified by the user as case insensitive string
+ // of hexadecimal digits for each option.
+ std::string option_data = getStringParam("data");
+ // Transform string of hexadecimal digits into binary format.
+ std::vector<uint8_t> binary;
+ try {
+ util::encode::decodeHex(option_data, binary);
+ } catch (...) {
+ isc_throw(Dhcp4ConfigError, "Parser error: option data is not a valid"
+ << " string of hexadecimal digits: " << option_data);
+ }
+ // Get all existing DHCPv4 option definitions. The one that matches
+ // our option will be picked and used to create it.
+ OptionDefContainer option_defs = LibDHCP::getOptionDefs(Option::V4);
+ // Get search index #1. It allows searching for options definitions
+ // using option type value.
+ const OptionDefContainerTypeIndex& idx = option_defs.get<1>();
+ // Get all option definitions matching option code we want to create.
+ const OptionDefContainerTypeRange& range = idx.equal_range(option_code);
+ size_t num_defs = std::distance(range.first, range.second);
+ OptionPtr option;
+ // Currently we do not allow duplicated definitions and if there are
+ // any duplicates we issue internal server error.
+ if (num_defs > 1) {
+ isc_throw(Dhcp4ConfigError, "Internal error: currently it is not"
+ << " supported to initialize multiple option definitions"
+ << " for the same option code. This will be supported once"
+ << " there option spaces are implemented.");
+ } else if (num_defs == 0) {
+ // @todo We have a limited set of option definitions intiialized at the moment.
+ // In the future we want to initialize option definitions for all options.
+ // Consequently an error will be issued if an option definition does not exist
+ // for a particular option code. For now it is ok to create generic option
+ // if definition does not exist.
+ OptionPtr option(new Option(Option::V4, static_cast<uint16_t>(option_code),
+ binary));
+ // The created option is stored in option_descriptor_ class member until the
+ // commit stage when it is inserted into the main storage. If an option with the
+ // same code exists in main storage already the old option is replaced.
+ option_descriptor_.option = option;
+ option_descriptor_.persistent = false;
+ } else {
+ // We have exactly one option definition for the particular option code
+ // use it to create the option instance.
+ const OptionDefinitionPtr& def = *(range.first);
+ try {
+ OptionPtr option = def->optionFactory(Option::V4, option_code, binary);
+ Subnet::OptionDescriptor desc(option, false);
+ option_descriptor_.option = option;
+ option_descriptor_.persistent = false;
+ } catch (const isc::Exception& ex) {
+ isc_throw(Dhcp4ConfigError, "Parser error: option data does not match"
+ << " option definition (code " << option_code << "): "
+ << ex.what());
+ }
+ }
+ }
+
+ /// @brief Get a parameter from the strings storage.
+ ///
+ /// @param param_id parameter identifier.
+ /// @throw Dhcp4ConfigError if parameter has not been found.
+ std::string getStringParam(const std::string& param_id) const {
+ StringStorage::const_iterator param = string_values_.find(param_id);
+ if (param == string_values_.end()) {
+ isc_throw(Dhcp4ConfigError, "Parser error: option-data parameter"
+ << " '" << param_id << "' not specified");
+ }
+ return (param->second);
+ }
+
+ /// @brief Get a parameter from the uint32 values storage.
+ ///
+ /// @param param_id parameter identifier.
+ /// @throw Dhcp4ConfigError if parameter has not been found.
+ uint32_t getUint32Param(const std::string& param_id) const {
+ Uint32Storage::const_iterator param = uint32_values_.find(param_id);
+ if (param == uint32_values_.end()) {
+ isc_throw(Dhcp4ConfigError, "Parser error: option-data parameter"
+ << " '" << param_id << "' not specified");
+ }
+ return (param->second);
+ }
+
+ /// Storage for uint32 values (e.g. option code).
+ Uint32Storage uint32_values_;
+ /// Storage for string values (e.g. option name or data).
+ StringStorage string_values_;
+ /// Pointer to options storage. This storage is provided by
+ /// the calling class and is shared by all OptionDataParser objects.
+ OptionStorage* options_;
+ /// Option descriptor holds newly configured option.
+ Subnet::OptionDescriptor option_descriptor_;
+};
+
+/// @brief Parser for option data values within a subnet.
+///
+/// This parser iterates over all entries that define options
+/// data for a particular subnet and creates a collection of options.
+/// If parsing is successful, all these options are added to the Subnet
+/// object.
+class OptionDataListParser : public Dhcp4ConfigParser {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// Unless otherwise specified, parsed options will be stored in
+ /// a global option container (option_default). That storage location
+ /// is overriden on a subnet basis.
+ OptionDataListParser(const std::string&)
+ : options_(&option_defaults), local_options_() { }
+
+ /// @brief Parses entries that define options' data for a subnet.
+ ///
+ /// This method iterates over all entries that define option data
+ /// for options within a single subnet and creates options' instances.
+ ///
+ /// @param option_data_list pointer to a list of options' data sets.
+ /// @throw Dhcp4ConfigError if option parsing failed.
+ void build(ConstElementPtr option_data_list) {
+ BOOST_FOREACH(ConstElementPtr option_value, option_data_list->listValue()) {
+ boost::shared_ptr<OptionDataParser> parser(new OptionDataParser("option-data"));
+ // options_ member will hold instances of all options thus
+ // each OptionDataParser takes it as a storage.
+ parser->setStorage(&local_options_);
+ // Build the instance of a single option.
+ parser->build(option_value);
+ // Store a parser as it will be used to commit.
+ parsers_.push_back(parser);
+ }
+ }
+
+ /// @brief Set storage for option instances.
+ ///
+ /// @param storage pointer to options storage.
+ void setStorage(OptionStorage* storage) {
+ options_ = storage;
+ }
+
+
+ /// @brief Commit all option values.
+ ///
+ /// This function invokes commit for all option values.
+ void commit() {
+ BOOST_FOREACH(ParserPtr parser, parsers_) {
+ parser->commit();
+ }
+ // Parsing was successful and we have all configured
+ // options in local storage. We can now replace old values
+ // with new values.
+ std::swap(local_options_, *options_);
+ }
+
+ /// @brief Create DhcpDataListParser object
+ ///
+ /// @param param_name param name.
+ ///
+ /// @return DhcpConfigParser object.
+ static Dhcp4ConfigParser* Factory(const std::string& param_name) {
+ return (new OptionDataListParser(param_name));
+ }
+
+ /// Intermediate option storage. This storage is used by
+ /// lower level parsers to add new options. Values held
+ /// in this storage are assigned to main storage (options_)
+ /// if overall parsing was successful.
+ OptionStorage local_options_;
+ /// Pointer to options instances storage.
+ OptionStorage* options_;
+ /// Collection of parsers;
+ ParserCollection parsers_;
+};
+
/// @brief this class parses a single subnet
///
/// This class parses the whole subnet definition. It creates parsers