Commit b927deb2 authored by Thomas Markwalder's avatar Thomas Markwalder
Browse files

[master] Merge branch 'trac3436'

Fixed Conflicts:
	src/bin/d2/tests/Makefile.am
parents c1896c91 6308f360
# This is an example configuration file for D2, Kea's DHCP-DDNS processor.
# It supports updating two Forward DNS zones "four.example.com" and
# "six.example.com"; and one Reverse DNS zone, "2.0.192.in-addr.arpa."
{
# ------------------ DHCP-DDNS ---------------------
#
"DhcpDdns":
{
# -------------- Global Parameters ----------------
#
# D2 will listen for update requests for Kea DHCP servers at 172.16.1.10
# on port 53001. Maximum time to we will wait for a DNS server to
# respond to us is 1000 ms.
"ip_address": "172.16.1.10",
"port": 53001,
"dns_server_timeout" : 1000,
#
# ----------------- Forward DDNS ------------------
#
# 1. Zone - "four.example.com.
# It uses TSIG, key name is "d2.md5.key"
# It is served by one DNS server which listens for DDNS requests at
# 172.16.1.1 on the default port 53 (standard DNS port)
#
# 2. Zone - "six.example.com."
# It does not use TSIG.
# It is server by one DNS server at "2001:db8:1::10" on port 7802
"forward_ddns":
{
"ddns_domains":
[
# DdnsDomain for zone "four.example.com."
{
"name": "four.example.com.",
"key_name": "d2.md5.key",
"dns_servers":
[
{
"ip_address": "172.16.1.1"
}
]
},
# DdnsDomain for zone "six.example.com."
{
"name": "six.example.com.",
"dns_servers":
[
{
"ip_address": "2001:db8:1::10",
"port": 7802
}
]
}
]
},
#
# ----------------- Reverse DDNS ------------------
#
# We will update Reverse DNS for one zone "2.0.192.in-addr-arpa". It
# uses TSIG with key "d2.sha1.key" and is served by two DNS servers:
# one listening at "172.16.1.1" on 53001 and the other at "192.168.2.10".
#
"reverse_ddns":
{
"ddns_domains":
[
{
"name": "2.0.192.in-addr.arpa.",
"key_name": "d2.sha1.key",
"dns_servers":
[
{
"ip_address": "172.16.1.1",
"port": 53001
},
{
"ip_address": "192.168.2.10"
}
]
}
]
},
#
# ------------------ TSIG keys ---------------------
#
# Each key has a name, an algorithm (HMAC-MD5, HMAC-SHA1, HMAC-SHA224...)
# and a base-64 encoded shared secret.
#
"tsig_keys":
[
{
"name": "d2.md5.key",
"algorithm": "HMAC-MD5",
"secret": "LSWXnfkKZjdPJI5QxlpnfQ=="
},
{
"name": "d2.sha1.key",
"algorithm": "HMAC-SHA1",
"secret": "hRrp29wzUv3uzSNRLlY68w=="
}
]
}
}
# This file may be used a template for constructing DHCP-DDNS JSON
# configuration.
#
# Default values that may be omitted are '#' commented out.
# If in a file by itself, it must start with a left-curly-bracket.
{
"DhcpDdns" :
{
#
# -------------- Global Parameters ----------------
#
# All of the global parameters have default values as shown. If these
# are satisfactory you may omit them.
#
# "ip_address" : "127.0.0.1",
# "port" : 53001,
# "dns_server_timeout" : 100,
# "ncr_protocol" : "UDP"
# "ncr_format" : "JSON"
#
# ----------------- Forward DDNS ------------------
#
"forward_ddns" :
{
"ddns_domains" :
[
{
"name" : "<zone name 1>",
# "key_name" : "<key name>",
"dns_servers" :
[
{
"ip_address" : "<ip address>"
# ,"port" : 53
}
# ,
# {
# next DNS server for this DdnsDomain
# }
# :
]
}
# ,
# {
# next Forward DdnsDomain
# }
# :
]
},
#
# ----------------- Reverse DDNS ------------------
#
"reverse_ddns" :
{
"ddns_domains" :
[
{
"name" : "<reverse zone name 1>",
# "key_name" : "<key name>",
"dns_servers" :
[
{
"ip_address" : "<ip address>"
# ,"port" : 53
}
# ,
# {
# next DNS server for this DdnsDomain
# }
# :
]
}
# ,
# {
# next Reverse DdnsDomain
# }
# :
]
},
#
# ------------------ TSIG keys ---------------------
#
"tsig_keys" :
[
{
"name" : "<key name>",
"algorithm" : "<algorithm name>",
# Valid values for algorithm are: HMAC-MD5, HMAC-SHA1,
# HMAC-SHA224, HMAC-SHA256,
# HMAC-SHA384, HMAC-SHA512
"secret" : "<shared secret value>"
}
# ,
# {
# next TSIG Key
# }
]
}
# If in a file by itself, it must end with an right-curly-bracket.
}
......@@ -202,31 +202,94 @@ D2CfgMgr::buildParams(isc::data::ConstElementPtr params_config) {
// This populate the context scalar stores with all of the parameters.
DCfgMgrBase::buildParams(params_config);
// Fetch the parameters from the context to create the D2Params.
// Fetch and validate the parameters from the context to create D2Params.
// We validate them here rather than just relying on D2Param constructor
// so we can spit out config text position info with errors.
// Fetch and validate ip_address.
D2CfgContextPtr context = getD2CfgContext();
isc::dhcp::StringStoragePtr strings = context->getStringStorage();
asiolink::IOAddress ip_address(strings->
getOptionalParam("ip_address",
D2Params::DFT_IP_ADDRESS));
asiolink::IOAddress ip_address(D2Params::DFT_IP_ADDRESS);
std::string ip_address_str = strings->getOptionalParam("ip_address",
D2Params::
DFT_IP_ADDRESS);
try {
ip_address = asiolink::IOAddress(ip_address_str);
} catch (const std::exception& ex) {
isc_throw(D2CfgError, "IP address invalid : \""
<< ip_address_str << "\" ("
<< strings->getPosition("ip_address") << ")");
}
if ((ip_address.toText() == "0.0.0.0") || (ip_address.toText() == "::")) {
isc_throw(D2CfgError, "IP address cannot be \"" << ip_address << "\" ("
<< strings->getPosition("ip_address") << ")");
}
// Fetch and validate port.
isc::dhcp::Uint32StoragePtr ints = context->getUint32Storage();
uint32_t port = ints->getOptionalParam("port", D2Params::DFT_PORT);
if (port == 0) {
isc_throw(D2CfgError, "port cannot be 0 ("
<< ints->getPosition("port") << ")");
}
// Fetch and validate dns_server_timeout.
uint32_t dns_server_timeout
= ints->getOptionalParam("dns_server_timeout",
D2Params::DFT_DNS_SERVER_TIMEOUT);
dhcp_ddns::NameChangeProtocol ncr_protocol
= dhcp_ddns::stringToNcrProtocol(strings->
getOptionalParam("ncr_protocol",
D2Params::
DFT_NCR_PROTOCOL));
dhcp_ddns::NameChangeFormat ncr_format
= dhcp_ddns::stringToNcrFormat(strings->
if (dns_server_timeout < 1) {
isc_throw(D2CfgError, "DNS server timeout must be larger than 0 ("
<< ints->getPosition("dns_server_timeout") << ")");
}
// Fetch and validate ncr_protocol.
dhcp_ddns::NameChangeProtocol ncr_protocol;
try {
ncr_protocol = dhcp_ddns::
stringToNcrProtocol(strings->
getOptionalParam("ncr_protocol",
D2Params::
DFT_NCR_PROTOCOL));
} catch (const std::exception& ex) {
isc_throw(D2CfgError, "ncr_protocol : "
<< ex.what() << " ("
<< strings->getPosition("ncr_protocol") << ")");
}
if (ncr_protocol != dhcp_ddns::NCR_UDP) {
isc_throw(D2CfgError, "ncr_protocol : "
<< dhcp_ddns::ncrProtocolToString(ncr_protocol)
<< " is not yet supported ("
<< strings->getPosition("ncr_protocol") << ")");
}
// Fetch and validate ncr_format.
dhcp_ddns::NameChangeFormat ncr_format;
try {
ncr_format = dhcp_ddns::
stringToNcrFormat(strings->
getOptionalParam("ncr_format",
D2Params::
DFT_NCR_FORMAT));
// Attempt to create the new client config.
D2Params::
DFT_NCR_FORMAT));
} catch (const std::exception& ex) {
isc_throw(D2CfgError, "ncr_format : "
<< ex.what() << " ("
<< strings->getPosition("ncr_format") << ")");
}
if (ncr_format != dhcp_ddns::FMT_JSON) {
isc_throw(D2CfgError, "NCR Format:"
<< dhcp_ddns::ncrFormatToString(ncr_format)
<< " is not yet supported ("
<< strings->getPosition("ncr_format") << ")");
}
// Attempt to create the new client config. This ought to fly as
// we already validated everything.
D2ParamsPtr params(new D2Params(ip_address, port, dns_server_timeout,
ncr_protocol, ncr_format));
......@@ -234,7 +297,8 @@ D2CfgMgr::buildParams(isc::data::ConstElementPtr params_config) {
}
isc::dhcp::ParserPtr
D2CfgMgr::createConfigParser(const std::string& config_id) {
D2CfgMgr::createConfigParser(const std::string& config_id,
const isc::data::Element::Position& pos) {
// Get D2 specific context.
D2CfgContextPtr context = getD2CfgContext();
......@@ -262,8 +326,8 @@ D2CfgMgr::createConfigParser(const std::string& config_id) {
context->getKeys()));
} else {
isc_throw(NotImplemented,
"parser error: D2CfgMgr parameter not supported: "
<< config_id);
"parser error: D2CfgMgr parameter not supported : "
" (" << config_id << pos << ")");
}
return (parser);
......
......@@ -245,7 +245,18 @@ protected:
/// parameter's id and then invoking its build method passing in the
/// parameter's configuration value.
///
/// It then fetches the parameters, validating their values and if
/// valid instantiates a D2Params instance. Invalid values result in
/// a throw.
///
/// @param params_config set of scalar configuration elements to parse
///
/// @throw D2CfgError if any of the following are true:
/// -# ip_address is 0.0.0.0 or ::
/// -# port is 0
/// -# dns_server_timeout is < 1
/// -# ncr_protocol is invalid, currently only NCR_UDP is supported
/// -# ncr_format is invalid, currently only FMT_JSON is supported
virtual void buildParams(isc::data::ConstElementPtr params_config);
/// @brief Given an element_id returns an instance of the appropriate
......@@ -264,11 +275,15 @@ protected:
///
/// @param element_id is the string name of the element as it will appear
/// in the configuration set.
/// @param pos position within the configuration text (or file) of element
/// to be parsed. This is passed for error messaging.
///
/// @return returns a ParserPtr to the parser instance.
/// @throw throws DCfgMgrBaseError if an error occurs.
virtual isc::dhcp::ParserPtr
createConfigParser(const std::string& element_id);
createConfigParser(const std::string& element_id,
const isc::data::Element::Position& pos =
isc::data::Element::Position());
/// @brief Creates an new, blank D2CfgContext context
///
......
......@@ -348,7 +348,9 @@ TSIGKeyInfoParser::build(isc::data::ConstElementPtr key_config) {
// 3. Invoke the parser's commit method to store the element's parsed
// data to the parser's local storage.
BOOST_FOREACH (config_pair, key_config->mapValue()) {
isc::dhcp::ParserPtr parser(createConfigParser(config_pair.first));
isc::dhcp::ParserPtr parser(createConfigParser(config_pair.first,
config_pair.second->
getPosition()));
parser->build(config_pair.second);
parser->commit();
}
......@@ -356,43 +358,66 @@ TSIGKeyInfoParser::build(isc::data::ConstElementPtr key_config) {
std::string name;
std::string algorithm;
std::string secret;
std::map<std::string, isc::data::Element::Position> pos;
// Fetch the key configuration's parsed scalar values from parser's
// local storage.
local_scalars_.getParam("name", name);
local_scalars_.getParam("algorithm", algorithm);
local_scalars_.getParam("secret", secret);
// Fetch the key's parsed scalar values from parser's local storage.
// All are required, if any are missing we'll throw.
try {
pos["name"] = local_scalars_.getParam("name", name);
pos["algorithm"] = local_scalars_.getParam("algorithm", algorithm);
pos["secret"] = local_scalars_.getParam("secret", secret);
} catch (const std::exception& ex) {
isc_throw(D2CfgError, "TSIG Key incomplete : " << ex.what()
<< " (" << key_config->getPosition() << ")");
}
// Name cannot be blank.
if (name.empty()) {
isc_throw(D2CfgError, "TSIG Key Info must specify name");
isc_throw(D2CfgError, "TSIG key must specify name (" << pos["name"] << ")");
}
// Algorithm cannot be blank.
if (algorithm.empty()) {
isc_throw(D2CfgError, "TSIG Key Info must specify algorithm");
// Currently, the premise is that key storage is always empty prior to
// parsing so we are always adding keys never replacing them. Duplicates
// are not allowed and should be flagged as a configuration error.
if (keys_->find(name) != keys_->end()) {
isc_throw(D2CfgError, "Duplicate TSIG key name specified : " << name
<< " (" << pos["name"] << ")");
}
// Algorithm must be valid.
try {
TSIGKeyInfo::stringToAlgorithmName(algorithm);
} catch (const std::exception& ex) {
isc_throw(D2CfgError, "TSIG key : " << ex.what() << " (" << pos["algorithm"] << ")");
}
// Secret cannot be blank.
// Cryptolink lib doesn't offer any way to validate these. As long as it
// isn't blank we'll accept it. If the content is bad, the call to in
// TSIGKeyInfo::remakeKey() made in the TSIGKeyInfo ctor will throw.
// We'll deal with that below.
if (secret.empty()) {
isc_throw(D2CfgError, "TSIG Key Info must specify secret");
isc_throw(D2CfgError, "TSIG key must specify secret (" << pos["secret"] << ")");
}
// Currently, the premise is that key storage is always empty prior to
// parsing so we are always adding keys never replacing them. Duplicates
// are not allowed and should be flagged as a configuration error.
if (keys_->find(name) != keys_->end()) {
isc_throw(D2CfgError, "Duplicate TSIG key specified:" << name);
}
// Everything should be valid, so create the key instance.
// It is possible for the asiodns::dns::TSIGKey create to fail such as
// with an invalid secret content.
TSIGKeyInfoPtr key_info;
try {
key_info.reset(new TSIGKeyInfo(name, algorithm, secret));
} catch (const std::exception& ex) {
isc_throw(D2CfgError, ex.what() << " (" << key_config->getPosition() << ")");
TSIGKeyInfoPtr key_info(new TSIGKeyInfo(name, algorithm, secret));
}
// Add the new TSIGKeyInfo to the key storage.
(*keys_)[name]=key_info;
}
isc::dhcp::ParserPtr
TSIGKeyInfoParser::createConfigParser(const std::string& config_id) {
TSIGKeyInfoParser::createConfigParser(const std::string& config_id,
const isc::data::Element::Position& pos) {
DhcpConfigParser* parser = NULL;
// Based on the configuration id of the element, create the appropriate
// parser. Scalars are set to use the parser's local scalar storage.
......@@ -404,7 +429,7 @@ TSIGKeyInfoParser::createConfigParser(const std::string& config_id) {
} else {
isc_throw(NotImplemented,
"parser error: TSIGKeyInfo parameter not supported: "
<< config_id);
<< config_id << " (" << pos << ")");
}
// Return the new parser instance.
......@@ -487,7 +512,9 @@ DnsServerInfoParser::build(isc::data::ConstElementPtr server_config) {
// 3. Invoke the parser's commit method to store the element's parsed
// data to the parser's local storage.
BOOST_FOREACH (config_pair, server_config->mapValue()) {
isc::dhcp::ParserPtr parser(createConfigParser(config_pair.first));
isc::dhcp::ParserPtr parser(createConfigParser(config_pair.first,
config_pair.second->
getPosition()));
parser->build(config_pair.second);
parser->commit();
}
......@@ -495,26 +522,48 @@ DnsServerInfoParser::build(isc::data::ConstElementPtr server_config) {
std::string hostname;
std::string ip_address;
uint32_t port = DnsServerInfo::STANDARD_DNS_PORT;
std::map<std::string, isc::data::Element::Position> pos;
// Fetch the server configuration's parsed scalar values from parser's
// local storage.
local_scalars_.getParam("hostname", hostname, DCfgContextBase::OPTIONAL);
local_scalars_.getParam("ip_address", ip_address,
DCfgContextBase::OPTIONAL);
local_scalars_.getParam("port", port, DCfgContextBase::OPTIONAL);
// local storage. They're all optional, so no try-catch here.
pos["hostname"] = local_scalars_.getParam("hostname", hostname,
DCfgContextBase::OPTIONAL);
pos["ip_address"] = local_scalars_.getParam("ip_address", ip_address,
DCfgContextBase::OPTIONAL);
pos["port"] = local_scalars_.getParam("port", port,
DCfgContextBase::OPTIONAL);
// The configuration must specify one or the other.
if (hostname.empty() == ip_address.empty()) {
isc_throw(D2CfgError, "Dns Server must specify one or the other"
" of hostname and IP address");
" of hostname or IP address"
<< " (" << server_config->getPosition() << ")");
}
// Port cannot be zero.
if (port == 0) {
isc_throw(D2CfgError, "Dns Server : port cannot be 0"
<< " (" << pos["port"] << ")");
}
DnsServerInfoPtr serverInfo;
if (!hostname.empty()) {
// When hostname is specified, create a valid, blank IOAddress and
// then create the DnsServerInfo.
isc::asiolink::IOAddress io_addr(DnsServerInfo::EMPTY_IP_STR);
serverInfo.reset(new DnsServerInfo(hostname, io_addr, port));
/// @todo when resolvable hostname is supported we create the entry
/// as follows:
///
/// @code
/// // When hostname is specified, create a valid, blank IOAddress
/// // and then create the DnsServerInfo.
/// isc::asiolink::IOAddress io_addr(DnsServerInfo::EMPTY_IP_STR);
/// serverInfo.reset(new DnsServerInfo(hostname, io_addr, port));
///
/// @endcode
///
/// Resolution will be done prior to connection during transaction
/// processing.
/// Until then we'll throw unsupported.
isc_throw(D2CfgError, "Dns Server : hostname is not yet supported"
<< " (" << pos["hostname"] << ")");
} else {
try {
// Create an IOAddress from the IP address string given and then
......@@ -522,7 +571,8 @@ DnsServerInfoParser::build(isc::data::ConstElementPtr server_config) {
isc::asiolink::IOAddress io_addr(ip_address);
serverInfo.reset(new DnsServerInfo(hostname, io_addr, port));
} catch (const isc::asiolink::IOError& ex) {
isc_throw(D2CfgError, "Invalid IP address:" << ip_address);
isc_throw(D2CfgError, "Dns Server : invalid IP address : "
<< ip_address << " (" << pos["ip_address"] << ")");
}
}
......@@ -531,7 +581,9 @@ DnsServerInfoParser::build(isc::data::ConstElementPtr server_config) {
}
isc::dhcp::ParserPtr
DnsServerInfoParser::createConfigParser(const std::string& config_id) {
DnsServerInfoParser::createConfigParser(const std::string& config_id,
const isc::data::Element::
Position& pos) {
DhcpConfigParser* parser = NULL;
// Based on the configuration id of the element, create the appropriate
// parser. Scalars are set to use the parser's local scalar storage.
......@@ -545,7 +597,7 @@ DnsServerInfoParser::createConfigParser(const std::string& config_id) {
} else {
isc_throw(NotImplemented,
"parser error: DnsServerInfo parameter not supported: "
<< config_id);
<< config_id << " (" << pos << ")");
}
// Return the new parser instance.
......@@ -591,7 +643,8 @@ build(isc::data::ConstElementPtr server_list){
// Domains must have at least one server.
if (parsers_.size() == 0) {
isc_throw (D2CfgError, "Server List must contain at least one server");
isc_throw (D2CfgError, "Server List must contain at least one server"
<< " (" << server_list->getPosition() << ")");
}
}
......@@ -630,7 +683,9 @@ DdnsDomainParser::build(isc::data::ConstElementPtr domain_config) {
// data to the parser's local storage.
isc::dhcp::ConfigPair config_pair;
BOOST_FOREACH(config_pair, domain_config->mapValue()) {
isc::dhcp::ParserPtr parser(createConfigParser(config_pair.first));
isc::dhcp::ParserPtr parser(createConfigParser(config_pair.first,
config_pair.second->
getPosition()));
parser->build(config_pair.second);
parser->commit();
}
......@@ -638,13 +693,24 @@ DdnsDomainParser::build(isc::data::ConstElementPtr domain_config) {
// Now construct the domain.
std::string name;
std::string key_name;