mirror of
https://gitlab.isc.org/isc-projects/kea
synced 2025-08-31 22:15:23 +00:00
[master] Merge branch 'master' into trac3336
This commit is contained in:
22
ChangeLog
22
ChangeLog
@@ -1,3 +1,23 @@
|
||||
788. [func] tomek
|
||||
DHCPv4 server: New parameter added to configure.ac: --with-kea-config.
|
||||
It allows selecting configuration backend and accepts one of two
|
||||
values: BUNDY, which uses Bundy (former BIND10) framework as Kea
|
||||
0.8 did, or JSON, which reads configuration from a JSON file.
|
||||
(Trac #3399, git 6e4dd3ae58c091ba0fd64c87fa8d7c268210f99b)
|
||||
|
||||
787. [func] marcin
|
||||
DHCPv6 server: Implemented dynamic reconfiguration of the server,
|
||||
triggered when the SIGHUP signal is received by the server's
|
||||
process. Also, server performs a graceful shut down when SIGINT
|
||||
or SIGTERM signal is received.
|
||||
(Trac #3406, git 3be60fa6ac521aecae6ae92d26dc03792bc76903)
|
||||
|
||||
786. [func] tmark
|
||||
DHCP-DDNS now supports DDNS updates with TSIG. Please refer to the
|
||||
Kea Guide for details. Prior to this TSIG keys could be defined but
|
||||
were not used.
|
||||
(Trac #3432, git 80fea12a53d1e832d4e7b710ca6ea613300f73ea)
|
||||
|
||||
785. [bug] marcin
|
||||
DHCPv6 server avoids collisions between prefixes that are allocated
|
||||
as a result of receiving hints from the clients. Previously the
|
||||
@@ -18,7 +38,7 @@
|
||||
(Trac #3268, git bd60252e679f19b062f61926647f661ab169f21c)
|
||||
|
||||
783. [func]* tomek
|
||||
b10-dhcp6: New parameter added to configure: --with-kea-config.
|
||||
DHCPv6 server: New parameter added to configure: --with-kea-config.
|
||||
It allows selecting configuration backend and accepts one of two
|
||||
values: BUNDY, which uses Bundy (former BIND10 framework as Kea
|
||||
0.8 did, or JSON, which reads configuration from a JSON file.
|
||||
|
@@ -55,6 +55,7 @@
|
||||
* - @subpage dhcpv4OptionsParse
|
||||
* - @subpage dhcpv4DDNSIntegration
|
||||
* - @subpage dhcpv4Classifier
|
||||
* - @subpage dhcpv4ConfigBackend
|
||||
* - @subpage dhcpv4Other
|
||||
* - @subpage dhcp6
|
||||
* - @subpage dhcpv6Session
|
||||
@@ -64,6 +65,7 @@
|
||||
* - @subpage dhcpv6OptionsParse
|
||||
* - @subpage dhcpv6Classifier
|
||||
* - @subpage dhcpv6ConfigBackend
|
||||
* - @subpage dhcpv6SignalBasedReconfiguration
|
||||
* - @subpage dhcpv6Other
|
||||
* - @subpage d2
|
||||
* - @subpage d2CPL
|
||||
|
38
doc/examples/kea4/several-subnets.json
Normal file
38
doc/examples/kea4/several-subnets.json
Normal file
@@ -0,0 +1,38 @@
|
||||
# This is an example configuration file for DHCPv4 server in Kea.
|
||||
# It's a basic scenario with three IPv4 subnets configured. In each
|
||||
# subnet, there's a smaller pool of dynamic addresses.
|
||||
|
||||
{ "Dhcp4":
|
||||
|
||||
{
|
||||
# Kea is told to listen on eth0 interface only.
|
||||
"interfaces": [ "eth0" ],
|
||||
|
||||
# We need to specify lease type. As of May 2014, three backends are supported:
|
||||
# memfile, mysql and pgsql. We'll just use memfile, because it doesn't require
|
||||
# any prior set up.
|
||||
"lease-database": {
|
||||
"type": "memfile"
|
||||
},
|
||||
|
||||
# Addresses will be assigned with the valid lifetimes being 4000.
|
||||
# Client is told to start renewing after 1000 seconds. If the server
|
||||
# does not repond after 2000 seconds since the lease was granted, client
|
||||
# is supposed to start REBIND procedure (emergency renewal that allows
|
||||
# switching to a different server).
|
||||
"valid-lifetime": 4000,
|
||||
"renew-timer": 1000,
|
||||
"rebind-timer": 2000,
|
||||
|
||||
# The following list defines subnets. Each subnet consists of at
|
||||
# least subnet and pool entries.
|
||||
"subnet4": [
|
||||
{ "pool": [ "192.0.2.1 - 192.0.2.200" ],
|
||||
"subnet": "192.0.2.0/24" },
|
||||
{ "pool": [ "192.0.3.100 - 192.0.3.200" ],
|
||||
"subnet": "192.0.3.0/24" },
|
||||
{ "pool": [ "192.0.4.1 - 192.0.4.254" ],
|
||||
"subnet": "192.0.4.0/24" } ]
|
||||
}
|
||||
|
||||
}
|
34
doc/examples/kea4/single-subnet.json
Normal file
34
doc/examples/kea4/single-subnet.json
Normal file
@@ -0,0 +1,34 @@
|
||||
# This is an example configuration file for the DHCPv4 server in Kea.
|
||||
# It is a basic scenario with one IPv4 subnet configured. The subnet
|
||||
# contains a single pool of dynamically allocated addresses.
|
||||
|
||||
{ "Dhcp4":
|
||||
|
||||
{
|
||||
# Kea is told to listen on eth0 interface only.
|
||||
"interfaces": [ "eth0" ],
|
||||
|
||||
# We need to specify lease type. As of May 2014, three backends are supported:
|
||||
# memfile, mysql and pgsql. We'll just use memfile, because it doesn't require
|
||||
# any prior set up.
|
||||
"lease-database": {
|
||||
"type": "memfile"
|
||||
},
|
||||
|
||||
# Addresses will be assigned with valid lifetimes being 4000. Client
|
||||
# is told to start renewing after 1000 seconds. If the server does not respond
|
||||
# after 2000 seconds since the lease was granted, client is supposed
|
||||
# to start REBIND procedure (emergency renewal that allows switching
|
||||
# to a different server).
|
||||
"valid-lifetime": 4000,
|
||||
"renew-timer": 1000,
|
||||
"rebind-timer": 2000,
|
||||
|
||||
# The following list defines subnets. We have only one subnet
|
||||
# here.
|
||||
"subnet4": [
|
||||
{ "pool": [ "192.0.2.1 - 192.0.2.200" ],
|
||||
"subnet": "192.0.2.0/24" } ]
|
||||
}
|
||||
|
||||
}
|
@@ -5372,75 +5372,118 @@ corresponding values in the DHCP servers' "dhcp-ddns" configuration section.
|
||||
|
||||
<section id="d2-tsig-key-list-config">
|
||||
<title>TSIG Key List</title>
|
||||
<note>
|
||||
<simpara>
|
||||
While this section may be displayed and edited using bindctl, the use
|
||||
of TSIG in actual communications between D2 and DNS servers is not yet
|
||||
implemented.
|
||||
</simpara>
|
||||
</note>
|
||||
<para>
|
||||
DDNS protocol can be conducted with or without TSIG as defined in
|
||||
RFC 2845. This configuration section allows the administrator to
|
||||
define the dictionary of TSIG keys to may be used. To use TSIG
|
||||
when working with a specific DDNS Domain that key must be defined in
|
||||
the TSIG Key List and referenced by name in that domain's entry in
|
||||
the DDNS catalog.
|
||||
</para>
|
||||
A DDNS protocol exchange can be conducted with or without TSIG
|
||||
(defined in <ulink url="http://tools.ietf/org/html/rfc2845">RFC
|
||||
2845</ulink>). This configuration section allows the administrator
|
||||
to define the set of TSIG keys that may be used in such
|
||||
exchanges.</para>
|
||||
|
||||
<para>To use TSIG when updating entries in a DNS Domain,
|
||||
a key must be defined in the TSIG Key List and referenced by
|
||||
name in that domain's configuration entry. When D2 matches a
|
||||
change request to a domain, it checks whether the domain has
|
||||
a TSIG key associated with it. If so, D2 will use that key to
|
||||
sign DNS update messages sent to and verify repsonses received
|
||||
from the domain's DNS server(s). For each TSIG key required by
|
||||
the DNS servers that D2 will be working with there must be a
|
||||
corresponding TSIG key in the TSIG Key list.</para>
|
||||
|
||||
<para>
|
||||
As one might gather from its name, this section is a list of
|
||||
TSIG keys. Each key has three parameters:
|
||||
As one might gather from the name, the tsig_key section of the
|
||||
D2 configuration lists the TSIG keys. Each entry describes a
|
||||
TSIG key used by one or more DNS servers to authenticate requests
|
||||
and sign responses. Every entry in the list has three parameters:
|
||||
<itemizedlist>
|
||||
<listitem>
|
||||
<simpara>
|
||||
<command>name</command> —
|
||||
is a unique text label used to identify the this key within the
|
||||
list. It is this value that is used to specify which key (if any)
|
||||
should be used with a specific DNS server. So long as it is
|
||||
unique, its content is arbitrary. It cannot be blank.
|
||||
a unique text label used to identify this key within the
|
||||
list. This value is used to specify which key (if any) should be
|
||||
used when updating a specific domain. So long as it is unique its
|
||||
content is arbitrary, although for clarity and ease of maintenance
|
||||
it is recommended that it match the name used on the DNS server(s).
|
||||
It cannot be blank.
|
||||
</simpara>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<simpara>
|
||||
<command>algorithm</command> —
|
||||
specifies which hashing algorithm should be used with this
|
||||
key. This value is not currently used.
|
||||
key. This value must specify the same algorithm used for the
|
||||
key on the DNS server(s). The supported algorithms are listed below:
|
||||
<itemizedlist>
|
||||
<listitem>
|
||||
<command>HMAC-MD5</command>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<command>HMAC-SHA1</command>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<command>HMAC-SHA224</command>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<command>HMAC-SHA256</command>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<command>HMAC-SHA384</command>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<command>HMAC-SHA512</command>
|
||||
</listitem>
|
||||
</itemizedlist>
|
||||
This value is not case sensitive.
|
||||
</simpara>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<simpara>
|
||||
<command>secret</command> —
|
||||
is used to specify the shared secret key code for this key. This
|
||||
value is not currently used.
|
||||
is used to specify the shared secret key code for this key. This value is
|
||||
case sensitive and must exactly match the value specified on the DNS server(s).
|
||||
It is a base64-encoded text value.
|
||||
</simpara>
|
||||
</listitem>
|
||||
</itemizedlist>
|
||||
</para>
|
||||
<para>
|
||||
As an example, suppose that a domain D2 will be updating is
|
||||
maintained by a BIND9 DNS server which requires dynamic updates
|
||||
to be secured with TSIG. Suppose further that the entry for
|
||||
the TSIG key in BIND9's named.conf file looks like this:
|
||||
<screen>
|
||||
:
|
||||
key "key.four.example.com." {
|
||||
algorithm hmac-sha224;
|
||||
secret "bZEG7Ow8OgAUPfLWV3aAUQ==";
|
||||
};
|
||||
:
|
||||
</screen>
|
||||
By default, the TSIG Key list is empty:
|
||||
<screen>
|
||||
<userinput>> config show DhcpDdns/tsig_keys</userinput>
|
||||
DhcpDdns/tsig_keys [] list (default)
|
||||
</screen>
|
||||
To create a new key in the list, one must first add a new key element:
|
||||
We must first create a new key in the list:
|
||||
<screen>
|
||||
<userinput>> config add DhcpDdns/tsig_keys</userinput>
|
||||
</screen>
|
||||
Displaying the new element, reveals this:
|
||||
Displaying the new element, reveals:
|
||||
<screen>
|
||||
<userinput>> config show DhcpDdns/tsig_keys[0]</userinput>
|
||||
DhcpDdns/tsig_keys[0]/name "" string (default)
|
||||
DhcpDdns/tsig_keys[0]/algorithm "hmac_md5" string (modified)
|
||||
DhcpDdns/tsig_keys[0]/algorithm "HMAC-MD5" string (modified)
|
||||
DhcpDdns/tsig_keys[0]/secret "" string (default)
|
||||
</screen>
|
||||
Populating the key name and secret, while accepting the default value
|
||||
for alogorithm:
|
||||
Now set all three values to match BIND9's key:
|
||||
<screen>
|
||||
<userinput>> config set DhcpDdns/tsig_keys[0]/name "key1.example.com"</userinput>
|
||||
<userinput>> config set DhcpDdns/tsig_keys[0]/secret "123456789"</userinput>
|
||||
<userinput>> config set DhcpDdns/tsig_keys[0]/name "key.four.example.com"</userinput>
|
||||
<userinput>> config set DhcpDdns/tsig_keys[0]/algorithm "HMAC-SHA224"</userinput>
|
||||
<userinput>> config set DhcpDdns/tsig_keys[0]/secret "bZEG7Ow8OgAUPfLWV3aAUQ=="</userinput>
|
||||
<userinput>> config commit</userinput>
|
||||
</screen>
|
||||
</para>
|
||||
These steps would be repeated for each TSIG key needed. Note that the same TSIG key
|
||||
can be used with more than one domain.
|
||||
</section> <!-- "d2-tsig-key-list-config" -->
|
||||
|
||||
<section id="d2-forward-ddns-config">
|
||||
@@ -5458,8 +5501,6 @@ DhcpDdns/forward_ddns/ddns_domains [] list (default)
|
||||
</para>
|
||||
<section id="add-forward-ddns-domain">
|
||||
<title>Adding Forward DDNS Domains</title>
|
||||
|
||||
|
||||
<para>
|
||||
A forward DDNS Domain maps a forward DNS zone to a set of DNS servers
|
||||
which maintain the forward DNS data for that zone. You will need one
|
||||
@@ -5638,8 +5679,8 @@ DhcpDdns/reverse_ddns/ddns_domains [] list (default)
|
||||
<simpara>
|
||||
<command>key_name</command> —
|
||||
If TSIG should be used with this domain's servers, then this
|
||||
value should be the name of the key from within the TSIG Key List
|
||||
to use. If the value is blank (the default), TSIG will not be
|
||||
value should be the name of that key from the TSIG Key List.
|
||||
If the value is blank (the default), TSIG will not be
|
||||
used in DDNS conversations with this domain's servers. Currently
|
||||
this value is not used as TSIG has not been implemented.
|
||||
</simpara>
|
||||
|
@@ -124,15 +124,59 @@ operator<<(std::ostream& os, const D2Params& config) {
|
||||
}
|
||||
|
||||
// *********************** TSIGKeyInfo *************************
|
||||
// Note these values match correpsonding values for Bind9's
|
||||
// dnssec-keygen
|
||||
const char* TSIGKeyInfo::HMAC_MD5_STR = "HMAC-MD5";
|
||||
const char* TSIGKeyInfo::HMAC_SHA1_STR = "HMAC-SHA1";
|
||||
const char* TSIGKeyInfo::HMAC_SHA224_STR = "HMAC-SHA224";
|
||||
const char* TSIGKeyInfo::HMAC_SHA256_STR = "HMAC-SHA256";
|
||||
const char* TSIGKeyInfo::HMAC_SHA384_STR = "HMAC-SHA384";
|
||||
const char* TSIGKeyInfo::HMAC_SHA512_STR = "HMAC-SHA512";
|
||||
|
||||
TSIGKeyInfo::TSIGKeyInfo(const std::string& name, const std::string& algorithm,
|
||||
const std::string& secret)
|
||||
:name_(name), algorithm_(algorithm), secret_(secret) {
|
||||
:name_(name), algorithm_(algorithm), secret_(secret), tsig_key_() {
|
||||
remakeKey();
|
||||
}
|
||||
|
||||
TSIGKeyInfo::~TSIGKeyInfo() {
|
||||
}
|
||||
|
||||
const dns::Name&
|
||||
TSIGKeyInfo::stringToAlgorithmName(const std::string& algorithm_id) {
|
||||
if (boost::iequals(algorithm_id, HMAC_MD5_STR)) {
|
||||
return (dns::TSIGKey::HMACMD5_NAME());
|
||||
} else if (boost::iequals(algorithm_id, HMAC_SHA1_STR)) {
|
||||
return (dns::TSIGKey::HMACSHA1_NAME());
|
||||
} else if (boost::iequals(algorithm_id, HMAC_SHA224_STR)) {
|
||||
return (dns::TSIGKey::HMACSHA224_NAME());
|
||||
} else if (boost::iequals(algorithm_id, HMAC_SHA256_STR)) {
|
||||
return (dns::TSIGKey::HMACSHA256_NAME());
|
||||
} else if (boost::iequals(algorithm_id, HMAC_SHA384_STR)) {
|
||||
return (dns::TSIGKey::HMACSHA384_NAME());
|
||||
} else if (boost::iequals(algorithm_id, HMAC_SHA512_STR)) {
|
||||
return (dns::TSIGKey::HMACSHA512_NAME());
|
||||
}
|
||||
|
||||
isc_throw(BadValue, "Unknown TSIG Key algorithm: " << algorithm_id);
|
||||
}
|
||||
|
||||
void
|
||||
TSIGKeyInfo::remakeKey() {
|
||||
try {
|
||||
// Since our secret value is base64 encoded already, we need to
|
||||
// build the input string for the appropriate TSIGKey constructor.
|
||||
// If secret isn't a valid base64 value, the constructor will throw.
|
||||
std::ostringstream stream;
|
||||
stream << dns::Name(name_).toText() << ":"
|
||||
<< secret_ << ":"
|
||||
<< stringToAlgorithmName(algorithm_);
|
||||
|
||||
tsig_key_.reset(new dns::TSIGKey(stream.str()));
|
||||
} catch (const std::exception& ex) {
|
||||
isc_throw(D2CfgError, "Cannot make TSIGKey: " << ex.what());
|
||||
}
|
||||
}
|
||||
|
||||
// *********************** DnsServerInfo *************************
|
||||
|
||||
@@ -164,14 +208,25 @@ operator<<(std::ostream& os, const DnsServerInfo& server) {
|
||||
|
||||
// *********************** DdnsDomain *************************
|
||||
|
||||
DdnsDomain::DdnsDomain(const std::string& name, const std::string& key_name,
|
||||
DnsServerInfoStoragePtr servers)
|
||||
: name_(name), key_name_(key_name), servers_(servers) {
|
||||
DdnsDomain::DdnsDomain(const std::string& name,
|
||||
DnsServerInfoStoragePtr servers,
|
||||
const TSIGKeyInfoPtr& tsig_key_info)
|
||||
: name_(name), servers_(servers),
|
||||
tsig_key_info_(tsig_key_info) {
|
||||
}
|
||||
|
||||
DdnsDomain::~DdnsDomain() {
|
||||
}
|
||||
|
||||
const std::string
|
||||
DdnsDomain::getKeyName() const {
|
||||
if (tsig_key_info_) {
|
||||
return (tsig_key_info_->getName());
|
||||
}
|
||||
|
||||
return ("");
|
||||
}
|
||||
|
||||
// *********************** DdnsDomainLstMgr *************************
|
||||
|
||||
const char* DdnsDomainListMgr::wildcard_domain_name_ = "*";
|
||||
@@ -308,9 +363,6 @@ TSIGKeyInfoParser::build(isc::data::ConstElementPtr key_config) {
|
||||
local_scalars_.getParam("algorithm", algorithm);
|
||||
local_scalars_.getParam("secret", secret);
|
||||
|
||||
// @todo Validation here is very superficial. This will expand as TSIG
|
||||
// Key use is more fully implemented.
|
||||
|
||||
// Name cannot be blank.
|
||||
if (name.empty()) {
|
||||
isc_throw(D2CfgError, "TSIG Key Info must specify name");
|
||||
@@ -361,9 +413,6 @@ TSIGKeyInfoParser::createConfigParser(const std::string& config_id) {
|
||||
|
||||
void
|
||||
TSIGKeyInfoParser::commit() {
|
||||
/// @todo if at some point TSIG keys need some form of runtime resource
|
||||
/// initialization, such as creating some sort of hash instance in
|
||||
/// crytpolib. Once TSIG is fully implemented under Trac #3432 we'll know.
|
||||
}
|
||||
|
||||
// *********************** TSIGKeyInfoListParser *************************
|
||||
@@ -606,18 +655,26 @@ DdnsDomainParser::build(isc::data::ConstElementPtr domain_config) {
|
||||
isc_throw(D2CfgError, "Duplicate domain specified:" << name);
|
||||
}
|
||||
|
||||
// Key name is optional. If it is not blank, then validate it against
|
||||
// the defined list of keys.
|
||||
// Key name is optional. If it is not blank, then find the key in the
|
||||
/// list of defined keys.
|
||||
TSIGKeyInfoPtr tsig_key_info;
|
||||
local_scalars_.getParam("key_name", key_name, DCfgContextBase::OPTIONAL);
|
||||
if (!key_name.empty()) {
|
||||
if ((!keys_) || (keys_->find(key_name) == keys_->end())) {
|
||||
isc_throw(D2CfgError, "DdnsDomain :" << name <<
|
||||
" specifies and undefined key:" << key_name);
|
||||
if (keys_) {
|
||||
TSIGKeyInfoMap::iterator kit = keys_->find(key_name);
|
||||
if (kit != keys_->end()) {
|
||||
tsig_key_info = kit->second;
|
||||
}
|
||||
}
|
||||
|
||||
if (!tsig_key_info) {
|
||||
isc_throw(D2CfgError, "DdnsDomain " << name <<
|
||||
" specifies an undefined key: " << key_name);
|
||||
}
|
||||
}
|
||||
|
||||
// Instantiate the new domain and add it to domain storage.
|
||||
DdnsDomainPtr domain(new DdnsDomain(name, key_name, local_servers_));
|
||||
DdnsDomainPtr domain(new DdnsDomain(name, local_servers_, tsig_key_info));
|
||||
|
||||
// Add the new domain to the domain storage.
|
||||
(*domains_)[name] = domain;
|
||||
|
@@ -19,6 +19,7 @@
|
||||
#include <d2/d2_asio.h>
|
||||
#include <d2/d_cfg_mgr.h>
|
||||
#include <dhcpsrv/dhcp_parsers.h>
|
||||
#include <dns/tsig.h>
|
||||
#include <exceptions/exceptions.h>
|
||||
|
||||
#include <boost/foreach.hpp>
|
||||
@@ -264,22 +265,53 @@ typedef boost::shared_ptr<D2Params> D2ParamsPtr;
|
||||
|
||||
/// @brief Represents a TSIG Key.
|
||||
///
|
||||
/// Currently, this is simple storage class containing the basic attributes of
|
||||
/// a TSIG Key. It is intended primarily as a reference for working with
|
||||
/// actual keys and may eventually be replaced by isc::dns::TSIGKey. TSIG Key
|
||||
/// functionality at this stage is strictly limited to configuration parsing.
|
||||
/// @todo full functionality for using TSIG during DNS updates will be added
|
||||
/// in a future release.
|
||||
/// Acts as both a storage class containing the basic attributes which
|
||||
/// describe a TSIG Key, as well as owning and providing access to an
|
||||
/// instance of the actual key (@ref isc::dns::TSIGKey) that can be used
|
||||
/// by the IO layer for signing and verifying messages.
|
||||
///
|
||||
class TSIGKeyInfo {
|
||||
public:
|
||||
/// @brief Defines string values for the supported TSIG algorithms
|
||||
//@{
|
||||
static const char* HMAC_MD5_STR;
|
||||
static const char* HMAC_SHA1_STR;
|
||||
static const char* HMAC_SHA256_STR;
|
||||
static const char* HMAC_SHA224_STR;
|
||||
static const char* HMAC_SHA384_STR;
|
||||
static const char* HMAC_SHA512_STR;
|
||||
//}@
|
||||
|
||||
/// @brief Constructor
|
||||
///
|
||||
/// @param name the unique label used to identify this key
|
||||
/// @param algorithm the name of the encryption alogirthm this key uses.
|
||||
/// (@todo This will be a fixed list of choices)
|
||||
/// @param algorithm the id of the encryption alogirthm this key uses.
|
||||
/// Currently supported values are (case insensitive):
|
||||
/// -# "HMAC-MD5"
|
||||
/// -# "HMAC-SHA1"
|
||||
/// -# "HMAC-SHA224"
|
||||
/// -# "HMAC-SHA256"
|
||||
/// -# "HMAC-SHA384"
|
||||
/// -# "HMAC-SHA512"
|
||||
///
|
||||
/// @param secret the secret component of this key
|
||||
/// @param secret The base-64 encoded secret component for this key.
|
||||
/// (A suitable string for use here could be obtained by running the
|
||||
/// BIND 9 dnssec-keygen program; the contents of resulting key file
|
||||
/// will look similar to:
|
||||
/// @code
|
||||
/// Private-key-format: v1.3
|
||||
/// Algorithm: 157 (HMAC_MD5)
|
||||
/// Key: LSWXnfkKZjdPJI5QxlpnfQ==
|
||||
/// Bits: AAA=
|
||||
/// Created: 20140515143700
|
||||
/// Publish: 20140515143700
|
||||
/// Activate: 20140515143700
|
||||
/// @endcode
|
||||
/// where the value the "Key:" entry is the secret component of the key.)
|
||||
///
|
||||
/// @throw D2CfgError if values supplied are invalid:
|
||||
/// name cannot be blank, algorithm must be a supported value,
|
||||
/// secret must be a non-blank, base64 encoded string.
|
||||
TSIGKeyInfo(const std::string& name, const std::string& algorithm,
|
||||
const std::string& secret);
|
||||
|
||||
@@ -293,7 +325,7 @@ public:
|
||||
return (name_);
|
||||
}
|
||||
|
||||
/// @brief Getter which returns the key's algorithm.
|
||||
/// @brief Getter which returns the key's algorithm string ID
|
||||
///
|
||||
/// @return returns the algorithm as as std::string.
|
||||
const std::string getAlgorithm() const {
|
||||
@@ -307,18 +339,55 @@ public:
|
||||
return (secret_);
|
||||
}
|
||||
|
||||
/// @brief Getter which returns the TSIG key used to sign and verify
|
||||
/// messages
|
||||
///
|
||||
/// @return const pointer reference to dns::TSIGKey.
|
||||
const dns::TSIGKeyPtr& getTSIGKey() const {
|
||||
return (tsig_key_);
|
||||
}
|
||||
|
||||
/// @brief Converts algorithm id to dns::TSIGKey algorithm dns::Name
|
||||
///
|
||||
/// @param algorithm_id string value to translate into an algorithm name.
|
||||
/// Currently supported values are (case insensitive):
|
||||
/// -# "HMAC-MD5"
|
||||
/// -# "HMAC-SHA1"
|
||||
/// -# "HMAC-SHA224"
|
||||
/// -# "HMAC-SHA256"
|
||||
/// -# "HMAC-SHA384"
|
||||
/// -# "HMAC-SHA512"
|
||||
///
|
||||
/// @return const reference to a dns::Name containing the algorithm name
|
||||
/// @throw BadValue if ID isn't recognized.
|
||||
static const dns::Name& stringToAlgorithmName(const std::string&
|
||||
algorithm_id);
|
||||
|
||||
private:
|
||||
/// @brief Creates the actual TSIG key instance member
|
||||
///
|
||||
/// Replaces this tsig_key member with a key newly created using the key
|
||||
/// name, algorithm id, and secret.
|
||||
/// This method is currently only called by the constructor, however it
|
||||
/// could be called post-construction should keys ever support expiration.
|
||||
///
|
||||
/// @throw D2CfgError with an explanation if the key could not be created.
|
||||
void remakeKey();
|
||||
|
||||
/// @brief The name of the key.
|
||||
///
|
||||
/// This value is the unique identifier that domains use to
|
||||
/// to specify which TSIG key they need.
|
||||
std::string name_;
|
||||
|
||||
/// @brief The algorithm that should be used for this key.
|
||||
/// @brief The string ID of the algorithm that should be used for this key.
|
||||
std::string algorithm_;
|
||||
|
||||
/// @brief The secret value component of this key.
|
||||
/// @brief The base64 encoded string secret value component of this key.
|
||||
std::string secret_;
|
||||
|
||||
/// @brief The actual TSIG key.
|
||||
dns::TSIGKeyPtr tsig_key_;
|
||||
};
|
||||
|
||||
/// @brief Defines a pointer for TSIGKeyInfo instances.
|
||||
@@ -454,10 +523,12 @@ public:
|
||||
/// @brief Constructor
|
||||
///
|
||||
/// @param name is the domain name of the domain.
|
||||
/// @param key_name is the TSIG key name for use with this domain.
|
||||
/// @param servers is the list of server(s) supporting this domain.
|
||||
DdnsDomain(const std::string& name, const std::string& key_name,
|
||||
DnsServerInfoStoragePtr servers);
|
||||
/// @param tsig_key_info pointer to the TSIGKeyInfo for the dommain's key
|
||||
/// It defaults to an empty pointer, signifying the domain has no key.
|
||||
DdnsDomain(const std::string& name,
|
||||
DnsServerInfoStoragePtr servers,
|
||||
const TSIGKeyInfoPtr& tsig_key_info = TSIGKeyInfoPtr());
|
||||
|
||||
/// @brief Destructor
|
||||
virtual ~DdnsDomain();
|
||||
@@ -469,12 +540,11 @@ public:
|
||||
return (name_);
|
||||
}
|
||||
|
||||
/// @brief Getter which returns the domain's TSIG key name.
|
||||
/// @brief Convenience method which returns the domain's TSIG key name.
|
||||
///
|
||||
/// @return returns the key name in an std::string.
|
||||
const std::string getKeyName() const {
|
||||
return (key_name_);
|
||||
}
|
||||
/// @return returns the key name in an std::string. If domain has no
|
||||
/// TSIG key, the string will empty.
|
||||
const std::string getKeyName() const;
|
||||
|
||||
/// @brief Getter which returns the domain's list of servers.
|
||||
///
|
||||
@@ -483,15 +553,24 @@ public:
|
||||
return (servers_);
|
||||
}
|
||||
|
||||
/// @brief Getter which returns the domain's TSIGKey info
|
||||
///
|
||||
/// @return returns the pointer to the server storage. If the domain
|
||||
/// is not configured to use TSIG the pointer will be empty.
|
||||
const TSIGKeyInfoPtr& getTSIGKeyInfo() {
|
||||
return (tsig_key_info_);
|
||||
}
|
||||
|
||||
private:
|
||||
/// @brief The domain name of the domain.
|
||||
std::string name_;
|
||||
|
||||
/// @brief The name of the TSIG key for use with this domain.
|
||||
std::string key_name_;
|
||||
|
||||
/// @brief The list of server(s) supporting this domain.
|
||||
DnsServerInfoStoragePtr servers_;
|
||||
|
||||
/// @brief Pointer to domain's the TSIGKeyInfo.
|
||||
/// Value is empty if the domain is not configured for TSIG.
|
||||
TSIGKeyInfoPtr tsig_key_info_;
|
||||
};
|
||||
|
||||
/// @brief Defines a pointer for DdnsDomain instances.
|
||||
|
@@ -1,4 +1,4 @@
|
||||
// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
|
||||
// Copyright (C) 2013-2014 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
|
||||
@@ -108,7 +108,8 @@ D2UpdateMessage::addRRset(const UpdateMsgSection section,
|
||||
}
|
||||
|
||||
void
|
||||
D2UpdateMessage::toWire(AbstractMessageRenderer& renderer) {
|
||||
D2UpdateMessage::toWire(AbstractMessageRenderer& renderer,
|
||||
TSIGContext* const tsig_context) {
|
||||
// We are preparing the wire format of the message, meaning
|
||||
// that this message will be sent as a request to the DNS.
|
||||
// Therefore, we expect that this message is a REQUEST.
|
||||
@@ -122,16 +123,29 @@ D2UpdateMessage::toWire(AbstractMessageRenderer& renderer) {
|
||||
isc_throw(InvalidZoneSection, "Zone section of the DNS Update message"
|
||||
" must comprise exactly one record (RFC2136, section 2.3)");
|
||||
}
|
||||
message_.toWire(renderer);
|
||||
message_.toWire(renderer, tsig_context);
|
||||
}
|
||||
|
||||
void
|
||||
D2UpdateMessage::fromWire(isc::util::InputBuffer& buffer) {
|
||||
D2UpdateMessage::fromWire(const void* received_data, size_t bytes_received,
|
||||
dns::TSIGContext* const tsig_context) {
|
||||
// First, use the underlying dns::Message implementation to get the
|
||||
// contents of the DNS response message. Note that it may or may
|
||||
// not be the message that we are interested in, but needs to be
|
||||
// parsed so as we can check its ID, Opcode etc.
|
||||
message_.fromWire(buffer);
|
||||
isc::util::InputBuffer received_data_buffer(received_data, bytes_received);
|
||||
message_.fromWire(received_data_buffer);
|
||||
|
||||
// If tsig_contex is not NULL, then we need to verify the message.
|
||||
if (tsig_context) {
|
||||
TSIGError error = tsig_context->verify(message_.getTSIGRecord(),
|
||||
received_data, bytes_received);
|
||||
if (error != TSIGError::NOERROR()) {
|
||||
isc_throw(TSIGVerifyError, "TSIG verification failed: "
|
||||
<< error.toText());
|
||||
}
|
||||
}
|
||||
|
||||
// This class exposes the getZone() function. This function will return
|
||||
// pointer to the D2Zone object if non-empty Zone section exists in the
|
||||
// received message. It will return NULL pointer if it doesn't exist.
|
||||
|
@@ -1,4 +1,4 @@
|
||||
// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
|
||||
// Copyright (C) 2013-2014 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
|
||||
@@ -21,6 +21,7 @@
|
||||
#include <dns/rcode.h>
|
||||
#include <dns/rrclass.h>
|
||||
#include <dns/rrset.h>
|
||||
#include <dns/tsig.h>
|
||||
|
||||
#include <map>
|
||||
|
||||
@@ -63,6 +64,17 @@ public:
|
||||
isc::Exception(file, line, what) {}
|
||||
};
|
||||
|
||||
/// @brief Exception indicating that a signed, inbound message failed to verfiy
|
||||
///
|
||||
/// This exception is thrown when TSIG verification of a DNS server's response
|
||||
/// fails.
|
||||
class TSIGVerifyError : public Exception {
|
||||
public:
|
||||
TSIGVerifyError(const char* file, size_t line, const char* what) :
|
||||
isc::Exception(file, line, what) {}
|
||||
};
|
||||
|
||||
|
||||
class D2UpdateMessage;
|
||||
|
||||
/// @brief Pointer to the DNS Update Message.
|
||||
@@ -250,6 +262,9 @@ public:
|
||||
/// RRs respectively. The ZOCOUNT must be equal to 1 because RFC2136
|
||||
/// requires that the message comprises exactly one Zone record.
|
||||
///
|
||||
/// If given a TSIG context, this method will pass the context down into
|
||||
/// dns::Message.toWire() method which signs the message using the context.
|
||||
///
|
||||
/// This function does not guarantee exception safety. However, exceptions
|
||||
/// should be rare because @c D2UpdateMessage class API prevents invalid
|
||||
/// use of the class. The typical case, when this function may throw an
|
||||
@@ -260,18 +275,23 @@ public:
|
||||
///
|
||||
/// @param renderer A renderer object used to generate the message wire
|
||||
/// format.
|
||||
void toWire(dns::AbstractMessageRenderer& renderer);
|
||||
/// @param tsig_ctx A TSIG context that is to be used for signing the
|
||||
/// message. If NULL the message will not be signed.
|
||||
void toWire(dns::AbstractMessageRenderer& renderer,
|
||||
dns::TSIGContext* const tsig_ctx = NULL);
|
||||
|
||||
/// @brief Decode incoming message from the wire format.
|
||||
///
|
||||
/// This function decodes the DNS Update message stored in the buffer
|
||||
/// specified by the function argument. In the first turn, this function
|
||||
/// parses message header and extracts the section counters: ZOCOUNT,
|
||||
/// PRCOUNT, UPCOUNT and ADCOUNT. Using these counters, function identifies
|
||||
/// message sections, which follow message header. These sections can be
|
||||
/// later accessed using: @c D2UpdateMessage::getZone,
|
||||
/// @c D2UpdateMessage::beginSection and @c D2UpdateMessage::endSection
|
||||
/// functions.
|
||||
/// specified by the function argument. If given a TSIG context, then
|
||||
/// the function will first attempt to use that context to verify the
|
||||
/// message signature. If verification fails a TSIGVefiryError exception
|
||||
/// will be thrown. The function then parses message header and extracts
|
||||
/// the section counters: ZOCOUNT, PRCOUNT, UPCOUNT and ADCOUNT. Using
|
||||
/// these counters, function identifies message sections, which follow
|
||||
/// message header. These sections can be later accessed using:
|
||||
/// @c D2UpdateMessage::getZone, @c D2UpdateMessage::beginSection and
|
||||
/// @c D2UpdateMessage::endSection functions.
|
||||
///
|
||||
/// This function is NOT exception safe. It signals message decoding errors
|
||||
/// through exceptions. Message decoding error may occur if the received
|
||||
@@ -282,8 +302,12 @@ public:
|
||||
/// message is the server response.
|
||||
/// - The number of records in the Zone section is greater than 1.
|
||||
///
|
||||
/// @param buffer input buffer, holding DNS Update message to be parsed.
|
||||
void fromWire(isc::util::InputBuffer& buffer);
|
||||
/// @param received_data buffer holding DNS Update message to be parsed.
|
||||
/// @param bytes_received the number of bytes in received_data
|
||||
/// @param tsig_context A TSIG context that is to be used to verify the
|
||||
/// message. If NULL TSIG verification will not be attempted.
|
||||
void fromWire(const void* received_data, size_t bytes_received,
|
||||
dns::TSIGContext* const tsig_context = NULL);
|
||||
//@}
|
||||
|
||||
private:
|
||||
|
@@ -44,7 +44,7 @@
|
||||
"item_name": "tsig_key",
|
||||
"item_type": "map",
|
||||
"item_optional": false,
|
||||
"item_default": {"algorithm" : "hmac_md5"},
|
||||
"item_default": {"algorithm" : "HMAC-MD5"},
|
||||
"map_item_spec": [
|
||||
{
|
||||
"item_name": "name",
|
||||
|
@@ -1,4 +1,4 @@
|
||||
// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
|
||||
// Copyright (C) 2013-2014 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
|
||||
@@ -60,6 +60,8 @@ public:
|
||||
DNSClient::Callback* callback_;
|
||||
// A Transport Layer protocol used to communicate with a DNS.
|
||||
DNSClient::Protocol proto_;
|
||||
// TSIG context used to sign outbound and verify inbound messages.
|
||||
dns::TSIGContextPtr tsig_context_;
|
||||
|
||||
// Constructor and Destructor
|
||||
DNSClientImpl(D2UpdateMessagePtr& response_placeholder,
|
||||
@@ -73,12 +75,13 @@ public:
|
||||
// type, representing a response from the server is set.
|
||||
virtual void operator()(asiodns::IOFetch::Result result);
|
||||
|
||||
// Starts asynchronous DNS Update.
|
||||
// Starts asynchronous DNS Update using TSIG.
|
||||
void doUpdate(asiolink::IOService& io_service,
|
||||
const asiolink::IOAddress& ns_addr,
|
||||
const uint16_t ns_port,
|
||||
D2UpdateMessage& update,
|
||||
const unsigned int wait);
|
||||
const unsigned int wait,
|
||||
const dns::TSIGKeyPtr& tsig_key);
|
||||
|
||||
// This function maps the IO error to the DNSClient error.
|
||||
DNSClient::Status getStatus(const asiodns::IOFetch::Result);
|
||||
@@ -130,7 +133,6 @@ DNSClientImpl::operator()(asiodns::IOFetch::Result result) {
|
||||
// and pass the status code.
|
||||
DNSClient::Status status = getStatus(result);
|
||||
if (status == DNSClient::SUCCESS) {
|
||||
InputBuffer response_buf(in_buf_->getData(), in_buf_->getLength());
|
||||
// Allocate a new response message. (Note that Message::fromWire
|
||||
// may only be run once per message, so we need to start fresh
|
||||
// each time.)
|
||||
@@ -140,14 +142,19 @@ DNSClientImpl::operator()(asiodns::IOFetch::Result result) {
|
||||
// throw an exception. We want to catch this exception to return
|
||||
// appropriate status code to the caller and log this event.
|
||||
try {
|
||||
response_->fromWire(response_buf);
|
||||
|
||||
response_->fromWire(in_buf_->getData(), in_buf_->getLength(),
|
||||
tsig_context_.get());
|
||||
} catch (const isc::Exception& ex) {
|
||||
status = DNSClient::INVALID_RESPONSE;
|
||||
LOG_DEBUG(dctl_logger, DBGLVL_TRACE_DETAIL,
|
||||
DHCP_DDNS_INVALID_RESPONSE).arg(ex.what());
|
||||
|
||||
}
|
||||
|
||||
if (tsig_context_) {
|
||||
// Context is a one-shot deal, get rid of it.
|
||||
tsig_context_.reset();
|
||||
}
|
||||
}
|
||||
|
||||
// Once we are done with internal business, let's call a callback supplied
|
||||
@@ -174,13 +181,31 @@ DNSClientImpl::getStatus(const asiodns::IOFetch::Result result) {
|
||||
}
|
||||
return (DNSClient::OTHER);
|
||||
}
|
||||
|
||||
void
|
||||
DNSClientImpl::doUpdate(asiolink::IOService& io_service,
|
||||
const IOAddress& ns_addr,
|
||||
const uint16_t ns_port,
|
||||
D2UpdateMessage& update,
|
||||
const unsigned int wait) {
|
||||
const unsigned int wait,
|
||||
const dns::TSIGKeyPtr& tsig_key) {
|
||||
// The underlying implementation which we use to send DNS Updates uses
|
||||
// signed integers for timeout. If we want to avoid overflows we need to
|
||||
// respect this limitation here.
|
||||
if (wait > DNSClient::getMaxTimeout()) {
|
||||
isc_throw(isc::BadValue, "A timeout value for DNS Update request must"
|
||||
" not exceed " << DNSClient::getMaxTimeout()
|
||||
<< ". Provided timeout value is '" << wait << "'");
|
||||
}
|
||||
|
||||
// Create a TSIG context if we have a key, otherwise clear the context
|
||||
// pointer. Message marshalling uses non-null context is the indicator
|
||||
// that TSIG should be used.
|
||||
if (tsig_key) {
|
||||
tsig_context_.reset(new TSIGContext(*tsig_key));
|
||||
} else {
|
||||
tsig_context_.reset();
|
||||
}
|
||||
|
||||
// A renderer is used by the toWire function which creates the on-wire data
|
||||
// from the DNS Update message. A renderer has its internal buffer where it
|
||||
// renders data by default. However, this buffer can't be directly accessed.
|
||||
@@ -193,7 +218,7 @@ DNSClientImpl::doUpdate(asiolink::IOService& io_service,
|
||||
|
||||
// Render DNS Update message. This may throw a bunch of exceptions if
|
||||
// invalid message object is given.
|
||||
update.toWire(renderer);
|
||||
update.toWire(renderer, tsig_context_.get());
|
||||
|
||||
// IOFetch has all the mechanisms that we need to perform asynchronous
|
||||
// communication with the DNS server. The last but one argument points to
|
||||
@@ -211,7 +236,6 @@ DNSClientImpl::doUpdate(asiolink::IOService& io_service,
|
||||
io_service.post(io_fetch);
|
||||
}
|
||||
|
||||
|
||||
DNSClient::DNSClient(D2UpdateMessagePtr& response_placeholder,
|
||||
Callback* callback, const DNSClient::Protocol proto)
|
||||
: impl_(new DNSClientImpl(response_placeholder, callback, proto)) {
|
||||
@@ -227,36 +251,15 @@ DNSClient::getMaxTimeout() {
|
||||
return (max_timeout);
|
||||
}
|
||||
|
||||
void
|
||||
DNSClient::doUpdate(asiolink::IOService&,
|
||||
const IOAddress&,
|
||||
const uint16_t,
|
||||
D2UpdateMessage&,
|
||||
const unsigned int,
|
||||
const dns::TSIGKey&) {
|
||||
isc_throw(isc::NotImplemented, "TSIG is currently not supported for"
|
||||
"DNS Update message");
|
||||
}
|
||||
|
||||
void
|
||||
DNSClient::doUpdate(asiolink::IOService& io_service,
|
||||
const IOAddress& ns_addr,
|
||||
const uint16_t ns_port,
|
||||
D2UpdateMessage& update,
|
||||
const unsigned int wait) {
|
||||
// The underlying implementation which we use to send DNS Updates uses
|
||||
// signed integers for timeout. If we want to avoid overflows we need to
|
||||
// respect this limitation here.
|
||||
if (wait > getMaxTimeout()) {
|
||||
isc_throw(isc::BadValue, "A timeout value for DNS Update request must"
|
||||
" not exceed " << getMaxTimeout()
|
||||
<< ". Provided timeout value is '" << wait << "'");
|
||||
}
|
||||
impl_->doUpdate(io_service, ns_addr, ns_port, update, wait);
|
||||
const unsigned int wait,
|
||||
const dns::TSIGKeyPtr& tsig_key) {
|
||||
impl_->doUpdate(io_service, ns_addr, ns_port, update, wait, tsig_key);
|
||||
}
|
||||
|
||||
|
||||
|
||||
} // namespace d2
|
||||
} // namespace isc
|
||||
|
||||
|
@@ -146,41 +146,15 @@ public:
|
||||
/// @param wait A timeout (in milliseconds) for the response. If a response
|
||||
/// is not received within the timeout, exchange is interrupted. This value
|
||||
/// must not exceed maximal value for 'int' data type.
|
||||
/// @param tsig_key An @c isc::dns::TSIGKey object representing TSIG
|
||||
/// context which will be used to render the DNS Update message.
|
||||
///
|
||||
/// @todo Implement TSIG Support. Currently any attempt to call this
|
||||
/// function will result in exception.
|
||||
/// @param tsig_key A pointer to an @c isc::dns::TSIGKey object that will
|
||||
/// (if not null) be used to sign the DNS Update message and verify the
|
||||
/// response.
|
||||
void doUpdate(asiolink::IOService& io_service,
|
||||
const asiolink::IOAddress& ns_addr,
|
||||
const uint16_t ns_port,
|
||||
D2UpdateMessage& update,
|
||||
const unsigned int wait,
|
||||
const dns::TSIGKey& tsig_key);
|
||||
|
||||
/// @brief Start asynchronous DNS Update without TSIG.
|
||||
///
|
||||
/// This function starts asynchronous DNS Update and returns. The DNS Update
|
||||
/// will be executed by the specified IO service. Once the message exchange
|
||||
/// with a DNS server is complete, timeout occurs or IO operation is
|
||||
/// interrupted, the caller-supplied callback function will be invoked.
|
||||
///
|
||||
/// An address and port of the DNS server is specified through the function
|
||||
/// arguments so as the same instance of the @c DNSClient can be used to
|
||||
/// initiate multiple message exchanges.
|
||||
///
|
||||
/// @param io_service IO service to be used to run the message exchange.
|
||||
/// @param ns_addr DNS server address.
|
||||
/// @param ns_port DNS server port.
|
||||
/// @param update A DNS Update message to be sent to the server.
|
||||
/// @param wait A timeout (in milliseconds) for the response. If a response
|
||||
/// is not received within the timeout, exchange is interrupted. This value
|
||||
/// must not exceed maximal value for 'int' data type.
|
||||
void doUpdate(asiolink::IOService& io_service,
|
||||
const asiolink::IOAddress& ns_addr,
|
||||
const uint16_t ns_port,
|
||||
D2UpdateMessage& update,
|
||||
const unsigned int wait);
|
||||
const dns::TSIGKeyPtr& tsig_key = dns::TSIGKeyPtr());
|
||||
|
||||
private:
|
||||
DNSClientImpl* impl_; ///< Pointer to DNSClient implementation.
|
||||
|
@@ -54,7 +54,7 @@ NameChangeTransaction(IOServicePtr& io_service,
|
||||
dns_update_status_(DNSClient::OTHER), dns_update_response_(),
|
||||
forward_change_completed_(false), reverse_change_completed_(false),
|
||||
current_server_list_(), current_server_(), next_server_pos_(0),
|
||||
update_attempts_(0), cfg_mgr_(cfg_mgr) {
|
||||
update_attempts_(0), cfg_mgr_(cfg_mgr), tsig_key_() {
|
||||
/// @todo if io_service is NULL we are multi-threading and should
|
||||
/// instantiate our own
|
||||
if (!io_service_) {
|
||||
@@ -168,8 +168,7 @@ NameChangeTransaction::transactionOutcomeString() const {
|
||||
|
||||
|
||||
void
|
||||
NameChangeTransaction::sendUpdate(const std::string& comment,
|
||||
bool /* use_tsig_ */) {
|
||||
NameChangeTransaction::sendUpdate(const std::string& comment) {
|
||||
try {
|
||||
++update_attempts_;
|
||||
// @todo add logic to add/replace TSIG key info in request if
|
||||
@@ -179,8 +178,7 @@ NameChangeTransaction::sendUpdate(const std::string& comment,
|
||||
D2ParamsPtr d2_params = cfg_mgr_->getD2Params();
|
||||
dns_client_->doUpdate(*io_service_, current_server_->getIpAddress(),
|
||||
current_server_->getPort(), *dns_update_request_,
|
||||
d2_params->getDnsServerTimeout());
|
||||
|
||||
d2_params->getDnsServerTimeout(), tsig_key_);
|
||||
// Message is on its way, so the next event should be NOP_EVT.
|
||||
postNextEvent(NOP_EVT);
|
||||
LOG_DEBUG(dctl_logger, DBGLVL_TRACE_DETAIL,
|
||||
@@ -424,6 +422,15 @@ NameChangeTransaction::initServerSelection(const DdnsDomainPtr& domain) {
|
||||
isc_throw(NameChangeTransactionError,
|
||||
"initServerSelection called with an empty domain");
|
||||
}
|
||||
|
||||
// Set the tsig_key to that of the DdnsDomain.
|
||||
TSIGKeyInfoPtr tsig_key_info = domain->getTSIGKeyInfo();
|
||||
if (tsig_key_info) {
|
||||
tsig_key_ = tsig_key_info->getTSIGKey();
|
||||
} else {
|
||||
tsig_key_.reset();
|
||||
}
|
||||
|
||||
current_server_list_ = domain->getServers();
|
||||
next_server_pos_ = 0;
|
||||
current_server_.reset();
|
||||
|
@@ -23,6 +23,7 @@
|
||||
#include <d2/dns_client.h>
|
||||
#include <d2/state_model.h>
|
||||
#include <dhcp_ddns/ncr_msg.h>
|
||||
#include <dns/tsig.h>
|
||||
|
||||
#include <boost/shared_ptr.hpp>
|
||||
#include <map>
|
||||
@@ -209,14 +210,15 @@ protected:
|
||||
/// currently selected server. Since the send is asynchronous, the method
|
||||
/// posts NOP_EVT as the next event and then returns.
|
||||
///
|
||||
/// If tsig_key_ is not NULL, then the update will be conducted using
|
||||
/// the key to sign the request and verify the response, otherwise it
|
||||
/// will be conducted without TSIG.
|
||||
///
|
||||
/// @param comment text to include in log detail
|
||||
/// @param use_tsig True if the update should be include a TSIG key. This
|
||||
/// is not yet implemented.
|
||||
///
|
||||
/// If an exception occurs it will be logged and and the transaction will
|
||||
/// be failed.
|
||||
virtual void sendUpdate(const std::string& comment = "",
|
||||
bool use_tsig = false);
|
||||
virtual void sendUpdate(const std::string& comment = "");
|
||||
|
||||
/// @brief Adds events defined by NameChangeTransaction to the event set.
|
||||
///
|
||||
@@ -575,6 +577,9 @@ private:
|
||||
|
||||
/// @brief Pointer to the configuration manager.
|
||||
D2CfgMgrPtr cfg_mgr_;
|
||||
|
||||
/// @brief Pointer to the TSIG key which should be used (if any).
|
||||
dns::TSIGKeyPtr tsig_key_;
|
||||
};
|
||||
|
||||
/// @brief Defines a pointer to a NameChangeTransaction.
|
||||
|
@@ -17,6 +17,7 @@
|
||||
#include <d2/d2_cfg_mgr.h>
|
||||
#include <d_test_stubs.h>
|
||||
#include <test_data_files_config.h>
|
||||
#include <util/encode/base64.h>
|
||||
|
||||
#include <boost/foreach.hpp>
|
||||
#include <gtest/gtest.h>
|
||||
@@ -170,47 +171,24 @@ bool checkServer(DnsServerInfoPtr server, const char* hostname,
|
||||
}
|
||||
|
||||
/// @brief Convenience function which compares the contents of the given
|
||||
/// TSIGKeyInfo against the given set of values.
|
||||
/// TSIGKeyInfo against the given set of values, and that the TSIGKey
|
||||
/// member points to a key.
|
||||
///
|
||||
/// It is structured in such a way that each value is checked, and output
|
||||
/// is generate for all that do not match.
|
||||
///
|
||||
/// @param key is a pointer to the key to check against.
|
||||
/// @param key is a pointer to the TSIGKeyInfo instance to verify
|
||||
/// @param name is the value to compare against key's name_.
|
||||
/// @param algorithm is the string value to compare against key's algorithm.
|
||||
/// @param secret is the value to compare against key's secret.
|
||||
///
|
||||
/// @return returns true if there is a match across the board, otherwise it
|
||||
/// returns false.
|
||||
bool checkKey(TSIGKeyInfoPtr key, const char* name,
|
||||
const char *algorithm, const char* secret)
|
||||
{
|
||||
bool checkKey(TSIGKeyInfoPtr key, const std::string& name,
|
||||
const std::string& algorithm, const std::string& secret) {
|
||||
// Return value, assume its a match.
|
||||
bool result = true;
|
||||
if (!key) {
|
||||
EXPECT_TRUE(key);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check name.
|
||||
if (key->getName() != name) {
|
||||
EXPECT_EQ(name, key->getName());
|
||||
result = false;
|
||||
}
|
||||
|
||||
// Check algorithm.
|
||||
if (key->getAlgorithm() != algorithm) {
|
||||
EXPECT_EQ(algorithm, key->getAlgorithm());
|
||||
result = false;
|
||||
}
|
||||
|
||||
// Check secret.
|
||||
if (key->getSecret() != secret) {
|
||||
EXPECT_EQ (secret, key->getSecret());
|
||||
result = false;
|
||||
}
|
||||
|
||||
return (result);
|
||||
return (((key) &&
|
||||
(key->getName() == name) &&
|
||||
(key->getAlgorithm() == algorithm) &&
|
||||
(key->getSecret() == secret) &&
|
||||
(key->getTSIGKey())));
|
||||
}
|
||||
|
||||
/// @brief Test fixture class for testing DnsServerInfo parsing.
|
||||
@@ -459,14 +437,12 @@ TEST_F(D2CfgMgrTest, invalidEntry) {
|
||||
/// 1. Name cannot be blank.
|
||||
/// 2. Algorithm cannot be blank.
|
||||
/// 3. Secret cannot be blank.
|
||||
/// @TODO TSIG keys are not fully functional. Only basic validation is
|
||||
/// currently supported. This test will need to expand as they evolve.
|
||||
TEST_F(TSIGKeyInfoTest, invalidEntry) {
|
||||
// Config with a blank name entry.
|
||||
std::string config = "{"
|
||||
" \"name\": \"\" , "
|
||||
" \"algorithm\": \"md5\" , "
|
||||
" \"secret\": \"0123456789\" "
|
||||
" \"algorithm\": \"HMAC-MD5\" , "
|
||||
" \"secret\": \"LSWXnfkKZjdPJI5QxlpnfQ==\" "
|
||||
"}";
|
||||
ASSERT_TRUE(fromJSON(config));
|
||||
|
||||
@@ -477,7 +453,19 @@ TEST_F(TSIGKeyInfoTest, invalidEntry) {
|
||||
config = "{"
|
||||
" \"name\": \"d2_key_one\" , "
|
||||
" \"algorithm\": \"\" , "
|
||||
" \"secret\": \"0123456789\" "
|
||||
" \"secret\": \"LSWXnfkKZjdPJI5QxlpnfQ==\" "
|
||||
"}";
|
||||
|
||||
ASSERT_TRUE(fromJSON(config));
|
||||
|
||||
// Verify that build fails on blank algorithm.
|
||||
EXPECT_THROW(parser_->build(config_set_), D2CfgError);
|
||||
|
||||
// Config with an invalid algorithm entry.
|
||||
config = "{"
|
||||
" \"name\": \"d2_key_one\" , "
|
||||
" \"algorithm\": \"bogus\" , "
|
||||
" \"secret\": \"LSWXnfkKZjdPJI5QxlpnfQ==\" "
|
||||
"}";
|
||||
|
||||
ASSERT_TRUE(fromJSON(config));
|
||||
@@ -488,7 +476,7 @@ TEST_F(TSIGKeyInfoTest, invalidEntry) {
|
||||
// Config with a blank secret entry.
|
||||
config = "{"
|
||||
" \"name\": \"d2_key_one\" , "
|
||||
" \"algorithm\": \"md5\" , "
|
||||
" \"algorithm\": \"HMAC-MD5\" , "
|
||||
" \"secret\": \"\" "
|
||||
"}";
|
||||
|
||||
@@ -496,6 +484,18 @@ TEST_F(TSIGKeyInfoTest, invalidEntry) {
|
||||
|
||||
// Verify that build fails blank secret
|
||||
EXPECT_THROW(parser_->build(config_set_), D2CfgError);
|
||||
|
||||
// Config with an invalid secret entry.
|
||||
config = "{"
|
||||
" \"name\": \"d2_key_one\" , "
|
||||
" \"algorithm\": \"HMAC-MD5\" , "
|
||||
" \"secret\": \"bogus\" "
|
||||
"}";
|
||||
|
||||
ASSERT_TRUE(fromJSON(config));
|
||||
|
||||
// Verify that build fails an invalid secret
|
||||
EXPECT_THROW(parser_->build(config_set_), D2CfgError);
|
||||
}
|
||||
|
||||
/// @brief Verifies that TSIGKeyInfo parsing creates a proper TSIGKeyInfo
|
||||
@@ -504,13 +504,14 @@ TEST_F(TSIGKeyInfoTest, validEntry) {
|
||||
// Valid entries for TSIG key, all items are required.
|
||||
std::string config = "{"
|
||||
" \"name\": \"d2_key_one\" , "
|
||||
" \"algorithm\": \"md5\" , "
|
||||
" \"secret\": \"0123456789\" "
|
||||
" \"algorithm\": \"HMAC-MD5\" , "
|
||||
" \"secret\": \"dGhpcyBrZXkgd2lsbCBtYXRjaA==\" "
|
||||
"}";
|
||||
ASSERT_TRUE(fromJSON(config));
|
||||
|
||||
// Verify that it builds and commits without throwing.
|
||||
ASSERT_NO_THROW(parser_->build(config_set_));
|
||||
//ASSERT_NO_THROW(parser_->build(config_set_));
|
||||
(parser_->build(config_set_));
|
||||
ASSERT_NO_THROW(parser_->commit());
|
||||
|
||||
// Verify the correct number of keys are present
|
||||
@@ -523,7 +524,8 @@ TEST_F(TSIGKeyInfoTest, validEntry) {
|
||||
TSIGKeyInfoPtr& key = gotit->second;
|
||||
|
||||
// Verify the key contents.
|
||||
EXPECT_TRUE(checkKey(key, "d2_key_one", "md5", "0123456789"));
|
||||
EXPECT_TRUE(checkKey(key, "d2_key_one", "HMAC-MD5",
|
||||
"dGhpcyBrZXkgd2lsbCBtYXRjaA=="));
|
||||
}
|
||||
|
||||
/// @brief Verifies that attempting to parse an invalid list of TSIGKeyInfo
|
||||
@@ -533,16 +535,17 @@ TEST_F(TSIGKeyInfoTest, invalidTSIGKeyList) {
|
||||
std::string config = "["
|
||||
|
||||
" { \"name\": \"key1\" , "
|
||||
" \"algorithm\": \"algo1\" ,"
|
||||
" \"secret\": \"secret11\" "
|
||||
" \"algorithm\": \"HMAC-MD5\" ,"
|
||||
" \"secret\": \"GWG/Xfbju4O2iXGqkSu4PQ==\" "
|
||||
" },"
|
||||
// this entry has an invalid algorithm
|
||||
" { \"name\": \"key2\" , "
|
||||
" \"algorithm\": \"\" ,"
|
||||
" \"secret\": \"secret12\" "
|
||||
" \"secret\": \"GWG/Xfbju4O2iXGqkSu4PQ==\" "
|
||||
" },"
|
||||
" { \"name\": \"key3\" , "
|
||||
" \"algorithm\": \"algo3\" ,"
|
||||
" \"secret\": \"secret13\" "
|
||||
" \"algorithm\": \"HMAC-MD5\" ,"
|
||||
" \"secret\": \"GWG/Xfbju4O2iXGqkSu4PQ==\" "
|
||||
" }"
|
||||
" ]";
|
||||
|
||||
@@ -563,16 +566,16 @@ TEST_F(TSIGKeyInfoTest, duplicateTSIGKey) {
|
||||
std::string config = "["
|
||||
|
||||
" { \"name\": \"key1\" , "
|
||||
" \"algorithm\": \"algo1\" ,"
|
||||
" \"secret\": \"secret11\" "
|
||||
" \"algorithm\": \"HMAC-MD5\" ,"
|
||||
" \"secret\": \"GWG/Xfbju4O2iXGqkSu4PQ==\" "
|
||||
" },"
|
||||
" { \"name\": \"key2\" , "
|
||||
" \"algorithm\": \"algo2\" ,"
|
||||
" \"secret\": \"secret12\" "
|
||||
" \"algorithm\": \"HMAC-MD5\" ,"
|
||||
" \"secret\": \"GWG/Xfbju4O2iXGqkSu4PQ==\" "
|
||||
" },"
|
||||
" { \"name\": \"key1\" , "
|
||||
" \"algorithm\": \"algo3\" ,"
|
||||
" \"secret\": \"secret13\" "
|
||||
" \"algorithm\": \"HMAC-MD5\" ,"
|
||||
" \"secret\": \"GWG/Xfbju4O2iXGqkSu4PQ==\" "
|
||||
" }"
|
||||
" ]";
|
||||
|
||||
@@ -587,21 +590,34 @@ TEST_F(TSIGKeyInfoTest, duplicateTSIGKey) {
|
||||
}
|
||||
|
||||
/// @brief Verifies a valid list of TSIG Keys parses correctly.
|
||||
/// Also verifies that all of the supported algorithm names work.
|
||||
TEST_F(TSIGKeyInfoTest, validTSIGKeyList) {
|
||||
// Construct a valid list of keys.
|
||||
std::string config = "["
|
||||
|
||||
" { \"name\": \"key1\" , "
|
||||
" \"algorithm\": \"algo1\" ,"
|
||||
" \"secret\": \"secret1\" "
|
||||
" \"algorithm\": \"HMAC-MD5\" ,"
|
||||
" \"secret\": \"dGhpcyBrZXkgd2lsbCBtYXRjaA==\" "
|
||||
" },"
|
||||
" { \"name\": \"key2\" , "
|
||||
" \"algorithm\": \"algo2\" ,"
|
||||
" \"secret\": \"secret2\" "
|
||||
" \"algorithm\": \"HMAC-SHA1\" ,"
|
||||
" \"secret\": \"dGhpcyBrZXkgd2lsbCBtYXRjaA==\" "
|
||||
" },"
|
||||
" { \"name\": \"key3\" , "
|
||||
" \"algorithm\": \"algo3\" ,"
|
||||
" \"secret\": \"secret3\" "
|
||||
" \"algorithm\": \"HMAC-SHA256\" ,"
|
||||
" \"secret\": \"dGhpcyBrZXkgd2lsbCBtYXRjaA==\" "
|
||||
" },"
|
||||
" { \"name\": \"key4\" , "
|
||||
" \"algorithm\": \"HMAC-SHA224\" ,"
|
||||
" \"secret\": \"dGhpcyBrZXkgd2lsbCBtYXRjaA==\" "
|
||||
" },"
|
||||
" { \"name\": \"key5\" , "
|
||||
" \"algorithm\": \"HMAC-SHA384\" ,"
|
||||
" \"secret\": \"dGhpcyBrZXkgd2lsbCBtYXRjaA==\" "
|
||||
" },"
|
||||
" { \"name\": \"key6\" , "
|
||||
" \"algorithm\": \"HMAC-SHA512\" ,"
|
||||
" \"secret\": \"dGhpcyBrZXkgd2lsbCBtYXRjaA==\" "
|
||||
" }"
|
||||
" ]";
|
||||
|
||||
@@ -614,9 +630,10 @@ TEST_F(TSIGKeyInfoTest, validTSIGKeyList) {
|
||||
ASSERT_NO_THROW(parser->build(config_set_));
|
||||
ASSERT_NO_THROW(parser->commit());
|
||||
|
||||
std::string ref_secret = "dGhpcyBrZXkgd2lsbCBtYXRjaA==";
|
||||
// Verify the correct number of keys are present
|
||||
int count = keys_->size();
|
||||
ASSERT_EQ(3, count);
|
||||
ASSERT_EQ(6, count);
|
||||
|
||||
// Find the 1st key and retrieve it.
|
||||
TSIGKeyInfoMap::iterator gotit = keys_->find("key1");
|
||||
@@ -624,7 +641,7 @@ TEST_F(TSIGKeyInfoTest, validTSIGKeyList) {
|
||||
TSIGKeyInfoPtr& key = gotit->second;
|
||||
|
||||
// Verify the key contents.
|
||||
EXPECT_TRUE(checkKey(key, "key1", "algo1", "secret1"));
|
||||
EXPECT_TRUE(checkKey(key, "key1", TSIGKeyInfo::HMAC_MD5_STR, ref_secret));
|
||||
|
||||
// Find the 2nd key and retrieve it.
|
||||
gotit = keys_->find("key2");
|
||||
@@ -632,7 +649,7 @@ TEST_F(TSIGKeyInfoTest, validTSIGKeyList) {
|
||||
key = gotit->second;
|
||||
|
||||
// Verify the key contents.
|
||||
EXPECT_TRUE(checkKey(key, "key2", "algo2", "secret2"));
|
||||
EXPECT_TRUE(checkKey(key, "key2", TSIGKeyInfo::HMAC_SHA1_STR, ref_secret));
|
||||
|
||||
// Find the 3rd key and retrieve it.
|
||||
gotit = keys_->find("key3");
|
||||
@@ -640,7 +657,35 @@ TEST_F(TSIGKeyInfoTest, validTSIGKeyList) {
|
||||
key = gotit->second;
|
||||
|
||||
// Verify the key contents.
|
||||
EXPECT_TRUE(checkKey(key, "key3", "algo3", "secret3"));
|
||||
EXPECT_TRUE(checkKey(key, "key3", TSIGKeyInfo::HMAC_SHA256_STR,
|
||||
ref_secret));
|
||||
|
||||
// Find the 4th key and retrieve it.
|
||||
gotit = keys_->find("key4");
|
||||
ASSERT_TRUE(gotit != keys_->end());
|
||||
key = gotit->second;
|
||||
|
||||
// Verify the key contents.
|
||||
EXPECT_TRUE(checkKey(key, "key4", TSIGKeyInfo::HMAC_SHA224_STR,
|
||||
ref_secret));
|
||||
|
||||
// Find the 5th key and retrieve it.
|
||||
gotit = keys_->find("key5");
|
||||
ASSERT_TRUE(gotit != keys_->end());
|
||||
key = gotit->second;
|
||||
|
||||
// Verify the key contents.
|
||||
EXPECT_TRUE(checkKey(key, "key5", TSIGKeyInfo::HMAC_SHA384_STR,
|
||||
ref_secret));
|
||||
|
||||
// Find the 6th key and retrieve it.
|
||||
gotit = keys_->find("key6");
|
||||
ASSERT_TRUE(gotit != keys_->end());
|
||||
key = gotit->second;
|
||||
|
||||
// Verify the key contents.
|
||||
EXPECT_TRUE(checkKey(key, "key6", TSIGKeyInfo::HMAC_SHA512_STR,
|
||||
ref_secret));
|
||||
}
|
||||
|
||||
/// @brief Tests the enforcement of data validation when parsing DnsServerInfos.
|
||||
@@ -876,7 +921,7 @@ TEST_F(DdnsDomainTest, ddnsDomainParsing) {
|
||||
ASSERT_TRUE(fromJSON(config));
|
||||
|
||||
// Add a TSIG key to the test key map, so key validation will pass.
|
||||
addKey("d2_key.tmark.org", "md5", "0123456789");
|
||||
addKey("d2_key.tmark.org", "HMAC-MD5", "GWG/Xfbju4O2iXGqkSu4PQ==");
|
||||
|
||||
// Verify that the domain configuration builds and commits without error.
|
||||
ASSERT_NO_THROW(parser_->build(config_set_));
|
||||
@@ -895,6 +940,8 @@ TEST_F(DdnsDomainTest, ddnsDomainParsing) {
|
||||
// Verify the name and key_name values.
|
||||
EXPECT_EQ("tmark.org", domain->getName());
|
||||
EXPECT_EQ("d2_key.tmark.org", domain->getKeyName());
|
||||
ASSERT_TRUE(domain->getTSIGKeyInfo());
|
||||
ASSERT_TRUE(domain->getTSIGKeyInfo()->getTSIGKey());
|
||||
|
||||
// Verify that the server list exists and contains the correct number of
|
||||
// servers.
|
||||
@@ -952,8 +999,8 @@ TEST_F(DdnsDomainTest, DdnsDomainListParsing) {
|
||||
ASSERT_TRUE(fromJSON(config));
|
||||
|
||||
// Add keys to key map so key validation passes.
|
||||
addKey("d2_key.tmark.org", "algo1", "secret1");
|
||||
addKey("d2_key.billcat.net", "algo2", "secret2");
|
||||
addKey("d2_key.tmark.org", "HMAC-MD5", "GWG/Xfbju4O2iXGqkSu4PQ==");
|
||||
addKey("d2_key.billcat.net", "HMAC-MD5", "GWG/Xfbju4O2iXGqkSu4PQ==");
|
||||
|
||||
// Create the list parser
|
||||
isc::dhcp::ParserPtr list_parser;
|
||||
@@ -976,6 +1023,8 @@ TEST_F(DdnsDomainTest, DdnsDomainListParsing) {
|
||||
// Verify the name and key_name values of the first domain.
|
||||
EXPECT_EQ("tmark.org", domain->getName());
|
||||
EXPECT_EQ("d2_key.tmark.org", domain->getKeyName());
|
||||
ASSERT_TRUE(domain->getTSIGKeyInfo());
|
||||
ASSERT_TRUE(domain->getTSIGKeyInfo()->getTSIGKey());
|
||||
|
||||
// Verify the each of the first domain's servers
|
||||
DnsServerInfoStoragePtr servers = domain->getServers();
|
||||
@@ -1003,6 +1052,8 @@ TEST_F(DdnsDomainTest, DdnsDomainListParsing) {
|
||||
// Verify the name and key_name values of the second domain.
|
||||
EXPECT_EQ("billcat.net", domain->getName());
|
||||
EXPECT_EQ("d2_key.billcat.net", domain->getKeyName());
|
||||
ASSERT_TRUE(domain->getTSIGKeyInfo());
|
||||
ASSERT_TRUE(domain->getTSIGKeyInfo()->getTSIGKey());
|
||||
|
||||
// Verify the each of second domain's servers
|
||||
servers = domain->getServers();
|
||||
@@ -1090,13 +1141,13 @@ TEST_F(D2CfgMgrTest, fullConfig) {
|
||||
"\"tsig_keys\": ["
|
||||
"{"
|
||||
" \"name\": \"d2_key.tmark.org\" , "
|
||||
" \"algorithm\": \"md5\" , "
|
||||
" \"secret\": \"ssh-dont-tell\" "
|
||||
" \"algorithm\": \"hmac-md5\" , "
|
||||
" \"secret\": \"LSWXnfkKZjdPJI5QxlpnfQ==\" "
|
||||
"},"
|
||||
"{"
|
||||
" \"name\": \"d2_key.billcat.net\" , "
|
||||
" \"algorithm\": \"md5\" , "
|
||||
" \"secret\": \"ollie-ollie-in-free\" "
|
||||
" \"algorithm\": \"hmac-md5\" , "
|
||||
" \"secret\": \"LSWXnfkKZjdPJI5QxlpnfQ==\" "
|
||||
"}"
|
||||
"],"
|
||||
"\"forward_ddns\" : {"
|
||||
|
@@ -1,4 +1,4 @@
|
||||
// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
|
||||
// Copyright (C) 2013-2014 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
|
||||
@@ -39,8 +39,8 @@ const char* bad_ip_d2_config = "{ "
|
||||
"\"port\" : 5031, "
|
||||
"\"tsig_keys\": ["
|
||||
"{ \"name\": \"d2_key.tmark.org\" , "
|
||||
" \"algorithm\": \"md5\" ,"
|
||||
" \"secret\": \"0123456989\" "
|
||||
" \"algorithm\": \"HMAC-MD5\" ,"
|
||||
" \"secret\": \"LSWXnfkKZjdPJI5QxlpnfQ==\" "
|
||||
"} ],"
|
||||
"\"forward_ddns\" : {"
|
||||
"\"ddns_domains\": [ "
|
||||
|
@@ -1,4 +1,4 @@
|
||||
// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
|
||||
// Copyright (C) 2013-2014 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
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
#include <config.h>
|
||||
|
||||
#include <d2/d2_config.h>
|
||||
#include <d2/d2_update_message.h>
|
||||
#include <d2/d2_zone.h>
|
||||
#include <dns/messagerenderer.h>
|
||||
@@ -201,12 +202,12 @@ TEST_F(D2UpdateMessageTest, fromWire) {
|
||||
0x20, 0x01, 0x0D, 0xB8, 0x00, 0x01, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01
|
||||
};
|
||||
InputBuffer buf(bin_msg, sizeof(bin_msg));
|
||||
|
||||
// Create an object to be used to decode the message from the wire format.
|
||||
D2UpdateMessage msg(D2UpdateMessage::INBOUND);
|
||||
|
||||
// Decode the message.
|
||||
ASSERT_NO_THROW(msg.fromWire(buf));
|
||||
ASSERT_NO_THROW(msg.fromWire(bin_msg, sizeof(bin_msg)));
|
||||
|
||||
// Check that the message header is valid.
|
||||
EXPECT_EQ(0x05AF, msg.getId());
|
||||
@@ -287,14 +288,14 @@ TEST_F(D2UpdateMessageTest, fromWireInvalidOpcode) {
|
||||
0x0, 0x0, // UPCOUNT=0
|
||||
0x0, 0x0 // ADCOUNT=0
|
||||
};
|
||||
InputBuffer buf(bin_msg, sizeof(bin_msg));
|
||||
// The 'true' argument passed to the constructor turns the
|
||||
// message into the parse mode in which the fromWire function
|
||||
// can be used to decode the binary mesasage data.
|
||||
D2UpdateMessage msg(D2UpdateMessage::INBOUND);
|
||||
// When using invalid Opcode, the fromWire function should
|
||||
// throw NotUpdateMessage exception.
|
||||
EXPECT_THROW(msg.fromWire(buf), isc::d2::NotUpdateMessage);
|
||||
EXPECT_THROW(msg.fromWire(bin_msg, sizeof(bin_msg)),
|
||||
isc::d2::NotUpdateMessage);
|
||||
}
|
||||
|
||||
// This test verifies that the fromWire function throws appropriate exception
|
||||
@@ -311,14 +312,14 @@ TEST_F(D2UpdateMessageTest, fromWireInvalidQRFlag) {
|
||||
0x0, 0x0, // UPCOUNT=0
|
||||
0x0, 0x0 // ADCOUNT=0
|
||||
};
|
||||
InputBuffer buf(bin_msg, sizeof(bin_msg));
|
||||
// The 'true' argument passed to the constructor turns the
|
||||
// message into the parse mode in which the fromWire function
|
||||
// can be used to decode the binary mesasage data.
|
||||
D2UpdateMessage msg(D2UpdateMessage::INBOUND);
|
||||
// When using invalid QR flag, the fromWire function should
|
||||
// throw InvalidQRFlag exception.
|
||||
EXPECT_THROW(msg.fromWire(buf), isc::d2::InvalidQRFlag);
|
||||
EXPECT_THROW(msg.fromWire(bin_msg, sizeof(bin_msg)),
|
||||
isc::d2::InvalidQRFlag);
|
||||
}
|
||||
|
||||
// This test verifies that the fromWire function throws appropriate exception
|
||||
@@ -349,7 +350,6 @@ TEST_F(D2UpdateMessageTest, fromWireTooManyZones) {
|
||||
0x0, 0x6, // ZTYPE='SOA'
|
||||
0x0, 0x1 // ZCLASS='IN'
|
||||
};
|
||||
InputBuffer buf(bin_msg, sizeof(bin_msg));
|
||||
|
||||
// The 'true' argument passed to the constructor turns the
|
||||
// message into the parse mode in which the fromWire function
|
||||
@@ -357,7 +357,8 @@ TEST_F(D2UpdateMessageTest, fromWireTooManyZones) {
|
||||
D2UpdateMessage msg(D2UpdateMessage::INBOUND);
|
||||
// When parsing a message with more than one Zone record,
|
||||
// exception should be thrown.
|
||||
EXPECT_THROW(msg.fromWire(buf), isc::d2::InvalidZoneSection);
|
||||
EXPECT_THROW(msg.fromWire(bin_msg, sizeof(bin_msg)),
|
||||
isc::d2::InvalidZoneSection);
|
||||
}
|
||||
|
||||
// This test verifies that the wire format of the message is produced
|
||||
@@ -571,12 +572,11 @@ TEST_F(D2UpdateMessageTest, toWireInvalidQRFlag) {
|
||||
0x0, 0x0 // ADCOUNT=0
|
||||
};
|
||||
|
||||
InputBuffer buf(bin_msg, sizeof(bin_msg));
|
||||
// The 'true' argument passed to the constructor turns the
|
||||
// message into the parse mode in which the fromWire function
|
||||
// can be used to decode the binary mesasage data.
|
||||
D2UpdateMessage msg(D2UpdateMessage::INBOUND);
|
||||
ASSERT_NO_THROW(msg.fromWire(buf));
|
||||
ASSERT_NO_THROW(msg.fromWire(bin_msg, sizeof(bin_msg)));
|
||||
|
||||
// The message is parsed. The QR Flag should now indicate that
|
||||
// it is a Response message.
|
||||
@@ -588,4 +588,118 @@ TEST_F(D2UpdateMessageTest, toWireInvalidQRFlag) {
|
||||
EXPECT_THROW(msg.toWire(renderer), isc::d2::InvalidQRFlag);
|
||||
}
|
||||
|
||||
// TSIG test
|
||||
TEST_F(D2UpdateMessageTest, validTSIG) {
|
||||
// Create a TSIG Key and context
|
||||
std::string secret("this key will match");
|
||||
TSIGKeyPtr right_key;
|
||||
ASSERT_NO_THROW(right_key.reset(new
|
||||
TSIGKey(Name("right.com"),
|
||||
TSIGKey::HMACMD5_NAME(),
|
||||
secret.c_str(), secret.size())));
|
||||
|
||||
TSIGKeyPtr wrong_key;
|
||||
secret = "this key will not match";
|
||||
ASSERT_NO_THROW(wrong_key.reset(new
|
||||
TSIGKey(Name("wrong.com"),
|
||||
TSIGKey::HMACMD5_NAME(),
|
||||
secret.c_str(), secret.size())));
|
||||
|
||||
|
||||
// Build a request message
|
||||
D2UpdateMessage msg;
|
||||
msg.setId(0x1234);
|
||||
msg.setRcode(Rcode(Rcode::NOERROR_CODE));
|
||||
msg.setZone(Name("example.com"), RRClass::IN());
|
||||
RRsetPtr prereq1(new RRset(Name("foo.example.com"), RRClass::NONE(),
|
||||
RRType::ANY(), RRTTL(0)));
|
||||
msg.addRRset(D2UpdateMessage::SECTION_PREREQUISITE, prereq1);
|
||||
RRsetPtr prereq2(new RRset(Name("bar.example.com"), RRClass::ANY(),
|
||||
RRType::ANY(), RRTTL(0)));
|
||||
msg.addRRset(D2UpdateMessage::SECTION_PREREQUISITE, prereq2);
|
||||
RRsetPtr updaterr1(new RRset(Name("foo.example.com"), RRClass::IN(),
|
||||
RRType::A(), RRTTL(10)));
|
||||
char rdata1[] = {
|
||||
0xA, 0xA , 0x1, 0x1
|
||||
};
|
||||
InputBuffer buf_rdata1(rdata1, 4);
|
||||
updaterr1->addRdata(createRdata(RRType::A(), RRClass::IN(), buf_rdata1,
|
||||
buf_rdata1.getLength()));
|
||||
msg.addRRset(D2UpdateMessage::SECTION_UPDATE, updaterr1);
|
||||
|
||||
// Make a context to send the message with and use it to render
|
||||
// the message into the wire format.
|
||||
TSIGContextPtr context;
|
||||
ASSERT_NO_THROW(context.reset(new TSIGContext(*right_key)));
|
||||
MessageRenderer renderer;
|
||||
ASSERT_NO_THROW(msg.toWire(renderer, context.get()));
|
||||
|
||||
// Grab the wire data from the signed message.
|
||||
const void* wire_data = renderer.getData();
|
||||
const size_t wire_size = renderer.getLength();
|
||||
|
||||
// Make a context with the wrong key and use it to convert the wired data.
|
||||
// Verification should fail.
|
||||
D2UpdateMessage msg2(D2UpdateMessage::INBOUND);
|
||||
ASSERT_NO_THROW(context.reset(new TSIGContext(*wrong_key)));
|
||||
ASSERT_THROW(msg2.fromWire(wire_data, wire_size, context.get()),
|
||||
TSIGVerifyError);
|
||||
|
||||
// Now make a context with the correct key and try again.
|
||||
// If the message passes TSIG verification, then the QR Flag test in
|
||||
// the subsequent call to D2UpdateMessage::validateResponse should
|
||||
// fail because this isn't really received message.
|
||||
ASSERT_NO_THROW(context.reset(new TSIGContext(*right_key)));
|
||||
ASSERT_THROW(msg2.fromWire(wire_data, wire_size, context.get()),
|
||||
InvalidQRFlag);
|
||||
}
|
||||
|
||||
// Tests message signing and verification for all supported algorithms.
|
||||
TEST_F(D2UpdateMessageTest, allValidTSIG) {
|
||||
std::vector<std::string>algorithms;
|
||||
algorithms.push_back(TSIGKeyInfo::HMAC_MD5_STR);
|
||||
algorithms.push_back(TSIGKeyInfo::HMAC_SHA1_STR);
|
||||
algorithms.push_back(TSIGKeyInfo::HMAC_SHA224_STR);
|
||||
algorithms.push_back(TSIGKeyInfo::HMAC_SHA256_STR);
|
||||
algorithms.push_back(TSIGKeyInfo::HMAC_SHA384_STR);
|
||||
algorithms.push_back(TSIGKeyInfo::HMAC_SHA512_STR);
|
||||
|
||||
dns::Name key_name("test_key");
|
||||
std::string secret("random text for secret");
|
||||
for (int i = 0; i < algorithms.size(); ++i) {
|
||||
dns::TSIGKey key(key_name,
|
||||
TSIGKeyInfo::stringToAlgorithmName(algorithms[i]),
|
||||
secret.c_str(), secret.size());
|
||||
|
||||
// Build a request message
|
||||
D2UpdateMessage msg;
|
||||
msg.setId(0x1234);
|
||||
msg.setRcode(Rcode(Rcode::NOERROR_CODE));
|
||||
msg.setZone(Name("example.com"), RRClass::IN());
|
||||
|
||||
// Make a context to send the message with and use it to render
|
||||
// the message into the wire format.
|
||||
TSIGContextPtr context;
|
||||
ASSERT_NO_THROW(context.reset(new TSIGContext(key)));
|
||||
MessageRenderer renderer;
|
||||
ASSERT_NO_THROW(msg.toWire(renderer, context.get()));
|
||||
|
||||
// Grab the wire data from the signed message.
|
||||
const void* wire_data = renderer.getData();
|
||||
const size_t wire_size = renderer.getLength();
|
||||
|
||||
// Create a fresh context to "receive" the message. (We can't use the
|
||||
// one we signed it with, as its expecting a signed response to its
|
||||
// request. Here we are acting like the server).
|
||||
// If the message passes TSIG verification, then the QR Flag test in
|
||||
// the subsequent call to D2UpdateMessage::validateResponse should
|
||||
// fail because this isn't really received message.
|
||||
ASSERT_NO_THROW(context.reset(new TSIGContext(key)));
|
||||
D2UpdateMessage msg2(D2UpdateMessage::INBOUND);
|
||||
ASSERT_THROW(msg2.fromWire(wire_data, wire_size, context.get()),
|
||||
InvalidQRFlag);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} // End of anonymous namespace
|
||||
|
@@ -26,8 +26,8 @@ const char* valid_d2_config = "{ "
|
||||
"\"port\" : 5031, "
|
||||
"\"tsig_keys\": ["
|
||||
"{ \"name\": \"d2_key.tmark.org\" , "
|
||||
" \"algorithm\": \"md5\" ,"
|
||||
" \"secret\": \"0123456989\" "
|
||||
" \"algorithm\": \"HMAC-MD5\" ,"
|
||||
" \"secret\": \"LSWXnfkKZjdPJI5QxlpnfQ==\" "
|
||||
"} ],"
|
||||
"\"forward_ddns\" : {"
|
||||
"\"ddns_domains\": [ "
|
||||
|
@@ -14,12 +14,11 @@
|
||||
|
||||
#include <config.h>
|
||||
#include <d2/dns_client.h>
|
||||
#include <dns/opcode.h>
|
||||
#include <asiodns/io_fetch.h>
|
||||
#include <asiodns/logger.h>
|
||||
#include <asiolink/interval_timer.h>
|
||||
#include <dns/rcode.h>
|
||||
#include <dns/rrclass.h>
|
||||
#include <dns/tsig.h>
|
||||
#include <dns/messagerenderer.h>
|
||||
#include <asio/ip/udp.hpp>
|
||||
#include <asio/socket_base.hpp>
|
||||
#include <boost/bind.hpp>
|
||||
@@ -189,6 +188,80 @@ public:
|
||||
*remote);
|
||||
}
|
||||
|
||||
// @brief Request handler for testing clients using TSIG
|
||||
//
|
||||
// This callback handler is installed when performing async read on a
|
||||
// socket to emulate reception of the DNS Update request with TSIG by a
|
||||
// server. As a result, this handler will send an appropriate DNS Update
|
||||
// response message back to the address from which the request has come.
|
||||
//
|
||||
// @param socket A pointer to a socket used to receive a query and send a
|
||||
// response.
|
||||
// @param remote A pointer to an object which specifies the host (address
|
||||
// and port) from which a request has come.
|
||||
// @param receive_length A length (in bytes) of the received data.
|
||||
// @param corrupt_response A bool value which indicates that the server's
|
||||
// response should be invalid (true) or valid (false)
|
||||
// @param client_key TSIG key the server should use to verify the inbound
|
||||
// request. If the pointer is NULL, the server will not attempt to
|
||||
// verify the request.
|
||||
// @param server_key TSIG key the server should use to sign the outbound
|
||||
// request. If the pointer is NULL, the server will not sign the outbound
|
||||
// response. If the pointer is not NULL and not the same value as the
|
||||
// client_key, the server will use a new context to sign the response then
|
||||
// the one used to verify it. This allows us to simulate the server
|
||||
// signing with the wrong key.
|
||||
void TSIGReceiveHandler(udp::socket* socket, udp::endpoint* remote,
|
||||
size_t receive_length,
|
||||
TSIGKeyPtr client_key,
|
||||
TSIGKeyPtr server_key) {
|
||||
|
||||
TSIGContextPtr context;
|
||||
if (client_key) {
|
||||
context.reset(new TSIGContext(*client_key));
|
||||
}
|
||||
|
||||
isc::util::InputBuffer received_data_buffer(receive_buffer_,
|
||||
receive_length);
|
||||
|
||||
dns::Message request(Message::PARSE);
|
||||
request.fromWire(received_data_buffer);
|
||||
|
||||
// If contex is not NULL, then we need to verify the message.
|
||||
if (context) {
|
||||
TSIGError error = context->verify(request.getTSIGRecord(),
|
||||
receive_buffer_, receive_length);
|
||||
if (error != TSIGError::NOERROR()) {
|
||||
isc_throw(TSIGVerifyError, "TSIG verification failed: "
|
||||
<< error.toText());
|
||||
}
|
||||
}
|
||||
|
||||
dns::Message response(Message::RENDER);
|
||||
response.setOpcode(Opcode(Opcode::UPDATE_CODE));
|
||||
response.setHeaderFlag(dns::Message::HEADERFLAG_QR, true);
|
||||
response.setQid(request.getQid());
|
||||
response.setRcode(Rcode::NOERROR());
|
||||
dns::Question question(Name("example.com."),
|
||||
RRClass::IN(), RRType::SOA());
|
||||
response.addQuestion(question);
|
||||
|
||||
MessageRenderer renderer;
|
||||
|
||||
if (!server_key) {
|
||||
// don't sign the response.
|
||||
context.reset();
|
||||
} else if (server_key != client_key) {
|
||||
// use a different key to sign the response.
|
||||
context.reset(new TSIGContext(*server_key));
|
||||
} // otherwise use the context based on client_key.
|
||||
|
||||
response.toWire(renderer, context.get());
|
||||
// A response message is now ready to send. Send it!
|
||||
socket->send_to(asio::buffer(renderer.getData(), renderer.getLength()),
|
||||
*remote);
|
||||
}
|
||||
|
||||
// This test verifies that when invalid response placeholder object is
|
||||
// passed to a constructor, constructor throws the appropriate exception.
|
||||
// It also verifies that the constructor will not throw if the supplied
|
||||
@@ -229,26 +302,6 @@ public:
|
||||
isc::BadValue);
|
||||
}
|
||||
|
||||
// This test verifies that isc::NotImplemented exception is thrown when
|
||||
// attempt to send DNS Update message with TSIG is attempted.
|
||||
void runTSIGTest() {
|
||||
// Create outgoing message. Simply set the required message fields:
|
||||
// error code and Zone section. This is enough to create on-wire format
|
||||
// of this message and send it.
|
||||
D2UpdateMessage message(D2UpdateMessage::OUTBOUND);
|
||||
ASSERT_NO_THROW(message.setRcode(Rcode(Rcode::NOERROR_CODE)));
|
||||
ASSERT_NO_THROW(message.setZone(Name("example.com"), RRClass::IN()));
|
||||
|
||||
const int timeout = 0;
|
||||
// Try to send DNS Update with TSIG key. Currently TSIG is not supported
|
||||
// and therefore we expect an exception.
|
||||
TSIGKey tsig_key("key.example:MSG6Ng==");
|
||||
EXPECT_THROW(dns_client_->doUpdate(service_, IOAddress(TEST_ADDRESS),
|
||||
TEST_PORT, message, timeout,
|
||||
tsig_key),
|
||||
isc::NotImplemented);
|
||||
}
|
||||
|
||||
// This test verifies the DNSClient behavior when a server does not respond
|
||||
// do the DNS Update message. In such case, the callback function is
|
||||
// expected to be called and the TIME_OUT error code should be returned.
|
||||
@@ -359,6 +412,59 @@ public:
|
||||
// run_one() to work.
|
||||
service_.get_io_service().reset();
|
||||
}
|
||||
|
||||
// Performs a single request-response exchange with or without TSIG
|
||||
//
|
||||
// @param client_key TSIG passed to dns_client and also used by the
|
||||
// ""server" to verify the request.
|
||||
// request.
|
||||
// @param server_key TSIG key the "server" should use to sign the response.
|
||||
// If this is NULL, then client_key is used.
|
||||
// @param should_pass indicates if the test should pass.
|
||||
void runTSIGTest(TSIGKeyPtr client_key, TSIGKeyPtr server_key,
|
||||
bool should_pass = true) {
|
||||
// Tell operator() method if we expect an invalid response.
|
||||
corrupt_response_ = !should_pass;
|
||||
|
||||
// Create a request DNS Update message.
|
||||
D2UpdateMessage message(D2UpdateMessage::OUTBOUND);
|
||||
ASSERT_NO_THROW(message.setRcode(Rcode(Rcode::NOERROR_CODE)));
|
||||
ASSERT_NO_THROW(message.setZone(Name("example.com"), RRClass::IN()));
|
||||
|
||||
// Setup our "loopback" server.
|
||||
udp::socket udp_socket(service_.get_io_service(), asio::ip::udp::v4());
|
||||
udp_socket.set_option(socket_base::reuse_address(true));
|
||||
udp_socket.bind(udp::endpoint(address::from_string(TEST_ADDRESS),
|
||||
TEST_PORT));
|
||||
udp::endpoint remote;
|
||||
udp_socket.async_receive_from(asio::buffer(receive_buffer_,
|
||||
sizeof(receive_buffer_)),
|
||||
remote,
|
||||
boost::bind(&DNSClientTest::
|
||||
TSIGReceiveHandler, this,
|
||||
&udp_socket, &remote, _2,
|
||||
client_key, server_key));
|
||||
|
||||
// The socket is now ready to receive the data. Let's post some request
|
||||
// message then. Set timeout to some reasonable value to make sure that
|
||||
// there is sufficient amount of time for the test to generate a
|
||||
// response.
|
||||
const int timeout = 500;
|
||||
expected_++;
|
||||
dns_client_->doUpdate(service_, IOAddress(TEST_ADDRESS), TEST_PORT,
|
||||
message, timeout, client_key);
|
||||
|
||||
// Kick of the message exchange by actually running the scheduled
|
||||
// "send" and "receive" operations.
|
||||
service_.run();
|
||||
|
||||
udp_socket.close();
|
||||
|
||||
// Since the callback, operator(), calls stop() on the io_service,
|
||||
// we must reset it in order for subsequent calls to run() or
|
||||
// run_one() to work.
|
||||
service_.get_io_service().reset();
|
||||
}
|
||||
};
|
||||
|
||||
// Verify that the DNSClient object can be created if provided parameters are
|
||||
@@ -384,10 +490,38 @@ TEST_F(DNSClientTest, invalidTimeout) {
|
||||
runInvalidTimeoutTest();
|
||||
}
|
||||
|
||||
// Verify that exception is thrown when an attempt to send DNS Update with TSIG
|
||||
// is made. This test will be removed/changed once TSIG support is added.
|
||||
// Verifies that TSIG can be used to sign requests and verify responses.
|
||||
TEST_F(DNSClientTest, runTSIGTest) {
|
||||
runTSIGTest();
|
||||
std::string secret ("key number one");
|
||||
TSIGKeyPtr key_one;
|
||||
ASSERT_NO_THROW(key_one.reset(new
|
||||
TSIGKey(Name("one.com"),
|
||||
TSIGKey::HMACMD5_NAME(),
|
||||
secret.c_str(), secret.size())));
|
||||
secret = "key number two";
|
||||
TSIGKeyPtr key_two;
|
||||
ASSERT_NO_THROW(key_two.reset(new
|
||||
TSIGKey(Name("two.com"),
|
||||
TSIGKey::HMACMD5_NAME(),
|
||||
secret.c_str(), secret.size())));
|
||||
TSIGKeyPtr nokey;
|
||||
|
||||
// Should be able to send and receive with no keys.
|
||||
// Neither client nor server will attempt to sign or verify.
|
||||
runTSIGTest(nokey, nokey);
|
||||
|
||||
// Client signs the request, server verfies but doesn't sign.
|
||||
runTSIGTest(key_one, nokey, false);
|
||||
|
||||
// Client and server use the same key to sign and verify.
|
||||
runTSIGTest(key_one, key_one);
|
||||
|
||||
// Server uses different key to sign the response.
|
||||
runTSIGTest(key_one, key_two, false);
|
||||
|
||||
// Client neither signs nor verifies, server responds with a signed answer
|
||||
// Since we are "liberal" in what we accept this should be ok.
|
||||
runTSIGTest(nokey, key_two);
|
||||
}
|
||||
|
||||
// Verify that the DNSClient receives the response from DNS and the received
|
||||
|
@@ -57,10 +57,8 @@ public:
|
||||
/// the simulate_send_exception_ flag is true.
|
||||
///
|
||||
/// @param comment Parameter is unused, but present in base class method.
|
||||
/// @param use_tsig_ Parameter is unused, but present in base class method.
|
||||
///
|
||||
virtual void sendUpdate(const std::string& /*comment*/,
|
||||
bool /* use_tsig_ = false */) {
|
||||
virtual void sendUpdate(const std::string& /*comment*/) {
|
||||
if (simulate_send_exception_) {
|
||||
// Make the flag a one-shot by resetting it.
|
||||
simulate_send_exception_ = false;
|
||||
@@ -290,8 +288,8 @@ TEST(NameAddTransaction, construction) {
|
||||
DdnsDomainPtr empty_domain;
|
||||
|
||||
ASSERT_NO_THROW(ncr = dhcp_ddns::NameChangeRequest::fromJSON(msg_str));
|
||||
ASSERT_NO_THROW(forward_domain.reset(new DdnsDomain("*", "", servers)));
|
||||
ASSERT_NO_THROW(reverse_domain.reset(new DdnsDomain("*", "", servers)));
|
||||
ASSERT_NO_THROW(forward_domain.reset(new DdnsDomain("*", servers)));
|
||||
ASSERT_NO_THROW(reverse_domain.reset(new DdnsDomain("*", servers)));
|
||||
|
||||
// Verify that construction with wrong change type fails.
|
||||
EXPECT_THROW(NameAddTransaction(io_service, ncr,
|
||||
|
@@ -57,10 +57,8 @@ public:
|
||||
/// the simulate_send_exception_ flag is true.
|
||||
///
|
||||
/// @param comment Parameter is unused, but present in base class method
|
||||
/// @param use_tsig Parameter is unused, but present in base class method.
|
||||
///
|
||||
virtual void sendUpdate(const std::string& /* comment */,
|
||||
bool /* use_tsig = false */) {
|
||||
virtual void sendUpdate(const std::string& /* comment */) {
|
||||
if (simulate_send_exception_) {
|
||||
// Make the flag a one-shot by resetting it.
|
||||
simulate_send_exception_ = false;
|
||||
@@ -292,8 +290,8 @@ TEST(NameRemoveTransaction, construction) {
|
||||
DdnsDomainPtr empty_domain;
|
||||
|
||||
ASSERT_NO_THROW(ncr = dhcp_ddns::NameChangeRequest::fromJSON(msg_str));
|
||||
ASSERT_NO_THROW(forward_domain.reset(new DdnsDomain("*", "", servers)));
|
||||
ASSERT_NO_THROW(reverse_domain.reset(new DdnsDomain("*", "", servers)));
|
||||
ASSERT_NO_THROW(forward_domain.reset(new DdnsDomain("*", servers)));
|
||||
ASSERT_NO_THROW(reverse_domain.reset(new DdnsDomain("*", servers)));
|
||||
|
||||
// Verify that construction with wrong change type fails.
|
||||
EXPECT_THROW(NameRemoveTransaction(io_service, ncr,
|
||||
|
@@ -18,6 +18,7 @@
|
||||
#include <nc_test_utils.h>
|
||||
#include <asio.hpp>
|
||||
#include <asiolink/udp_endpoint.h>
|
||||
#include <util/encode/base64.h>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
@@ -39,7 +40,8 @@ const bool NO_RDATA = false;
|
||||
FauxServer::FauxServer(asiolink::IOService& io_service,
|
||||
asiolink::IOAddress& address, size_t port)
|
||||
:io_service_(io_service), address_(address), port_(port),
|
||||
server_socket_(), receive_pending_(false), perpetual_receive_(true) {
|
||||
server_socket_(), receive_pending_(false), perpetual_receive_(true),
|
||||
tsig_key_() {
|
||||
|
||||
server_socket_.reset(new asio::ip::udp::socket(io_service_.get_io_service(),
|
||||
asio::ip::udp::v4()));
|
||||
@@ -53,7 +55,7 @@ FauxServer::FauxServer(asiolink::IOService& io_service,
|
||||
DnsServerInfo& server)
|
||||
:io_service_(io_service), address_(server.getIpAddress()),
|
||||
port_(server.getPort()), server_socket_(), receive_pending_(false),
|
||||
perpetual_receive_(true) {
|
||||
perpetual_receive_(true), tsig_key_() {
|
||||
server_socket_.reset(new asio::ip::udp::socket(io_service_.get_io_service(),
|
||||
asio::ip::udp::v4()));
|
||||
server_socket_->set_option(asio::socket_base::reuse_address(true));
|
||||
@@ -87,14 +89,21 @@ FauxServer::requestHandler(const asio::error_code& error,
|
||||
std::size_t bytes_recvd,
|
||||
const ResponseMode& response_mode,
|
||||
const dns::Rcode& response_rcode) {
|
||||
receive_pending_ = false;
|
||||
// If we encountered an error or received no data then fail.
|
||||
// We expect the client to send good requests.
|
||||
if (error.value() != 0 || bytes_recvd < 1) {
|
||||
ADD_FAILURE() << "FauxServer receive failed" << error.message();
|
||||
receive_pending_ = false;
|
||||
ADD_FAILURE() << "FauxServer receive failed: " << error.message();
|
||||
return;
|
||||
}
|
||||
|
||||
// If TSIG key isn't NULL, create a context and use to verify the
|
||||
// request and sign the response.
|
||||
dns::TSIGContextPtr context;
|
||||
if (tsig_key_) {
|
||||
context.reset(new dns::TSIGContext(*tsig_key_));
|
||||
}
|
||||
|
||||
// We have a successfully received data. We need to turn it into
|
||||
// a request in order to build a proper response.
|
||||
// Note D2UpdateMessage is geared towards making requests and
|
||||
@@ -104,11 +113,21 @@ FauxServer::requestHandler(const asio::error_code& error,
|
||||
util::InputBuffer request_buf(receive_buffer_, bytes_recvd);
|
||||
try {
|
||||
request.fromWire(request_buf);
|
||||
|
||||
// If contex is not NULL, then we need to verify the message.
|
||||
if (context) {
|
||||
dns::TSIGError error = context->verify(request.getTSIGRecord(),
|
||||
receive_buffer_,
|
||||
bytes_recvd);
|
||||
if (error != dns::TSIGError::NOERROR()) {
|
||||
isc_throw(TSIGVerifyError, "TSIG verification failed: "
|
||||
<< error.toText());
|
||||
}
|
||||
}
|
||||
} catch (const std::exception& ex) {
|
||||
// If the request cannot be parsed, then fail the test.
|
||||
// We expect the client to send good requests.
|
||||
ADD_FAILURE() << "FauxServer request is corrupt:" << ex.what();
|
||||
receive_pending_ = false;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -131,7 +150,19 @@ FauxServer::requestHandler(const asio::error_code& error,
|
||||
dns::MessageRenderer renderer;
|
||||
util::OutputBuffer response_buf(TEST_MSG_MAX);
|
||||
renderer.setBuffer(&response_buf);
|
||||
response.toWire(renderer);
|
||||
|
||||
if (response_mode == INVALID_TSIG) {
|
||||
// Create a different key to sign the response.
|
||||
std::string secret ("key that doesn't match");
|
||||
dns::TSIGKeyPtr key;
|
||||
ASSERT_NO_THROW(key.reset(new
|
||||
dns::TSIGKey(dns::Name("badkey"),
|
||||
dns::TSIGKey::HMACMD5_NAME(),
|
||||
secret.c_str(), secret.size())));
|
||||
context.reset(new dns::TSIGContext(*key));
|
||||
}
|
||||
|
||||
response.toWire(renderer, context.get());
|
||||
|
||||
// If mode is to ship garbage, then stomp on part of the rendered
|
||||
// message.
|
||||
@@ -154,7 +185,6 @@ FauxServer::requestHandler(const asio::error_code& error,
|
||||
ADD_FAILURE() << "FauxServer send failed: " << ex.what();
|
||||
}
|
||||
|
||||
receive_pending_ = false;
|
||||
if (perpetual_receive_) {
|
||||
// Schedule the next receive
|
||||
receive (response_mode, response_rcode);
|
||||
@@ -162,6 +192,7 @@ FauxServer::requestHandler(const asio::error_code& error,
|
||||
}
|
||||
|
||||
|
||||
|
||||
//********************** TimedIO class ***********************
|
||||
|
||||
TimedIO::TimedIO()
|
||||
@@ -205,7 +236,8 @@ TransactionTest::~TransactionTest() {
|
||||
|
||||
void
|
||||
TransactionTest::setupForIPv4Transaction(dhcp_ddns::NameChangeType chg_type,
|
||||
int change_mask) {
|
||||
int change_mask,
|
||||
const TSIGKeyInfoPtr& tsig_key_info) {
|
||||
const char* msg_str =
|
||||
"{"
|
||||
" \"change_type\" : 0 , "
|
||||
@@ -231,7 +263,7 @@ TransactionTest::setupForIPv4Transaction(dhcp_ddns::NameChangeType chg_type,
|
||||
forward_domain_.reset();
|
||||
} else {
|
||||
// Create the forward domain and then its servers.
|
||||
forward_domain_ = makeDomain("example.com.");
|
||||
forward_domain_ = makeDomain("example.com.", tsig_key_info);
|
||||
addDomainServer(forward_domain_, "forward.example.com",
|
||||
"127.0.0.1", 5301);
|
||||
addDomainServer(forward_domain_, "forward2.example.com",
|
||||
@@ -245,7 +277,7 @@ TransactionTest::setupForIPv4Transaction(dhcp_ddns::NameChangeType chg_type,
|
||||
reverse_domain_.reset();
|
||||
} else {
|
||||
// Create the reverse domain and its server.
|
||||
reverse_domain_ = makeDomain("2.168.192.in.addr.arpa.");
|
||||
reverse_domain_ = makeDomain("2.168.192.in.addr.arpa.", tsig_key_info);
|
||||
addDomainServer(reverse_domain_, "reverse.example.com",
|
||||
"127.0.0.1", 5301);
|
||||
addDomainServer(reverse_domain_, "reverse2.example.com",
|
||||
@@ -253,9 +285,17 @@ TransactionTest::setupForIPv4Transaction(dhcp_ddns::NameChangeType chg_type,
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
TransactionTest::setupForIPv4Transaction(dhcp_ddns::NameChangeType chg_type,
|
||||
int change_mask,
|
||||
const std::string& key_name) {
|
||||
setupForIPv4Transaction(chg_type, change_mask, makeTSIGKeyInfo(key_name));
|
||||
}
|
||||
|
||||
void
|
||||
TransactionTest::setupForIPv6Transaction(dhcp_ddns::NameChangeType chg_type,
|
||||
int change_mask) {
|
||||
int change_mask,
|
||||
const TSIGKeyInfoPtr& tsig_key_info) {
|
||||
const char* msg_str =
|
||||
"{"
|
||||
" \"change_type\" : 0 , "
|
||||
@@ -281,7 +321,7 @@ TransactionTest::setupForIPv6Transaction(dhcp_ddns::NameChangeType chg_type,
|
||||
forward_domain_.reset();
|
||||
} else {
|
||||
// Create the forward domain and then its servers.
|
||||
forward_domain_ = makeDomain("example.com.");
|
||||
forward_domain_ = makeDomain("example.com.", tsig_key_info);
|
||||
addDomainServer(forward_domain_, "fwd6-server.example.com",
|
||||
"::1", 5301);
|
||||
addDomainServer(forward_domain_, "fwd6-server2.example.com",
|
||||
@@ -295,7 +335,7 @@ TransactionTest::setupForIPv6Transaction(dhcp_ddns::NameChangeType chg_type,
|
||||
reverse_domain_.reset();
|
||||
} else {
|
||||
// Create the reverse domain and its server.
|
||||
reverse_domain_ = makeDomain("1.2001.ip6.arpa.");
|
||||
reverse_domain_ = makeDomain("1.2001.ip6.arpa.", tsig_key_info);
|
||||
addDomainServer(reverse_domain_, "rev6-server.example.com",
|
||||
"::1", 5301);
|
||||
addDomainServer(reverse_domain_, "rev6-server2.example.com",
|
||||
@@ -303,6 +343,13 @@ TransactionTest::setupForIPv6Transaction(dhcp_ddns::NameChangeType chg_type,
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
TransactionTest::setupForIPv6Transaction(dhcp_ddns::NameChangeType chg_type,
|
||||
int change_mask,
|
||||
const std::string& key_name) {
|
||||
setupForIPv6Transaction(chg_type, change_mask, makeTSIGKeyInfo(key_name));
|
||||
}
|
||||
|
||||
|
||||
//********************** Functions ****************************
|
||||
|
||||
@@ -385,12 +432,46 @@ dhcp_ddns::NameChangeRequestPtr makeNcrFromString(const std::string& ncr_str) {
|
||||
|
||||
DdnsDomainPtr makeDomain(const std::string& zone_name,
|
||||
const std::string& key_name) {
|
||||
DnsServerInfoStoragePtr servers(new DnsServerInfoStorage());
|
||||
DdnsDomainPtr domain(new DdnsDomain(zone_name, servers,
|
||||
makeTSIGKeyInfo(key_name)));
|
||||
return (domain);
|
||||
}
|
||||
|
||||
DdnsDomainPtr makeDomain(const std::string& zone_name,
|
||||
const TSIGKeyInfoPtr &tsig_key_info) {
|
||||
DdnsDomainPtr domain;
|
||||
DnsServerInfoStoragePtr servers(new DnsServerInfoStorage());
|
||||
domain.reset(new DdnsDomain(zone_name, key_name, servers));
|
||||
domain.reset(new DdnsDomain(zone_name, servers, tsig_key_info));
|
||||
return (domain);
|
||||
}
|
||||
|
||||
TSIGKeyInfoPtr makeTSIGKeyInfo(const std::string& key_name,
|
||||
const std::string& secret,
|
||||
const std::string& algorithm) {
|
||||
TSIGKeyInfoPtr key_info;
|
||||
if (!key_name.empty()) {
|
||||
if (!secret.empty()) {
|
||||
key_info.reset(new TSIGKeyInfo(key_name, algorithm, secret));
|
||||
} else {
|
||||
// Since secret was left blank, we'll convert key_name into a
|
||||
// base64 encoded string and use that.
|
||||
const uint8_t* bytes = reinterpret_cast<const uint8_t*>
|
||||
(key_name.c_str());
|
||||
size_t len = key_name.size();
|
||||
const vector<uint8_t> key_name_v(bytes, bytes + len);
|
||||
std::string key_name64
|
||||
= isc::util::encode::encodeBase64(key_name_v);
|
||||
|
||||
// Now, make the TSIGKeyInfo with a real base64 secret.
|
||||
key_info.reset(new TSIGKeyInfo(key_name, algorithm, key_name64));
|
||||
}
|
||||
}
|
||||
|
||||
return (key_info);
|
||||
|
||||
}
|
||||
|
||||
void addDomainServer(DdnsDomainPtr& domain, const std::string& name,
|
||||
const std::string& ip, const size_t port) {
|
||||
DnsServerInfoPtr server(new DnsServerInfo(name, asiolink::IOAddress(ip),
|
||||
|
@@ -41,7 +41,8 @@ class FauxServer {
|
||||
public:
|
||||
enum ResponseMode {
|
||||
USE_RCODE, // Generate a response with a given RCODE
|
||||
CORRUPT_RESP // Generate a corrupt response
|
||||
CORRUPT_RESP, // Generate a corrupt response
|
||||
INVALID_TSIG // Generate a repsonse with the wrong TSIG key
|
||||
};
|
||||
|
||||
// Reference to IOService to use for IO processing.
|
||||
@@ -63,6 +64,9 @@ public:
|
||||
// a receive has been completed, a new one will be automatically
|
||||
// initiated.
|
||||
bool perpetual_receive_;
|
||||
// TSIG Key to use to verify requests and sign responses. If its
|
||||
// NULL TSIG is not used.
|
||||
dns::TSIGKeyPtr tsig_key_;
|
||||
|
||||
/// @brief Constructor
|
||||
///
|
||||
@@ -96,7 +100,9 @@ public:
|
||||
/// @brief Socket IO Completion callback
|
||||
///
|
||||
/// This method servers as the Server's UDP socket receive callback handler.
|
||||
/// When the receive completes the handler is invoked with the
|
||||
/// When the receive completes the handler is invoked with the parameters
|
||||
/// listed.
|
||||
///
|
||||
/// @param error result code of the receive (determined by asio layer)
|
||||
/// @param bytes_recvd number of bytes received, if any
|
||||
/// @param response_mode type of response the handler should produce
|
||||
@@ -111,6 +117,14 @@ public:
|
||||
bool isReceivePending() {
|
||||
return receive_pending_;
|
||||
}
|
||||
|
||||
/// @brief Sets the TSIG key to the given value.
|
||||
///
|
||||
/// @param tsig_key Pointer to the TSIG key to use. If the pointer is
|
||||
/// empty, TSIG will not be used.
|
||||
void setTSIGKey (const dns::TSIGKeyPtr& tsig_key) {
|
||||
tsig_key_ = tsig_key;
|
||||
}
|
||||
};
|
||||
|
||||
/// @brief Provides a means to process IOService IO for a finite amount of time.
|
||||
@@ -187,8 +201,28 @@ public:
|
||||
/// CHG_REMOVE.
|
||||
/// @param change_mask determines which change directions are requested
|
||||
/// FORWARD_CHG, REVERSE_CHG, or FWD_AND_REV_CHG.
|
||||
/// @param tsig_key_info pointer to the TSIGKeyInfo to assign to both
|
||||
/// domains in the transaction. This will cause the transaction to
|
||||
/// use TSIG. If the pointer is empty, TSIG will not be used.
|
||||
void setupForIPv4Transaction(dhcp_ddns::NameChangeType change_type,
|
||||
int change_mask);
|
||||
int change_mask,
|
||||
const TSIGKeyInfoPtr& tsig_key_info =
|
||||
TSIGKeyInfoPtr());
|
||||
|
||||
/// @brief Creates a transaction which requests an IPv4 DNS update.
|
||||
///
|
||||
/// Convenience wrapper around the above method which accepts a string
|
||||
/// key_name from which the TSIGKeyInfo is constructed. Note the string
|
||||
/// may not be blank.
|
||||
///
|
||||
/// @param change_type selects the type of change requested, CHG_ADD or
|
||||
/// CHG_REMOVE.
|
||||
/// @param change_mask determines which change directions are requested
|
||||
/// FORWARD_CHG, REVERSE_CHG, or FWD_AND_REV_CHG.
|
||||
/// @param key_name value to use to create TSIG key. The value may not
|
||||
/// be blank.
|
||||
void setupForIPv4Transaction(dhcp_ddns::NameChangeType change_type,
|
||||
int change_mask, const std::string& key_name);
|
||||
|
||||
/// @brief Creates a transaction which requests an IPv6 DNS update.
|
||||
///
|
||||
@@ -201,8 +235,29 @@ public:
|
||||
/// CHG_REMOVE.
|
||||
/// @param change_mask determines which change directions are requested
|
||||
/// FORWARD_CHG, REVERSE_CHG, or FWD_AND_REV_CHG.
|
||||
/// @param tsig_key_info pointer to the TSIGKeyInfo to assign to both
|
||||
/// domains in the transaction. This will cause the transaction to
|
||||
/// use TSIG. If the pointer is empty, TSIG will not be used.
|
||||
void setupForIPv6Transaction(dhcp_ddns::NameChangeType change_type,
|
||||
int change_mask);
|
||||
int change_mask,
|
||||
const TSIGKeyInfoPtr& tsig_key_info =
|
||||
TSIGKeyInfoPtr());
|
||||
|
||||
/// @brief Creates a transaction which requests an IPv6 DNS update.
|
||||
///
|
||||
/// Convenience wrapper around the above method which accepts a string
|
||||
/// key_name from which the TSIGKeyInfo is constructed. Note the string
|
||||
/// may not be blank.
|
||||
///
|
||||
/// @param change_type selects the type of change requested, CHG_ADD or
|
||||
/// CHG_REMOVE.
|
||||
/// @param change_mask determines which change directions are requested
|
||||
/// FORWARD_CHG, REVERSE_CHG, or FWD_AND_REV_CHG.
|
||||
/// @param key_name value to use to create TSIG key, if blank TSIG will not
|
||||
/// be used.
|
||||
void setupForIPv6Transaction(dhcp_ddns::NameChangeType change_type,
|
||||
int change_mask, const std::string& key_name);
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -336,11 +391,38 @@ dhcp_ddns::NameChangeRequestPtr makeNcrFromString(const std::string& ncr_str);
|
||||
/// @brief Creates a DdnsDomain with the one server.
|
||||
///
|
||||
/// @param zone_name zone name of the domain
|
||||
/// @param key_name TSIG key name of the TSIG key for this domain
|
||||
/// @param key_name TSIG key name of the TSIG key for this domain. It will
|
||||
/// create a TSIGKeyInfo based on the key_name and assign it to the domain.
|
||||
///
|
||||
/// @throw Underlying methods may throw.
|
||||
extern DdnsDomainPtr makeDomain(const std::string& zone_name,
|
||||
const std::string& key_name = "");
|
||||
const std::string& key_name);
|
||||
|
||||
/// @brief Creates a DdnsDomain with the one server.
|
||||
///
|
||||
/// @param zone_name zone name of the domain
|
||||
/// @param tsig_key_info pointer to the TSIGInfog key for this domain.
|
||||
/// Defaults to an empty pointer, meaning this domain has no key.
|
||||
///
|
||||
/// @throw Underlying methods may throw.
|
||||
extern DdnsDomainPtr makeDomain(const std::string& zone_name,
|
||||
const TSIGKeyInfoPtr&
|
||||
tsig_key_info = TSIGKeyInfoPtr());
|
||||
|
||||
/// @brief Creates a TSIGKeyInfo
|
||||
///
|
||||
/// @param key_name name of the key
|
||||
/// @param secret key secret data as a base64 encoded string. If blank,
|
||||
/// then the secret value will be generated from key_name.
|
||||
/// @param algorithm algorithm to use. Defaults to MD5.
|
||||
/// @return a TSIGKeyInfoPtr for the newly created key. If key_name is blank
|
||||
/// the pointer will be empty.
|
||||
/// @throw Underlying methods may throw.
|
||||
extern
|
||||
TSIGKeyInfoPtr makeTSIGKeyInfo(const std::string& key_name,
|
||||
const std::string& secret = "",
|
||||
const std::string& algorithm
|
||||
= TSIGKeyInfo::HMAC_MD5_STR);
|
||||
|
||||
/// @brief Creates a DnsServerInfo and adds it to the given DdnsDomain.
|
||||
///
|
||||
|
@@ -78,15 +78,11 @@ public:
|
||||
/// sendUpdate without incorporating exectution of the state model
|
||||
/// into the test.
|
||||
/// It sets the DNS status update and posts IO_COMPLETED_EVT as does
|
||||
/// the normal callback, but rather than invoking runModel it stops
|
||||
/// the IO service. This allows tests to be constructed that consisted
|
||||
/// of generating a DNS request and invoking sendUpdate to post it and
|
||||
/// wait for response.
|
||||
/// the normal callback.
|
||||
virtual void operator()(DNSClient::Status status) {
|
||||
if (use_stub_callback_) {
|
||||
setDnsUpdateStatus(status);
|
||||
postNextEvent(IO_COMPLETED_EVT);
|
||||
getIOService()->stop();
|
||||
} else {
|
||||
// For tests which need to use the real callback.
|
||||
NameChangeTransaction::operator()(status);
|
||||
@@ -288,13 +284,34 @@ public:
|
||||
virtual ~NameChangeTransactionTest() {
|
||||
}
|
||||
|
||||
|
||||
/// @brief Instantiates a NameChangeStub test transaction
|
||||
/// The transaction is constructed around a predefined (i.e "canned")
|
||||
/// NameChangeRequest. The request has both forward and reverse DNS
|
||||
/// changes requested, and both forward and reverse domains are populated.
|
||||
NameChangeStubPtr makeCannedTransaction() {
|
||||
/// @param tsig_key_info pointer to the TSIGKeyInfo to use, defaults to
|
||||
/// an empty pointer, in which case TSIG will not be used.
|
||||
NameChangeStubPtr makeCannedTransaction(const TSIGKeyInfoPtr&
|
||||
tsig_key_info = TSIGKeyInfoPtr()) {
|
||||
// Creates IPv4 remove request, forward, and reverse domains.
|
||||
setupForIPv4Transaction(dhcp_ddns::CHG_ADD, FWD_AND_REV_CHG);
|
||||
setupForIPv4Transaction(dhcp_ddns::CHG_ADD, FWD_AND_REV_CHG,
|
||||
tsig_key_info);
|
||||
|
||||
// Now create the test transaction as would occur in update manager.
|
||||
// Instantiate the transaction as would be done by update manager.
|
||||
return (NameChangeStubPtr(new NameChangeStub(io_service_, ncr_,
|
||||
forward_domain_, reverse_domain_, cfg_mgr_)));
|
||||
}
|
||||
|
||||
/// @brief Instantiates a NameChangeStub test transaction
|
||||
/// The transaction is constructed around a predefined (i.e "canned")
|
||||
/// NameChangeRequest. The request has both forward and reverse DNS
|
||||
/// changes requested, and both forward and reverse domains are populated.
|
||||
/// @param key_name value to use to create TSIG key, if blank TSIG will not
|
||||
/// be used.
|
||||
NameChangeStubPtr makeCannedTransaction(const std::string& key_name) {
|
||||
// Creates IPv4 remove request, forward, and reverse domains.
|
||||
setupForIPv4Transaction(dhcp_ddns::CHG_ADD, FWD_AND_REV_CHG, key_name);
|
||||
|
||||
// Now create the test transaction as would occur in update manager.
|
||||
// Instantiate the transaction as would be done by update manager.
|
||||
@@ -374,8 +391,8 @@ TEST(NameChangeTransaction, construction) {
|
||||
DdnsDomainPtr empty_domain;
|
||||
|
||||
ASSERT_NO_THROW(ncr = dhcp_ddns::NameChangeRequest::fromJSON(msg_str));
|
||||
ASSERT_NO_THROW(forward_domain.reset(new DdnsDomain("*", "", servers)));
|
||||
ASSERT_NO_THROW(reverse_domain.reset(new DdnsDomain("*", "", servers)));
|
||||
ASSERT_NO_THROW(forward_domain.reset(new DdnsDomain("*", servers)));
|
||||
ASSERT_NO_THROW(reverse_domain.reset(new DdnsDomain("*", servers)));
|
||||
|
||||
// Verify that construction with a null IOServicePtr fails.
|
||||
// @todo Subject to change if multi-threading is implemented.
|
||||
@@ -1021,6 +1038,148 @@ TEST_F(NameChangeTransactionTest, sendUpdate) {
|
||||
EXPECT_EQ("response.example.com.", zone->getName().toText());
|
||||
}
|
||||
|
||||
/// @brief Tests that an unsigned response to a signed request is an error
|
||||
TEST_F(NameChangeTransactionTest, tsigUnsignedResponse) {
|
||||
NameChangeStubPtr name_change;
|
||||
ASSERT_NO_THROW(name_change = makeCannedTransaction("key_one"));
|
||||
ASSERT_NO_THROW(name_change->initDictionaries());
|
||||
ASSERT_TRUE(name_change->selectFwdServer());
|
||||
|
||||
// Create a server and start it listening.
|
||||
FauxServer server(*io_service_, *(name_change->getCurrentServer()));
|
||||
server.receive (FauxServer::USE_RCODE, dns::Rcode::NOERROR());
|
||||
|
||||
// Do the udpate.
|
||||
ASSERT_NO_FATAL_FAILURE(doOneExchange(name_change));
|
||||
|
||||
// Verify that next event is IO_COMPLETED_EVT and DNS status is
|
||||
// INVALID_RESPONSE.
|
||||
ASSERT_EQ(NameChangeTransaction::IO_COMPLETED_EVT,
|
||||
name_change->getNextEvent());
|
||||
|
||||
ASSERT_EQ(DNSClient::INVALID_RESPONSE, name_change->getDnsUpdateStatus());
|
||||
|
||||
// When TSIG errors occur, only the message header (including Rcode) is
|
||||
// unpacked. In this case, it should be NOERROR but have no other
|
||||
// information.
|
||||
D2UpdateMessagePtr response = name_change->getDnsUpdateResponse();
|
||||
ASSERT_TRUE(response);
|
||||
ASSERT_EQ(dns::Rcode::NOERROR().getCode(), response->getRcode().getCode());
|
||||
EXPECT_FALSE(response->getZone());
|
||||
}
|
||||
|
||||
/// @brief Tests that a response signed with the wrong key is an error
|
||||
TEST_F(NameChangeTransactionTest, tsigInvalidResponse) {
|
||||
NameChangeStubPtr name_change;
|
||||
ASSERT_NO_THROW(name_change = makeCannedTransaction("key_one"));
|
||||
ASSERT_NO_THROW(name_change->initDictionaries());
|
||||
ASSERT_TRUE(name_change->selectFwdServer());
|
||||
|
||||
// Create a server, tell it to sign responses with a "random" key,
|
||||
// then start it listening.
|
||||
FauxServer server(*io_service_, *(name_change->getCurrentServer()));
|
||||
server.receive (FauxServer::INVALID_TSIG, dns::Rcode::NOERROR());
|
||||
|
||||
// Do the udpate.
|
||||
ASSERT_NO_FATAL_FAILURE(doOneExchange(name_change));
|
||||
|
||||
// Verify that next event is IO_COMPLETED_EVT and DNS status is
|
||||
// INVALID_RESPONSE.
|
||||
ASSERT_EQ(NameChangeTransaction::IO_COMPLETED_EVT,
|
||||
name_change->getNextEvent());
|
||||
|
||||
ASSERT_EQ(DNSClient::INVALID_RESPONSE, name_change->getDnsUpdateStatus());
|
||||
|
||||
// When TSIG errors occur, only the message header (including Rcode) is
|
||||
// unpacked. In this case, it should be NOERROR but have no other
|
||||
// information.
|
||||
D2UpdateMessagePtr response = name_change->getDnsUpdateResponse();
|
||||
ASSERT_TRUE(response);
|
||||
ASSERT_EQ(dns::Rcode::NOERROR().getCode(), response->getRcode().getCode());
|
||||
EXPECT_FALSE(response->getZone());
|
||||
}
|
||||
|
||||
/// @brief Tests that a signed response to an unsigned request is ok.
|
||||
/// Currently our policy is to accept a signed response to an unsigned request
|
||||
/// even though the spec says a server MUST not do that.
|
||||
TEST_F(NameChangeTransactionTest, tsigUnexpectedSignedResponse) {
|
||||
NameChangeStubPtr name_change;
|
||||
ASSERT_NO_THROW(name_change = makeCannedTransaction());
|
||||
ASSERT_NO_THROW(name_change->initDictionaries());
|
||||
ASSERT_TRUE(name_change->selectFwdServer());
|
||||
|
||||
// Create a server, tell it to sign responses with a "random" key,
|
||||
// then start it listening.
|
||||
FauxServer server(*io_service_, *(name_change->getCurrentServer()));
|
||||
server.receive (FauxServer::INVALID_TSIG, dns::Rcode::NOERROR());
|
||||
|
||||
// Perform an update without TSIG.
|
||||
ASSERT_NO_FATAL_FAILURE(doOneExchange(name_change));
|
||||
|
||||
// Verify that next event is IO_COMPLETED_EVT and DNS status is SUCCESS.
|
||||
ASSERT_EQ(NameChangeTransaction::IO_COMPLETED_EVT,
|
||||
name_change->getNextEvent());
|
||||
|
||||
ASSERT_EQ(DNSClient::SUCCESS, name_change->getDnsUpdateStatus());
|
||||
|
||||
D2UpdateMessagePtr response = name_change->getDnsUpdateResponse();
|
||||
ASSERT_TRUE(response);
|
||||
ASSERT_EQ(dns::Rcode::NOERROR().getCode(), response->getRcode().getCode());
|
||||
D2ZonePtr zone = response->getZone();
|
||||
EXPECT_TRUE(zone);
|
||||
EXPECT_EQ("response.example.com.", zone->getName().toText());
|
||||
}
|
||||
|
||||
/// @brief Tests that a TSIG udpate succeeds when client and server both use
|
||||
/// the right key. Runs the test for all supported algorithms.
|
||||
TEST_F(NameChangeTransactionTest, tsigAllValid) {
|
||||
std::vector<std::string>algorithms;
|
||||
algorithms.push_back(TSIGKeyInfo::HMAC_MD5_STR);
|
||||
algorithms.push_back(TSIGKeyInfo::HMAC_SHA1_STR);
|
||||
algorithms.push_back(TSIGKeyInfo::HMAC_SHA224_STR);
|
||||
algorithms.push_back(TSIGKeyInfo::HMAC_SHA256_STR);
|
||||
algorithms.push_back(TSIGKeyInfo::HMAC_SHA384_STR);
|
||||
algorithms.push_back(TSIGKeyInfo::HMAC_SHA512_STR);
|
||||
|
||||
for (int i = 0; i < algorithms.size(); ++i) {
|
||||
SCOPED_TRACE (algorithms[i]);
|
||||
TSIGKeyInfoPtr key;
|
||||
ASSERT_NO_THROW(key.reset(new TSIGKeyInfo("test_key",
|
||||
algorithms[i],
|
||||
"GWG/Xfbju4O2iXGqkSu4PQ==")));
|
||||
NameChangeStubPtr name_change;
|
||||
ASSERT_NO_THROW(name_change = makeCannedTransaction(key));
|
||||
ASSERT_NO_THROW(name_change->initDictionaries());
|
||||
ASSERT_TRUE(name_change->selectFwdServer());
|
||||
|
||||
// Create a server, set its TSIG key, and then start it listening.
|
||||
FauxServer server(*io_service_, *(name_change->getCurrentServer()));
|
||||
// Since we create a new server instance each time we need to tell
|
||||
// it not reschedule receives automatically.
|
||||
server.perpetual_receive_ = false;
|
||||
server.setTSIGKey(key->getTSIGKey());
|
||||
server.receive (FauxServer::USE_RCODE, dns::Rcode::NOERROR());
|
||||
|
||||
// Do the update.
|
||||
ASSERT_NO_FATAL_FAILURE(doOneExchange(name_change));
|
||||
|
||||
// Verify that next event is IO_COMPLETED_EVT and DNS status is SUCCESS.
|
||||
ASSERT_EQ(NameChangeTransaction::IO_COMPLETED_EVT,
|
||||
name_change->getNextEvent());
|
||||
|
||||
ASSERT_EQ(DNSClient::SUCCESS, name_change->getDnsUpdateStatus());
|
||||
|
||||
D2UpdateMessagePtr response = name_change->getDnsUpdateResponse();
|
||||
ASSERT_TRUE(response);
|
||||
ASSERT_EQ(dns::Rcode::NOERROR().getCode(),
|
||||
response->getRcode().getCode());
|
||||
D2ZonePtr zone = response->getZone();
|
||||
EXPECT_TRUE(zone);
|
||||
EXPECT_EQ("response.example.com.", zone->getName().toText());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// @brief Tests the prepNewRequest method
|
||||
TEST_F(NameChangeTransactionTest, prepNewRequest) {
|
||||
NameChangeStubPtr name_change;
|
||||
|
@@ -51,10 +51,18 @@ pkglibexec_PROGRAMS = b10-dhcp4
|
||||
|
||||
b10_dhcp4_SOURCES = main.cc
|
||||
b10_dhcp4_SOURCES += ctrl_dhcp4_srv.cc ctrl_dhcp4_srv.h
|
||||
b10_dhcp4_SOURCES += config_parser.cc config_parser.h
|
||||
b10_dhcp4_SOURCES += json_config_parser.cc json_config_parser.h
|
||||
b10_dhcp4_SOURCES += dhcp4_log.cc dhcp4_log.h
|
||||
b10_dhcp4_SOURCES += dhcp4_srv.cc dhcp4_srv.h
|
||||
|
||||
if CONFIG_BACKEND_BUNDY
|
||||
b10_dhcp4_SOURCES += bundy_controller.cc
|
||||
endif
|
||||
|
||||
if CONFIG_BACKEND_JSON
|
||||
b10_dhcp4_SOURCES += kea_controller.cc
|
||||
endif
|
||||
|
||||
nodist_b10_dhcp4_SOURCES = dhcp4_messages.h dhcp4_messages.cc
|
||||
EXTRA_DIST += dhcp4_messages.mes
|
||||
|
||||
|
219
src/bin/dhcp4/bundy_controller.cc
Normal file
219
src/bin/dhcp4/bundy_controller.cc
Normal file
@@ -0,0 +1,219 @@
|
||||
// Copyright (C) 2012-2014 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 <config.h>
|
||||
|
||||
#include <asiolink/asiolink.h>
|
||||
#include <cc/data.h>
|
||||
#include <cc/session.h>
|
||||
#include <config/ccsession.h>
|
||||
#include <dhcp/iface_mgr.h>
|
||||
#include <dhcp4/json_config_parser.h>
|
||||
#include <dhcp4/ctrl_dhcp4_srv.h>
|
||||
#include <dhcp4/dhcp4_log.h>
|
||||
#include <dhcp4/spec_config.h>
|
||||
#include <dhcpsrv/cfgmgr.h>
|
||||
#include <dhcpsrv/dhcp_config_parser.h>
|
||||
#include <exceptions/exceptions.h>
|
||||
#include <hooks/hooks_manager.h>
|
||||
#include <util/buffer.h>
|
||||
|
||||
#include <cassert>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
using namespace isc::asiolink;
|
||||
using namespace isc::cc;
|
||||
using namespace isc::config;
|
||||
using namespace isc::data;
|
||||
using namespace isc::dhcp;
|
||||
using namespace isc::hooks;
|
||||
using namespace isc::log;
|
||||
using namespace isc::util;
|
||||
using namespace std;
|
||||
|
||||
namespace isc {
|
||||
namespace dhcp {
|
||||
|
||||
/// @brief Helper session object that represents raw connection to msgq.
|
||||
isc::cc::Session* cc_session_ = NULL;
|
||||
|
||||
/// @brief Session that receives configuration and commands
|
||||
isc::config::ModuleCCSession* config_session_ = NULL;
|
||||
|
||||
/// @brief A dummy configuration handler that always returns success.
|
||||
///
|
||||
/// This configuration handler does not perform configuration
|
||||
/// parsing and always returns success. A dummy handler should
|
||||
/// be installed using \ref isc::config::ModuleCCSession ctor
|
||||
/// to get the initial configuration. This initial configuration
|
||||
/// comprises values for only those elements that were modified
|
||||
/// the previous session. The \ref dhcp4ConfigHandler can't be
|
||||
/// used to parse the initial configuration because it needs the
|
||||
/// full configuration to satisfy dependencies between the
|
||||
/// various configuration values. Installing the dummy handler
|
||||
/// that guarantees to return success causes initial configuration
|
||||
/// to be stored for the session being created and that it can
|
||||
/// be later accessed with
|
||||
/// \ref isc::config::ConfigData::getFullConfig().
|
||||
///
|
||||
/// @param new_config new configuration.
|
||||
///
|
||||
/// @return success configuration status.
|
||||
ConstElementPtr
|
||||
dhcp4StubConfigHandler(ConstElementPtr) {
|
||||
// This configuration handler is intended to be used only
|
||||
// when the initial configuration comes in. To receive this
|
||||
// configuration a pointer to this handler must be passed
|
||||
// using ModuleCCSession constructor. This constructor will
|
||||
// invoke the handler and will store the configuration for
|
||||
// the configuration session when the handler returns success.
|
||||
// Since this configuration is partial we just pretend to
|
||||
// parse it and always return success. The function that
|
||||
// initiates the session must get the configuration on its
|
||||
// own using getFullConfig.
|
||||
return (isc::config::createAnswer(0, "Configuration accepted."));
|
||||
}
|
||||
|
||||
ConstElementPtr
|
||||
bundyConfigHandler(ConstElementPtr new_config) {
|
||||
if (!ControlledDhcpv4Srv::getInstance() || !config_session_) {
|
||||
// That should never happen as we install config_handler
|
||||
// after we instantiate the server.
|
||||
ConstElementPtr answer =
|
||||
isc::config::createAnswer(1, "Configuration rejected,"
|
||||
" server is during startup/shutdown phase.");
|
||||
return (answer);
|
||||
}
|
||||
|
||||
// The configuration passed to this handler function is partial.
|
||||
// In other words, it just includes the values being modified.
|
||||
// In the same time, there are dependencies between various
|
||||
// DHCP configuration parsers. For example: the option value can
|
||||
// be set if the definition of this option is set. If someone removes
|
||||
// an existing option definition then the partial configuration that
|
||||
// removes that definition is triggered while a relevant option value
|
||||
// may remain configured. This eventually results in the DHCP server
|
||||
// configuration being in the inconsistent state.
|
||||
// In order to work around this problem we need to merge the new
|
||||
// configuration with the existing (full) configuration.
|
||||
|
||||
// Let's create a new object that will hold the merged configuration.
|
||||
boost::shared_ptr<MapElement> merged_config(new MapElement());
|
||||
// Let's get the existing configuration.
|
||||
ConstElementPtr full_config = config_session_->getFullConfig();
|
||||
// The full_config and merged_config should be always non-NULL
|
||||
// but to provide some level of exception safety we check that they
|
||||
// really are (in case we go out of memory).
|
||||
if (full_config && merged_config) {
|
||||
merged_config->setValue(full_config->mapValue());
|
||||
|
||||
// Merge an existing and new configuration.
|
||||
isc::data::merge(merged_config, new_config);
|
||||
LOG_DEBUG(dhcp4_logger, DBG_DHCP4_COMMAND, DHCP4_CONFIG_UPDATE)
|
||||
.arg(full_config->str());
|
||||
}
|
||||
|
||||
// Configure the server.
|
||||
return (ControlledDhcpv4Srv::processConfig(merged_config));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void ControlledDhcpv4Srv::init(const std::string& /*config_file*/) {
|
||||
|
||||
string specfile;
|
||||
if (getenv("B10_FROM_BUILD")) {
|
||||
specfile = string(getenv("B10_FROM_BUILD")) +
|
||||
"/src/bin/dhcp4/dhcp4.spec";
|
||||
} else {
|
||||
specfile = string(DHCP4_SPECFILE_LOCATION);
|
||||
}
|
||||
|
||||
/// @todo: Check if session is not established already. Throw, if it is.
|
||||
|
||||
LOG_DEBUG(dhcp4_logger, DBG_DHCP4_START, DHCP4_CCSESSION_STARTING)
|
||||
.arg(specfile);
|
||||
cc_session_ = new Session(io_service_.get_io_service());
|
||||
// Create a session with the dummy configuration handler.
|
||||
// Dumy configuration handler is internally invoked by the
|
||||
// constructor and on success the constructor updates
|
||||
// the current session with the configuration that had been
|
||||
// committed in the previous session. If we did not install
|
||||
// the dummy handler, the previous configuration would have
|
||||
// been lost.
|
||||
config_session_ = new ModuleCCSession(specfile, *cc_session_,
|
||||
dhcp4StubConfigHandler,
|
||||
processCommand, false);
|
||||
config_session_->start();
|
||||
|
||||
// We initially create ModuleCCSession() without configHandler, as
|
||||
// the session module is too eager to send partial configuration.
|
||||
// We want to get the full configuration, so we explicitly call
|
||||
// getFullConfig() and then pass it to our configHandler.
|
||||
config_session_->setConfigHandler(bundyConfigHandler);
|
||||
|
||||
try {
|
||||
configureDhcp4Server(*this, config_session_->getFullConfig());
|
||||
|
||||
// Server will start DDNS communications if its enabled.
|
||||
server_->startD2();
|
||||
|
||||
// Configuration may disable or enable interfaces so we have to
|
||||
// reopen sockets according to new configuration.
|
||||
openActiveSockets(getPort(), useBroadcast());
|
||||
|
||||
} catch (const std::exception& ex) {
|
||||
LOG_ERROR(dhcp4_logger, DHCP4_CONFIG_LOAD_FAIL).arg(ex.what());
|
||||
|
||||
}
|
||||
|
||||
/// Integrate the asynchronous I/O model of BIND 10 configuration
|
||||
/// control with the "select" model of the DHCP server. This is
|
||||
/// fully explained in \ref dhcpv4Session.
|
||||
int ctrl_socket = cc_session_->getSocketDesc();
|
||||
LOG_DEBUG(dhcp4_logger, DBG_DHCP4_START, DHCP4_CCSESSION_STARTED)
|
||||
.arg(ctrl_socket);
|
||||
IfaceMgr::instance().addExternalSocket(ctrl_socket, sessionReader);
|
||||
}
|
||||
|
||||
|
||||
void ControlledDhcpv4Srv::cleanup() {
|
||||
if (config_session_) {
|
||||
delete config_session_;
|
||||
config_session_ = NULL;
|
||||
}
|
||||
if (cc_session_) {
|
||||
|
||||
int ctrl_socket = cc_session_->getSocketDesc();
|
||||
cc_session_->disconnect();
|
||||
|
||||
IfaceMgr::instance().deleteExternalSocket(ctrl_socket);
|
||||
delete cc_session_;
|
||||
cc_session_ = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Daemon::loggerInit(const char* log_name, bool verbose) {
|
||||
isc::log::initLogger(log_name,
|
||||
(verbose ? isc::log::DEBUG : isc::log::INFO),
|
||||
isc::log::MAX_DEBUG_LEVEL, NULL, true);
|
||||
}
|
||||
|
||||
};
|
||||
};
|
@@ -1,4 +1,4 @@
|
||||
// Copyright (C) 2012-2014 Internet Systems Consortium, Inc. ("ISC")
|
||||
// Copyright (C) 2014 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
|
||||
@@ -13,36 +13,14 @@
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
#include <config.h>
|
||||
|
||||
#include <asiolink/asiolink.h>
|
||||
#include <cc/data.h>
|
||||
#include <cc/session.h>
|
||||
#include <config/ccsession.h>
|
||||
#include <dhcp/iface_mgr.h>
|
||||
#include <dhcp4/config_parser.h>
|
||||
#include <dhcp4/ctrl_dhcp4_srv.h>
|
||||
#include <dhcp4/dhcp4_log.h>
|
||||
#include <dhcp4/spec_config.h>
|
||||
#include <dhcpsrv/cfgmgr.h>
|
||||
#include <dhcpsrv/dhcp_config_parser.h>
|
||||
#include <exceptions/exceptions.h>
|
||||
#include <hooks/hooks_manager.h>
|
||||
#include <util/buffer.h>
|
||||
#include <dhcp4/json_config_parser.h>
|
||||
|
||||
#include <cassert>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
using namespace isc::asiolink;
|
||||
using namespace isc::cc;
|
||||
using namespace isc::config;
|
||||
using namespace isc::data;
|
||||
using namespace isc::dhcp;
|
||||
using namespace isc::hooks;
|
||||
using namespace isc::log;
|
||||
using namespace isc::util;
|
||||
using namespace std;
|
||||
|
||||
namespace isc {
|
||||
@@ -51,114 +29,24 @@ namespace dhcp {
|
||||
ControlledDhcpv4Srv* ControlledDhcpv4Srv::server_ = NULL;
|
||||
|
||||
ConstElementPtr
|
||||
ControlledDhcpv4Srv::dhcp4StubConfigHandler(ConstElementPtr) {
|
||||
// This configuration handler is intended to be used only
|
||||
// when the initial configuration comes in. To receive this
|
||||
// configuration a pointer to this handler must be passed
|
||||
// using ModuleCCSession constructor. This constructor will
|
||||
// invoke the handler and will store the configuration for
|
||||
// the configuration session when the handler returns success.
|
||||
// Since this configuration is partial we just pretend to
|
||||
// parse it and always return success. The function that
|
||||
// initiates the session must get the configuration on its
|
||||
// own using getFullConfig.
|
||||
return (isc::config::createAnswer(0, "Configuration accepted."));
|
||||
}
|
||||
|
||||
ConstElementPtr
|
||||
ControlledDhcpv4Srv::dhcp4ConfigHandler(ConstElementPtr new_config) {
|
||||
if (!server_ || !server_->config_session_) {
|
||||
// That should never happen as we install config_handler
|
||||
// after we instantiate the server.
|
||||
ConstElementPtr answer =
|
||||
isc::config::createAnswer(1, "Configuration rejected,"
|
||||
" server is during startup/shutdown phase.");
|
||||
return (answer);
|
||||
}
|
||||
|
||||
// The configuration passed to this handler function is partial.
|
||||
// In other words, it just includes the values being modified.
|
||||
// In the same time, there are dependencies between various
|
||||
// DHCP configuration parsers. For example: the option value can
|
||||
// be set if the definition of this option is set. If someone removes
|
||||
// an existing option definition then the partial configuration that
|
||||
// removes that definition is triggered while a relevant option value
|
||||
// may remain configured. This eventually results in the DHCP server
|
||||
// configuration being in the inconsistent state.
|
||||
// In order to work around this problem we need to merge the new
|
||||
// configuration with the existing (full) configuration.
|
||||
|
||||
// Let's create a new object that will hold the merged configuration.
|
||||
boost::shared_ptr<MapElement> merged_config(new MapElement());
|
||||
// Let's get the existing configuration.
|
||||
ConstElementPtr full_config = server_->config_session_->getFullConfig();
|
||||
// The full_config and merged_config should be always non-NULL
|
||||
// but to provide some level of exception safety we check that they
|
||||
// really are (in case we go out of memory).
|
||||
if (full_config && merged_config) {
|
||||
merged_config->setValue(full_config->mapValue());
|
||||
|
||||
// Merge an existing and new configuration.
|
||||
isc::data::merge(merged_config, new_config);
|
||||
LOG_DEBUG(dhcp4_logger, DBG_DHCP4_COMMAND, DHCP4_CONFIG_UPDATE)
|
||||
.arg(full_config->str());
|
||||
}
|
||||
|
||||
// Configure the server.
|
||||
ConstElementPtr answer = configureDhcp4Server(*server_, merged_config);
|
||||
|
||||
// Check that configuration was successful. If not, do not reopen sockets.
|
||||
int rcode = 0;
|
||||
parseAnswer(rcode, answer);
|
||||
if (rcode != 0) {
|
||||
return (answer);
|
||||
}
|
||||
|
||||
// Server will start DDNS communications if its enabled.
|
||||
try {
|
||||
server_->startD2();
|
||||
} catch (const std::exception& ex) {
|
||||
std::ostringstream err;
|
||||
err << "error starting DHCP_DDNS client "
|
||||
" after server reconfiguration: " << ex.what();
|
||||
return (isc::config::createAnswer(1, err.str()));
|
||||
}
|
||||
|
||||
// Configuration may change active interfaces. Therefore, we have to reopen
|
||||
// sockets according to new configuration. This operation is not exception
|
||||
// safe and we really don't want to emit exceptions to the callback caller.
|
||||
// Instead, catch an exception and create appropriate answer.
|
||||
try {
|
||||
server_->openActiveSockets(server_->getPort(), server_->useBroadcast());
|
||||
} catch (std::exception& ex) {
|
||||
std::ostringstream err;
|
||||
err << "failed to open sockets after server reconfiguration: " << ex.what();
|
||||
answer = isc::config::createAnswer(1, err.str());
|
||||
}
|
||||
return (answer);
|
||||
}
|
||||
|
||||
ConstElementPtr
|
||||
ControlledDhcpv4Srv::dhcp4CommandHandler(const string& command, ConstElementPtr args) {
|
||||
LOG_DEBUG(dhcp4_logger, DBG_DHCP4_COMMAND, DHCP4_COMMAND_RECEIVED)
|
||||
.arg(command).arg(args->str());
|
||||
|
||||
if (command == "shutdown") {
|
||||
if (ControlledDhcpv4Srv::server_) {
|
||||
ControlledDhcpv4Srv::server_->shutdown();
|
||||
ControlledDhcpv4Srv::commandShutdownHandler(const string&, ConstElementPtr) {
|
||||
if (ControlledDhcpv4Srv::getInstance()) {
|
||||
ControlledDhcpv4Srv::getInstance()->shutdown();
|
||||
} else {
|
||||
LOG_WARN(dhcp4_logger, DHCP4_NOT_RUNNING);
|
||||
ConstElementPtr answer = isc::config::createAnswer(1,
|
||||
"Shutdown failure.");
|
||||
return (answer);
|
||||
}
|
||||
ConstElementPtr answer = isc::config::createAnswer(0,
|
||||
"Shutting down.");
|
||||
ConstElementPtr answer = isc::config::createAnswer(0, "Shutting down.");
|
||||
return (answer);
|
||||
}
|
||||
|
||||
} else if (command == "libreload") {
|
||||
// TODO delete any stored CalloutHandles referring to the old libraries
|
||||
// Get list of currently loaded libraries and reload them.
|
||||
ConstElementPtr
|
||||
ControlledDhcpv4Srv::commandLibReloadHandler(const string&, ConstElementPtr) {
|
||||
|
||||
/// @todo delete any stored CalloutHandles referring to the old libraries
|
||||
/// Get list of currently loaded libraries and reload them.
|
||||
vector<string> loaded = HooksManager::getLibraryNames();
|
||||
bool status = HooksManager::loadLibraries(loaded);
|
||||
if (!status) {
|
||||
@@ -170,98 +58,112 @@ ControlledDhcpv4Srv::dhcp4CommandHandler(const string& command, ConstElementPtr
|
||||
ConstElementPtr answer = isc::config::createAnswer(0,
|
||||
"Hooks libraries successfully reloaded.");
|
||||
return (answer);
|
||||
}
|
||||
|
||||
ConstElementPtr
|
||||
ControlledDhcpv4Srv::commandConfigReloadHandler(const string&,
|
||||
ConstElementPtr args) {
|
||||
return (processConfig(args));
|
||||
}
|
||||
|
||||
ConstElementPtr
|
||||
ControlledDhcpv4Srv::processCommand(const string& command,
|
||||
ConstElementPtr args) {
|
||||
LOG_DEBUG(dhcp4_logger, DBG_DHCP4_COMMAND, DHCP4_COMMAND_RECEIVED)
|
||||
.arg(command).arg(args->str());
|
||||
|
||||
ControlledDhcpv4Srv* srv = ControlledDhcpv4Srv::getInstance();
|
||||
|
||||
if (!srv) {
|
||||
ConstElementPtr no_srv = isc::config::createAnswer(1,
|
||||
"Server object not initialized, so can't process command '" +
|
||||
command + "', arguments: '" + args->str() + "'.");
|
||||
return (no_srv);
|
||||
}
|
||||
|
||||
ConstElementPtr answer = isc::config::createAnswer(1,
|
||||
"Unrecognized command.");
|
||||
try {
|
||||
if (command == "shutdown") {
|
||||
return (srv->commandShutdownHandler(command, args));
|
||||
|
||||
} else if (command == "libreload") {
|
||||
return (srv->commandLibReloadHandler(command, args));
|
||||
|
||||
} else if (command == "config-reload") {
|
||||
return (srv->commandConfigReloadHandler(command, args));
|
||||
|
||||
}
|
||||
ConstElementPtr answer = isc::config::createAnswer(1,
|
||||
"Unrecognized command:" + command);
|
||||
return (answer);
|
||||
} catch (const Exception& ex) {
|
||||
return (isc::config::createAnswer(1, "Error while processing command '"
|
||||
+ command + "':" + ex.what() +
|
||||
", params: '" + args->str() + "'"));
|
||||
}
|
||||
}
|
||||
|
||||
isc::data::ConstElementPtr
|
||||
ControlledDhcpv4Srv::processConfig(isc::data::ConstElementPtr config) {
|
||||
|
||||
LOG_DEBUG(dhcp4_logger, DBG_DHCP4_COMMAND, DHCP4_CONFIG_RECEIVED)
|
||||
.arg(config->str());
|
||||
|
||||
ControlledDhcpv4Srv* srv = ControlledDhcpv4Srv::getInstance();
|
||||
|
||||
// Single stream instance used in all error clauses
|
||||
std::ostringstream err;
|
||||
|
||||
if (!srv) {
|
||||
err << "Server object not initialized, can't process config.";
|
||||
return (isc::config::createAnswer(1, err.str()));
|
||||
}
|
||||
|
||||
ConstElementPtr answer = configureDhcp4Server(*srv, config);
|
||||
|
||||
|
||||
// Check that configuration was successful. If not, do not reopen sockets
|
||||
// and don't bother with DDNS stuff.
|
||||
try {
|
||||
int rcode = 0;
|
||||
isc::config::parseAnswer(rcode, answer);
|
||||
if (rcode != 0) {
|
||||
return (answer);
|
||||
}
|
||||
} catch (const std::exception& ex) {
|
||||
err << "Failed to process configuration:" << ex.what();
|
||||
return (isc::config::createAnswer(1, err.str()));
|
||||
}
|
||||
|
||||
// Server will start DDNS communications if its enabled.
|
||||
try {
|
||||
srv->startD2();
|
||||
} catch (const std::exception& ex) {
|
||||
err << "Error starting DHCP_DDNS client after server reconfiguration: "
|
||||
<< ex.what();
|
||||
return (isc::config::createAnswer(1, err.str()));
|
||||
}
|
||||
|
||||
// Configuration may change active interfaces. Therefore, we have to reopen
|
||||
// sockets according to new configuration. This operation is not exception
|
||||
// safe and we really don't want to emit exceptions to whoever called this
|
||||
// method. Instead, catch an exception and create appropriate answer.
|
||||
try {
|
||||
srv->openActiveSockets(srv->getPort(), getInstance()->useBroadcast());
|
||||
} catch (std::exception& ex) {
|
||||
err << "failed to open sockets after server reconfiguration: "
|
||||
<< ex.what();
|
||||
answer = isc::config::createAnswer(1, err.str());
|
||||
}
|
||||
return (answer);
|
||||
}
|
||||
|
||||
void ControlledDhcpv4Srv::sessionReader(void) {
|
||||
// Process one asio event. If there are more events, iface_mgr will call
|
||||
// this callback more than once.
|
||||
if (server_) {
|
||||
server_->io_service_.run_one();
|
||||
}
|
||||
}
|
||||
|
||||
void ControlledDhcpv4Srv::establishSession() {
|
||||
|
||||
string specfile;
|
||||
if (getenv("B10_FROM_BUILD")) {
|
||||
specfile = string(getenv("B10_FROM_BUILD")) +
|
||||
"/src/bin/dhcp4/dhcp4.spec";
|
||||
} else {
|
||||
specfile = string(DHCP4_SPECFILE_LOCATION);
|
||||
}
|
||||
|
||||
/// @todo: Check if session is not established already. Throw, if it is.
|
||||
|
||||
LOG_DEBUG(dhcp4_logger, DBG_DHCP4_START, DHCP4_CCSESSION_STARTING)
|
||||
.arg(specfile);
|
||||
cc_session_ = new Session(io_service_.get_io_service());
|
||||
// Create a session with the dummy configuration handler.
|
||||
// Dumy configuration handler is internally invoked by the
|
||||
// constructor and on success the constructor updates
|
||||
// the current session with the configuration that had been
|
||||
// committed in the previous session. If we did not install
|
||||
// the dummy handler, the previous configuration would have
|
||||
// been lost.
|
||||
config_session_ = new ModuleCCSession(specfile, *cc_session_,
|
||||
dhcp4StubConfigHandler,
|
||||
dhcp4CommandHandler, false);
|
||||
config_session_->start();
|
||||
|
||||
// We initially create ModuleCCSession() without configHandler, as
|
||||
// the session module is too eager to send partial configuration.
|
||||
// We want to get the full configuration, so we explicitly call
|
||||
// getFullConfig() and then pass it to our configHandler.
|
||||
config_session_->setConfigHandler(dhcp4ConfigHandler);
|
||||
|
||||
try {
|
||||
configureDhcp4Server(*this, config_session_->getFullConfig());
|
||||
|
||||
// Server will start DDNS communications if its enabled.
|
||||
server_->startD2();
|
||||
|
||||
// Configuration may disable or enable interfaces so we have to
|
||||
// reopen sockets according to new configuration.
|
||||
openActiveSockets(getPort(), useBroadcast());
|
||||
|
||||
} catch (const std::exception& ex) {
|
||||
LOG_ERROR(dhcp4_logger, DHCP4_CONFIG_LOAD_FAIL).arg(ex.what());
|
||||
|
||||
}
|
||||
|
||||
/// Integrate the asynchronous I/O model of BIND 10 configuration
|
||||
/// control with the "select" model of the DHCP server. This is
|
||||
/// fully explained in \ref dhcpv4Session.
|
||||
int ctrl_socket = cc_session_->getSocketDesc();
|
||||
LOG_DEBUG(dhcp4_logger, DBG_DHCP4_START, DHCP4_CCSESSION_STARTED)
|
||||
.arg(ctrl_socket);
|
||||
IfaceMgr::instance().addExternalSocket(ctrl_socket, sessionReader);
|
||||
}
|
||||
|
||||
void ControlledDhcpv4Srv::disconnectSession() {
|
||||
if (config_session_) {
|
||||
delete config_session_;
|
||||
config_session_ = NULL;
|
||||
}
|
||||
if (cc_session_) {
|
||||
|
||||
int ctrl_socket = cc_session_->getSocketDesc();
|
||||
cc_session_->disconnect();
|
||||
|
||||
IfaceMgr::instance().deleteExternalSocket(ctrl_socket);
|
||||
delete cc_session_;
|
||||
cc_session_ = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
ControlledDhcpv4Srv::ControlledDhcpv4Srv(uint16_t port /*= DHCP4_SERVER_PORT*/)
|
||||
:Dhcpv4Srv(port), cc_session_(NULL), config_session_(NULL) {
|
||||
server_ = this; // remember this instance for use in callback
|
||||
:Dhcpv4Srv(port) {
|
||||
if (getInstance()) {
|
||||
isc_throw(InvalidOperation,
|
||||
"There is another Dhcpv4Srv instance already.");
|
||||
}
|
||||
server_ = this; // remember this instance for later use in handlers
|
||||
}
|
||||
|
||||
void ControlledDhcpv4Srv::shutdown() {
|
||||
@@ -270,22 +172,19 @@ void ControlledDhcpv4Srv::shutdown() {
|
||||
}
|
||||
|
||||
ControlledDhcpv4Srv::~ControlledDhcpv4Srv() {
|
||||
disconnectSession();
|
||||
cleanup();
|
||||
|
||||
server_ = NULL; // forget this instance. There should be no callback anymore
|
||||
// at this stage anyway.
|
||||
server_ = NULL; // forget this instance. Noone should call any handlers at
|
||||
// this stage.
|
||||
}
|
||||
|
||||
isc::data::ConstElementPtr
|
||||
ControlledDhcpv4Srv::execDhcpv4ServerCommand(const std::string& command_id,
|
||||
isc::data::ConstElementPtr args) {
|
||||
try {
|
||||
return (dhcp4CommandHandler(command_id, args));
|
||||
} catch (const Exception& ex) {
|
||||
ConstElementPtr answer = isc::config::createAnswer(1, ex.what());
|
||||
return (answer);
|
||||
void ControlledDhcpv4Srv::sessionReader(void) {
|
||||
// Process one asio event. If there are more events, iface_mgr will call
|
||||
// this callback more than once.
|
||||
if (getInstance()) {
|
||||
getInstance()->io_service_.run_one();
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
};
|
||||
}; // end of isc::dhcp namespace
|
||||
}; // end of isc namespace
|
||||
|
@@ -1,4 +1,4 @@
|
||||
// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
|
||||
// Copyright (C) 2012-2014 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
|
||||
@@ -26,12 +26,10 @@ namespace dhcp {
|
||||
|
||||
/// @brief Controlled version of the DHCPv4 server
|
||||
///
|
||||
/// This is a class that is responsible for establishing connection
|
||||
/// with msqg (receving commands and configuration). This is an extended
|
||||
/// version of Dhcpv4Srv class that is purely a DHCPv4 server, without
|
||||
/// external control. ControlledDhcpv4Srv should be used in typical BIND10
|
||||
/// (i.e. featuring msgq) environment, while Dhcpv4Srv should be used in
|
||||
/// embedded environments.
|
||||
/// This is a class that is responsible for DHCPv4 server being controllable.
|
||||
/// It does various things, depending on the configuration backend.
|
||||
/// For Bundy backend it establishes a connection with msqg and later receives
|
||||
/// commands over it. For Kea backend, it reads configuration file from disk.
|
||||
///
|
||||
/// For detailed explanation or relations between main(), ControlledDhcpv4Srv,
|
||||
/// Dhcpv4Srv and other classes, see \ref dhcpv4Session.
|
||||
@@ -46,44 +44,57 @@ public:
|
||||
/// @brief Destructor.
|
||||
~ControlledDhcpv4Srv();
|
||||
|
||||
/// @brief Establishes msgq session.
|
||||
/// @brief Initializes the server.
|
||||
///
|
||||
/// Creates session that will be used to receive commands and updated
|
||||
/// configuration from cfgmgr (or indirectly from user via bindctl).
|
||||
void establishSession();
|
||||
/// Depending on the configuration backend, it establishes msgq session,
|
||||
/// reads the JSON file from disk or may perform any other setup
|
||||
/// operation. For specific details, see actual implementation in
|
||||
/// *_backend.cc
|
||||
///
|
||||
/// This method may throw if initialization fails. Exception types may be
|
||||
/// specific to used configuration backend.
|
||||
void init(const std::string& config_file);
|
||||
|
||||
/// @brief Terminates existing msgq session.
|
||||
/// @brief Performs cleanup, immediately before termination
|
||||
///
|
||||
/// This method terminates existing session with msgq. After calling
|
||||
/// This method performs final clean up, just before the Dhcpv4Srv object
|
||||
/// is destroyed. The actual behavior is backend dependent. For Bundy
|
||||
/// backend, it terminates existing session with msgq. After calling
|
||||
/// it, no further messages over msgq (commands or configuration updates)
|
||||
/// may be received.
|
||||
/// may be received. For JSON backend, it is no-op.
|
||||
///
|
||||
/// It is ok to call this method when session is disconnected already.
|
||||
void disconnectSession();
|
||||
/// For specific details, see actual implementation in *_backend.cc
|
||||
void cleanup();
|
||||
|
||||
/// @brief Initiates shutdown procedure for the whole DHCPv4 server.
|
||||
void shutdown();
|
||||
|
||||
/// @brief Session callback, processes received commands.
|
||||
/// @brief Command processor
|
||||
///
|
||||
/// This method is uniform for all config backends. It processes received
|
||||
/// command (as a string + JSON arguments). Internally, it's just a
|
||||
/// wrapper that calls process*Command() methods and catches exceptions
|
||||
/// in them.
|
||||
///
|
||||
/// Currently supported commands are:
|
||||
/// - shutdown
|
||||
/// - libreload
|
||||
/// - config-reload
|
||||
///
|
||||
/// @note It never throws.
|
||||
///
|
||||
/// @param command Text represenation of the command (e.g. "shutdown")
|
||||
/// @param args Optional parameters
|
||||
/// @param command text represenation of the command (e.g. "shutdown")
|
||||
/// @param args optional parameters
|
||||
///
|
||||
/// @return status of the command
|
||||
static isc::data::ConstElementPtr
|
||||
execDhcpv4ServerCommand(const std::string& command,
|
||||
isc::data::ConstElementPtr args);
|
||||
processCommand(const std::string& command, isc::data::ConstElementPtr args);
|
||||
|
||||
protected:
|
||||
/// @brief Static pointer to the sole instance of the DHCP server.
|
||||
/// @brief Configuration processor
|
||||
///
|
||||
/// This is required for config and command handlers to gain access to
|
||||
/// the server
|
||||
static ControlledDhcpv4Srv* server_;
|
||||
|
||||
/// @brief A callback for handling incoming configuration updates.
|
||||
/// This is a method for handling incoming configuration updates.
|
||||
/// This method should be called by all configuration backends when the
|
||||
/// server is starting up or when configuration has changed.
|
||||
///
|
||||
/// As pointer to this method is used a callback in ASIO used in
|
||||
/// ModuleCCSession, it has to be static.
|
||||
@@ -92,54 +103,72 @@ protected:
|
||||
///
|
||||
/// @return status of the config update
|
||||
static isc::data::ConstElementPtr
|
||||
dhcp4ConfigHandler(isc::data::ConstElementPtr new_config);
|
||||
processConfig(isc::data::ConstElementPtr new_config);
|
||||
|
||||
/// @brief A dummy configuration handler that always returns success.
|
||||
/// @brief Returns pointer to the sole instance of Dhcpv4Srv
|
||||
///
|
||||
/// This configuration handler does not perform configuration
|
||||
/// parsing and always returns success. A dummy handler should
|
||||
/// be installed using \ref isc::config::ModuleCCSession ctor
|
||||
/// to get the initial configuration. This initial configuration
|
||||
/// comprises values for only those elements that were modified
|
||||
/// the previous session. The \ref dhcp4ConfigHandler can't be
|
||||
/// used to parse the initial configuration because it needs the
|
||||
/// full configuration to satisfy dependencies between the
|
||||
/// various configuration values. Installing the dummy handler
|
||||
/// that guarantees to return success causes initial configuration
|
||||
/// to be stored for the session being created and that it can
|
||||
/// be later accessed with
|
||||
/// \ref isc::config::ConfigData::getFullConfig().
|
||||
///
|
||||
/// @param new_config new configuration.
|
||||
///
|
||||
/// @return success configuration status.
|
||||
static isc::data::ConstElementPtr
|
||||
dhcp4StubConfigHandler(isc::data::ConstElementPtr new_config);
|
||||
/// @return server instance (may return NULL, if called before server is spawned)
|
||||
static ControlledDhcpv4Srv* getInstance() {
|
||||
return (server_);
|
||||
}
|
||||
|
||||
/// @brief A callback for handling incoming commands.
|
||||
///
|
||||
/// @param command textual representation of the command
|
||||
/// @param args parameters of the command
|
||||
///
|
||||
/// @return status of the processed command
|
||||
static isc::data::ConstElementPtr
|
||||
dhcp4CommandHandler(const std::string& command, isc::data::ConstElementPtr args);
|
||||
|
||||
/// @brief Callback that will be called from iface_mgr when command/config arrives.
|
||||
protected:
|
||||
/// @brief Static pointer to the sole instance of the DHCP server.
|
||||
///
|
||||
/// This static callback method is called from IfaceMgr::receive4() method,
|
||||
/// when there is a new command or configuration sent over msgq.
|
||||
/// This is required for config and command handlers to gain access to
|
||||
/// the server
|
||||
static ControlledDhcpv4Srv* server_;
|
||||
|
||||
/// @brief Callback that will be called from iface_mgr when data
|
||||
/// is received over control socket.
|
||||
///
|
||||
/// This static callback method is called from IfaceMgr::receive6() method,
|
||||
/// when there is a new command or configuration sent over control socket
|
||||
/// (that was sent from msgq if backend is Bundy, or some yet unspecified
|
||||
/// sender if the backend is JSON file).
|
||||
static void sessionReader(void);
|
||||
|
||||
|
||||
/// @brief IOService object, used for all ASIO operations.
|
||||
isc::asiolink::IOService io_service_;
|
||||
|
||||
/// @brief Helper session object that represents raw connection to msgq.
|
||||
isc::cc::Session* cc_session_;
|
||||
/// @brief Handler for processing 'shutdown' command
|
||||
///
|
||||
/// This handler processes shutdown command, which initializes shutdown
|
||||
/// procedure.
|
||||
/// @param command (parameter ignored)
|
||||
/// @param args (parameter ignored)
|
||||
///
|
||||
/// @return status of the command
|
||||
isc::data::ConstElementPtr
|
||||
commandShutdownHandler(const std::string& command,
|
||||
isc::data::ConstElementPtr args);
|
||||
|
||||
/// @brief Session that receives configuration and commands
|
||||
isc::config::ModuleCCSession* config_session_;
|
||||
/// @brief Handler for processing 'libreload' command
|
||||
///
|
||||
/// This handler processes libreload command, which unloads all hook
|
||||
/// libraries and reloads them.
|
||||
///
|
||||
/// @param command (parameter ignored)
|
||||
/// @param args (parameter ignored)
|
||||
///
|
||||
/// @return status of the command
|
||||
isc::data::ConstElementPtr
|
||||
commandLibReloadHandler(const std::string& command,
|
||||
isc::data::ConstElementPtr args);
|
||||
|
||||
/// @brief Handler for processing 'config-reload' command
|
||||
///
|
||||
/// This handler processes config-reload command, which processes
|
||||
/// configuration specified in args parameter.
|
||||
///
|
||||
/// @param command (parameter ignored)
|
||||
/// @param args configuration to be processed
|
||||
///
|
||||
/// @return status of the command
|
||||
isc::data::ConstElementPtr
|
||||
commandConfigReloadHandler(const std::string& command,
|
||||
isc::data::ConstElementPtr args);
|
||||
};
|
||||
|
||||
}; // namespace isc::dhcp
|
||||
|
@@ -191,6 +191,46 @@ being passed in isc::dhcp::Dhcpv4Srv::selectSubnet() to isc::dhcp::CfgMgr::getSu
|
||||
Currently this capability is usable, but the number of scenarios it supports is
|
||||
limited.
|
||||
|
||||
@section dhcpv4ConfigBackend Configuration backend for DHCPv4
|
||||
|
||||
There are many theoretical ways in which server configuration can be stored. Kea 0.8 and
|
||||
earlier versions used BIND10 framework and its internal storage for DHCPv6 server configuration.
|
||||
The legacy ISC-DHCP implementation uses flat files. Configuration stored in JSON files is
|
||||
becoming more and more popular among various projects. There are unofficial patches for
|
||||
ISC-DHCP that keep parts of the configuration in LDAP. It was also suggested that in some
|
||||
cases it would be convenient to keep configuration in XML files.
|
||||
|
||||
Kea 0.9 introduces configuration backends that are switchable during compilation phase.
|
||||
There is a new parameter for configure script: --with-kea-config. It currently supports
|
||||
two values: BUNDY and JSON.
|
||||
|
||||
BUNDY (which is the default value as of May 2014) means that Kea4 is linked with the
|
||||
Bundy (former BIND10) configuration backend that connects to the BIND10 framework and in general works
|
||||
exactly the same as Kea 0.8 and earlier versions. The benefits of that backend are uniform
|
||||
integration with Bundy/BIND10 framework, easy on-line reconfiguration using bindctl, available
|
||||
RESTful API. On the other hand, it requires the whole heavy Bundy framework that requires
|
||||
Python3 to be present. That framework is going away with the release of Kea 0.9.
|
||||
|
||||
JSON is a new configuration backend that causes Kea to read JSON configuration file from
|
||||
disk. It does not require any framework and thus is considered more lightweight. It will
|
||||
allow dynamic on-line reconfiguration, but will lack remote capabilities (i.e. no RESTful
|
||||
API). This configuration backend is expected to be the default for upcoming Kea 0.9. It
|
||||
requires <tt> -c config-file </tt> command-line option.
|
||||
|
||||
Internally, configuration backends are implemented as different implementations of the
|
||||
isc::dhcp::ControlledDhcpv4Srv class, stored in {kea,bundy}_controller.cc files. Depending on
|
||||
the choice made by ./configure script, only one of those files is compiled and linked.
|
||||
There are backend specific tests in src/bin/dhcp4/tests/{kea,bundy}_controller_unittest.cc.
|
||||
Only tests specific to selected backend are linked and executed during make distcheck.
|
||||
|
||||
While it is unlikely that ISC will support more than one backend at any given time, there
|
||||
are several aspects that make that approach appealing in the long term. First, having
|
||||
two backends is essential during transition time, where both old and new backend is used.
|
||||
Second, there are external organizations that develop and presumably maintain LDAP backend
|
||||
for ISC-DHCP. Is at least possible that similar will happen for Kea. Finally, if we ever
|
||||
extend the isc::dhcp::CfgMgr with configuration export, this approach could be used as
|
||||
a migration tool.
|
||||
|
||||
@section dhcpv4Other Other DHCPv4 topics
|
||||
|
||||
For hooks API support in DHCPv4, see @ref dhcpv4Hooks.
|
||||
|
@@ -44,6 +44,10 @@ name was malformed or due to internal server error.
|
||||
A debug message listing the command (and possible arguments) received
|
||||
from the BIND 10 control system by the DHCPv4 server.
|
||||
|
||||
% DHCP4_CONFIG_RECEIVED received configuration %1
|
||||
A debug message listing the configuration received by the DHCPv4 server.
|
||||
The source of that configuration depends on used configuration backend.
|
||||
|
||||
% DHCP4_CONFIG_COMPLETE DHCPv4 server has completed configuration: %1
|
||||
This is an informational message announcing the successful processing of a
|
||||
new configuration. It is output during server startup, and when an updated
|
||||
@@ -339,17 +343,11 @@ core component within the DHCPv4 server (the Dhcpv4 server object)
|
||||
has failed. As a result, the server will exit. The reason for the
|
||||
failure is given within the message.
|
||||
|
||||
% DHCP4_STANDALONE skipping message queue, running standalone
|
||||
This is a debug message indicating that the DHCPv4 server is running in
|
||||
standalone mode, not connected to the message queue. Standalone mode
|
||||
is only useful during program development, and should not be used in a
|
||||
production environment.
|
||||
|
||||
% DHCP4_STARTING server starting
|
||||
This informational message indicates that the DHCPv4 server has
|
||||
processed any command-line switches and is starting.
|
||||
|
||||
% DHCP4_START_INFO pid: %1, port: %2, verbose: %3, standalone: %4
|
||||
% DHCP4_START_INFO pid: %1, port: %2, verbose: %3
|
||||
This is a debug message issued during the DHCPv4 server startup.
|
||||
It lists some information about the parameters with which the server
|
||||
is running.
|
||||
|
@@ -1,4 +1,4 @@
|
||||
// Copyright (C) 2011-2013 Internet Systems Consortium, Inc. ("ISC")
|
||||
// Copyright (C) 2011-2014 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
|
||||
@@ -26,6 +26,7 @@
|
||||
#include <dhcpsrv/subnet.h>
|
||||
#include <dhcpsrv/alloc_engine.h>
|
||||
#include <hooks/callout_handle.h>
|
||||
#include <dhcpsrv/daemon.h>
|
||||
|
||||
#include <boost/noncopyable.hpp>
|
||||
|
||||
@@ -57,7 +58,7 @@ public:
|
||||
///
|
||||
/// For detailed explanation or relations between main(), ControlledDhcpv4Srv,
|
||||
/// Dhcpv4Srv and other classes, see \ref dhcpv4Session.
|
||||
class Dhcpv4Srv : public boost::noncopyable {
|
||||
class Dhcpv4Srv : public Daemon {
|
||||
|
||||
public:
|
||||
|
||||
|
@@ -17,7 +17,7 @@
|
||||
#include <dhcp/libdhcp++.h>
|
||||
#include <dhcp/option_definition.h>
|
||||
#include <dhcpsrv/cfgmgr.h>
|
||||
#include <dhcp4/config_parser.h>
|
||||
#include <dhcp4/json_config_parser.h>
|
||||
#include <dhcpsrv/dbaccess_parser.h>
|
||||
#include <dhcpsrv/dhcp_parsers.h>
|
||||
#include <dhcpsrv/option_space_container.h>
|
124
src/bin/dhcp4/kea_controller.cc
Normal file
124
src/bin/dhcp4/kea_controller.cc
Normal file
@@ -0,0 +1,124 @@
|
||||
// Copyright (C) 2014 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 <config.h>
|
||||
|
||||
#include <dhcp4/json_config_parser.h>
|
||||
#include <dhcp4/ctrl_dhcp4_srv.h>
|
||||
#include <dhcp4/dhcp4_log.h>
|
||||
#include <exceptions/exceptions.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
using namespace isc::asiolink;
|
||||
using namespace isc::dhcp;
|
||||
using namespace std;
|
||||
|
||||
namespace isc {
|
||||
namespace dhcp {
|
||||
|
||||
void
|
||||
ControlledDhcpv4Srv::init(const std::string& file_name) {
|
||||
// This is a configuration backend implementation that reads the
|
||||
// configuration from a JSON file.
|
||||
|
||||
isc::data::ConstElementPtr json;
|
||||
isc::data::ConstElementPtr dhcp4;
|
||||
isc::data::ConstElementPtr result;
|
||||
|
||||
// Basic sanity check: file name must not be empty.
|
||||
try {
|
||||
if (file_name.empty()) {
|
||||
// Basic sanity check: file name must not be empty.
|
||||
isc_throw(BadValue, "JSON configuration file not specified. Please "
|
||||
"use -c command line option.");
|
||||
}
|
||||
|
||||
// Read contents of the file and parse it as JSON
|
||||
json = isc::data::Element::fromJSONFile(file_name, true);
|
||||
|
||||
if (!json) {
|
||||
LOG_ERROR(dhcp4_logger, DHCP4_CONFIG_LOAD_FAIL)
|
||||
.arg("Config file " + file_name + " missing or empty.");
|
||||
isc_throw(BadValue, "Unable to process JSON configuration file:"
|
||||
+ file_name);
|
||||
}
|
||||
|
||||
// Get Dhcp4 component from the config
|
||||
dhcp4 = json->get("Dhcp4");
|
||||
|
||||
if (!dhcp4) {
|
||||
LOG_ERROR(dhcp4_logger, DHCP4_CONFIG_LOAD_FAIL)
|
||||
.arg("Config file " + file_name + " does not include 'Dhcp4' entry.");
|
||||
isc_throw(BadValue, "Unable to process JSON configuration file:"
|
||||
+ file_name);
|
||||
}
|
||||
|
||||
// Use parsed JSON structures to configure the server
|
||||
result = processCommand("config-reload", dhcp4);
|
||||
|
||||
} catch (const std::exception& ex) {
|
||||
LOG_ERROR(dhcp4_logger, DHCP4_CONFIG_LOAD_FAIL).arg(ex.what());
|
||||
isc_throw(BadValue, "Unable to process JSON configuration file:"
|
||||
+ file_name);
|
||||
}
|
||||
|
||||
if (!result) {
|
||||
// Undetermined status of the configuration. This should never happen,
|
||||
// but as the configureDhcp4Server returns a pointer, it is theoretically
|
||||
// possible that it will return NULL.
|
||||
LOG_ERROR(dhcp4_logger, DHCP4_CONFIG_LOAD_FAIL)
|
||||
.arg("Configuration failed: Undefined result of processCommand("
|
||||
"config-reload, " + file_name + ")");
|
||||
isc_throw(BadValue, "Configuration failed: Undefined result of "
|
||||
"processCommand('config-reload', " + file_name + ")");
|
||||
}
|
||||
|
||||
// Now check is the returned result is successful (rcode=0) or not
|
||||
isc::data::ConstElementPtr comment; /// see @ref isc::config::parseAnswer
|
||||
int rcode;
|
||||
comment = isc::config::parseAnswer(rcode, result);
|
||||
if (rcode != 0) {
|
||||
string reason = "";
|
||||
if (comment) {
|
||||
reason = string(" (") + comment->stringValue() + string(")");
|
||||
}
|
||||
LOG_ERROR(dhcp4_logger, DHCP4_CONFIG_LOAD_FAIL).arg(reason);
|
||||
isc_throw(BadValue, "Failed to apply configuration:" << reason);
|
||||
}
|
||||
|
||||
// We don't need to call openActiveSockets() or startD2() as these
|
||||
// methods are called in processConfig() which is called by
|
||||
// processCommand("reload-config", ...)
|
||||
}
|
||||
|
||||
void ControlledDhcpv4Srv::cleanup() {
|
||||
// Nothing to do here. No need to disconnect from anything.
|
||||
}
|
||||
|
||||
/// This is a logger initialization for JSON file backend.
|
||||
/// For now, it's just setting log messages to be printed on stdout.
|
||||
/// @todo: Implement this properly (see #3427)
|
||||
void Daemon::loggerInit(const char*, bool verbose) {
|
||||
|
||||
setenv("B10_LOCKFILE_DIR_FROM_BUILD", "/tmp", 1);
|
||||
setenv("B10_LOGGER_ROOT", "kea", 0);
|
||||
setenv("B10_LOGGER_SEVERITY", (verbose ? "DEBUG":"INFO"), 0);
|
||||
setenv("B10_LOGGER_DBGLEVEL", "99", 0);
|
||||
setenv("B10_LOGGER_DESTINATION", "stdout", 0);
|
||||
isc::log::initLogger();
|
||||
}
|
||||
|
||||
};
|
||||
};
|
@@ -39,13 +39,15 @@ namespace {
|
||||
|
||||
const char* const DHCP4_NAME = "b10-dhcp4";
|
||||
|
||||
const char* const DHCP4_LOGGER_NAME = "kea";
|
||||
|
||||
void
|
||||
usage() {
|
||||
cerr << "Usage: " << DHCP4_NAME << " [-v] [-s] [-p number]" << endl;
|
||||
cerr << "Usage: " << DHCP4_NAME << " [-v] [-p number] [-c file]" << endl;
|
||||
cerr << " -v: verbose output" << endl;
|
||||
cerr << " -s: stand-alone mode (don't connect to BIND10)" << endl;
|
||||
cerr << " -p number: specify non-standard port number 1-65535 "
|
||||
<< "(useful for testing only)" << endl;
|
||||
cerr << " -c file: specify configuration file" << endl;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
} // end of anonymous namespace
|
||||
@@ -55,19 +57,17 @@ main(int argc, char* argv[]) {
|
||||
int ch;
|
||||
int port_number = DHCP4_SERVER_PORT; // The default. any other values are
|
||||
// useful for testing only.
|
||||
bool stand_alone = false; // Should be connect to BIND10 msgq?
|
||||
bool verbose_mode = false; // Should server be verbose?
|
||||
|
||||
while ((ch = getopt(argc, argv, "vsp:")) != -1) {
|
||||
// The standard config file
|
||||
std::string config_file("");
|
||||
|
||||
while ((ch = getopt(argc, argv, "vp:c:")) != -1) {
|
||||
switch (ch) {
|
||||
case 'v':
|
||||
verbose_mode = true;
|
||||
break;
|
||||
|
||||
case 's':
|
||||
stand_alone = true;
|
||||
break;
|
||||
|
||||
case 'p':
|
||||
try {
|
||||
port_number = boost::lexical_cast<int>(optarg);
|
||||
@@ -83,6 +83,10 @@ main(int argc, char* argv[]) {
|
||||
}
|
||||
break;
|
||||
|
||||
case 'c': // config file
|
||||
config_file = optarg;
|
||||
break;
|
||||
|
||||
default:
|
||||
usage();
|
||||
}
|
||||
@@ -93,36 +97,39 @@ main(int argc, char* argv[]) {
|
||||
usage();
|
||||
}
|
||||
|
||||
int ret = EXIT_SUCCESS;
|
||||
|
||||
try {
|
||||
// Initialize logging. If verbose, we'll use maximum verbosity.
|
||||
// If standalone is enabled, do not buffer initial log messages
|
||||
isc::log::initLogger(DHCP4_NAME,
|
||||
(verbose_mode ? isc::log::DEBUG : isc::log::INFO),
|
||||
isc::log::MAX_DEBUG_LEVEL, NULL, !stand_alone);
|
||||
LOG_INFO(dhcp4_logger, DHCP4_STARTING);
|
||||
Daemon::loggerInit(DHCP4_LOGGER_NAME, verbose_mode);
|
||||
LOG_DEBUG(dhcp4_logger, DBG_DHCP4_START, DHCP4_START_INFO)
|
||||
.arg(getpid()).arg(port_number).arg(verbose_mode ? "yes" : "no")
|
||||
.arg(stand_alone ? "yes" : "no" );
|
||||
.arg(getpid()).arg(port_number).arg(verbose_mode ? "yes" : "no");
|
||||
|
||||
LOG_INFO(dhcp4_logger, DHCP4_STARTING);
|
||||
|
||||
int ret = EXIT_SUCCESS;
|
||||
try {
|
||||
// Create the server instance.
|
||||
ControlledDhcpv4Srv server(port_number);
|
||||
if (!stand_alone) {
|
||||
|
||||
try {
|
||||
server.establishSession();
|
||||
// Initialize the server.
|
||||
server.init(config_file);
|
||||
} catch (const std::exception& ex) {
|
||||
LOG_ERROR(dhcp4_logger, DHCP4_SESSION_FAIL).arg(ex.what());
|
||||
// Let's continue. It is useful to have the ability to run
|
||||
// DHCP server in stand-alone mode, e.g. for testing
|
||||
// We do need to make sure logging is no longer buffered
|
||||
// since then it would not print until dhcp6 is stopped
|
||||
|
||||
// We should not continue if were told to configure (either read
|
||||
// config file or establish Bundy control session).
|
||||
|
||||
isc::log::LoggerManager log_manager;
|
||||
log_manager.process();
|
||||
|
||||
cerr << "Failed to initialize server: " << ex.what() << endl;
|
||||
return (EXIT_FAILURE);
|
||||
}
|
||||
} else {
|
||||
LOG_DEBUG(dhcp4_logger, DBG_DHCP4_START, DHCP4_STANDALONE);
|
||||
}
|
||||
|
||||
// And run the main loop of the server.
|
||||
server.run();
|
||||
|
||||
LOG_INFO(dhcp4_logger, DHCP4_SHUTDOWN);
|
||||
|
||||
} catch (const std::exception& ex) {
|
||||
|
@@ -33,6 +33,7 @@ AM_CPPFLAGS += -DINSTALL_PROG=\"$(abs_top_srcdir)/install-sh\"
|
||||
|
||||
CLEANFILES = $(builddir)/interfaces.txt $(builddir)/logger_lockfile
|
||||
CLEANFILES += $(builddir)/load_marker.txt $(builddir)/unload_marker.txt
|
||||
CLEANFILES += *.json
|
||||
|
||||
AM_CXXFLAGS = $(B10_CXXFLAGS)
|
||||
if USE_CLANGPP
|
||||
@@ -77,7 +78,7 @@ TESTS += dhcp4_unittests
|
||||
|
||||
dhcp4_unittests_SOURCES = ../dhcp4_srv.h ../dhcp4_srv.cc ../ctrl_dhcp4_srv.cc
|
||||
dhcp4_unittests_SOURCES += ../dhcp4_log.h ../dhcp4_log.cc
|
||||
dhcp4_unittests_SOURCES += ../config_parser.cc ../config_parser.h
|
||||
dhcp4_unittests_SOURCES += ../json_config_parser.cc ../json_config_parser.h
|
||||
dhcp4_unittests_SOURCES += d2_unittest.h d2_unittest.cc
|
||||
dhcp4_unittests_SOURCES += dhcp4_test_utils.h
|
||||
dhcp4_unittests_SOURCES += dhcp4_unittests.cc
|
||||
@@ -89,6 +90,19 @@ dhcp4_unittests_SOURCES += ctrl_dhcp4_srv_unittest.cc
|
||||
dhcp4_unittests_SOURCES += config_parser_unittest.cc
|
||||
dhcp4_unittests_SOURCES += fqdn_unittest.cc
|
||||
dhcp4_unittests_SOURCES += marker_file.cc
|
||||
|
||||
if CONFIG_BACKEND_BUNDY
|
||||
# For Bundy backend, we only need to run the usual tests. There are no
|
||||
# Bundy-specific tests yet.
|
||||
dhcp4_unittests_SOURCES += ../bundy_controller.cc
|
||||
dhcp4_unittests_SOURCES += bundy_controller_unittest.cc
|
||||
endif
|
||||
|
||||
if CONFIG_BACKEND_JSON
|
||||
dhcp4_unittests_SOURCES += ../kea_controller.cc
|
||||
dhcp4_unittests_SOURCES += kea_controller_unittest.cc
|
||||
endif
|
||||
|
||||
nodist_dhcp4_unittests_SOURCES = ../dhcp4_messages.h ../dhcp4_messages.cc
|
||||
nodist_dhcp4_unittests_SOURCES += marker_file.h test_libraries.h
|
||||
|
||||
@@ -109,4 +123,6 @@ dhcp4_unittests_LDADD += $(top_builddir)/src/lib/hooks/libkea-hooks.la
|
||||
dhcp4_unittests_LDADD += $(top_builddir)/src/lib/dhcpsrv/testutils/libdhcpsrvtest.la
|
||||
endif
|
||||
|
||||
noinst_EXTRA_DIST = configs-list.txt
|
||||
|
||||
noinst_PROGRAMS = $(TESTS)
|
||||
|
30
src/bin/dhcp4/tests/bundy_controller_unittest.cc
Normal file
30
src/bin/dhcp4/tests/bundy_controller_unittest.cc
Normal file
@@ -0,0 +1,30 @@
|
||||
// Copyright (C) 2014 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 <config.h>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
namespace {
|
||||
|
||||
/// As of May 2014, maintaining or extending Bundy support is very low
|
||||
/// prority for Kea team. We are looking for contributors, who would
|
||||
/// like to maintain this backend.
|
||||
|
||||
// Bundy framework specific tests should be added here.
|
||||
TEST(BundyBackendTest, dummy) {
|
||||
|
||||
}
|
||||
|
||||
} // End of anonymous namespace
|
@@ -19,7 +19,7 @@
|
||||
|
||||
#include <config/ccsession.h>
|
||||
#include <dhcp4/dhcp4_srv.h>
|
||||
#include <dhcp4/config_parser.h>
|
||||
#include <dhcp4/json_config_parser.h>
|
||||
#include <dhcp/option4_addrlst.h>
|
||||
#include <dhcp/option_custom.h>
|
||||
#include <dhcp/option_int.h>
|
||||
|
5
src/bin/dhcp4/tests/configs-list.txt
Normal file
5
src/bin/dhcp4/tests/configs-list.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
# This is a list of config files that the unit-tests (specifically
|
||||
# JSONFileBackendTest.loadAllConfigs) is going to load.
|
||||
|
||||
../../../../doc/examples/kea4/single-subnet.json
|
||||
../../../../doc/examples/kea4/several-subnets.json
|
@@ -85,12 +85,12 @@ TEST_F(CtrlDhcpv4SrvTest, commands) {
|
||||
int rcode = -1;
|
||||
|
||||
// Case 1: send bogus command
|
||||
ConstElementPtr result = ControlledDhcpv4Srv::execDhcpv4ServerCommand("blah", params);
|
||||
ConstElementPtr result = ControlledDhcpv4Srv::processCommand("blah", params);
|
||||
ConstElementPtr comment = parseAnswer(rcode, result);
|
||||
EXPECT_EQ(1, rcode); // expect failure (no such command as blah)
|
||||
|
||||
// Case 2: send shutdown command without any parameters
|
||||
result = ControlledDhcpv4Srv::execDhcpv4ServerCommand("shutdown", params);
|
||||
result = ControlledDhcpv4Srv::processCommand("shutdown", params);
|
||||
comment = parseAnswer(rcode, result);
|
||||
EXPECT_EQ(0, rcode); // expect success
|
||||
|
||||
@@ -99,7 +99,7 @@ TEST_F(CtrlDhcpv4SrvTest, commands) {
|
||||
params->set("pid", x);
|
||||
|
||||
// Case 3: send shutdown command with 1 parameter: pid
|
||||
result = ControlledDhcpv4Srv::execDhcpv4ServerCommand("shutdown", params);
|
||||
result = ControlledDhcpv4Srv::processCommand("shutdown", params);
|
||||
comment = parseAnswer(rcode, result);
|
||||
EXPECT_EQ(0, rcode); // expect success
|
||||
}
|
||||
@@ -107,6 +107,14 @@ TEST_F(CtrlDhcpv4SrvTest, commands) {
|
||||
// Check that the "libreload" command will reload libraries
|
||||
|
||||
TEST_F(CtrlDhcpv4SrvTest, libreload) {
|
||||
|
||||
// Sending commands for processing now requires a server that can process
|
||||
// them.
|
||||
boost::scoped_ptr<ControlledDhcpv4Srv> srv;
|
||||
ASSERT_NO_THROW(
|
||||
srv.reset(new ControlledDhcpv4Srv(0))
|
||||
);
|
||||
|
||||
// Ensure no marker files to start with.
|
||||
ASSERT_FALSE(checkMarkerFileExists(LOAD_MARKER_FILE));
|
||||
ASSERT_FALSE(checkMarkerFileExists(UNLOAD_MARKER_FILE));
|
||||
@@ -137,7 +145,7 @@ TEST_F(CtrlDhcpv4SrvTest, libreload) {
|
||||
int rcode = -1;
|
||||
|
||||
ConstElementPtr result =
|
||||
ControlledDhcpv4Srv::execDhcpv4ServerCommand("libreload", params);
|
||||
ControlledDhcpv4Srv::processCommand("libreload", params);
|
||||
ConstElementPtr comment = parseAnswer(rcode, result);
|
||||
EXPECT_EQ(0, rcode); // Expect success
|
||||
|
||||
|
@@ -13,7 +13,7 @@
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
#include <dhcp/iface_mgr.h>
|
||||
#include <dhcp4/config_parser.h>
|
||||
#include <dhcp4/json_config_parser.h>
|
||||
#include <dhcp4/tests/d2_unittest.h>
|
||||
#include <dhcpsrv/cfgmgr.h>
|
||||
|
||||
|
@@ -32,7 +32,7 @@
|
||||
#include <dhcp/tests/iface_mgr_test_config.h>
|
||||
#include <dhcp4/dhcp4_srv.h>
|
||||
#include <dhcp4/dhcp4_log.h>
|
||||
#include <dhcp4/config_parser.h>
|
||||
#include <dhcp4/json_config_parser.h>
|
||||
#include <hooks/server_hooks.h>
|
||||
#include <dhcpsrv/cfgmgr.h>
|
||||
#include <dhcpsrv/lease_mgr.h>
|
||||
|
@@ -13,7 +13,7 @@
|
||||
# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
|
||||
# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
from init import ProcessInfo, parse_args, dump_pid, unlink_pid_file, _BASETIME
|
||||
from init import ProcessInfo
|
||||
|
||||
import unittest
|
||||
import sys
|
||||
@@ -207,22 +207,5 @@ class TestDhcpv4Daemon(unittest.TestCase):
|
||||
# Check that there is an error message about invalid port number printed on stderr
|
||||
self.assertEqual( str(error).count("Failed to parse port number"), 1)
|
||||
|
||||
def test_portnumber_nonroot(self):
|
||||
print("Check that specifying unprivileged port number will work.")
|
||||
|
||||
# Check that there is a message about running with an unprivileged port
|
||||
(returncode, output, error) = self.runCommand(['../b10-dhcp4', '-v', '-s', '-p', '10057'])
|
||||
output_text = str(output) + str(error)
|
||||
self.assertEqual(output_text.count("DHCP4_OPEN_SOCKET opening sockets on port 10057"), 1)
|
||||
|
||||
def test_skip_msgq(self):
|
||||
print("Check that connection to BIND10 msgq can be disabled.")
|
||||
|
||||
# Check that the system outputs a message on one of its streams about running
|
||||
# standalone.
|
||||
(returncode, output, error) = self.runCommand(['../b10-dhcp4', '-v', '-s', '-p', '10057'])
|
||||
output_text = str(output) + str(error)
|
||||
self.assertEqual(output_text.count("DHCP4_STANDALONE"), 1)
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
@@ -17,7 +17,7 @@
|
||||
#include <asiolink/io_address.h>
|
||||
#include <cc/data.h>
|
||||
#include <config/ccsession.h>
|
||||
#include <dhcp4/config_parser.h>
|
||||
#include <dhcp4/json_config_parser.h>
|
||||
#include <dhcp4/tests/dhcp4_test_utils.h>
|
||||
#include <dhcp/option4_addrlst.h>
|
||||
#include <dhcp/option_int.h>
|
||||
|
@@ -19,7 +19,7 @@
|
||||
#include <dhcpsrv/cfgmgr.h>
|
||||
#include <dhcpsrv/lease_mgr_factory.h>
|
||||
#include <dhcpsrv/subnet.h>
|
||||
#include <dhcp4/config_parser.h>
|
||||
#include <dhcp4/json_config_parser.h>
|
||||
#include <dhcp4/tests/dhcp4_test_utils.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <string>
|
||||
|
296
src/bin/dhcp4/tests/kea_controller_unittest.cc
Normal file
296
src/bin/dhcp4/tests/kea_controller_unittest.cc
Normal file
@@ -0,0 +1,296 @@
|
||||
// Copyright (C) 2014 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 <config.h>
|
||||
|
||||
#include <config/ccsession.h>
|
||||
#include <dhcp/dhcp4.h>
|
||||
#include <dhcp4/ctrl_dhcp4_srv.h>
|
||||
#include <dhcpsrv/cfgmgr.h>
|
||||
|
||||
#include <boost/scoped_ptr.hpp>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <unistd.h>
|
||||
|
||||
using namespace std;
|
||||
using namespace isc;
|
||||
using namespace isc::asiolink;
|
||||
using namespace isc::config;
|
||||
using namespace isc::data;
|
||||
using namespace isc::dhcp;
|
||||
using namespace isc::hooks;
|
||||
|
||||
namespace {
|
||||
|
||||
class NakedControlledDhcpv4Srv: public ControlledDhcpv4Srv {
|
||||
// "Naked" DHCPv4 server, exposes internal fields
|
||||
public:
|
||||
NakedControlledDhcpv4Srv():ControlledDhcpv4Srv(0) { }
|
||||
};
|
||||
|
||||
/// @brief test class for Kea configuration backend
|
||||
///
|
||||
/// This class is used for testing Kea configuration backend.
|
||||
/// It is very simple and currently focuses on reading
|
||||
/// config file from disk. It is expected to be expanded in the
|
||||
/// near future.
|
||||
class JSONFileBackendTest : public ::testing::Test {
|
||||
public:
|
||||
JSONFileBackendTest() {
|
||||
}
|
||||
|
||||
~JSONFileBackendTest() {
|
||||
static_cast<void>(unlink(TEST_FILE));
|
||||
};
|
||||
|
||||
/// @brief writes specified content to a well known file
|
||||
///
|
||||
/// Writes specified content to TEST_FILE. Tests will
|
||||
/// attempt to read that file.
|
||||
///
|
||||
/// @param content content to be written to file
|
||||
void writeFile(const std::string& content) {
|
||||
static_cast<void>(unlink(TEST_FILE));
|
||||
|
||||
ofstream out(TEST_FILE, ios::trunc);
|
||||
EXPECT_TRUE(out.is_open());
|
||||
out << content;
|
||||
out.close();
|
||||
}
|
||||
|
||||
/// Name of a config file used during tests
|
||||
static const char* TEST_FILE;
|
||||
};
|
||||
|
||||
const char* JSONFileBackendTest::TEST_FILE = "test-config.json";
|
||||
|
||||
// This test checks if configuration can be read from a JSON file.
|
||||
TEST_F(JSONFileBackendTest, jsonFile) {
|
||||
|
||||
// Prepare configuration file.
|
||||
string config = "{ \"Dhcp4\": { \"interfaces\": [ \"*\" ],"
|
||||
"\"rebind-timer\": 2000, "
|
||||
"\"renew-timer\": 1000, "
|
||||
"\"subnet4\": [ { "
|
||||
" \"pool\": [ \"192.0.2.1 - 192.0.2.100\" ],"
|
||||
" \"subnet\": \"192.0.2.0/24\" "
|
||||
" },"
|
||||
" {"
|
||||
" \"pool\": [ \"192.0.3.101 - 192.0.3.150\" ],"
|
||||
" \"subnet\": \"192.0.3.0/24\", "
|
||||
" \"id\": 0 "
|
||||
" },"
|
||||
" {"
|
||||
" \"pool\": [ \"192.0.4.101 - 192.0.4.150\" ],"
|
||||
" \"subnet\": \"192.0.4.0/24\" "
|
||||
" } ],"
|
||||
"\"valid-lifetime\": 4000 }"
|
||||
"}";
|
||||
|
||||
writeFile(config);
|
||||
|
||||
// Now initialize the server
|
||||
boost::scoped_ptr<ControlledDhcpv4Srv> srv;
|
||||
ASSERT_NO_THROW(
|
||||
srv.reset(new ControlledDhcpv4Srv(0))
|
||||
);
|
||||
|
||||
// And configure it using the config file.
|
||||
EXPECT_NO_THROW(srv->init(TEST_FILE));
|
||||
|
||||
// Now check if the configuration has been applied correctly.
|
||||
const Subnet4Collection* subnets = CfgMgr::instance().getSubnets4();
|
||||
ASSERT_TRUE(subnets);
|
||||
ASSERT_EQ(3, subnets->size()); // We expect 3 subnets.
|
||||
|
||||
|
||||
// Check subnet 1.
|
||||
EXPECT_EQ("192.0.2.0", subnets->at(0)->get().first.toText());
|
||||
EXPECT_EQ(24, subnets->at(0)->get().second);
|
||||
|
||||
// Check pools in the first subnet.
|
||||
const PoolCollection& pools1 = subnets->at(0)->getPools(Lease::TYPE_V4);
|
||||
ASSERT_EQ(1, pools1.size());
|
||||
EXPECT_EQ("192.0.2.1", pools1.at(0)->getFirstAddress().toText());
|
||||
EXPECT_EQ("192.0.2.100", pools1.at(0)->getLastAddress().toText());
|
||||
EXPECT_EQ(Lease::TYPE_V4, pools1.at(0)->getType());
|
||||
|
||||
// Check subnet 2.
|
||||
EXPECT_EQ("192.0.3.0", subnets->at(1)->get().first.toText());
|
||||
EXPECT_EQ(24, subnets->at(1)->get().second);
|
||||
|
||||
// Check pools in the second subnet.
|
||||
const PoolCollection& pools2 = subnets->at(1)->getPools(Lease::TYPE_V4);
|
||||
ASSERT_EQ(1, pools2.size());
|
||||
EXPECT_EQ("192.0.3.101", pools2.at(0)->getFirstAddress().toText());
|
||||
EXPECT_EQ("192.0.3.150", pools2.at(0)->getLastAddress().toText());
|
||||
EXPECT_EQ(Lease::TYPE_V4, pools2.at(0)->getType());
|
||||
|
||||
// And finally check subnet 3.
|
||||
EXPECT_EQ("192.0.4.0", subnets->at(2)->get().first.toText());
|
||||
EXPECT_EQ(24, subnets->at(2)->get().second);
|
||||
|
||||
// ... and it's only pool.
|
||||
const PoolCollection& pools3 = subnets->at(2)->getPools(Lease::TYPE_V4);
|
||||
EXPECT_EQ("192.0.4.101", pools3.at(0)->getFirstAddress().toText());
|
||||
EXPECT_EQ("192.0.4.150", pools3.at(0)->getLastAddress().toText());
|
||||
EXPECT_EQ(Lease::TYPE_V4, pools3.at(0)->getType());
|
||||
}
|
||||
|
||||
// This test checks if configuration can be read from a JSON file.
|
||||
TEST_F(JSONFileBackendTest, comments) {
|
||||
|
||||
string config_hash_comments = "# This is a comment. It should be \n"
|
||||
"#ignored. Real config starts in line below\n"
|
||||
"{ \"Dhcp4\": { \"interfaces\": [ \"*\" ],"
|
||||
"\"rebind-timer\": 2000, "
|
||||
"\"renew-timer\": 1000, \n"
|
||||
"# comments in the middle should be ignored, too\n"
|
||||
"\"subnet4\": [ { "
|
||||
" \"pool\": [ \"192.0.2.0/24\" ],"
|
||||
" \"subnet\": \"192.0.2.0/22\" "
|
||||
" } ],"
|
||||
"\"valid-lifetime\": 4000 }"
|
||||
"}";
|
||||
|
||||
/// @todo: Implement C++-style (// ...) comments
|
||||
/// @todo: Implement C-style (/* ... */) comments
|
||||
|
||||
writeFile(config_hash_comments);
|
||||
|
||||
// Now initialize the server
|
||||
boost::scoped_ptr<ControlledDhcpv4Srv> srv;
|
||||
ASSERT_NO_THROW(
|
||||
srv.reset(new ControlledDhcpv4Srv(0))
|
||||
);
|
||||
|
||||
// And configure it using config with comments.
|
||||
EXPECT_NO_THROW(srv->init(TEST_FILE));
|
||||
|
||||
// Now check if the configuration has been applied correctly.
|
||||
const Subnet4Collection* subnets = CfgMgr::instance().getSubnets4();
|
||||
ASSERT_TRUE(subnets);
|
||||
ASSERT_EQ(1, subnets->size());
|
||||
|
||||
// Check subnet 1.
|
||||
EXPECT_EQ("192.0.2.0", subnets->at(0)->get().first.toText());
|
||||
EXPECT_EQ(22, subnets->at(0)->get().second);
|
||||
|
||||
// Check pools in the first subnet.
|
||||
const PoolCollection& pools1 = subnets->at(0)->getPools(Lease::TYPE_V4);
|
||||
ASSERT_EQ(1, pools1.size());
|
||||
EXPECT_EQ("192.0.2.0", pools1.at(0)->getFirstAddress().toText());
|
||||
EXPECT_EQ("192.0.2.255", pools1.at(0)->getLastAddress().toText());
|
||||
EXPECT_EQ(Lease::TYPE_V4, pools1.at(0)->getType());
|
||||
}
|
||||
|
||||
// This test checks if configuration detects failure when trying:
|
||||
// - empty file
|
||||
// - empty filename
|
||||
// - no Dhcp4 element
|
||||
// - Config file that contains Dhcp4 but has a content error
|
||||
TEST_F(JSONFileBackendTest, configBroken) {
|
||||
|
||||
// Empty config is not allowed, because Dhcp4 element is missing
|
||||
string config_empty = "";
|
||||
|
||||
// This config does not have mandatory Dhcp4 element
|
||||
string config_v4 = "{ \"Dhcp6\": { \"interfaces\": [ \"*\" ],"
|
||||
"\"preferred-lifetime\": 3000,"
|
||||
"\"rebind-timer\": 2000, "
|
||||
"\"renew-timer\": 1000, "
|
||||
"\"subnet4\": [ { "
|
||||
" \"pool\": [ \"2001:db8::/80\" ],"
|
||||
" \"subnet\": \"2001:db8::/64\" "
|
||||
" } ]}";
|
||||
|
||||
// This has Dhcp4 element, but it's utter nonsense
|
||||
string config_nonsense = "{ \"Dhcp4\": { \"reviews\": \"are so much fun\" } }";
|
||||
|
||||
// Now initialize the server
|
||||
boost::scoped_ptr<ControlledDhcpv4Srv> srv;
|
||||
ASSERT_NO_THROW(
|
||||
srv.reset(new ControlledDhcpv4Srv(0))
|
||||
);
|
||||
|
||||
// Try to configure without filename. Should fail.
|
||||
EXPECT_THROW(srv->init(""), BadValue);
|
||||
|
||||
// Try to configure it using empty file. Should fail.
|
||||
writeFile(config_empty);
|
||||
EXPECT_THROW(srv->init(TEST_FILE), BadValue);
|
||||
|
||||
// Now try to load a config that does not have Dhcp4 component.
|
||||
writeFile(config_v4);
|
||||
EXPECT_THROW(srv->init(TEST_FILE), BadValue);
|
||||
|
||||
// Now try to load a config with Dhcp4 full of nonsense.
|
||||
writeFile(config_nonsense);
|
||||
EXPECT_THROW(srv->init(TEST_FILE), BadValue);
|
||||
}
|
||||
|
||||
/// This unit-test reads all files enumerated in configs-test.txt file, loads
|
||||
/// each of them and verify that they can be loaded.
|
||||
///
|
||||
/// @todo: Unfortunately, we have this test disabled, because all loaded
|
||||
/// configs use memfile, which attempts to create lease file in
|
||||
/// /usr/local/var/bind10/kea-leases4.csv. We have couple options here:
|
||||
/// a) disable persistence in example configs - a very bad thing to do
|
||||
/// as users will forget to reenable it and then will be surprised when their
|
||||
/// leases disappear
|
||||
/// b) change configs to store lease file in /tmp. It's almost as bad as the
|
||||
/// previous one. Users will then be displeased when all their leases are
|
||||
/// wiped. (most systems wipe /tmp during boot)
|
||||
/// c) read each config and rewrite it on the fly, so persistence is disabled.
|
||||
/// This is probably the way to go, but this is a work for a dedicated ticket.
|
||||
///
|
||||
/// Hence I'm leaving the test in, but it is disabled.
|
||||
TEST_F(JSONFileBackendTest, DISABLED_loadAllConfigs) {
|
||||
|
||||
// Create server first
|
||||
boost::scoped_ptr<ControlledDhcpv4Srv> srv;
|
||||
ASSERT_NO_THROW(
|
||||
srv.reset(new ControlledDhcpv4Srv(0))
|
||||
);
|
||||
|
||||
const char* configs_list = "configs-list.txt";
|
||||
fstream configs(configs_list, ios::in);
|
||||
ASSERT_TRUE(configs.is_open());
|
||||
std::string config_name;
|
||||
while (std::getline(configs, config_name)) {
|
||||
|
||||
// Ignore empty and commented lines
|
||||
if (config_name.empty() || config_name[0] == '#') {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Unit-tests usually do not print out anything, but in this case I
|
||||
// think printing out tests configs is warranted.
|
||||
std::cout << "Loading config file " << config_name << std::endl;
|
||||
|
||||
try {
|
||||
srv->init(config_name);
|
||||
} catch (const std::exception& ex) {
|
||||
ADD_FAILURE() << "Exception thrown" << ex.what() << endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // End of anonymous namespace
|
@@ -133,7 +133,10 @@ bundyConfigHandler(ConstElementPtr new_config) {
|
||||
}
|
||||
|
||||
void
|
||||
ControlledDhcpv6Srv::init(const std::string& /* config_file*/) {
|
||||
ControlledDhcpv6Srv::init(const std::string& config_file) {
|
||||
// Call base class's init.
|
||||
Daemon::init(config_file);
|
||||
|
||||
// This is Bundy configuration backed. It established control session
|
||||
// that is used to connect to Bundy framework.
|
||||
//
|
||||
@@ -217,10 +220,10 @@ void ControlledDhcpv6Srv::cleanup() {
|
||||
}
|
||||
|
||||
void
|
||||
Daemon::loggerInit(const char* log_name, bool verbose, bool stand_alone) {
|
||||
Daemon::loggerInit(const char* log_name, bool verbose) {
|
||||
isc::log::initLogger(log_name,
|
||||
(verbose ? isc::log::DEBUG : isc::log::INFO),
|
||||
isc::log::MAX_DEBUG_LEVEL, NULL, !stand_alone);
|
||||
isc::log::MAX_DEBUG_LEVEL, NULL, true);
|
||||
}
|
||||
|
||||
}; // end of isc::dhcp namespace
|
||||
|
@@ -43,8 +43,8 @@ ControlledDhcpv6Srv::commandShutdownHandler(const string&, ConstElementPtr) {
|
||||
|
||||
ConstElementPtr
|
||||
ControlledDhcpv6Srv::commandLibReloadHandler(const string&, ConstElementPtr) {
|
||||
// TODO delete any stored CalloutHandles referring to the old libraries
|
||||
// Get list of currently loaded libraries and reload them.
|
||||
/// @todo delete any stored CalloutHandles referring to the old libraries
|
||||
/// Get list of currently loaded libraries and reload them.
|
||||
vector<string> loaded = HooksManager::getLibraryNames();
|
||||
bool status = HooksManager::loadLibraries(loaded);
|
||||
if (!status) {
|
||||
@@ -99,9 +99,12 @@ ControlledDhcpv6Srv::processCommand(const std::string& command,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
isc::data::ConstElementPtr
|
||||
ControlledDhcpv6Srv::processConfig(isc::data::ConstElementPtr config) {
|
||||
|
||||
LOG_DEBUG(dhcp6_logger, DBG_DHCP6_COMMAND, DHCP6_CONFIG_RECEIVED)
|
||||
.arg(config->str());
|
||||
|
||||
ControlledDhcpv6Srv* srv = ControlledDhcpv6Srv::getInstance();
|
||||
|
||||
if (!srv) {
|
||||
|
@@ -1,4 +1,4 @@
|
||||
// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
|
||||
// Copyright (C) 2012-2014 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
|
||||
@@ -26,12 +26,10 @@ namespace dhcp {
|
||||
|
||||
/// @brief Controlled version of the DHCPv6 server
|
||||
///
|
||||
/// This is a class that is responsible for establishing connection
|
||||
/// with msqg (receving commands and configuration). This is an extended
|
||||
/// version of Dhcpv6Srv class that is purely a DHCPv6 server, without
|
||||
/// external control. ControlledDhcpv6Srv should be used in typical BIND10
|
||||
/// (i.e. featuring msgq) environment, while Dhcpv6Srv should be used in
|
||||
/// embedded environments.
|
||||
/// This is a class that is responsible for DHCPv6 server being controllable.
|
||||
/// It does various things, depending on the configuration backend.
|
||||
/// For Bundy backend it establishes a connection with msqg and later receives
|
||||
/// commands over it. For Kea backend, it reads configuration file from disk.
|
||||
///
|
||||
/// For detailed explanation or relations between main(), ControlledDhcpv6Srv,
|
||||
/// Dhcpv6Srv and other classes, see \ref dhcpv6Session.
|
||||
@@ -53,7 +51,8 @@ public:
|
||||
/// operation. For specific details, see actual implementation in
|
||||
/// *_backend.cc
|
||||
///
|
||||
/// @return true if initialization was successful, false if it failed
|
||||
/// This method may throw if initialization fails. Exception types may be
|
||||
/// specific to used configuration backend.
|
||||
void init(const std::string& config_file);
|
||||
|
||||
/// @brief Performs cleanup, immediately before termination
|
||||
@@ -77,6 +76,11 @@ public:
|
||||
/// wrapper that calls process*Command() methods and catches exceptions
|
||||
/// in them.
|
||||
///
|
||||
/// Currently supported commands are:
|
||||
/// - shutdown
|
||||
/// - libreload
|
||||
/// - config-reload
|
||||
///
|
||||
/// @note It never throws.
|
||||
///
|
||||
/// @param command Text represenation of the command (e.g. "shutdown")
|
||||
@@ -88,7 +92,7 @@ public:
|
||||
|
||||
/// @brief configuration processor
|
||||
///
|
||||
/// This is a callback for handling incoming configuration updates.
|
||||
/// This is a method for handling incoming configuration updates.
|
||||
/// This method should be called by all configuration backends when the
|
||||
/// server is starting up or when configuration has changed.
|
||||
///
|
||||
@@ -103,7 +107,7 @@ public:
|
||||
|
||||
/// @brief returns pointer to the sole instance of Dhcpv6Srv
|
||||
///
|
||||
/// @note may return NULL, if called before server is spawned
|
||||
/// @return server instance (may return NULL, if called before server is spawned)
|
||||
static ControlledDhcpv6Srv* getInstance() {
|
||||
return (server_);
|
||||
}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
|
||||
// Copyright (C) 2012-2014 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
|
||||
@@ -234,24 +234,25 @@ cases it would be convenient to keep configuration in XML files.
|
||||
|
||||
Kea 0.9 introduces configuration backends that are switchable during compilation phase.
|
||||
There is a new parameter for configure script: --with-kea-config. It currently supports
|
||||
two values: BIND10 and JSON.
|
||||
two values: BUNDY and JSON.
|
||||
|
||||
BIND10 (which is the default value as of April 2014) means that Kea6 is linked with the
|
||||
BIND10 configuration backend that connects to the BIND10 framework and in general works
|
||||
BUNDY (which is the default value as of April 2014) means that Kea6 is linked with the
|
||||
Bundy (former BIND10) configuration backend that connects to the BIND10 framework and in general works
|
||||
exactly the same as Kea 0.8 and earlier versions. The benefits of that backend are uniform
|
||||
integration with BIND10 framework, easy on-line reconfiguration using bindctl, available
|
||||
RESTful API. On the other hand, it requires the whole heavy BIND10 framework that requires
|
||||
Python3 to be present. That backend is likely to go away with the release of Kea 0.9.
|
||||
integration with Bundy/BIND10 framework, easy on-line reconfiguration using bindctl, available
|
||||
RESTful API. On the other hand, it requires the whole heavy Bundy framework that requires
|
||||
Python3 to be present. That framework is going away with the release of Kea 0.9.
|
||||
|
||||
JSON is a new configuration backend that causes Kea to read JSON configuration file from
|
||||
disk. It does not require any framework and thus is considered more lightweight. It will
|
||||
allow dynamic on-line reconfiguration, but will lack remote capabilities (i.e. no RESTful
|
||||
API). This configuration backend is expected to be the default for upcoming Kea 0.9.
|
||||
API). This configuration backend is expected to be the default for upcoming Kea 0.9. It
|
||||
requires <tt> -c config-file </tt> command-line option.
|
||||
|
||||
Internally, configuration backends are implemented as different implementations of the
|
||||
isc::dhcp::ControlledDhcpv6Srv class, stored in ctrl_*_dhcpv6_srv.cc files. Depending on
|
||||
isc::dhcp::ControlledDhcpv6Srv class, stored in {kea,bundy}_controller.cc files. Depending on
|
||||
the choice made by ./configure script, only one of those files is compiled and linked.
|
||||
There are backend specific tests in src/bin/dhcp6/tests/ctrl_*_dhcpv6_srv_unittest.cc.
|
||||
There are backend specific tests in src/bin/dhcp6/tests/{kea,bundy}_controller_unittest.cc.
|
||||
Only tests specific to selected backend are linked and executed during make distcheck.
|
||||
|
||||
While it is unlikely that ISC will support more than one backend at any given time, there
|
||||
@@ -262,6 +263,38 @@ for ISC-DHCP. Is at least possible that similar will happen for Kea. Finally, if
|
||||
extend the isc::dhcp::CfgMgr with configuration export, this approach could be used as
|
||||
a migration tool.
|
||||
|
||||
@section dhcpv6SignalBasedReconfiguration Reconfiguring DHCPv6 server with SIGHUP signal
|
||||
|
||||
Online reconfiguration (reconfiguration without a need to restart the server) is an
|
||||
important feature which is supported by all modern DHCP servers. When using the JSON
|
||||
configuration backend, a configuration file name is specified with a command line
|
||||
option of the DHCP server binary. The configuration file is used to configure the
|
||||
server at startup. If the initial configuration fails, the server will fail to start.
|
||||
If the server starts and configures successfully it will use the initial configuration
|
||||
until it is reconfigured.
|
||||
|
||||
The reconfiguration request can be triggered externally (from other process) by editing
|
||||
a configuration file and sending a SIGHUP signal to DHCP server process. After receiving
|
||||
the SIGHUP signal, the server will re-read the configuration file specified at startup.
|
||||
If the reconfiguration fails, the server will continue to run and use the last good
|
||||
configuration.
|
||||
|
||||
The SIGHUP signal handler is defined in the kea_controller.cc. The handler calls the
|
||||
same function to reconfigure the server which is called to configure it at startup.
|
||||
The signal handler catches exceptions emitted during reconfiguration so as the
|
||||
uncaught exceptions don't cause the process to exit.
|
||||
|
||||
Signal handlers are static and therefore they must call static functions. The
|
||||
@c ControlledDhcpv6Srv::processCommand which performs the actual server
|
||||
reconfiguration is static, so it can be called from the signal handler. In order
|
||||
for the signal handler to know the location of the configuration file (specified
|
||||
at process startup), the location of this file needs to be stored in a static
|
||||
variable so as it may be directly accessed by the signal handler. This static
|
||||
variable is stored in the @c dhcp::Daemon class and all Kea processes can use
|
||||
it (all processes derive from this class). The configuration file location is
|
||||
initialized when the @c Daemon::init method is called. Therefore, derived
|
||||
classes should call it in their implementations of the @c init method.
|
||||
|
||||
@section dhcpv6Other Other DHCPv6 topics
|
||||
|
||||
For hooks API support in DHCPv6, see @ref dhcpv6Hooks.
|
||||
|
@@ -40,6 +40,10 @@ address assignment. The most likely cause is a problem with the client.
|
||||
A debug message listing the command (and possible arguments) received
|
||||
from the BIND 10 control system by the IPv6 DHCP server.
|
||||
|
||||
% DHCP6_CONFIG_RECEIVED received configuration: %1
|
||||
A debug message listing the configuration received by the DHCPv6 server.
|
||||
The source of that configuration depends on used configuration backend.
|
||||
|
||||
% DHCP6_CONFIG_COMPLETE DHCPv6 server has completed configuration: %1
|
||||
This is an informational message announcing the successful processing of a
|
||||
new configuration. it is output during server startup, and when an updated
|
||||
@@ -115,6 +119,14 @@ This message is printed when DHCPv6 server disables an interface from being
|
||||
used to receive DHCPv6 traffic. Sockets on this interface will not be opened
|
||||
by the Interface Manager until interface is enabled.
|
||||
|
||||
% DHCP6_DYNAMIC_RECONFIGURATION initate server reconfiguration using file: %1, after receiving SIGHUP signal
|
||||
This is the info message logged when the DHCPv6 server starts reconfiguration
|
||||
as a result of receiving SIGHUP signal.
|
||||
|
||||
% DHCP6_DYNAMIC_RECONFIGURATION_FAIL dynamic server reconfiguration failed with file: %1
|
||||
This is an error message logged when the dynamic reconfiguration of the
|
||||
DHCP server failed.
|
||||
|
||||
% DHCP6_EXTEND_LEASE_SUBNET_SELECTED the %1 subnet was selected for client extending its lease
|
||||
This is a debug message informing that a given subnet was selected. It will
|
||||
be used for extending lifetime of the lease. This is one of the early steps
|
||||
@@ -532,7 +544,7 @@ production environment.
|
||||
This informational message indicates that the IPv6 DHCP server has
|
||||
processed any command-line switches and is starting.
|
||||
|
||||
% DHCP6_START_INFO pid: %1, port: %2, verbose: %3, standalone: %4
|
||||
% DHCP6_START_INFO pid: %1, port: %2, verbose: %3
|
||||
This is a debug message issued during the IPv6 DHCP server startup.
|
||||
It lists some information about the parameters with which the server
|
||||
is running.
|
||||
|
@@ -15,41 +15,33 @@
|
||||
#include <config.h>
|
||||
|
||||
#include <asiolink/asiolink.h>
|
||||
#include <dhcp/iface_mgr.h>
|
||||
#include <dhcpsrv/dhcp_config_parser.h>
|
||||
#include <dhcpsrv/cfgmgr.h>
|
||||
#include <dhcp6/json_config_parser.h>
|
||||
#include <dhcp6/ctrl_dhcp6_srv.h>
|
||||
#include <dhcp6/dhcp6_log.h>
|
||||
#include <dhcp6/spec_config.h>
|
||||
#include <log/logger_level.h>
|
||||
#include <log/logger_name.h>
|
||||
#include <log/logger_manager.h>
|
||||
#include <log/logger_specification.h>
|
||||
#include <log/logger_support.h>
|
||||
#include <log/output_option.h>
|
||||
#include <exceptions/exceptions.h>
|
||||
#include <util/buffer.h>
|
||||
|
||||
#include <cassert>
|
||||
#include <iostream>
|
||||
#include <signal.h>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
using namespace isc::asiolink;
|
||||
using namespace isc::cc;
|
||||
using namespace isc::config;
|
||||
using namespace isc::data;
|
||||
using namespace isc::dhcp;
|
||||
using namespace isc::log;
|
||||
using namespace isc::util;
|
||||
using namespace std;
|
||||
|
||||
namespace isc {
|
||||
namespace dhcp {
|
||||
namespace {
|
||||
|
||||
void
|
||||
ControlledDhcpv6Srv::init(const std::string& file_name) {
|
||||
/// @brief Configure DHCPv6 server using the configuration file specified.
|
||||
///
|
||||
/// This function is used to both configure the DHCP server on its startup
|
||||
/// and dynamically reconfigure the server when SIGHUP signal is received.
|
||||
///
|
||||
/// It fetches DHCPv6 server's configuration from the 'Dhcp6' section of
|
||||
/// the JSON configuration file.
|
||||
///
|
||||
/// @param file_name Configuration file location.
|
||||
void configure(const std::string& file_name) {
|
||||
// This is a configuration backend implementation that reads the
|
||||
// configuration from a JSON file.
|
||||
|
||||
@@ -61,17 +53,17 @@ ControlledDhcpv6Srv::init(const std::string& file_name) {
|
||||
try {
|
||||
if (file_name.empty()) {
|
||||
// Basic sanity check: file name must not be empty.
|
||||
isc_throw(BadValue, "JSON configuration file not specified. Please "
|
||||
isc_throw(isc::BadValue, "JSON configuration file not specified. Please "
|
||||
"use -c command line option.");
|
||||
}
|
||||
|
||||
// Read contents of the file and parse it as JSON
|
||||
json = Element::fromJSONFile(file_name, true);
|
||||
json = isc::data::Element::fromJSONFile(file_name, true);
|
||||
|
||||
if (!json) {
|
||||
LOG_ERROR(dhcp6_logger, DHCP6_CONFIG_LOAD_FAIL)
|
||||
.arg("Config file " + file_name + " missing or empty.");
|
||||
isc_throw(BadValue, "Unable to process JSON configuration file:"
|
||||
isc_throw(isc::BadValue, "Unable to process JSON configuration file:"
|
||||
+ file_name);
|
||||
}
|
||||
|
||||
@@ -81,16 +73,16 @@ ControlledDhcpv6Srv::init(const std::string& file_name) {
|
||||
if (!dhcp6) {
|
||||
LOG_ERROR(dhcp6_logger, DHCP6_CONFIG_LOAD_FAIL)
|
||||
.arg("Config file " + file_name + " does not include 'Dhcp6' entry.");
|
||||
isc_throw(BadValue, "Unable to process JSON configuration file:"
|
||||
isc_throw(isc::BadValue, "Unable to process JSON configuration file:"
|
||||
+ file_name);
|
||||
}
|
||||
|
||||
// Use parsed JSON structures to configure the server
|
||||
result = processCommand("config-reload", dhcp6);
|
||||
result = ControlledDhcpv6Srv::processCommand("config-reload", dhcp6);
|
||||
|
||||
} catch (const std::exception& ex) {
|
||||
LOG_ERROR(dhcp6_logger, DHCP6_CONFIG_LOAD_FAIL).arg(ex.what());
|
||||
isc_throw(BadValue, "Unable to process JSON configuration file:"
|
||||
isc_throw(isc::BadValue, "Unable to process JSON configuration file:"
|
||||
+ file_name);
|
||||
}
|
||||
|
||||
@@ -105,21 +97,72 @@ ControlledDhcpv6Srv::init(const std::string& file_name) {
|
||||
}
|
||||
|
||||
// Now check is the returned result is successful (rcode=0) or not
|
||||
ConstElementPtr comment; /// see @ref isc::config::parseAnswer
|
||||
isc::data::ConstElementPtr comment; /// see @ref isc::config::parseAnswer
|
||||
int rcode;
|
||||
comment = parseAnswer(rcode, result);
|
||||
comment = isc::config::parseAnswer(rcode, result);
|
||||
if (rcode != 0) {
|
||||
string reason = "";
|
||||
if (comment) {
|
||||
reason = string(" (") + comment->stringValue() + string(")");
|
||||
}
|
||||
LOG_ERROR(dhcp6_logger, DHCP6_CONFIG_LOAD_FAIL).arg(reason);
|
||||
isc_throw(BadValue, "Failed to apply configuration:" << reason);
|
||||
isc_throw(isc::BadValue, "Failed to apply configuration:" << reason);
|
||||
}
|
||||
}
|
||||
|
||||
/// @brief Signals handler for DHCPv6 server.
|
||||
///
|
||||
/// This signal handler handles the following signals received by the DHCPv6
|
||||
/// server process:
|
||||
/// - SIGHUP - triggers server's dynamic reconfiguration.
|
||||
/// - SIGTERM - triggers server's shut down.
|
||||
/// - SIGINT - triggers server's shut down.
|
||||
///
|
||||
/// @param signo Signal number received.
|
||||
void signalHandler(int signo) {
|
||||
// SIGHUP signals a request to reconfigure the server.
|
||||
if (signo == SIGHUP) {
|
||||
// Get configuration file name.
|
||||
std::string file = ControlledDhcpv6Srv::getInstance()->getConfigFile();
|
||||
try {
|
||||
LOG_INFO(dhcp6_logger, DHCP6_DYNAMIC_RECONFIGURATION).arg(file);
|
||||
configure(file);
|
||||
} catch (const std::exception& ex) {
|
||||
// Log the unsuccessful reconfiguration. The reason for failure
|
||||
// should be already logged. Don't rethrow an exception so as
|
||||
// the server keeps working.
|
||||
LOG_ERROR(dhcp6_logger, DHCP6_DYNAMIC_RECONFIGURATION_FAIL)
|
||||
.arg(file);
|
||||
}
|
||||
} else if ((signo == SIGTERM) || (signo == SIGINT)) {
|
||||
isc::data::ElementPtr params(new isc::data::MapElement());
|
||||
ControlledDhcpv6Srv::processCommand("shutdown", params);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace isc {
|
||||
namespace dhcp {
|
||||
|
||||
void
|
||||
ControlledDhcpv6Srv::init(const std::string& file_name) {
|
||||
// Call parent class's init to initialize file name.
|
||||
Daemon::init(file_name);
|
||||
|
||||
// Configure the server using JSON file.
|
||||
configure(file_name);
|
||||
|
||||
// We don't need to call openActiveSockets() or startD2() as these
|
||||
// methods are called in processConfig() which is called by
|
||||
// processCommand("reload-config", ...)
|
||||
|
||||
// Set signal handlers. When the SIGHUP is received by the process
|
||||
// the server reconfiguration will be triggered. When SIGTERM or
|
||||
// SIGINT will be received, the server will start shutting down.
|
||||
signal(SIGHUP, signalHandler);
|
||||
signal(SIGTERM, signalHandler);
|
||||
signal(SIGINT, signalHandler);
|
||||
}
|
||||
|
||||
void ControlledDhcpv6Srv::cleanup() {
|
||||
@@ -129,7 +172,7 @@ void ControlledDhcpv6Srv::cleanup() {
|
||||
/// This is a logger initialization for JSON file backend.
|
||||
/// For now, it's just setting log messages to be printed on stdout.
|
||||
/// @todo: Implement this properly (see #3427)
|
||||
void Daemon::loggerInit(const char*, bool verbose, bool ) {
|
||||
void Daemon::loggerInit(const char*, bool verbose) {
|
||||
|
||||
setenv("B10_LOCKFILE_DIR_FROM_BUILD", "/tmp", 1);
|
||||
setenv("B10_LOGGER_ROOT", "kea", 0);
|
||||
|
@@ -43,9 +43,8 @@ const char* const DHCP6_LOGGER_NAME = "kea";
|
||||
|
||||
void
|
||||
usage() {
|
||||
cerr << "Usage: " << DHCP6_NAME << " [-v] [-s] [-p port_number] [-c cfgfile]" << endl;
|
||||
cerr << "Usage: " << DHCP6_NAME << " [-v] [-p port_number] [-c cfgfile]" << endl;
|
||||
cerr << " -v: verbose output" << endl;
|
||||
cerr << " -s: skip configuration (don't connect to BIND10 or don't read config file)" << endl;
|
||||
cerr << " -p number: specify non-standard port number 1-65535 "
|
||||
<< "(useful for testing only)" << endl;
|
||||
cerr << " -c file: specify configuration file" << endl;
|
||||
@@ -58,22 +57,17 @@ main(int argc, char* argv[]) {
|
||||
int ch;
|
||||
int port_number = DHCP6_SERVER_PORT; // The default. Any other values are
|
||||
// useful for testing only.
|
||||
bool stand_alone = false; // Should be connect to BIND10 msgq?
|
||||
bool verbose_mode = false; // Should server be verbose?
|
||||
|
||||
// The standard config file
|
||||
std::string config_file("");
|
||||
|
||||
while ((ch = getopt(argc, argv, "vsp:c:")) != -1) {
|
||||
while ((ch = getopt(argc, argv, "vp:c:")) != -1) {
|
||||
switch (ch) {
|
||||
case 'v':
|
||||
verbose_mode = true;
|
||||
break;
|
||||
|
||||
case 's': // stand-alone
|
||||
stand_alone = true;
|
||||
break;
|
||||
|
||||
case 'p': // port number
|
||||
try {
|
||||
port_number = boost::lexical_cast<int>(optarg);
|
||||
@@ -107,21 +101,20 @@ main(int argc, char* argv[]) {
|
||||
int ret = EXIT_SUCCESS;
|
||||
try {
|
||||
// Initialize logging. If verbose, we'll use maximum verbosity.
|
||||
// If standalone is enabled, do not buffer initial log messages
|
||||
Daemon::loggerInit(DHCP6_LOGGER_NAME, verbose_mode, stand_alone);
|
||||
Daemon::loggerInit(DHCP6_LOGGER_NAME, verbose_mode);
|
||||
|
||||
LOG_DEBUG(dhcp6_logger, DBG_DHCP6_START, DHCP6_START_INFO)
|
||||
.arg(getpid()).arg(port_number).arg(verbose_mode ? "yes" : "no")
|
||||
.arg(stand_alone ? "yes" : "no" );
|
||||
.arg(getpid()).arg(port_number).arg(verbose_mode ? "yes" : "no");
|
||||
|
||||
LOG_INFO(dhcp6_logger, DHCP6_STARTING);
|
||||
|
||||
// Create the server instance.
|
||||
ControlledDhcpv6Srv server(port_number);
|
||||
|
||||
if (!stand_alone) {
|
||||
try {
|
||||
// Initialize the server, i.e. establish control session
|
||||
// if BIND10 backend is used or read a configuration file
|
||||
// if Kea backend is used.
|
||||
server.init(config_file);
|
||||
|
||||
} catch (const std::exception& ex) {
|
||||
@@ -135,11 +128,10 @@ main(int argc, char* argv[]) {
|
||||
cerr << "Failed to initialize server: " << ex.what() << endl;
|
||||
return (EXIT_FAILURE);
|
||||
}
|
||||
} else {
|
||||
LOG_DEBUG(dhcp6_logger, DBG_DHCP6_START, DHCP6_STANDALONE);
|
||||
}
|
||||
|
||||
// And run the main loop of the server.
|
||||
server.run();
|
||||
|
||||
LOG_INFO(dhcp6_logger, DHCP6_SHUTDOWN);
|
||||
|
||||
} catch (const std::exception& ex) {
|
||||
|
@@ -1,6 +1,14 @@
|
||||
PYCOVERAGE_RUN = @PYCOVERAGE_RUN@
|
||||
PYTESTS = dhcp6_test.py
|
||||
EXTRA_DIST = $(PYTESTS)
|
||||
SHTESTS =
|
||||
# The test of dynamic reconfiguration based on signals will work only
|
||||
# if we are using file based configuration approach.
|
||||
if CONFIG_BACKEND_JSON
|
||||
SHTESTS += dhcp6_reconfigure_test.sh
|
||||
SHTESTS += dhcp6_sigterm_test.sh
|
||||
SHTESTS += dhcp6_sigint_test.sh
|
||||
endif
|
||||
EXTRA_DIST = $(PYTESTS) $(SHTESTS)
|
||||
|
||||
# Explicitly specify paths to dynamic libraries required by loadable python
|
||||
# modules. That is required on Mac OS systems. Otherwise we will get exception
|
||||
@@ -20,6 +28,12 @@ check-local:
|
||||
$(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
|
||||
done
|
||||
|
||||
for shtest in $(SHTESTS) ; do \
|
||||
echo Running test: $$shtest ; \
|
||||
export B10_LOCKFILE_DIR_FROM_BUILD=$(abs_top_builddir); \
|
||||
$(abs_srcdir)/$$shtest || exit ; \
|
||||
done
|
||||
|
||||
|
||||
AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
|
||||
AM_CPPFLAGS += -I$(top_builddir)/src/bin # for generated spec_config.h header
|
||||
@@ -30,6 +44,7 @@ AM_CPPFLAGS += -DINSTALL_PROG=\"$(abs_top_srcdir)/install-sh\"
|
||||
|
||||
CLEANFILES = $(builddir)/interfaces.txt $(builddir)/logger_lockfile
|
||||
CLEANFILES += $(builddir)/load_marker.txt $(builddir)/unload_marker.txt
|
||||
CLEANFILES += *.json *.log
|
||||
|
||||
AM_CXXFLAGS = $(B10_CXXFLAGS)
|
||||
if USE_CLANGPP
|
||||
|
@@ -18,6 +18,10 @@
|
||||
|
||||
namespace {
|
||||
|
||||
/// As of May 2014, maintaining or extending Bundy support is very low
|
||||
/// prority for Kea team. We are looking for contributors, who would
|
||||
/// like to maintain this backend.
|
||||
|
||||
// Bundy framework specific tests should be added here.
|
||||
TEST(BundyBackendTest, dummy) {
|
||||
|
||||
|
164
src/bin/dhcp6/tests/dhcp6_reconfigure_test.sh
Executable file
164
src/bin/dhcp6/tests/dhcp6_reconfigure_test.sh
Executable file
@@ -0,0 +1,164 @@
|
||||
# Copyright (C) 2014 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.
|
||||
|
||||
# Test name
|
||||
TEST_NAME="DynamicReconfiguration"
|
||||
# Path to the temporary configuration file.
|
||||
CFG_FILE="test_config.json"
|
||||
# Path to the Kea log file.
|
||||
LOG_FILE="test.log"
|
||||
# Kea configuration to be stored in the configuration file.
|
||||
CONFIG="{
|
||||
\"Dhcp6\":
|
||||
{
|
||||
\"interfaces\": [ ],
|
||||
\"preferred-lifetime\": 3000,
|
||||
\"valid-lifetime\": 4000,
|
||||
\"renew-timer\": 1000,
|
||||
\"rebind-timer\": 2000,
|
||||
\"lease-database\":
|
||||
{
|
||||
\"type\": \"memfile\",
|
||||
\"persist\": false
|
||||
},
|
||||
\"subnet6\": [
|
||||
{
|
||||
\"subnet\": \"2001:db8:1::/64\",
|
||||
\"pool\": [ \"2001:db8:1::10-2001:db8:1::100\" ]
|
||||
} ]
|
||||
}
|
||||
}"
|
||||
# Invalid configuration (negative preferred-lifetime) to check that Kea
|
||||
# gracefully handles reconfiguration errors.
|
||||
CONFIG_INVALID="{
|
||||
\"Dhcp6\":
|
||||
{
|
||||
\"interfaces\": [ ],
|
||||
\"preferred-lifetime\": -3,
|
||||
\"valid-lifetime\": 4000,
|
||||
\"renew-timer\": 1000,
|
||||
\"rebind-timer\": 2000,
|
||||
\"lease-database\":
|
||||
{
|
||||
\"type\": \"memfile\",
|
||||
\"persist\": false
|
||||
},
|
||||
\"subnet6\": [
|
||||
{
|
||||
\"subnet\": \"2001:db8:1::/64\",
|
||||
\"pool\": [ \"2001:db8:1::10-2001:db8:1::100\" ]
|
||||
} ]
|
||||
}
|
||||
}"
|
||||
|
||||
# Set the location of the executable.
|
||||
BIN="b10-dhcp6"
|
||||
BIN_PATH=".."
|
||||
|
||||
# Import common test library.
|
||||
. $(dirname $0)/../../../lib/testutils/dhcp_test_lib.sh
|
||||
|
||||
# Log the start of the test and print test name.
|
||||
test_start
|
||||
# Remove dangling Kea instances and remove log files.
|
||||
cleanup
|
||||
# Create new configuration file.
|
||||
create_config "${CONFIG}"
|
||||
# Instruct Kea to log to the specific file.
|
||||
set_logger
|
||||
# Start Kea.
|
||||
start_kea
|
||||
# Wait up to 20s for Kea to start.
|
||||
wait_for_kea 20
|
||||
if [ ${_WAIT_FOR_KEA} -eq 0 ]; then
|
||||
printf "ERROR: timeout waiting for Kea to start.\n"
|
||||
clean_exit 1
|
||||
fi
|
||||
|
||||
# Check if it is still running. It could have terminated (e.g. as a result
|
||||
# of configuration failure).
|
||||
get_pids
|
||||
if [ ${_GET_PIDS_NUM} -ne 1 ]; then
|
||||
printf "ERROR: expected one Kea process to be started. Found %d processes started.\n" ${_GET_PIDS_NUM}
|
||||
clean_exit 1
|
||||
fi
|
||||
|
||||
# Check in the log file, how many times server has been configured. It should
|
||||
# be just once on startup.
|
||||
get_reconfigs
|
||||
if [ ${_GET_RECONFIGS} -ne 1 ]; then
|
||||
printf "ERROR: server hasn't been configured.\n"
|
||||
clean_exit 1
|
||||
else
|
||||
printf "Server successfully configured.\n"
|
||||
fi
|
||||
|
||||
# Now use invalid configuration.
|
||||
create_config "${CONFIG_INVALID}"
|
||||
|
||||
# Try to reconfigure by sending SIGHUP
|
||||
send_signal 1
|
||||
|
||||
# The configuration should fail and the error message should be there.
|
||||
wait_for_message 10 "DHCP6_CONFIG_LOAD_FAIL" 1
|
||||
|
||||
# After receiving SIGHUP the server should try to reconfigure itself.
|
||||
# The configuration provided is invalid so it should result in
|
||||
# reconfiguration failure but the server should still be running.
|
||||
get_reconfigs
|
||||
if [ ${_GET_RECONFIGS} -ne 1 ]; then
|
||||
printf "ERROR: server has been reconfigured despite bogus configuration.\n"
|
||||
clean_exit 1
|
||||
elif [ ${_GET_RECONFIG_ERRORS} -ne 1 ]; then
|
||||
printf "ERROR: server did not report reconfiguration error despite attempt" \
|
||||
" to configure it with invalid configuration.\n"
|
||||
clean_exit 1
|
||||
fi
|
||||
|
||||
# Make sure the server is still operational.
|
||||
get_pids
|
||||
if [ ${_GET_PIDS_NUM} -ne 1 ]; then
|
||||
printf "ERROR: Kea process was killed when attempting reconfiguration.\n"
|
||||
clean_exit 1
|
||||
fi
|
||||
|
||||
# Restore the good configuration.
|
||||
create_config "${CONFIG}"
|
||||
|
||||
# Reconfigure the server with SIGHUP.
|
||||
send_signal 1
|
||||
|
||||
# There should be two occurrences of the DHCP6_CONFIG_COMPLETE messages.
|
||||
# Wait for it up to 10s.
|
||||
wait_for_message 10 "DHCP6_CONFIG_COMPLETE" 2
|
||||
|
||||
# After receiving SIGHUP the server should get reconfigured and the
|
||||
# reconfiguration should be noted in the log file. We should now
|
||||
# have two configurations logged in the log file.
|
||||
if [ ${_WAIT_FOR_MESSAGE} -eq 0 ]; then
|
||||
printf "ERROR: server hasn't been reconfigured.\n"
|
||||
clean_exit 1
|
||||
else
|
||||
printf "Server successfully reconfigured.\n"
|
||||
fi
|
||||
|
||||
# Make sure the server is still operational.
|
||||
get_pids
|
||||
if [ ${_GET_PIDS_NUM} -ne 1 ]; then
|
||||
printf "ERROR: Kea process was killed when attempting reconfiguration.\n"
|
||||
clean_exit 1
|
||||
fi
|
||||
|
||||
# All ok. Shut down Kea and exit.
|
||||
clean_exit 0
|
110
src/bin/dhcp6/tests/dhcp6_shutdown_test.sh
Executable file
110
src/bin/dhcp6/tests/dhcp6_shutdown_test.sh
Executable file
@@ -0,0 +1,110 @@
|
||||
# Copyright (C) 2014 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.
|
||||
|
||||
if [ $# -ne 2 ]; then
|
||||
printf "USAGE: dhcp6_shutdown_test.sh <test_name> <signal_num>\n"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Test name
|
||||
TEST_NAME=$1
|
||||
# Signal number to be used for this test.
|
||||
SIG_NUM=$2
|
||||
# Path to the temporary configuration file.
|
||||
CFG_FILE="test_config.json"
|
||||
# Path to the Kea log file.
|
||||
LOG_FILE="test.log"
|
||||
# Kea configuration to be stored in the configuration file.
|
||||
CONFIG="{
|
||||
\"Dhcp6\":
|
||||
{
|
||||
\"interfaces\": [ ],
|
||||
\"preferred-lifetime\": 3000,
|
||||
\"valid-lifetime\": 4000,
|
||||
\"renew-timer\": 1000,
|
||||
\"rebind-timer\": 2000,
|
||||
\"lease-database\":
|
||||
{
|
||||
\"type\": \"memfile\",
|
||||
\"persist\": false
|
||||
},
|
||||
\"subnet6\": [
|
||||
{
|
||||
\"subnet\": \"2001:db8:1::/64\",
|
||||
\"pool\": [ \"2001:db8:1::10-2001:db8:1::100\" ]
|
||||
} ]
|
||||
}
|
||||
}"
|
||||
|
||||
# Set the location of the executable.
|
||||
BIN="b10-dhcp6"
|
||||
BIN_PATH=".."
|
||||
|
||||
# Import common test library.
|
||||
. $(dirname $0)/../../../lib/testutils/dhcp_test_lib.sh
|
||||
|
||||
# Log the start of the test and print test name.
|
||||
test_start
|
||||
# Remove dangling Kea instances and remove log files.
|
||||
cleanup
|
||||
# Create new configuration file.
|
||||
create_config "${CONFIG}"
|
||||
# Instruct Kea to log to the specific file.
|
||||
set_logger
|
||||
# Start Kea.
|
||||
start_kea
|
||||
# Wait up to 20s for Kea to start.
|
||||
wait_for_kea 20
|
||||
if [ ${_WAIT_FOR_KEA} -eq 0 ]; then
|
||||
printf "ERROR: timeout waiting for Kea to start.\n"
|
||||
clean_exit 1
|
||||
fi
|
||||
|
||||
# Check if it is still running. It could have terminated (e.g. as a result
|
||||
# of configuration failure).
|
||||
get_pids
|
||||
if [ ${_GET_PIDS_NUM} -ne 1 ]; then
|
||||
printf "ERROR: expected one Kea process to be started. Found %d processes started.\n" ${_GET_PIDS_NUM}
|
||||
clean_exit 1
|
||||
fi
|
||||
|
||||
# Check in the log file, how many times server has been configured. It should
|
||||
# be just once on startup.
|
||||
get_reconfigs
|
||||
if [ ${_GET_RECONFIGS} -ne 1 ]; then
|
||||
printf "ERROR: server hasn't been configured.\n"
|
||||
clean_exit 1
|
||||
else
|
||||
printf "Server successfully configured.\n"
|
||||
fi
|
||||
|
||||
# Send signal to Kea (SIGTERM, SIGINT etc.)
|
||||
send_signal ${SIG_NUM}
|
||||
|
||||
# Wait up to 10s for the server's graceful shutdown. The graceful shut down
|
||||
# should be recorded in the log file with the appropriate message.
|
||||
wait_for_message 10 "DHCP6_SHUTDOWN" 1
|
||||
if [ ${_WAIT_FOR_MESSAGE} -eq 0 ]; then
|
||||
printf "ERROR: Server did not record shutdown in the log.\n"
|
||||
clean_exit 1
|
||||
fi
|
||||
|
||||
# Server should have shut down.
|
||||
get_pids
|
||||
if [ ${_GET_PIDS_NUM} -ne 0 ]; then
|
||||
printf "ERROR: Kea did not shut down after receiving signal.\n" ${_GET_PIDS_NUM}
|
||||
clean_exit 1
|
||||
fi
|
||||
|
||||
clean_exit 0
|
16
src/bin/dhcp6/tests/dhcp6_sigint_test.sh
Executable file
16
src/bin/dhcp6/tests/dhcp6_sigint_test.sh
Executable file
@@ -0,0 +1,16 @@
|
||||
# Copyright (C) 2014 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.
|
||||
|
||||
# Run a test that sends SIGINT to Kea and checks if it shuts down gracefully.
|
||||
$(dirname $0)/dhcp6_shutdown_test.sh "Sigint" 2
|
16
src/bin/dhcp6/tests/dhcp6_sigterm_test.sh
Executable file
16
src/bin/dhcp6/tests/dhcp6_sigterm_test.sh
Executable file
@@ -0,0 +1,16 @@
|
||||
# Copyright (C) 2014 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.
|
||||
|
||||
# Run a test that sends SIGTERM to Kea and checks if it shuts down gracefully.
|
||||
$(dirname $0)/dhcp6_shutdown_test.sh "Sigterm" 15
|
@@ -210,22 +210,5 @@ class TestDhcpv6Daemon(unittest.TestCase):
|
||||
# Check that there is an error message about invalid port number printed on stderr
|
||||
self.assertEqual( str(error).count("Failed to parse port number"), 1)
|
||||
|
||||
def test_portnumber_nonroot(self):
|
||||
print("Check that specifying unprivileged port number will work.")
|
||||
|
||||
# Check that there is a message about running with an unprivileged port
|
||||
(returncode, output, error) = self.runCommand(['../b10-dhcp6', '-v', '-s', '-p', '10547'])
|
||||
output_text = str(output) + str(error)
|
||||
self.assertEqual(output_text.count("DHCP6_OPEN_SOCKET opening sockets on port 10547"), 1)
|
||||
|
||||
def test_skip_msgq(self):
|
||||
print("Check that connection to BIND10 msgq can be disabled.")
|
||||
|
||||
# Check that the system outputs a message on one of its streams about running
|
||||
# standalone.
|
||||
(returncode, output, error) = self.runCommand(['../b10-dhcp6', '-v', '-s', '-p', '10547'])
|
||||
output_text = str(output) + str(error)
|
||||
self.assertEqual(output_text.count("DHCP6_STANDALONE"), 1)
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
@@ -736,8 +736,8 @@ Element::fromJSONFile(const std::string& file_name,
|
||||
if (!infile.is_open())
|
||||
{
|
||||
const char* error = strerror(errno);
|
||||
isc_throw(InvalidOperation, "Failed to read file " << file_name
|
||||
<< ",error:" << error);
|
||||
isc_throw(InvalidOperation, "Failed to read file '" << file_name
|
||||
<< "', error:" << error);
|
||||
}
|
||||
|
||||
return (fromJSON(infile, file_name, preproc));
|
||||
|
@@ -24,10 +24,14 @@
|
||||
namespace isc {
|
||||
namespace dhcp {
|
||||
|
||||
// This is an initial config file location.
|
||||
std::string Daemon::config_file_ = "";
|
||||
|
||||
Daemon::Daemon() {
|
||||
}
|
||||
|
||||
void Daemon::init(const std::string&) {
|
||||
void Daemon::init(const std::string& config_file) {
|
||||
config_file_ = config_file;
|
||||
}
|
||||
|
||||
void Daemon::cleanup() {
|
||||
|
@@ -34,6 +34,16 @@ namespace dhcp {
|
||||
/// Dhcpv6Srv) in tests, without going through the hassles of implemeting stub
|
||||
/// methods.
|
||||
///
|
||||
/// This class comprises a static object holding a location of the configuration
|
||||
/// file. The object must be static because it is instantiated by the signal
|
||||
/// handler functions, which are static by their nature. The signal handlers
|
||||
/// are used to reconfigure a running server and they need access to the
|
||||
/// configuration file location. They get this access by calling
|
||||
/// @c Daemon::getConfigFile function.
|
||||
///
|
||||
/// By default, the configuration file location is empty and its actual value
|
||||
/// is assigned to the static object in @c Daemon::init function.
|
||||
///
|
||||
/// @note Only one instance of this class is instantiated as it encompasses
|
||||
/// the whole operation of the server. Nothing, however, enforces the
|
||||
/// singleton status of the object.
|
||||
@@ -48,9 +58,7 @@ public:
|
||||
/// @brief Initializes the server.
|
||||
///
|
||||
/// Depending on the configuration backend, it establishes msgq session,
|
||||
/// or reads the
|
||||
/// Creates session that will be used to receive commands and updated
|
||||
/// configuration from cfgmgr (or indirectly from user via bindctl).
|
||||
/// or reads the configuration file.
|
||||
///
|
||||
/// Note: This function may throw to report enountered problems. It may
|
||||
/// also return false if the initialization was skipped. That may seem
|
||||
@@ -62,6 +70,8 @@ public:
|
||||
/// decide that it is not needed and will shut down.
|
||||
///
|
||||
/// @note this method may throw
|
||||
///
|
||||
/// @param config_file Config file name (may be empty if unused).
|
||||
virtual void init(const std::string& config_file);
|
||||
|
||||
/// @brief Performs final deconfiguration.
|
||||
@@ -83,10 +93,24 @@ public:
|
||||
/// virtual destructor as well.
|
||||
virtual ~Daemon();
|
||||
|
||||
/// Initializez logger
|
||||
/// @brief Returns config file name.
|
||||
static std::string getConfigFile() {
|
||||
return (config_file_);
|
||||
}
|
||||
|
||||
/// Initializes logger
|
||||
///
|
||||
/// This method initializes logger. I
|
||||
static void loggerInit(const char* log_name, bool verbose, bool stand_alone);
|
||||
/// This method initializes logger. Currently its implementation is specific
|
||||
/// to each configuration backend.
|
||||
///
|
||||
/// @param log_name name used in logger initialization
|
||||
/// @param verbose verbose mode (true usually enables DEBUG messages)
|
||||
static void loggerInit(const char* log_name, bool verbose);
|
||||
|
||||
private:
|
||||
|
||||
/// @brief Config file name or empty if config file not used.
|
||||
static std::string config_file_;
|
||||
};
|
||||
|
||||
}; // end of isc::dhcp namespace
|
||||
|
@@ -16,6 +16,7 @@
|
||||
#define TSIG_H 1
|
||||
|
||||
#include <boost/noncopyable.hpp>
|
||||
#include <boost/shared_ptr.hpp>
|
||||
|
||||
#include <exceptions/exceptions.h>
|
||||
|
||||
@@ -430,6 +431,10 @@ private:
|
||||
struct TSIGContextImpl;
|
||||
TSIGContextImpl* impl_;
|
||||
};
|
||||
|
||||
typedef boost::shared_ptr<TSIGContext> TSIGContextPtr;
|
||||
typedef boost::shared_ptr<TSIGKey> TSIGKeyPtr;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -15,4 +15,7 @@ libkea_testutils_la_LIBADD = $(top_builddir)/src/lib/asiolink/libkea-asiolink.l
|
||||
libkea_testutils_la_LIBADD += $(top_builddir)/src/lib/dns/libkea-dns++.la
|
||||
endif
|
||||
|
||||
EXTRA_DIST = portconfig.h socket_request.h
|
||||
# Include common libraries being used by shell-based tests.
|
||||
SHLIBS = dhcp_test_lib.sh
|
||||
|
||||
EXTRA_DIST = portconfig.h socket_request.h $(SHLIBS)
|
||||
|
191
src/lib/testutils/dhcp_test_lib.sh
Executable file
191
src/lib/testutils/dhcp_test_lib.sh
Executable file
@@ -0,0 +1,191 @@
|
||||
# Copyright (C) 2014 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.
|
||||
|
||||
# The following two parameters must to be specified in a script
|
||||
# including this library.
|
||||
# - BIN - Name of the Kea executable (excluding a path), e.g. b10-dhcp6
|
||||
# - BIN_PATH - Path to the Kea executable (excluding an executable name),
|
||||
# e.g. ../
|
||||
|
||||
# Begins a test by prining its name.
|
||||
# It requires the ${TEST_NAME} variable to hold the test name.
|
||||
test_start() {
|
||||
printf "\nSTART TEST ${TEST_NAME}\n"
|
||||
}
|
||||
|
||||
# Stores the configuration specified as a parameter in the configuration
|
||||
# file which name has been set in the ${CFG_FILE} variable.
|
||||
create_config() {
|
||||
printf "Creating Kea configuration file: %s.\n" ${CFG_FILE}
|
||||
printf "%b" ${1} > ${CFG_FILE}
|
||||
}
|
||||
|
||||
# Sets Kea logger to write to the file specified by the global value
|
||||
# ${LOG_FILE}.
|
||||
set_logger() {
|
||||
printf "Kea log will be stored in %s.\n" ${LOG_FILE}
|
||||
export B10_LOGGER_DESTINATION=${LOG_FILE}
|
||||
}
|
||||
|
||||
# Returns the number of running process pids and the list of pids.
|
||||
_GET_PIDS= # Return value: holds space separated list of DHCPv6 pids.
|
||||
_GET_PIDS_NUM= # Return value: holds the number of DHCPv6 server pids.
|
||||
get_pids() {
|
||||
_GET_PIDS=`ps axwwo pid,command | grep ${BIN} | grep -v grep | awk '{print $1}'`
|
||||
_GET_PIDS_NUM=`printf "%s" "${_GET_PIDS}" | wc -w | awk '{print $1}'`
|
||||
}
|
||||
|
||||
# Returns the number of occurrences of the Kea log message in the
|
||||
# log file.
|
||||
_GET_LOG_MESSAGES= # Holds the number of log message occurrences.
|
||||
get_log_messages() {
|
||||
# Grep log file for the logger message occurrences.
|
||||
_GET_LOG_MESSAGES=`grep -o ${1} ${LOG_FILE} | wc -w`
|
||||
# Remove whitespaces.
|
||||
${_GET_LOG_MESSAGES##*[! ]}
|
||||
}
|
||||
|
||||
# Returns the number of server configurations performed so far. Also
|
||||
# returns the number of configuration errors.
|
||||
_GET_RECONFIGS= # Return value: number of configurations so far.
|
||||
_GET_RECONFIG_ERRORS= # Return value: number of configuration errors.
|
||||
get_reconfigs() {
|
||||
# Grep log file for DHCP6_CONFIG_COMPLETE occurences. There should
|
||||
# be one occurence per (re)configuration.
|
||||
_GET_RECONFIGS=`grep -o DHCP6_CONFIG_COMPLETE ${LOG_FILE} | wc -w`
|
||||
# Grep log file for DHCP6_CONFIG_LOAD_FAIL to check for configuration
|
||||
# failures.
|
||||
_GET_RECONFIG_ERRORS=`grep -o DHCP6_CONFIG_LOAD_FAIL ${LOG_FILE} | wc -w`
|
||||
# Remove whitespaces
|
||||
${_GET_RECONFIGS##*[! ]}
|
||||
${_GET_RECONFIG_ERRORS##*[! ]}
|
||||
}
|
||||
|
||||
# Performs cleanup for a test.
|
||||
# It shuts down running Kea processes and removes temporary files.
|
||||
# The location of the log file and the configuration file should be set
|
||||
# in the ${LOG_FILE} and ${CFG_FILE} variables recpectively, prior to
|
||||
# calling this function.
|
||||
cleanup() {
|
||||
get_pids
|
||||
# Shut down running Kea processes.
|
||||
for pid in ${_GET_PIDS}
|
||||
do
|
||||
printf "Shutting down Kea proccess having pid %d.\n" ${pid}
|
||||
kill -9 ${pid}
|
||||
done
|
||||
# Remove temporary files.
|
||||
rm -rf ${LOG_FILE}
|
||||
rm -rf ${CFG_FILE}
|
||||
}
|
||||
|
||||
# Exists the test in the clean way.
|
||||
# It peformes the cleanup and prints whether the test has passed or failed.
|
||||
# If a test fails, the Kea log is dumped.
|
||||
clean_exit() {
|
||||
exit_code=${1} # Exit code to be returned by the exit function.
|
||||
if [ ${exit_code} -eq 0 ]; then
|
||||
cleanup
|
||||
printf "PASSED ${TEST_NAME}\n\n"
|
||||
else
|
||||
# Dump log file if exists for debugging purposes.
|
||||
if [ -s ${LOG_FILE} ]; then
|
||||
printf "Log file dump:\n"
|
||||
cat ${LOG_FILE}
|
||||
fi
|
||||
cleanup
|
||||
printf "FAILED ${TEST_NAME}\n\n"
|
||||
fi
|
||||
exit ${exit_code}
|
||||
}
|
||||
|
||||
# Starts Kea process in background using a configuration file specified
|
||||
# in the global variable ${CFG_FILE}
|
||||
start_kea() {
|
||||
printf "Running command %s.\n" "\"${BIN_PATH}/${BIN} -c ${CFG_FILE}\""
|
||||
${BIN_PATH}/$BIN -c ${CFG_FILE} &
|
||||
}
|
||||
|
||||
# Waits for Kea to startup with timeout.
|
||||
# This function repeatedly checs if the Kea log file has been created
|
||||
# and is non-empty. If it is, the function assumes that Kea has started.
|
||||
# It doesn't check the contents of the log file though.
|
||||
# If the log file doesn't exist the function sleeps for a second and
|
||||
# checks again. This is repeated until timeout is reached or non-empty
|
||||
# log file is found. If timeout is reached, the function reports an
|
||||
# error.
|
||||
_WAIT_FOR_KEA=0 # Return value: Holds 0 if Kea hasn't started, 1 otherwise
|
||||
wait_for_kea() {
|
||||
timeout=${1} # Desired timeout in seconds.
|
||||
loops=0 # Loops counter
|
||||
_WAIT_FOR_KEA=0
|
||||
while [ ! -s ${LOG_FILE} ] && [ ${loops} -le ${timeout} ]; do
|
||||
printf "."
|
||||
sleep 1
|
||||
loops=`expr $loops + 1`
|
||||
done
|
||||
printf "\n"
|
||||
if [ ${loops} -le ${timeout} ]; then
|
||||
_WAIT_FOR_KEA=1
|
||||
fi
|
||||
}
|
||||
|
||||
# Waits for a specific message to occur in the Kea log file.
|
||||
# This function is called when the test expects specific message
|
||||
# to show up in the log file as a result of some action that has
|
||||
# been taken. Typically, the test expects that the message
|
||||
# is logged when the SIGHUP or SIGTERM signal has been sent to the
|
||||
# Kea process.
|
||||
# This function waits a specified number of seconds for the number
|
||||
# of message occurrences to show up. If the expected number of
|
||||
# message doesn't occur, the error status is returned.
|
||||
_WAIT_FOR_MESSAGE=0 # Return value: holds 0 if the message hasn't occured,
|
||||
# 1 otherwise.
|
||||
wait_for_message() {
|
||||
timeout=${1} # Expecte timeout value in seconds.
|
||||
message=${2} # Expected message id.
|
||||
occurrences=${3} # Number of expected occurrences.
|
||||
loops=0 # Number of loops performed so far.
|
||||
_WAIT_FOR_MESSAGE=0
|
||||
# Check if log file exists and if we reached timeout.
|
||||
while [ ! -s {LOG_FILE} ] && [ ${loops} -le ${timeout} ]; do
|
||||
printf "."
|
||||
# Check if the message has been logged.
|
||||
get_log_messages ${message}
|
||||
if [ ${_GET_LOG_MESSAGES} -eq ${occurrences} ]; then
|
||||
printf "\n"
|
||||
_WAIT_FOR_MESSAGE=1
|
||||
return
|
||||
fi
|
||||
# Message not recorded. Keep going.
|
||||
sleep 1
|
||||
loops=`expr ${loops} + 1`
|
||||
done
|
||||
printf "\n"
|
||||
# Timeout.
|
||||
}
|
||||
|
||||
# Sends specified signal to the Kea process.
|
||||
send_signal() {
|
||||
sig=${1} # Signal number.
|
||||
# Get Kea pid.
|
||||
get_pids
|
||||
if [ ${_GET_PIDS_NUM} -ne 1 ]; then
|
||||
printf "ERROR: expected one Kea process to be started. Found %d processes started.\n" ${_GET_PIDS_NUM}
|
||||
clean_exit 1
|
||||
fi
|
||||
printf "Sending signal ${sig} to Kea process (pid=%s).\n" ${_GET_PIDS}
|
||||
# Actually send a signal.
|
||||
kill -${sig} ${_GET_PIDS}
|
||||
}
|
Reference in New Issue
Block a user