mirror of
https://gitlab.isc.org/isc-projects/kea
synced 2025-08-31 14:05:33 +00:00
[master]Merge branch 'master' of ssh://git.bind10.isc.org/var/bind10/git/bind10
This commit is contained in:
22
ChangeLog
22
ChangeLog
@@ -1,3 +1,25 @@
|
||||
406. [bug] muks
|
||||
On platforms such as OpenBSD where pselect() is not available,
|
||||
make a wrapper around select() in perfdhcp.
|
||||
(Trac #1639, git 6ea0b1d62e7b8b6596209291aa6c8b34b8e73191)
|
||||
|
||||
405. [bug] jinmei
|
||||
Make sure disabling Boost threads if the default configuration is
|
||||
to disable it for the system. This fixes a crash and hang up
|
||||
problem on OpenBSD, where the use of Boost thread could be
|
||||
different in different program files depending on the order of
|
||||
including various header files, and could introduce inconsistent
|
||||
states between a library and a program. Explicitly forcing the
|
||||
original default throughout the BIND 10 build environment will
|
||||
prevent this from happening.
|
||||
(Trac #1727, git 23f9c3670b544c5f8105958ff148aeba050bc1b4)
|
||||
|
||||
404. [bug] naokikambe
|
||||
The statistic counters are now properly accumulated across multiple
|
||||
instances of b10-auth (if there are multiple instances), instead of
|
||||
providing result for random instance.
|
||||
(Trac #1751, git 3285353a660e881ec2b645e1bc10d94e5020f357)
|
||||
|
||||
403. [build]* jelte
|
||||
The configure option for botan (--with-botan=PATH) is replaced by
|
||||
--with-botan-config=PATH, which takes a full path to a botan-config
|
||||
|
20
configure.ac
20
configure.ac
@@ -780,7 +780,22 @@ if test "${boost_include_path}" ; then
|
||||
fi
|
||||
AC_CHECK_HEADERS([boost/shared_ptr.hpp boost/foreach.hpp boost/interprocess/sync/interprocess_upgradable_mutex.hpp boost/date_time/posix_time/posix_time_types.hpp boost/bind.hpp boost/function.hpp],,
|
||||
AC_MSG_ERROR([Missing required header files.]))
|
||||
CPPFLAGS="$CPPFLAGS_SAVES"
|
||||
|
||||
# Detect whether Boost tries to use threads by default, and, if not,
|
||||
# make it sure explicitly. In some systems the automatic detection
|
||||
# may depend on preceding header files, and if inconsistency happens
|
||||
# it could lead to a critical disruption.
|
||||
AC_MSG_CHECKING([whether Boost tries to use threads])
|
||||
AC_TRY_COMPILE([
|
||||
#include <boost/config.hpp>
|
||||
#ifdef BOOST_HAS_THREADS
|
||||
#error "boost will use threads"
|
||||
#endif],,
|
||||
[AC_MSG_RESULT(no)
|
||||
CPPFLAGS_BOOST_THREADCONF="-DBOOST_DISABLE_THREADS=1"],
|
||||
[AC_MSG_RESULT(yes)])
|
||||
|
||||
CPPFLAGS="$CPPFLAGS_SAVES $CPPFLAGS_BOOST_THREADCONF"
|
||||
AC_SUBST(BOOST_INCLUDES)
|
||||
|
||||
# I can't get some of the #include <asio.hpp> right without this
|
||||
@@ -930,6 +945,9 @@ EV_SET(&kevent, 0, 0, 0, 0, 0, udata);],
|
||||
])
|
||||
fi
|
||||
|
||||
# Check for functions that are not available on all platforms
|
||||
AC_CHECK_FUNCS([pselect])
|
||||
|
||||
# perfdhcp: If the clock_gettime() function does not exist on the system,
|
||||
# use an alternative supplied in the code based on gettimeofday().
|
||||
CLOCK_GETTIME_LDFLAGS=
|
||||
|
@@ -47,6 +47,8 @@
|
||||
#include <dns/message.h>
|
||||
#include <dns/tsig.h>
|
||||
|
||||
#include <asiodns/dns_service.h>
|
||||
|
||||
#include <datasrc/query.h>
|
||||
#include <datasrc/data_source.h>
|
||||
#include <datasrc/memory_datasrc.h>
|
||||
@@ -78,6 +80,35 @@ using namespace isc::asiolink;
|
||||
using namespace isc::asiodns;
|
||||
using namespace isc::server_common::portconfig;
|
||||
|
||||
namespace {
|
||||
// A helper class for cleaning up message renderer.
|
||||
//
|
||||
// A temporary object of this class is expected to be created before starting
|
||||
// response message rendering. On construction, it (re)initialize the given
|
||||
// message renderer with the given buffer. On destruction, it releases
|
||||
// the previously set buffer and then release any internal resource in the
|
||||
// renderer, no matter what happened during the rendering, especially even
|
||||
// when it resulted in an exception.
|
||||
//
|
||||
// Note: if we need this helper in many other places we might consider making
|
||||
// it visible to other modules. As of this implementation this is the only
|
||||
// user of this class, so we hide it within the implementation.
|
||||
class RendererHolder {
|
||||
public:
|
||||
RendererHolder(MessageRenderer& renderer, OutputBuffer* buffer) :
|
||||
renderer_(renderer)
|
||||
{
|
||||
renderer.setBuffer(buffer);
|
||||
}
|
||||
~RendererHolder() {
|
||||
renderer_.setBuffer(NULL);
|
||||
renderer_.clear();
|
||||
}
|
||||
private:
|
||||
MessageRenderer& renderer_;
|
||||
};
|
||||
}
|
||||
|
||||
class AuthSrvImpl {
|
||||
private:
|
||||
// prohibit copy
|
||||
@@ -277,8 +308,8 @@ public:
|
||||
};
|
||||
|
||||
void
|
||||
makeErrorMessage(Message& message, OutputBuffer& buffer,
|
||||
const Rcode& rcode,
|
||||
makeErrorMessage(MessageRenderer& renderer, Message& message,
|
||||
OutputBuffer& buffer, const Rcode& rcode,
|
||||
std::auto_ptr<TSIGContext> tsig_context =
|
||||
std::auto_ptr<TSIGContext>())
|
||||
{
|
||||
@@ -311,14 +342,12 @@ makeErrorMessage(Message& message, OutputBuffer& buffer,
|
||||
|
||||
message.setRcode(rcode);
|
||||
|
||||
MessageRenderer renderer;
|
||||
renderer.setBuffer(&buffer);
|
||||
RendererHolder holder(renderer, &buffer);
|
||||
if (tsig_context.get() != NULL) {
|
||||
message.toWire(renderer, *tsig_context);
|
||||
} else {
|
||||
message.toWire(renderer);
|
||||
}
|
||||
renderer.setBuffer(NULL);
|
||||
LOG_DEBUG(auth_logger, DBG_AUTH_MESSAGES, AUTH_SEND_ERROR_RESPONSE)
|
||||
.arg(renderer.getLength()).arg(message);
|
||||
}
|
||||
@@ -447,13 +476,13 @@ AuthSrv::processMessage(const IOMessage& io_message, Message& message,
|
||||
} catch (const DNSProtocolError& error) {
|
||||
LOG_DEBUG(auth_logger, DBG_AUTH_DETAIL, AUTH_PACKET_PROTOCOL_ERROR)
|
||||
.arg(error.getRcode().toText()).arg(error.what());
|
||||
makeErrorMessage(message, buffer, error.getRcode());
|
||||
makeErrorMessage(impl_->renderer_, message, buffer, error.getRcode());
|
||||
impl_->resumeServer(server, message, true);
|
||||
return;
|
||||
} catch (const Exception& ex) {
|
||||
LOG_DEBUG(auth_logger, DBG_AUTH_DETAIL, AUTH_PACKET_PARSE_ERROR)
|
||||
.arg(ex.what());
|
||||
makeErrorMessage(message, buffer, Rcode::SERVFAIL());
|
||||
makeErrorMessage(impl_->renderer_, message, buffer, Rcode::SERVFAIL());
|
||||
impl_->resumeServer(server, message, true);
|
||||
return;
|
||||
} // other exceptions will be handled at a higher layer.
|
||||
@@ -480,7 +509,8 @@ AuthSrv::processMessage(const IOMessage& io_message, Message& message,
|
||||
}
|
||||
|
||||
if (tsig_error != TSIGError::NOERROR()) {
|
||||
makeErrorMessage(message, buffer, tsig_error.toRcode(), tsig_context);
|
||||
makeErrorMessage(impl_->renderer_, message, buffer,
|
||||
tsig_error.toRcode(), tsig_context);
|
||||
impl_->resumeServer(server, message, true);
|
||||
return;
|
||||
}
|
||||
@@ -497,9 +527,11 @@ AuthSrv::processMessage(const IOMessage& io_message, Message& message,
|
||||
} else if (message.getOpcode() != Opcode::QUERY()) {
|
||||
LOG_DEBUG(auth_logger, DBG_AUTH_DETAIL, AUTH_UNSUPPORTED_OPCODE)
|
||||
.arg(message.getOpcode().toText());
|
||||
makeErrorMessage(message, buffer, Rcode::NOTIMP(), tsig_context);
|
||||
makeErrorMessage(impl_->renderer_, message, buffer,
|
||||
Rcode::NOTIMP(), tsig_context);
|
||||
} else if (message.getRRCount(Message::SECTION_QUESTION) != 1) {
|
||||
makeErrorMessage(message, buffer, Rcode::FORMERR(), tsig_context);
|
||||
makeErrorMessage(impl_->renderer_, message, buffer,
|
||||
Rcode::FORMERR(), tsig_context);
|
||||
} else {
|
||||
ConstQuestionPtr question = *message.beginQuestion();
|
||||
const RRType &qtype = question->getType();
|
||||
@@ -517,10 +549,10 @@ AuthSrv::processMessage(const IOMessage& io_message, Message& message,
|
||||
} catch (const std::exception& ex) {
|
||||
LOG_DEBUG(auth_logger, DBG_AUTH_DETAIL, AUTH_RESPONSE_FAILURE)
|
||||
.arg(ex.what());
|
||||
makeErrorMessage(message, buffer, Rcode::SERVFAIL());
|
||||
makeErrorMessage(impl_->renderer_, message, buffer, Rcode::SERVFAIL());
|
||||
} catch (...) {
|
||||
LOG_DEBUG(auth_logger, DBG_AUTH_DETAIL, AUTH_RESPONSE_FAILURE_UNKNOWN);
|
||||
makeErrorMessage(message, buffer, Rcode::SERVFAIL());
|
||||
makeErrorMessage(impl_->renderer_, message, buffer, Rcode::SERVFAIL());
|
||||
}
|
||||
impl_->resumeServer(server, message, send_answer);
|
||||
}
|
||||
@@ -563,13 +595,11 @@ AuthSrvImpl::processNormalQuery(const IOMessage& io_message, Message& message,
|
||||
}
|
||||
} catch (const Exception& ex) {
|
||||
LOG_ERROR(auth_logger, AUTH_PROCESS_FAIL).arg(ex.what());
|
||||
makeErrorMessage(message, buffer, Rcode::SERVFAIL());
|
||||
makeErrorMessage(renderer_, message, buffer, Rcode::SERVFAIL());
|
||||
return (true);
|
||||
}
|
||||
|
||||
renderer_.clear();
|
||||
renderer_.setBuffer(&buffer);
|
||||
|
||||
RendererHolder holder(renderer_, &buffer);
|
||||
const bool udp_buffer =
|
||||
(io_message.getSocket().getProtocol() == IPPROTO_UDP);
|
||||
renderer_.setLengthLimit(udp_buffer ? remote_bufsize : 65535);
|
||||
@@ -578,7 +608,6 @@ AuthSrvImpl::processNormalQuery(const IOMessage& io_message, Message& message,
|
||||
} else {
|
||||
message.toWire(renderer_);
|
||||
}
|
||||
renderer_.setBuffer(NULL);
|
||||
LOG_DEBUG(auth_logger, DBG_AUTH_MESSAGES, AUTH_SEND_NORMAL_RESPONSE)
|
||||
.arg(renderer_.getLength()).arg(message);
|
||||
return (true);
|
||||
@@ -594,7 +623,8 @@ AuthSrvImpl::processXfrQuery(const IOMessage& io_message, Message& message,
|
||||
|
||||
if (io_message.getSocket().getProtocol() == IPPROTO_UDP) {
|
||||
LOG_DEBUG(auth_logger, DBG_AUTH_DETAIL, AUTH_AXFR_UDP);
|
||||
makeErrorMessage(message, buffer, Rcode::FORMERR(), tsig_context);
|
||||
makeErrorMessage(renderer_, message, buffer, Rcode::FORMERR(),
|
||||
tsig_context);
|
||||
return (true);
|
||||
}
|
||||
|
||||
@@ -619,7 +649,8 @@ AuthSrvImpl::processXfrQuery(const IOMessage& io_message, Message& message,
|
||||
|
||||
LOG_DEBUG(auth_logger, DBG_AUTH_DETAIL, AUTH_AXFR_ERROR)
|
||||
.arg(err.what());
|
||||
makeErrorMessage(message, buffer, Rcode::SERVFAIL(), tsig_context);
|
||||
makeErrorMessage(renderer_, message, buffer, Rcode::SERVFAIL(),
|
||||
tsig_context);
|
||||
return (true);
|
||||
}
|
||||
|
||||
@@ -636,14 +667,16 @@ AuthSrvImpl::processNotify(const IOMessage& io_message, Message& message,
|
||||
if (message.getRRCount(Message::SECTION_QUESTION) != 1) {
|
||||
LOG_DEBUG(auth_logger, DBG_AUTH_DETAIL, AUTH_NOTIFY_QUESTIONS)
|
||||
.arg(message.getRRCount(Message::SECTION_QUESTION));
|
||||
makeErrorMessage(message, buffer, Rcode::FORMERR(), tsig_context);
|
||||
makeErrorMessage(renderer_, message, buffer, Rcode::FORMERR(),
|
||||
tsig_context);
|
||||
return (true);
|
||||
}
|
||||
ConstQuestionPtr question = *message.beginQuestion();
|
||||
if (question->getType() != RRType::SOA()) {
|
||||
LOG_DEBUG(auth_logger, DBG_AUTH_DETAIL, AUTH_NOTIFY_RRTYPE)
|
||||
.arg(question->getType().toText());
|
||||
makeErrorMessage(message, buffer, Rcode::FORMERR(), tsig_context);
|
||||
makeErrorMessage(renderer_, message, buffer, Rcode::FORMERR(),
|
||||
tsig_context);
|
||||
return (true);
|
||||
}
|
||||
|
||||
@@ -698,14 +731,12 @@ AuthSrvImpl::processNotify(const IOMessage& io_message, Message& message,
|
||||
message.setHeaderFlag(Message::HEADERFLAG_AA);
|
||||
message.setRcode(Rcode::NOERROR());
|
||||
|
||||
renderer_.clear();
|
||||
renderer_.setBuffer(&buffer);
|
||||
RendererHolder holder(renderer_, &buffer);
|
||||
if (tsig_context.get() != NULL) {
|
||||
message.toWire(renderer_, *tsig_context);
|
||||
} else {
|
||||
message.toWire(renderer_);
|
||||
}
|
||||
renderer_.setBuffer(NULL);
|
||||
return (true);
|
||||
}
|
||||
|
||||
@@ -837,11 +868,14 @@ AuthSrv::getListenAddresses() const {
|
||||
|
||||
void
|
||||
AuthSrv::setListenAddresses(const AddressList& addresses) {
|
||||
installListenAddresses(addresses, impl_->listen_addresses_, *dnss_);
|
||||
// For UDP servers we specify the "SYNC_OK" option because in our usage
|
||||
// it can act in the synchronous mode.
|
||||
installListenAddresses(addresses, impl_->listen_addresses_, *dnss_,
|
||||
DNSService::SERVER_SYNC_OK);
|
||||
}
|
||||
|
||||
void
|
||||
AuthSrv::setDNSService(isc::asiodns::DNSService& dnss) {
|
||||
AuthSrv::setDNSService(isc::asiodns::DNSServiceBase& dnss) {
|
||||
dnss_ = &dnss;
|
||||
}
|
||||
|
||||
|
@@ -28,6 +28,7 @@
|
||||
#include <util/buffer.h>
|
||||
|
||||
#include <asiodns/dns_server.h>
|
||||
#include <asiodns/dns_service.h>
|
||||
#include <asiodns/dns_lookup.h>
|
||||
#include <asiodns/dns_answer.h>
|
||||
#include <asiolink/io_message.h>
|
||||
@@ -384,7 +385,7 @@ public:
|
||||
const;
|
||||
|
||||
/// \brief Assign an ASIO DNS Service queue to this Auth object
|
||||
void setDNSService(isc::asiodns::DNSService& dnss);
|
||||
void setDNSService(isc::asiodns::DNSServiceBase& dnss);
|
||||
|
||||
/// \brief Sets the keyring used for verifying and signing
|
||||
///
|
||||
@@ -400,7 +401,7 @@ private:
|
||||
isc::asiolink::SimpleCallback* checkin_;
|
||||
isc::asiodns::DNSLookup* dns_lookup_;
|
||||
isc::asiodns::DNSAnswer* dns_answer_;
|
||||
isc::asiodns::DNSService* dnss_;
|
||||
isc::asiodns::DNSServiceBase* dnss_;
|
||||
};
|
||||
|
||||
#endif // __AUTH_SRV_H
|
||||
|
@@ -112,9 +112,13 @@ AuthCountersImpl::submitStatistics() const {
|
||||
return (false);
|
||||
}
|
||||
std::stringstream statistics_string;
|
||||
// add pid in order for stats to identify which auth sends
|
||||
// statistics in the situation that multiple auth instances are
|
||||
// working
|
||||
statistics_string << "{\"command\": [\"set\","
|
||||
<< "{ \"owner\": \"Auth\","
|
||||
<< " \"data\":"
|
||||
<< " \"pid\":" << getpid()
|
||||
<< ", \"data\":"
|
||||
<< "{ \"queries.udp\": "
|
||||
<< server_counter_.get(AuthCounters::SERVER_UDP_QUERY)
|
||||
<< ", \"queries.tcp\": "
|
||||
|
@@ -41,6 +41,7 @@
|
||||
#include <dns/tests/unittest_util.h>
|
||||
#include <testutils/dnsmessage_test.h>
|
||||
#include <testutils/srv_test.h>
|
||||
#include <testutils/mockups.h>
|
||||
#include <testutils/portconfig.h>
|
||||
#include <testutils/socket_request.h>
|
||||
|
||||
@@ -75,7 +76,7 @@ const char* const CONFIG_INMEMORY_EXAMPLE =
|
||||
class AuthSrvTest : public SrvTestBase {
|
||||
protected:
|
||||
AuthSrvTest() :
|
||||
dnss_(ios_, NULL, NULL, NULL),
|
||||
dnss_(),
|
||||
server(true, xfrout),
|
||||
rrclass(RRClass::IN()),
|
||||
// The empty string is expected value of the parameter of
|
||||
@@ -135,8 +136,7 @@ protected:
|
||||
opcode.getCode(), QR_FLAG, 1, 0, 0, 0);
|
||||
}
|
||||
|
||||
IOService ios_;
|
||||
DNSService dnss_;
|
||||
MockDNSService dnss_;
|
||||
MockSession statistics_session;
|
||||
MockXfroutClient xfrout;
|
||||
AuthSrv server;
|
||||
@@ -1079,10 +1079,11 @@ TEST_F(AuthSrvTest, processNormalQuery_reuseRenderer1) {
|
||||
UnitTestUtil::createRequestMessage(request_message, Opcode::QUERY(),
|
||||
default_qid, Name("example.com"),
|
||||
RRClass::IN(), RRType::NS());
|
||||
|
||||
|
||||
request_message.setHeaderFlag(Message::HEADERFLAG_AA);
|
||||
createRequestPacket(request_message, IPPROTO_UDP);
|
||||
server.processMessage(*io_message, *parse_message, *response_obuffer, &dnsserv);
|
||||
server.processMessage(*io_message, *parse_message, *response_obuffer,
|
||||
&dnsserv);
|
||||
EXPECT_NE(request_message.getRcode(), parse_message->getRcode());
|
||||
}
|
||||
|
||||
@@ -1090,12 +1091,14 @@ TEST_F(AuthSrvTest, processNormalQuery_reuseRenderer2) {
|
||||
UnitTestUtil::createRequestMessage(request_message, Opcode::QUERY(),
|
||||
default_qid, Name("example.com"),
|
||||
RRClass::IN(), RRType::SOA());
|
||||
|
||||
|
||||
request_message.setHeaderFlag(Message::HEADERFLAG_AA);
|
||||
createRequestPacket(request_message, IPPROTO_UDP);
|
||||
server.processMessage(*io_message, *parse_message, *response_obuffer, &dnsserv);
|
||||
server.processMessage(*io_message, *parse_message, *response_obuffer,
|
||||
&dnsserv);
|
||||
ConstQuestionPtr question = *parse_message->beginQuestion();
|
||||
EXPECT_STRNE(question->getType().toText().c_str(),RRType::NS().toText().c_str());
|
||||
EXPECT_STRNE(question->getType().toText().c_str(),
|
||||
RRType::NS().toText().c_str());
|
||||
}
|
||||
//
|
||||
// Tests for catching exceptions in various stages of the query processing
|
||||
@@ -1138,11 +1141,12 @@ checkThrow(ThrowWhen method, ThrowWhen throw_at, bool isc_exception) {
|
||||
class FakeZoneFinder : public isc::datasrc::ZoneFinder {
|
||||
public:
|
||||
FakeZoneFinder(isc::datasrc::ZoneFinderPtr zone_finder,
|
||||
ThrowWhen throw_when,
|
||||
bool isc_exception) :
|
||||
ThrowWhen throw_when, bool isc_exception,
|
||||
ConstRRsetPtr fake_rrset) :
|
||||
real_zone_finder_(zone_finder),
|
||||
throw_when_(throw_when),
|
||||
isc_exception_(isc_exception)
|
||||
isc_exception_(isc_exception),
|
||||
fake_rrset_(fake_rrset)
|
||||
{}
|
||||
|
||||
virtual isc::dns::Name
|
||||
@@ -1162,7 +1166,18 @@ public:
|
||||
const isc::dns::RRType& type,
|
||||
isc::datasrc::ZoneFinder::FindOptions options)
|
||||
{
|
||||
using namespace isc::datasrc;
|
||||
checkThrow(THROW_AT_FIND, throw_when_, isc_exception_);
|
||||
// If faked RRset was specified on construction and it matches the
|
||||
// query, return it instead of searching the real data source.
|
||||
if (fake_rrset_ && fake_rrset_->getName() == name &&
|
||||
fake_rrset_->getType() == type)
|
||||
{
|
||||
return (ZoneFinderContextPtr(new ZoneFinder::Context(
|
||||
*this, options,
|
||||
ResultContext(SUCCESS,
|
||||
fake_rrset_))));
|
||||
}
|
||||
return (real_zone_finder_->find(name, type, options));
|
||||
}
|
||||
|
||||
@@ -1190,6 +1205,7 @@ private:
|
||||
isc::datasrc::ZoneFinderPtr real_zone_finder_;
|
||||
ThrowWhen throw_when_;
|
||||
bool isc_exception_;
|
||||
ConstRRsetPtr fake_rrset_;
|
||||
};
|
||||
|
||||
/// \brief Proxy InMemoryClient that can throw exceptions at specified times
|
||||
@@ -1206,12 +1222,15 @@ public:
|
||||
/// class or the related FakeZoneFinder)
|
||||
/// \param isc_exception if true, throw isc::Exception, otherwise,
|
||||
/// throw std::exception
|
||||
/// \param fake_rrset If non NULL, it will be used as an answer to
|
||||
/// find() for that name and type.
|
||||
FakeInMemoryClient(AuthSrv::InMemoryClientPtr real_client,
|
||||
ThrowWhen throw_when,
|
||||
bool isc_exception) :
|
||||
ThrowWhen throw_when, bool isc_exception,
|
||||
ConstRRsetPtr fake_rrset = ConstRRsetPtr()) :
|
||||
real_client_(real_client),
|
||||
throw_when_(throw_when),
|
||||
isc_exception_(isc_exception)
|
||||
isc_exception_(isc_exception),
|
||||
fake_rrset_(fake_rrset)
|
||||
{}
|
||||
|
||||
/// \brief proxy call for findZone
|
||||
@@ -1226,14 +1245,16 @@ public:
|
||||
const FindResult result = real_client_->findZone(name);
|
||||
return (FindResult(result.code, isc::datasrc::ZoneFinderPtr(
|
||||
new FakeZoneFinder(result.zone_finder,
|
||||
throw_when_,
|
||||
isc_exception_))));
|
||||
throw_when_,
|
||||
isc_exception_,
|
||||
fake_rrset_))));
|
||||
}
|
||||
|
||||
private:
|
||||
AuthSrv::InMemoryClientPtr real_client_;
|
||||
ThrowWhen throw_when_;
|
||||
bool isc_exception_;
|
||||
ConstRRsetPtr fake_rrset_;
|
||||
};
|
||||
|
||||
} // end anonymous namespace for throwing proxy classes
|
||||
@@ -1248,9 +1269,7 @@ TEST_F(AuthSrvTest, queryWithInMemoryClientProxy) {
|
||||
|
||||
AuthSrv::InMemoryClientPtr fake_client(
|
||||
new FakeInMemoryClient(server.getInMemoryClient(rrclass),
|
||||
THROW_NEVER,
|
||||
false));
|
||||
|
||||
THROW_NEVER, false));
|
||||
ASSERT_NE(AuthSrv::InMemoryClientPtr(), server.getInMemoryClient(rrclass));
|
||||
server.setInMemoryClient(rrclass, fake_client);
|
||||
|
||||
@@ -1267,9 +1286,11 @@ TEST_F(AuthSrvTest, queryWithInMemoryClientProxy) {
|
||||
// to throw in the given method
|
||||
// If isc_exception is true, it will throw isc::Exception, otherwise
|
||||
// it will throw std::exception
|
||||
// If non null rrset is given, it will be passed to the proxy so it can
|
||||
// return some faked response.
|
||||
void
|
||||
setupThrow(AuthSrv* server, const char *config, ThrowWhen throw_when,
|
||||
bool isc_exception)
|
||||
bool isc_exception, ConstRRsetPtr rrset = ConstRRsetPtr())
|
||||
{
|
||||
// Set real inmem client to proxy
|
||||
updateConfig(server, config, true);
|
||||
@@ -1279,8 +1300,7 @@ setupThrow(AuthSrv* server, const char *config, ThrowWhen throw_when,
|
||||
AuthSrv::InMemoryClientPtr fake_client(
|
||||
new FakeInMemoryClient(
|
||||
server->getInMemoryClient(isc::dns::RRClass::IN()),
|
||||
throw_when,
|
||||
isc_exception));
|
||||
throw_when, isc_exception, rrset));
|
||||
|
||||
ASSERT_NE(AuthSrv::InMemoryClientPtr(),
|
||||
server->getInMemoryClient(isc::dns::RRClass::IN()));
|
||||
@@ -1324,4 +1344,45 @@ TEST_F(AuthSrvTest, queryWithInMemoryClientProxyGetClass) {
|
||||
opcode.getCode(), QR_FLAG | AA_FLAG, 1, 1, 2, 1);
|
||||
}
|
||||
|
||||
TEST_F(AuthSrvTest, queryWithThrowingInToWire) {
|
||||
// Set up a faked data source. It will return an empty RRset for the
|
||||
// query.
|
||||
ConstRRsetPtr empty_rrset(new RRset(Name("foo.example"),
|
||||
RRClass::IN(), RRType::TXT(),
|
||||
RRTTL(0)));
|
||||
setupThrow(&server, CONFIG_INMEMORY_EXAMPLE, THROW_NEVER, true,
|
||||
empty_rrset);
|
||||
|
||||
// Repeat the query processing two times. Due to the faked RRset,
|
||||
// toWire() should throw, and it should result in SERVFAIL.
|
||||
OutputBufferPtr orig_buffer;
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
UnitTestUtil::createDNSSECRequestMessage(request_message, opcode,
|
||||
default_qid,
|
||||
Name("foo.example."),
|
||||
RRClass::IN(), RRType::TXT());
|
||||
createRequestPacket(request_message, IPPROTO_UDP);
|
||||
server.processMessage(*io_message, *parse_message, *response_obuffer,
|
||||
&dnsserv);
|
||||
headerCheck(*parse_message, default_qid, Rcode::SERVFAIL(),
|
||||
opcode.getCode(), QR_FLAG, 1, 0, 0, 0);
|
||||
|
||||
// Make a backup of the original buffer for latest tests and replace
|
||||
// it with a new one
|
||||
if (!orig_buffer) {
|
||||
orig_buffer = response_obuffer;
|
||||
response_obuffer.reset(new OutputBuffer(0));
|
||||
}
|
||||
request_message.clear(Message::RENDER);
|
||||
parse_message->clear(Message::PARSE);
|
||||
}
|
||||
|
||||
// Now check if the original buffer is intact
|
||||
parse_message->clear(Message::PARSE);
|
||||
InputBuffer ibuffer(orig_buffer->getData(), orig_buffer->getLength());
|
||||
parse_message->fromWire(ibuffer);
|
||||
headerCheck(*parse_message, default_qid, Rcode::SERVFAIL(),
|
||||
opcode.getCode(), QR_FLAG, 1, 0, 0, 0);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -49,6 +49,7 @@ using namespace isc::dns;
|
||||
using namespace isc::data;
|
||||
using namespace isc::datasrc;
|
||||
using namespace isc::config;
|
||||
using namespace isc::testutils;
|
||||
|
||||
namespace {
|
||||
class AuthCommandTest : public ::testing::Test {
|
||||
|
@@ -37,13 +37,13 @@ using namespace isc::dns;
|
||||
using namespace isc::data;
|
||||
using namespace isc::datasrc;
|
||||
using namespace isc::asiodns;
|
||||
using namespace isc::asiolink;
|
||||
using namespace isc::testutils;
|
||||
|
||||
namespace {
|
||||
class AuthConfigTest : public ::testing::Test {
|
||||
protected:
|
||||
AuthConfigTest() :
|
||||
dnss_(ios_, NULL, NULL, NULL),
|
||||
dnss_(),
|
||||
rrclass(RRClass::IN()),
|
||||
server(true, xfrout),
|
||||
// The empty string is expected value of the parameter of
|
||||
@@ -53,8 +53,7 @@ protected:
|
||||
{
|
||||
server.setDNSService(dnss_);
|
||||
}
|
||||
IOService ios_;
|
||||
DNSService dnss_;
|
||||
MockDNSService dnss_;
|
||||
const RRClass rrclass;
|
||||
MockXfroutClient xfrout;
|
||||
AuthSrv server;
|
||||
@@ -146,6 +145,14 @@ TEST_F(AuthConfigTest, invalidListenAddressConfig) {
|
||||
// Try setting addresses trough config
|
||||
TEST_F(AuthConfigTest, listenAddressConfig) {
|
||||
isc::testutils::portconfig::listenAddressConfig(server);
|
||||
|
||||
// listenAddressConfig should have attempted to create 4 DNS server
|
||||
// objects: two IP addresses, TCP and UDP for each. For UDP, the "SYNC_OK"
|
||||
// option should have been specified.
|
||||
EXPECT_EQ(2, dnss_.getTCPFdParams().size());
|
||||
EXPECT_EQ(2, dnss_.getUDPFdParams().size());
|
||||
EXPECT_EQ(DNSService::SERVER_SYNC_OK, dnss_.getUDPFdParams().at(0).options);
|
||||
EXPECT_EQ(DNSService::SERVER_SYNC_OK, dnss_.getUDPFdParams().at(1).options);
|
||||
}
|
||||
|
||||
class MemoryDatasrcConfigTest : public AuthConfigTest {
|
||||
|
@@ -281,6 +281,8 @@ TEST_F(AuthCountersTest, submitStatisticsWithoutValidator) {
|
||||
->get(0)->stringValue());
|
||||
EXPECT_EQ("Auth", statistics_session_.sent_msg->get("command")
|
||||
->get(1)->get("owner")->stringValue());
|
||||
EXPECT_EQ(statistics_session_.sent_msg->get("command")
|
||||
->get(1)->get("pid")->intValue(), getpid());
|
||||
ConstElementPtr statistics_data = statistics_session_.sent_msg
|
||||
->get("command")->get(1)
|
||||
->get("data");
|
||||
|
@@ -92,7 +92,7 @@ public:
|
||||
queryShutdown();
|
||||
}
|
||||
|
||||
void querySetup(DNSService& dnss,
|
||||
void querySetup(DNSServiceBase& dnss,
|
||||
isc::nsas::NameserverAddressStore& nsas,
|
||||
isc::cache::ResolverCache& cache)
|
||||
{
|
||||
@@ -121,10 +121,10 @@ public:
|
||||
}
|
||||
|
||||
void setForwardAddresses(const AddressList& upstream,
|
||||
DNSService *dnss)
|
||||
DNSServiceBase* dnss)
|
||||
{
|
||||
upstream_ = upstream;
|
||||
if (dnss) {
|
||||
if (dnss != NULL) {
|
||||
if (!upstream_.empty()) {
|
||||
BOOST_FOREACH(const AddressPair& address, upstream) {
|
||||
LOG_INFO(resolver_logger, RESOLVER_FORWARD_ADDRESS)
|
||||
@@ -137,10 +137,10 @@ public:
|
||||
}
|
||||
|
||||
void setRootAddresses(const AddressList& upstream_root,
|
||||
DNSService *dnss)
|
||||
DNSServiceBase* dnss)
|
||||
{
|
||||
upstream_root_ = upstream_root;
|
||||
if (dnss) {
|
||||
if (dnss != NULL) {
|
||||
if (!upstream_root_.empty()) {
|
||||
BOOST_FOREACH(const AddressPair& address, upstream_root) {
|
||||
LOG_INFO(resolver_logger, RESOLVER_SET_ROOT_ADDRESS)
|
||||
@@ -377,7 +377,7 @@ Resolver::~Resolver() {
|
||||
}
|
||||
|
||||
void
|
||||
Resolver::setDNSService(isc::asiodns::DNSService& dnss) {
|
||||
Resolver::setDNSService(isc::asiodns::DNSServiceBase& dnss) {
|
||||
dnss_ = &dnss;
|
||||
}
|
||||
|
||||
|
@@ -104,7 +104,7 @@ public:
|
||||
bool startup = false);
|
||||
|
||||
/// \brief Assign an ASIO IO Service queue to this Resolver object
|
||||
void setDNSService(isc::asiodns::DNSService& dnss);
|
||||
void setDNSService(isc::asiodns::DNSServiceBase& dnss);
|
||||
|
||||
/// \brief Assign a NameserverAddressStore to this Resolver object
|
||||
void setNameserverAddressStore(isc::nsas::NameserverAddressStore &nsas);
|
||||
@@ -113,7 +113,7 @@ public:
|
||||
void setCache(isc::cache::ResolverCache& cache);
|
||||
|
||||
/// \brief Return this object's ASIO IO Service queue
|
||||
isc::asiodns::DNSService& getDNSService() const { return (*dnss_); }
|
||||
isc::asiodns::DNSServiceBase& getDNSService() const { return (*dnss_); }
|
||||
|
||||
/// \brief Returns this object's NSAS
|
||||
isc::nsas::NameserverAddressStore& getNameserverAddressStore() const {
|
||||
@@ -258,7 +258,7 @@ public:
|
||||
|
||||
private:
|
||||
ResolverImpl* impl_;
|
||||
isc::asiodns::DNSService* dnss_;
|
||||
isc::asiodns::DNSServiceBase* dnss_;
|
||||
isc::asiolink::SimpleCallback* checkin_;
|
||||
isc::asiodns::DNSLookup* dns_lookup_;
|
||||
isc::asiodns::DNSAnswer* dns_answer_;
|
||||
|
@@ -48,6 +48,7 @@
|
||||
|
||||
#include <dns/tests/unittest_util.h>
|
||||
#include <testutils/srv_test.h>
|
||||
#include <testutils/mockups.h>
|
||||
#include <testutils/portconfig.h>
|
||||
#include <testutils/socket_request.h>
|
||||
|
||||
@@ -76,15 +77,13 @@ public:
|
||||
|
||||
class ResolverConfig : public ::testing::Test {
|
||||
protected:
|
||||
IOService ios;
|
||||
DNSService dnss;
|
||||
MockDNSService dnss;
|
||||
Resolver server;
|
||||
scoped_ptr<const IOEndpoint> endpoint;
|
||||
scoped_ptr<const IOMessage> query_message;
|
||||
scoped_ptr<const Client> client;
|
||||
scoped_ptr<const RequestContext> request;
|
||||
ResolverConfig() :
|
||||
dnss(ios, NULL, NULL, NULL),
|
||||
// The empty string is expected value of the parameter of
|
||||
// requestSocket, not the app_name (there's no fallback, it checks
|
||||
// the empty string is passed).
|
||||
@@ -320,6 +319,15 @@ TEST_F(ResolverConfig, invalidForwardAddresses) {
|
||||
// Try setting the addresses directly
|
||||
TEST_F(ResolverConfig, listenAddresses) {
|
||||
isc::testutils::portconfig::listenAddresses(server);
|
||||
|
||||
// listenAddressConfig should have attempted to create 4 DNS server
|
||||
// objects: two IP addresses, TCP and UDP for each. For UDP, the "SYNC_OK"
|
||||
// option (or anything else) should have NOT been specified.
|
||||
EXPECT_EQ(2, dnss.getTCPFdParams().size());
|
||||
EXPECT_EQ(2, dnss.getUDPFdParams().size());
|
||||
EXPECT_EQ(DNSService::SERVER_DEFAULT, dnss.getUDPFdParams().at(0).options);
|
||||
EXPECT_EQ(DNSService::SERVER_DEFAULT, dnss.getUDPFdParams().at(1).options);
|
||||
|
||||
// Check it requests the correct addresses
|
||||
const char* tokens[] = {
|
||||
"TCP:127.0.0.1:53210:1",
|
||||
|
@@ -59,23 +59,19 @@ command does not have any configurable settings\&.
|
||||
The configuration commands are:
|
||||
.PP
|
||||
|
||||
|
||||
\fBremove\fR
|
||||
removes the named statistics name and data\&.
|
||||
.PP
|
||||
|
||||
|
||||
\fBreset\fR
|
||||
will reset all statistics data to default values except for constant names\&. This may re\-add previously removed statistics names\&.
|
||||
.PP
|
||||
|
||||
\fBset\fR
|
||||
will set new statistics data specified in arguments\&. Statistics data to be set and the module name which owns statistics data are required in argument\&. Pid of the module in argument is optional\&.
|
||||
.PP
|
||||
|
||||
\fBshow\fR
|
||||
will send the statistics data in JSON format\&. By default, it outputs all the statistics data it has collected\&. An optional item name may be specified to receive individual output\&.
|
||||
.PP
|
||||
|
||||
\fBshowschema\fR
|
||||
will send the schema of the statistics data in JSON format\&. The output is equivalent to the statistics part of
|
||||
stats\&.spec\&.
|
||||
.PP
|
||||
|
||||
\fBshutdown\fR
|
||||
will shutdown the
|
||||
\fBb10\-stats\fR
|
||||
|
@@ -101,20 +101,10 @@
|
||||
</para>
|
||||
|
||||
<para>
|
||||
<!-- TODO: remove is removed in trac930 -->
|
||||
<command>remove</command> removes the named statistics name and data.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
<!-- TODO: reset is removed in trac930 -->
|
||||
<command>reset</command> will reset all statistics data to
|
||||
default values except for constant names.
|
||||
This may re-add previously removed statistics names.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
<command>set</command>
|
||||
<!-- TODO: document this -->
|
||||
<command>set</command> will set new statistics data specified in
|
||||
arguments. Statistics data to be set and the module name which owns
|
||||
statistics data are required in argument. Pid of the module in argument
|
||||
is optional.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
@@ -124,7 +114,11 @@
|
||||
An optional item name may be specified to receive individual output.
|
||||
</para>
|
||||
|
||||
<!-- TODO: document showschema -->
|
||||
<para>
|
||||
<command>showschema</command> will send the schema of the statistics data
|
||||
in JSON format. The output is equivalent to the statistics part
|
||||
of <filename>stats.spec</filename>.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
<command>shutdown</command> will shutdown the
|
||||
|
@@ -129,6 +129,8 @@ class Stats:
|
||||
self.module_name = self.mccs.get_module_spec().get_module_name()
|
||||
self.modules = {}
|
||||
self.statistics_data = {}
|
||||
# statistics data by each pid
|
||||
self.statistics_data_bypid = {}
|
||||
# get commands spec
|
||||
self.commands_spec = self.mccs.get_module_spec().get_commands_spec()
|
||||
# add event handler related command_handler of ModuleCCSession
|
||||
@@ -265,36 +267,129 @@ class Stats:
|
||||
+ "owner: " + str(owner) + ", "
|
||||
+ "name: " + str(name))
|
||||
|
||||
def update_statistics_data(self, owner=None, **data):
|
||||
def update_statistics_data(self, owner=None, pid=-1, **data):
|
||||
"""
|
||||
change statistics date of specified module into specified
|
||||
data. It updates information of each module first, and it
|
||||
updates statistics data. If specified data is invalid for
|
||||
statistics spec of specified owner, it returns a list of error
|
||||
messeges. If there is no error or if neither owner nor data is
|
||||
specified in args, it returns None.
|
||||
messages. If there is no error or if neither owner nor data is
|
||||
specified in args, it returns None. pid is the process id of
|
||||
the sender module in order for stats to identify which
|
||||
instance sends statistics data in the situation that multiple
|
||||
instances are working.
|
||||
"""
|
||||
# Note:
|
||||
# The fix of #1751 is for multiple instances working. It is
|
||||
# assumed here that they send different statistics data with
|
||||
# each PID. Stats should save their statistics data by
|
||||
# PID. The statistics data, which is the existing variable, is
|
||||
# preserved by accumlating from statistics data by PID. This
|
||||
# is an ad-hoc fix because administrators can not see
|
||||
# statistics by each instance via bindctl or HTTP/XML. These
|
||||
# interfaces aren't changed in this fix.
|
||||
|
||||
def _accum_bymodule(statistics_data_bypid):
|
||||
# This is an internal function for the superordinate
|
||||
# function. It accumulates statistics data of each PID by
|
||||
# module. It returns the accumulation result.
|
||||
def _accum(a, b):
|
||||
# If the first arg is dict or list type, two values
|
||||
# would be merged and accumlated.
|
||||
if type(a) is dict:
|
||||
return dict([ (k, _accum(v, b[k])) \
|
||||
if k in b else (k, v) \
|
||||
for (k, v) in a.items() ] \
|
||||
+ [ (k, v) \
|
||||
for (k, v) in b.items() \
|
||||
if k not in a ])
|
||||
elif type(a) is list:
|
||||
return [ _accum(a[i], b[i]) \
|
||||
if len(b) > i else a[i] \
|
||||
for i in range(len(a)) ] \
|
||||
+ [ b[i] \
|
||||
for i in range(len(b)) \
|
||||
if len(a) <= i ]
|
||||
# If the first arg is integer or float type, two
|
||||
# values are just added.
|
||||
elif type(a) is int or type(a) is float:
|
||||
return a + b
|
||||
# If the first arg is str or other types than above,
|
||||
# then it just returns the first arg which is assumed
|
||||
# to be the newer value.
|
||||
return a
|
||||
ret = {}
|
||||
for data in statistics_data_bypid.values():
|
||||
ret.update(_accum(data, ret))
|
||||
return ret
|
||||
|
||||
# Firstly, it gets default statistics data in each spec file.
|
||||
self.update_modules()
|
||||
statistics_data = {}
|
||||
for (name, module) in self.modules.items():
|
||||
value = get_spec_defaults(module.get_statistics_spec())
|
||||
if module.validate_statistics(True, value):
|
||||
statistics_data[name] = value
|
||||
for (name, value) in self.statistics_data.items():
|
||||
if name in statistics_data:
|
||||
statistics_data[name].update(value)
|
||||
else:
|
||||
statistics_data[name] = value
|
||||
self.statistics_data = statistics_data
|
||||
|
||||
# If the "owner" and "data" arguments in this function are
|
||||
# specified, then the variable of statistics data of each pid
|
||||
# would be updated.
|
||||
errors = []
|
||||
if owner and data:
|
||||
errors = []
|
||||
try:
|
||||
if self.modules[owner].validate_statistics(False, data, errors):
|
||||
self.statistics_data[owner].update(data)
|
||||
return
|
||||
if owner in self.statistics_data_bypid:
|
||||
if pid in self.statistics_data_bypid[owner]:
|
||||
self.statistics_data_bypid[owner][pid].update(data)
|
||||
else:
|
||||
self.statistics_data_bypid[owner][pid] = data
|
||||
else:
|
||||
self.statistics_data_bypid[owner] = { pid : data }
|
||||
except KeyError:
|
||||
errors.append("unknown module name: " + str(owner))
|
||||
return errors
|
||||
|
||||
# If there are inactive instances, which was actually running
|
||||
# on the system before, their statistics data would be
|
||||
# removed. To find inactive instances, it invokes the
|
||||
# "show_processes" command to Boss via the cc session. Then it
|
||||
# gets active instance list and compares its PIDs with PIDs in
|
||||
# statistics data which it already has. If inactive instances
|
||||
# are found, it would remove their statistics data.
|
||||
seq = self.cc_session.group_sendmsg(
|
||||
isc.config.ccsession.create_command("show_processes", None),
|
||||
"Boss")
|
||||
(answer, env) = self.cc_session.group_recvmsg(False, seq)
|
||||
if answer:
|
||||
(rcode, value) = isc.config.ccsession.parse_answer(answer)
|
||||
if rcode == 0:
|
||||
if type(value) is list and len(value) > 0 \
|
||||
and type(value[0]) is list and len(value[0]) > 1:
|
||||
mlist = [ k for k in self.statistics_data_bypid.keys() ]
|
||||
for m in mlist:
|
||||
# PID list which it has before except for -1
|
||||
plist1 = [ p for p in self.statistics_data_bypid[m]\
|
||||
.keys() if p != -1]
|
||||
# PID list of active instances which is
|
||||
# received from Boss
|
||||
plist2 = [ v[0] for v in value \
|
||||
if v[1].lower().find(m.lower()) \
|
||||
>= 0 ]
|
||||
# get inactive instance list by the difference
|
||||
# between plist1 and plist2
|
||||
nplist = set(plist1).difference(set(plist2))
|
||||
for p in nplist:
|
||||
self.statistics_data_bypid[m].pop(p)
|
||||
if self.statistics_data_bypid[m]:
|
||||
if m in self.statistics_data:
|
||||
self.statistics_data[m].update(
|
||||
_accum_bymodule(
|
||||
self.statistics_data_bypid[m]))
|
||||
# remove statistics data of the module with no
|
||||
# PID
|
||||
else:
|
||||
self.statistics_data_bypid.pop(m)
|
||||
if errors: return errors
|
||||
|
||||
def command_status(self):
|
||||
"""
|
||||
@@ -379,11 +474,11 @@ class Stats:
|
||||
1, "specified arguments are incorrect: " \
|
||||
+ "owner: " + str(owner) + ", name: " + str(name))
|
||||
|
||||
def command_set(self, owner, data):
|
||||
def command_set(self, owner, pid=-1, data={}):
|
||||
"""
|
||||
handle set command
|
||||
"""
|
||||
errors = self.update_statistics_data(owner, **data)
|
||||
errors = self.update_statistics_data(owner, pid, **data)
|
||||
if errors:
|
||||
return isc.config.create_answer(
|
||||
1, "errors while setting statistics data: " \
|
||||
|
@@ -71,6 +71,13 @@
|
||||
"item_default": "",
|
||||
"item_description": "module name of the owner of the statistics data"
|
||||
},
|
||||
{
|
||||
"item_name": "pid",
|
||||
"item_type": "integer",
|
||||
"item_optional": true,
|
||||
"item_default": -1,
|
||||
"item_description": "process id of the owner module"
|
||||
},
|
||||
{
|
||||
"item_name": "data",
|
||||
"item_type": "map",
|
||||
|
@@ -373,6 +373,68 @@ class TestStats(unittest.TestCase):
|
||||
self.assertEqual(self.stats.update_statistics_data(owner='Dummy', foo='bar'),
|
||||
['unknown module name: Dummy'])
|
||||
|
||||
def test_update_statistics_data_withpid(self):
|
||||
# one pid of Auth
|
||||
self.stats.update_statistics_data(owner='Auth',
|
||||
pid=9999,
|
||||
**{'queries.tcp':1001})
|
||||
self.assertTrue('Auth' in self.stats.statistics_data)
|
||||
self.assertTrue('queries.tcp' in self.stats.statistics_data['Auth'])
|
||||
self.assertEqual(self.stats.statistics_data['Auth']['queries.tcp'], 1001)
|
||||
self.assertTrue('Auth' in self.stats.statistics_data_bypid)
|
||||
self.assertTrue(9999 in self.stats.statistics_data_bypid['Auth'])
|
||||
self.assertTrue('queries.tcp' in self.stats.statistics_data_bypid['Auth'][9999])
|
||||
self.assertEqual(self.stats.statistics_data_bypid['Auth'][9999]['queries.tcp'], 1001)
|
||||
self.assertEqual(self.stats.statistics_data_bypid,
|
||||
{'Auth': {9999: {'queries.tcp': 1001}}})
|
||||
# non-existent pid of Auth, but no changes in statistics data
|
||||
self.stats.update_statistics_data(owner='Auth',
|
||||
pid=10000,
|
||||
**{'queries.tcp':2001})
|
||||
self.assertTrue('Auth' in self.stats.statistics_data)
|
||||
self.assertTrue('queries.tcp' in self.stats.statistics_data['Auth'])
|
||||
self.assertEqual(self.stats.statistics_data['Auth']['queries.tcp'], 1001)
|
||||
self.assertTrue('Auth' in self.stats.statistics_data_bypid)
|
||||
self.assertTrue(9999 in self.stats.statistics_data_bypid['Auth'])
|
||||
self.assertTrue('queries.tcp' in self.stats.statistics_data_bypid['Auth'][9999])
|
||||
self.assertEqual(self.stats.statistics_data_bypid['Auth'][9999]['queries.tcp'], 1001)
|
||||
self.assertEqual(self.stats.statistics_data_bypid,
|
||||
{'Auth': {9999: {'queries.tcp': 1001}}})
|
||||
# kill running Auth, then statistics is reset
|
||||
self.assertEqual(self.base.boss.server.pid_list[0][0], 9999)
|
||||
killed = self.base.boss.server.pid_list.pop(0)
|
||||
self.stats.update_statistics_data()
|
||||
self.assertTrue('Auth' in self.stats.statistics_data)
|
||||
self.assertTrue('queries.tcp' in self.stats.statistics_data['Auth'])
|
||||
self.assertTrue('queries.udp' in self.stats.statistics_data['Auth'])
|
||||
self.assertEqual(self.stats.statistics_data['Auth']['queries.tcp'], 0)
|
||||
self.assertEqual(self.stats.statistics_data['Auth']['queries.udp'], 0)
|
||||
self.assertFalse('Auth' in self.stats.statistics_data_bypid)
|
||||
# restore statistics data of killed auth
|
||||
self.base.boss.server.pid_list = [ killed ] + self.base.boss.server.pid_list[:]
|
||||
self.stats.update_statistics_data(owner='Auth',
|
||||
pid=9999,
|
||||
**{'queries.tcp':1001})
|
||||
# another pid of Auth
|
||||
self.stats.update_statistics_data(owner='Auth',
|
||||
pid=9998,
|
||||
**{'queries.tcp':1002,
|
||||
'queries.udp':1003})
|
||||
self.assertTrue('Auth' in self.stats.statistics_data)
|
||||
self.assertTrue('queries.tcp' in self.stats.statistics_data['Auth'])
|
||||
self.assertTrue('queries.udp' in self.stats.statistics_data['Auth'])
|
||||
self.assertEqual(self.stats.statistics_data['Auth']['queries.tcp'], 2003)
|
||||
self.assertEqual(self.stats.statistics_data['Auth']['queries.udp'], 1003)
|
||||
self.assertTrue('Auth' in self.stats.statistics_data_bypid)
|
||||
self.assertTrue(9999 in self.stats.statistics_data_bypid['Auth'])
|
||||
self.assertTrue(9998 in self.stats.statistics_data_bypid['Auth'])
|
||||
self.assertTrue('queries.tcp' in self.stats.statistics_data_bypid['Auth'][9999])
|
||||
self.assertTrue('queries.udp' in self.stats.statistics_data_bypid['Auth'][9998])
|
||||
self.assertTrue('queries.udp' in self.stats.statistics_data_bypid['Auth'][9998])
|
||||
self.assertEqual(self.stats.statistics_data_bypid['Auth'][9999]['queries.tcp'], 1001)
|
||||
self.assertEqual(self.stats.statistics_data_bypid['Auth'][9998]['queries.tcp'], 1002)
|
||||
self.assertEqual(self.stats.statistics_data_bypid['Auth'][9998]['queries.udp'], 1003)
|
||||
|
||||
def test_commands(self):
|
||||
# status
|
||||
self.assertEqual(self.stats.command_status(),
|
||||
@@ -710,6 +772,123 @@ class TestStats(unittest.TestCase):
|
||||
self.assertRaises(stats.StatsError,
|
||||
self.stats.command_set, owner='Stats', data={ 'dummy' : '_xxxx_yyyy_zzz_' })
|
||||
|
||||
def test_command_set_withpid(self):
|
||||
# one pid of Auth
|
||||
retval = isc.config.ccsession.parse_answer(
|
||||
self.stats.command_set(owner='Auth',
|
||||
pid=9997,
|
||||
data={ 'queries.tcp' : 1001,
|
||||
'queries.perzone':
|
||||
[{ 'zonename': 'test1.example',
|
||||
'queries.tcp': 1 },
|
||||
{ 'zonename': 'test2.example',
|
||||
'queries.tcp': 2,
|
||||
'queries.udp': 3 }]}))
|
||||
self.assertEqual(retval, (0,None))
|
||||
self.assertTrue('Auth' in self.stats.statistics_data)
|
||||
self.assertTrue('queries.tcp' in self.stats.statistics_data['Auth'])
|
||||
self.assertEqual(self.stats.statistics_data['Auth']['queries.tcp'], 1001)
|
||||
self.assertEqual(self.stats.statistics_data['Auth']['queries.perzone'],
|
||||
[{ 'zonename': 'test1.example',
|
||||
'queries.tcp': 1 },
|
||||
{ 'zonename': 'test2.example',
|
||||
'queries.tcp': 2,
|
||||
'queries.udp': 3 }])
|
||||
self.assertTrue('Stats' in self.stats.statistics_data)
|
||||
self.assertTrue('last_update_time' in self.stats.statistics_data['Stats'])
|
||||
self.assertTrue('Auth' in self.stats.statistics_data_bypid)
|
||||
self.assertTrue(9997 in self.stats.statistics_data_bypid['Auth'])
|
||||
self.assertTrue('queries.tcp' in self.stats.statistics_data_bypid['Auth'][9997])
|
||||
self.assertTrue('queries.perzone' in self.stats.statistics_data_bypid['Auth'][9997])
|
||||
self.assertEqual(self.stats.statistics_data_bypid['Auth'][9997]['queries.tcp'], 1001)
|
||||
self.assertEqual(self.stats.statistics_data_bypid['Auth'][9997]['queries.perzone'],
|
||||
[{ 'zonename': 'test1.example',
|
||||
'queries.tcp': 1 },
|
||||
{ 'zonename': 'test2.example',
|
||||
'queries.tcp': 2,
|
||||
'queries.udp': 3 }])
|
||||
# non-existent pid of Auth, but no changes in statistics data
|
||||
retval = isc.config.ccsession.parse_answer(
|
||||
self.stats.command_set(owner='Auth',
|
||||
pid=10000,
|
||||
data={ 'queries.tcp' : 2001,
|
||||
'queries.perzone':
|
||||
[{ 'zonename': 'test1.example',
|
||||
'queries.tcp': 101 },
|
||||
{ 'zonename': 'test2.example',
|
||||
'queries.tcp': 102,
|
||||
'queries.udp': 103 }]}))
|
||||
self.assertEqual(retval, (0,None))
|
||||
self.assertTrue('Auth' in self.stats.statistics_data)
|
||||
self.assertTrue('queries.tcp' in self.stats.statistics_data['Auth'])
|
||||
self.assertEqual(self.stats.statistics_data['Auth']['queries.tcp'], 1001)
|
||||
self.assertEqual(self.stats.statistics_data['Auth']['queries.perzone'],
|
||||
[{ 'zonename': 'test1.example',
|
||||
'queries.tcp': 1 },
|
||||
{ 'zonename': 'test2.example',
|
||||
'queries.tcp': 2,
|
||||
'queries.udp': 3 }])
|
||||
self.assertTrue('Auth' in self.stats.statistics_data_bypid)
|
||||
self.assertTrue(9997 in self.stats.statistics_data_bypid['Auth'])
|
||||
self.assertTrue('queries.tcp' in self.stats.statistics_data_bypid['Auth'][9997])
|
||||
self.assertEqual(self.stats.statistics_data_bypid['Auth'][9997]['queries.tcp'], 1001)
|
||||
self.assertEqual(self.stats.statistics_data_bypid['Auth'][9997]['queries.perzone'],
|
||||
[{ 'zonename': 'test1.example',
|
||||
'queries.tcp': 1 },
|
||||
{ 'zonename': 'test2.example',
|
||||
'queries.tcp': 2,
|
||||
'queries.udp': 3 }])
|
||||
# another pid of Auth
|
||||
retval = isc.config.ccsession.parse_answer(
|
||||
self.stats.command_set(owner='Auth',
|
||||
pid=9996,
|
||||
data={ 'queries.tcp' : 1002,
|
||||
'queries.udp' : 1003,
|
||||
'queries.perzone':
|
||||
[{ 'zonename': 'test1.example',
|
||||
'queries.tcp': 10,
|
||||
'queries.udp': 11},
|
||||
{ 'zonename': 'test2.example',
|
||||
'queries.tcp': 12,
|
||||
'queries.udp': 13 }]}))
|
||||
self.assertEqual(retval, (0,None))
|
||||
self.assertTrue('Auth' in self.stats.statistics_data)
|
||||
self.assertTrue('queries.tcp' in self.stats.statistics_data['Auth'])
|
||||
self.assertTrue('queries.udp' in self.stats.statistics_data['Auth'])
|
||||
self.assertTrue('queries.perzone' in self.stats.statistics_data['Auth'])
|
||||
self.assertEqual(self.stats.statistics_data['Auth']['queries.tcp'], 2003)
|
||||
self.assertEqual(self.stats.statistics_data['Auth']['queries.udp'], 1003)
|
||||
self.assertEqual(self.stats.statistics_data['Auth']['queries.perzone'],
|
||||
[{ 'zonename': 'test1.example',
|
||||
'queries.tcp': 11,
|
||||
'queries.udp': 11},
|
||||
{ 'zonename': 'test2.example',
|
||||
'queries.tcp': 14,
|
||||
'queries.udp': 16 }])
|
||||
self.assertTrue('Auth' in self.stats.statistics_data_bypid)
|
||||
self.assertTrue(9997 in self.stats.statistics_data_bypid['Auth'])
|
||||
self.assertTrue(9996 in self.stats.statistics_data_bypid['Auth'])
|
||||
self.assertTrue('queries.tcp' in self.stats.statistics_data_bypid['Auth'][9997])
|
||||
self.assertTrue('queries.udp' in self.stats.statistics_data_bypid['Auth'][9996])
|
||||
self.assertTrue('queries.udp' in self.stats.statistics_data_bypid['Auth'][9996])
|
||||
self.assertTrue('queries.perzone' in self.stats.statistics_data_bypid['Auth'][9996])
|
||||
self.assertEqual(self.stats.statistics_data_bypid['Auth'][9997]['queries.tcp'], 1001)
|
||||
self.assertEqual(self.stats.statistics_data_bypid['Auth'][9997]['queries.perzone'],
|
||||
[{ 'zonename': 'test1.example',
|
||||
'queries.tcp': 1 },
|
||||
{ 'zonename': 'test2.example',
|
||||
'queries.tcp': 2,
|
||||
'queries.udp': 3 }])
|
||||
self.assertEqual(self.stats.statistics_data_bypid['Auth'][9996]['queries.tcp'], 1002)
|
||||
self.assertEqual(self.stats.statistics_data_bypid['Auth'][9996]['queries.udp'], 1003)
|
||||
self.assertEqual(self.stats.statistics_data_bypid['Auth'][9996]['queries.perzone'],
|
||||
[{ 'zonename': 'test1.example',
|
||||
'queries.tcp': 10,
|
||||
'queries.udp': 11},
|
||||
{ 'zonename': 'test2.example',
|
||||
'queries.tcp': 12,
|
||||
'queries.udp': 13 }])
|
||||
|
||||
class TestOSEnv(unittest.TestCase):
|
||||
def test_osenv(self):
|
||||
"""
|
||||
|
@@ -150,6 +150,11 @@ class MockBoss:
|
||||
"command_name": "sendstats",
|
||||
"command_description": "Send data to a statistics module at once",
|
||||
"command_args": []
|
||||
},
|
||||
{
|
||||
"command_name": "show_processes",
|
||||
"command_description": "List the running BIND 10 processes",
|
||||
"command_args": []
|
||||
}
|
||||
],
|
||||
"statistics": [
|
||||
@@ -180,6 +185,10 @@ class MockBoss:
|
||||
self.spec_file.close()
|
||||
self.cc_session = self.mccs._session
|
||||
self.got_command_name = ''
|
||||
self.pid_list = [[ 9999, "b10-auth" ],
|
||||
[ 9998, "b10-auth-2" ],
|
||||
[ 9997, "b10-auth-3" ],
|
||||
[ 9996, "b10-auth-4" ]]
|
||||
|
||||
def run(self):
|
||||
self.mccs.start()
|
||||
@@ -210,6 +219,10 @@ class MockBoss:
|
||||
return isc.config.create_answer(0)
|
||||
elif command == 'getstats':
|
||||
return isc.config.create_answer(0, params)
|
||||
elif command == 'show_processes':
|
||||
# Return dummy pids
|
||||
return isc.config.create_answer(
|
||||
0, self.pid_list)
|
||||
return isc.config.create_answer(1, "Unknown Command")
|
||||
|
||||
class MockAuth:
|
||||
@@ -340,7 +353,7 @@ class MockAuth:
|
||||
params = { "owner": "Auth",
|
||||
"data": { 'queries.tcp': self.queries_tcp,
|
||||
'queries.udp': self.queries_udp,
|
||||
'queries.per-zone' : self.queries_per_zone } }
|
||||
'queries.perzone' : self.queries_per_zone } }
|
||||
return send_command("set", "Stats", params=params, session=self.cc_session)
|
||||
return isc.config.create_answer(1, "Unknown Command")
|
||||
|
||||
|
@@ -22,6 +22,8 @@
|
||||
|
||||
#include <log/dummylog.h>
|
||||
|
||||
#include <exceptions/exceptions.h>
|
||||
|
||||
#include <asio.hpp>
|
||||
#include <dns_service.h>
|
||||
#include <asiolink/io_service.h>
|
||||
@@ -63,11 +65,10 @@ convertAddr(const std::string& address) {
|
||||
class DNSServiceImpl {
|
||||
public:
|
||||
DNSServiceImpl(IOService& io_service, const char& port,
|
||||
const asio::ip::address* v4addr,
|
||||
const asio::ip::address* v6addr,
|
||||
SimpleCallback* checkin, DNSLookup* lookup,
|
||||
DNSAnswer* answer,
|
||||
const UDPVersion param_flags);
|
||||
const asio::ip::address* v4addr,
|
||||
const asio::ip::address* v6addr,
|
||||
SimpleCallback* checkin, DNSLookup* lookup,
|
||||
DNSAnswer* answe);
|
||||
|
||||
IOService& io_service_;
|
||||
|
||||
@@ -87,38 +88,24 @@ public:
|
||||
servers_.push_back(server);
|
||||
}
|
||||
|
||||
void addServer(uint16_t port, const asio::ip::address& address,
|
||||
const UDPVersion param_flags) {
|
||||
void addServer(uint16_t port, const asio::ip::address& address) {
|
||||
try {
|
||||
dlog(std::string("Initialize TCP server at ") + address.to_string() + ":" + boost::lexical_cast<std::string>(port));
|
||||
dlog(std::string("Initialize TCP server at ") +
|
||||
address.to_string() + ":" +
|
||||
boost::lexical_cast<std::string>(port));
|
||||
TCPServerPtr tcpServer(new TCPServer(io_service_.get_io_service(),
|
||||
address, port, checkin_, lookup_, answer_));
|
||||
address, port, checkin_,
|
||||
lookup_, answer_));
|
||||
(*tcpServer)();
|
||||
servers_.push_back(tcpServer);
|
||||
dlog(std::string("Initialize UDP server at ") + address.to_string() + ":" + boost::lexical_cast<std::string>(port));
|
||||
// Use param_flags to generate diff UDPServers.
|
||||
switch(param_flags) {
|
||||
case SYNC_: {
|
||||
SyncUDPServerPtr syncUdpServer(new SyncUDPServer(io_service_.get_io_service(),
|
||||
address, port, checkin_, lookup_, answer_));
|
||||
(*syncUdpServer)();
|
||||
servers_.push_back(syncUdpServer);
|
||||
break;
|
||||
}
|
||||
case ASYNC_: {
|
||||
UDPServerPtr udpServer(new UDPServer(io_service_.get_io_service(),
|
||||
address, port, checkin_, lookup_, answer_));
|
||||
(*udpServer)();
|
||||
servers_.push_back(udpServer);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
// If nerther asyn UDPServer nor sync UDNServer, it throws.
|
||||
isc_throw(IOError, "Bad UDPServer Version!");
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (const asio::system_error& err) {
|
||||
dlog(std::string("Initialize UDP server at ") +
|
||||
address.to_string() + ":" +
|
||||
boost::lexical_cast<std::string>(port));
|
||||
UDPServerPtr udpServer(new UDPServer(io_service_.get_io_service(),
|
||||
address, port, checkin_, lookup_, answer_));
|
||||
(*udpServer)();
|
||||
servers_.push_back(udpServer);
|
||||
} catch (const asio::system_error& err) {
|
||||
// We need to catch and convert any ASIO level exceptions.
|
||||
// This can happen for unavailable address, binding a privilege port
|
||||
// without the privilege, etc.
|
||||
@@ -126,8 +113,7 @@ public:
|
||||
err.what());
|
||||
}
|
||||
}
|
||||
void addServer(const char& port, const asio::ip::address& address,
|
||||
const UDPVersion param_flags) {
|
||||
void addServer(const char& port, const asio::ip::address& address) {
|
||||
uint16_t portnum;
|
||||
try {
|
||||
// XXX: SunStudio with stlport4 doesn't reject some invalid
|
||||
@@ -143,7 +129,7 @@ public:
|
||||
isc_throw(IOError, "Invalid port number '" << &port << "': " <<
|
||||
ex.what());
|
||||
}
|
||||
addServer(portnum, address,param_flags);
|
||||
addServer(portnum, address);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -153,8 +139,7 @@ DNSServiceImpl::DNSServiceImpl(IOService& io_service,
|
||||
const asio::ip::address* const v6addr,
|
||||
SimpleCallback* checkin,
|
||||
DNSLookup* lookup,
|
||||
DNSAnswer* answer,
|
||||
const UDPVersion param_flags):
|
||||
DNSAnswer* answer) :
|
||||
io_service_(io_service),
|
||||
checkin_(checkin),
|
||||
lookup_(lookup),
|
||||
@@ -162,10 +147,10 @@ DNSServiceImpl::DNSServiceImpl(IOService& io_service,
|
||||
{
|
||||
|
||||
if (v4addr) {
|
||||
addServer(port, *v4addr,param_flags);
|
||||
addServer(port, *v4addr);
|
||||
}
|
||||
if (v6addr) {
|
||||
addServer(port, *v6addr,param_flags);
|
||||
addServer(port, *v6addr);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,12 +158,12 @@ DNSService::DNSService(IOService& io_service,
|
||||
const char& port, const char& address,
|
||||
SimpleCallback* checkin,
|
||||
DNSLookup* lookup,
|
||||
DNSAnswer* answer,
|
||||
const UDPVersion param_flags) :
|
||||
DNSAnswer* answer) :
|
||||
impl_(new DNSServiceImpl(io_service, port, NULL, NULL, checkin, lookup,
|
||||
answer,param_flags)), io_service_(io_service)
|
||||
answer)),
|
||||
io_service_(io_service)
|
||||
{
|
||||
addServer(port, &address,param_flags);
|
||||
addServer(port, &address);
|
||||
}
|
||||
|
||||
DNSService::DNSService(IOService& io_service,
|
||||
@@ -186,8 +171,7 @@ DNSService::DNSService(IOService& io_service,
|
||||
const bool use_ipv4, const bool use_ipv6,
|
||||
SimpleCallback* checkin,
|
||||
DNSLookup* lookup,
|
||||
DNSAnswer* answer,
|
||||
const UDPVersion param_flags) :
|
||||
DNSAnswer* answer) :
|
||||
impl_(NULL), io_service_(io_service)
|
||||
{
|
||||
const asio::ip::address v4addr_any =
|
||||
@@ -196,13 +180,14 @@ DNSService::DNSService(IOService& io_service,
|
||||
const asio::ip::address v6addr_any =
|
||||
asio::ip::address(asio::ip::address_v6::any());
|
||||
const asio::ip::address* const v6addrp = use_ipv6 ? &v6addr_any : NULL;
|
||||
impl_ = new DNSServiceImpl(io_service, port, v4addrp, v6addrp, checkin, lookup, answer,param_flags);
|
||||
impl_ = new DNSServiceImpl(io_service, port, v4addrp, v6addrp, checkin,
|
||||
lookup, answer);
|
||||
}
|
||||
|
||||
DNSService::DNSService(IOService& io_service, SimpleCallback* checkin,
|
||||
DNSLookup* lookup, DNSAnswer *answer,const UDPVersion param_flags) :
|
||||
DNSLookup* lookup, DNSAnswer *answer) :
|
||||
impl_(new DNSServiceImpl(io_service, *"0", NULL, NULL, checkin, lookup,
|
||||
answer,param_flags)), io_service_(io_service)
|
||||
answer)), io_service_(io_service)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -211,24 +196,30 @@ DNSService::~DNSService() {
|
||||
}
|
||||
|
||||
void
|
||||
DNSService::addServer(const char& port, const std::string& address,UDPVersion param_flags) {
|
||||
impl_->addServer(port, convertAddr(address),param_flags);
|
||||
DNSService::addServer(const char& port, const std::string& address) {
|
||||
impl_->addServer(port, convertAddr(address));
|
||||
}
|
||||
|
||||
void
|
||||
DNSService::addServer(uint16_t port, const std::string& address,UDPVersion param_flags) {
|
||||
impl_->addServer(port, convertAddr(address),param_flags);
|
||||
DNSService::addServer(uint16_t port, const std::string& address) {
|
||||
impl_->addServer(port, convertAddr(address));
|
||||
}
|
||||
|
||||
void DNSService::addServerTCPFromFD(int fd, int af) {
|
||||
impl_->addServerFromFD<DNSServiceImpl::TCPServerPtr, TCPServer>(fd, af);
|
||||
}
|
||||
|
||||
void DNSService::addServerUDPFromFD(int fd, int af,const UDPVersion param_flags) {
|
||||
if(SYNC_ == param_flags) {
|
||||
impl_->addServerFromFD<DNSServiceImpl::SyncUDPServerPtr, SyncUDPServer>(fd, af);
|
||||
} else if(ASYNC_ == param_flags) {
|
||||
impl_->addServerFromFD<DNSServiceImpl::UDPServerPtr, UDPServer>(fd, af);
|
||||
void DNSService::addServerUDPFromFD(int fd, int af, ServerFlag options) {
|
||||
if ((~SERVER_DEFINED_FLAGS & static_cast<unsigned int>(options)) != 0) {
|
||||
isc_throw(isc::InvalidParameter, "Invalid DNS/UDP server option: "
|
||||
<< options);
|
||||
}
|
||||
if ((options & SERVER_SYNC_OK) != 0) {
|
||||
impl_->addServerFromFD<DNSServiceImpl::SyncUDPServerPtr,
|
||||
SyncUDPServer>(fd, af);
|
||||
} else {
|
||||
impl_->addServerFromFD<DNSServiceImpl::UDPServerPtr, UDPServer>(
|
||||
fd, af);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -27,13 +27,64 @@ class DNSLookup;
|
||||
class DNSAnswer;
|
||||
class DNSServiceImpl;
|
||||
|
||||
|
||||
/// Codes for UDPServers used in addServer()method.
|
||||
/// \brief A base class for common \c DNSService interfaces.
|
||||
///
|
||||
/// Note: the codes only used in how to create the UDPServers.
|
||||
enum UDPVersion {
|
||||
SYNC_ = 1, ///< used synchronous UDPServer
|
||||
ASYNC_ = 2 ///< used asynchronous UDPServer
|
||||
/// This class is defined mainly for test code so it can use a faked/mock
|
||||
/// version of a derived class and test scenarios that would involve
|
||||
/// \c DNSService without actually instantiating the real service class.
|
||||
///
|
||||
/// It doesn't intend to be a customization for other purposes - we generally
|
||||
/// expect non test code only use \c DNSService directly.
|
||||
/// For this reason most of the detailed description are given in the
|
||||
/// \c DNSService class. See that for further details of specific methods
|
||||
/// and class behaviors.
|
||||
class DNSServiceBase {
|
||||
protected:
|
||||
/// \brief Default constructor.
|
||||
///
|
||||
/// This is protected so this class couldn't be accidentally instantiated
|
||||
/// directly, even if there were no pure virtual functions.
|
||||
DNSServiceBase() {}
|
||||
|
||||
public:
|
||||
/// \brief Flags for optional server properties.
|
||||
///
|
||||
/// The values of this enumerable type are intended to be used to specify
|
||||
/// a particular property of the server created via the \c addServer
|
||||
/// variants. As we see need for more such properties, a compound
|
||||
/// form of flags (i.e., a single value generated by bitwise OR'ed
|
||||
/// multiple flag values) will be allowed.
|
||||
///
|
||||
/// Note: the description is given here because it's used in the method
|
||||
/// signature. It essentially belongs to the derived \c DNSService
|
||||
/// class.
|
||||
enum ServerFlag {
|
||||
SERVER_DEFAULT = 0, ///< The default flag (no particular property)
|
||||
SERVER_SYNC_OK = 1 ///< The server can act in the "synchronous" mode.
|
||||
///< In this mode, the client ensures that the
|
||||
///< lookup provider always completes the query
|
||||
///< process and it immediately releases the
|
||||
///< ownership of the given buffer. This allows
|
||||
///< the server implementation to introduce some
|
||||
///< optimization such as omitting unnecessary
|
||||
///< operation or reusing internal resources.
|
||||
///< Note that in functionality the non
|
||||
///< "synchronous" mode is compatible with the
|
||||
///< synchronous mode; it's up to the server
|
||||
///< implementation whether it exploits the
|
||||
///< information given by the client.
|
||||
};
|
||||
|
||||
public:
|
||||
/// \brief The destructor.
|
||||
virtual ~DNSServiceBase() {}
|
||||
|
||||
virtual void addServerTCPFromFD(int fd, int af) = 0;
|
||||
virtual void addServerUDPFromFD(int fd, int af,
|
||||
ServerFlag options = SERVER_DEFAULT) = 0;
|
||||
virtual void clearServers() = 0;
|
||||
|
||||
virtual asiolink::IOService& getIOService() = 0;
|
||||
};
|
||||
|
||||
/// \brief Handle DNS Queries
|
||||
@@ -43,7 +94,7 @@ enum UDPVersion {
|
||||
/// logic that is shared between the authoritative and the recursive
|
||||
/// server implementations. As such, it handles asio, including config
|
||||
/// updates (through the 'Checkinprovider'), and listening sockets.
|
||||
class DNSService {
|
||||
class DNSService : public DNSServiceBase {
|
||||
///
|
||||
/// \name Constructors and Destructor
|
||||
///
|
||||
@@ -54,6 +105,12 @@ private:
|
||||
DNSService(const DNSService& source);
|
||||
DNSService& operator=(const DNSService& source);
|
||||
|
||||
private:
|
||||
// Bit or'ed all defined \c ServerFlag values. Used internally for
|
||||
// compatibility check. Note that this doesn't have to be used by
|
||||
// applications, and doesn't have to be defined in the "base" class.
|
||||
static const unsigned int SERVER_DEFINED_FLAGS = 1;
|
||||
|
||||
public:
|
||||
/// \brief The constructor with a specific IP address and port on which
|
||||
/// the services listen on.
|
||||
@@ -66,8 +123,8 @@ public:
|
||||
/// \param answer The answer provider (see \c DNSAnswer)
|
||||
DNSService(asiolink::IOService& io_service, const char& port,
|
||||
const char& address, isc::asiolink::SimpleCallback* checkin,
|
||||
DNSLookup* lookup, DNSAnswer* answer,
|
||||
const UDPVersion param_flags = SYNC_);
|
||||
DNSLookup* lookup, DNSAnswer* answer);
|
||||
|
||||
/// \brief The constructor with a specific port on which the services
|
||||
/// listen on.
|
||||
///
|
||||
@@ -85,23 +142,21 @@ public:
|
||||
DNSService(asiolink::IOService& io_service, const char& port,
|
||||
const bool use_ipv4, const bool use_ipv6,
|
||||
isc::asiolink::SimpleCallback* checkin, DNSLookup* lookup,
|
||||
DNSAnswer* answer,
|
||||
const UDPVersion param_flags = SYNC_);
|
||||
DNSAnswer* answer);
|
||||
/// \brief The constructor without any servers.
|
||||
///
|
||||
/// Use addServer() to add some servers.
|
||||
DNSService(asiolink::IOService& io_service, isc::asiolink::SimpleCallback* checkin,
|
||||
DNSLookup* lookup, DNSAnswer* answer,
|
||||
const UDPVersion param_flags = SYNC_);
|
||||
DNSService(asiolink::IOService& io_service,
|
||||
isc::asiolink::SimpleCallback* checkin,
|
||||
DNSLookup* lookup, DNSAnswer* answer);
|
||||
|
||||
/// \brief The destructor.
|
||||
~DNSService();
|
||||
virtual ~DNSService();
|
||||
//@}
|
||||
|
||||
/// \brief Add another server to the service
|
||||
void addServer(uint16_t port, const std::string &address,
|
||||
const UDPVersion param_flags = SYNC_);
|
||||
void addServer(const char &port, const std::string &address,
|
||||
const UDPVersion param_flags = SYNC_);
|
||||
void addServer(uint16_t port, const std::string &address);
|
||||
void addServer(const char& port, const std::string& address);
|
||||
|
||||
/// \brief Add another TCP server/listener to the service from already
|
||||
/// opened file descriptor
|
||||
@@ -113,13 +168,17 @@ public:
|
||||
/// specific address) and is ready for listening to new connection
|
||||
/// requests but has not actually started listening.
|
||||
///
|
||||
/// At the moment, TCP servers don't support any optional properties;
|
||||
/// so unlike the UDP version of the method it doesn't have an \c options
|
||||
/// argument.
|
||||
///
|
||||
/// \param fd the file descriptor to be used.
|
||||
/// \param af the address family of the file descriptor. Must be either
|
||||
/// AF_INET or AF_INET6.
|
||||
/// \throw isc::InvalidParameter if af is neither AF_INET nor AF_INET6.
|
||||
/// \throw isc::asiolink::IOError when a low-level error happens, like the
|
||||
/// fd is not a valid descriptor or it can't be listened on.
|
||||
void addServerTCPFromFD(int fd, int af);
|
||||
virtual void addServerTCPFromFD(int fd, int af);
|
||||
|
||||
/// \brief Add another UDP server to the service from already opened
|
||||
/// file descriptor
|
||||
@@ -133,10 +192,14 @@ public:
|
||||
/// \param fd the file descriptor to be used.
|
||||
/// \param af the address family of the file descriptor. Must be either
|
||||
/// AF_INET or AF_INET6.
|
||||
/// \throw isc::InvalidParameter if af is neither AF_INET nor AF_INET6.
|
||||
/// \param options Optional properties of the server (see ServerFlag).
|
||||
///
|
||||
/// \throw isc::InvalidParameter if af is neither AF_INET nor AF_INET6,
|
||||
/// or the given \c options include an unsupported or invalid value.
|
||||
/// \throw isc::asiolink::IOError when a low-level error happens, like the
|
||||
/// fd is not a valid descriptor or it can't be listened on.
|
||||
void addServerUDPFromFD(int fd, int af,const UDPVersion param_flags = ASYNC_);
|
||||
virtual void addServerUDPFromFD(int fd, int af,
|
||||
ServerFlag options = SERVER_DEFAULT);
|
||||
|
||||
/// \brief Remove all servers from the service
|
||||
void clearServers();
|
||||
@@ -152,7 +215,7 @@ public:
|
||||
/// \brief Return the IO Service Object
|
||||
///
|
||||
/// \return IOService object for this DNS service.
|
||||
asiolink::IOService& getIOService() { return (io_service_);}
|
||||
virtual asiolink::IOService& getIOService() { return (io_service_);}
|
||||
|
||||
private:
|
||||
DNSServiceImpl* impl_;
|
||||
@@ -162,3 +225,7 @@ private:
|
||||
} // namespace asiodns
|
||||
} // namespace isc
|
||||
#endif // __ASIOLINK_DNS_SERVICE_H
|
||||
|
||||
// Local Variables:
|
||||
// mode: c++
|
||||
// End:
|
||||
|
@@ -18,7 +18,7 @@ TESTS += run_unittests
|
||||
run_unittests_SOURCES = run_unittests.cc
|
||||
run_unittests_SOURCES += $(top_srcdir)/src/lib/dns/tests/unittest_util.h
|
||||
run_unittests_SOURCES += $(top_srcdir)/src/lib/dns/tests/unittest_util.cc
|
||||
run_unittests_SOURCES += io_service_unittest.cc
|
||||
run_unittests_SOURCES += dns_service_unittest.cc
|
||||
run_unittests_SOURCES += dns_server_unittest.cc
|
||||
run_unittests_SOURCES += io_fetch_unittest.cc
|
||||
|
||||
|
323
src/lib/asiodns/tests/dns_service_unittest.cc
Normal file
323
src/lib/asiodns/tests/dns_service_unittest.cc
Normal file
@@ -0,0 +1,323 @@
|
||||
// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
|
||||
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
#include <config.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <exceptions/exceptions.h>
|
||||
|
||||
#include <asio.hpp>
|
||||
#include <asiolink/asiolink.h>
|
||||
#include <asiodns/asiodns.h>
|
||||
|
||||
#include <boost/scoped_ptr.hpp>
|
||||
#include <boost/lexical_cast.hpp>
|
||||
|
||||
#include <csignal>
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
|
||||
#include <unistd.h>
|
||||
#include <netdb.h>
|
||||
|
||||
using namespace isc::asiolink;
|
||||
using namespace isc::asiodns;
|
||||
using boost::scoped_ptr;
|
||||
using boost::lexical_cast;
|
||||
|
||||
namespace {
|
||||
const char* const TEST_SERVER_PORT = "53535";
|
||||
const char* const TEST_CLIENT_PORT = "53536";
|
||||
const char* const TEST_IPV6_ADDR = "::1";
|
||||
const char* const TEST_IPV4_ADDR = "127.0.0.1";
|
||||
|
||||
TEST(IOServiceTest, badPort) {
|
||||
IOService io_service;
|
||||
EXPECT_THROW(DNSService(io_service, *"65536", true, false, NULL, NULL, NULL), IOError);
|
||||
EXPECT_THROW(DNSService(io_service, *"53210.0", true, false, NULL, NULL, NULL), IOError);
|
||||
EXPECT_THROW(DNSService(io_service, *"-1", true, false, NULL, NULL, NULL), IOError);
|
||||
EXPECT_THROW(DNSService(io_service, *"domain", true, false, NULL, NULL, NULL), IOError);
|
||||
}
|
||||
|
||||
TEST(IOServiceTest, badAddress) {
|
||||
IOService io_service;
|
||||
EXPECT_THROW(DNSService(io_service, *TEST_SERVER_PORT, *"192.0.2.1.1", NULL, NULL, NULL), IOError);
|
||||
EXPECT_THROW(DNSService(io_service, *TEST_SERVER_PORT, *"2001:db8:::1", NULL, NULL, NULL), IOError);
|
||||
EXPECT_THROW(DNSService(io_service, *TEST_SERVER_PORT, *"localhost", NULL, NULL, NULL), IOError);
|
||||
}
|
||||
|
||||
TEST(IOServiceTest, unavailableAddress) {
|
||||
IOService io_service;
|
||||
// These addresses should generally be unavailable as a valid local
|
||||
// address, although there's no guarantee in theory.
|
||||
EXPECT_THROW(DNSService(io_service, *TEST_SERVER_PORT, *"192.0.2.0", NULL, NULL, NULL), IOError);
|
||||
|
||||
// Some OSes would simply reject binding attempt for an AF_INET6 socket
|
||||
// to an IPv4-mapped IPv6 address. Even if those that allow it, since
|
||||
// the corresponding IPv4 address is the same as the one used in the
|
||||
// AF_INET socket case above, it should at least show the same result
|
||||
// as the previous one.
|
||||
EXPECT_THROW(DNSService(io_service, *TEST_SERVER_PORT, *"::ffff:192.0.2.0", NULL, NULL, NULL), IOError);
|
||||
}
|
||||
|
||||
TEST(IOServiceTest, duplicateBind_v6) {
|
||||
// In each sub test case, second attempt should fail due to duplicate bind
|
||||
IOService io_service;
|
||||
|
||||
// IPv6, "any" address
|
||||
DNSService* dns_service = new DNSService(io_service, *TEST_SERVER_PORT, false, true, NULL, NULL, NULL);
|
||||
EXPECT_THROW(DNSService(io_service, *TEST_SERVER_PORT, false, true, NULL, NULL, NULL), IOError);
|
||||
delete dns_service;
|
||||
|
||||
}
|
||||
|
||||
TEST(IOServiceTest, duplicateBind_v6_address) {
|
||||
// In each sub test case, second attempt should fail due to duplicate bind
|
||||
IOService io_service;
|
||||
|
||||
// IPv6, specific address
|
||||
DNSService* dns_service = new DNSService(io_service, *TEST_SERVER_PORT, *TEST_IPV6_ADDR, NULL, NULL, NULL);
|
||||
EXPECT_THROW(DNSService(io_service, *TEST_SERVER_PORT, *TEST_IPV6_ADDR, NULL, NULL, NULL), IOError);
|
||||
delete dns_service;
|
||||
|
||||
}
|
||||
|
||||
TEST(IOServiceTest, duplicateBind_v4) {
|
||||
// In each sub test case, second attempt should fail due to duplicate bind
|
||||
IOService io_service;
|
||||
|
||||
// IPv4, "any" address
|
||||
DNSService* dns_service = new DNSService(io_service, *TEST_SERVER_PORT, true, false, NULL, NULL, NULL);
|
||||
EXPECT_THROW(DNSService(io_service, *TEST_SERVER_PORT, true, false, NULL, NULL, NULL), IOError);
|
||||
delete dns_service;
|
||||
|
||||
}
|
||||
|
||||
TEST(IOServiceTest, duplicateBind_v4_address) {
|
||||
// In each sub test case, second attempt should fail due to duplicate bind
|
||||
IOService io_service;
|
||||
|
||||
// IPv4, specific address
|
||||
DNSService* dns_service = new DNSService(io_service, *TEST_SERVER_PORT, *TEST_IPV4_ADDR, NULL, NULL, NULL);
|
||||
EXPECT_THROW(DNSService(io_service, *TEST_SERVER_PORT, *TEST_IPV4_ADDR, NULL, NULL, NULL), IOError);
|
||||
delete dns_service;
|
||||
}
|
||||
|
||||
// Disabled because IPv4-mapped addresses don't seem to be working with
|
||||
// the IOService constructor
|
||||
TEST(IOServiceTest, DISABLED_IPv4MappedDuplicateBind) {
|
||||
IOService io_service;
|
||||
// Duplicate bind on IPv4-mapped IPv6 address
|
||||
DNSService* dns_service = new DNSService(io_service, *TEST_SERVER_PORT, *"127.0.0.1", NULL, NULL, NULL);
|
||||
EXPECT_THROW(DNSService(io_service, *TEST_SERVER_PORT, *"::ffff:127.0.0.1", NULL, NULL, NULL), IOError);
|
||||
delete dns_service;
|
||||
|
||||
// XXX:
|
||||
// Currently, this throws an "invalid argument" exception. I have
|
||||
// not been able to get IPv4-mapped addresses to work.
|
||||
dns_service = new DNSService(io_service, *TEST_SERVER_PORT, *"::ffff:127.0.0.1", NULL, NULL, NULL);
|
||||
EXPECT_THROW(DNSService(io_service, *TEST_SERVER_PORT, *"127.0.0.1", NULL, NULL, NULL), IOError);
|
||||
delete dns_service;
|
||||
}
|
||||
|
||||
// A simple lookup callback for DNS services. It records the pointer value of
|
||||
// to given output buffer each time the callback is called (up to two times)
|
||||
// for the main tests. At the end of the second callback it stops the server.
|
||||
// The sender of the data doesn't expect to get a response, so it simply
|
||||
// discards any received data.
|
||||
class TestLookup : public DNSLookup {
|
||||
public:
|
||||
TestLookup(isc::util::OutputBuffer** b1, isc::util::OutputBuffer** b2,
|
||||
IOService& io_service) :
|
||||
first_buffer_(b1), second_buffer_(b2), io_service_(io_service)
|
||||
{}
|
||||
void operator()(const IOMessage&, isc::dns::MessagePtr,
|
||||
isc::dns::MessagePtr, isc::util::OutputBufferPtr buffer,
|
||||
DNSServer* server) const
|
||||
{
|
||||
server->resume(false);
|
||||
if (*first_buffer_ == NULL) {
|
||||
*first_buffer_ = buffer.get();
|
||||
} else {
|
||||
assert(*second_buffer_ == NULL);
|
||||
*second_buffer_ = buffer.get();
|
||||
server->stop();
|
||||
io_service_.stop();
|
||||
}
|
||||
}
|
||||
isc::util::OutputBuffer** first_buffer_;
|
||||
isc::util::OutputBuffer** second_buffer_;
|
||||
IOService& io_service_;
|
||||
};
|
||||
|
||||
// A test fixture to check creation of UDP servers from a socket FD, changing
|
||||
// options.
|
||||
class UDPDNSServiceTest : public::testing::Test {
|
||||
private:
|
||||
static const unsigned int IO_SERVICE_TIME_OUT = 5;
|
||||
|
||||
protected:
|
||||
UDPDNSServiceTest() :
|
||||
first_buffer_(NULL), second_buffer_(NULL),
|
||||
lookup(&first_buffer_, &second_buffer_, io_service),
|
||||
dns_service(io_service, NULL, &lookup, NULL),
|
||||
client_socket(io_service.get_io_service(), asio::ip::udp::v6()),
|
||||
server_ep(asio::ip::address::from_string(TEST_IPV6_ADDR),
|
||||
lexical_cast<uint16_t>(TEST_SERVER_PORT)),
|
||||
asio_service(io_service.get_io_service())
|
||||
{
|
||||
current_service = &io_service;
|
||||
// Content shouldn't matter for the tests, but initialize anyway
|
||||
memset(data, 1, sizeof(data));
|
||||
}
|
||||
|
||||
~UDPDNSServiceTest() {
|
||||
current_service = NULL;
|
||||
}
|
||||
|
||||
void runService() {
|
||||
io_service_is_time_out = false;
|
||||
|
||||
// Send two UDP packets, which will be passed to the TestLookup
|
||||
// callback. They are not expected to be responded, so it simply
|
||||
// closes the socket right after that.
|
||||
client_socket.send_to(asio::buffer(data, sizeof(data)), server_ep);
|
||||
client_socket.send_to(asio::buffer(data, sizeof(data)), server_ep);
|
||||
client_socket.close();
|
||||
|
||||
// set a signal-based alarm to prevent the test from hanging up
|
||||
// due to a bug.
|
||||
void (*prev_handler)(int) =
|
||||
std::signal(SIGALRM, UDPDNSServiceTest::stopIOService);
|
||||
current_service = &io_service;
|
||||
alarm(IO_SERVICE_TIME_OUT);
|
||||
io_service.run();
|
||||
io_service.get_io_service().reset();
|
||||
//cancel scheduled alarm
|
||||
alarm(0);
|
||||
std::signal(SIGALRM, prev_handler);
|
||||
}
|
||||
|
||||
// last resort service stopper by signal
|
||||
static void stopIOService(int) {
|
||||
io_service_is_time_out = true;
|
||||
if (current_service != NULL) {
|
||||
current_service->stop();
|
||||
}
|
||||
}
|
||||
|
||||
bool serverStopSucceed() const {
|
||||
return (!io_service_is_time_out);
|
||||
}
|
||||
|
||||
isc::util::OutputBuffer* first_buffer_;
|
||||
isc::util::OutputBuffer* second_buffer_;
|
||||
IOService io_service;
|
||||
TestLookup lookup;
|
||||
DNSService dns_service;
|
||||
private:
|
||||
asio::ip::udp::socket client_socket;
|
||||
const asio::ip::udp::endpoint server_ep;
|
||||
char data[4];
|
||||
|
||||
// To access them in signal handle function, the following
|
||||
// variables have to be static.
|
||||
static IOService* current_service;
|
||||
static bool io_service_is_time_out;
|
||||
|
||||
asio::io_service& asio_service;
|
||||
};
|
||||
|
||||
// Need to define the non-const static members outside of the class
|
||||
// declaration
|
||||
IOService* UDPDNSServiceTest::current_service;
|
||||
bool UDPDNSServiceTest::io_service_is_time_out;
|
||||
|
||||
// A helper socket FD creator for given address and port. It's generally
|
||||
// expected to succeed; on failure it simply throws an exception to make
|
||||
// the test fail.
|
||||
int
|
||||
getSocketFD(int family, const char* const address, const char* const port) {
|
||||
struct addrinfo hints, *res = NULL;
|
||||
memset(&hints, 0, sizeof(hints));
|
||||
hints.ai_family = family;
|
||||
hints.ai_socktype = SOCK_DGRAM;
|
||||
hints.ai_protocol = IPPROTO_UDP;
|
||||
hints.ai_flags = AI_NUMERICHOST | AI_NUMERICSERV;
|
||||
int s = -1;
|
||||
int error = getaddrinfo(address, port, &hints, &res);
|
||||
if (error == 0) {
|
||||
// If getaddrinfo returns 0, res should be set to a non NULL valid
|
||||
// pointer, but some variants of cppcheck reportedly complains about
|
||||
// it, so we satisfy them here.
|
||||
if (res != NULL) {
|
||||
s = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
|
||||
if (s >= 0) {
|
||||
error = bind(s, res->ai_addr, res->ai_addrlen);
|
||||
}
|
||||
freeaddrinfo(res);
|
||||
}
|
||||
}
|
||||
if (error != 0) {
|
||||
if (s >= 0) {
|
||||
close(s);
|
||||
}
|
||||
isc_throw(isc::Unexpected, "failed to open test socket");
|
||||
}
|
||||
return (s);
|
||||
}
|
||||
|
||||
TEST_F(UDPDNSServiceTest, defaultUDPServerFromFD) {
|
||||
// If no option is explicitly specified, an asynchronous server should be
|
||||
// created. So the two output buffers should be different.
|
||||
dns_service.addServerUDPFromFD(getSocketFD(AF_INET6, TEST_IPV6_ADDR,
|
||||
TEST_SERVER_PORT), AF_INET6);
|
||||
runService();
|
||||
EXPECT_TRUE(serverStopSucceed());
|
||||
EXPECT_NE(first_buffer_, second_buffer_);
|
||||
}
|
||||
|
||||
TEST_F(UDPDNSServiceTest, explicitDefaultUDPServerFromFD) {
|
||||
// If "default" option is explicitly specified, the effect should be the
|
||||
// same as the previous case.
|
||||
dns_service.addServerUDPFromFD(getSocketFD(AF_INET6, TEST_IPV6_ADDR,
|
||||
TEST_SERVER_PORT),
|
||||
AF_INET6, DNSService::SERVER_DEFAULT);
|
||||
runService();
|
||||
EXPECT_TRUE(serverStopSucceed());
|
||||
EXPECT_NE(first_buffer_, second_buffer_);
|
||||
}
|
||||
|
||||
TEST_F(UDPDNSServiceTest, syncUDPServerFromFD) {
|
||||
// If "SYNC_OK" option is specified, a synchronous server should be
|
||||
// created. It will reuse the output buffer, so the recorded two pointer
|
||||
// should be identical.
|
||||
dns_service.addServerUDPFromFD(getSocketFD(AF_INET6, TEST_IPV6_ADDR,
|
||||
TEST_SERVER_PORT),
|
||||
AF_INET6, DNSService::SERVER_SYNC_OK);
|
||||
runService();
|
||||
EXPECT_TRUE(serverStopSucceed());
|
||||
EXPECT_EQ(first_buffer_, second_buffer_);
|
||||
}
|
||||
|
||||
TEST_F(UDPDNSServiceTest, addUDPServerFromFDWithUnknownOption) {
|
||||
// Use of undefined/incompatible options should result in an exception.
|
||||
EXPECT_THROW(dns_service.addServerUDPFromFD(
|
||||
getSocketFD(AF_INET6, TEST_IPV6_ADDR, TEST_SERVER_PORT),
|
||||
AF_INET6, static_cast<DNSService::ServerFlag>(2)),
|
||||
isc::InvalidParameter);
|
||||
}
|
||||
|
||||
} // unnamed namespace
|
@@ -1,123 +0,0 @@
|
||||
// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
|
||||
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
#include <config.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <asio.hpp>
|
||||
#include <asiolink/asiolink.h>
|
||||
#include <asiodns/asiodns.h>
|
||||
|
||||
using namespace isc::asiolink;
|
||||
using namespace isc::asiodns;
|
||||
|
||||
const char* const TEST_SERVER_PORT = "53535";
|
||||
const char* const TEST_CLIENT_PORT = "53536";
|
||||
const char* const TEST_IPV6_ADDR = "::1";
|
||||
const char* const TEST_IPV4_ADDR = "127.0.0.1";
|
||||
|
||||
TEST(IOServiceTest, badPort) {
|
||||
IOService io_service;
|
||||
EXPECT_THROW(DNSService(io_service, *"65536", true, false, NULL, NULL, NULL), IOError);
|
||||
EXPECT_THROW(DNSService(io_service, *"53210.0", true, false, NULL, NULL, NULL), IOError);
|
||||
EXPECT_THROW(DNSService(io_service, *"-1", true, false, NULL, NULL, NULL), IOError);
|
||||
EXPECT_THROW(DNSService(io_service, *"domain", true, false, NULL, NULL, NULL), IOError);
|
||||
}
|
||||
|
||||
TEST(IOServiceTest, badAddress) {
|
||||
IOService io_service;
|
||||
EXPECT_THROW(DNSService(io_service, *TEST_SERVER_PORT, *"192.0.2.1.1", NULL, NULL, NULL), IOError);
|
||||
EXPECT_THROW(DNSService(io_service, *TEST_SERVER_PORT, *"2001:db8:::1", NULL, NULL, NULL), IOError);
|
||||
EXPECT_THROW(DNSService(io_service, *TEST_SERVER_PORT, *"localhost", NULL, NULL, NULL), IOError);
|
||||
}
|
||||
|
||||
TEST(IOServiceTest, unavailableAddress) {
|
||||
IOService io_service;
|
||||
// These addresses should generally be unavailable as a valid local
|
||||
// address, although there's no guarantee in theory.
|
||||
EXPECT_THROW(DNSService(io_service, *TEST_SERVER_PORT, *"192.0.2.0", NULL, NULL, NULL), IOError);
|
||||
|
||||
// Some OSes would simply reject binding attempt for an AF_INET6 socket
|
||||
// to an IPv4-mapped IPv6 address. Even if those that allow it, since
|
||||
// the corresponding IPv4 address is the same as the one used in the
|
||||
// AF_INET socket case above, it should at least show the same result
|
||||
// as the previous one.
|
||||
EXPECT_THROW(DNSService(io_service, *TEST_SERVER_PORT, *"::ffff:192.0.2.0", NULL, NULL, NULL), IOError);
|
||||
}
|
||||
|
||||
TEST(IOServiceTest, duplicateBind_v6) {
|
||||
// In each sub test case, second attempt should fail due to duplicate bind
|
||||
IOService io_service;
|
||||
|
||||
// IPv6, "any" address
|
||||
DNSService* dns_service = new DNSService(io_service, *TEST_SERVER_PORT, false, true, NULL, NULL, NULL);
|
||||
EXPECT_THROW(DNSService(io_service, *TEST_SERVER_PORT, false, true, NULL, NULL, NULL), IOError);
|
||||
delete dns_service;
|
||||
|
||||
}
|
||||
|
||||
TEST(IOServiceTest, duplicateBind_v6_address) {
|
||||
// In each sub test case, second attempt should fail due to duplicate bind
|
||||
IOService io_service;
|
||||
|
||||
// IPv6, specific address
|
||||
DNSService* dns_service = new DNSService(io_service, *TEST_SERVER_PORT, *TEST_IPV6_ADDR, NULL, NULL, NULL);
|
||||
EXPECT_THROW(DNSService(io_service, *TEST_SERVER_PORT, *TEST_IPV6_ADDR, NULL, NULL, NULL), IOError);
|
||||
delete dns_service;
|
||||
|
||||
}
|
||||
|
||||
TEST(IOServiceTest, duplicateBind_v4) {
|
||||
// In each sub test case, second attempt should fail due to duplicate bind
|
||||
IOService io_service;
|
||||
|
||||
// IPv4, "any" address
|
||||
DNSService* dns_service = new DNSService(io_service, *TEST_SERVER_PORT, true, false, NULL, NULL, NULL);
|
||||
EXPECT_THROW(DNSService(io_service, *TEST_SERVER_PORT, true, false, NULL, NULL, NULL), IOError);
|
||||
delete dns_service;
|
||||
|
||||
}
|
||||
|
||||
TEST(IOServiceTest, duplicateBind_v4_address) {
|
||||
// In each sub test case, second attempt should fail due to duplicate bind
|
||||
IOService io_service;
|
||||
|
||||
// IPv4, specific address
|
||||
DNSService* dns_service = new DNSService(io_service, *TEST_SERVER_PORT, *TEST_IPV4_ADDR, NULL, NULL, NULL);
|
||||
EXPECT_THROW(DNSService(io_service, *TEST_SERVER_PORT, *TEST_IPV4_ADDR, NULL, NULL, NULL), IOError);
|
||||
delete dns_service;
|
||||
}
|
||||
|
||||
// Disabled because IPv4-mapped addresses don't seem to be working with
|
||||
// the IOService constructor
|
||||
TEST(IOServiceTest, DISABLED_IPv4MappedDuplicateBind) {
|
||||
IOService io_service;
|
||||
// Duplicate bind on IPv4-mapped IPv6 address
|
||||
DNSService* dns_service = new DNSService(io_service, *TEST_SERVER_PORT, *"127.0.0.1", NULL, NULL, NULL);
|
||||
EXPECT_THROW(DNSService(io_service, *TEST_SERVER_PORT, *"::ffff:127.0.0.1", NULL, NULL, NULL), IOError);
|
||||
delete dns_service;
|
||||
|
||||
// XXX:
|
||||
// Currently, this throws an "invalid argument" exception. I have
|
||||
// not been able to get IPv4-mapped addresses to work.
|
||||
dns_service = new DNSService(io_service, *TEST_SERVER_PORT, *"::ffff:127.0.0.1", NULL, NULL, NULL);
|
||||
EXPECT_THROW(DNSService(io_service, *TEST_SERVER_PORT, *"127.0.0.1", NULL, NULL, NULL), IOError);
|
||||
delete dns_service;
|
||||
}
|
||||
|
||||
TEST(IOServiceTest, BadUdpServerVersion) {
|
||||
IOService io_service;
|
||||
DNSService* dns_service = new DNSService(io_service, NULL, NULL, NULL);
|
||||
EXPECT_THROW(dns_service->addServer(*TEST_SERVER_PORT, "127.0.0.1", UDPVersion(3)), IOError);
|
||||
}
|
@@ -384,7 +384,8 @@ doQueryTask(QueryTask& task, ZoneInfo& zoneinfo, RRsetList& target) {
|
||||
const Name* const zonename = zoneinfo.getEnclosingZone();
|
||||
if (ds == NULL) {
|
||||
task.flags |= DataSrc::NO_SUCH_ZONE;
|
||||
logger.info(DATASRC_QUERY_NO_ZONE).arg(task.qname).arg(task.qclass);
|
||||
LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_QUERY_NO_ZONE).
|
||||
arg(task.qname).arg(task.qclass);
|
||||
return (DataSrc::SUCCESS);
|
||||
}
|
||||
|
||||
|
@@ -234,6 +234,41 @@ public:
|
||||
int id,
|
||||
bool subdomains = false) const = 0;
|
||||
|
||||
/// \brief Creates an iterator context for the records of NSEC3 namespace
|
||||
/// for the given hash
|
||||
///
|
||||
/// Returns an Iteratorcontextptr that contains all the records of the given
|
||||
/// hash in the NSEC3 namespace of the given zone.
|
||||
///
|
||||
/// The implementation of the iterator that is returned may leave the
|
||||
/// NAME_COLUMN column of the array passed to getNext() untouched,
|
||||
/// as that name is easy to construct on the caller side (both the
|
||||
/// hash and the name of the zone is known). The SIGTYPE_COLUMN can
|
||||
/// be omitted as well, as it would be always empty for NSEC3 RRs or
|
||||
/// contained "NSEC3" in case of RRSIG RRs.
|
||||
///
|
||||
/// The iterator will contain both the NSEC3 records and the corresponding
|
||||
/// RRSIGs, in arbitrary order.
|
||||
///
|
||||
/// The iterator might be empty (containing no RRs) in case the zone is not
|
||||
/// signed by NSEC3.
|
||||
///
|
||||
/// \note In case there are multiple NSEC3 chains and they collide
|
||||
/// (unlikely, but it can happen), this can return multiple NSEC3
|
||||
/// records.
|
||||
/// \exception any Since any implementaion can be used, the caller should
|
||||
/// expect any exception to be thrown.
|
||||
/// \exception isc::NotImplemented in case the database does not support
|
||||
/// NSEC3
|
||||
///
|
||||
/// \param hash The hash part of the NSEC3 name (eg. for a name of NSEC3
|
||||
/// RKBUCQT8T78GV6QBCGBHCHC019LG73SJ.example.com., we the hash would be
|
||||
/// RKBUCQT8T78GV6QBCGBHCHC019LG73SJ).
|
||||
/// \param id The id of te zone, as returned from getZone().
|
||||
/// \return Newly created iterator context. Must not be NULL.
|
||||
virtual IteratorContextPtr getNSEC3Records(const std::string& hash,
|
||||
int id) const = 0;
|
||||
|
||||
/// \brief Creates an iterator context for the whole zone.
|
||||
///
|
||||
/// Returns an IteratorContextPtr that contains all records of the
|
||||
@@ -643,6 +678,34 @@ public:
|
||||
/// apex of the zone).
|
||||
virtual std::string findPreviousName(int zone_id,
|
||||
const std::string& rname) const = 0;
|
||||
|
||||
/// \brief It returns the previous hash in the NSEC3 chain.
|
||||
///
|
||||
/// This is used to find previous NSEC3 hashes, to find covering NSEC3 in
|
||||
/// case none match exactly.
|
||||
///
|
||||
/// In case a hash before before the lowest or the lowest is provided,
|
||||
/// this should return the largest one in the zone (NSEC3 needs a
|
||||
/// wrap-around semantics).
|
||||
///
|
||||
/// \param zone_id Specifies the zone to look into, as returned by getZone.
|
||||
/// \param hash The hash to look before.
|
||||
/// \return The nearest smaller hash than the provided one, or the largest
|
||||
/// hash in the zone if something smaller or equal to the lowest one
|
||||
/// is provided.
|
||||
/// \note If the zone contains multiple NSEC3 chains, you should check that
|
||||
/// the returned result contains the NSEC3 for correct parameters. If
|
||||
/// not, query again and get something smaller - this will eventually
|
||||
/// get to the correct one. This interface and semantics might change
|
||||
/// in future.
|
||||
///
|
||||
/// \throw DataSourceError if there's a problem with the database or if
|
||||
/// this zone is not signed with NSEC3.
|
||||
/// \throw NotImplemented if this database doesn't support NSEC3.
|
||||
/// \throw anything else, as this might be any implementation.
|
||||
virtual std::string findPreviousNSEC3Hash(int zone_id,
|
||||
const std::string& hash)
|
||||
const = 0;
|
||||
};
|
||||
|
||||
/// \brief Concrete data source client oriented at database backends.
|
||||
|
@@ -569,8 +569,10 @@ An attempt to add a NSEC3 record into the message failed, because the zone does
|
||||
not have any DS record. This indicates problem with the provided data.
|
||||
|
||||
% DATASRC_QUERY_NO_ZONE no zone containing '%1' in class '%2'
|
||||
Lookup of domain failed because the data have no zone that contain the
|
||||
domain. Maybe someone sent a query to the wrong server for some reason.
|
||||
Debug information. Lookup of domain failed because the datasource
|
||||
has no zone that contains the domain. Maybe someone sent a query
|
||||
to the wrong server for some reason. This may also happen when
|
||||
looking in the datasource for addresses for NS records.
|
||||
|
||||
% DATASRC_QUERY_PROCESS processing query '%1/%2' in the '%3' class
|
||||
Debug information. A sure query is being processed now.
|
||||
|
@@ -54,7 +54,10 @@ enum StatementID {
|
||||
LOW_DIFF_ID = 13,
|
||||
HIGH_DIFF_ID = 14,
|
||||
DIFF_RECS = 15,
|
||||
NUM_STATEMENTS = 16
|
||||
NSEC3 = 16,
|
||||
NSEC3_PREVIOUS = 17,
|
||||
NSEC3_LAST = 18,
|
||||
NUM_STATEMENTS = 19
|
||||
};
|
||||
|
||||
const char* const text_statements[NUM_STATEMENTS] = {
|
||||
@@ -83,7 +86,7 @@ const char* const text_statements[NUM_STATEMENTS] = {
|
||||
*/
|
||||
"SELECT name FROM records " // FIND_PREVIOUS
|
||||
"WHERE zone_id=?1 AND rdtype = 'NSEC' AND "
|
||||
"rname < $2 ORDER BY rname DESC LIMIT 1",
|
||||
"rname < ?2 ORDER BY rname DESC LIMIT 1",
|
||||
"INSERT INTO diffs " // ADD_RECORD_DIFF
|
||||
"(zone_id, version, operation, name, rrtype, ttl, rdata) "
|
||||
"VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)",
|
||||
@@ -103,7 +106,21 @@ const char* const text_statements[NUM_STATEMENTS] = {
|
||||
// that the columns match the column IDs passed to the iterator
|
||||
"SELECT rrtype, ttl, id, rdata, name FROM diffs " // DIFF_RECS
|
||||
"WHERE zone_id=?1 AND id>=?2 and id<=?3 "
|
||||
"ORDER BY id ASC"
|
||||
"ORDER BY id ASC",
|
||||
|
||||
// Query to get the NSEC3 records
|
||||
//
|
||||
// The "1" in SELECT is for positioning the rdata column to the
|
||||
// expected position, so we can reuse the same code as for other
|
||||
// lookups.
|
||||
"SELECT rdtype, ttl, 1, rdata FROM nsec3 WHERE zone_id=?1 AND "
|
||||
"hash=?2",
|
||||
// For getting the previous NSEC3 hash
|
||||
"SELECT DISTINCT hash FROM nsec3 WHERE zone_id=?1 AND hash < ?2 "
|
||||
"ORDER BY hash DESC LIMIT 1",
|
||||
// And for wrap-around
|
||||
"SELECT DISTINCT hash FROM nsec3 WHERE zone_id=?1 "
|
||||
"ORDER BY hash DESC LIMIT 1",
|
||||
};
|
||||
|
||||
struct SQLite3Parameters {
|
||||
@@ -482,19 +499,46 @@ public:
|
||||
bindZoneId(id);
|
||||
}
|
||||
|
||||
// What kind of query it is - selection of the statement for DB
|
||||
enum QueryType {
|
||||
QT_ANY, // Directly for a domain
|
||||
QT_SUBDOMAINS, // Subdomains of a given domain
|
||||
QT_NSEC3 // Domain in the NSEC3 namespace (the name is is the hash,
|
||||
// not the whole name)
|
||||
};
|
||||
|
||||
// Construct an iterator for records with a specific name. When constructed
|
||||
// this way, the getNext() call will copy all fields except name
|
||||
Context(const boost::shared_ptr<const SQLite3Accessor>& accessor, int id,
|
||||
const std::string& name, bool subdomains) :
|
||||
iterator_type_(ITT_NAME),
|
||||
const std::string& name, QueryType qtype) :
|
||||
iterator_type_(qtype == QT_NSEC3 ? ITT_NSEC3 : ITT_NAME),
|
||||
accessor_(accessor),
|
||||
statement_(NULL),
|
||||
name_(name)
|
||||
{
|
||||
// Choose the statement text depending on the query type
|
||||
const char* statement(NULL);
|
||||
switch (qtype) {
|
||||
case QT_ANY:
|
||||
statement = text_statements[ANY];
|
||||
break;
|
||||
case QT_SUBDOMAINS:
|
||||
statement = text_statements[ANY_SUB];
|
||||
break;
|
||||
case QT_NSEC3:
|
||||
statement = text_statements[NSEC3];
|
||||
break;
|
||||
default:
|
||||
// Can Not Happen - there isn't any other type of query
|
||||
// and all the calls to the constructor are from this
|
||||
// file. Therefore no way to test it throws :-(.
|
||||
isc_throw(Unexpected,
|
||||
"Invalid qtype passed - unreachable code branch "
|
||||
"reached");
|
||||
}
|
||||
|
||||
// We create the statement now and then just keep getting data from it
|
||||
statement_ = prepare(accessor->dbparameters_->db_,
|
||||
subdomains ? text_statements[ANY_SUB] :
|
||||
text_statements[ANY]);
|
||||
statement_ = prepare(accessor->dbparameters_->db_, statement);
|
||||
bindZoneId(id);
|
||||
bindName(name_);
|
||||
}
|
||||
@@ -511,7 +555,11 @@ public:
|
||||
// For both types, we copy the first four columns
|
||||
copyColumn(data, TYPE_COLUMN);
|
||||
copyColumn(data, TTL_COLUMN);
|
||||
copyColumn(data, SIGTYPE_COLUMN);
|
||||
// The NSEC3 lookup does not provide the SIGTYPE, it is not
|
||||
// necessary and not contained in the table.
|
||||
if (iterator_type_ != ITT_NSEC3) {
|
||||
copyColumn(data, SIGTYPE_COLUMN);
|
||||
}
|
||||
copyColumn(data, RDATA_COLUMN);
|
||||
// Only copy Name if we are iterating over every record
|
||||
if (iterator_type_ == ITT_ALL) {
|
||||
@@ -537,7 +585,8 @@ private:
|
||||
// See description of getNext() and the constructors
|
||||
enum IteratorType {
|
||||
ITT_ALL,
|
||||
ITT_NAME
|
||||
ITT_NAME,
|
||||
ITT_NSEC3
|
||||
};
|
||||
|
||||
void copyColumn(std::string (&data)[COLUMN_COUNT], int column) {
|
||||
@@ -584,7 +633,15 @@ SQLite3Accessor::getRecords(const std::string& name, int id,
|
||||
bool subdomains) const
|
||||
{
|
||||
return (IteratorContextPtr(new Context(shared_from_this(), id, name,
|
||||
subdomains)));
|
||||
subdomains ?
|
||||
Context::QT_SUBDOMAINS :
|
||||
Context::QT_ANY)));
|
||||
}
|
||||
|
||||
DatabaseAccessor::IteratorContextPtr
|
||||
SQLite3Accessor::getNSEC3Records(const std::string& hash, int id) const {
|
||||
return (IteratorContextPtr(new Context(shared_from_this(), id, hash,
|
||||
Context::QT_NSEC3)));
|
||||
}
|
||||
|
||||
DatabaseAccessor::IteratorContextPtr
|
||||
@@ -1092,5 +1149,75 @@ SQLite3Accessor::findPreviousName(int zone_id, const std::string& rname)
|
||||
return (result);
|
||||
}
|
||||
|
||||
std::string
|
||||
SQLite3Accessor::findPreviousNSEC3Hash(int zone_id, const std::string& hash)
|
||||
const
|
||||
{
|
||||
sqlite3_stmt* const stmt = dbparameters_->getStatement(NSEC3_PREVIOUS);
|
||||
sqlite3_reset(stmt);
|
||||
sqlite3_clear_bindings(stmt);
|
||||
|
||||
if (sqlite3_bind_int(stmt, 1, zone_id) != SQLITE_OK) {
|
||||
isc_throw(SQLite3Error, "Could not bind zone ID " << zone_id <<
|
||||
" to SQL statement (find previous NSEC3): " <<
|
||||
sqlite3_errmsg(dbparameters_->db_));
|
||||
}
|
||||
if (sqlite3_bind_text(stmt, 2, hash.c_str(), -1, SQLITE_STATIC) !=
|
||||
SQLITE_OK) {
|
||||
isc_throw(SQLite3Error, "Could not bind hash " << hash <<
|
||||
" to SQL statement (find previous NSEC3): " <<
|
||||
sqlite3_errmsg(dbparameters_->db_));
|
||||
}
|
||||
|
||||
std::string result;
|
||||
const int rc = sqlite3_step(stmt);
|
||||
if (rc == SQLITE_ROW) {
|
||||
// We found it
|
||||
result = convertToPlainChar(sqlite3_column_text(stmt, 0),
|
||||
dbparameters_->db_);
|
||||
}
|
||||
sqlite3_reset(stmt);
|
||||
|
||||
if (rc != SQLITE_ROW && rc != SQLITE_DONE) {
|
||||
// Some kind of error
|
||||
isc_throw(SQLite3Error, "Could not get data for previous hash");
|
||||
}
|
||||
|
||||
if (rc == SQLITE_DONE) {
|
||||
// No NSEC3 records before this hash. This means we should wrap
|
||||
// around and take the last one.
|
||||
sqlite3_stmt* const stmt = dbparameters_->getStatement(NSEC3_LAST);
|
||||
sqlite3_reset(stmt);
|
||||
sqlite3_clear_bindings(stmt);
|
||||
|
||||
if (sqlite3_bind_int(stmt, 1, zone_id) != SQLITE_OK) {
|
||||
isc_throw(SQLite3Error, "Could not bind zone ID " << zone_id <<
|
||||
" to SQL statement (find last NSEC3): " <<
|
||||
sqlite3_errmsg(dbparameters_->db_));
|
||||
}
|
||||
|
||||
const int rc = sqlite3_step(stmt);
|
||||
if (rc == SQLITE_ROW) {
|
||||
// We found it
|
||||
result = convertToPlainChar(sqlite3_column_text(stmt, 0),
|
||||
dbparameters_->db_);
|
||||
}
|
||||
sqlite3_reset(stmt);
|
||||
|
||||
if (rc != SQLITE_ROW && rc != SQLITE_DONE) {
|
||||
// Some kind of error
|
||||
isc_throw(SQLite3Error, "Could not get data for last hash");
|
||||
}
|
||||
|
||||
if (rc == SQLITE_DONE) {
|
||||
// No NSEC3 at all in the zone. Well, bad luck, but you should not
|
||||
// have asked in the first place.
|
||||
isc_throw(DataSourceError, "No NSEC3 in this zone");
|
||||
}
|
||||
}
|
||||
|
||||
return (result);
|
||||
}
|
||||
|
||||
} // end of namespace datasrc
|
||||
} // end of namespace isc
|
||||
|
@@ -141,6 +141,14 @@ public:
|
||||
int id,
|
||||
bool subdomains = false) const;
|
||||
|
||||
/// \brief Look up NSEC3 records for the given hash
|
||||
///
|
||||
/// This implements the getNSEC3Records of DatabaseAccessor.
|
||||
///
|
||||
/// \todo Actually implement, currently throws NotImplemented.
|
||||
virtual IteratorContextPtr getNSEC3Records(const std::string& hash,
|
||||
int id) const;
|
||||
|
||||
/** \brief Look up all resource records for a zone
|
||||
*
|
||||
* This implements the getRecords() method from DatabaseAccessor
|
||||
@@ -231,6 +239,11 @@ public:
|
||||
virtual std::string findPreviousName(int zone_id, const std::string& rname)
|
||||
const;
|
||||
|
||||
/// \brief Conrete implemantion of the pure virtual method of
|
||||
/// DatabaseAccessor
|
||||
virtual std::string findPreviousNSEC3Hash(int zone_id,
|
||||
const std::string& hash) const;
|
||||
|
||||
private:
|
||||
/// \brief Private database data
|
||||
boost::scoped_ptr<SQLite3Parameters> dbparameters_;
|
||||
|
@@ -205,6 +205,13 @@ const char* const TEST_RECORDS[][5] = {
|
||||
{NULL, NULL, NULL, NULL, NULL},
|
||||
};
|
||||
|
||||
// FIXME: Taken from a different test. Fill with proper data when creating a test.
|
||||
const char* TEST_NSEC3_RECORDS[][5] = {
|
||||
{"1BB7SO0452U1QHL98UISNDD9218GELR5", "NSEC3", "3600", "", "1 0 10 FEEDABEE 4KLSVDE8KH8G95VU68R7AHBE1CPQN38J"},
|
||||
{"1BB7SO0452U1QHL98UISNDD9218GELR5", "RRSIG", "3600", "", "NSEC3 5 4 7200 20100410172647 20100311172647 63192 example.org. gNIVj4T8t51fEU6kOPpvK7HOGBFZGbalN5ZK mInyrww6UWZsUNdw07ge6/U6HfG+/s61RZ/L is2M6yUWHyXbNbj/QqwqgadG5dhxTArfuR02 xP600x0fWX8LXzW4yLMdKVxGbzYT+vvGz71o 8gHSY5vYTtothcZQa4BMKhmGQEk="},
|
||||
{NULL, NULL, NULL, NULL, NULL}
|
||||
};
|
||||
|
||||
/*
|
||||
* An accessor with minimum implementation, keeping the original
|
||||
* "NotImplemented" methods.
|
||||
@@ -251,11 +258,16 @@ public:
|
||||
|
||||
virtual IteratorContextPtr getRecords(const std::string&, int, bool)
|
||||
const
|
||||
{
|
||||
{
|
||||
isc_throw(isc::NotImplemented,
|
||||
"This database datasource can't be iterated");
|
||||
}
|
||||
|
||||
virtual IteratorContextPtr getNSEC3Records(const std::string&, int) const {
|
||||
isc_throw(isc::NotImplemented, "This test database datasource won't "
|
||||
"give you any NSEC3. Ever. Ask someone else.");
|
||||
}
|
||||
|
||||
virtual IteratorContextPtr getAllRecords(int) const {
|
||||
isc_throw(isc::NotImplemented,
|
||||
"This database datasource can't be iterated");
|
||||
@@ -270,6 +282,11 @@ public:
|
||||
isc_throw(isc::NotImplemented,
|
||||
"This data source doesn't support DNSSEC");
|
||||
}
|
||||
|
||||
virtual std::string findPreviousNSEC3Hash(int, const std::string&) const {
|
||||
isc_throw(isc::NotImplemented,
|
||||
"This test database knows nothing about NSEC3 nor order");
|
||||
}
|
||||
private:
|
||||
const std::string database_name_;
|
||||
|
||||
@@ -378,6 +395,28 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
class DomainIterator : public IteratorContext {
|
||||
public:
|
||||
DomainIterator(const std::vector<std::vector<std::string> >& domain) :
|
||||
domain_(domain),
|
||||
position_(domain_.begin())
|
||||
{}
|
||||
virtual bool getNext(std::string (&columns)[COLUMN_COUNT]) {
|
||||
if (position_ == domain_.end()) {
|
||||
return (false);
|
||||
} else {
|
||||
for (size_t i(0); i < COLUMN_COUNT; ++ i) {
|
||||
columns[i] = (*position_)[i];
|
||||
}
|
||||
++ position_;
|
||||
return (true);
|
||||
}
|
||||
}
|
||||
private:
|
||||
const std::vector<std::vector<std::string> > domain_;
|
||||
std::vector<std::vector<std::string> >::const_iterator position_;
|
||||
};
|
||||
|
||||
class MockNameIteratorContext : public IteratorContext {
|
||||
public:
|
||||
MockNameIteratorContext(const MockAccessor& mock_accessor, int zone_id,
|
||||
@@ -610,6 +649,17 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
virtual IteratorContextPtr getNSEC3Records(const std::string& hash,
|
||||
int) const
|
||||
{
|
||||
Domains::const_iterator it(nsec3_namespace_.find(hash));
|
||||
if (it == nsec3_namespace_.end()) {
|
||||
return (IteratorContextPtr(new EmptyIteratorContext()));
|
||||
} else {
|
||||
return (IteratorContextPtr(new DomainIterator(it->second)));
|
||||
}
|
||||
}
|
||||
|
||||
virtual pair<bool, int> startUpdateZone(const std::string& zone_name,
|
||||
bool replace)
|
||||
{
|
||||
@@ -747,6 +797,21 @@ public:
|
||||
isc_throw(isc::Unexpected, "Unknown zone ID");
|
||||
}
|
||||
}
|
||||
virtual std::string findPreviousNSEC3Hash(int,
|
||||
const std::string& hash) const
|
||||
{
|
||||
// TODO: Provide some broken data, but it is not known yet how broken
|
||||
// they'll have to be.
|
||||
Domains::const_iterator it(nsec3_namespace_.lower_bound(hash));
|
||||
// We got just after the one we want
|
||||
if (it == nsec3_namespace_.begin()) {
|
||||
// Hmm, we got something really small. So we wrap around.
|
||||
// This is one after the last, so after decreasing it we'll get
|
||||
// the biggest.
|
||||
it = nsec3_namespace_.end();
|
||||
}
|
||||
return ((--it)->first);
|
||||
}
|
||||
virtual void addRecordDiff(int id, uint32_t serial,
|
||||
DiffOperation operation,
|
||||
const std::string (&data)[DIFF_PARAM_COUNT])
|
||||
@@ -830,6 +895,9 @@ private:
|
||||
const Domains empty_records_master_;
|
||||
const Domains* empty_records_;
|
||||
|
||||
// The NSEC3 namespace. The above trick will be added once it is needed.
|
||||
Domains nsec3_namespace_;
|
||||
|
||||
// The journal data
|
||||
std::vector<JournalEntry> journal_entries_master_;
|
||||
std::vector<JournalEntry>* journal_entries_;
|
||||
@@ -890,6 +958,20 @@ private:
|
||||
cur_name_.clear();
|
||||
}
|
||||
|
||||
// Works in a similar way to addCurName, but it is added to
|
||||
// the NSEC3 namespace. You don't provide the full name, only
|
||||
// the hash part.
|
||||
void addCurHash(const std::string& hash) {
|
||||
ASSERT_EQ(0, nsec3_namespace_.count(hash));
|
||||
// Append the name to all of them
|
||||
for (std::vector<std::vector<std::string> >::iterator
|
||||
i = cur_name_.begin(); i != cur_name_.end(); ++ i) {
|
||||
i->push_back(hash);
|
||||
}
|
||||
(*readonly_records_)[hash] = cur_name_;
|
||||
cur_name_.clear();
|
||||
}
|
||||
|
||||
// Fills the database with zone data.
|
||||
// This method constructs a number of resource records (with addRecord),
|
||||
// which will all be added for one domain name to the fake database
|
||||
@@ -912,6 +994,17 @@ private:
|
||||
TEST_RECORDS[i][3], TEST_RECORDS[i][4]);
|
||||
}
|
||||
addCurName(prev_name);
|
||||
prev_name = NULL;
|
||||
for (int i = 0; TEST_NSEC3_RECORDS[i][0] != NULL; ++i) {
|
||||
if (prev_name != NULL &&
|
||||
strcmp(prev_name, TEST_NSEC3_RECORDS[i][0]) != 0) {
|
||||
addCurHash(prev_name);
|
||||
}
|
||||
prev_name = TEST_NSEC3_RECORDS[i][0];
|
||||
addRecord(TEST_NSEC3_RECORDS[i][1], TEST_NSEC3_RECORDS[i][2],
|
||||
TEST_NSEC3_RECORDS[i][3], TEST_NSEC3_RECORDS[i][4]);
|
||||
}
|
||||
addCurHash(prev_name);
|
||||
}
|
||||
};
|
||||
|
||||
|
@@ -173,6 +173,108 @@ TEST_F(SQLite3AccessorTest, iterator) {
|
||||
EXPECT_FALSE(context->getNext(data));
|
||||
}
|
||||
|
||||
// This tests getting NSEC3 records
|
||||
TEST_F(SQLite3AccessorTest, nsec3) {
|
||||
const std::pair<bool, int>
|
||||
zone_info(accessor->getZone("sql2.example.com."));
|
||||
ASSERT_TRUE(zone_info.first);
|
||||
|
||||
DatabaseAccessor::IteratorContextPtr
|
||||
context(accessor->getNSEC3Records("1BB7SO0452U1QHL98UISNDD9218GELR5",
|
||||
zone_info.second));
|
||||
// This relies on specific ordering in the DB. Is it OK?
|
||||
// The name field is empty, as well as the sigtype one. This is OK, as
|
||||
// both are not needed and the interface allows it.
|
||||
checkRR(context, "", "7200", "NSEC3",
|
||||
"1 0 10 FEEDABEE 4KLSVDE8KH8G95VU68R7AHBE1CPQN38J");
|
||||
checkRR(context, "", "7200", "RRSIG",
|
||||
"NSEC3 5 4 7200 20100410172647 20100311172647 63192 "
|
||||
"sql2.example.com. gNIVj4T8t51fEU6kOPpvK7HOGBFZGbalN5ZK "
|
||||
"mInyrww6UWZsUNdw07ge6/U6HfG+/s61RZ/L is2M6yUWHyXbNbj/"
|
||||
"QqwqgadG5dhxTArfuR02 xP600x0fWX8LXzW4yLMdKVxGbzYT+vvGz71o "
|
||||
"8gHSY5vYTtothcZQa4BMKhmGQEk=");
|
||||
|
||||
// And that's all
|
||||
std::string data[DatabaseAccessor::COLUMN_COUNT];
|
||||
EXPECT_FALSE(context->getNext(data));
|
||||
|
||||
// Calling again won't hurt
|
||||
EXPECT_FALSE(context->getNext(data));
|
||||
|
||||
// This one should be empty ‒ no data here
|
||||
context = accessor->getNSEC3Records("NO_SUCH_HASH", zone_info.second);
|
||||
EXPECT_FALSE(context->getNext(data));
|
||||
// Still nothing? ;-)
|
||||
EXPECT_FALSE(context->getNext(data));
|
||||
}
|
||||
|
||||
// This tests getting a previoeus hash in the NSEC3 namespace of a zone,
|
||||
// including a wrap-around and asking for a hash that does not exist in the.
|
||||
// zone at all.
|
||||
TEST_F(SQLite3AccessorTest, nsec3Previous) {
|
||||
// Get the zone
|
||||
const std::pair<bool, int>
|
||||
zone_info(accessor->getZone("sql2.example.com."));
|
||||
ASSERT_TRUE(zone_info.first);
|
||||
|
||||
std::string data[DatabaseAccessor::COLUMN_COUNT];
|
||||
|
||||
// Test a previous hash for something that is in the zone
|
||||
// (ensuring it is really there)
|
||||
DatabaseAccessor::IteratorContextPtr
|
||||
context(accessor->getNSEC3Records("703OOGCKF8VEV1N7U64D1JG19URETN8N",
|
||||
zone_info.second));
|
||||
EXPECT_TRUE(context->getNext(data));
|
||||
EXPECT_EQ("56IEQ664LHDAKVPE2FL179MSM3QAOFVC", accessor->
|
||||
findPreviousNSEC3Hash(zone_info.second,
|
||||
"703OOGCKF8VEV1N7U64D1JG19URETN8N"));
|
||||
|
||||
// Test a previous hash for something that is not in the
|
||||
// zone
|
||||
context = accessor->getNSEC3Records("702OOGCKF8VEV1N7U64D1JG19URETN8N",
|
||||
zone_info.second);
|
||||
EXPECT_FALSE(context->getNext(data));
|
||||
EXPECT_EQ("56IEQ664LHDAKVPE2FL179MSM3QAOFVC", accessor->
|
||||
findPreviousNSEC3Hash(zone_info.second,
|
||||
"702OOGCKF8VEV1N7U64D1JG19URETN8N"));
|
||||
|
||||
// Search at the first item, should wrap around
|
||||
context = accessor->getNSEC3Records("1BB7SO0452U1QHL98UISNDD9218GELR5",
|
||||
zone_info.second);
|
||||
EXPECT_TRUE(context->getNext(data));
|
||||
EXPECT_EQ("RKBUCQT8T78GV6QBCGBHCHC019LG73SJ", accessor->
|
||||
findPreviousNSEC3Hash(zone_info.second,
|
||||
"1BB7SO0452U1QHL98UISNDD9218GELR5"));
|
||||
|
||||
// Search before the first item, should wrap around
|
||||
context = accessor->getNSEC3Records("0BB7SO0452U1QHL98UISNDD9218GELR5",
|
||||
zone_info.second);
|
||||
EXPECT_FALSE(context->getNext(data));
|
||||
EXPECT_EQ("RKBUCQT8T78GV6QBCGBHCHC019LG73SJ", accessor->
|
||||
findPreviousNSEC3Hash(zone_info.second,
|
||||
"0BB7SO0452U1QHL98UISNDD9218GELR5"));
|
||||
|
||||
// Search after the last item (should return the last one)
|
||||
context = accessor->getNSEC3Records("RRBUCQT8T78GV6QBCGBHCHC019LG73SJ",
|
||||
zone_info.second);
|
||||
EXPECT_FALSE(context->getNext(data));
|
||||
EXPECT_EQ("RKBUCQT8T78GV6QBCGBHCHC019LG73SJ", accessor->
|
||||
findPreviousNSEC3Hash(zone_info.second,
|
||||
"RRBUCQT8T78GV6QBCGBHCHC019LG73SJ"));
|
||||
}
|
||||
|
||||
// Check it throws when we want a previous NSEC3 hash in an unsigned zone
|
||||
TEST_F(SQLite3AccessorTest, nsec3PreviousUnsigned) {
|
||||
// This zone did not look signed in the test file.
|
||||
const std::pair<bool, int>
|
||||
unsigned_zone_info(accessor->getZone("example.com."));
|
||||
|
||||
EXPECT_THROW(accessor->
|
||||
findPreviousNSEC3Hash(unsigned_zone_info.second,
|
||||
"0BB7SO0452U1QHL98UISNDD9218GELR5"),
|
||||
DataSourceError);
|
||||
}
|
||||
|
||||
// This tests the difference iterator context
|
||||
|
||||
// Test that at attempt to create a difference iterator for a serial number
|
||||
|
@@ -99,9 +99,8 @@ Hash::Hash(uint32_t tablesize, uint32_t maxkeylen, bool randomise) :
|
||||
|
||||
if (randomise) {
|
||||
init_value.curtime = time(NULL);
|
||||
}
|
||||
else {
|
||||
init_value.seed = 0;
|
||||
} else {
|
||||
init_value.seed = 1;
|
||||
}
|
||||
srandom(init_value.seed);
|
||||
|
||||
|
@@ -34,6 +34,7 @@ nodist_libresolve_la_SOURCES = resolve_messages.h resolve_messages.cc
|
||||
libresolve_la_LIBADD = $(top_builddir)/src/lib/dns/libdns++.la
|
||||
libresolve_la_LIBADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
|
||||
libresolve_la_LIBADD += $(top_builddir)/src/lib/log/liblog.la
|
||||
libresolve_la_LIBADD += $(top_builddir)/src/lib/asiodns/libasiodns.la
|
||||
|
||||
# The message file should be in the distribution.
|
||||
EXTRA_DIST = resolve_messages.mes
|
||||
|
@@ -128,7 +128,7 @@ typedef std::vector<std::pair<std::string, uint16_t> > AddressVector;
|
||||
// mishandles this in its name mangling, and wouldn't compile.
|
||||
// We can probably use a typedef, but need to move it to a central
|
||||
// location and use it consistently.
|
||||
RecursiveQuery::RecursiveQuery(DNSService& dns_service,
|
||||
RecursiveQuery::RecursiveQuery(DNSServiceBase& dns_service,
|
||||
isc::nsas::NameserverAddressStore& nsas,
|
||||
isc::cache::ResolverCache& cache,
|
||||
const std::vector<std::pair<std::string, uint16_t> >& upstream,
|
||||
|
@@ -87,7 +87,7 @@ public:
|
||||
/// \param lookup_timeout Timeout value for when we give up, in ms
|
||||
/// \param retries how many times we try again (0 means just send and
|
||||
/// and return if it returs).
|
||||
RecursiveQuery(DNSService& dns_service,
|
||||
RecursiveQuery(DNSServiceBase& dns_service,
|
||||
isc::nsas::NameserverAddressStore& nsas,
|
||||
isc::cache::ResolverCache& cache,
|
||||
const std::vector<std::pair<std::string, uint16_t> >&
|
||||
@@ -178,7 +178,7 @@ public:
|
||||
void setTestServer(const std::string& address, uint16_t port);
|
||||
|
||||
private:
|
||||
DNSService& dns_service_;
|
||||
DNSServiceBase& dns_service_;
|
||||
isc::nsas::NameserverAddressStore& nsas_;
|
||||
isc::cache::ResolverCache& cache_;
|
||||
boost::shared_ptr<std::vector<std::pair<std::string, uint16_t> > >
|
||||
|
@@ -31,11 +31,6 @@ namespace isc {
|
||||
namespace server_common {
|
||||
namespace portconfig {
|
||||
|
||||
// This flags disables pushing the sockets to the DNSService. It prevents
|
||||
// the clearServers() method to close the file descriptors we made up.
|
||||
// It is not presented in any header, but we use it from the tests anyway.
|
||||
bool test_mode(false);
|
||||
|
||||
AddressList
|
||||
parseAddresses(isc::data::ConstElementPtr addresses,
|
||||
const std::string& elemName)
|
||||
@@ -84,7 +79,9 @@ namespace {
|
||||
vector<string> current_sockets;
|
||||
|
||||
void
|
||||
setAddresses(DNSService& service, const AddressList& addresses) {
|
||||
setAddresses(DNSServiceBase& service, const AddressList& addresses,
|
||||
DNSService::ServerFlag server_options)
|
||||
{
|
||||
service.clearServers();
|
||||
BOOST_FOREACH(const string& token, current_sockets) {
|
||||
socketRequestor().releaseSocket(token);
|
||||
@@ -99,35 +96,32 @@ setAddresses(DNSService& service, const AddressList& addresses) {
|
||||
address.first, address.second,
|
||||
SocketRequestor::SHARE_SAME));
|
||||
current_sockets.push_back(tcp.second);
|
||||
if (!test_mode) {
|
||||
service.addServerTCPFromFD(tcp.first, af);
|
||||
}
|
||||
service.addServerTCPFromFD(tcp.first, af);
|
||||
const SocketRequestor::SocketID
|
||||
udp(socketRequestor().requestSocket(SocketRequestor::UDP,
|
||||
address.first, address.second,
|
||||
SocketRequestor::SHARE_SAME));
|
||||
current_sockets.push_back(udp.second);
|
||||
if (!test_mode) {
|
||||
service.addServerUDPFromFD(udp.first, af);
|
||||
}
|
||||
service.addServerUDPFromFD(udp.first, af, server_options);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void
|
||||
installListenAddresses(const AddressList& newAddresses,
|
||||
AddressList& addressStore,
|
||||
isc::asiodns::DNSService& service)
|
||||
installListenAddresses(const AddressList& new_addresses,
|
||||
AddressList& address_store,
|
||||
DNSServiceBase& service,
|
||||
DNSService::ServerFlag server_options)
|
||||
{
|
||||
try {
|
||||
LOG_DEBUG(logger, DBG_TRACE_BASIC, SRVCOMM_SET_LISTEN);
|
||||
BOOST_FOREACH(const AddressPair& addr, newAddresses) {
|
||||
BOOST_FOREACH(const AddressPair& addr, new_addresses) {
|
||||
LOG_DEBUG(logger, DBG_TRACE_VALUES, SRVCOMM_ADDRESS_VALUE).
|
||||
arg(addr.first).arg(addr.second);
|
||||
}
|
||||
setAddresses(service, newAddresses);
|
||||
addressStore = newAddresses;
|
||||
setAddresses(service, new_addresses, server_options);
|
||||
address_store = new_addresses;
|
||||
} catch (const SocketRequestor::NonFatalSocketError& e) {
|
||||
/*
|
||||
* If one of the addresses isn't set successfully, we will restore
|
||||
@@ -144,14 +138,14 @@ installListenAddresses(const AddressList& newAddresses,
|
||||
*/
|
||||
LOG_ERROR(logger, SRVCOMM_ADDRESS_FAIL).arg(e.what());
|
||||
try {
|
||||
setAddresses(service, addressStore);
|
||||
setAddresses(service, address_store, server_options);
|
||||
} catch (const SocketRequestor::NonFatalSocketError& e2) {
|
||||
LOG_FATAL(logger, SRVCOMM_ADDRESS_UNRECOVERABLE).arg(e2.what());
|
||||
// If we can't set the new ones, nor the old ones, at least
|
||||
// releasing everything should work. If it doesn't, there isn't
|
||||
// anything else we could do.
|
||||
setAddresses(service, AddressList());
|
||||
addressStore.clear();
|
||||
setAddresses(service, AddressList(), server_options);
|
||||
address_store.clear();
|
||||
}
|
||||
//Anyway the new configure has problem, we need to notify configure
|
||||
//manager the new configure doesn't work
|
||||
|
@@ -15,22 +15,15 @@
|
||||
#ifndef ISC_SERVER_COMMON_PORTCONFIG_H
|
||||
#define ISC_SERVER_COMMON_PORTCONFIG_H
|
||||
|
||||
#include <cc/data.h>
|
||||
|
||||
#include <asiodns/dns_service.h>
|
||||
|
||||
#include <utility>
|
||||
#include <string>
|
||||
#include <stdint.h>
|
||||
#include <vector>
|
||||
|
||||
#include <cc/data.h>
|
||||
|
||||
/*
|
||||
* Some forward declarations.
|
||||
*/
|
||||
namespace isc {
|
||||
namespace asiodns {
|
||||
class DNSService;
|
||||
}
|
||||
}
|
||||
|
||||
namespace isc {
|
||||
namespace server_common {
|
||||
/**
|
||||
@@ -88,42 +81,49 @@ AddressList
|
||||
parseAddresses(isc::data::ConstElementPtr addresses,
|
||||
const std::string& elemName);
|
||||
|
||||
/**
|
||||
* \brief Changes current listening addresses and ports.
|
||||
*
|
||||
* Removes all sockets we currently listen on and starts listening on the
|
||||
* addresses and ports requested in newAddresses.
|
||||
*
|
||||
* If it fails to set up the new addresses, it attempts to roll back to the
|
||||
* previous addresses (but it still propagates the exception). If the rollback
|
||||
* fails as well, it doesn't abort the application (to allow reconfiguration),
|
||||
* but removes all the sockets it listened on. One of the exceptions is
|
||||
* propagated.
|
||||
*
|
||||
* The ports are requested from the socket creator through boss. Therefore
|
||||
* you need to initialize the SocketRequestor before using this function.
|
||||
*
|
||||
* \param newAddresses are the addresses you want to listen on.
|
||||
* \param addressStore is the place you store your current addresses. It is
|
||||
* used when there's a need for rollback. The newAddresses are copied here
|
||||
* when the change is successful.
|
||||
* \param dnsService is the DNSService object we use now. The requests from
|
||||
* the new sockets are handled using this dnsService (and all current
|
||||
* sockets on the service are closed first).
|
||||
* \throw asiolink::IOError when initialization or closing of socket fails.
|
||||
* \throw isc::server_common::SocketRequestor::Socket error when the
|
||||
* boss/socket creator doesn't want to give us the socket.
|
||||
* \throw std::bad_alloc when allocation fails.
|
||||
* \throw isc::InvalidOperation when the function is called and the
|
||||
* SocketRequestor isn't initialized yet.
|
||||
*/
|
||||
/// \brief Changes current listening addresses and ports.
|
||||
///
|
||||
/// Removes all sockets we currently listen on and starts listening on the
|
||||
/// addresses and ports requested in new_addresses.
|
||||
///
|
||||
/// If it fails to set up the new addresses, it attempts to roll back to the
|
||||
/// previous addresses (but it still propagates the exception). If the rollback
|
||||
/// fails as well, it doesn't abort the application (to allow reconfiguration),
|
||||
/// but removes all the sockets it listened on. One of the exceptions is
|
||||
/// propagated.
|
||||
///
|
||||
/// The ports are requested from the socket creator through boss. Therefore
|
||||
/// you need to initialize the SocketRequestor before using this function.
|
||||
///
|
||||
/// \param new_addresses are the addresses you want to listen on.
|
||||
/// \param address_store is the place you store your current addresses. It is
|
||||
/// used when there's a need for rollback. The new_addresses are copied
|
||||
/// here when the change is successful.
|
||||
/// \param dns_service is the DNSService object we use now. The requests from
|
||||
/// the new sockets are handled using this dns_service (and all current
|
||||
/// sockets on the service are closed first).
|
||||
/// \param server_options specifies optional properties for the servers
|
||||
/// created via \c dns_service.
|
||||
///
|
||||
/// \throw asiolink::IOError when initialization or closing of socket fails.
|
||||
/// \throw isc::server_common::SocketRequestor::Socket error when the
|
||||
/// boss/socket creator doesn't want to give us the socket.
|
||||
/// \throw std::bad_alloc when allocation fails.
|
||||
/// \throw isc::InvalidOperation when the function is called and the
|
||||
/// SocketRequestor isn't initialized yet.
|
||||
void
|
||||
installListenAddresses(const AddressList& newAddresses,
|
||||
AddressList& addressStore,
|
||||
asiodns::DNSService& dnsService);
|
||||
installListenAddresses(const AddressList& new_addresses,
|
||||
AddressList& address_store,
|
||||
asiodns::DNSServiceBase& dns_service,
|
||||
asiodns::DNSService::ServerFlag server_options =
|
||||
asiodns::DNSService::SERVER_DEFAULT);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
// Local Variables:
|
||||
// mode: c++
|
||||
// End:
|
||||
|
@@ -14,6 +14,7 @@
|
||||
|
||||
#include <server_common/portconfig.h>
|
||||
#include <testutils/socket_request.h>
|
||||
#include <testutils/mockups.h>
|
||||
|
||||
#include <cc/data.h>
|
||||
#include <exceptions/exceptions.h>
|
||||
@@ -30,6 +31,7 @@ using namespace isc;
|
||||
using namespace std;
|
||||
using namespace isc::asiolink;
|
||||
using namespace isc::asiodns;
|
||||
using namespace isc::testutils;
|
||||
using boost::lexical_cast;
|
||||
|
||||
namespace {
|
||||
@@ -132,7 +134,6 @@ TEST_F(ParseAddresses, invalid) {
|
||||
// Test fixture for installListenAddresses
|
||||
struct InstallListenAddresses : public ::testing::Test {
|
||||
InstallListenAddresses() :
|
||||
dnss_(ios_, NULL, NULL, NULL),
|
||||
// The empty string is expected parameter of requestSocket,
|
||||
// not app_name - the request does not fall back to this, it
|
||||
// is checked to be the same.
|
||||
@@ -143,8 +144,7 @@ struct InstallListenAddresses : public ::testing::Test {
|
||||
invalid_.push_back(AddressPair("127.0.0.1", 5288));
|
||||
invalid_.push_back(AddressPair("192.0.2.2", 1));
|
||||
}
|
||||
IOService ios_;
|
||||
DNSService dnss_;
|
||||
MockDNSService dnss_;
|
||||
AddressList store_;
|
||||
isc::testutils::TestSocketRequestor sock_requestor_;
|
||||
// We should be able to bind to these addresses
|
||||
|
@@ -12,6 +12,9 @@
|
||||
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
#ifndef __ISC_TESTUTILS_DNSMESSAGETEST_H
|
||||
#define __ISC_TESTUTILS_DNSMESSAGETEST_H 1
|
||||
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include <iosfwd>
|
||||
@@ -339,6 +342,7 @@ rrsetsCheck(const std::string& expected,
|
||||
|
||||
} // end of namespace testutils
|
||||
} // end of namespace isc
|
||||
#endif // __ISC_TESTUTILS_DNSMESSAGETEST_H
|
||||
|
||||
// Local Variables:
|
||||
// mode: c++
|
||||
|
@@ -12,8 +12,13 @@
|
||||
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
#ifndef __ISC_TESTUTILS_MOCKUPS_H
|
||||
#define __ISC_TESTUTILS_MOCKUPS_H 1
|
||||
|
||||
#include <config.h>
|
||||
|
||||
#include <exceptions/exceptions.h>
|
||||
|
||||
#include <cc/data.h>
|
||||
#include <cc/session.h>
|
||||
|
||||
@@ -21,6 +26,12 @@
|
||||
|
||||
#include <asiodns/asiodns.h>
|
||||
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace isc {
|
||||
namespace testutils {
|
||||
|
||||
// A minimal mock configuration session. Most the methods are
|
||||
// stubbed out, except for a very basic group_sendmsg() and
|
||||
// group_recvmsg(). hasQueuedMessages() always returns false.
|
||||
@@ -93,6 +104,45 @@ private:
|
||||
bool receive_ok_;
|
||||
};
|
||||
|
||||
// This mock object does nothing except for recording passed parameters
|
||||
// to addServerXXX methods so the test code subsequently checks the parameters.
|
||||
class MockDNSService : public isc::asiodns::DNSServiceBase {
|
||||
public:
|
||||
// A helper tuple of parameters passed to addServerUDPFromFD().
|
||||
struct UDPFdParams {
|
||||
int fd;
|
||||
int af;
|
||||
ServerFlag options;
|
||||
};
|
||||
|
||||
virtual void addServerTCPFromFD(int fd, int af) {
|
||||
tcp_fd_params_.push_back(std::pair<int, int>(fd, af));
|
||||
}
|
||||
virtual void addServerUDPFromFD(int fd, int af, ServerFlag options) {
|
||||
UDPFdParams params = { fd, af, options };
|
||||
udp_fd_params_.push_back(params);
|
||||
}
|
||||
virtual void clearServers() {}
|
||||
|
||||
virtual asiolink::IOService& getIOService() {
|
||||
isc_throw(isc::Unexpected,
|
||||
"MockDNSService::getIOService() shouldn't be called");
|
||||
}
|
||||
|
||||
// These two allow the tests to check how the servers have been created
|
||||
// through this object.
|
||||
const std::vector<std::pair<int, int> >& getTCPFdParams() const {
|
||||
return (tcp_fd_params_);
|
||||
}
|
||||
const std::vector<UDPFdParams>& getUDPFdParams() const {
|
||||
return (udp_fd_params_);
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<std::pair<int, int> > tcp_fd_params_;
|
||||
std::vector<UDPFdParams> udp_fd_params_;
|
||||
};
|
||||
|
||||
// A nonoperative DNSServer object to be used in calls to processMessage().
|
||||
class MockServer : public isc::asiodns::DNSServer {
|
||||
public:
|
||||
@@ -149,3 +199,10 @@ private:
|
||||
bool disconnect_ok_;
|
||||
};
|
||||
|
||||
} // end of testutils
|
||||
} // end of isc
|
||||
#endif // __ISC_TESTUTILS_MOCKUPS_H
|
||||
|
||||
// Local Variables:
|
||||
// mode: c++
|
||||
// End:
|
||||
|
@@ -12,8 +12,8 @@
|
||||
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
#ifndef TESTUTILS_PORTCONFIG_H
|
||||
#define TESTUTILS_PORTCONFIG_H
|
||||
#ifndef __ISC_TESTUTILS_PORTCONFIG_H
|
||||
#define __ISC_TESTUTILS_PORTCONFIG_H
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <cc/data.h>
|
||||
@@ -186,4 +186,4 @@ invalidListenAddressConfig(Server& server) {
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
#endif // __ISC_TESTUTILS_PORTCONFIG_H
|
||||
|
@@ -12,6 +12,9 @@
|
||||
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
#ifndef __ISC_TESTUTILS_SOCKETREQUEST_H
|
||||
#define __ISC_TESTUTILS_SOCKETREQUEST_H 1
|
||||
|
||||
#include <server_common/socket_request.h>
|
||||
#include <server_common/portconfig.h>
|
||||
|
||||
@@ -24,13 +27,6 @@
|
||||
#include <string>
|
||||
|
||||
namespace isc {
|
||||
namespace server_common {
|
||||
namespace portconfig {
|
||||
// Access the private hidden flag
|
||||
extern bool test_mode;
|
||||
}
|
||||
}
|
||||
|
||||
namespace testutils {
|
||||
|
||||
/// \brief A testcase part for faking the SocketRequestor in tests
|
||||
@@ -64,7 +60,7 @@ public:
|
||||
/// not fall back to this value if its share_name is left empty, if
|
||||
/// you want to check the code relies on the requestor to use the
|
||||
/// app name, you set this to empty string.
|
||||
TestSocketRequestor(asiodns::DNSService& dnss,
|
||||
TestSocketRequestor(asiodns::DNSServiceBase& dnss,
|
||||
server_common::portconfig::AddressList& store,
|
||||
uint16_t expect_port,
|
||||
const std::string& expected_app) :
|
||||
@@ -74,8 +70,6 @@ public:
|
||||
{
|
||||
// Prepare the requestor (us) for the test
|
||||
server_common::initTestSocketRequestor(this);
|
||||
// Don't manipulate the real sockets
|
||||
server_common::portconfig::test_mode = true;
|
||||
}
|
||||
|
||||
/// \brief Destructor
|
||||
@@ -90,8 +84,6 @@ public:
|
||||
server_common::portconfig::installListenAddresses(list, store_, dnss_);
|
||||
// Don't leave invalid pointers here
|
||||
server_common::initTestSocketRequestor(NULL);
|
||||
// And return the mode
|
||||
server_common::portconfig::test_mode = false;
|
||||
}
|
||||
|
||||
/// \brief Tokens released by releaseSocket
|
||||
@@ -216,7 +208,7 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
asiodns::DNSService& dnss_;
|
||||
asiodns::DNSServiceBase& dnss_;
|
||||
server_common::portconfig::AddressList& store_;
|
||||
const uint16_t expect_port_;
|
||||
const std::string expected_app_;
|
||||
@@ -224,3 +216,4 @@ private:
|
||||
|
||||
}
|
||||
}
|
||||
#endif // __ISC_TESTUTILS_SOCKETREQUEST_H
|
||||
|
@@ -12,6 +12,9 @@
|
||||
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
#ifndef __ISC_TESTUTILS_SRVTEST_H
|
||||
#define __ISC_TESTUTILS_SRVTEST_H 1
|
||||
|
||||
#include <util/buffer.h>
|
||||
#include <dns/name.h>
|
||||
#include <dns/message.h>
|
||||
@@ -106,6 +109,7 @@ protected:
|
||||
};
|
||||
} // end of namespace testutils
|
||||
} // end of namespace isc
|
||||
#endif // __ISC_TESTUTILS_SRVTEST_H
|
||||
|
||||
// Local Variables:
|
||||
// mode: c++
|
||||
|
@@ -25,9 +25,12 @@ status=0
|
||||
n=0
|
||||
|
||||
# TODO: consider consistency with statistics definition in auth.spec
|
||||
auth_queries_tcp="\<queries\.tcp\>"
|
||||
auth_queries_udp="\<queries\.udp\>"
|
||||
auth_opcode_queries="\<opcode\.query\>"
|
||||
cnt_name1="\<queries\.tcp\>"
|
||||
cnt_name2="\<queries\.udp\>"
|
||||
cnt_name3="\<opcode\.query\>"
|
||||
cnt_value1=0
|
||||
cnt_value2=0
|
||||
cnt_value3=0
|
||||
|
||||
echo "I:Checking b10-auth is working by default ($n)"
|
||||
$DIG +norec @10.53.0.1 -p 53210 ns.example.com. A >dig.out.$n || status=1
|
||||
@@ -45,9 +48,12 @@ echo 'Stats show
|
||||
--csv-file-dir=$BINDCTL_CSV_DIR > bindctl.out.$n || status=1
|
||||
# the server should have received 1 UDP and 1 TCP queries (TCP query was
|
||||
# sent from the server startup script)
|
||||
grep $auth_queries_tcp".*\<1\>" bindctl.out.$n > /dev/null || status=1
|
||||
grep $auth_queries_udp".*\<1\>" bindctl.out.$n > /dev/null || status=1
|
||||
grep $auth_opcode_queries".*\<2\>" bindctl.out.$n > /dev/null || status=1
|
||||
cnt_value1=`expr $cnt_value1 + 1`
|
||||
cnt_value2=`expr $cnt_value2 + 1`
|
||||
cnt_value3=`expr $cnt_value1 + $cnt_value2`
|
||||
grep $cnt_name1".*\<"$cnt_value1"\>" bindctl.out.$n > /dev/null || status=1
|
||||
grep $cnt_name2".*\<"$cnt_value2"\>" bindctl.out.$n > /dev/null || status=1
|
||||
grep $cnt_name3".*\<"$cnt_value3"\>" bindctl.out.$n > /dev/null || status=1
|
||||
if [ $status != 0 ]; then echo "I:failed"; fi
|
||||
n=`expr $n + 1`
|
||||
|
||||
@@ -80,9 +86,12 @@ echo 'Stats show
|
||||
' | $RUN_BINDCTL \
|
||||
--csv-file-dir=$BINDCTL_CSV_DIR > bindctl.out.$n || status=1
|
||||
# The statistics counters should have been reset while stop/start.
|
||||
grep $auth_queries_tcp".*\<0\>" bindctl.out.$n > /dev/null || status=1
|
||||
grep $auth_queries_udp".*\<1\>" bindctl.out.$n > /dev/null || status=1
|
||||
grep $auth_opcode_queries".*\<1\>" bindctl.out.$n > /dev/null || status=1
|
||||
cnt_value1=0
|
||||
cnt_value2=1
|
||||
cnt_value3=`expr $cnt_value1 + $cnt_value2`
|
||||
grep $cnt_name1".*\<"$cnt_value1"\>" bindctl.out.$n > /dev/null || status=1
|
||||
grep $cnt_name2".*\<"$cnt_value2"\>" bindctl.out.$n > /dev/null || status=1
|
||||
grep $cnt_name3".*\<"$cnt_value3"\>" bindctl.out.$n > /dev/null || status=1
|
||||
if [ $status != 0 ]; then echo "I:failed"; fi
|
||||
n=`expr $n + 1`
|
||||
|
||||
@@ -105,11 +114,68 @@ echo 'Stats show
|
||||
' | $RUN_BINDCTL \
|
||||
--csv-file-dir=$BINDCTL_CSV_DIR > bindctl.out.$n || status=1
|
||||
# The statistics counters shouldn't be reset due to hot-swapping datasource.
|
||||
grep $auth_queries_tcp".*\<0\>" bindctl.out.$n > /dev/null || status=1
|
||||
grep $auth_queries_udp".*\<2\>" bindctl.out.$n > /dev/null || status=1
|
||||
grep $auth_opcode_queries".*\<2\>" bindctl.out.$n > /dev/null || status=1
|
||||
cnt_value1=`expr $cnt_value1 + 0`
|
||||
cnt_value2=`expr $cnt_value2 + 1`
|
||||
cnt_value3=`expr $cnt_value1 + $cnt_value2`
|
||||
grep $cnt_name1".*\<"$cnt_value1"\>" bindctl.out.$n > /dev/null || status=1
|
||||
grep $cnt_name2".*\<"$cnt_value2"\>" bindctl.out.$n > /dev/null || status=1
|
||||
grep $cnt_name3".*\<"$cnt_value3"\>" bindctl.out.$n > /dev/null || status=1
|
||||
if [ $status != 0 ]; then echo "I:failed"; fi
|
||||
n=`expr $n + 1`
|
||||
|
||||
echo "I:Starting more b10-auths and checking that ($n)"
|
||||
for i in 2 3
|
||||
do
|
||||
echo 'config add Boss/components b10-auth-'$i'
|
||||
config set Boss/components/b10-auth-'$i' { "special": "auth", "kind": "needed" }
|
||||
config commit
|
||||
quit
|
||||
' | $RUN_BINDCTL \
|
||||
--csv-file-dir=$BINDCTL_CSV_DIR 2>&1 > /dev/null || status=1
|
||||
done
|
||||
$DIG +norec @10.53.0.1 -p 53210 ns.example.com. A >dig.out.$n || status=1
|
||||
grep 192.0.2.2 dig.out.$n > /dev/null || status=1
|
||||
if [ $status != 0 ]; then echo "I:failed"; fi
|
||||
n=`expr $n + 1`
|
||||
|
||||
echo "I:Rechecking BIND 10 statistics consistency after a pause ($n)"
|
||||
sleep 2
|
||||
cnt_value1=`expr $cnt_value1 + 0`
|
||||
cnt_value2=`expr $cnt_value2 + 1`
|
||||
cnt_value3=`expr $cnt_value1 + $cnt_value2`
|
||||
# Rechecking some times
|
||||
for i in 1 2 3 4
|
||||
do
|
||||
echo 'Stats show
|
||||
' | $RUN_BINDCTL \
|
||||
--csv-file-dir=$BINDCTL_CSV_DIR > bindctl.out.$n || status=1
|
||||
# The statistics counters should keep being consistent even while
|
||||
# multiple b10-auths are running.
|
||||
grep $cnt_name1".*\<"$cnt_value1"\>" bindctl.out.$n > /dev/null || status=1
|
||||
grep $cnt_name2".*\<"$cnt_value2"\>" bindctl.out.$n > /dev/null || status=1
|
||||
grep $cnt_name3".*\<"$cnt_value3"\>" bindctl.out.$n > /dev/null || status=1
|
||||
if [ $status != 0 ]; then echo "I:failed "; break ; fi
|
||||
done
|
||||
n=`expr $n + 1`
|
||||
|
||||
echo "I:Stopping extra b10-auths and checking that ($n)"
|
||||
for i in 3 2
|
||||
do
|
||||
echo 'config remove Boss/components b10-auth-'$i'
|
||||
config commit
|
||||
quit
|
||||
' | $RUN_BINDCTL \
|
||||
--csv-file-dir=$BINDCTL_CSV_DIR 2>&1 > /dev/null || status=1
|
||||
done
|
||||
$DIG +norec @10.53.0.1 -p 53210 ns.example.com. A >dig.out.$n || status=1
|
||||
grep 192.0.2.2 dig.out.$n > /dev/null || status=1
|
||||
if [ $status != 0 ]; then echo "I:failed"; fi
|
||||
n=`expr $n + 1`
|
||||
|
||||
# The statistics counters can not be rechecked here because the auth
|
||||
# instance seems to hang up after one of the multiple auth instances
|
||||
# was removed via bindctl. This reason seems to be the same reason as
|
||||
# #1703.
|
||||
|
||||
echo "I:exit status: $status"
|
||||
exit $status
|
||||
|
@@ -66,6 +66,35 @@ main(const int argc, char* const argv[])
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#ifndef HAVE_PSELECT
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
/* Platforms such as OpenBSD don't provide a pselect(), so we use our
|
||||
own implementation for this testcase, which wraps around select() and
|
||||
hence doesn't implement the high precision timer. This implementation
|
||||
is fine for our purpose. */
|
||||
|
||||
static int
|
||||
pselect (int nfds, fd_set *readfds, fd_set *writefds,
|
||||
fd_set *exceptfds, const struct timespec *timeout,
|
||||
const sigset_t *sigmask)
|
||||
{
|
||||
struct timeval my_timeout;
|
||||
|
||||
/* Our particular usage of pselect() doesn't use these fields. */
|
||||
assert(writefds == NULL);
|
||||
assert(exceptfds == NULL);
|
||||
assert(sigmask == NULL);
|
||||
|
||||
my_timeout.tv_sec = timeout->tv_sec;
|
||||
my_timeout.tv_usec = timeout->tv_nsec / 1000;
|
||||
|
||||
return (select(nfds, readfds, writefds, exceptfds, &my_timeout));
|
||||
}
|
||||
|
||||
#endif /* HAVE_PSELECT */
|
||||
|
||||
/* DHCPv4 defines (to be moved/shared) */
|
||||
|
||||
#define DHCP_OFF_OPCODE 0
|
||||
|
Reference in New Issue
Block a user