2
0
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:
Thomas Markwalder
2014-05-28 07:40:34 -04:00
21 changed files with 1136 additions and 316 deletions

View File

@@ -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> &mdash;
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> &mdash;
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> &mdash;
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> &mdash;
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>

View File

@@ -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;

View File

@@ -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.

View File

@@ -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.

View File

@@ -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:

View File

@@ -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",

View File

@@ -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

View File

@@ -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.

View File

@@ -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();

View File

@@ -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.

View File

@@ -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\" : {"

View File

@@ -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\": [ "

View File

@@ -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

View File

@@ -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\": [ "

View File

@@ -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

View File

@@ -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,

View File

@@ -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,

View File

@@ -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),

View File

@@ -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.
///

View File

@@ -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;

View File

@@ -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;
}
}