mirror of
https://gitlab.isc.org/isc-projects/kea
synced 2025-08-30 21:45:37 +00:00
[master] Merge branch 'trac3432'
Implements TSIG in D2 Fixed Conflicts: src/bin/d2/nc_trans.cc src/bin/d2/nc_trans.h src/bin/d2/tests/nc_test_utils.cc src/bin/d2/tests/nc_trans_unittests.cc
This commit is contained in:
@@ -5367,75 +5367,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">
|
||||
@@ -5453,8 +5496,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
|
||||
@@ -5633,8 +5674,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),
|
||||
|
@@ -40,8 +40,9 @@ typedef boost::shared_ptr<asio::ip::udp::socket> SocketPtr;
|
||||
class FauxServer {
|
||||
public:
|
||||
enum ResponseMode {
|
||||
USE_RCODE, // Generate a response with a given RCODE
|
||||
CORRUPT_RESP // Generate a corrupt response
|
||||
USE_RCODE, // Generate a response with a given RCODE
|
||||
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,28 @@ 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 key_name value to use to create TSIG key, if blank 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_)));
|
||||
}
|
||||
|
||||
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 +385,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 +1032,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;
|
||||
|
@@ -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;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user