Commit 5a67a898 authored by Marcin Siodelski's avatar Marcin Siodelski
Browse files

[master] Merge branch 'trac2977'

Conflicts:
	src/bin/d2/Makefile.am
	src/bin/d2/d2_messages.mes
	src/bin/d2/tests/Makefile.am
parents 196c9f80 67a39b06
......@@ -56,6 +56,7 @@ b10_dhcp_ddns_SOURCES += d2_config.cc d2_config.h
b10_dhcp_ddns_SOURCES += d2_cfg_mgr.cc d2_cfg_mgr.h
b10_dhcp_ddns_SOURCES += d2_update_message.cc d2_update_message.h
b10_dhcp_ddns_SOURCES += d2_zone.cc d2_zone.h
b10_dhcp_ddns_SOURCES += dns_client.cc dns_client.h
b10_dhcp_ddns_SOURCES += ncr_msg.cc ncr_msg.h
nodist_b10_dhcp_ddns_SOURCES = d2_messages.h d2_messages.cc
......@@ -64,6 +65,7 @@ EXTRA_DIST += d2_messages.mes
b10_dhcp_ddns_LDADD = $(top_builddir)/src/lib/log/libb10-log.la
b10_dhcp_ddns_LDADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
b10_dhcp_ddns_LDADD += $(top_builddir)/src/lib/cc/libb10-cc.la
b10_dhcp_ddns_LDADD += $(top_builddir)/src/lib/asiodns/libb10-asiodns.la
b10_dhcp_ddns_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
b10_dhcp_ddns_LDADD += $(top_builddir)/src/lib/config/libb10-cfgclient.la
b10_dhcp_ddns_LDADD += $(top_builddir)/src/lib/dhcpsrv/libb10-dhcpsrv.la
......
......@@ -129,6 +129,11 @@ This is warning message issued when there are no domains in the configuration
which match the cited fully qualified domain name (FQDN). The DNS Update
request for the FQDN cannot be processed.
% DHCP_DDNS_INVALID_RESPONSE received response to DNS Update message is malformed: %1
This is a debug message issued when the DHCP-DDNS application encountered an error
while decoding a response to DNS Update message. Typically, this error will be
encountered when a response message is malformed.
% DHCP_DDNS_PROCESS_INIT application init invoked
This is a debug message issued when the Dhcp-Ddns application enters
its init method.
......
......@@ -76,8 +76,8 @@ D2Process::command(const std::string& command, isc::data::ConstElementPtr args){
// @todo This is the initial implementation. If and when D2 is extended
// to support its own commands, this implementation must change. Otherwise
// it should reject all commands as it does now.
LOG_DEBUG(dctl_logger, DBGLVL_TRACE_BASIC,
DHCP_DDNS_COMMAND).arg(command).arg(args->str());
LOG_DEBUG(dctl_logger, DBGLVL_TRACE_BASIC, DHCP_DDNS_COMMAND)
.arg(command).arg(args ? args->str() : "(no args)");
return (isc::config::createAnswer(COMMAND_INVALID, "Unrecognized command: "
+ command));
......
......@@ -85,7 +85,7 @@ public:
///
/// @param command is a string label representing the command to execute.
/// @param args is a set of arguments (if any) required for the given
/// command.
/// command. It can be a NULL pointer if no arguments exist for a command.
/// @return an Element that contains the results of command composed
/// of an integer status value (0 means successful, non-zero means failure),
/// and a string explanation of the outcome.
......
......@@ -63,6 +63,10 @@ public:
isc::Exception(file, line, what) {}
};
class D2UpdateMessage;
/// @brief Pointer to the DNS Update Message.
typedef boost::shared_ptr<D2UpdateMessage> D2UpdateMessagePtr;
/// @brief The @c D2UpdateMessage encapsulates a DNS Update message.
///
......
......@@ -46,7 +46,7 @@ DControllerBase::setController(const DControllerBasePtr& controller) {
}
void
DControllerBase::launch(int argc, char* argv[]) {
DControllerBase::launch(int argc, char* argv[], const bool test_mode) {
// Step 1 is to parse the command line arguments.
try {
parseArgs(argc, argv);
......@@ -55,12 +55,16 @@ DControllerBase::launch(int argc, char* argv[]) {
throw; // rethrow it
}
// Now that we know what the mode flags are, we can init logging.
// If standalone is enabled, do not buffer initial log messages
isc::log::initLogger(bin_name_,
((verbose_ && stand_alone_)
? isc::log::DEBUG : isc::log::INFO),
isc::log::MAX_DEBUG_LEVEL, NULL, !stand_alone_);
// Do not initialize logger here if we are running unit tests. It would
// replace an instance of unit test specific logger.
if (!test_mode) {
// Now that we know what the mode flags are, we can init logging.
// If standalone is enabled, do not buffer initial log messages
isc::log::initLogger(bin_name_,
((verbose_ && stand_alone_)
? isc::log::DEBUG : isc::log::INFO),
isc::log::MAX_DEBUG_LEVEL, NULL, !stand_alone_);
}
LOG_DEBUG(dctl_logger, DBGLVL_START_SHUT, DCTL_STARTING)
.arg(app_name_).arg(getpid());
......@@ -295,7 +299,8 @@ DControllerBase::commandHandler(const std::string& command,
isc::data::ConstElementPtr args) {
LOG_DEBUG(dctl_logger, DBGLVL_COMMAND, DCTL_COMMAND_RECEIVED)
.arg(controller_->getAppName()).arg(command).arg(args->str());
.arg(controller_->getAppName()).arg(command)
.arg(args ? args->str() : "(no args)");
// Invoke the instance method on the controller singleton.
return (controller_->executeCommand(command, args));
......@@ -368,7 +373,7 @@ DControllerBase::executeCommand(const std::string& command,
if (rcode == COMMAND_INVALID)
{
// It wasn't controller command, so may be an application command.
answer = process_->command(command,args);
answer = process_->command(command, args);
}
}
......
......@@ -144,8 +144,19 @@ public:
/// arguments. Note this method is deliberately not virtual to ensure the
/// proper sequence of events occur.
///
/// This function can be run in the test mode. It prevents initialization
/// of D2 module logger. This is used in unit tests which initialize logger
/// in their main function. Such logger uses environmental variables to
/// control severity, verbosity etc. Reinitialization of logger by this
/// function would replace unit tests specific logger configuration with
/// this suitable for D2 running as a bind10 module.
///
/// @param argc is the number of command line arguments supplied
/// @param argv is the array of string (char *) command line arguments
/// @param test_mode is a bool value which indicates if
/// @c DControllerBase::launch should be run in the test mode (if true).
/// This parameter doesn't have default value to force test implementers to
/// enable test mode explicitly.
///
/// @throw throws one of the following exceptions:
/// InvalidUsage - Indicates invalid command line.
......@@ -156,7 +167,7 @@ public:
/// process event loop.
/// SessionEndError - Could not disconnect from BIND10 (integrated mode
/// only).
void launch(int argc, char* argv[]);
void launch(int argc, char* argv[], const bool test_mode);
/// @brief A dummy configuration handler that always returns success.
///
......@@ -198,7 +209,8 @@ public:
/// the virtual instance method, executeCommand.
///
/// @param command textual representation of the command
/// @param args parameters of the command
/// @param args parameters of the command. It can be NULL pointer if no
/// arguments exist for a particular command.
///
/// @return status of the processed command
static isc::data::ConstElementPtr
......
// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
#include <d2/dns_client.h>
#include <d2/d2_log.h>
#include <dns/messagerenderer.h>
#include <limits>
namespace isc {
namespace d2 {
namespace {
// OutputBuffer objects are pre-allocated before data is written to them.
// This is a default number of bytes for the buffers we create within
// DNSClient class.
const size_t DEFAULT_BUFFER_SIZE = 128;
}
using namespace isc::util;
using namespace isc::asiolink;
using namespace isc::asiodns;
using namespace isc::dns;
// This class provides the implementation for the DNSClient. This allows for
// the separation of the DNSClient interface from the implementation details.
// Currently, implementation uses IOFetch object to handle asynchronous
// communication with the DNS. This design may be revisited in the future. If
// implementation is changed, the DNSClient API will remain unchanged thanks
// to this separation.
class DNSClientImpl : public asiodns::IOFetch::Callback {
public:
// A buffer holding response from a DNS.
util::OutputBufferPtr in_buf_;
// A caller-supplied object holding a parsed response from DNS.
D2UpdateMessagePtr response_;
// A caller-supplied external callback which is invoked when DNS message
// exchange is complete or interrupted.
DNSClient::Callback* callback_;
// A Transport Layer protocol used to communicate with a DNS.
DNSClient::Protocol proto_;
// Constructor and Destructor
DNSClientImpl(D2UpdateMessagePtr& response_placeholder,
DNSClient::Callback* callback,
const DNSClient::Protocol proto);
virtual ~DNSClientImpl();
// This internal callback is called when the DNS update message exchange is
// complete. It further invokes the external callback provided by a caller.
// Before external callback is invoked, an object of the D2UpdateMessage
// type, representing a response from the server is set.
virtual void operator()(asiodns::IOFetch::Result result);
// Starts asynchronous DNS Update.
void doUpdate(asiolink::IOService& io_service,
const asiolink::IOAddress& ns_addr,
const uint16_t ns_port,
D2UpdateMessage& update,
const unsigned int wait);
// This function maps the IO error to the DNSClient error.
DNSClient::Status getStatus(const asiodns::IOFetch::Result);
};
DNSClientImpl::DNSClientImpl(D2UpdateMessagePtr& response_placeholder,
DNSClient::Callback* callback,
const DNSClient::Protocol proto)
: in_buf_(new OutputBuffer(DEFAULT_BUFFER_SIZE)),
response_(response_placeholder), callback_(callback), proto_(proto) {
// @todo Currently we only support UDP. The support for TCP is planned for
// the future release.
if (proto_ == DNSClient::TCP) {
isc_throw(isc::NotImplemented, "TCP is currently not supported as a"
<< " Transport protocol for DNS Updates; please use UDP");
}
// Given that we already eliminated the possibility that TCP is used, it
// would be sufficient to check that (proto != DNSClient::UDP). But, once
// support TCP is added the check above will disappear and the extra check
// will be needed here anyway.
// Note that cascaded check is used here instead of:
// if (proto_ != DNSClient::TCP && proto_ != DNSClient::UDP)..
// because some versions of GCC compiler complain that check above would
// be always 'false' due to limited range of enumeration. In fact, it is
// possible to pass out of range integral value through enum and it should
// be caught here.
if (proto_ != DNSClient::TCP) {
if (proto_ != DNSClient::UDP) {
isc_throw(isc::NotImplemented, "invalid transport protocol type '"
<< proto_ << "' specified for DNS Updates");
}
}
if (!response_) {
isc_throw(BadValue, "a pointer to an object to encapsulate the DNS"
" server must be provided; found NULL value");
}
}
DNSClientImpl::~DNSClientImpl() {
}
void
DNSClientImpl::operator()(asiodns::IOFetch::Result result) {
// Get the status from IO. If no success, we just call user's callback
// and pass the status code.
DNSClient::Status status = getStatus(result);
if (status == DNSClient::SUCCESS) {
InputBuffer response_buf(in_buf_->getData(), in_buf_->getLength());
// Server's response may be corrupted. In such case, fromWire will
// 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);
} catch (const Exception& ex) {
status = DNSClient::INVALID_RESPONSE;
LOG_DEBUG(dctl_logger, DBGLVL_TRACE_DETAIL,
DHCP_DDNS_INVALID_RESPONSE).arg(ex.what());
}
}
// Once we are done with internal business, let's call a callback supplied
// by a caller.
if (callback_ != NULL) {
(*callback_)(status);
}
}
DNSClient::Status
DNSClientImpl::getStatus(const asiodns::IOFetch::Result result) {
switch (result) {
case IOFetch::SUCCESS:
return (DNSClient::SUCCESS);
case IOFetch::TIME_OUT:
return (DNSClient::TIMEOUT);
case IOFetch::STOPPED:
return (DNSClient::IO_STOPPED);
default:
;
}
return (DNSClient::OTHER);
}
void
DNSClientImpl::doUpdate(IOService& io_service,
const IOAddress& ns_addr,
const uint16_t ns_port,
D2UpdateMessage& update,
const unsigned int wait) {
// 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.
// Fortunately, the renderer's API accepts user-supplied buffers. So, let's
// create our own buffer and pass it to the renderer so as the message is
// rendered to this buffer. Finally, we pass this buffer to IOFetch.
dns::MessageRenderer renderer;
OutputBufferPtr msg_buf(new OutputBuffer(DEFAULT_BUFFER_SIZE));
renderer.setBuffer(msg_buf.get());
// Render DNS Update message. This may throw a bunch of exceptions if
// invalid message object is given.
update.toWire(renderer);
// IOFetch has all the mechanisms that we need to perform asynchronous
// communication with the DNS server. The last but one argument points to
// this object as a completion callback for the message exchange. As a
// result operator()(Status) will be called.
// Timeout value is explicitly cast to the int type to avoid warnings about
// overflows when doing implicit cast. It should have been checked by the
// caller that the unsigned timeout value will fit into int.
IOFetch io_fetch(IOFetch::UDP, io_service, msg_buf, ns_addr, ns_port,
in_buf_, this, static_cast<int>(wait));
// Post the task to the task queue in the IO service. Caller will actually
// run these tasks by executing IOService::run.
io_service.post(io_fetch);
}
DNSClient::DNSClient(D2UpdateMessagePtr& response_placeholder,
Callback* callback, const DNSClient::Protocol proto)
: impl_(new DNSClientImpl(response_placeholder, callback, proto)) {
}
DNSClient::~DNSClient() {
delete (impl_);
}
unsigned int
DNSClient::getMaxTimeout() {
static const unsigned int max_timeout = std::numeric_limits<int>::max();
return (max_timeout);
}
void
DNSClient::doUpdate(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(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);
}
} // namespace d2
} // namespace isc
// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
#ifndef DNS_CLIENT_H
#define DNS_CLIENT_H
#include <d2/d2_update_message.h>
#include <asiolink/io_service.h>
#include <util/buffer.h>
#include <asiodns/io_fetch.h>
#include <dns/tsig.h>
namespace isc {
namespace d2 {
class DNSClient;
typedef boost::shared_ptr<DNSClient> DNSClientPtr;
/// DNSClient class implementation.
class DNSClientImpl;
/// @brief The @c DNSClient class handles communication with the DNS server.
///
/// Communication with the DNS server is asynchronous. Caller must provide a
/// callback, which will be invoked when the response from the DNS server is
/// received, a timeout has occurred or IO service has been stopped for any
/// reason. The caller-supplied callback is called by the internal callback
/// operator implemented by @c DNSClient. This callback is responsible for
/// initializing the @c D2UpdateMessage instance which encapsulates the response
/// from the DNS. This initialization does not take place if the response from
/// DNS is not received.
///
/// Caller must supply a pointer to the @c D2UpdateMessage object, which will
/// encapsulate DNS response, through class constructor. An exception will be
/// thrown if the pointer is not initialized by the caller.
///
/// @todo Ultimately, this class will support both TCP and UDP Transport.
/// Currently only UDP is supported and can be specified as a preferred
/// protocol. @c DNSClient constructor will throw an exception if TCP is
/// specified. Once both protocols are supported, the @c DNSClient logic will
/// try to obey caller's preference. However, it may use the other protocol if
/// on its own discretion, when there is a legitimate reason to do so. For
/// example, if communication with the server using preferred protocol fails.
class DNSClient {
public:
/// @brief Transport layer protocol used by a DNS Client to communicate
/// with a server.
enum Protocol {
UDP,
TCP
};
/// @brief A status code of the DNSClient.
enum Status {
SUCCESS, ///< Response received and is ok.
TIMEOUT, ///< No response, timeout.
IO_STOPPED, ///< IO was stopped.
INVALID_RESPONSE, ///< Response received but invalid.
OTHER ///< Other, unclassified error.
};
/// @brief Callback for the @c DNSClient class.
///
/// This is is abstract class which represents the external callback for the
/// @c DNSClient. Caller must implement this class and supply its instance
/// in the @c DNSClient constructor to get callbacks when the DNS Update
/// exchange is complete (@see @c DNSClient).
class Callback {
public:
/// @brief Virtual destructor.
virtual ~Callback() { }
/// @brief Function operator implementing a callback.
///
/// @param status a @c DNSClient::Status enum representing status code
/// of DNSClient operation.
virtual void operator()(DNSClient::Status status) = 0;
};
/// @brief Constructor.
///
/// @param response_placeholder Pointer to an object which will hold a
/// DNS server's response. Caller is responsible for allocating this object.
/// @param callback Pointer to an object implementing @c DNSClient::Callback
/// class. This object will be called when DNS message exchange completes or
/// if an error occurs. NULL value disables callback invocation.
/// @param proto caller's preference regarding Transport layer protocol to
/// be used by DNS Client to communicate with a server.
DNSClient(D2UpdateMessagePtr& response_placeholder, Callback* callback,
const Protocol proto = UDP);
/// @brief Virtual destructor, does nothing.
~DNSClient();
///
/// @name Copy constructor and assignment operator
///
/// Copy constructor and assignment operator are private because there are
/// no use cases when @DNSClient instance will need to be copied. Also, it
/// is desired to avoid copying @DNSClient::impl_ pointer and external
/// callbacks.
///
//@{
private:
DNSClient(const DNSClient& source);
DNSClient& operator=(const DNSClient& source);
//@}
public:
/// @brief Returns maximal allowed timeout value accepted by
/// @c DNSClient::doUpdate.
///
/// @return maximal allowed timeout value accepted by @c DNSClient::doUpdate
static unsigned int getMaxTimeout();
/// @brief Start asynchronous DNS Update with 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 seconds) 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.
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 seconds) 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);
private:
DNSClientImpl* impl_; ///< Pointer to DNSClient implementation.
};
} // namespace d2
} // namespace isc
#endif // DNS_CLIENT_H
......@@ -38,7 +38,8 @@ int main(int argc, char* argv[]) {
// Launch the controller passing in command line arguments.
// Exit program with the controller's return code.
try {
controller->launch(argc, argv);
// 'false' value disables test mode.
controller->launch(argc, argv, false);
} catch (const isc::Exception& ex) {
std::cerr << "Service failed:" << ex.what() << std::endl;
ret = EXIT_FAILURE;
......
......@@ -61,6 +61,7 @@ d2_unittests_SOURCES += ../d2_config.cc ../d2_config.h
d2_unittests_SOURCES += ../d2_cfg_mgr.cc ../d2_cfg_mgr.h
d2_unittests_SOURCES += ../d2_update_message.cc ../d2_update_message.h
d2_unittests_SOURCES += ../d2_zone.cc ../d2_zone.h
d2_unittests_SOURCES += ../dns_client.cc ../dns_client.h
d2_unittests_SOURCES += ../ncr_msg.cc ../ncr_msg.h
d2_unittests_SOURCES += d_test_stubs.cc d_test_stubs.h
d2_unittests_SOURCES += d2_unittests.cc
......@@ -71,6 +72,7 @@ d2_unittests_SOURCES += d_cfg_mgr_unittests.cc
d2_unittests_SOURCES += d2_cfg_mgr_unittests.cc
d2_unittests_SOURCES += d2_update_message_unittests.cc
d2_unittests_SOURCES += d2_zone_unittests.cc
d2_unittests_SOURCES += dns_client_unittests.cc
d2_unittests_SOURCES += ncr_unittests.cc
nodist_d2_unittests_SOURCES = ../d2_messages.h ../d2_messages.cc
......@@ -79,6 +81,7 @@ d2_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
d2_unittests_LDADD = $(GTEST_LDADD)
d2_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
d2_unittests_LDADD += $(top_builddir)/src/lib/log/libb10-log.la
d2_unittests_LDADD += $(top_builddir)/src/lib/asiodns/libb10-asiodns.la
d2_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
d2_unittests_LDADD += $(top_builddir)/src/lib/cc/libb10-cc.la
d2_unittests_LDADD += $(top_builddir)/src/lib/config/libb10-cfgclient.la
......