From: Marcin Siodelski Date: Fri, 14 Dec 2012 11:50:23 +0000 (+0100) Subject: [2544] Added option config parser classes to DHCPv4. X-Git-Tag: bind10-1.0.0-beta-release~2^2~12 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=165e2775424f0377b5eb8f574d83c0d0dcd67a4b;p=thirdparty%2Fkea.git [2544] Added option config parser classes to DHCPv4. --- diff --git a/src/bin/dhcp4/config_parser.cc b/src/bin/dhcp4/config_parser.cc index 08d16f8bcb..5c9491a4c4 100644 --- a/src/bin/dhcp4/config_parser.cc +++ b/src/bin/dhcp4/config_parser.cc @@ -13,9 +13,12 @@ // PERFORMANCE OF THIS SOFTWARE. #include -#include #include #include +#include +#include +#include +#include #include #include #include @@ -46,12 +49,20 @@ typedef std::map FactoryMap; /// no subnet object created yet to store them. typedef std::vector 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, @@ -451,6 +462,344 @@ private: 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 + name_parser(dynamic_cast(StringParser::Factory(param.first))); + if (name_parser) { + name_parser->setStorage(&string_values_); + parser = name_parser; + } + } else if (param.first == "code") { + boost::shared_ptr + code_parser(dynamic_cast(Uint32Parser::Factory(param.first))); + if (code_parser) { + code_parser->setStorage(&uint32_values_); + parser = code_parser; + } + } else if (param.first == "data") { + boost::shared_ptr + value_parser(dynamic_cast(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::max()) { + isc_throw(Dhcp4ConfigError, "Parser error: value of 'code' must not" + << " exceed " << std::numeric_limits::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 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(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 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