--- /dev/null
+// Copyright (C) 2010 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.
+
+#include <stdint.h>
+#include <iostream>
+#include <vector>
+#include <map>
+#include <boost/foreach.hpp>
+#include <boost/shared_ptr.hpp>
+#include <boost/scoped_ptr.hpp>
+#include <boost/lexical_cast.hpp>
+#include <boost/algorithm/string.hpp>
+#include <cc/data.h>
+#include <asiolink/io_address.h>
+#include <dhcp6/config_parser.h>
+#include <dhcp/triplet.h>
+#include <dhcp/pool.h>
+#include <dhcp/subnet.h>
+#include <dhcp/cfgmgr.h>
+
+using namespace std;
+using namespace isc::data;
+using namespace isc::asiolink;
+
+namespace isc {
+namespace dhcp {
+
+typedef boost::shared_ptr<Dhcp6ConfigParser> ParserPtr;
+typedef pair<string, ConstElementPtr> ConfigPair;
+typedef std::vector<ParserPtr> ParserCollection;
+typedef Dhcp6ConfigParser* ParserFactory(const std::string& config_id);
+
+typedef std::map<std::string, ParserFactory*> FactoryMap;
+
+typedef std::map<string, uint32_t> Uint32Storage;
+/// @brief That is a map with global parameters that will be used as defaults
+Uint32Storage uint32_defaults;
+
+typedef std::map<string, string> StringStorage;
+StringStorage string_defaults;
+
+typedef std::vector<Pool6Ptr> PoolStorage;
+PoolStorage pool_defaults;
+
+/// @brief a dummy configuration parser
+///
+/// It is a debugging parser. It does not configure anything,
+/// will accept any configuration and will just print it out
+/// on commit.
+class DummyParser : public Dhcp6ConfigParser {
+public:
+ DummyParser(const std::string& param_name)
+ :param_name_(param_name) {
+ }
+ virtual void build(ConstElementPtr new_config) {
+ value_ = new_config;
+ }
+ virtual void commit() {
+ // debug message. The whole DummyParser class is used only for parser
+ // debugging, and is not used in production code. It is very convenient
+ // to keep it around. Please do not turn this cout into logger calls
+ std::cout << "Commit for token: [" << param_name_ << "] = ["
+ << value_->str() << "]" << std::endl;
+ }
+
+ static Dhcp6ConfigParser* Factory(const std::string& param_name) {
+ return (new DummyParser(param_name));
+ }
+
+protected:
+ std::string param_name_;
+ ConstElementPtr value_;
+};
+
+class Uint32Parser : public Dhcp6ConfigParser {
+public:
+ Uint32Parser(const std::string& param_name)
+ :storage_(&uint32_defaults), param_name_(param_name) {
+ }
+
+ virtual void build(ConstElementPtr value) {
+ try {
+ value_ = boost::lexical_cast<uint32_t>(value->str());
+ } catch (const boost::bad_lexical_cast &) {
+ isc_throw(BadValue, "Failed to parse value " << value->str()
+ << " as unsigned 32-bit integer.");
+ }
+ cout << "### storing " << param_name_ << "=" << value_ <<
+ " in " << storage_ << endl;
+ storage_->insert(pair<string, uint32_t>(param_name_, value_));
+ }
+
+ virtual void commit() {
+ }
+
+ static Dhcp6ConfigParser* Factory(const std::string& param_name) {
+ return (new Uint32Parser(param_name));
+ }
+
+ void setStorage(Uint32Storage* storage) {
+ storage_ = storage;
+ }
+
+protected:
+ Uint32Storage * storage_;
+ std::string param_name_;
+ uint32_t value_;
+};
+
+class StringParser : public Dhcp6ConfigParser {
+public:
+ StringParser(const std::string& param_name)
+ :storage_(&string_defaults), param_name_(param_name) {
+ }
+
+ virtual void build(ConstElementPtr value) {
+ value_ = value->str();
+ boost::erase_all(value_, "\"");
+ storage_->insert(pair<string, string>(param_name_, value_));
+ }
+
+ virtual void commit() {
+ }
+
+ static Dhcp6ConfigParser* Factory(const std::string& param_name) {
+ return (new StringParser(param_name));
+ }
+
+ void setStorage(StringStorage * storage) {
+ storage_ = storage;
+ }
+
+protected:
+ StringStorage * storage_;
+ std::string param_name_;
+ std::string value_;
+};
+
+class InterfaceListConfigParser : public Dhcp6ConfigParser {
+public:
+ InterfaceListConfigParser(const std::string& param_name) {
+ if (param_name != "interface") {
+ isc_throw(NotImplemented, "Internal error. Interface configuration "
+ "parser called for the wrong parameter: " << param_name);
+ }
+ }
+ virtual void build(ConstElementPtr value) {
+ BOOST_FOREACH(ConstElementPtr iface, value->listValue()) {
+ interfaces_.push_back(iface->str());
+ cout << "#### Configured to listen on interface " << iface->str() << endl;
+ }
+ }
+
+ virtual void commit() {
+ /// @todo: Implement per interface listening. Currently always listening on all
+ /// interfaces.
+ }
+
+ static Dhcp6ConfigParser* Factory(const std::string& param_name) {
+ return (new InterfaceListConfigParser(param_name));
+ }
+
+protected:
+ vector<string> interfaces_;
+};
+
+class PoolParser : public Dhcp6ConfigParser {
+public:
+ PoolParser(const std::string& /*param_name*/)
+ :pools_(NULL) {
+ // ignore parameter name, it is always Dhcp6/subnet6[X]/pool
+ }
+ void build(ConstElementPtr pools_list) {
+ // setStorage() should have been called before build
+ if (!pools_) {
+ isc_throw(NotImplemented, "Parser logic error. No pool storage set,"
+ " but pool parser asked to parse pools");
+ }
+
+ BOOST_FOREACH(ConstElementPtr text_pool, pools_list->listValue()) {
+
+
+
+ // That should be a single pool representation. It should contain
+ // text is form prefix/len or first - last. Note that spaces
+ // are allowed
+ string txt = text_pool->stringValue();
+
+ // first let's remove any spaces or tabs
+ boost::erase_all(txt, " ");
+ boost::erase_all(txt, "\t");
+
+ size_t pos = txt.find("/");
+ if (pos != string::npos) {
+ IOAddress addr("::");
+ uint8_t len = 0;
+ try {
+ addr = IOAddress(txt.substr(0, pos));
+ string num = txt.substr(pos+1);
+
+ // it is lexical cast to int and then downcast to uint8_t
+ // direct cast to uint8_t (which is really an unsigned char)
+ // will result in interpreting the first digit as output
+ // value and throwing exception if length written on two
+ // digits (because there are extra characters left over)
+ len = boost::lexical_cast<int>(num);
+ } catch (...) {
+ isc_throw(Dhcp6ConfigError, "Failed to parse pool "
+ "definition: " << text_pool->stringValue());
+ }
+
+ cout << "#### Creating Pool6(TYPE_IA, " << addr.toText() << "/"
+ << (int)len << ")" << endl;
+ // using prefix/len notation
+ Pool6Ptr pool(new Pool6(Pool6::TYPE_IA, addr, len));
+ pools_->push_back(pool);
+ continue;
+ }
+
+ pos = txt.find("-");
+ if (pos != string::npos) {
+ IOAddress min(txt.substr(0,pos-1));
+ IOAddress max(txt.substr(pos+1));
+
+ cout << "#### Creating Pool6(TYPE_IA, " << min.toText() << ","
+ << max.toText() << ")" << endl;
+
+ Pool6Ptr pool(new Pool6(Pool6::TYPE_IA, min, max));
+
+ pools_->push_back(pool);
+ continue;
+ }
+
+ isc_throw(Dhcp6ConfigError, "Failed to parse pool definition:"
+ << text_pool->stringValue() <<
+ ". Does not contain - (for min-max) nor / (prefix/len)");
+ }
+ }
+ void setStorage(PoolStorage* storage) {
+ pools_ = storage;
+ }
+
+ void commit() {}
+
+ static Dhcp6ConfigParser* Factory(const std::string& param_name) {
+ return (new PoolParser(param_name));
+ }
+
+protected:
+ PoolStorage * pools_;
+
+};
+
+/// @brief this class parses a single subnet
+class Subnet6ConfigParser : public Dhcp6ConfigParser {
+public:
+ Subnet6ConfigParser(const std::string& param_name) {
+ }
+
+ void build(ConstElementPtr subnet) {
+
+ cout << "#### Subnet6ConfigParser::build(): parsing: [" << subnet->str() << "]" << endl;
+ BOOST_FOREACH(ConfigPair param, subnet->mapValue()) {
+
+ ParserPtr parser(createSubnet6ConfigParser(param.first));
+
+ // if this is an Uint32 parser, tell it to store the values
+ // in values_, rather than in global storage
+ boost::shared_ptr<Uint32Parser> uintParser =
+ boost::dynamic_pointer_cast<Uint32Parser>(parser);
+ if (uintParser) {
+ uintParser->setStorage(&uint32_values_);
+ }
+
+ boost::shared_ptr<StringParser> stringParser =
+ boost::dynamic_pointer_cast<StringParser>(parser);
+ if (stringParser) {
+ stringParser->setStorage(&string_values_);
+ }
+
+ boost::shared_ptr<PoolParser> poolParser =
+ boost::dynamic_pointer_cast<PoolParser>(parser);
+ if (poolParser) {
+ poolParser->setStorage(&pools_);
+ }
+
+ parser->build(param.second);
+ parsers_.push_back(parser);
+ }
+
+ // Ok, we now have subnet parsed
+ }
+
+ void commit() {
+
+ StringStorage::const_iterator it = string_values_.find("subnet");
+ cout << "#### Subnet6ConfigParser::commit() string_values_.size()="
+ << string_values_.size() << endl;
+ if (it == string_values_.end()) {
+ isc_throw(Dhcp6ConfigError,
+ "Mandatory subnet definition in subnet missing");
+ }
+ string subnet_txt = it->second;
+ boost::erase_all(subnet_txt, " ");
+ boost::erase_all(subnet_txt, "\t");
+
+ size_t pos = subnet_txt.find("/");
+ if (pos == string::npos) {
+ isc_throw(Dhcp6ConfigError,
+ "Invalid subnet syntax (prefix/len expected):" << it->second);
+ }
+ IOAddress addr(subnet_txt.substr(0, pos));
+ uint8_t len = boost::lexical_cast<unsigned int>(subnet_txt.substr(pos + 1));
+
+ Triplet<uint32_t> t1 = getParam("renew-timer");
+ Triplet<uint32_t> t2 = getParam("rebind-timer");
+ Triplet<uint32_t> pref = getParam("preferred-lifetime");
+ Triplet<uint32_t> valid = getParam("valid-lifetime");
+
+ cout << "#### Adding subnet " << addr.toText() << "/" << (int)len
+ << " with params t1=" << t1 << ", t2=" << t2 << ", pref="
+ << pref << ", valid=" << valid << endl;
+
+ Subnet6Ptr subnet(new Subnet6(addr, len, t1, t2, pref, valid));
+
+
+
+ CfgMgr::instance().addSubnet6(subnet);
+ }
+
+protected:
+ Dhcp6ConfigParser* createSubnet6ConfigParser(const std::string& config_id) {
+ FactoryMap factories;
+
+ factories.insert(pair<string, ParserFactory*>(
+ "preferred-lifetime", Uint32Parser::Factory));
+ factories.insert(pair<string, ParserFactory*>(
+ "valid-lifetime", Uint32Parser::Factory));
+ factories.insert(pair<string, ParserFactory*>(
+ "renew-timer", Uint32Parser::Factory));
+ factories.insert(pair<string, ParserFactory*>(
+ "rebind-timer", Uint32Parser::Factory));
+
+ factories.insert(pair<string, ParserFactory*>(
+ "subnet", StringParser::Factory));
+
+ factories.insert(pair<string, ParserFactory*>(
+ "pool", PoolParser::Factory));
+
+ FactoryMap::iterator f = factories.find(config_id);
+ if (f == factories.end()) {
+ // Used for debugging only.
+ // return new DummyParser(config_id);
+
+ isc_throw(NotImplemented,
+ "Parser error: Subnet6 parameter not supported: "
+ << config_id);
+ }
+ return (f->second(config_id));
+ }
+
+ Triplet<uint32_t> getParam(const std::string& name) {
+ uint32_t value = 0;
+ bool found = false;
+ Uint32Storage::iterator global = uint32_defaults.find(name);
+ if (global != uint32_defaults.end()) {
+ value = global->second;
+ found = true;
+ }
+
+ Uint32Storage::iterator local = uint32_values_.find(name);
+ if (local != uint32_values_.end()) {
+ value = local->second;
+ found = true;
+ }
+
+ if (found) {
+ return (Triplet<uint32_t>(value));
+ } else {
+ isc_throw(Dhcp6ConfigError, "Mandatory parameter " << name
+ << " missing (no global default and no subnet-"
+ << "specific value)");
+ }
+ }
+
+ Uint32Storage uint32_values_;
+ StringStorage string_values_;
+ PoolStorage pools_;
+ ParserCollection parsers_;
+};
+
+/// @brief this class parses list of subnets
+class Subnets6ListConfigParser : public Dhcp6ConfigParser {
+public:
+ Subnets6ListConfigParser(const std::string& param_name) {
+ }
+
+ void build(ConstElementPtr subnets_list) {
+
+ // No need to define FactoryMap here. There's only one type
+ // used: Subnet6ConfigParser
+
+ BOOST_FOREACH(ConstElementPtr subnet, subnets_list->listValue()) {
+
+ ParserPtr parser(new Subnet6ConfigParser("subnet"));
+ parser->build(subnet);
+ subnets_.push_back(parser);
+ }
+
+ }
+
+ void commit() {
+ BOOST_FOREACH(ParserPtr subnet, subnets_) {
+ subnet->commit();
+ }
+
+ }
+
+ static Dhcp6ConfigParser* Factory(const std::string& param_name) {
+ return (new Subnets6ListConfigParser(param_name));
+ }
+
+ ParserCollection subnets_;
+};
+
+/// @brief creates global parsers
+///
+/// This method creates global parsers that parse global parameters, i.e.
+/// those that take format of Dhcp6/param1, Dhcp6/param2 and so forth.
+Dhcp6ConfigParser* createGlobalDhcp6ConfigParser(const std::string& config_id) {
+ FactoryMap factories;
+
+ //
+ factories.insert(pair<string, ParserFactory*>(
+ "preferred-lifetime", Uint32Parser::Factory));
+ factories.insert(pair<string, ParserFactory*>(
+ "valid-lifetime", Uint32Parser::Factory));
+ factories.insert(pair<string, ParserFactory*>(
+ "renew-timer", Uint32Parser::Factory));
+ factories.insert(pair<string, ParserFactory*>(
+ "rebind-timer", Uint32Parser::Factory));
+
+ factories.insert(pair<string, ParserFactory*>(
+ "interface", InterfaceListConfigParser::Factory));
+ factories.insert(pair<string, ParserFactory*>(
+ "subnet6", Subnets6ListConfigParser::Factory));
+
+ factories.insert(pair<string, ParserFactory*>(
+ "version", StringParser::Factory));
+
+ FactoryMap::iterator f = factories.find(config_id);
+ if (f == factories.end()) {
+ // Used for debugging only.
+ // return new DummyParser(config_id);
+
+ isc_throw(NotImplemented,
+ "Parser error: Global configuration parameter not supported: "
+ << config_id);
+ }
+ return (f->second(config_id));
+}
+
+void
+configureDhcp6Server(Dhcpv6Srv& server, ConstElementPtr config_set) {
+ if (!config_set) {
+ isc_throw(Dhcp6ConfigError,
+ "Null pointer is passed to configuration parser");
+ }
+
+ ParserCollection parsers;
+ try {
+ BOOST_FOREACH(ConfigPair config_pair, config_set->mapValue()) {
+
+ ParserPtr parser(createGlobalDhcp6ConfigParser(config_pair.first));
+ parser->build(config_pair.second);
+ parsers.push_back(parser);
+ }
+ } catch (const Dhcp6ConfigError& ex) {
+ throw; // simply rethrowing it
+ } catch (const isc::Exception& ex) {
+ isc_throw(Dhcp6ConfigError, "Server configuration failed: " <<
+ ex.what());
+ }
+
+ try {
+ BOOST_FOREACH(ParserPtr parser, parsers) {
+ parser->commit();
+ }
+ } catch (...) {
+ isc_throw(Dhcp6ConfigError, "Unrecoverable error: "
+ "a configuration parser threw in commit");
+ }
+}
+
+}; // end of isc::dhcp namespace
+}; // end of isc namespace
--- /dev/null
+// Copyright (C) 2012 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.
+
+#include <string>
+#include <exceptions/exceptions.h>
+#include <cc/data.h>
+
+#ifndef DHCP6_CONFIG_PARSER_H
+#define DHCP6_CONFIG_PARSER_H
+
+namespace isc {
+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:
+Dhcp6ConfigError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+class Dhcp6ConfigParser {
+ ///
+ /// \name Constructors and Destructor
+ ///
+ /// Note: The copy constructor and the assignment operator are
+ /// intentionally defined as private to make it explicit that this is a
+ /// pure base class.
+ //@{
+private:
+ Dhcp6ConfigParser(const Dhcp6ConfigParser& source);
+ Dhcp6ConfigParser& operator=(const Dhcp6ConfigParser& source);
+protected:
+ /// \brief The default constructor.
+ ///
+ /// This is intentionally defined as \c protected as this base class should
+ /// never be instantiated (except as part of a derived class).
+ Dhcp6ConfigParser() {}
+public:
+ /// The destructor.
+ virtual ~Dhcp6ConfigParser() {}
+ //@}
+
+ /// Prepare configuration value.
+ ///
+ /// This method parses the "value part" of the configuration identifier
+ /// that corresponds to this derived class and prepares a new value to
+ /// apply to the server.
+ /// In the above example, the derived class for the identifier "param1"
+ /// would be passed an data \c Element storing an integer whose value
+ /// is 10, and would record that value internally;
+ /// the derived class for the identifier "param2" would be passed a
+ /// map element and (after parsing) convert it into some internal
+ /// data structure.
+ ///
+ /// This method must validate the given value both in terms of syntax
+ /// and semantics of the configuration, so that the server will be
+ /// validly configured at the time of \c commit(). Note: the given
+ /// configuration value is normally syntactically validated, but the
+ /// \c build() implementation must also expect invalid input. If it
+ /// detects an error it may throw an exception of a derived class
+ /// of \c isc::Exception.
+ ///
+ /// Preparing a configuration value will often require resource
+ /// allocation. If it fails, it may throw a corresponding standard
+ /// exception.
+ ///
+ /// This method is not expected to be called more than once. Although
+ /// multiple calls are not prohibited by the interface, the behavior
+ /// is undefined.
+ ///
+ /// \param config_value The configuration value for the identifier
+ /// corresponding to the derived class.
+ virtual void build(isc::data::ConstElementPtr config_value) = 0;
+
+ /// Apply the prepared configuration value to the server.
+ ///
+ /// This method is expected to be exception free, and, as a consequence,
+ /// it should normally not involve resource allocation.
+ /// Typically it would simply perform exception free assignment or swap
+ /// operation on the value prepared in \c build().
+ /// In some cases, however, it may be very difficult to meet this
+ /// condition in a realistic way, while the failure case should really
+ /// be very rare. In such a case it may throw, and, if the parser is
+ /// called via \c configureDhcp6Server(), the caller will convert the
+ /// exception as a fatal error.
+ ///
+ /// This method is expected to be called after \c build(), and only once.
+ /// The result is undefined otherwise.
+ virtual void commit() = 0;
+};
+
+/// Configure an \c Dhcpv6Srv object with a set of configuration values.
+///
+/// This function parses configuration information stored in \c config_set
+/// and configures the \c server by applying the configuration to it.
+/// It provides the strong exception guarantee as long as the underlying
+/// derived class implementations of \c Dhcp6ConfigParser meet the assumption,
+/// that is, it ensures that either configuration is fully applied or the
+/// state of the server is intact.
+///
+/// If a syntax or semantics level error happens during the configuration
+/// (such as malformed configuration or invalid configuration parameter),
+/// this function throws an exception of class \c Dhcp6ConfigError.
+/// If the given configuration requires resource allocation and it fails,
+/// a corresponding standard exception will be thrown.
+/// Other exceptions may also be thrown, depending on the implementation of
+/// the underlying derived class of \c Dhcp6ConfigError.
+/// In any case the strong guarantee is provided as described above except
+/// in the very rare cases where the \c commit() method of a parser throws
+/// an exception. If that happens this function converts the exception
+/// into a \c FatalError exception and rethrows it. This exception is
+/// expected to be caught at the highest level of the application to terminate
+/// the program gracefully.
+///
+/// \param server The \c Dhcpv6Srv object to be configured.
+/// \param config_set A JSON style configuration to apply to \c server.
+void configureDhcp6Server(Dhcpv6Srv& server,
+ isc::data::ConstElementPtr config_set);
+
+
+/// Create a new \c Dhcp6ConfigParser object for a given configuration
+/// identifier.
+///
+/// It internally identifies an appropriate derived class for the given
+/// identifier and creates a new instance of that class. The caller can
+/// then configure the \c server regarding the identifier by calling
+/// the \c build() and \c commit() methods of the returned object.
+///
+/// In practice, this function is only expected to be used as a backend of
+/// \c configureDhcp6Server() and is not supposed to be called directly
+/// by applications. It is publicly available mainly for testing purposes.
+/// When called directly, the created object must be deleted by the caller.
+/// Note: this means if this module and the caller use incompatible sets of
+/// new/delete, it may cause unexpected strange failure. We could avoid that
+/// by providing a separate deallocation function or by using a smart pointer,
+/// but since the expected usage of this function is very limited (i.e. for
+/// our own testing purposes) it would be an overkilling. We therefore prefer
+/// simplicity and keeping the interface intuitive.
+///
+/// If the resource allocation for the new object fails, a corresponding
+/// standard exception will be thrown. Otherwise this function is not
+/// expected to throw an exception, unless the constructor of the underlying
+/// derived class implementation (unexpectedly) throws.
+///
+/// \param server The \c Dhcpv6Srv object to be configured.
+/// \param config_id The configuration identifier for which a parser object
+/// is to be created.
+/// \return A pointer to an \c Dhcp6ConfigParser object.
+Dhcp6ConfigParser* createDhcp6ConfigParser(Dhcpv6Srv& server,
+ const std::string& config_id);
+
+}; // end of isc::dhcp namespace
+}; // end of isc namespace
+
+#endif // DHCP6_CONFIG_PARSER_H