BIND 10 master, updated. 71e25eb81e58a695cf3bad465c4254b13a50696e [master] Merge branch 'trac2317'
BIND 10 source code commits
bind10-changes at lists.isc.org
Tue Jan 15 13:46:54 UTC 2013
The branch, master has been updated
via 71e25eb81e58a695cf3bad465c4254b13a50696e (commit)
via 5a326976867ae6a6f4c98d056b40d36ff1dbac22 (commit)
via 1cee583601a4e084bf54cf90e0d52b79f0783700 (commit)
via 7d705b35fe149cc0c6b93211f2733537b05bd905 (commit)
via 016a19585f32b9bdf57fc5aecdc3b073f3d1a482 (commit)
via c3edda086f948bc9c6bd46c8b9198a2e5261eac2 (commit)
via 6e2a0ba86f0607f3e9822a178a223e8a922de47a (commit)
via ec9d85afe154206e95b683eaa8733e0f453f4090 (commit)
via 0b3abc02d6b943026d5e60f6376d5c20d913bcb7 (commit)
via beef799a018be9b0be103973c9fa477a7d0ca6fb (commit)
via b82c412af1ef36000368f817f25aef23cc36728a (commit)
via adf283b9aa964639693d23a4e809fe67657583fb (commit)
via 5477d36770d789ce36b2a74acedf64cf63e79460 (commit)
via 905a40f2a1c627edd9bbaf26166311ba7e623d0e (commit)
via c924b23383294fe5b7a21aed00e62fbfd8e88d1c (commit)
via f000b6b54d4abfad7abe39ed0b817a29a1f073f0 (commit)
via 6b78fee487016e4920ab469448c559752523d474 (commit)
via db45e761f5f8c140d80d242033642e2d929a2a2d (commit)
via 36d56446c3758b1d8e6c005fabfe8432b9751131 (commit)
via c4904fef78c9bae9fdff9aa9852d7b8dca3a9ba8 (commit)
via e39b61088924933e1335760d21cfa10e0cfd5425 (commit)
via 533c4afdae8ff16eb7d7b1f5529dd572e1098cf3 (commit)
via 4e973e228e15b24d70b36c18f79180833aff1839 (commit)
from 65eb1db45415266f4c3c435b799f066eaf96998b (commit)
Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.
- Log -----------------------------------------------------------------
commit 71e25eb81e58a695cf3bad465c4254b13a50696e
Merge: 65eb1db 5a32697
Author: Marcin Siodelski <marcin at isc.org>
Date: Tue Jan 15 14:28:01 2013 +0100
[master] Merge branch 'trac2317'
Conflicts:
src/bin/dhcp6/dhcp6_srv.cc
src/lib/dhcpsrv/Makefile.am
src/lib/dhcpsrv/cfgmgr.h
-----------------------------------------------------------------------
Summary of changes:
src/bin/dhcp4/config_parser.cc | 641 +++++++++++++++------
src/bin/dhcp4/config_parser.h | 14 -
src/bin/dhcp4/ctrl_dhcp4_srv.cc | 3 +-
src/bin/dhcp4/dhcp4.spec | 60 ++
src/bin/dhcp4/tests/config_parser_unittest.cc | 470 ++++++++++++++-
src/bin/dhcp6/config_parser.cc | 663 ++++++++++++++++------
src/bin/dhcp6/config_parser.h | 14 -
src/bin/dhcp6/ctrl_dhcp6_srv.cc | 3 +-
src/bin/dhcp6/dhcp6.spec | 60 ++
src/bin/dhcp6/dhcp6_srv.cc | 2 +-
src/bin/dhcp6/tests/config_parser_unittest.cc | 560 ++++++++++++++++--
src/bin/dhcp6/tests/dhcp6_srv_unittest.cc | 20 +-
src/lib/dhcp/option_definition.cc | 31 +-
src/lib/dhcp/tests/option_definition_unittest.cc | 50 +-
src/lib/dhcpsrv/Makefile.am | 1 +
src/lib/dhcpsrv/cfgmgr.cc | 23 +-
src/lib/dhcpsrv/cfgmgr.h | 17 +-
src/lib/dhcpsrv/dhcp_config_parser.h | 42 ++
src/lib/dhcpsrv/option_space_container.h | 102 ++++
src/lib/dhcpsrv/subnet.cc | 26 +-
src/lib/dhcpsrv/subnet.h | 10 +-
21 files changed, 2287 insertions(+), 525 deletions(-)
create mode 100644 src/lib/dhcpsrv/option_space_container.h
-----------------------------------------------------------------------
diff --git a/src/bin/dhcp4/config_parser.cc b/src/bin/dhcp4/config_parser.cc
index 383e6b2..40c00bf 100644
--- a/src/bin/dhcp4/config_parser.cc
+++ b/src/bin/dhcp4/config_parser.cc
@@ -19,6 +19,7 @@
#include <dhcp/option_definition.h>
#include <dhcpsrv/cfgmgr.h>
#include <dhcpsrv/dhcp_config_parser.h>
+#include <dhcpsrv/option_space_container.h>
#include <util/encode/hex.h>
#include <util/strutil.h>
#include <boost/foreach.hpp>
@@ -37,6 +38,17 @@ using namespace isc::asiolink;
namespace {
+// Forward declarations of some of the parser classes.
+// They are used to define pointer types for these classes.
+class BooleanParser;
+class StringParser;
+class Uint32Parser;
+
+// Pointers to various parser objects.
+typedef boost::shared_ptr<BooleanParser> BooleanParserPtr;
+typedef boost::shared_ptr<StringParser> StringParserPtr;
+typedef boost::shared_ptr<Uint32Parser> Uint32ParserPtr;
+
/// @brief auxiliary type used for storing element name and its parser
typedef pair<string, ConstElementPtr> ConfigPair;
@@ -55,16 +67,20 @@ typedef std::map<std::string, std::string> StringStorage;
/// @brief Storage for parsed boolean values.
typedef std::map<string, bool> BooleanStorage;
+/// @brief Storage for option definitions.
+typedef OptionSpaceContainer<OptionDefContainer,
+ OptionDefinitionPtr> OptionDefStorage;
+
/// @brief a collection of pools
///
/// That type is used as intermediate storage, when pools are parsed, but there is
/// 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;
+/// Collection of containers holding option spaces. Each container within
+/// a particular option space holds so-called option descriptors.
+typedef OptionSpaceContainer<Subnet::OptionContainer,
+ Subnet::OptionDescriptor> OptionStorage;
/// @brief Global uint32 parameters that will be used as defaults.
Uint32Storage uint32_defaults;
@@ -75,6 +91,9 @@ StringStorage string_defaults;
/// @brief Global storage for options that will be used as defaults.
OptionStorage option_defaults;
+/// @brief Global storage for option definitions.
+OptionDefStorage option_def_intermediate;
+
/// @brief a dummy configuration parser
///
/// It is a debugging parser. It does not configure anything,
@@ -149,7 +168,7 @@ public:
value_(false) {
// Empty parameter name is invalid.
if (param_name_.empty()) {
- isc_throw(isc::dhcp::Dhcp4ConfigError, "parser logic error:"
+ isc_throw(isc::dhcp::DhcpConfigError, "parser logic error:"
<< "empty parameter name provided");
}
}
@@ -160,7 +179,7 @@ public:
///
/// @throw isc::InvalidOperation if a storage has not been set
/// prior to calling this function
- /// @throw isc::dhcp::Dhcp4ConfigError if a provided parameter's
+ /// @throw isc::dhcp::DhcpConfigError if a provided parameter's
/// name is empty.
virtual void build(ConstElementPtr value) {
if (storage_ == NULL) {
@@ -168,7 +187,7 @@ public:
<< " storage for the " << param_name_
<< " value has not been set");
} else if (param_name_.empty()) {
- isc_throw(isc::dhcp::Dhcp4ConfigError, "parser logic error:"
+ isc_throw(isc::dhcp::DhcpConfigError, "parser logic error:"
<< "empty parameter name provided");
}
// The Config Manager checks if user specified a
@@ -233,7 +252,7 @@ public:
param_name_(param_name) {
// Empty parameter name is invalid.
if (param_name_.empty()) {
- isc_throw(Dhcp4ConfigError, "parser logic error:"
+ isc_throw(DhcpConfigError, "parser logic error:"
<< "empty parameter name provided");
}
}
@@ -245,7 +264,7 @@ public:
/// or the parameter name is empty.
virtual void build(ConstElementPtr value) {
if (param_name_.empty()) {
- isc_throw(Dhcp4ConfigError, "parser logic error:"
+ isc_throw(DhcpConfigError, "parser logic error:"
<< "empty parameter name provided");
}
@@ -326,7 +345,7 @@ public:
:storage_(&string_defaults), param_name_(param_name) {
// Empty parameter name is invalid.
if (param_name_.empty()) {
- isc_throw(Dhcp4ConfigError, "parser logic error:"
+ isc_throw(DhcpConfigError, "parser logic error:"
<< "empty parameter name provided");
}
}
@@ -460,7 +479,7 @@ public:
/// interface name.
/// @param pools_list list of pools defined for a subnet
/// @throw InvalidOperation if storage was not specified (setStorage() not called)
- /// @throw Dhcp4ConfigError when pool parsing fails
+ /// @throw DhcpConfigError when pool parsing fails
void build(ConstElementPtr pools_list) {
// setStorage() should have been called before build
if (!pools_) {
@@ -500,7 +519,7 @@ public:
// be checked in Pool4 constructor.
len = boost::lexical_cast<int>(prefix_len);
} catch (...) {
- isc_throw(Dhcp4ConfigError, "Failed to parse pool "
+ isc_throw(DhcpConfigError, "Failed to parse pool "
"definition: " << text_pool->stringValue());
}
@@ -522,7 +541,7 @@ public:
continue;
}
- isc_throw(Dhcp4ConfigError, "Failed to parse pool definition:"
+ isc_throw(DhcpConfigError, "Failed to parse pool definition:"
<< text_pool->stringValue() <<
". Does not contain - (for min-max) nor / (prefix/len)");
}
@@ -571,15 +590,20 @@ private:
///
/// 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 then 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 until 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.
+/// and data carried by the option. The option data can be specified
+/// in one of the two available formats: binary value represented as
+/// a string of hexadecimal digits or a list of comma separated values.
+/// The format being used is controlled by csv-format configuration
+/// parameter. When setting this value to True, the latter format is
+/// used. The subsequent values in the CSV format apply to relevant
+/// option data fields in the configured option. For example the
+/// configuration: "data" : "192.168.2.0, 56, hello world" can be
+/// used to set values for the option comprising IPv4 address,
+/// integer and string data field. Note that order matters. If the
+/// order of values does not match the order of data fields within
+/// an option the configuration will not be accepted. If parsing
+/// is successful then an instance of an option is created and
+/// added to the storage provided by the calling class.
class OptionDataParser : public DhcpConfigParser {
public:
@@ -603,11 +627,10 @@ public:
///
/// @param option_data_entries collection of entries that define value
/// for a particular option.
- /// @throw Dhcp4ConfigError if invalid parameter specified in
+ /// @throw DhcpConfigError 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 "
@@ -615,7 +638,8 @@ public:
}
BOOST_FOREACH(ConfigPair param, option_data_entries->mapValue()) {
ParserPtr parser;
- if (param.first == "name") {
+ if (param.first == "name" || param.first == "data" ||
+ param.first == "space") {
boost::shared_ptr<StringParser>
name_parser(dynamic_cast<StringParser*>(StringParser::factory(param.first)));
if (name_parser) {
@@ -629,13 +653,6 @@ public:
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 if (param.first == "csv-format") {
boost::shared_ptr<BooleanParser>
value_parser(dynamic_cast<BooleanParser*>(BooleanParser::factory(param.first)));
@@ -644,7 +661,7 @@ public:
parser = value_parser;
}
} else {
- isc_throw(Dhcp4ConfigError,
+ isc_throw(DhcpConfigError,
"Parser error: option-data parameter not supported: "
<< param.first);
}
@@ -671,16 +688,21 @@ public:
/// remain un-modified.
virtual void commit() {
if (options_ == NULL) {
- isc_throw(isc::InvalidOperation, "Parser logic error: storage must be set before "
+ 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"
+ 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>();
+ Subnet::OptionContainerPtr options = options_->getItems(option_space_);
+ // The getItems() should never return NULL pointer. If there are no
+ // options configured for the particular option space a pointer
+ // to an empty container should be returned.
+ assert(options);
+ 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.
@@ -690,7 +712,7 @@ public:
idx.erase(range.first, range.second);
}
// Append new option to the main storage.
- options_->push_back(option_descriptor_);
+ options_->addItem(option_descriptor_, option_space_);
}
/// @brief Set storage for the parser.
@@ -718,36 +740,72 @@ private:
/// 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
+ /// @throw DhcpConfigError 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");
+ uint32_t option_code = getParam<uint32_t>("code", uint32_values_);
if (option_code == 0) {
- isc_throw(Dhcp4ConfigError, "Parser error: value of 'code' must not"
+ isc_throw(DhcpConfigError, "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"
+ isc_throw(DhcpConfigError, "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");
+ std::string option_name = getParam<std::string>("name", string_values_);
if (option_name.empty()) {
- isc_throw(Dhcp4ConfigError, "Parser error: option name must not be"
+ isc_throw(DhcpConfigError, "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"
+ isc_throw(DhcpConfigError, "Parser error: option name must not contain"
<< " spaces");
}
+ std::string option_space = getParam<std::string>("space", string_values_);
+ /// @todo Validate option space once #2313 is merged.
+
+ OptionDefinitionPtr def;
+ if (option_space == "dhcp4" &&
+ LibDHCP::isStandardOption(Option::V4, option_code)) {
+ def = LibDHCP::getOptionDef(Option::V4, option_code);
+
+ } else if (option_space == "dhcp6") {
+ isc_throw(DhcpConfigError, "'dhcp6' option space name is reserved"
+ << " for DHCPv6 server");
+ } else {
+ // If we are not dealing with a standard option then we
+ // need to search for its definition among user-configured
+ // options. They are expected to be in the global storage
+ // already.
+ OptionDefContainerPtr defs = option_def_intermediate.getItems(option_space);
+ // The getItems() should never return the NULL pointer. If there are
+ // no option definitions for the particular option space a pointer
+ // to an empty container should be returned.
+ assert(defs);
+ const OptionDefContainerTypeIndex& idx = defs->get<1>();
+ OptionDefContainerTypeRange range = idx.equal_range(option_code);
+ if (std::distance(range.first, range.second) > 0) {
+ def = *range.first;
+ }
+ if (!def) {
+ isc_throw(DhcpConfigError, "definition for the option '"
+ << option_space << "." << option_name
+ << "' having code '" << option_code
+ << "' does not exist");
+ }
+
+ }
+
// Get option data from the configuration database ('data' field).
- const std::string option_data = getStringParam("data");
- const bool csv_format = getBooleanParam("csv-format");
+ const std::string option_data = getParam<std::string>("data", string_values_);
+ const bool csv_format = getParam<bool>("csv-format", boolean_values_);
+
// Transform string of hexadecimal digits into binary format.
std::vector<uint8_t> binary;
std::vector<std::string> data_tokens;
@@ -764,30 +822,15 @@ private:
try {
util::encode::decodeHex(option_data, binary);
} catch (...) {
- isc_throw(Dhcp4ConfigError, "Parser error: option data is not a valid"
+ isc_throw(DhcpConfigError, "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) {
+ if (!def) {
if (csv_format) {
- isc_throw(Dhcp4ConfigError, "the CSV option data format can be"
+ isc_throw(DhcpConfigError, "the CSV option data format can be"
" used to specify values for an option that has a"
" definition. The option with code " << option_code
<< " does not have a definition.");
@@ -806,9 +849,21 @@ private:
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);
+
+ // Option name should match the definition. The option name
+ // may seem to be redundant but in the future we may want
+ // to reference options and definitions using their names
+ // and/or option codes so keeping the option name in the
+ // definition of option value makes sense.
+ if (def->getName() != option_name) {
+ isc_throw(DhcpConfigError, "specified option name '"
+ << option_name << " does not match the "
+ << "option definition: '" << option_space
+ << "." << def->getName() << "'");
+ }
+
+ // Option definition has been found so let's use it to create
+ // an instance of our option.
try {
OptionPtr option = csv_format ?
def->optionFactory(Option::V4, option_code, data_tokens) :
@@ -817,52 +872,14 @@ private:
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 << "): "
+ isc_throw(DhcpConfigError, "option data does not match"
+ << " option definition (space: " << option_space
+ << ", 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);
- }
-
- /// @brief Get a parameter from the boolean values storage.
- ///
- /// @param param_id parameter identifier.
- ///
- /// @throw isc::dhcp::Dhcp6ConfigError if a parameter has not been found.
- /// @return a value of the boolean parameter.
- bool getBooleanParam(const std::string& param_id) const {
- BooleanStorage::const_iterator param = boolean_values_.find(param_id);
- if (param == boolean_values_.end()) {
- isc_throw(isc::dhcp::Dhcp4ConfigError, "parser error: option-data parameter"
- << " '" << param_id << "' not specified");
- }
- return (param->second);
+ // All went good, so we can set the option space name.
+ option_space_ = option_space;
}
/// Storage for uint32 values (e.g. option code).
@@ -876,6 +893,8 @@ private:
OptionStorage* options_;
/// Option descriptor holds newly configured option.
Subnet::OptionDescriptor option_descriptor_;
+ /// Option space name where the option belongs to.
+ std::string option_space_;
};
/// @brief Parser for option data values within a subnet.
@@ -901,7 +920,7 @@ public:
/// 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.
+ /// @throw DhcpConfigError 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"));
@@ -956,6 +975,253 @@ public:
ParserCollection parsers_;
};
+/// @brief Parser for a single option definition.
+///
+/// This parser creates an instance of a single option definition.
+class OptionDefParser: DhcpConfigParser {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// This constructor sets the pointer to the option definitions
+ /// storage to NULL. It must be set to point to the actual storage
+ /// before \ref build is called.
+ OptionDefParser(const std::string&)
+ : storage_(NULL) {
+ }
+
+ /// @brief Parses an entry that describes single option definition.
+ ///
+ /// @param option_def a configuration entry to be parsed.
+ ///
+ /// @throw DhcpConfigError if parsing was unsuccessful.
+ void build(ConstElementPtr option_def) {
+ if (storage_ == NULL) {
+ isc_throw(DhcpConfigError, "parser logic error: storage must be set"
+ " before parsing option definition data");
+ }
+ // Parse the elements that make up the option definition.
+ BOOST_FOREACH(ConfigPair param, option_def->mapValue()) {
+ std::string entry(param.first);
+ ParserPtr parser;
+ if (entry == "name" || entry == "type" ||
+ entry == "record-types" || entry == "space") {
+ StringParserPtr
+ str_parser(dynamic_cast<StringParser*>(StringParser::factory(entry)));
+ if (str_parser) {
+ str_parser->setStorage(&string_values_);
+ parser = str_parser;
+ }
+ } else if (entry == "code") {
+ Uint32ParserPtr
+ code_parser(dynamic_cast<Uint32Parser*>(Uint32Parser::factory(entry)));
+ if (code_parser) {
+ code_parser->setStorage(&uint32_values_);
+ parser = code_parser;
+ }
+ } else if (entry == "array") {
+ BooleanParserPtr
+ array_parser(dynamic_cast<BooleanParser*>(BooleanParser::factory(entry)));
+ if (array_parser) {
+ array_parser->setStorage(&boolean_values_);
+ parser = array_parser;
+ }
+ } else {
+ isc_throw(DhcpConfigError, "invalid parameter: " << entry);
+ }
+
+ parser->build(param.second);
+ parser->commit();
+ }
+
+ // Create an instance of option definition.
+ createOptionDef();
+
+ // Get all items we collected so far for the particular option space.
+ OptionDefContainerPtr defs = storage_->getItems(option_space_name_);
+ // Check if there are any items with option code the same as the
+ // one specified for the definition we are now creating.
+ const OptionDefContainerTypeIndex& idx = defs->get<1>();
+ const OptionDefContainerTypeRange& range =
+ idx.equal_range(option_definition_->getCode());
+ // If there are any items with this option code already we need
+ // to issue an error because we don't allow duplicates for
+ // option definitions within an option space.
+ if (std::distance(range.first, range.second) > 0) {
+ isc_throw(DhcpConfigError, "duplicated option definition for"
+ << " code '" << option_definition_->getCode() << "'");
+ }
+ }
+
+ /// @brief Stores the parsed option definition in a storage.
+ void commit() {
+ // @todo validate option space name once 2313 is merged.
+ if (storage_ && option_definition_) {
+ storage_->addItem(option_definition_, option_space_name_);
+ }
+ }
+
+ /// @brief Sets a pointer to the data store.
+ ///
+ /// The newly created instance of an option definition will be
+ /// added to the data store given by the argument.
+ ///
+ /// @param storage pointer to the data store where the option definition
+ /// will be added to.
+ void setStorage(OptionDefStorage* storage) {
+ storage_ = storage;
+ }
+
+private:
+
+ /// @brief Create option definition from the parsed parameters.
+ void createOptionDef() {
+ // Get the option space name and validate it.
+ std::string space = getParam<std::string>("space", string_values_);
+ // @todo uncomment the code below when the #2313 is merged.
+ /* if (!OptionSpace::validateName()) {
+ isc_throw(DhcpConfigError, "invalid option space name '"
+ << space << "'");
+ } */
+
+ // Get other parameters that are needed to create the
+ // option definition.
+ std::string name = getParam<std::string>("name", string_values_);
+ uint32_t code = getParam<uint32_t>("code", uint32_values_);
+ std::string type = getParam<std::string>("type", string_values_);
+ bool array_type = getParam<bool>("array", boolean_values_);
+
+ OptionDefinitionPtr def(new OptionDefinition(name, code,
+ type, array_type));
+ // The record-types field may carry a list of comma separated names
+ // of data types that form a record.
+ std::string record_types = getParam<std::string>("record-types",
+ string_values_);
+ // Split the list of record types into tokens.
+ std::vector<std::string> record_tokens =
+ isc::util::str::tokens(record_types, ",");
+ // Iterate over each token and add a record type into
+ // option definition.
+ BOOST_FOREACH(std::string record_type, record_tokens) {
+ try {
+ boost::trim(record_type);
+ if (!record_type.empty()) {
+ def->addRecordField(record_type);
+ }
+ } catch (const Exception& ex) {
+ isc_throw(DhcpConfigError, "invalid record type values"
+ << " specified for the option definition: "
+ << ex.what());
+ }
+ }
+
+ // Check the option definition parameters are valid.
+ try {
+ def->validate();
+ } catch (const isc::Exception& ex) {
+ isc_throw(DhcpConfigError, "invalid option definition"
+ << " parameters: " << ex.what());
+ }
+ // Option definition has been created successfully.
+ option_space_name_ = space;
+ option_definition_ = def;
+ }
+
+ /// Instance of option definition being created by this parser.
+ OptionDefinitionPtr option_definition_;
+ /// Name of the space the option definition belongs to.
+ std::string option_space_name_;
+
+ /// Pointer to a storage where the option definition will be
+ /// added when \ref commit is called.
+ OptionDefStorage* storage_;
+
+ /// Storage for boolean values.
+ BooleanStorage boolean_values_;
+ /// Storage for string values.
+ StringStorage string_values_;
+ /// Storage for uint32 values.
+ Uint32Storage uint32_values_;
+};
+
+/// @brief Parser for a list of option definitions.
+///
+/// This parser iterates over all configuration entries that define
+/// option definitions and creates instances of these definitions.
+/// If the parsing is successful, the collection of created definitions
+/// is put into the provided storage.
+class OptionDefListParser : DhcpConfigParser {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// This constructor initializes the pointer to option definitions
+ /// storage to NULL value. This pointer has to be set to point to
+ /// the actual storage before the \ref build function is called.
+ OptionDefListParser(const std::string&) {
+ }
+
+ /// @brief Parse configuration entries.
+ ///
+ /// This function parses configuration entries and creates instances
+ /// of option definitions.
+ ///
+ /// @param option_def_list pointer to an element that holds entries
+ /// that define option definitions.
+ /// @throw DhcpConfigError if configuration parsing fails.
+ void build(ConstElementPtr option_def_list) {
+ // Clear existing items in the global storage.
+ // We are going to replace all of them.
+ option_def_intermediate.clearItems();
+
+ if (!option_def_list) {
+ isc_throw(DhcpConfigError, "parser error: a pointer to a list of"
+ << " option definitions is NULL");
+ }
+
+ BOOST_FOREACH(ConstElementPtr option_def, option_def_list->listValue()) {
+ boost::shared_ptr<OptionDefParser>
+ parser(new OptionDefParser("single-option-def"));
+ parser->setStorage(&option_def_intermediate);
+ parser->build(option_def);
+ parser->commit();
+ }
+ }
+
+ /// @brief Stores option definitions in the CfgMgr.
+ void commit() {
+
+ CfgMgr& cfg_mgr = CfgMgr::instance();
+
+ cfg_mgr.deleteOptionDefs();
+
+ // We need to move option definitions from the temporary
+ // storage to the global storage.
+ BOOST_FOREACH(std::string space_name,
+ option_def_intermediate.getOptionSpaceNames()) {
+
+ BOOST_FOREACH(OptionDefinitionPtr def,
+ *option_def_intermediate.getItems(space_name)) {
+ // All option definitions should be initialized to non-NULL
+ // values. The validation is expected to be made by the
+ // OptionDefParser when creating an option definition.
+ assert(def);
+ cfg_mgr.addOptionDef(def, space_name);
+ }
+ }
+ }
+
+ /// @brief Create an OptionDefListParser object.
+ ///
+ /// @param param_name configuration entry holding option definitions.
+ ///
+ /// @return OptionDefListParser object.
+ static DhcpConfigParser* factory(const std::string& param_name) {
+ return (new OptionDefListParser(param_name));
+ }
+
+};
+
/// @brief this class parses a single subnet
///
/// This class parses the whole subnet definition. It creates parsers
@@ -997,7 +1263,7 @@ public:
// Appropriate parsers are created in the createSubnet6ConfigParser
// and they should be limited to those that we check here for. Thus,
// if we fail to find a matching parser here it is a programming error.
- isc_throw(Dhcp4ConfigError, "failed to find suitable parser");
+ isc_throw(DhcpConfigError, "failed to find suitable parser");
}
}
// In order to create new subnet we need to get the data out
@@ -1022,7 +1288,7 @@ public:
/// storing the values that are actually consumed here. Pool definitions
/// created in other parsers are used here and added to newly created Subnet4
/// objects. Subnet4 are then added to DHCP CfgMgr.
- /// @throw Dhcp4ConfigError if there are any issues encountered during commit
+ /// @throw DhcpConfigError if there are any issues encountered during commit
void commit() {
if (subnet_) {
CfgMgr::instance().addSubnet4(subnet_);
@@ -1067,11 +1333,11 @@ private:
/// @brief Create a new subnet using a data from child parsers.
///
- /// @throw isc::dhcp::Dhcp4ConfigError if subnet configuration parsing failed.
+ /// @throw isc::dhcp::DhcpConfigError if subnet configuration parsing failed.
void createSubnet() {
StringStorage::const_iterator it = string_values_.find("subnet");
if (it == string_values_.end()) {
- isc_throw(Dhcp4ConfigError,
+ isc_throw(DhcpConfigError,
"Mandatory subnet definition in subnet missing");
}
// Remove any spaces or tabs.
@@ -1086,7 +1352,7 @@ private:
// need to get all characters preceding "/".
size_t pos = subnet_txt.find("/");
if (pos == string::npos) {
- isc_throw(Dhcp4ConfigError,
+ isc_throw(DhcpConfigError,
"Invalid subnet syntax (prefix/len expected):" << it->second);
}
@@ -1117,39 +1383,57 @@ private:
subnet_->addPool(*it);
}
- Subnet::OptionContainerPtr options = subnet_->getOptionDescriptors("dhcp4");
- const Subnet::OptionContainerTypeIndex& idx = options->get<1>();
-
- // Add subnet specific options.
- BOOST_FOREACH(Subnet::OptionDescriptor desc, options_) {
- Subnet::OptionContainerTypeRange range = idx.equal_range(desc.option->getType());
- if (std::distance(range.first, range.second) > 0) {
- LOG_WARN(dhcp4_logger, DHCP4_CONFIG_OPTION_DUPLICATE)
- .arg(desc.option->getType()).arg(addr.toText());
+ // We are going to move configured options to the Subnet object.
+ // Configured options reside in the container where options
+ // are grouped by space names. Thus we need to get all space names
+ // and iterate over all options that belong to them.
+ BOOST_FOREACH(std::string option_space, options_.getOptionSpaceNames()) {
+ // Get all options within a particular option space.
+ BOOST_FOREACH(Subnet::OptionDescriptor desc,
+ *options_.getItems(option_space)) {
+ // The pointer should be non-NULL. The validation is expected
+ // to be performed by the OptionDataParser before adding an
+ // option descriptor to the container.
+ assert(desc.option);
+ // We want to check whether an option with the particular
+ // option code has been already added. If so, we want
+ // to issue a warning.
+ Subnet::OptionDescriptor existing_desc =
+ subnet_->getOptionDescriptor("option_space",
+ desc.option->getType());
+ if (existing_desc.option) {
+ LOG_WARN(dhcp4_logger, DHCP4_CONFIG_OPTION_DUPLICATE)
+ .arg(desc.option->getType()).arg(addr.toText());
+ }
+ // In any case, we add the option to the subnet.
+ subnet_->addOption(desc.option, false, option_space);
}
- subnet_->addOption(desc.option, false, "dhcp4");
}
// Check all global options and add them to the subnet object if
// they have been configured in the global scope. If they have been
// configured in the subnet scope we don't add global option because
// the one configured in the subnet scope always takes precedence.
- BOOST_FOREACH(Subnet::OptionDescriptor desc, option_defaults) {
- // Get all options specified locally in the subnet and having
- // code equal to global option's code.
- Subnet::OptionContainerPtr options = subnet_->getOptionDescriptors("dhcp4");
- const Subnet::OptionContainerTypeIndex& idx = options->get<1>();
- Subnet::OptionContainerTypeRange range = idx.equal_range(desc.option->getType());
- // @todo: In the future we will be searching for options using either
- // an option code or namespace. Currently we have only the option
- // code available so if there is at least one option found with the
- // specific code we don't add the globally configured option.
- // @todo with this code the first globally configured option
- // with the given code will be added to a subnet. We may
- // want to issue a warning about dropping the configuration of
- // a global option if one already exsists.
- if (std::distance(range.first, range.second) == 0) {
- subnet_->addOption(desc.option, false, "dhcp4");
+ BOOST_FOREACH(std::string option_space,
+ option_defaults.getOptionSpaceNames()) {
+ // Get all global options for the particular option space.
+ BOOST_FOREACH(Subnet::OptionDescriptor desc,
+ *option_defaults.getItems(option_space)) {
+ // The pointer should be non-NULL. The validation is expected
+ // to be performed by the OptionDataParser before adding an
+ // option descriptor to the container.
+ assert(desc.option);
+ // Check if the particular option has been already added.
+ // This would mean that it has been configured in the
+ // subnet scope. Since option values configured in the
+ // subnet scope take precedence over globally configured
+ // values we don't add option from the global storage
+ // if there is one already.
+ Subnet::OptionDescriptor existing_desc =
+ subnet_->getOptionDescriptor(option_space, desc.option->getType());
+ if (!existing_desc.option) {
+ subnet_->addOption(desc.option, false, option_space);
+ }
}
}
}
@@ -1177,13 +1461,13 @@ private:
// return new DebugParser(config_id);
isc_throw(NotImplemented,
- "Parser error: Subnet4 parameter not supported: "
+ "parser error: Subnet4 parameter not supported: "
<< config_id);
}
return (f->second(config_id));
}
- /// @brief returns value for a given parameter (after using inheritance)
+ /// @brief Returns value for a given parameter (after using inheritance)
///
/// This method implements inheritance. For a given parameter name, it first
/// checks if there is a global value for it and overwrites it with specific
@@ -1191,7 +1475,7 @@ private:
///
/// @param name name of the parameter
/// @return triplet with the parameter name
- /// @throw Dhcp4ConfigError when requested parameter is not present
+ /// @throw DhcpConfigError when requested parameter is not present
Triplet<uint32_t> getParam(const std::string& name) {
uint32_t value = 0;
bool found = false;
@@ -1210,7 +1494,7 @@ private:
if (found) {
return (Triplet<uint32_t>(value));
} else {
- isc_throw(Dhcp4ConfigError, "Mandatory parameter " << name
+ isc_throw(DhcpConfigError, "Mandatory parameter " << name
<< " missing (no global default and no subnet-"
<< "specific value)");
}
@@ -1319,6 +1603,7 @@ DhcpConfigParser* createGlobalDhcp4ConfigParser(const std::string& config_id) {
factories["interface"] = InterfaceListConfigParser::factory;
factories["subnet4"] = Subnets4ListConfigParser::factory;
factories["option-data"] = OptionDataListParser::factory;
+ factories["option-def"] = OptionDefListParser::factory;
factories["version"] = StringParser::factory;
FactoryMap::iterator f = factories.find(config_id);
@@ -1334,7 +1619,7 @@ DhcpConfigParser* createGlobalDhcp4ConfigParser(const std::string& config_id) {
}
isc::data::ConstElementPtr
-configureDhcp4Server(Dhcpv4Srv& , ConstElementPtr config_set) {
+configureDhcp4Server(Dhcpv4Srv&, ConstElementPtr config_set) {
if (!config_set) {
ConstElementPtr answer = isc::config::createAnswer(1,
string("Can't parse NULL config"));
@@ -1347,15 +1632,15 @@ configureDhcp4Server(Dhcpv4Srv& , ConstElementPtr config_set) {
LOG_DEBUG(dhcp4_logger, DBG_DHCP4_COMMAND, DHCP4_CONFIG_START).arg(config_set->str());
// Some of the values specified in the configuration depend on
- // other values. Typically, the values in the subnet6 structure
- // depend on the global values. Thus we need to make sure that
- // the global values are processed first and that they can be
- // accessed by the subnet6 parsers. We separate parsers that
- // should process data first (independent_parsers) from those
- // that must process data when the independent data is already
- // processed (dependent_parsers).
+ // other values. Typically, the values in the subnet4 structure
+ // depend on the global values. Also, option values configuration
+ // must be performed after the option definitions configurations.
+ // Thus we group parsers and will fire them in the right order:
+ // all parsers other than subnet4 and option-data parser,
+ // option-data parser, subnet4 parser.
ParserCollection independent_parsers;
- ParserCollection dependent_parsers;
+ ParserPtr subnet_parser;
+ ParserPtr option_parser;
// The subnet parsers implement data inheritance by directly
// accessing global storage. For this reason the global data
@@ -1367,6 +1652,7 @@ configureDhcp4Server(Dhcpv4Srv& , ConstElementPtr config_set) {
Uint32Storage uint32_local(uint32_defaults);
StringStorage string_local(string_defaults);
OptionStorage option_local(option_defaults);
+ OptionDefStorage option_def_local(option_def_intermediate);
// answer will hold the result.
ConstElementPtr answer;
@@ -1375,12 +1661,20 @@ configureDhcp4Server(Dhcpv4Srv& , ConstElementPtr config_set) {
bool rollback = false;
try {
+ // Make parsers grouping.
+ const std::map<std::string, ConstElementPtr>& values_map =
+ config_set->mapValue();
+ BOOST_FOREACH(ConfigPair config_pair, values_map) {
+ ParserPtr parser(createGlobalDhcp4ConfigParser(config_pair.first));
+ if (config_pair.first == "subnet4") {
+ subnet_parser = parser;
- // Iterate over all independent parsers first (all but subnet4)
- // and try to parse the data.
- BOOST_FOREACH(ConfigPair config_pair, config_set->mapValue()) {
- if (config_pair.first != "subnet4") {
- ParserPtr parser(createGlobalDhcp4ConfigParser(config_pair.first));
+ } else if (config_pair.first == "option-data") {
+ option_parser = parser;
+
+ } else {
+ // Those parsers should be started before other
+ // parsers so we can call build straight away.
independent_parsers.push_back(parser);
parser->build(config_pair.second);
// The commit operation here may modify the global storage
@@ -1390,13 +1684,19 @@ configureDhcp4Server(Dhcpv4Srv& , ConstElementPtr config_set) {
}
}
- // Process dependent configuration data.
- BOOST_FOREACH(ConfigPair config_pair, config_set->mapValue()) {
- if (config_pair.first == "subnet4") {
- ParserPtr parser(createGlobalDhcp4ConfigParser(config_pair.first));
- dependent_parsers.push_back(parser);
- parser->build(config_pair.second);
- }
+ // The option values parser is the next one to be run.
+ std::map<std::string, ConstElementPtr>::const_iterator option_config =
+ values_map.find("option-data");
+ if (option_config != values_map.end()) {
+ option_parser->build(option_config->second);
+ option_parser->commit();
+ }
+
+ // The subnet parser is the last one to be run.
+ std::map<std::string, ConstElementPtr>::const_iterator subnet_config =
+ values_map.find("subnet4");
+ if (subnet_config != values_map.end()) {
+ subnet_parser->build(subnet_config->second);
}
} catch (const isc::Exception& ex) {
@@ -1421,8 +1721,8 @@ configureDhcp4Server(Dhcpv4Srv& , ConstElementPtr config_set) {
// This operation should be exception safe but let's make sure.
if (!rollback) {
try {
- BOOST_FOREACH(ParserPtr parser, dependent_parsers) {
- parser->commit();
+ if (subnet_parser) {
+ subnet_parser->commit();
}
}
catch (const isc::Exception& ex) {
@@ -1444,6 +1744,7 @@ configureDhcp4Server(Dhcpv4Srv& , ConstElementPtr config_set) {
std::swap(uint32_defaults, uint32_local);
std::swap(string_defaults, string_local);
std::swap(option_defaults, option_local);
+ std::swap(option_def_intermediate, option_def_local);
return (answer);
}
diff --git a/src/bin/dhcp4/config_parser.h b/src/bin/dhcp4/config_parser.h
index 3247241..4f1ea32 100644
--- a/src/bin/dhcp4/config_parser.h
+++ b/src/bin/dhcp4/config_parser.h
@@ -28,20 +28,6 @@ namespace dhcp {
class Dhcpv4Srv;
-/// An exception that is thrown if an error occurs while configuring an
-/// @c Dhcpv4Srv object.
-class Dhcp4ConfigError : public isc::Exception {
-public:
-
- /// @brief constructor
- ///
- /// @param file name of the file, where exception occurred
- /// @param line line of the file, where exception occurred
- /// @param what text description of the issue that caused exception
- Dhcp4ConfigError(const char* file, size_t line, const char* what)
- : isc::Exception(file, line, what) {}
-};
-
/// @brief Configure DHCPv4 server (@c Dhcpv4Srv) with a set of configuration values.
///
/// This function parses configuration information stored in @c config_set
diff --git a/src/bin/dhcp4/ctrl_dhcp4_srv.cc b/src/bin/dhcp4/ctrl_dhcp4_srv.cc
index eb5d482..435a25e 100644
--- a/src/bin/dhcp4/ctrl_dhcp4_srv.cc
+++ b/src/bin/dhcp4/ctrl_dhcp4_srv.cc
@@ -19,6 +19,7 @@
#include <cc/session.h>
#include <config/ccsession.h>
#include <dhcp/iface_mgr.h>
+#include <dhcpsrv/dhcp_config_parser.h>
#include <dhcp4/ctrl_dhcp4_srv.h>
#include <dhcp4/dhcp4_log.h>
#include <dhcp4/spec_config.h>
@@ -121,7 +122,7 @@ void ControlledDhcpv4Srv::establishSession() {
try {
configureDhcp4Server(*this, config_session_->getFullConfig());
- } catch (const Dhcp4ConfigError& ex) {
+ } catch (const DhcpConfigError& ex) {
LOG_ERROR(dhcp4_logger, DHCP4_CONFIG_LOAD_FAIL).arg(ex.what());
}
diff --git a/src/bin/dhcp4/dhcp4.spec b/src/bin/dhcp4/dhcp4.spec
index 13acf83..c2b755c 100644
--- a/src/bin/dhcp4/dhcp4.spec
+++ b/src/bin/dhcp4/dhcp4.spec
@@ -34,6 +34,56 @@
"item_default": 4000
},
+ { "item_name": "option-def",
+ "item_type": "list",
+ "item_optional": false,
+ "item_default": [],
+ "list_item_spec":
+ {
+ "item_name": "single-option-def",
+ "item_type": "map",
+ "item_optional": false,
+ "item_default": {},
+ "map_item_spec": [
+ {
+ "item_name": "name",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": ""
+ },
+
+ { "item_name": "code",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ },
+
+ { "item_name": "type",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": "",
+ },
+
+ { "item_name": "array",
+ "item_type": "boolean",
+ "item_optional": false,
+ "item_default": False
+ },
+
+ { "item_name": "record_types",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": "",
+ },
+
+ { "item_name": "space",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": ""
+ } ]
+ }
+ },
+
{ "item_name": "option-data",
"item_type": "list",
"item_optional": false,
@@ -66,6 +116,11 @@
"item_type": "boolean",
"item_optional": false,
"item_default": False
+ },
+ { "item_name": "space",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": "dhcp4"
} ]
}
},
@@ -151,6 +206,11 @@
"item_type": "boolean",
"item_optional": false,
"item_default": False
+ },
+ { "item_name": "space",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": "dhcp4"
} ]
}
} ]
diff --git a/src/bin/dhcp4/tests/config_parser_unittest.cc b/src/bin/dhcp4/tests/config_parser_unittest.cc
index c6af514..ba14edf 100644
--- a/src/bin/dhcp4/tests/config_parser_unittest.cc
+++ b/src/bin/dhcp4/tests/config_parser_unittest.cc
@@ -78,8 +78,8 @@ public:
/// @brief Create the simple configuration with single option.
///
/// This function allows to set one of the parameters that configure
- /// option value. These parameters are: "name", "code", "data" and
- /// "csv-format".
+ /// option value. These parameters are: "name", "code", "data",
+ /// "csv-format" and "space".
///
/// @param param_value string holiding option parameter value to be
/// injected into the configuration string.
@@ -92,21 +92,31 @@ public:
std::map<std::string, std::string> params;
if (parameter == "name") {
params["name"] = param_value;
+ params["space"] = "dhcp4";
+ params["code"] = "56";
+ params["data"] = "AB CDEF0105";
+ params["csv-format"] = "False";
+ } else if (parameter == "space") {
+ params["name"] = "dhcp-message";
+ params["space"] = param_value;
params["code"] = "56";
params["data"] = "AB CDEF0105";
params["csv-format"] = "False";
} else if (parameter == "code") {
- params["name"] = "option_foo";
+ params["name"] = "dhcp-message";
+ params["space"] = "dhcp4";
params["code"] = param_value;
params["data"] = "AB CDEF0105";
params["csv-format"] = "False";
} else if (parameter == "data") {
- params["name"] = "option_foo";
+ params["name"] = "dhcp-message";
+ params["space"] = "dhcp4";
params["code"] = "56";
params["data"] = param_value;
params["csv-format"] = "False";
} else if (parameter == "csv-format") {
- params["name"] = "option_foo";
+ params["name"] = "dhcp-message";
+ params["space"] = "dhcp4";
params["code"] = "56";
params["data"] = "AB CDEF0105";
params["csv-format"] = param_value;
@@ -142,6 +152,8 @@ public:
}
if (param.first == "name") {
stream << "\"name\": \"" << param.second << "\"";
+ } else if (param.first == "space") {
+ stream << "\"space\": \"" << param.second << "\"";
} else if (param.first == "code") {
stream << "\"code\": " << param.second << "";
} else if (param.first == "data") {
@@ -234,6 +246,7 @@ public:
"\"renew-timer\": 1000, "
"\"valid-lifetime\": 4000, "
"\"subnet4\": [ ], "
+ "\"option-def\": [ ], "
"\"option-data\": [ ] }";
try {
@@ -436,6 +449,363 @@ TEST_F(Dhcp4ParserTest, poolPrefixLen) {
EXPECT_EQ(4000, subnet->getValid());
}
+// The goal of this test is to check whether an option definition
+// that defines an option carrying an IPv4 address can be created.
+TEST_F(Dhcp4ParserTest, optionDefIpv4Address) {
+
+ // Configuration string.
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 100,"
+ " \"type\": \"ipv4-address\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"isc\""
+ " } ]"
+ "}";
+ ElementPtr json = Element::fromJSON(config);
+
+ // Make sure that the particular option definition does not exist.
+ OptionDefinitionPtr def = CfgMgr::instance().getOptionDef("isc", 100);
+ ASSERT_FALSE(def);
+
+ // Use the configuration string to create new option definition.
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+ ASSERT_TRUE(status);
+
+ // The option definition should now be available in the CfgMgr.
+ def = CfgMgr::instance().getOptionDef("isc", 100);
+ ASSERT_TRUE(def);
+
+ // Verify that the option definition data is valid.
+ EXPECT_EQ("foo", def->getName());
+ EXPECT_EQ(100, def->getCode());
+ EXPECT_FALSE(def->getArrayType());
+ EXPECT_EQ(OPT_IPV4_ADDRESS_TYPE, def->getType());
+}
+
+// The goal of this test is to check whether an option definiiton
+// that defines an option carrying a record of data fields can
+// be created.
+TEST_F(Dhcp4ParserTest, optionDefRecord) {
+
+ // Configuration string.
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 100,"
+ " \"type\": \"record\","
+ " \"array\": False,"
+ " \"record-types\": \"uint16, ipv4-address, ipv6-address, string\","
+ " \"space\": \"isc\""
+ " } ]"
+ "}";
+ ElementPtr json = Element::fromJSON(config);
+
+ // Make sure that the particular option definition does not exist.
+ OptionDefinitionPtr def = CfgMgr::instance().getOptionDef("isc", 100);
+ ASSERT_FALSE(def);
+
+ // Use the configuration string to create new option definition.
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+ ASSERT_TRUE(status);
+ checkResult(status, 0);
+
+ // The option definition should now be available in the CfgMgr.
+ def = CfgMgr::instance().getOptionDef("isc", 100);
+ ASSERT_TRUE(def);
+
+ // Check the option data.
+ EXPECT_EQ("foo", def->getName());
+ EXPECT_EQ(100, def->getCode());
+ EXPECT_EQ(OPT_RECORD_TYPE, def->getType());
+ EXPECT_FALSE(def->getArrayType());
+
+ // The option comprises the record of data fields. Verify that all
+ // fields are present and they are of the expected types.
+ const OptionDefinition::RecordFieldsCollection& record_fields =
+ def->getRecordFields();
+ ASSERT_EQ(4, record_fields.size());
+ EXPECT_EQ(OPT_UINT16_TYPE, record_fields[0]);
+ EXPECT_EQ(OPT_IPV4_ADDRESS_TYPE, record_fields[1]);
+ EXPECT_EQ(OPT_IPV6_ADDRESS_TYPE, record_fields[2]);
+ EXPECT_EQ(OPT_STRING_TYPE, record_fields[3]);
+}
+
+// The goal of this test is to verify that multiple option definitions
+// can be created.
+TEST_F(Dhcp4ParserTest, optionDefMultiple) {
+ // Configuration string.
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 100,"
+ " \"type\": \"uint32\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"isc\""
+ " },"
+ " {"
+ " \"name\": \"foo-2\","
+ " \"code\": 101,"
+ " \"type\": \"ipv4-address\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"isc\""
+ " } ]"
+ "}";
+ ElementPtr json = Element::fromJSON(config);
+
+ // Make sure that the option definitions do not exist yet.
+ ASSERT_FALSE(CfgMgr::instance().getOptionDef("isc", 100));
+ ASSERT_FALSE(CfgMgr::instance().getOptionDef("isc", 101));
+
+ // Use the configuration string to create new option definitions.
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+ ASSERT_TRUE(status);
+ checkResult(status, 0);
+
+ // Check the first definition we have created.
+ OptionDefinitionPtr def1 = CfgMgr::instance().getOptionDef("isc", 100);
+ ASSERT_TRUE(def1);
+
+ // Check the option data.
+ EXPECT_EQ("foo", def1->getName());
+ EXPECT_EQ(100, def1->getCode());
+ EXPECT_EQ(OPT_UINT32_TYPE, def1->getType());
+ EXPECT_FALSE(def1->getArrayType());
+
+ // Check the second option definition we have created.
+ OptionDefinitionPtr def2 = CfgMgr::instance().getOptionDef("isc", 101);
+ ASSERT_TRUE(def2);
+
+ // Check the option data.
+ EXPECT_EQ("foo-2", def2->getName());
+ EXPECT_EQ(101, def2->getCode());
+ EXPECT_EQ(OPT_IPV4_ADDRESS_TYPE, def2->getType());
+ EXPECT_FALSE(def2->getArrayType());
+}
+
+// The goal of this test is to verify that the duplicated option
+// definition is not accepted.
+TEST_F(Dhcp4ParserTest, optionDefDuplicate) {
+
+ // Configuration string. Both option definitions have
+ // the same code and belong to the same option space.
+ // This configuration should not be accepted.
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 100,"
+ " \"type\": \"uint32\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"isc\""
+ " },"
+ " {"
+ " \"name\": \"foo-2\","
+ " \"code\": 100,"
+ " \"type\": \"ipv4-address\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"isc\""
+ " } ]"
+ "}";
+ ElementPtr json = Element::fromJSON(config);
+
+ // Make sure that the option definition does not exist yet.
+ ASSERT_FALSE(CfgMgr::instance().getOptionDef("isc", 100));
+
+ // Use the configuration string to create new option definitions.
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+ ASSERT_TRUE(status);
+ checkResult(status, 1);
+}
+
+// The goal of this test is to verify that the option definition
+// comprising an array of uint32 values can be created.
+TEST_F(Dhcp4ParserTest, optionDefArray) {
+
+ // Configuration string. Created option definition should
+ // comprise an array of uint32 values.
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 100,"
+ " \"type\": \"uint32\","
+ " \"array\": True,"
+ " \"record-types\": \"\","
+ " \"space\": \"isc\""
+ " } ]"
+ "}";
+ ElementPtr json = Element::fromJSON(config);
+
+ // Make sure that the particular option definition does not exist.
+ OptionDefinitionPtr def = CfgMgr::instance().getOptionDef("isc", 100);
+ ASSERT_FALSE(def);
+
+ // Use the configuration string to create new option definition.
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+ ASSERT_TRUE(status);
+ checkResult(status, 0);
+
+ // The option definition should now be available in the CfgMgr.
+ def = CfgMgr::instance().getOptionDef("isc", 100);
+ ASSERT_TRUE(def);
+
+ // Check the option data.
+ EXPECT_EQ("foo", def->getName());
+ EXPECT_EQ(100, def->getCode());
+ EXPECT_EQ(OPT_UINT32_TYPE, def->getType());
+ EXPECT_TRUE(def->getArrayType());
+}
+
+/// The purpose of this test is to verify that the option definition
+/// with invalid name is not accepted.
+TEST_F(Dhcp4ParserTest, optionDefInvalidName) {
+ // Configuration string. The option name is invalid as it
+ // contains the % character.
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"invalid%name\","
+ " \"code\": 100,"
+ " \"type\": \"string\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"isc\""
+ " } ]"
+ "}";
+ ElementPtr json = Element::fromJSON(config);
+
+ // Use the configuration string to create new option definition.
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+ ASSERT_TRUE(status);
+ // Expecting parsing error (error code 1).
+ checkResult(status, 1);
+}
+
+/// The purpose of this test is to verify that the option definition
+/// with invalid type is not accepted.
+TEST_F(Dhcp4ParserTest, optionDefInvalidType) {
+ // Configuration string. The option type is invalid. It is
+ // "sting" instead of "string".
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 100,"
+ " \"type\": \"sting\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"isc\""
+ " } ]"
+ "}";
+ ElementPtr json = Element::fromJSON(config);
+
+ // Use the configuration string to create new option definition.
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+ ASSERT_TRUE(status);
+ // Expecting parsing error (error code 1).
+ checkResult(status, 1);
+}
+
+/// The purpose of this test is to verify that the option definition
+/// with invalid type is not accepted.
+TEST_F(Dhcp4ParserTest, optionDefInvalidRecordType) {
+ // Configuration string. The third of the record fields
+ // is invalid. It is "sting" instead of "string".
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 100,"
+ " \"type\": \"record\","
+ " \"array\": False,"
+ " \"record-types\": \"uint32,uint8,sting\","
+ " \"space\": \"isc\""
+ " } ]"
+ "}";
+ ElementPtr json = Element::fromJSON(config);
+
+ // Use the configuration string to create new option definition.
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+ ASSERT_TRUE(status);
+ // Expecting parsing error (error code 1).
+ checkResult(status, 1);
+}
+
+
+/// The purpose of this test is to verify that it is not allowed
+/// to override the standard option (that belongs to dhcp4 option
+/// space) and that it is allowed to define option in the dhcp4
+/// option space that has a code which is not used by any of the
+/// standard options.
+TEST_F(Dhcp4ParserTest, optionStandardDefOverride) {
+
+ // Configuration string. The option code 109 is unassigned
+ // so it can be used for a custom option definition in
+ // dhcp4 option space.
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 109,"
+ " \"type\": \"string\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"dhcp4\""
+ " } ]"
+ "}";
+ ElementPtr json = Element::fromJSON(config);
+
+ OptionDefinitionPtr def = CfgMgr::instance().getOptionDef("dhcp4", 109);
+ ASSERT_FALSE(def);
+
+ // Use the configuration string to create new option definition.
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+ ASSERT_TRUE(status);
+ checkResult(status, 0);
+
+ // The option definition should now be available in the CfgMgr.
+ def = CfgMgr::instance().getOptionDef("dhcp4", 109);
+ ASSERT_TRUE(def);
+
+ // Check the option data.
+ EXPECT_EQ("foo", def->getName());
+ EXPECT_EQ(109, def->getCode());
+ EXPECT_EQ(OPT_STRING_TYPE, def->getType());
+ EXPECT_FALSE(def->getArrayType());
+
+ // The combination of option space and code is
+ // invalid. The 'dhcp4' option space groups
+ // standard options and the code 100 is reserved
+ // for one of them.
+ config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 100,"
+ " \"type\": \"string\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"dhcp4\""
+ " } ]"
+ "}";
+ json = Element::fromJSON(config);
+
+ // Use the configuration string to create new option definition.
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+ ASSERT_TRUE(status);
+ // Expecting parsing error (error code 1).
+ checkResult(status, 1);
+}
+
// Goal of this test is to verify that global option
// data is configured for the subnet if the subnet
// configuration does not include options configuration.
@@ -445,13 +815,15 @@ TEST_F(Dhcp4ParserTest, optionDataDefaults) {
"\"rebind-timer\": 2000,"
"\"renew-timer\": 1000,"
"\"option-data\": [ {"
- " \"name\": \"option_foo\","
+ " \"name\": \"dhcp-message\","
+ " \"space\": \"dhcp4\","
" \"code\": 56,"
" \"data\": \"AB CDEF0105\","
" \"csv-format\": False"
" },"
" {"
- " \"name\": \"option_foo2\","
+ " \"name\": \"default-ip-ttl\","
+ " \"space\": \"dhcp4\","
" \"code\": 23,"
" \"data\": \"01\","
" \"csv-format\": False"
@@ -500,6 +872,74 @@ TEST_F(Dhcp4ParserTest, optionDataDefaults) {
testOption(*range.first, 23, foo2_expected, sizeof(foo2_expected));
}
+/// The goal of this test is to verify that two options having the same
+/// option code can be added to different option spaces.
+TEST_F(Dhcp4ParserTest, optionDataTwoSpaces) {
+
+ // This configuration string is to configure two options
+ // sharing the code 56 and having different definitions
+ // and belonging to the different option spaces.
+ // The option definition must be provided for the
+ // option that belongs to the 'isc' option space.
+ // The definition is not required for the option that
+ // belongs to the 'dhcp4' option space as it is the
+ // standard option.
+ string config = "{ \"interface\": [ \"all\" ],"
+ "\"rebind-timer\": 2000,"
+ "\"renew-timer\": 1000,"
+ "\"option-data\": [ {"
+ " \"name\": \"dhcp-message\","
+ " \"space\": \"dhcp4\","
+ " \"code\": 56,"
+ " \"data\": \"AB CDEF0105\","
+ " \"csv-format\": False"
+ " },"
+ " {"
+ " \"name\": \"foo\","
+ " \"space\": \"isc\","
+ " \"code\": 56,"
+ " \"data\": \"1234\","
+ " \"csv-format\": True"
+ " } ],"
+ "\"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 56,"
+ " \"type\": \"uint32\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"isc\""
+ " } ],"
+ "\"subnet4\": [ { "
+ " \"pool\": [ \"192.0.2.1 - 192.0.2.100\" ],"
+ " \"subnet\": \"192.0.2.0/24\""
+ " } ]"
+ "}";
+
+ ConstElementPtr status;
+
+ ElementPtr json = Element::fromJSON(config);
+
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+ ASSERT_TRUE(status);
+ checkResult(status, 0);
+
+ // Options should be now availabe for the subnet.
+ Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.200"));
+ ASSERT_TRUE(subnet);
+ // Try to get the option from the space dhcp4.
+ Subnet::OptionDescriptor desc1 = subnet->getOptionDescriptor("dhcp4", 56);
+ ASSERT_TRUE(desc1.option);
+ EXPECT_EQ(56, desc1.option->getType());
+ // Try to get the option from the space isc.
+ Subnet::OptionDescriptor desc2 = subnet->getOptionDescriptor("isc", 56);
+ ASSERT_TRUE(desc2.option);
+ EXPECT_EQ(56, desc1.option->getType());
+ // Try to get the non-existing option from the non-existing
+ // option space and expect that option is not returned.
+ Subnet::OptionDescriptor desc3 = subnet->getOptionDescriptor("non-existing", 56);
+ ASSERT_FALSE(desc3.option);
+}
+
// Goal of this test is to verify options configuration
// for a single subnet. In particular this test checks
// that local options configuration overrides global
@@ -510,7 +950,8 @@ TEST_F(Dhcp4ParserTest, optionDataInSingleSubnet) {
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
"\"option-data\": [ {"
- " \"name\": \"option_foo\","
+ " \"name\": \"dhcp-message\","
+ " \"space\": \"dhcp4\","
" \"code\": 56,"
" \"data\": \"AB\","
" \"csv-format\": False"
@@ -519,13 +960,15 @@ TEST_F(Dhcp4ParserTest, optionDataInSingleSubnet) {
" \"pool\": [ \"192.0.2.1 - 192.0.2.100\" ],"
" \"subnet\": \"192.0.2.0/24\", "
" \"option-data\": [ {"
- " \"name\": \"option_foo\","
+ " \"name\": \"dhcp-message\","
+ " \"space\": \"dhcp4\","
" \"code\": 56,"
" \"data\": \"AB CDEF0105\","
" \"csv-format\": False"
" },"
" {"
- " \"name\": \"option_foo2\","
+ " \"name\": \"default-ip-ttl\","
+ " \"space\": \"dhcp4\","
" \"code\": 23,"
" \"data\": \"01\","
" \"csv-format\": False"
@@ -582,7 +1025,8 @@ TEST_F(Dhcp4ParserTest, optionDataInMultipleSubnets) {
" \"pool\": [ \"192.0.2.1 - 192.0.2.100\" ],"
" \"subnet\": \"192.0.2.0/24\", "
" \"option-data\": [ {"
- " \"name\": \"option_foo\","
+ " \"name\": \"dhcp-message\","
+ " \"space\": \"dhcp4\","
" \"code\": 56,"
" \"data\": \"0102030405060708090A\","
" \"csv-format\": False"
@@ -592,7 +1036,8 @@ TEST_F(Dhcp4ParserTest, optionDataInMultipleSubnets) {
" \"pool\": [ \"192.0.3.101 - 192.0.3.150\" ],"
" \"subnet\": \"192.0.3.0/24\", "
" \"option-data\": [ {"
- " \"name\": \"option_foo2\","
+ " \"name\": \"default-ip-ttl\","
+ " \"space\": \"dhcp4\","
" \"code\": 23,"
" \"data\": \"FF\","
" \"csv-format\": False"
@@ -745,6 +1190,7 @@ TEST_F(Dhcp4ParserTest, stdOptionData) {
ConstElementPtr x;
std::map<std::string, std::string> params;
params["name"] = "nis-servers";
+ params["space"] = "dhcp4";
// Option code 41 means nis-servers.
params["code"] = "41";
// Specify option values in a CSV (user friendly) format.
diff --git a/src/bin/dhcp6/config_parser.cc b/src/bin/dhcp6/config_parser.cc
index c17bf64..5b03ab8 100644
--- a/src/bin/dhcp6/config_parser.cc
+++ b/src/bin/dhcp6/config_parser.cc
@@ -40,12 +40,24 @@
#include <stdint.h>
using namespace std;
+using namespace isc;
using namespace isc::data;
using namespace isc::dhcp;
using namespace isc::asiolink;
namespace {
+// Forward declarations of some of the parser classes.
+// They are used to define pointer types for these classes.
+class BooleanParser;
+class StringParser;
+class Uint32Parser;
+
+// Pointers to various parser objects.
+typedef boost::shared_ptr<BooleanParser> BooleanParserPtr;
+typedef boost::shared_ptr<StringParser> StringParserPtr;
+typedef boost::shared_ptr<Uint32Parser> Uint32ParserPtr;
+
/// @brief Auxiliary type used for storing an element name and its parser.
typedef pair<string, ConstElementPtr> ConfigPair;
@@ -64,18 +76,20 @@ typedef std::map<string, uint32_t> Uint32Storage;
/// @brief Collection of elements that store string values.
typedef std::map<string, string> StringStorage;
+/// @brief Storage for option definitions.
+typedef OptionSpaceContainer<OptionDefContainer,
+ OptionDefinitionPtr> OptionDefStorage;
+
/// @brief Collection of address pools.
///
/// This type is used as intermediate storage, when pools are parsed, but there is
/// no subnet object created yet to store them.
typedef std::vector<isc::dhcp::Pool6Ptr> PoolStorage;
-/// @brief Collection of option descriptors.
-///
-/// This container allows to search options using an option code
-/// or a persistency flag. This is useful when merging existing
-/// options with newly configured options.
-typedef isc::dhcp::Subnet::OptionContainer OptionStorage;
+/// Collection of containers holding option spaces. Each container within
+/// a particular option space holds so-called option descriptors.
+typedef OptionSpaceContainer<Subnet::OptionContainer,
+ Subnet::OptionDescriptor> OptionStorage;
/// @brief Global uint32 parameters that will be used as defaults.
Uint32Storage uint32_defaults;
@@ -86,6 +100,10 @@ StringStorage string_defaults;
/// @brief Global storage for options that will be used as defaults.
OptionStorage option_defaults;
+/// @brief Global storage for option definitions.
+OptionDefStorage option_def_intermediate;
+
+
/// @brief a dummy configuration parser
///
/// This is a debugging parser. It does not configure anything,
@@ -161,7 +179,7 @@ public:
value_(false) {
// Empty parameter name is invalid.
if (param_name_.empty()) {
- isc_throw(isc::dhcp::Dhcp6ConfigError, "parser logic error:"
+ isc_throw(isc::dhcp::DhcpConfigError, "parser logic error:"
<< "empty parameter name provided");
}
}
@@ -172,7 +190,7 @@ public:
///
/// @throw isc::InvalidOperation if a storage has not been set
/// prior to calling this function
- /// @throw isc::dhcp::Dhcp6ConfigError if a provided parameter's
+ /// @throw isc::dhcp::DhcpConfigError if a provided parameter's
/// name is empty.
virtual void build(ConstElementPtr value) {
if (storage_ == NULL) {
@@ -180,7 +198,7 @@ public:
<< " storage for the " << param_name_
<< " value has not been set");
} else if (param_name_.empty()) {
- isc_throw(isc::dhcp::Dhcp6ConfigError, "parser logic error:"
+ isc_throw(isc::dhcp::DhcpConfigError, "parser logic error:"
<< "empty parameter name provided");
}
// The Config Manager checks if user specified a
@@ -249,7 +267,7 @@ public:
param_name_(param_name) {
// Empty parameter name is invalid.
if (param_name_.empty()) {
- isc_throw(Dhcp6ConfigError, "parser logic error:"
+ isc_throw(DhcpConfigError, "parser logic error:"
<< "empty parameter name provided");
}
}
@@ -257,11 +275,11 @@ public:
/// @brief Parses configuration configuration parameter as uint32_t.
///
/// @param value pointer to the content of parsed values
- /// @throw isc::dhcp::Dhcp6ConfigError if failed to parse
+ /// @throw isc::dhcp::DhcpConfigError if failed to parse
/// the configuration parameter as uint32_t value.
virtual void build(ConstElementPtr value) {
if (param_name_.empty()) {
- isc_throw(isc::dhcp::Dhcp6ConfigError, "parser logic error:"
+ isc_throw(isc::dhcp::DhcpConfigError, "parser logic error:"
<< "empty parameter name provided");
}
@@ -291,7 +309,7 @@ public:
}
// Invalid value provided.
if (parse_error) {
- isc_throw(isc::dhcp::Dhcp6ConfigError, "Failed to parse value " << value->str()
+ isc_throw(isc::dhcp::DhcpConfigError, "Failed to parse value " << value->str()
<< " as unsigned 32-bit integer.");
}
}
@@ -352,7 +370,7 @@ public:
param_name_(param_name) {
// Empty parameter name is invalid.
if (param_name_.empty()) {
- isc_throw(Dhcp6ConfigError, "parser logic error:"
+ isc_throw(DhcpConfigError, "parser logic error:"
<< "empty parameter name provided");
}
}
@@ -362,10 +380,10 @@ public:
/// Parses configuration parameter's value as string.
///
/// @param value pointer to the content of parsed values
- /// @throws Dhcp6ConfigError if the parsed parameter's name is empty.
+ /// @throws DhcpConfigError if the parsed parameter's name is empty.
virtual void build(ConstElementPtr value) {
if (param_name_.empty()) {
- isc_throw(isc::dhcp::Dhcp6ConfigError, "parser logic error:"
+ isc_throw(isc::dhcp::DhcpConfigError, "parser logic error:"
<< "empty parameter name provided");
}
value_ = value->str();
@@ -528,7 +546,7 @@ public:
// be checked in Pool6 constructor.
len = boost::lexical_cast<int>(prefix_len);
} catch (...) {
- isc_throw(Dhcp6ConfigError, "failed to parse pool "
+ isc_throw(DhcpConfigError, "failed to parse pool "
"definition: " << text_pool->stringValue());
}
@@ -550,7 +568,7 @@ public:
continue;
}
- isc_throw(Dhcp6ConfigError, "failed to parse pool definition:"
+ isc_throw(DhcpConfigError, "failed to parse pool definition:"
<< text_pool->stringValue() <<
". Does not contain - (for min-max) nor / (prefix/len)");
}
@@ -600,15 +618,20 @@ private:
///
/// 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 then 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 until 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.
+/// and data carried by the option. The option data can be specified
+/// in one of the two available formats: binary value represented as
+/// a string of hexadecimal digits or a list of comma separated values.
+/// The format being used is controlled by csv-format configuration
+/// parameter. When setting this value to True, the latter format is
+/// used. The subsequent values in the CSV format apply to relevant
+/// option data fields in the configured option. For example the
+/// configuration: "data" : "192.168.2.0, 56, hello world" can be
+/// used to set values for the option comprising IPv4 address,
+/// integer and string data field. Note that order matters. If the
+/// order of values does not match the order of data fields within
+/// an option the configuration will not be accepted. If parsing
+/// is successful then an instance of an option is created and
+/// added to the storage provided by the calling class.
class OptionDataParser : public DhcpConfigParser {
public:
@@ -632,11 +655,10 @@ public:
///
/// @param option_data_entries collection of entries that define value
/// for a particular option.
- /// @throw Dhcp6ConfigError if invalid parameter specified in
+ /// @throw DhcpConfigError 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) {
@@ -645,7 +667,8 @@ public:
}
BOOST_FOREACH(ConfigPair param, option_data_entries->mapValue()) {
ParserPtr parser;
- if (param.first == "name") {
+ if (param.first == "name" || param.first == "data" ||
+ param.first == "space") {
boost::shared_ptr<StringParser>
name_parser(dynamic_cast<StringParser*>(StringParser::factory(param.first)));
if (name_parser) {
@@ -659,13 +682,6 @@ public:
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 if (param.first == "csv-format") {
boost::shared_ptr<BooleanParser>
value_parser(dynamic_cast<BooleanParser*>(BooleanParser::factory(param.first)));
@@ -674,7 +690,7 @@ public:
parser = value_parser;
}
} else {
- isc_throw(Dhcp6ConfigError,
+ isc_throw(DhcpConfigError,
"parser error: option-data parameter not supported: "
<< param.first);
}
@@ -710,17 +726,22 @@ public:
" thus there is nothing to commit. Has build() been called?");
}
uint16_t opt_type = option_descriptor_.option->getType();
- isc::dhcp::Subnet::OptionContainerTypeIndex& idx = options_->get<1>();
+ Subnet::OptionContainerPtr options = options_->getItems(option_space_);
+ // The getItems() should never return NULL pointer. If there are no
+ // options configured for the particular option space a pointer
+ // to an empty container should be returned.
+ assert(options);
+ 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.
- isc::dhcp::Subnet::OptionContainerTypeRange range =
+ 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_);
+ options_->addItem(option_descriptor_, option_space_);
}
/// @brief Set storage for the parser.
@@ -748,39 +769,77 @@ private:
/// is intitialized but this check is not needed here because it is done
/// in the \ref build function.
///
- /// @throw Dhcp6ConfigError if parameters provided in the configuration
+ /// @throw DhcpConfigError 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");
+ uint32_t option_code = getParam<uint32_t>("code", uint32_values_);
if (option_code == 0) {
- isc_throw(Dhcp6ConfigError, "Parser error: value of 'code' must not"
+ isc_throw(DhcpConfigError, "Parser error: value of 'code' must not"
<< " be equal to zero. Option code '0' is reserved in"
<< " DHCPv6.");
} else if (option_code > std::numeric_limits<uint16_t>::max()) {
- isc_throw(Dhcp6ConfigError, "Parser error: value of 'code' must not"
+ isc_throw(DhcpConfigError, "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");
+ std::string option_name = getParam<std::string>("name", string_values_);
if (option_name.empty()) {
- isc_throw(Dhcp6ConfigError, "Parser error: option name must not be"
+ isc_throw(DhcpConfigError, "Parser error: option name must not be"
<< " empty");
} else if (option_name.find(" ") != std::string::npos) {
- isc_throw(Dhcp6ConfigError, "Parser error: option name must not contain"
+ isc_throw(DhcpConfigError, "Parser error: option name must not contain"
<< " spaces");
}
+ std::string option_space = getParam<std::string>("space", string_values_);
+ /// @todo Validate option space once #2313 is merged.
+
+ OptionDefinitionPtr def;
+ if (option_space == "dhcp6" &&
+ LibDHCP::isStandardOption(Option::V6, option_code)) {
+ def = LibDHCP::getOptionDef(Option::V6, option_code);
+
+ } else if (option_space == "dhcp4") {
+ isc_throw(DhcpConfigError, "'dhcp4' option space name is reserved"
+ << " for DHCPv4 server");
+ } else {
+ // If we are not dealing with a standard option then we
+ // need to search for its definition among user-configured
+ // options. They are expected to be in the global storage
+ // already.
+ OptionDefContainerPtr defs = option_def_intermediate.getItems(option_space);
+ // The getItems() should never return the NULL pointer. If there are
+ // no option definitions for the particular option space a pointer
+ // to an empty container should be returned.
+ assert(defs);
+ const OptionDefContainerTypeIndex& idx = defs->get<1>();
+ OptionDefContainerTypeRange range = idx.equal_range(option_code);
+ if (std::distance(range.first, range.second) > 0) {
+ def = *range.first;
+ }
+ if (!def) {
+ isc_throw(DhcpConfigError, "definition for the option '"
+ << option_space << "." << option_name
+ << "' having code '" << option_code
+ << "' does not exist");
+ }
+
+ }
+
// Get option data from the configuration database ('data' field).
- const std::string option_data = getStringParam("data");
- const bool csv_format = getBooleanParam("csv-format");
+ const std::string option_data = getParam<std::string>("data", string_values_);
+ const bool csv_format = getParam<bool>("csv-format", boolean_values_);
+
+ // Transform string of hexadecimal digits into binary format.
std::vector<uint8_t> binary;
std::vector<std::string> data_tokens;
+
if (csv_format) {
// If the option data is specified as a string of comma
// separated values then we need to split this string into
@@ -791,36 +850,22 @@ private:
// Otherwise, the option data is specified as a string of
// hexadecimal digits that we have to turn into binary format.
try {
- isc::util::encode::decodeHex(option_data, binary);
+ util::encode::decodeHex(option_data, binary);
} catch (...) {
- isc_throw(Dhcp6ConfigError, "Parser error: option data is not a valid"
+ isc_throw(DhcpConfigError, "Parser error: option data is not a valid"
<< " string of hexadecimal digits: " << option_data);
}
}
- // Get all existing DHCPv6 option definitions. The one that matches
- // our option will be picked and used to create it.
- OptionDefContainer option_defs = LibDHCP::getOptionDefs(Option::V6);
- // 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(Dhcp6ConfigError, "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) {
+ if (!def) {
if (csv_format) {
- isc_throw(Dhcp6ConfigError, "the CSV option data format can be"
+ isc_throw(DhcpConfigError, "the CSV option data format can be"
" used to specify values for an option that has a"
" definition. The option with code " << option_code
<< " does not have a definition.");
}
+
// @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
@@ -834,9 +879,21 @@ private:
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);
+
+ // Option name should match the definition. The option name
+ // may seem to be redundant but in the future we may want
+ // to reference options and definitions using their names
+ // and/or option codes so keeping the option name in the
+ // definition of option value makes sense.
+ if (def->getName() != option_name) {
+ isc_throw(DhcpConfigError, "specified option name '"
+ << option_name << " does not match the "
+ << "option definition: '" << option_space
+ << "." << def->getName() << "'");
+ }
+
+ // Option definition has been found so let's use it to create
+ // an instance of our option.
try {
OptionPtr option = csv_format ?
def->optionFactory(Option::V6, option_code, data_tokens) :
@@ -845,56 +902,14 @@ private:
option_descriptor_.option = option;
option_descriptor_.persistent = false;
} catch (const isc::Exception& ex) {
- isc_throw(Dhcp6ConfigError, "Parser error: option data does not match"
- << " option definition (code " << option_code << "): "
+ isc_throw(DhcpConfigError, "option data does not match"
+ << " option definition (space: " << option_space
+ << ", code: " << option_code << "): "
<< ex.what());
}
}
- }
-
- /// @brief Get a parameter from the strings storage.
- ///
- /// @param param_id parameter identifier.
- ///
- /// @throw Dhcp6ConfigError if a parameter has not been found.
- /// @return a value of the string parameter.
- 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(isc::dhcp::Dhcp6ConfigError, "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 Dhcp6ConfigError if a parameter has not been found.
- /// @return a value of the uint32_t parameter.
- 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(isc::dhcp::Dhcp6ConfigError, "parser error: option-data parameter"
- << " '" << param_id << "' not specified");
- }
- return (param->second);
- }
-
- /// @brief Get a parameter from the boolean values storage.
- ///
- /// @param param_id parameter identifier.
- ///
- /// @throw isc::dhcp::Dhcp6ConfigError if a parameter has not been found.
- /// @return a value of the boolean parameter.
- bool getBooleanParam(const std::string& param_id) const {
- BooleanStorage::const_iterator param = boolean_values_.find(param_id);
- if (param == boolean_values_.end()) {
- isc_throw(isc::dhcp::Dhcp6ConfigError, "parser error: option-data parameter"
- << " '" << param_id << "' not specified");
- }
- return (param->second);
+ // All went good, so we can set the option space name.
+ option_space_ = option_space;
}
/// Storage for uint32 values (e.g. option code).
@@ -908,6 +923,8 @@ private:
OptionStorage* options_;
/// Option descriptor holds newly configured option.
isc::dhcp::Subnet::OptionDescriptor option_descriptor_;
+ /// Option space name where the option belongs to.
+ std::string option_space_;
};
/// @brief Parser for option data values within a subnet.
@@ -933,7 +950,7 @@ public:
/// for options within a single subnet and creates options' instances.
///
/// @param option_data_list pointer to a list of options' data sets.
- /// @throw Dhcp6ConfigError if option parsing failed.
+ /// @throw DhcpConfigError 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"));
@@ -988,6 +1005,253 @@ public:
ParserCollection parsers_;
};
+/// @brief Parser for a single option definition.
+///
+/// This parser creates an instance of a single option definition.
+class OptionDefParser: DhcpConfigParser {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// This constructor sets the pointer to the option definitions
+ /// storage to NULL. It must be set to point to the actual storage
+ /// before \ref build is called.
+ OptionDefParser(const std::string&)
+ : storage_(NULL) {
+ }
+
+ /// @brief Parses an entry that describes single option definition.
+ ///
+ /// @param option_def a configuration entry to be parsed.
+ ///
+ /// @throw DhcpConfigError if parsing was unsuccessful.
+ void build(ConstElementPtr option_def) {
+ if (storage_ == NULL) {
+ isc_throw(DhcpConfigError, "parser logic error: storage must be set"
+ " before parsing option definition data");
+ }
+ // Parse the elements that make up the option definition.
+ BOOST_FOREACH(ConfigPair param, option_def->mapValue()) {
+ std::string entry(param.first);
+ ParserPtr parser;
+ if (entry == "name" || entry == "type" ||
+ entry == "record-types" || entry == "space") {
+ StringParserPtr
+ str_parser(dynamic_cast<StringParser*>(StringParser::factory(entry)));
+ if (str_parser) {
+ str_parser->setStorage(&string_values_);
+ parser = str_parser;
+ }
+ } else if (entry == "code") {
+ Uint32ParserPtr
+ code_parser(dynamic_cast<Uint32Parser*>(Uint32Parser::factory(entry)));
+ if (code_parser) {
+ code_parser->setStorage(&uint32_values_);
+ parser = code_parser;
+ }
+ } else if (entry == "array") {
+ BooleanParserPtr
+ array_parser(dynamic_cast<BooleanParser*>(BooleanParser::factory(entry)));
+ if (array_parser) {
+ array_parser->setStorage(&boolean_values_);
+ parser = array_parser;
+ }
+ } else {
+ isc_throw(DhcpConfigError, "invalid parameter: " << entry);
+ }
+
+ parser->build(param.second);
+ parser->commit();
+ }
+
+ // Create an instance of option definition.
+ createOptionDef();
+
+ // Get all items we collected so far for the particular option space.
+ OptionDefContainerPtr defs = storage_->getItems(option_space_name_);
+ // Check if there are any items with option code the same as the
+ // one specified for the definition we are now creating.
+ const OptionDefContainerTypeIndex& idx = defs->get<1>();
+ const OptionDefContainerTypeRange& range =
+ idx.equal_range(option_definition_->getCode());
+ // If there are any items with this option code already we need
+ // to issue an error because we don't allow duplicates for
+ // option definitions within an option space.
+ if (std::distance(range.first, range.second) > 0) {
+ isc_throw(DhcpConfigError, "duplicated option definition for"
+ << " code '" << option_definition_->getCode() << "'");
+ }
+ }
+
+ /// @brief Stores the parsed option definition in the data store.
+ void commit() {
+ // @todo validate option space name once 2313 is merged.
+ if (storage_ && option_definition_) {
+ storage_->addItem(option_definition_, option_space_name_);
+ }
+ }
+
+ /// @brief Sets a pointer to the data store.
+ ///
+ /// The newly created instance of an option definition will be
+ /// added to the data store given by the argument.
+ ///
+ /// @param storage pointer to the data store where the option definition
+ /// will be added to.
+ void setStorage(OptionDefStorage* storage) {
+ storage_ = storage;
+ }
+
+private:
+
+ /// @brief Create option definition from the parsed parameters.
+ void createOptionDef() {
+ // Get the option space name and validate it.
+ std::string space = getParam<std::string>("space", string_values_);
+ // @todo uncomment the code below when the #2313 is merged.
+ /* if (!OptionSpace::validateName()) {
+ isc_throw(DhcpConfigError, "invalid option space name '"
+ << space << "'");
+ } */
+
+ // Get other parameters that are needed to create the
+ // option definition.
+ std::string name = getParam<std::string>("name", string_values_);
+ uint32_t code = getParam<uint32_t>("code", uint32_values_);
+ std::string type = getParam<std::string>("type", string_values_);
+ bool array_type = getParam<bool>("array", boolean_values_);
+
+ OptionDefinitionPtr def(new OptionDefinition(name, code,
+ type, array_type));
+ // The record-types field may carry a list of comma separated names
+ // of data types that form a record.
+ std::string record_types = getParam<std::string>("record-types",
+ string_values_);
+ // Split the list of record types into tokens.
+ std::vector<std::string> record_tokens =
+ isc::util::str::tokens(record_types, ",");
+ // Iterate over each token and add a record type into
+ // option definition.
+ BOOST_FOREACH(std::string record_type, record_tokens) {
+ try {
+ boost::trim(record_type);
+ if (!record_type.empty()) {
+ def->addRecordField(record_type);
+ }
+ } catch (const Exception& ex) {
+ isc_throw(DhcpConfigError, "invalid record type values"
+ << " specified for the option definition: "
+ << ex.what());
+ }
+ }
+
+ // Check the option definition parameters are valid.
+ try {
+ def->validate();
+ } catch (const isc::Exception& ex) {
+ isc_throw(DhcpConfigError, "invalid option definition"
+ << " parameters: " << ex.what());
+ }
+ // Option definition has been created successfully.
+ option_space_name_ = space;
+ option_definition_ = def;
+ }
+
+ /// Instance of option definition being created by this parser.
+ OptionDefinitionPtr option_definition_;
+ /// Name of the space the option definition belongs to.
+ std::string option_space_name_;
+
+ /// Pointer to a storage where the option definition will be
+ /// added when \ref commit is called.
+ OptionDefStorage* storage_;
+
+ /// Storage for boolean values.
+ BooleanStorage boolean_values_;
+ /// Storage for string values.
+ StringStorage string_values_;
+ /// Storage for uint32 values.
+ Uint32Storage uint32_values_;
+};
+
+/// @brief Parser for a list of option definitions.
+///
+/// This parser iterates over all configuration entries that define
+/// option definitions and creates instances of these definitions.
+/// If the parsing is successful, the collection of created definitions
+/// is put into the provided storage.
+class OptionDefListParser : DhcpConfigParser {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// This constructor initializes the pointer to option definitions
+ /// storage to NULL value. This pointer has to be set to point to
+ /// the actual storage before the \ref build function is called.
+ OptionDefListParser(const std::string&) {
+ }
+
+ /// @brief Parse configuration entries.
+ ///
+ /// This function parses configuration entries and creates instances
+ /// of option definitions.
+ ///
+ /// @param option_def_list pointer to an element that holds entries
+ /// that define option definitions.
+ /// @throw DhcpConfigError if configuration parsing fails.
+ void build(ConstElementPtr option_def_list) {
+ // Clear existing items in the global storage.
+ // We are going to replace all of them.
+ option_def_intermediate.clearItems();
+
+ if (!option_def_list) {
+ isc_throw(DhcpConfigError, "parser error: a pointer to a list of"
+ << " option definitions is NULL");
+ }
+
+ BOOST_FOREACH(ConstElementPtr option_def, option_def_list->listValue()) {
+ boost::shared_ptr<OptionDefParser>
+ parser(new OptionDefParser("single-option-def"));
+ parser->setStorage(&option_def_intermediate);
+ parser->build(option_def);
+ parser->commit();
+ }
+ }
+
+ /// @brief Stores option definitions in the CfgMgr.
+ void commit() {
+
+ CfgMgr& cfg_mgr = CfgMgr::instance();
+
+ cfg_mgr.deleteOptionDefs();
+
+ // We need to move option definitions from the temporary
+ // storage to the global storage.
+ BOOST_FOREACH(std::string space_name,
+ option_def_intermediate.getOptionSpaceNames()) {
+
+ BOOST_FOREACH(OptionDefinitionPtr def,
+ *option_def_intermediate.getItems(space_name)) {
+ // All option definitions should be initialized to non-NULL
+ // values. The validation is expected to be made by the
+ // OptionDefParser when creating an option definition.
+ assert(def);
+ cfg_mgr.addOptionDef(def, space_name);
+ }
+ }
+ }
+
+ /// @brief Create an OptionDefListParser object.
+ ///
+ /// @param param_name configuration entry holding option definitions.
+ ///
+ /// @return OptionDefListParser object.
+ static DhcpConfigParser* factory(const std::string& param_name) {
+ return (new OptionDefListParser(param_name));
+ }
+
+};
+
/// @brief this class parses a single subnet
///
/// This class parses the whole subnet definition. It creates parsers
@@ -1005,7 +1269,7 @@ public:
///
/// @param subnet pointer to the content of subnet definition
///
- /// @throw isc::Dhcp6ConfigError if subnet configuration parsing failed.
+ /// @throw isc::DhcpConfigError if subnet configuration parsing failed.
void build(ConstElementPtr subnet) {
BOOST_FOREACH(ConfigPair param, subnet->mapValue()) {
@@ -1031,7 +1295,7 @@ public:
// Appropriate parsers are created in the createSubnet6ConfigParser
// and they should be limited to those that we check here for. Thus,
// if we fail to find a matching parser here it is a programming error.
- isc_throw(Dhcp6ConfigError, "failed to find suitable parser");
+ isc_throw(DhcpConfigError, "failed to find suitable parser");
}
}
@@ -1096,13 +1360,13 @@ private:
/// @brief Create a new subnet using a data from child parsers.
///
- /// @throw isc::dhcp::Dhcp6ConfigError if subnet configuration parsing failed.
+ /// @throw isc::dhcp::DhcpConfigError if subnet configuration parsing failed.
void createSubnet() {
-
+
// Find a subnet string.
StringStorage::const_iterator it = string_values_.find("subnet");
if (it == string_values_.end()) {
- isc_throw(Dhcp6ConfigError,
+ isc_throw(DhcpConfigError,
"Mandatory subnet definition in subnet missing");
}
// Remove any spaces or tabs.
@@ -1116,7 +1380,7 @@ private:
// need to get all characters preceding "/".
size_t pos = subnet_txt.find("/");
if (pos == string::npos) {
- isc_throw(Dhcp6ConfigError,
+ isc_throw(DhcpConfigError,
"Invalid subnet syntax (prefix/len expected):" << it->second);
}
@@ -1151,39 +1415,57 @@ private:
subnet_->addPool(*it);
}
- Subnet::OptionContainerPtr options = subnet_->getOptionDescriptors("dhcp6");
- const Subnet::OptionContainerTypeIndex& idx = options->get<1>();
-
- // Add subnet specific options.
- BOOST_FOREACH(Subnet::OptionDescriptor desc, options_) {
- Subnet::OptionContainerTypeRange range = idx.equal_range(desc.option->getType());
- if (std::distance(range.first, range.second) > 0) {
- LOG_WARN(dhcp6_logger, DHCP6_CONFIG_OPTION_DUPLICATE)
- .arg(desc.option->getType()).arg(addr.toText());
+ // We are going to move configured options to the Subnet object.
+ // Configured options reside in the container where options
+ // are grouped by space names. Thus we need to get all space names
+ // and iterate over all options that belong to them.
+ BOOST_FOREACH(std::string option_space, options_.getOptionSpaceNames()) {
+ // Get all options within a particular option space.
+ BOOST_FOREACH(Subnet::OptionDescriptor desc,
+ *options_.getItems(option_space)) {
+ // The pointer should be non-NULL. The validation is expected
+ // to be performed by the OptionDataParser before adding an
+ // option descriptor to the container.
+ assert(desc.option);
+ // We want to check whether an option with the particular
+ // option code has been already added. If so, we want
+ // to issue a warning.
+ Subnet::OptionDescriptor existing_desc =
+ subnet_->getOptionDescriptor("option_space",
+ desc.option->getType());
+ if (existing_desc.option) {
+ LOG_WARN(dhcp6_logger, DHCP6_CONFIG_OPTION_DUPLICATE)
+ .arg(desc.option->getType()).arg(addr.toText());
+ }
+ // In any case, we add the option to the subnet.
+ subnet_->addOption(desc.option, false, option_space);
}
- subnet_->addOption(desc.option, false, "dhcp6");
}
// Check all global options and add them to the subnet object if
// they have been configured in the global scope. If they have been
// configured in the subnet scope we don't add global option because
// the one configured in the subnet scope always takes precedence.
- BOOST_FOREACH(Subnet::OptionDescriptor desc, option_defaults) {
- // Get all options specified locally in the subnet and having
- // code equal to global option's code.
- Subnet::OptionContainerPtr options = subnet_->getOptionDescriptors("dhcp6");
- const Subnet::OptionContainerTypeIndex& idx = options->get<1>();
- Subnet::OptionContainerTypeRange range = idx.equal_range(desc.option->getType());
- // @todo: In the future we will be searching for options using either
- // an option code or namespace. Currently we have only the option
- // code available so if there is at least one option found with the
- // specific code we don't add the globally configured option.
- // @todo with this code the first globally configured option
- // with the given code will be added to a subnet. We may
- // want to issue a warning about dropping the configuration of
- // a global option if one already exsists.
- if (std::distance(range.first, range.second) == 0) {
- subnet_->addOption(desc.option, false, "dhcp6");
+ BOOST_FOREACH(std::string option_space,
+ option_defaults.getOptionSpaceNames()) {
+ // Get all global options for the particular option space.
+ BOOST_FOREACH(Subnet::OptionDescriptor desc,
+ *option_defaults.getItems(option_space)) {
+ // The pointer should be non-NULL. The validation is expected
+ // to be performed by the OptionDataParser before adding an
+ // option descriptor to the container.
+ assert(desc.option);
+ // Check if the particular option has been already added.
+ // This would mean that it has been configured in the
+ // subnet scope. Since option values configured in the
+ // subnet scope take precedence over globally configured
+ // values we don't add option from the global storage
+ // if there is one already.
+ Subnet::OptionDescriptor existing_desc =
+ subnet_->getOptionDescriptor(option_space, desc.option->getType());
+ if (!existing_desc.option) {
+ subnet_->addOption(desc.option, false, option_space);
+ }
}
}
}
@@ -1193,7 +1475,7 @@ private:
/// @param config_id name od the entry
///
/// @return parser object for specified entry name
- /// @throw isc::dhcp::Dhcp6ConfigError if trying to create a parser
+ /// @throw isc::dhcp::DhcpConfigError if trying to create a parser
/// for unknown config element
DhcpConfigParser* createSubnet6ConfigParser(const std::string& config_id) {
FactoryMap factories;
@@ -1211,7 +1493,7 @@ private:
// Used for debugging only.
// return new DebugParser(config_id);
- isc_throw(isc::dhcp::Dhcp6ConfigError,
+ isc_throw(isc::dhcp::DhcpConfigError,
"parser error: subnet6 parameter not supported: "
<< config_id);
}
@@ -1226,7 +1508,7 @@ private:
///
/// @param name name of the parameter
/// @return triplet with the parameter name
- /// @throw Dhcp6ConfigError when requested parameter is not present
+ /// @throw DhcpConfigError when requested parameter is not present
isc::dhcp::Triplet<uint32_t> getParam(const std::string& name) {
uint32_t value = 0;
bool found = false;
@@ -1245,7 +1527,7 @@ private:
if (found) {
return (isc::dhcp::Triplet<uint32_t>(value));
} else {
- isc_throw(isc::dhcp::Dhcp6ConfigError, "Mandatory parameter " << name
+ isc_throw(isc::dhcp::DhcpConfigError, "Mandatory parameter " << name
<< " missing (no global default and no subnet-"
<< "specific value)");
}
@@ -1355,6 +1637,7 @@ DhcpConfigParser* createGlobalDhcpConfigParser(const std::string& config_id) {
factories["interface"] = InterfaceListConfigParser::factory;
factories["subnet6"] = Subnets6ListConfigParser::factory;
factories["option-data"] = OptionDataListParser::factory;
+ factories["option-def"] = OptionDefListParser::factory;
factories["version"] = StringParser::factory;
FactoryMap::iterator f = factories.find(config_id);
@@ -1370,7 +1653,7 @@ DhcpConfigParser* createGlobalDhcpConfigParser(const std::string& config_id) {
}
ConstElementPtr
-configureDhcp6Server(Dhcpv6Srv& , ConstElementPtr config_set) {
+configureDhcp6Server(Dhcpv6Srv&, ConstElementPtr config_set) {
if (!config_set) {
ConstElementPtr answer = isc::config::createAnswer(1,
string("Can't parse NULL config"));
@@ -1383,18 +1666,18 @@ configureDhcp6Server(Dhcpv6Srv& , ConstElementPtr config_set) {
LOG_DEBUG(dhcp6_logger, DBG_DHCP6_COMMAND, DHCP6_CONFIG_START).arg(config_set->str());
// Some of the values specified in the configuration depend on
- // other values. Typically, the values in the subnet6 structure
- // depend on the global values. Thus we need to make sure that
- // the global values are processed first and that they can be
- // accessed by the subnet6 parsers. We separate parsers that
- // should process data first (independent_parsers) from those
- // that must process data when the independent data is already
- // processed (dependent_parsers).
+ // other values. Typically, the values in the subnet4 structure
+ // depend on the global values. Also, option values configuration
+ // must be performed after the option definitions configurations.
+ // Thus we group parsers and will fire them in the right order:
+ // all parsers other than subnet4 and option-data parser,
+ // option-data parser, subnet4 parser.
ParserCollection independent_parsers;
- ParserCollection dependent_parsers;
+ ParserPtr subnet_parser;
+ ParserPtr option_parser;
// The subnet parsers implement data inheritance by directly
- // accessing global storages. For this reason the global data
+ // accessing global storage. For this reason the global data
// parsers must store the parsed data into global storages
// immediately. This may cause data inconsistency if the
// parsing operation fails after the global storage has been
@@ -1403,6 +1686,7 @@ configureDhcp6Server(Dhcpv6Srv& , ConstElementPtr config_set) {
Uint32Storage uint32_local(uint32_defaults);
StringStorage string_local(string_defaults);
OptionStorage option_local(option_defaults);
+ OptionDefStorage option_def_local(option_def_intermediate);
// answer will hold the result.
ConstElementPtr answer;
@@ -1410,11 +1694,21 @@ configureDhcp6Server(Dhcpv6Srv& , ConstElementPtr config_set) {
// have to be restored to global storages.
bool rollback = false;
try {
- // Iterate over all independent parsers first (all but subnet6)
- // and try to parse the data.
- BOOST_FOREACH(ConfigPair config_pair, config_set->mapValue()) {
- if (config_pair.first != "subnet6") {
- ParserPtr parser(createGlobalDhcpConfigParser(config_pair.first));
+
+ // Make parsers grouping.
+ const std::map<std::string, ConstElementPtr>& values_map =
+ config_set->mapValue();
+ BOOST_FOREACH(ConfigPair config_pair, values_map) {
+ ParserPtr parser(createGlobalDhcpConfigParser(config_pair.first));
+ if (config_pair.first == "subnet6") {
+ subnet_parser = parser;
+
+ } else if (config_pair.first == "option-data") {
+ option_parser = parser;
+
+ } else {
+ // Those parsers should be started before other
+ // parsers so we can call build straight away.
independent_parsers.push_back(parser);
parser->build(config_pair.second);
// The commit operation here may modify the global storage
@@ -1424,13 +1718,19 @@ configureDhcp6Server(Dhcpv6Srv& , ConstElementPtr config_set) {
}
}
- // Process dependent configuration data.
- BOOST_FOREACH(ConfigPair config_pair, config_set->mapValue()) {
- if (config_pair.first == "subnet6") {
- ParserPtr parser(createGlobalDhcpConfigParser(config_pair.first));
- dependent_parsers.push_back(parser);
- parser->build(config_pair.second);
- }
+ // The option values parser is the next one to be run.
+ std::map<std::string, ConstElementPtr>::const_iterator option_config =
+ values_map.find("option-data");
+ if (option_config != values_map.end()) {
+ option_parser->build(option_config->second);
+ option_parser->commit();
+ }
+
+ // The subnet parser is the last one to be run.
+ std::map<std::string, ConstElementPtr>::const_iterator subnet_config =
+ values_map.find("subnet6");
+ if (subnet_config != values_map.end()) {
+ subnet_parser->build(subnet_config->second);
}
} catch (const isc::Exception& ex) {
@@ -1453,8 +1753,8 @@ configureDhcp6Server(Dhcpv6Srv& , ConstElementPtr config_set) {
// This operation should be exception safe but let's make sure.
if (!rollback) {
try {
- BOOST_FOREACH(ParserPtr parser, dependent_parsers) {
- parser->commit();
+ if (subnet_parser) {
+ subnet_parser->commit();
}
}
catch (const isc::Exception& ex) {
@@ -1477,6 +1777,7 @@ configureDhcp6Server(Dhcpv6Srv& , ConstElementPtr config_set) {
std::swap(uint32_defaults, uint32_local);
std::swap(string_defaults, string_local);
std::swap(option_defaults, option_local);
+ std::swap(option_def_intermediate, option_def_local);
return (answer);
}
diff --git a/src/bin/dhcp6/config_parser.h b/src/bin/dhcp6/config_parser.h
index 408d01f..6d7a807 100644
--- a/src/bin/dhcp6/config_parser.h
+++ b/src/bin/dhcp6/config_parser.h
@@ -27,20 +27,6 @@ namespace dhcp {
class Dhcpv6Srv;
-/// An exception that is thrown if an error occurs while configuring an
-/// @c Dhcpv6Srv object.
-class Dhcp6ConfigError : public isc::Exception {
-public:
-
- /// @brief constructor
- ///
- /// @param file name of the file, where exception occurred
- /// @param line line of the file, where exception occurred
- /// @param what text description of the issue that caused exception
- Dhcp6ConfigError(const char* file, size_t line, const char* what)
- : isc::Exception(file, line, what) {}
-};
-
/// @brief Configures DHCPv6 server
///
/// This function is called every time a new configuration is received. The extra
diff --git a/src/bin/dhcp6/ctrl_dhcp6_srv.cc b/src/bin/dhcp6/ctrl_dhcp6_srv.cc
index 8611365..ba3e2c2 100644
--- a/src/bin/dhcp6/ctrl_dhcp6_srv.cc
+++ b/src/bin/dhcp6/ctrl_dhcp6_srv.cc
@@ -19,6 +19,7 @@
#include <cc/session.h>
#include <config/ccsession.h>
#include <dhcp/iface_mgr.h>
+#include <dhcpsrv/dhcp_config_parser.h>
#include <dhcp6/config_parser.h>
#include <dhcp6/ctrl_dhcp6_srv.h>
#include <dhcp6/dhcp6_log.h>
@@ -121,7 +122,7 @@ void ControlledDhcpv6Srv::establishSession() {
try {
configureDhcp6Server(*this, config_session_->getFullConfig());
- } catch (const Dhcp6ConfigError& ex) {
+ } catch (const DhcpConfigError& ex) {
LOG_ERROR(dhcp6_logger, DHCP6_CONFIG_LOAD_FAIL).arg(ex.what());
}
diff --git a/src/bin/dhcp6/dhcp6.spec b/src/bin/dhcp6/dhcp6.spec
index 350b530..b719ebb 100644
--- a/src/bin/dhcp6/dhcp6.spec
+++ b/src/bin/dhcp6/dhcp6.spec
@@ -40,6 +40,56 @@
"item_default": 4000
},
+ { "item_name": "option-def",
+ "item_type": "list",
+ "item_optional": false,
+ "item_default": [],
+ "list_item_spec":
+ {
+ "item_name": "single-option-def",
+ "item_type": "map",
+ "item_optional": false,
+ "item_default": {},
+ "map_item_spec": [
+ {
+ "item_name": "name",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": ""
+ },
+
+ { "item_name": "code",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ },
+
+ { "item_name": "type",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": "",
+ },
+
+ { "item_name": "array",
+ "item_type": "boolean",
+ "item_optional": false,
+ "item_default": False
+ },
+
+ { "item_name": "record_types",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": "",
+ },
+
+ { "item_name": "space",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": ""
+ } ]
+ }
+ },
+
{ "item_name": "option-data",
"item_type": "list",
"item_optional": false,
@@ -72,6 +122,11 @@
"item_type": "boolean",
"item_optional": false,
"item_default": False
+ },
+ { "item_name": "space",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": "dhcp6"
} ]
}
},
@@ -162,6 +217,11 @@
"item_type": "boolean",
"item_optional": false,
"item_default": False
+ },
+ { "item_name": "space",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": "dhcp6"
} ]
}
} ]
diff --git a/src/bin/dhcp6/dhcp6_srv.cc b/src/bin/dhcp6/dhcp6_srv.cc
index 951cb5f..18acd76 100644
--- a/src/bin/dhcp6/dhcp6_srv.cc
+++ b/src/bin/dhcp6/dhcp6_srv.cc
@@ -405,7 +405,7 @@ void Dhcpv6Srv::sanityCheck(const Pkt6Ptr& pkt, RequirementLevel clientid,
Option::OptionCollection server_ids = pkt->getOptions(D6O_SERVERID);
switch (serverid) {
case FORBIDDEN:
- if (server_ids.size() > 0) {
+ if (!server_ids.empty()) {
isc_throw(RFCViolation, "Server-id option was not expected, but "
<< server_ids.size() << " received in " << pkt->getName());
}
diff --git a/src/bin/dhcp6/tests/config_parser_unittest.cc b/src/bin/dhcp6/tests/config_parser_unittest.cc
index be5bbf8..b6ef97f 100644
--- a/src/bin/dhcp6/tests/config_parser_unittest.cc
+++ b/src/bin/dhcp6/tests/config_parser_unittest.cc
@@ -53,6 +53,15 @@ public:
resetConfiguration();
};
+ // Checks if config_result (result of DHCP server configuration) has
+ // expected code (0 for success, other for failures).
+ // Also stores result in rcode_ and comment_.
+ void checkResult(ConstElementPtr status, int expected_code) {
+ ASSERT_TRUE(status);
+ comment_ = parseAnswer(rcode_, status);
+ EXPECT_EQ(expected_code, rcode_);
+ }
+
/// @brief Create the simple configuration with single option.
///
/// This function allows to set one of the parameters that configure
@@ -68,22 +77,32 @@ public:
std::map<std::string, std::string> params;
if (parameter == "name") {
params["name"] = param_value;
- params["code"] = "80";
+ params["space"] = "dhcp6";
+ params["code"] = "38";
+ params["data"] = "AB CDEF0105";
+ params["csv-format"] = "False";
+ } else if (parameter == "space") {
+ params["name"] = "subscriber-id";
+ params["space"] = param_value;
+ params["code"] = "38";
params["data"] = "AB CDEF0105";
params["csv-format"] = "False";
} else if (parameter == "code") {
- params["name"] = "option_foo";
+ params["name"] = "subscriber-id";
+ params["space"] = "dhcp6";
params["code"] = param_value;
params["data"] = "AB CDEF0105";
params["csv-format"] = "False";
} else if (parameter == "data") {
- params["name"] = "option_foo";
- params["code"] = "80";
+ params["name"] = "subscriber-id";
+ params["space"] = "dhcp6";
+ params["code"] = "38";
params["data"] = param_value;
params["csv-format"] = "False";
} else if (parameter == "csv-format") {
- params["name"] = "option_foo";
- params["code"] = "80";
+ params["name"] = "subscriber-id";
+ params["space"] = "dhcp6";
+ params["code"] = "38";
params["data"] = "AB CDEF0105";
params["csv-format"] = param_value;
}
@@ -113,6 +132,8 @@ public:
}
if (param.first == "name") {
stream << "\"name\": \"" << param.second << "\"";
+ } else if (param.first == "space") {
+ stream << "\"space\": \"" << param.second << "\"";
} else if (param.first == "code") {
stream << "\"code\": " << param.second;;
} else if (param.first == "data") {
@@ -144,6 +165,7 @@ public:
"\"renew-timer\": 1000, "
"\"valid-lifetime\": 4000, "
"\"subnet6\": [ ], "
+ "\"option-def\": [ ], "
"\"option-data\": [ ] }";
try {
@@ -427,6 +449,363 @@ TEST_F(Dhcp6ParserTest, poolPrefixLen) {
EXPECT_EQ(4000, subnet->getValid());
}
+// The goal of this test is to check whether an option definition
+// that defines an option carrying an IPv6 address can be created.
+TEST_F(Dhcp6ParserTest, optionDefIpv6Address) {
+
+ // Configuration string.
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 100,"
+ " \"type\": \"ipv6-address\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"isc\""
+ " } ]"
+ "}";
+ ElementPtr json = Element::fromJSON(config);
+
+ // Make sure that the particular option definition does not exist.
+ OptionDefinitionPtr def = CfgMgr::instance().getOptionDef("isc", 100);
+ ASSERT_FALSE(def);
+
+ // Use the configuration string to create new option definition.
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+ ASSERT_TRUE(status);
+
+ // The option definition should now be available in the CfgMgr.
+ def = CfgMgr::instance().getOptionDef("isc", 100);
+ ASSERT_TRUE(def);
+
+ // Verify that the option definition data is valid.
+ EXPECT_EQ("foo", def->getName());
+ EXPECT_EQ(100, def->getCode());
+ EXPECT_FALSE(def->getArrayType());
+ EXPECT_EQ(OPT_IPV6_ADDRESS_TYPE, def->getType());
+}
+
+// The goal of this test is to check whether an option definiiton
+// that defines an option carrying a record of data fields can
+// be created.
+TEST_F(Dhcp6ParserTest, optionDefRecord) {
+
+ // Configuration string.
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 100,"
+ " \"type\": \"record\","
+ " \"array\": False,"
+ " \"record-types\": \"uint16, ipv4-address, ipv6-address, string\","
+ " \"space\": \"isc\""
+ " } ]"
+ "}";
+ ElementPtr json = Element::fromJSON(config);
+
+ // Make sure that the particular option definition does not exist.
+ OptionDefinitionPtr def = CfgMgr::instance().getOptionDef("isc", 100);
+ ASSERT_FALSE(def);
+
+ // Use the configuration string to create new option definition.
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+ ASSERT_TRUE(status);
+ checkResult(status, 0);
+
+ // The option definition should now be available in the CfgMgr.
+ def = CfgMgr::instance().getOptionDef("isc", 100);
+ ASSERT_TRUE(def);
+
+ // Check the option data.
+ EXPECT_EQ("foo", def->getName());
+ EXPECT_EQ(100, def->getCode());
+ EXPECT_EQ(OPT_RECORD_TYPE, def->getType());
+ EXPECT_FALSE(def->getArrayType());
+
+ // The option comprises the record of data fields. Verify that all
+ // fields are present and they are of the expected types.
+ const OptionDefinition::RecordFieldsCollection& record_fields =
+ def->getRecordFields();
+ ASSERT_EQ(4, record_fields.size());
+ EXPECT_EQ(OPT_UINT16_TYPE, record_fields[0]);
+ EXPECT_EQ(OPT_IPV4_ADDRESS_TYPE, record_fields[1]);
+ EXPECT_EQ(OPT_IPV6_ADDRESS_TYPE, record_fields[2]);
+ EXPECT_EQ(OPT_STRING_TYPE, record_fields[3]);
+}
+
+// The goal of this test is to verify that multiple option definitions
+// can be created.
+TEST_F(Dhcp6ParserTest, optionDefMultiple) {
+ // Configuration string.
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 100,"
+ " \"type\": \"uint32\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"isc\""
+ " },"
+ " {"
+ " \"name\": \"foo-2\","
+ " \"code\": 101,"
+ " \"type\": \"ipv4-address\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"isc\""
+ " } ]"
+ "}";
+ ElementPtr json = Element::fromJSON(config);
+
+ // Make sure that the option definitions do not exist yet.
+ ASSERT_FALSE(CfgMgr::instance().getOptionDef("isc", 100));
+ ASSERT_FALSE(CfgMgr::instance().getOptionDef("isc", 101));
+
+ // Use the configuration string to create new option definitions.
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+ ASSERT_TRUE(status);
+ checkResult(status, 0);
+
+ // Check the first definition we have created.
+ OptionDefinitionPtr def1 = CfgMgr::instance().getOptionDef("isc", 100);
+ ASSERT_TRUE(def1);
+
+ // Check the option data.
+ EXPECT_EQ("foo", def1->getName());
+ EXPECT_EQ(100, def1->getCode());
+ EXPECT_EQ(OPT_UINT32_TYPE, def1->getType());
+ EXPECT_FALSE(def1->getArrayType());
+
+ // Check the second option definition we have created.
+ OptionDefinitionPtr def2 = CfgMgr::instance().getOptionDef("isc", 101);
+ ASSERT_TRUE(def2);
+
+ // Check the option data.
+ EXPECT_EQ("foo-2", def2->getName());
+ EXPECT_EQ(101, def2->getCode());
+ EXPECT_EQ(OPT_IPV4_ADDRESS_TYPE, def2->getType());
+ EXPECT_FALSE(def2->getArrayType());
+}
+
+// The goal of this test is to verify that the duplicated option
+// definition is not accepted.
+TEST_F(Dhcp6ParserTest, optionDefDuplicate) {
+
+ // Configuration string. Both option definitions have
+ // the same code and belong to the same option space.
+ // This configuration should not be accepted.
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 100,"
+ " \"type\": \"uint32\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"isc\""
+ " },"
+ " {"
+ " \"name\": \"foo-2\","
+ " \"code\": 100,"
+ " \"type\": \"ipv4-address\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"isc\""
+ " } ]"
+ "}";
+ ElementPtr json = Element::fromJSON(config);
+
+ // Make sure that the option definition does not exist yet.
+ ASSERT_FALSE(CfgMgr::instance().getOptionDef("isc", 100));
+
+ // Use the configuration string to create new option definitions.
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+ ASSERT_TRUE(status);
+ checkResult(status, 1);
+}
+
+// The goal of this test is to verify that the option definition
+// comprising an array of uint32 values can be created.
+TEST_F(Dhcp6ParserTest, optionDefArray) {
+
+ // Configuration string. Created option definition should
+ // comprise an array of uint32 values.
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 100,"
+ " \"type\": \"uint32\","
+ " \"array\": True,"
+ " \"record-types\": \"\","
+ " \"space\": \"isc\""
+ " } ]"
+ "}";
+ ElementPtr json = Element::fromJSON(config);
+
+ // Make sure that the particular option definition does not exist.
+ OptionDefinitionPtr def = CfgMgr::instance().getOptionDef("isc", 100);
+ ASSERT_FALSE(def);
+
+ // Use the configuration string to create new option definition.
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+ ASSERT_TRUE(status);
+ checkResult(status, 0);
+
+ // The option definition should now be available in the CfgMgr.
+ def = CfgMgr::instance().getOptionDef("isc", 100);
+ ASSERT_TRUE(def);
+
+ // Check the option data.
+ EXPECT_EQ("foo", def->getName());
+ EXPECT_EQ(100, def->getCode());
+ EXPECT_EQ(OPT_UINT32_TYPE, def->getType());
+ EXPECT_TRUE(def->getArrayType());
+}
+
+/// The purpose of this test is to verify that the option definition
+/// with invalid name is not accepted.
+TEST_F(Dhcp6ParserTest, optionDefInvalidName) {
+ // Configuration string. The option name is invalid as it
+ // contains the % character.
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"invalid%name\","
+ " \"code\": 100,"
+ " \"type\": \"string\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"isc\""
+ " } ]"
+ "}";
+ ElementPtr json = Element::fromJSON(config);
+
+ // Use the configuration string to create new option definition.
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+ ASSERT_TRUE(status);
+ // Expecting parsing error (error code 1).
+ checkResult(status, 1);
+}
+
+/// The purpose of this test is to verify that the option definition
+/// with invalid type is not accepted.
+TEST_F(Dhcp6ParserTest, optionDefInvalidType) {
+ // Configuration string. The option type is invalid. It is
+ // "sting" instead of "string".
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 100,"
+ " \"type\": \"sting\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"isc\""
+ " } ]"
+ "}";
+ ElementPtr json = Element::fromJSON(config);
+
+ // Use the configuration string to create new option definition.
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+ ASSERT_TRUE(status);
+ // Expecting parsing error (error code 1).
+ checkResult(status, 1);
+}
+
+/// The purpose of this test is to verify that the option definition
+/// with invalid type is not accepted.
+TEST_F(Dhcp6ParserTest, optionDefInvalidRecordType) {
+ // Configuration string. The third of the record fields
+ // is invalid. It is "sting" instead of "string".
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 100,"
+ " \"type\": \"record\","
+ " \"array\": False,"
+ " \"record-types\": \"uint32,uint8,sting\","
+ " \"space\": \"isc\""
+ " } ]"
+ "}";
+ ElementPtr json = Element::fromJSON(config);
+
+ // Use the configuration string to create new option definition.
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+ ASSERT_TRUE(status);
+ // Expecting parsing error (error code 1).
+ checkResult(status, 1);
+}
+
+
+/// The purpose of this test is to verify that it is not allowed
+/// to override the standard option (that belongs to dhcp6 option
+/// space) and that it is allowed to define option in the dhcp6
+/// option space that has a code which is not used by any of the
+/// standard options.
+TEST_F(Dhcp6ParserTest, optionStandardDefOverride) {
+
+ // Configuration string. The option code 100 is unassigned
+ // so it can be used for a custom option definition in
+ // dhcp6 option space.
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 100,"
+ " \"type\": \"string\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"dhcp6\""
+ " } ]"
+ "}";
+ ElementPtr json = Element::fromJSON(config);
+
+ OptionDefinitionPtr def = CfgMgr::instance().getOptionDef("dhcp6", 100);
+ ASSERT_FALSE(def);
+
+ // Use the configuration string to create new option definition.
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+ ASSERT_TRUE(status);
+ checkResult(status, 0);
+
+ // The option definition should now be available in the CfgMgr.
+ def = CfgMgr::instance().getOptionDef("dhcp6", 100);
+ ASSERT_TRUE(def);
+
+ // Check the option data.
+ EXPECT_EQ("foo", def->getName());
+ EXPECT_EQ(100, def->getCode());
+ EXPECT_EQ(OPT_STRING_TYPE, def->getType());
+ EXPECT_FALSE(def->getArrayType());
+
+ // The combination of option space and code is
+ // invalid. The 'dhcp6' option space groups
+ // standard options and the code 3 is reserved
+ // for one of them.
+ config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 3,"
+ " \"type\": \"string\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"dhcp6\""
+ " } ]"
+ "}";
+ json = Element::fromJSON(config);
+
+ // Use the configuration string to create new option definition.
+ EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+ ASSERT_TRUE(status);
+ // Expecting parsing error (error code 1).
+ checkResult(status, 1);
+}
+
// Goal of this test is to verify that global option
// data is configured for the subnet if the subnet
// configuration does not include options configuration.
@@ -437,16 +816,18 @@ TEST_F(Dhcp6ParserTest, optionDataDefaults) {
"\"rebind-timer\": 2000,"
"\"renew-timer\": 1000,"
"\"option-data\": [ {"
- " \"name\": \"option_foo\","
- " \"code\": 100,"
+ " \"name\": \"subscriber-id\","
+ " \"space\": \"dhcp6\","
+ " \"code\": 38,"
" \"data\": \"AB CDEF0105\","
" \"csv-format\": False"
" },"
" {"
- " \"name\": \"option_foo2\","
- " \"code\": 101,"
+ " \"name\": \"preference\","
+ " \"space\": \"dhcp6\","
+ " \"code\": 7,"
" \"data\": \"01\","
- " \"csv-format\": False"
+ " \"csv-format\": True"
" } ],"
"\"subnet6\": [ { "
" \"pool\": [ \"2001:db8:1::/80\" ],"
@@ -474,31 +855,101 @@ TEST_F(Dhcp6ParserTest, optionDataDefaults) {
// code so we get the range.
std::pair<Subnet::OptionContainerTypeIndex::const_iterator,
Subnet::OptionContainerTypeIndex::const_iterator> range =
- idx.equal_range(100);
- // Expect single option with the code equal to 100.
+ idx.equal_range(D6O_SUBSCRIBER_ID);
+ // Expect single option with the code equal to 38.
ASSERT_EQ(1, std::distance(range.first, range.second));
- const uint8_t foo_expected[] = {
+ const uint8_t subid_expected[] = {
0xAB, 0xCD, 0xEF, 0x01, 0x05
};
// Check if option is valid in terms of code and carried data.
- testOption(*range.first, 100, foo_expected, sizeof(foo_expected));
+ testOption(*range.first, D6O_SUBSCRIBER_ID, subid_expected,
+ sizeof(subid_expected));
- range = idx.equal_range(101);
+ range = idx.equal_range(D6O_PREFERENCE);
ASSERT_EQ(1, std::distance(range.first, range.second));
// Do another round of testing with second option.
- const uint8_t foo2_expected[] = {
+ const uint8_t pref_expected[] = {
0x01
};
- testOption(*range.first, 101, foo2_expected, sizeof(foo2_expected));
+ testOption(*range.first, D6O_PREFERENCE, pref_expected,
+ sizeof(pref_expected));
// Check that options with other option codes are not returned.
- for (uint16_t code = 102; code < 110; ++code) {
+ for (uint16_t code = 47; code < 57; ++code) {
range = idx.equal_range(code);
EXPECT_EQ(0, std::distance(range.first, range.second));
}
}
-// Goal of this test is to verify options configuration
+/// The goal of this test is to verify that two options having the same
+/// option code can be added to different option spaces.
+TEST_F(Dhcp6ParserTest, optionDataTwoSpaces) {
+
+ // This configuration string is to configure two options
+ // sharing the code 56 and having different definitions
+ // and belonging to the different option spaces.
+ // The option definition must be provided for the
+ // option that belongs to the 'isc' option space.
+ // The definition is not required for the option that
+ // belongs to the 'dhcp6' option space as it is the
+ // standard option.
+ string config = "{ \"interface\": [ \"all\" ],"
+ "\"rebind-timer\": 2000,"
+ "\"renew-timer\": 1000,"
+ "\"option-data\": [ {"
+ " \"name\": \"subscriber-id\","
+ " \"space\": \"dhcp6\","
+ " \"code\": 38,"
+ " \"data\": \"AB CDEF0105\","
+ " \"csv-format\": False"
+ " },"
+ " {"
+ " \"name\": \"foo\","
+ " \"space\": \"isc\","
+ " \"code\": 38,"
+ " \"data\": \"1234\","
+ " \"csv-format\": True"
+ " } ],"
+ "\"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 38,"
+ " \"type\": \"uint32\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"isc\""
+ " } ],"
+ "\"subnet6\": [ { "
+ " \"pool\": [ \"2001:db8:1::/80\" ],"
+ " \"subnet\": \"2001:db8:1::/64\""
+ " } ]"
+ "}";
+
+ ConstElementPtr status;
+
+ ElementPtr json = Element::fromJSON(config);
+
+ EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+ ASSERT_TRUE(status);
+ checkResult(status, 0);
+
+ // Options should be now availabe for the subnet.
+ Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
+ ASSERT_TRUE(subnet);
+ // Try to get the option from the space dhcp6.
+ Subnet::OptionDescriptor desc1 = subnet->getOptionDescriptor("dhcp6", 38);
+ ASSERT_TRUE(desc1.option);
+ EXPECT_EQ(38, desc1.option->getType());
+ // Try to get the option from the space isc.
+ Subnet::OptionDescriptor desc2 = subnet->getOptionDescriptor("isc", 38);
+ ASSERT_TRUE(desc2.option);
+ EXPECT_EQ(38, desc1.option->getType());
+ // Try to get the non-existing option from the non-existing
+ // option space and expect that option is not returned.
+ Subnet::OptionDescriptor desc3 = subnet->getOptionDescriptor("non-existing", 38);
+ ASSERT_FALSE(desc3.option);
+}
+
+// The goal of this test is to verify options configuration
// for a single subnet. In particular this test checks
// that local options configuration overrides global
// option setting.
@@ -509,8 +960,9 @@ TEST_F(Dhcp6ParserTest, optionDataInSingleSubnet) {
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
"\"option-data\": [ {"
- " \"name\": \"option_foo\","
- " \"code\": 100,"
+ " \"name\": \"subscriber-id\","
+ " \"space\": \"dhcp6\","
+ " \"code\": 38,"
" \"data\": \"AB\","
" \"csv-format\": False"
" } ],"
@@ -518,14 +970,16 @@ TEST_F(Dhcp6ParserTest, optionDataInSingleSubnet) {
" \"pool\": [ \"2001:db8:1::/80\" ],"
" \"subnet\": \"2001:db8:1::/64\", "
" \"option-data\": [ {"
- " \"name\": \"option_foo\","
- " \"code\": 100,"
+ " \"name\": \"subscriber-id\","
+ " \"space\": \"dhcp6\","
+ " \"code\": 38,"
" \"data\": \"AB CDEF0105\","
" \"csv-format\": False"
" },"
" {"
- " \"name\": \"option_foo2\","
- " \"code\": 101,"
+ " \"name\": \"preference\","
+ " \"space\": \"dhcp6\","
+ " \"code\": 7,"
" \"data\": \"01\","
" \"csv-format\": False"
" } ]"
@@ -552,22 +1006,24 @@ TEST_F(Dhcp6ParserTest, optionDataInSingleSubnet) {
// code so we get the range.
std::pair<Subnet::OptionContainerTypeIndex::const_iterator,
Subnet::OptionContainerTypeIndex::const_iterator> range =
- idx.equal_range(100);
- // Expect single option with the code equal to 100.
+ idx.equal_range(D6O_SUBSCRIBER_ID);
+ // Expect single option with the code equal to 38.
ASSERT_EQ(1, std::distance(range.first, range.second));
- const uint8_t foo_expected[] = {
+ const uint8_t subid_expected[] = {
0xAB, 0xCD, 0xEF, 0x01, 0x05
};
// Check if option is valid in terms of code and carried data.
- testOption(*range.first, 100, foo_expected, sizeof(foo_expected));
+ testOption(*range.first, D6O_SUBSCRIBER_ID, subid_expected,
+ sizeof(subid_expected));
- range = idx.equal_range(101);
+ range = idx.equal_range(D6O_PREFERENCE);
ASSERT_EQ(1, std::distance(range.first, range.second));
// Do another round of testing with second option.
- const uint8_t foo2_expected[] = {
+ const uint8_t pref_expected[] = {
0x01
};
- testOption(*range.first, 101, foo2_expected, sizeof(foo2_expected));
+ testOption(*range.first, D6O_PREFERENCE, pref_expected,
+ sizeof(pref_expected));
}
// Goal of this test is to verify options configuration
@@ -582,8 +1038,9 @@ TEST_F(Dhcp6ParserTest, optionDataInMultipleSubnets) {
" \"pool\": [ \"2001:db8:1::/80\" ],"
" \"subnet\": \"2001:db8:1::/64\", "
" \"option-data\": [ {"
- " \"name\": \"option_foo\","
- " \"code\": 100,"
+ " \"name\": \"subscriber-id\","
+ " \"space\": \"dhcp6\","
+ " \"code\": 38,"
" \"data\": \"0102030405060708090A\","
" \"csv-format\": False"
" } ]"
@@ -592,8 +1049,9 @@ TEST_F(Dhcp6ParserTest, optionDataInMultipleSubnets) {
" \"pool\": [ \"2001:db8:2::/80\" ],"
" \"subnet\": \"2001:db8:2::/64\", "
" \"option-data\": [ {"
- " \"name\": \"option_foo2\","
- " \"code\": 101,"
+ " \"name\": \"user-class\","
+ " \"space\": \"dhcp6\","
+ " \"code\": 15,"
" \"data\": \"FFFEFDFCFB\","
" \"csv-format\": False"
" } ]"
@@ -620,15 +1078,16 @@ TEST_F(Dhcp6ParserTest, optionDataInMultipleSubnets) {
// code so we get the range.
std::pair<Subnet::OptionContainerTypeIndex::const_iterator,
Subnet::OptionContainerTypeIndex::const_iterator> range1 =
- idx1.equal_range(100);
- // Expect single option with the code equal to 100.
+ idx1.equal_range(D6O_SUBSCRIBER_ID);
+ // Expect single option with the code equal to 38.
ASSERT_EQ(1, std::distance(range1.first, range1.second));
- const uint8_t foo_expected[] = {
+ const uint8_t subid_expected[] = {
0x01, 0x02, 0x03, 0x04, 0x05,
0x06, 0x07, 0x08, 0x09, 0x0A
};
// Check if option is valid in terms of code and carried data.
- testOption(*range1.first, 100, foo_expected, sizeof(foo_expected));
+ testOption(*range1.first, D6O_SUBSCRIBER_ID, subid_expected,
+ sizeof(subid_expected));
// Test another subnet in the same way.
Subnet6Ptr subnet2 = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:2::4"));
@@ -639,13 +1098,14 @@ TEST_F(Dhcp6ParserTest, optionDataInMultipleSubnets) {
const Subnet::OptionContainerTypeIndex& idx2 = options2->get<1>();
std::pair<Subnet::OptionContainerTypeIndex::const_iterator,
Subnet::OptionContainerTypeIndex::const_iterator> range2 =
- idx2.equal_range(101);
+ idx2.equal_range(D6O_USER_CLASS);
ASSERT_EQ(1, std::distance(range2.first, range2.second));
- const uint8_t foo2_expected[] = {
+ const uint8_t user_class_expected[] = {
0xFF, 0xFE, 0xFD, 0xFC, 0xFB
};
- testOption(*range2.first, 101, foo2_expected, sizeof(foo2_expected));
+ testOption(*range2.first, D6O_USER_CLASS, user_class_expected,
+ sizeof(user_class_expected));
}
// Verify that empty option name is rejected in the configuration.
@@ -738,14 +1198,15 @@ TEST_F(Dhcp6ParserTest, optionDataLowerCase) {
// code so we get the range.
std::pair<Subnet::OptionContainerTypeIndex::const_iterator,
Subnet::OptionContainerTypeIndex::const_iterator> range =
- idx.equal_range(80);
- // Expect single option with the code equal to 100.
+ idx.equal_range(D6O_SUBSCRIBER_ID);
+ // Expect single option with the code equal to 38.
ASSERT_EQ(1, std::distance(range.first, range.second));
- const uint8_t foo_expected[] = {
+ const uint8_t subid_expected[] = {
0x0A, 0x0B, 0x0C, 0x0D
};
// Check if option is valid in terms of code and carried data.
- testOption(*range.first, 80, foo_expected, sizeof(foo_expected));
+ testOption(*range.first, D6O_SUBSCRIBER_ID, subid_expected,
+ sizeof(subid_expected));
}
// Verify that specific option object is returned for standard
@@ -753,7 +1214,8 @@ TEST_F(Dhcp6ParserTest, optionDataLowerCase) {
TEST_F(Dhcp6ParserTest, stdOptionData) {
ConstElementPtr x;
std::map<std::string, std::string> params;
- params["name"] = "OPTION_IA_NA";
+ params["name"] = "ia-na";
+ params["space"] = "dhcp6";
// Option code 3 means OPTION_IA_NA.
params["code"] = "3";
params["data"] = "12345, 6789, 1516";
diff --git a/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc b/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc
index ee508ad..dee3de5 100644
--- a/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc
+++ b/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc
@@ -370,14 +370,16 @@ TEST_F(Dhcpv6SrvTest, advertiseOptions) {
" \"pool\": [ \"2001:db8:1::/64\" ],"
" \"subnet\": \"2001:db8:1::/48\", "
" \"option-data\": [ {"
- " \"name\": \"OPTION_DNS_SERVERS\","
+ " \"name\": \"dns-servers\","
+ " \"space\": \"dhcp6\","
" \"code\": 23,"
" \"data\": \"2001:db8:1234:FFFF::1, 2001:db8:1234:FFFF::2\","
" \"csv-format\": True"
" },"
" {"
- " \"name\": \"OPTION_FOO\","
- " \"code\": 1000,"
+ " \"name\": \"subscriber-id\","
+ " \"space\": \"dhcp6\","
+ " \"code\": 38,"
" \"data\": \"1234\","
" \"csv-format\": False"
" } ]"
@@ -406,18 +408,18 @@ TEST_F(Dhcpv6SrvTest, advertiseOptions) {
// check if we get response at all
ASSERT_TRUE(adv);
- // We have not requested option with code 1000 so it should not
+ // We have not requested any options so they should not
// be included in the response.
- ASSERT_FALSE(adv->getOption(1000));
+ ASSERT_FALSE(adv->getOption(D6O_SUBSCRIBER_ID));
ASSERT_FALSE(adv->getOption(D6O_NAME_SERVERS));
- // Let's now request option with code 1000.
- // We expect that server will include this option in its reply.
+ // Let's now request some options. We expect that the server
+ // will include them in its response.
boost::shared_ptr<OptionIntArray<uint16_t> >
option_oro(new OptionIntArray<uint16_t>(Option::V6, D6O_ORO));
// Create vector with two option codes.
std::vector<uint16_t> codes(2);
- codes[0] = 1000;
+ codes[0] = D6O_SUBSCRIBER_ID;
codes[1] = D6O_NAME_SERVERS;
// Pass this code to option.
option_oro->setValues(codes);
@@ -442,7 +444,7 @@ TEST_F(Dhcpv6SrvTest, advertiseOptions) {
// There is a dummy option with code 1000 we requested from a server.
// Expect that this option is in server's response.
- tmp = adv->getOption(1000);
+ tmp = adv->getOption(D6O_SUBSCRIBER_ID);
ASSERT_TRUE(tmp);
// Check that the option contains valid data (from configuration).
diff --git a/src/lib/dhcp/option_definition.cc b/src/lib/dhcp/option_definition.cc
index d97ca71..d2b5aae 100644
--- a/src/lib/dhcp/option_definition.cc
+++ b/src/lib/dhcp/option_definition.cc
@@ -23,6 +23,8 @@
#include <dhcp/option_int_array.h>
#include <util/encode/hex.h>
#include <util/strutil.h>
+#include <boost/algorithm/string/classification.hpp>
+#include <boost/algorithm/string/predicate.hpp>
using namespace std;
using namespace isc::util;
@@ -207,16 +209,29 @@ OptionDefinition::sanityCheckUniverse(const Option::Universe expected_universe,
void
OptionDefinition::validate() const {
+
+ using namespace boost::algorithm;
+
std::ostringstream err_str;
- if (name_.empty()) {
- // Option name must not be empty.
- err_str << "option name must not be empty.";
- } else if (name_.find(" ") != string::npos) {
- // Option name must not contain spaces.
- err_str << "option name must not contain spaces.";
+
+ // Allowed characters in the option name are: lower or
+ // upper case letters, digits, underscores and hyphens.
+ // Empty option spaces are not allowed.
+ if (!all(name_, boost::is_from_range('a', 'z') ||
+ boost::is_from_range('A', 'Z') ||
+ boost::is_digit() ||
+ boost::is_any_of(std::string("-_"))) ||
+ name_.empty() ||
+ // Hyphens and underscores are not allowed at the beginning
+ // and at the end of the option name.
+ all(find_head(name_, 1), boost::is_any_of(std::string("-_"))) ||
+ all(find_tail(name_, 1), boost::is_any_of(std::string("-_")))) {
+ err_str << "invalid option name '" << name_ << "'";
+
} else if (type_ >= OPT_UNKNOWN_TYPE) {
// Option definition must be of a known type.
err_str << "option type value " << type_ << " is out of range.";
+
} else if (array_type_) {
if (type_ == OPT_STRING_TYPE) {
// Array of strings is not allowed because there is no way
@@ -225,9 +240,12 @@ OptionDefinition::validate() const {
err_str << "array of strings is not a valid option definition.";
} else if (type_ == OPT_BINARY_TYPE) {
err_str << "array of binary values is not a valid option definition.";
+
} else if (type_ == OPT_EMPTY_TYPE) {
err_str << "array of empty value is not a valid option definition.";
+
}
+
} else if (type_ == OPT_RECORD_TYPE) {
// At least two data fields should be added to the record. Otherwise
// non-record option definition could be used.
@@ -235,6 +253,7 @@ OptionDefinition::validate() const {
err_str << "invalid number of data fields: " << getRecordFields().size()
<< " specified for the option of type 'record'. Expected at"
<< " least 2 fields.";
+
} else {
// If the number of fields is valid we have to check if their order
// is valid too. We check that string or binary data fields are not
diff --git a/src/lib/dhcp/tests/option_definition_unittest.cc b/src/lib/dhcp/tests/option_definition_unittest.cc
index deb61b5..310c7bf 100644
--- a/src/lib/dhcp/tests/option_definition_unittest.cc
+++ b/src/lib/dhcp/tests/option_definition_unittest.cc
@@ -164,29 +164,55 @@ TEST_F(OptionDefinitionTest, validate) {
EXPECT_THROW(opt_def5.validate(), MalformedOptionDefinition);
// Option name must not contain spaces.
- OptionDefinition opt_def6("OPTION CLIENTID", D6O_CLIENTID, "string", true);
+ OptionDefinition opt_def6("OPTION CLIENTID", D6O_CLIENTID, "string");
EXPECT_THROW(opt_def6.validate(), MalformedOptionDefinition);
+ // Option name may contain lower case letters.
+ OptionDefinition opt_def7("option_clientid", D6O_CLIENTID, "string");
+ EXPECT_NO_THROW(opt_def7.validate());
+
+ // Using digits in option name is legal.
+ OptionDefinition opt_def8("option_123", D6O_CLIENTID, "string");
+ EXPECT_NO_THROW(opt_def8.validate());
+
+ // Using hyphen is legal.
+ OptionDefinition opt_def9("option-clientid", D6O_CLIENTID, "string");
+ EXPECT_NO_THROW(opt_def9.validate());
+
+ // Using hyphen or undescore at the beginning or at the end
+ // of the option name is not allowed.
+ OptionDefinition opt_def10("-option-clientid", D6O_CLIENTID, "string");
+ EXPECT_THROW(opt_def10.validate(), MalformedOptionDefinition);
+
+ OptionDefinition opt_def11("_option-clientid", D6O_CLIENTID, "string");
+ EXPECT_THROW(opt_def11.validate(), MalformedOptionDefinition);
+
+ OptionDefinition opt_def12("option-clientid_", D6O_CLIENTID, "string");
+ EXPECT_THROW(opt_def12.validate(), MalformedOptionDefinition);
+
+ OptionDefinition opt_def13("option-clientid-", D6O_CLIENTID, "string");
+ EXPECT_THROW(opt_def13.validate(), MalformedOptionDefinition);
+
// Having array of strings does not make sense because there is no way
// to determine string's length.
- OptionDefinition opt_def7("OPTION_CLIENTID", D6O_CLIENTID, "string", true);
- EXPECT_THROW(opt_def7.validate(), MalformedOptionDefinition);
+ OptionDefinition opt_def14("OPTION_CLIENTID", D6O_CLIENTID, "string", true);
+ EXPECT_THROW(opt_def14.validate(), MalformedOptionDefinition);
// It does not make sense to have string field within the record before
// other fields because there is no way to determine the length of this
// string and thus there is no way to determine where the other field
// begins.
- OptionDefinition opt_def8("OPTION_STATUS_CODE", D6O_STATUS_CODE,
- "record");
- opt_def8.addRecordField("string");
- opt_def8.addRecordField("uint16");
- EXPECT_THROW(opt_def8.validate(), MalformedOptionDefinition);
+ OptionDefinition opt_def15("OPTION_STATUS_CODE", D6O_STATUS_CODE,
+ "record");
+ opt_def15.addRecordField("string");
+ opt_def15.addRecordField("uint16");
+ EXPECT_THROW(opt_def15.validate(), MalformedOptionDefinition);
// ... but it is ok if the string value is the last one.
- OptionDefinition opt_def9("OPTION_STATUS_CODE", D6O_STATUS_CODE,
- "record");
- opt_def9.addRecordField("uint8");
- opt_def9.addRecordField("string");
+ OptionDefinition opt_def16("OPTION_STATUS_CODE", D6O_STATUS_CODE,
+ "record");
+ opt_def16.addRecordField("uint8");
+ opt_def16.addRecordField("string");
}
diff --git a/src/lib/dhcpsrv/Makefile.am b/src/lib/dhcpsrv/Makefile.am
index 5b0027f..cc5daec 100644
--- a/src/lib/dhcpsrv/Makefile.am
+++ b/src/lib/dhcpsrv/Makefile.am
@@ -43,6 +43,7 @@ if HAVE_MYSQL
libb10_dhcpsrv_la_SOURCES += mysql_lease_mgr.cc mysql_lease_mgr.h
endif
libb10_dhcpsrv_la_SOURCES += option_space.cc option_space.h
+libb10_dhcpsrv_la_SOURCES += option_space_container.h
libb10_dhcpsrv_la_SOURCES += pool.cc pool.h
libb10_dhcpsrv_la_SOURCES += subnet.cc subnet.h
libb10_dhcpsrv_la_SOURCES += triplet.h
diff --git a/src/lib/dhcpsrv/cfgmgr.cc b/src/lib/dhcpsrv/cfgmgr.cc
index 26ee978..ee40130 100644
--- a/src/lib/dhcpsrv/cfgmgr.cc
+++ b/src/lib/dhcpsrv/cfgmgr.cc
@@ -86,30 +86,15 @@ CfgMgr::addOptionDef(const OptionDefinitionPtr& def,
<< option_space << "'.");
}
- // Get existing option definitions for the option space.
- OptionDefContainerPtr defs = getOptionDefs(option_space);
- // getOptionDefs always returns a valid pointer to
- // the container. Let's make an assert to make sure.
- assert(defs);
- // Actually add the new definition.
- defs->push_back(def);
- option_def_spaces_[option_space] = defs;
+ // Actually add a new item.
+ option_def_spaces_.addItem(def, option_space);
}
OptionDefContainerPtr
CfgMgr::getOptionDefs(const std::string& option_space) const {
// @todo Validate the option space once the #2313 is implemented.
- // Get all option definitions for the particular option space.
- const OptionDefsMap::const_iterator& defs =
- option_def_spaces_.find(option_space);
- // If there are no option definitions for the particular option space
- // then return empty container.
- if (defs == option_def_spaces_.end()) {
- return (OptionDefContainerPtr(new OptionDefContainer()));
- }
- // If option definitions found, return them.
- return (defs->second);
+ return (option_def_spaces_.getItems(option_space));
}
OptionDefinitionPtr
@@ -229,7 +214,7 @@ void CfgMgr::addSubnet4(const Subnet4Ptr& subnet) {
}
void CfgMgr::deleteOptionDefs() {
- option_def_spaces_.clear();
+ option_def_spaces_.clearItems();
}
void CfgMgr::deleteSubnets4() {
diff --git a/src/lib/dhcpsrv/cfgmgr.h b/src/lib/dhcpsrv/cfgmgr.h
index c1f1dd6..9cfde9d 100644
--- a/src/lib/dhcpsrv/cfgmgr.h
+++ b/src/lib/dhcpsrv/cfgmgr.h
@@ -19,6 +19,7 @@
#include <dhcp/option.h>
#include <dhcp/option_definition.h>
#include <dhcpsrv/option_space.h>
+#include <dhcpsrv/option_space_container.h>
#include <dhcpsrv/pool.h>
#include <dhcpsrv/subnet.h>
#include <util/buffer.h>
@@ -66,6 +67,7 @@ namespace dhcp {
/// Parameter inheritance is likely to be implemented in configuration handling
/// routines, so there is no storage capability in a global scope for
/// subnet-specific parameters.
+///
/// @todo: Implement Subnet4 support (ticket #2237)
/// @todo: Implement option definition support
/// @todo: Implement parameter inheritance
@@ -250,15 +252,12 @@ protected:
private:
- /// A map containing option definitions for various option spaces.
- /// They key of this map is the name of the option space. The
- /// value is the the option container holding option definitions
- /// for the particular option space.
- typedef std::map<std::string, OptionDefContainerPtr> OptionDefsMap;
-
- /// A map containing option definitions for different option spaces.
- /// The map key holds an option space name.
- OptionDefsMap option_def_spaces_;
+ /// @brief A collection of option definitions.
+ ///
+ /// A collection of option definitions that can be accessed
+ /// using option space name they belong to.
+ OptionSpaceContainer<OptionDefContainer,
+ OptionDefinitionPtr> option_def_spaces_;
/// @brief Container for defined DHCPv6 option spaces.
OptionSpaceCollection spaces6_;
diff --git a/src/lib/dhcpsrv/dhcp_config_parser.h b/src/lib/dhcpsrv/dhcp_config_parser.h
index 7077552..d3d05f6 100644
--- a/src/lib/dhcpsrv/dhcp_config_parser.h
+++ b/src/lib/dhcpsrv/dhcp_config_parser.h
@@ -18,6 +18,20 @@
namespace isc {
namespace dhcp {
+/// An exception that is thrown if an error occurs while configuring
+/// DHCP server.
+class DhcpConfigError : public isc::Exception {
+public:
+
+ /// @brief constructor
+ ///
+ /// @param file name of the file, where exception occurred
+ /// @param line line of the file, where exception occurred
+ /// @param what text description of the issue that caused exception
+ DhcpConfigError(const char* file, size_t line, const char* what)
+ : isc::Exception(file, line, what) {}
+};
+
/// @brief Forward declaration to DhcpConfigParser class.
///
/// It is only needed here to define types that are
@@ -105,6 +119,34 @@ public:
/// This method is expected to be called after @c build(), and only once.
/// The result is undefined otherwise.
virtual void commit() = 0;
+
+protected:
+
+ /// @brief Return the parsed entry from the provided storage.
+ ///
+ /// This method returns the parsed entry from the provided
+ /// storage. If the entry is not found, then exception is
+ /// thrown.
+ ///
+ /// @param param_id name of the configuration entry.
+ /// @param storage storage where the entry should be searched.
+ /// @tparam ReturnType type of the returned value.
+ /// @tparam StorageType type of the storage.
+ ///
+ /// @throw DhcpConfigError if the entry has not been found
+ /// in the storage.
+ template<typename ReturnType, typename StorageType>
+ static ReturnType getParam(const std::string& param_id,
+ const StorageType& storage) {
+ typename StorageType::const_iterator param = storage.find(param_id);
+ if (param == storage.end()) {
+ isc_throw(DhcpConfigError, "missing parameter '"
+ << param_id << "'");
+ }
+ ReturnType value = param->second;
+ return (value);
+ }
+
};
diff --git a/src/lib/dhcpsrv/option_space_container.h b/src/lib/dhcpsrv/option_space_container.h
new file mode 100644
index 0000000..f90bedd
--- /dev/null
+++ b/src/lib/dhcpsrv/option_space_container.h
@@ -0,0 +1,102 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef OPTION_SPACE_CONTAINER_H
+#define OPTION_SPACE_CONTAINER_H
+
+#include <list>
+#include <string>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Simple container for option spaces holding various items.
+///
+/// This helper class is used to store items of various types in
+/// that are grouped by option space names. Each option space is
+/// mapped to a container that holds items which specifically can
+/// be OptionDefinition objects or Subnet::OptionDescriptor structures.
+///
+/// @tparam ContainerType of the container holding items within
+/// option space.
+/// @tparam ItemType type of the item being held by the container.
+template<typename ContainerType, typename ItemType>
+class OptionSpaceContainer {
+public:
+
+ /// Pointer to the container.
+ typedef boost::shared_ptr<ContainerType> ItemsContainerPtr;
+
+ /// @brief Adds a new item to the option_space.
+ ///
+ /// @param item reference to the item being added.
+ /// @param name of the option space.
+ void addItem(const ItemType& item, const std::string& option_space) {
+ ItemsContainerPtr items = getItems(option_space);
+ items->push_back(item);
+ option_space_map_[option_space] = items;
+ }
+
+ /// @brief Get all items for the particular option space.
+ ///
+ /// @warning when there are no items for the specified option
+ /// space an empty container is created and returned. However
+ /// this container is not added to the list of option spaces.
+ ///
+ /// @param option_space name of the option space.
+ ///
+ /// @return pointer to the container holding items.
+ ItemsContainerPtr getItems(const std::string& option_space) const {
+ const typename OptionSpaceMap::const_iterator& items =
+ option_space_map_.find(option_space);
+ if (items == option_space_map_.end()) {
+ return (ItemsContainerPtr(new ContainerType()));
+ }
+ return (items->second);
+ }
+
+ /// @brief Get a list of existing option spaces.
+ ///
+ /// @return a list of option spaces.
+ ///
+ /// @todo This function is likely to be removed once
+ /// we create a structore of OptionSpaces defined
+ /// through the configuration manager.
+ std::list<std::string> getOptionSpaceNames() {
+ std::list<std::string> names;
+ for (typename OptionSpaceMap::const_iterator space =
+ option_space_map_.begin();
+ space != option_space_map_.end(); ++space) {
+ names.push_back(space->first);
+ }
+ return (names);
+ }
+
+ /// @brief Remove all items from the container.
+ void clearItems() {
+ option_space_map_.clear();
+ }
+
+private:
+
+ /// A map holding container (option space name is the key).
+ typedef std::map<std::string, ItemsContainerPtr> OptionSpaceMap;
+ OptionSpaceMap option_space_map_;
+};
+
+
+} // end of isc::dhcp namespace
+} // end of isc namespace
+
+#endif // OPTION_SPACE_CONTAINER_H
diff --git a/src/lib/dhcpsrv/subnet.cc b/src/lib/dhcpsrv/subnet.cc
index ddc19e1..6414f88 100644
--- a/src/lib/dhcpsrv/subnet.cc
+++ b/src/lib/dhcpsrv/subnet.cc
@@ -55,36 +55,18 @@ Subnet::addOption(OptionPtr& option, bool persistent,
}
validateOption(option);
- OptionContainerPtr container = getOptionDescriptors(option_space);
- // getOptionDescriptors is expected to return the pointer to the
- // valid container. Let's make sure it does by performing an assert.
- assert(container);
- // Actually add the new descriptor.
- container->push_back(OptionDescriptor(option, persistent));
- option_spaces_[option_space] = container;
+ // Actually add new option descriptor.
+ option_spaces_.addItem(OptionDescriptor(option, persistent), option_space);
}
void
Subnet::delOptions() {
- option_spaces_.clear();
+ option_spaces_.clearItems();
}
Subnet::OptionContainerPtr
Subnet::getOptionDescriptors(const std::string& option_space) const {
- // Search the map to get the options container for the particular
- // option space.
- const OptionSpacesPtr::const_iterator& options =
- option_spaces_.find(option_space);
- // If the option space has not been found it means that no option
- // has been configured for this option space yet. Thus we have to
- // return an empty container to the caller.
- if (options == option_spaces_.end()) {
- // The default constructor creates an empty container.
- return (OptionContainerPtr(new OptionContainer()));
- }
- // We found some option container for the option space specified.
- // Let's return a const reference to it.
- return (options->second);
+ return (option_spaces_.getItems(option_space));
}
Subnet::OptionDescriptor
diff --git a/src/lib/dhcpsrv/subnet.h b/src/lib/dhcpsrv/subnet.h
index da2f762..b864321 100644
--- a/src/lib/dhcpsrv/subnet.h
+++ b/src/lib/dhcpsrv/subnet.h
@@ -24,6 +24,7 @@
#include <asiolink/io_address.h>
#include <dhcp/option.h>
+#include <dhcpsrv/option_space_container.h>
#include <dhcpsrv/pool.h>
#include <dhcpsrv/triplet.h>
@@ -417,12 +418,11 @@ protected:
private:
- /// Container holding options grouped by option space names.
- typedef std::map<std::string, OptionContainerPtr> OptionSpacesPtr;
+ /// A collection of option spaces grouping option descriptors.
+ typedef OptionSpaceContainer<OptionContainer,
+ OptionDescriptor> OptionSpaceCollection;
+ OptionSpaceCollection option_spaces_;
- /// @brief a collection of DHCP option spaces holding options
- /// configured for a subnet.
- OptionSpacesPtr option_spaces_;
};
/// @brief A generic pointer to either Subnet4 or Subnet6 object
More information about the bind10-changes
mailing list