Commit e2a552fc authored by Tomek Mrugalski's avatar Tomek Mrugalski 🛰
Browse files

[2269] Configuration parser for DHCPv6 implemented.

parent e3cc1803
......@@ -2911,7 +2911,7 @@ Dhcp6/subnet6 [] list (default)</screen>
<screen>
&gt; <userinput>config add Dhcp6/subnet6</userinput>
&gt; <userinput>config set Dhcp6/subnet6[0]/subnet "2001:db8:1::/64"</userinput>
&gt; <userinput>config set Dhcp6/subnet6[0]/pool6 [ "2001:db8:1::1 - 2001:db8:1::ffff" ]</userinput>
&gt; <userinput>config set Dhcp6/subnet6[0]/pool [ "2001:db8:1::1 - 2001:db8:1::ffff" ]</userinput>
&gt; <userinput>config commit</userinput></screen>
Please note that subnet is defined as a simple string, but the pool is
an actual list of pools, therefore must be defined with square
......@@ -2922,7 +2922,7 @@ Dhcp6/subnet6 [] list (default)</screen>
is cumbersome. It can be expressed simply as 2001:db8:1:0:5::/80. Both
formats are supported by Dhcp6. For example, one could define the following pools:
<screen>
&gt; <userinput>config set Dhcp6/subnet6[0]/pool6 [ "2001:db8:1::1 - 2001:db8:1::ffff", "2001:db8:1:0:5::/80" ]</userinput>
&gt; <userinput>config set Dhcp6/subnet6[0]/pool [ "2001:db8:1::1 - 2001:db8:1::ffff", "2001:db8:1:0:5::/80" ]</userinput>
&gt; <userinput>config commit</userinput></screen>
Number of subnets is not limited, but for performance reasons it is recommended to keep
them as low as possible.
......@@ -2933,7 +2933,7 @@ Dhcp6/subnet6 [] list (default)</screen>
<screen>
&gt; <userinput>config add Dhcp6/subnet6</userinput>
&gt; <userinput>config set Dhcp6/subnet6[1]/subnet "2001:db8:beef::/48"</userinput>
&gt; <userinput>config set Dhcp6/subnet6[1]/pool6 [ "2001:db8:beef::/48" ]</userinput>
&gt; <userinput>config set Dhcp6/subnet6[1]/pool [ "2001:db8:beef::/48" ]</userinput>
&gt; <userinput>config commit</userinput></screen>
Arrays are counted from 0. subnet[0] refers to the subnet defined in the
previous example. The <command>config add Dhcp6/subnet6</command> adds
......
......@@ -46,6 +46,7 @@ pkglibexec_PROGRAMS = b10-dhcp6
b10_dhcp6_SOURCES = main.cc
b10_dhcp6_SOURCES += ctrl_dhcp6_srv.cc ctrl_dhcp6_srv.h
b10_dhcp6_SOURCES += config_parser.cc config_parser.h
b10_dhcp6_SOURCES += dhcp6_log.cc dhcp6_log.h
b10_dhcp6_SOURCES += dhcp6_srv.cc dhcp6_srv.h
......@@ -62,6 +63,7 @@ b10_dhcp6_LDADD = $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
b10_dhcp6_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
b10_dhcp6_LDADD += $(top_builddir)/src/lib/log/libb10-log.la
b10_dhcp6_LDADD += $(top_builddir)/src/lib/dhcp/libb10-dhcp++.la
b10_dhcp6_LDADD += $(top_builddir)/src/lib/dhcp/libb10-dhcpsrv.la
b10_dhcp6_LDADD += $(top_builddir)/src/lib/config/libb10-cfgclient.la
b10_dhcp6_LDADD += $(top_builddir)/src/lib/cc/libb10-cc.la
......
// 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
// 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