|
@@ -13,9 +13,12 @@
|
|
|
// 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>
|
|
@@ -46,12 +49,20 @@ typedef std::map<std::string, ParserFactory*> FactoryMap;
|
|
|
/// 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,
|
|
@@ -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<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
|