BIND 10 master, updated. 19a98f3b7abb7ec47957f9143628174f61e3f590 [master] removed some LDADD deps that are now unneeded
BIND 10 source code commits
bind10-changes at lists.isc.org
Fri Mar 11 15:30:18 UTC 2011
The branch, master has been updated
via 19a98f3b7abb7ec47957f9143628174f61e3f590 (commit)
via 76022a7e9f3ff339f0f9f10049aa85e5784d72c5 (commit)
via 454739d105be01d7b9711038ca68271d0f4fd02c (commit)
via 87308eeddf767dea581b817d1d2ab2aaa4a99dd3 (commit)
via e1db14abce5e64985340e4ebb9eb4e7bee56763e (commit)
via 6483bb374e7ac88e3b736f08bf11e292605aaeef (commit)
via a3d81c1479c88a8ba26941e351fce1b0b1e3a2ca (commit)
via 895e8df77e0d3f8957f3b518052ff874f67bb8b8 (commit)
via cb13c22e8b2464d903d2254dec4d46c1103cf5ea (commit)
via 68c33a7725a2e41dab53b4d74e2aedec79090380 (commit)
via 860be372d776c05c9535790257812dbde1b9f74f (commit)
via b73ea473019bceeab7aab78ddc5d1c4a7710a8bd (commit)
via cac094e7e14a483bbf84394adf55a86d70097188 (commit)
via 38f2d6e49c7f693c55e5d27b3b247a167895826c (commit)
via 120a7ea6efb5ba35008ed9b3502846f4b8fb2ed8 (commit)
via 28c720f2b0319ee8b2ee21cea1105e411a31360c (commit)
via d82bd9a601e95a301e268c21a8ddcfea560d38dc (commit)
via 19603be4d345c061cb2345187cfed58a785bf03a (commit)
from 11889ab357ea40764683e4be03bcc27a4ab6bb04 (commit)
Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.
- Log -----------------------------------------------------------------
commit 19a98f3b7abb7ec47957f9143628174f61e3f590
Author: Jelte Jansen <jelte at isc.org>
Date: Fri Mar 11 16:29:54 2011 +0100
[master] removed some LDADD deps that are now unneeded
commit 76022a7e9f3ff339f0f9f10049aa85e5784d72c5
Merge: 11889ab357ea40764683e4be03bcc27a4ab6bb04 454739d105be01d7b9711038ca68271d0f4fd02c
Author: Jelte Jansen <jelte at isc.org>
Date: Fri Mar 11 16:11:46 2011 +0100
[master] Merge branch 'trac495'
Conflicts:
src/lib/Makefile.am
src/lib/asiolink/Makefile.am
src/lib/asiolink/tcp_socket.h
src/lib/asiolink/tests/Makefile.am
src/lib/nsas/asiolink.h
src/lib/resolve/recursive_query.cc
-----------------------------------------------------------------------
Summary of changes:
ext/asio/asio/detail/epoll_reactor.hpp | 2 +-
src/bin/resolver/Makefile.am | 1 +
src/bin/resolver/main.cc | 53 ++-
src/bin/resolver/resolver.cc | 28 +-
src/bin/resolver/resolver.h | 21 +
src/bin/resolver/tests/Makefile.am | 1 +
src/lib/Makefile.am | 2 +-
src/lib/asiolink/Makefile.am | 14 +-
src/lib/asiolink/asiolink.h | 1 -
src/lib/asiolink/tcp_socket.h | 4 +-
src/lib/asiolink/tests/Makefile.am | 2 -
src/lib/cache/tests/Makefile.am | 1 +
src/lib/nsas/address_entry.h | 4 +-
src/lib/nsas/asiolink.h | 37 --
src/lib/nsas/nameserver_address.cc | 4 +-
src/lib/nsas/nameserver_address_store.cc | 13 +
src/lib/nsas/nameserver_address_store.h | 7 +
src/lib/nsas/nameserver_entry.cc | 9 +-
src/lib/nsas/tests/Makefile.am | 1 +
src/lib/nsas/tests/address_entry_unittest.cc | 4 +-
src/lib/nsas/tests/nameserver_address_unittest.cc | 2 +-
src/lib/nsas/tests/nameserver_entry_unittest.cc | 4 +-
src/lib/nsas/tests/zone_entry_unittest.cc | 32 +-
src/lib/nsas/zone_entry.cc | 12 +
src/lib/nsas/zone_entry.h | 9 +
src/lib/resolve/Makefile.am | 1 +
src/lib/{asiolink => resolve}/recursive_query.cc | 484 +++++++++++++-------
src/lib/{asiolink => resolve}/recursive_query.h | 14 +-
src/lib/resolve/response_classifier.cc | 10 +-
src/lib/resolve/response_classifier.h | 1 +
src/lib/resolve/tests/Makefile.am | 7 +
.../tests/recursive_query_unittest.cc | 95 ++++-
.../tests/recursive_query_unittest_2.cc | 22 +-
.../resolve/tests/response_classifier_unittest.cc | 17 +
34 files changed, 641 insertions(+), 278 deletions(-)
rename src/lib/{asiolink => resolve}/recursive_query.cc (54%)
rename src/lib/{asiolink => resolve}/recursive_query.h (94%)
rename src/lib/{asiolink => resolve}/tests/recursive_query_unittest.cc (90%)
rename src/lib/{asiolink => resolve}/tests/recursive_query_unittest_2.cc (97%)
-----------------------------------------------------------------------
diff --git a/ext/asio/asio/detail/epoll_reactor.hpp b/ext/asio/asio/detail/epoll_reactor.hpp
index 6367398..0a1eac0 100644
--- a/ext/asio/asio/detail/epoll_reactor.hpp
+++ b/ext/asio/asio/detail/epoll_reactor.hpp
@@ -207,7 +207,7 @@ public:
// Cancel all operations associated with the given descriptor. The
// handlers associated with the descriptor will be invoked with the
// operation_aborted error.
- void cancel_ops(socket_type descriptor, per_descriptor_data& descriptor_data)
+ void cancel_ops(socket_type, per_descriptor_data& descriptor_data)
{
mutex::scoped_lock descriptor_lock(descriptor_data->mutex_);
diff --git a/src/bin/resolver/Makefile.am b/src/bin/resolver/Makefile.am
index 36b8551..54e15bd 100644
--- a/src/bin/resolver/Makefile.am
+++ b/src/bin/resolver/Makefile.am
@@ -51,6 +51,7 @@ b10_resolver_LDADD += $(top_builddir)/src/lib/log/liblog.la
b10_resolver_LDADD += $(top_builddir)/src/lib/server_common/libserver_common.la
b10_resolver_LDADD += $(top_builddir)/src/lib/cache/libcache.la
b10_resolver_LDADD += $(top_builddir)/src/lib/nsas/libnsas.la
+b10_resolver_LDADD += $(top_builddir)/src/lib/resolve/libresolve.la
b10_resolver_LDADD += $(top_builddir)/src/bin/auth/change_user.o
b10_resolver_LDFLAGS = -pthread
diff --git a/src/bin/resolver/main.cc b/src/bin/resolver/main.cc
index d987c74..bbb7d13 100644
--- a/src/bin/resolver/main.cc
+++ b/src/bin/resolver/main.cc
@@ -45,6 +45,9 @@
#include <resolver/spec_config.h>
#include <resolver/resolver.h>
+#include <cache/resolver_cache.h>
+#include <nsas/nameserver_address_store.h>
+
#include <log/dummylog.h>
using namespace std;
@@ -59,7 +62,7 @@ namespace {
static const string PROGRAM = "Resolver";
IOService io_service;
-static Resolver *resolver;
+static boost::shared_ptr<Resolver> resolver;
ConstElementPtr
my_config_handler(ConstElementPtr new_config) {
@@ -135,15 +138,58 @@ main(int argc, char* argv[]) {
specfile = string(RESOLVER_SPECFILE_LOCATION);
}
- resolver = new Resolver();
+ resolver = boost::shared_ptr<Resolver>(new Resolver());
dlog("Server created.");
SimpleCallback* checkin = resolver->getCheckinProvider();
DNSLookup* lookup = resolver->getDNSLookupProvider();
DNSAnswer* answer = resolver->getDNSAnswerProvider();
+ isc::nsas::NameserverAddressStore nsas(resolver);
+ resolver->setNameserverAddressStore(nsas);
+
+ isc::cache::ResolverCache cache;
+ resolver->setCache(cache);
+
+ // TODO priming query, remove root from direct
+ // Fake a priming query result here (TODO2 how to flag non-expiry?)
+ // propagation to runningquery. And check for forwarder mode?
+ isc::dns::QuestionPtr root_question(new isc::dns::Question(
+ isc::dns::Name("."),
+ isc::dns::RRClass::IN(),
+ isc::dns::RRType::NS()));
+ isc::dns::RRsetPtr root_ns_rrset(new isc::dns::RRset(isc::dns::Name("."),
+ isc::dns::RRClass::IN(),
+ isc::dns::RRType::NS(),
+ isc::dns::RRTTL(8888)));
+ root_ns_rrset->addRdata(isc::dns::rdata::createRdata(isc::dns::RRType::NS(),
+ isc::dns::RRClass::IN(),
+ "l.root-servers.net."));
+ isc::dns::RRsetPtr root_a_rrset(new isc::dns::RRset(isc::dns::Name("l.root-servers.net"),
+ isc::dns::RRClass::IN(),
+ isc::dns::RRType::A(),
+ isc::dns::RRTTL(8888)));
+ root_a_rrset->addRdata(isc::dns::rdata::createRdata(isc::dns::RRType::A(),
+ isc::dns::RRClass::IN(),
+ "199.7.83.42"));
+ isc::dns::RRsetPtr root_aaaa_rrset(new isc::dns::RRset(isc::dns::Name("l.root-servers.net"),
+ isc::dns::RRClass::IN(),
+ isc::dns::RRType::AAAA(),
+ isc::dns::RRTTL(8888)));
+ root_aaaa_rrset->addRdata(isc::dns::rdata::createRdata(isc::dns::RRType::AAAA(),
+ isc::dns::RRClass::IN(),
+ "2001:500:3::42"));
+ isc::dns::MessagePtr priming_result(new isc::dns::Message(isc::dns::Message::RENDER));
+ priming_result->addQuestion(root_question);
+ priming_result->addRRset(isc::dns::Message::SECTION_ANSWER, root_ns_rrset);
+ priming_result->addRRset(isc::dns::Message::SECTION_ADDITIONAL, root_a_rrset);
+ priming_result->addRRset(isc::dns::Message::SECTION_ADDITIONAL, root_aaaa_rrset);
+ cache.update(*priming_result);
+ cache.update(root_ns_rrset);
+ cache.update(root_a_rrset);
+ cache.update(root_aaaa_rrset);
+
DNSService dns_service(io_service, checkin, lookup, answer);
-
resolver->setDNSService(dns_service);
dlog("IOService created.");
@@ -172,7 +218,6 @@ main(int argc, char* argv[]) {
delete config_session;
delete cc_session;
- delete resolver;
return (ret);
}
diff --git a/src/bin/resolver/resolver.cc b/src/bin/resolver/resolver.cc
index 84df9d2..7656e2b 100644
--- a/src/bin/resolver/resolver.cc
+++ b/src/bin/resolver/resolver.cc
@@ -41,6 +41,8 @@
#include <dns/messagerenderer.h>
#include <server_common/portconfig.h>
+#include <resolve/recursive_query.h>
+
#include <log/dummylog.h>
#include <resolver/resolver.h>
@@ -74,10 +76,15 @@ public:
queryShutdown();
}
- void querySetup(DNSService& dnss) {
+ void querySetup(DNSService& dnss,
+ isc::nsas::NameserverAddressStore& nsas,
+ isc::cache::ResolverCache& cache)
+ {
assert(!rec_query_); // queryShutdown must be called first
dlog("Query setup");
- rec_query_ = new RecursiveQuery(dnss, upstream_,
+ rec_query_ = new RecursiveQuery(dnss,
+ nsas, cache,
+ upstream_,
upstream_root_,
query_timeout_,
client_timeout_,
@@ -129,7 +136,7 @@ public:
}
}
}
-
+
void resolve(const isc::dns::QuestionPtr& question,
const isc::resolve::ResolverInterface::CallbackPtr& callback);
@@ -342,6 +349,19 @@ Resolver::setDNSService(asiolink::DNSService& dnss) {
}
void
+Resolver::setNameserverAddressStore(isc::nsas::NameserverAddressStore& nsas)
+{
+ nsas_ = &nsas;
+}
+
+void
+Resolver::setCache(isc::cache::ResolverCache& cache)
+{
+ cache_ = &cache;
+}
+
+
+void
Resolver::setConfigSession(ModuleCCSession* config_session) {
impl_->config_session_ = config_session;
}
@@ -544,7 +564,7 @@ Resolver::updateConfig(ConstElementPtr config) {
if (need_query_restart) {
impl_->queryShutdown();
- impl_->querySetup(*dnss_);
+ impl_->querySetup(*dnss_, *nsas_, *cache_);
}
return (isc::config::createAnswer());
} catch (const isc::Exception& error) {
diff --git a/src/bin/resolver/resolver.h b/src/bin/resolver/resolver.h
index 2ae8079..d851656 100644
--- a/src/bin/resolver/resolver.h
+++ b/src/bin/resolver/resolver.h
@@ -24,6 +24,9 @@
#include <asiolink/asiolink.h>
+#include <nsas/nameserver_address_store.h>
+#include <cache/resolver_cache.h>
+
#include <resolve/resolver_interface.h>
class ResolverImpl;
@@ -86,10 +89,26 @@ public:
/// \brief Assign an ASIO IO Service queue to this Resolver object
void setDNSService(asiolink::DNSService& dnss);
+
+ /// \brief Assign a NameserverAddressStore to this Resolver object
+ void setNameserverAddressStore(isc::nsas::NameserverAddressStore &nsas);
+
+ /// \brief Assign a cache to this Resolver object
+ void setCache(isc::cache::ResolverCache& cache);
/// \brief Return this object's ASIO IO Service queue
asiolink::DNSService& getDNSService() const { return (*dnss_); }
+ /// \brief Returns this object's NSAS
+ isc::nsas::NameserverAddressStore& getNameserverAddressStore() const {
+ return *nsas_;
+ };
+
+ /// \brief Returns this object's ResolverCache
+ isc::cache::ResolverCache& getResolverCache() const {
+ return *cache_;
+ };
+
/// \brief Return pointer to the DNS Lookup callback function
asiolink::DNSLookup* getDNSLookupProvider() { return (dns_lookup_); }
@@ -208,6 +227,8 @@ private:
asiolink::SimpleCallback* checkin_;
asiolink::DNSLookup* dns_lookup_;
asiolink::DNSAnswer* dns_answer_;
+ isc::nsas::NameserverAddressStore* nsas_;
+ isc::cache::ResolverCache* cache_;
};
#endif // __RESOLVER_H
diff --git a/src/bin/resolver/tests/Makefile.am b/src/bin/resolver/tests/Makefile.am
index eb7e3e1..b85c223 100644
--- a/src/bin/resolver/tests/Makefile.am
+++ b/src/bin/resolver/tests/Makefile.am
@@ -40,6 +40,7 @@ run_unittests_LDADD += $(top_builddir)/src/lib/log/liblog.la
run_unittests_LDADD += $(top_builddir)/src/lib/server_common/libserver_common.la
run_unittests_LDADD += $(top_builddir)/src/lib/cache/libcache.la
run_unittests_LDADD += $(top_builddir)/src/lib/nsas/libnsas.la
+run_unittests_LDADD += $(top_builddir)/src/lib/resolve/libresolve.la
# Note the ordering matters: -Wno-... must follow -Wextra (defined in
# B10_CXXFLAGS
diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am
index 27d9b8b..f8e8172 100644
--- a/src/lib/Makefile.am
+++ b/src/lib/Makefile.am
@@ -1,2 +1,2 @@
SUBDIRS = exceptions dns cc config datasrc python xfr bench log \
- resolve nsas cache asiolink testutils server_common
+ asiolink nsas cache resolve testutils server_common
diff --git a/src/lib/asiolink/Makefile.am b/src/lib/asiolink/Makefile.am
index b28b784..2fda728 100644
--- a/src/lib/asiolink/Makefile.am
+++ b/src/lib/asiolink/Makefile.am
@@ -27,10 +27,9 @@ libasiolink_la_SOURCES += io_endpoint.cc io_endpoint.h
libasiolink_la_SOURCES += io_error.h
libasiolink_la_SOURCES += io_fetch.cc io_fetch.h
libasiolink_la_SOURCES += io_message.h
-libasiolink_la_SOURCES += io_service.cc io_service.h
-libasiolink_la_SOURCES += io_socket.cc io_socket.h
libasiolink_la_SOURCES += qid_gen.cc qid_gen.h
-libasiolink_la_SOURCES += recursive_query.cc recursive_query.h
+libasiolink_la_SOURCES += io_service.h io_service.cc
+libasiolink_la_SOURCES += io_socket.h io_socket.cc
libasiolink_la_SOURCES += simple_callback.h
libasiolink_la_SOURCES += tcp_endpoint.h
libasiolink_la_SOURCES += tcp_server.cc tcp_server.h
@@ -44,16 +43,9 @@ EXTRA_DIST = asiodef.msg
# Note: the ordering matters: -Wno-... must follow -Wextra (defined in
# B10_CXXFLAGS)
libasiolink_la_CXXFLAGS = $(AM_CXXFLAGS)
-if USE_GXX
-libasiolink_la_CXXFLAGS += -Wno-unused-parameter
-endif
if USE_CLANGPP
# Same for clang++, but we need to turn off -Werror completely.
libasiolink_la_CXXFLAGS += -Wno-error
endif
libasiolink_la_CPPFLAGS = $(AM_CPPFLAGS)
-libasiolink_la_LIBADD =
-libasiolink_la_LIBADD += $(top_builddir)/src/lib/resolve/libresolve.la
-libasiolink_la_LIBADD += $(top_builddir)/src/lib/cache/libcache.la
-libasiolink_la_LIBADD += $(top_builddir)/src/lib/nsas/libnsas.la
-libasiolink_la_LIBADD += $(top_builddir)/src/lib/log/liblog.la
+libasiolink_la_LIBADD = $(top_builddir)/src/lib/log/liblog.la
diff --git a/src/lib/asiolink/asiolink.h b/src/lib/asiolink/asiolink.h
index 251413e..6e8fe84 100644
--- a/src/lib/asiolink/asiolink.h
+++ b/src/lib/asiolink/asiolink.h
@@ -25,7 +25,6 @@
#include <asiolink/dns_lookup.h>
#include <asiolink/dns_answer.h>
#include <asiolink/simple_callback.h>
-#include <asiolink/recursive_query.h>
#include <asiolink/interval_timer.h>
#include <asiolink/io_address.h>
diff --git a/src/lib/asiolink/recursive_query.cc b/src/lib/asiolink/recursive_query.cc
deleted file mode 100644
index 406b176..0000000
--- a/src/lib/asiolink/recursive_query.cc
+++ /dev/null
@@ -1,593 +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 <netinet/in.h>
-#include <stdlib.h>
-#include <sys/socket.h>
-#include <unistd.h> // for some IPC/network system calls
-
-#include <boost/lexical_cast.hpp>
-#include <boost/bind.hpp>
-
-#include <config.h>
-
-#include <log/dummylog.h>
-
-#include <dns/question.h>
-#include <dns/message.h>
-#include <dns/opcode.h>
-
-#include <resolve/resolve.h>
-#include <cache/resolver_cache.h>
-
-#include <asio.hpp>
-#include <asiolink/dns_service.h>
-#include <asiolink/io_fetch.h>
-#include <asiolink/io_service.h>
-#include <asiolink/recursive_query.h>
-
-using isc::log::dlog;
-using namespace isc::dns;
-
-namespace asiolink {
-
-typedef std::vector<std::pair<std::string, uint16_t> > AddressVector;
-
-// Here we do not use the typedef above, as the SunStudio compiler
-// 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,
- const std::vector<std::pair<std::string, uint16_t> >& upstream,
- const std::vector<std::pair<std::string, uint16_t> >& upstream_root,
- int query_timeout, int client_timeout, int lookup_timeout,
- unsigned retries) :
- dns_service_(dns_service), upstream_(new AddressVector(upstream)),
- upstream_root_(new AddressVector(upstream_root)),
- test_server_("", 0),
- query_timeout_(query_timeout), client_timeout_(client_timeout),
- lookup_timeout_(lookup_timeout), retries_(retries)
-{}
-
-// Set the test server - only used for unit testing.
-
-void
-RecursiveQuery::setTestServer(const std::string& address, uint16_t port) {
- dlog("Setting test server to " + address + "(" +
- boost::lexical_cast<std::string>(port) + ")");
- test_server_.first = address;
- test_server_.second = port;
-}
-
-
-namespace {
-
-typedef std::pair<std::string, uint16_t> addr_t;
-
-/*
- * This is a query in progress. When a new query is made, this one holds
- * the context information about it, like how many times we are allowed
- * to retry on failure, what to do when we succeed, etc.
- *
- * Used by RecursiveQuery::sendQuery.
- */
-class RunningQuery : public IOFetch::Callback {
-private:
- // The io service to handle async calls
- IOService& io_;
-
- // Info for (re)sending the query (the question and destination)
- Question question_;
-
- // This is where we build and store our final answer
- MessagePtr answer_message_;
-
- // currently we use upstream as the current list of NS records
- // we should differentiate between forwarding and resolving
- boost::shared_ptr<AddressVector> upstream_;
-
- // root servers...just copied over to the zone_servers_
- boost::shared_ptr<AddressVector> upstream_root_;
-
- // Test server - only used for testing. This takes precedence over all
- // other servers if the port is non-zero.
- std::pair<std::string, uint16_t> test_server_;
-
- // Buffer to store the result.
- OutputBufferPtr buffer_;
-
- // Server to notify when we succeed or fail
- //shared_ptr<DNSServer> server_;
- isc::resolve::ResolverInterface::CallbackPtr resolvercallback_;
-
- // Protocol used for the last query. This is set to IOFetch::UDP when a
- // new upstream query is initiated, and changed to IOFetch::TCP if a
- // packet is returned with the TC bit set. It is stored here to detect the
- // case of a TCP packet being returned with the TC bit set.
- IOFetch::Protocol protocol_;
-
- // To prevent both unreasonably long cname chains and cname loops,
- // we simply keep a counter of the number of CNAMEs we have
- // followed so far (and error if it exceeds RESOLVER_MAX_CNAME_CHAIN
- // from lib/resolve/response_classifier.h)
- unsigned cname_count_;
-
- /*
- * TODO Do something more clever with timeouts. In the long term, some
- * computation of average RTT, increase with each retry, etc.
- */
- // Timeout information
- int query_timeout_;
- unsigned retries_;
-
- // normal query state
-
- // Not using NSAS at this moment, so we keep a list
- // of 'current' zone servers
- std::vector<addr_t> zone_servers_;
-
- // Update the question that will be sent to the server
- void setQuestion(const Question& new_question) {
- question_ = new_question;
- }
-
- // TODO: replace by our wrapper
- asio::deadline_timer client_timer;
- asio::deadline_timer lookup_timer;
-
- size_t queries_out_;
-
- // If we timed out ourselves (lookup timeout), stop issuing queries
- bool done_;
-
- // If we have a client timeout, we send back an answer, but don't
- // stop. We use this variable to make sure we don't send another
- // answer if we do find one later (or if we have a lookup_timeout)
- bool answer_sent_;
-
- // Reference to our cache
- isc::cache::ResolverCache& cache_;
-
- // perform a single lookup; first we check the cache to see
- // if we have a response for our query stored already. if
- // so, call handlerecursiveresponse(), if not, we call send()
- void doLookup() {
- dlog("doLookup: try cache");
- Message cached_message(Message::RENDER);
- isc::resolve::initResponseMessage(question_, cached_message);
- if (cache_.lookup(question_.getName(), question_.getType(),
- question_.getClass(), cached_message)) {
- dlog("Message found in cache, returning that");
- handleRecursiveAnswer(cached_message);
- } else {
- send();
- }
-
- }
-
- // (re)send the query to the server.
- //
- // \param protocol Protocol to use for the fetch (default is UDP)
- void send(IOFetch::Protocol protocol = IOFetch::UDP) {
- const int uc = upstream_->size();
- const int zs = zone_servers_.size();
- protocol_ = protocol; // Store protocol being used for this
- buffer_->clear();
- if (test_server_.second != 0) {
- dlog("Sending upstream query (" + question_.toText() +
- ") to test server at " + test_server_.first);
- IOFetch query(protocol, io_, question_,
- test_server_.first,
- test_server_.second, buffer_, this,
- query_timeout_);
- ++queries_out_;
- io_.get_io_service().post(query);
- } else if (uc > 0) {
- int serverIndex = rand() % uc;
- dlog("Sending upstream query (" + question_.toText() +
- ") to " + upstream_->at(serverIndex).first);
- IOFetch query(protocol, io_, question_,
- upstream_->at(serverIndex).first,
- upstream_->at(serverIndex).second, buffer_, this,
- query_timeout_);
- ++queries_out_;
- io_.get_io_service().post(query);
- } else if (zs > 0) {
- int serverIndex = rand() % zs;
- dlog("Sending query to zone server (" + question_.toText() +
- ") to " + zone_servers_.at(serverIndex).first);
- IOFetch query(protocol, io_, question_,
- zone_servers_.at(serverIndex).first,
- zone_servers_.at(serverIndex).second, buffer_, this,
- query_timeout_);
- ++queries_out_;
- io_.get_io_service().post(query);
- } else {
- dlog("Error, no upstream servers to send to.");
- }
- }
-
- // This function is called by operator() if there is an actual
- // answer from a server and we are in recursive mode
- // depending on the contents, we go on recursing or return
- //
- // Note that the footprint may change as this function may
- // need to append data to the answer we are building later.
- //
- // returns true if we are done (either we have an answer or an
- // error message)
- // returns false if we are not done
- bool handleRecursiveAnswer(const Message& incoming) {
- dlog("Handle response");
- // In case we get a CNAME, we store the target
- // here (classify() will set it when it walks through
- // the cname chain to verify it).
- Name cname_target(question_.getName());
-
- isc::resolve::ResponseClassifier::Category category =
- isc::resolve::ResponseClassifier::classify(
- question_, incoming, cname_target, cname_count_);
-
- bool found_ns_address = false;
-
- // If the packet is OK, store it in the cache
- if (!isc::resolve::ResponseClassifier::error(category)) {
- cache_.update(incoming);
- }
-
- switch (category) {
- case isc::resolve::ResponseClassifier::ANSWER:
- case isc::resolve::ResponseClassifier::ANSWERCNAME:
- // Done. copy and return.
- isc::resolve::copyResponseMessage(incoming, answer_message_);
- return true;
- break;
- case isc::resolve::ResponseClassifier::CNAME:
- dlog("Response is CNAME!");
- // (unfinished) CNAME. We set our question_ to the CNAME
- // target, then start over at the beginning (for now, that
- // is, we reset our 'current servers' to the root servers).
- if (cname_count_ >= RESOLVER_MAX_CNAME_CHAIN) {
- // just give up
- dlog("CNAME chain too long");
- isc::resolve::makeErrorMessage(answer_message_,
- Rcode::SERVFAIL());
- return true;
- }
-
- answer_message_->appendSection(Message::SECTION_ANSWER,
- incoming);
- setZoneServersToRoot();
-
- question_ = Question(cname_target, question_.getClass(),
- question_.getType());
-
- dlog("Following CNAME chain to " + question_.toText());
- doLookup();
- return false;
- break;
- case isc::resolve::ResponseClassifier::NXDOMAIN:
- // NXDOMAIN, just copy and return.
- isc::resolve::copyResponseMessage(incoming, answer_message_);
- return true;
- break;
- case isc::resolve::ResponseClassifier::REFERRAL:
- // Referral. For now we just take the first glue address
- // we find and continue with that
- zone_servers_.clear();
-
- for (RRsetIterator rrsi = incoming.beginSection(Message::SECTION_ADDITIONAL);
- rrsi != incoming.endSection(Message::SECTION_ADDITIONAL) && !found_ns_address;
- rrsi++) {
- ConstRRsetPtr rrs = *rrsi;
- if (rrs->getType() == RRType::A()) {
- // found address
- RdataIteratorPtr rdi = rrs->getRdataIterator();
- // just use the first for now
- if (!rdi->isLast()) {
- std::string addr_str = rdi->getCurrent().toText();
- dlog("[XX] first address found: " + addr_str);
- // now we have one address, simply
- // resend that exact same query
- // to that address and yield, when it
- // returns, loop again.
-
- // TODO should use NSAS
- zone_servers_.push_back(addr_t(addr_str, 53));
- found_ns_address = true;
- break;
- }
- }
- }
- if (found_ns_address) {
- // next resolver round
- // we do NOT use doLookup() here, but send() (i.e. we
- // skip the cache), since if we had the final answer
- // instead of a delegation cached, we would have been
- // there by now.
- send();
- return false;
- } else {
- dlog("[XX] no ready-made addresses in additional. need nsas.");
- // TODO this will result in answering with the delegation. oh well
- isc::resolve::copyResponseMessage(incoming, answer_message_);
- return true;
- }
- break;
- case isc::resolve::ResponseClassifier::TRUNCATED:
- // Truncated packet. If the protocol we used for the last one is
- // UDP, re-query using TCP. Otherwise regard it as an error.
- if (protocol_ == IOFetch::UDP) {
- dlog("Response truncated, re-querying over TCP");
- send(IOFetch::TCP);
- return false;
- }
- // Was a TCP query so we have received a packet over TCP with the TC
- // bit set: drop through to common error processing.
- // TODO: Can we use what we have received instead of discarding it?
-
- case isc::resolve::ResponseClassifier::EMPTY:
- case isc::resolve::ResponseClassifier::EXTRADATA:
- case isc::resolve::ResponseClassifier::INVNAMCLASS:
- case isc::resolve::ResponseClassifier::INVTYPE:
- case isc::resolve::ResponseClassifier::MISMATQUEST:
- case isc::resolve::ResponseClassifier::MULTICLASS:
- case isc::resolve::ResponseClassifier::NOTONEQUEST:
- case isc::resolve::ResponseClassifier::NOTRESPONSE:
- case isc::resolve::ResponseClassifier::NOTSINGLE:
- case isc::resolve::ResponseClassifier::OPCODE:
- case isc::resolve::ResponseClassifier::RCODE:
-
- // Should we try a different server rather than SERVFAIL?
- isc::resolve::makeErrorMessage(answer_message_,
- Rcode::SERVFAIL());
- return true;
- break;
- }
- // should not be reached. assert here?
- dlog("[FATAL] unreachable code");
- return true;
- }
-
-public:
- RunningQuery(IOService& io,
- const Question &question,
- MessagePtr answer_message,
- boost::shared_ptr<AddressVector> upstream,
- boost::shared_ptr<AddressVector> upstream_root,
- std::pair<std::string, uint16_t>& test_server,
- OutputBufferPtr buffer,
- isc::resolve::ResolverInterface::CallbackPtr cb,
- int query_timeout, int client_timeout, int lookup_timeout,
- unsigned retries,
- isc::cache::ResolverCache& cache) :
- io_(io),
- question_(question),
- answer_message_(answer_message),
- upstream_(upstream),
- upstream_root_(upstream_root),
- test_server_(test_server),
- buffer_(buffer),
- resolvercallback_(cb),
- protocol_(IOFetch::UDP),
- cname_count_(0),
- query_timeout_(query_timeout),
- retries_(retries),
- client_timer(io.get_io_service()),
- lookup_timer(io.get_io_service()),
- queries_out_(0),
- done_(false),
- answer_sent_(false),
- cache_(cache)
- {
- // Setup the timer to stop trying (lookup_timeout)
- if (lookup_timeout >= 0) {
- lookup_timer.expires_from_now(
- boost::posix_time::milliseconds(lookup_timeout));
- lookup_timer.async_wait(boost::bind(&RunningQuery::stop, this, false));
- }
-
- // Setup the timer to send an answer (client_timeout)
- if (client_timeout >= 0) {
- client_timer.expires_from_now(
- boost::posix_time::milliseconds(client_timeout));
- client_timer.async_wait(boost::bind(&RunningQuery::clientTimeout, this));
- }
-
- // should use NSAS for root servers
- // Adding root servers if not a forwarder
- if (upstream_->empty()) {
- setZoneServersToRoot();
- }
-
- doLookup();
- }
-
- void setZoneServersToRoot() {
- zone_servers_.clear();
- if (upstream_root_->empty()) { //if no root ips given, use this
- zone_servers_.push_back(addr_t("192.5.5.241", 53));
- } else {
- // copy the list
- dlog("Size is " +
- boost::lexical_cast<std::string>(upstream_root_->size()) +
- "\n");
- for(AddressVector::iterator it = upstream_root_->begin();
- it < upstream_root_->end(); ++it) {
- zone_servers_.push_back(addr_t(it->first,it->second));
- dlog("Put " + zone_servers_.back().first + "into root list\n");
- }
- }
- }
- virtual void clientTimeout() {
- // Return a SERVFAIL, but do not stop until
- // we have an answer or timeout ourselves
- isc::resolve::makeErrorMessage(answer_message_,
- Rcode::SERVFAIL());
- if (!answer_sent_) {
- answer_sent_ = true;
- resolvercallback_->success(answer_message_);
- }
- }
-
- virtual void stop(bool resume) {
- // if we cancel our timers, we will still get an event for
- // that, so we cannot delete ourselves just yet (those events
- // would be bound to a deleted object)
- // cancel them one by one, both cancels should get us back
- // here again.
- // same goes if we have an outstanding query (can't delete
- // until that one comes back to us)
- done_ = true;
- if (resume && !answer_sent_) {
- answer_sent_ = true;
-
- // There are two types of messages we could store in the
- // cache;
- // 1. answers to our fetches from authoritative servers,
- // exactly as we receive them, and
- // 2. answers to queries we received from clients, which
- // have received additional processing (following CNAME
- // chains, for instance)
- //
- // Doing only the first would mean we would have to re-do
- // processing when we get data from our cache, and doing
- // only the second would miss out on the side-effect of
- // having nameserver data in our cache.
- //
- // So right now we do both. Since the cache (currently)
- // stores Messages on their question section only, this
- // does mean that we overwrite the messages we stored in
- // the previous iteration if we are following a delegation.
- cache_.update(*answer_message_);
-
- resolvercallback_->success(answer_message_);
- } else {
- resolvercallback_->failure();
- }
- if (lookup_timer.cancel() != 0) {
- return;
- }
- if (client_timer.cancel() != 0) {
- return;
- }
- if (queries_out_ > 0) {
- return;
- }
- delete this;
- }
-
- // This function is used as callback from DNSQuery.
- virtual void operator()(IOFetch::Result result) {
- --queries_out_;
- if (!done_ && result != IOFetch::TIME_OUT) {
- // we got an answer
- Message incoming(Message::PARSE);
- InputBuffer ibuf(buffer_->getData(), buffer_->getLength());
- incoming.fromWire(ibuf);
-
- if (upstream_->size() == 0 &&
- incoming.getRcode() == Rcode::NOERROR()) {
- done_ = handleRecursiveAnswer(incoming);
- } else {
- isc::resolve::copyResponseMessage(incoming, answer_message_);
- done_ = true;
- }
-
- if (done_) {
- stop(true);
- }
- } else if (!done_ && retries_--) {
- // We timed out, but we have some retries, so send again
- dlog("Timeout, resending query");
- send();
- } else {
- // out of retries, give up for now
- stop(false);
- }
- }
-};
-
-}
-
-void
-RecursiveQuery::resolve(const QuestionPtr& question,
- const isc::resolve::ResolverInterface::CallbackPtr callback)
-{
- IOService& io = dns_service_.getIOService();
-
- MessagePtr answer_message(new Message(Message::RENDER));
- isc::resolve::initResponseMessage(*question, *answer_message);
-
- OutputBufferPtr buffer(new OutputBuffer(0));
-
- dlog("Try out cache first (direct call to resolve)");
- // First try to see if we have something cached in the messagecache
- if (cache_.lookup(question->getName(), question->getType(),
- question->getClass(), *answer_message)) {
- dlog("Message found in cache, returning that");
- // TODO: err, should cache set rcode as well?
- answer_message->setRcode(Rcode::NOERROR());
- callback->success(answer_message);
- } else {
- dlog("Message not found in cache, starting recursive query");
- // It will delete itself when it is done
- new RunningQuery(io, *question, answer_message, upstream_,
- upstream_root_, test_server_,
- buffer, callback, query_timeout_,
- client_timeout_, lookup_timeout_, retries_,
- cache_);
- }
-}
-
-void
-RecursiveQuery::resolve(const Question& question,
- MessagePtr answer_message,
- OutputBufferPtr buffer,
- DNSServer* server)
-{
- // XXX: eventually we will need to be able to determine whether
- // the message should be sent via TCP or UDP, or sent initially via
- // UDP and then fall back to TCP on failure, but for the moment
- // we're only going to handle UDP.
- IOService& io = dns_service_.getIOService();
-
- isc::resolve::ResolverInterface::CallbackPtr crs(
- new isc::resolve::ResolverCallbackServer(server));
-
- // TODO: general 'prepareinitialanswer'
- answer_message->setOpcode(isc::dns::Opcode::QUERY());
- answer_message->addQuestion(question);
-
- // First try to see if we have something cached in the messagecache
- dlog("Try out cache first (started by incoming event)");
- if (cache_.lookup(question.getName(), question.getType(),
- question.getClass(), *answer_message)) {
- dlog("Message found in cache, returning that");
- // TODO: err, should cache set rcode as well?
- answer_message->setRcode(Rcode::NOERROR());
- crs->success(answer_message);
- } else {
- dlog("Message not found in cache, starting recursive query");
- // It will delete itself when it is done
- new RunningQuery(io, question, answer_message, upstream_, upstream_root_,
- test_server_,
- buffer, crs, query_timeout_, client_timeout_,
- lookup_timeout_, retries_, cache_);
- }
-}
-
-
-
-} // namespace asiolink
diff --git a/src/lib/asiolink/recursive_query.h b/src/lib/asiolink/recursive_query.h
deleted file mode 100644
index 626ff42..0000000
--- a/src/lib/asiolink/recursive_query.h
+++ /dev/null
@@ -1,131 +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.
-
-#ifndef __ASIOLINK_RECURSIVE_QUERY_H
-#define __ASIOLINK_RECURSIVE_QUERY_H 1
-
-#include <asiolink/dns_service.h>
-#include <asiolink/dns_server.h>
-#include <dns/buffer.h>
-#include <cache/resolver_cache.h>
-
-namespace asiolink {
-/// \brief The \c RecursiveQuery class provides a layer of abstraction around
-/// the ASIO code that carries out an upstream query.
-///
-/// This design is very preliminary; currently it is only capable of
-/// handling simple forward requests to a single resolver.
-class RecursiveQuery {
- ///
- /// \name Constructors
- ///
- //@{
-public:
- /// \brief Constructor
- ///
- /// This is currently the only way to construct \c RecursiveQuery
- /// object. If the addresses of the forward nameservers is specified,
- /// and every upstream query will be sent to one random address, and
- /// the result sent back directly. If not, it will do full resolving.
- ///
- /// \param dns_service The DNS Service to perform the recursive
- /// query on.
- /// \param upstream Addresses and ports of the upstream servers
- /// to forward queries to.
- /// \param upstream_root Addresses and ports of the root servers
- /// to use when resolving.
- /// \param query_timeout Timeout value for queries we sent, in ms
- /// \param client_timeout Timeout value for when we send back an
- /// error, in ms
- /// \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,
- const std::vector<std::pair<std::string, uint16_t> >&
- upstream,
- const std::vector<std::pair<std::string, uint16_t> >&
- upstream_root,
- int query_timeout = 2000,
- int client_timeout = 4000,
- int lookup_timeout = 30000,
- unsigned retries = 3);
- //@}
-
- /// \brief Initiate resolving
- ///
- /// When sendQuery() is called, a (set of) message(s) is sent
- /// asynchronously. If upstream servers are set, one is chosen
- /// and the response (if any) from that server will be returned.
- ///
- /// If not upstream is set, a root server is chosen from the
- /// root_servers, and the RunningQuery shall do a full resolve
- /// (i.e. if the answer is a delegation, it will be followed, etc.)
- /// until there is an answer or an error.
- ///
- /// When there is a response or an error and we give up, the given
- /// CallbackPtr object shall be called (with either success() or
- /// failure(). See ResolverInterface::Callback for more information.
- ///
- /// \param question The question being answered <qname/qclass/qtype>
- /// \param callback Callback object. See
- /// \c ResolverInterface::Callback for more information
- void resolve(const isc::dns::QuestionPtr& question,
- const isc::resolve::ResolverInterface::CallbackPtr callback);
-
-
- /// \brief Initiates resolving for the given question.
- ///
- /// This actually calls the previous sendQuery() with a default
- /// callback object, which calls resume() on the given DNSServer
- /// object.
- ///
- /// \param question The question being answered <qname/qclass/qtype>
- /// \param answer_message An output Message into which the final response will be copied
- /// \param buffer An output buffer into which the intermediate responses will be copied
- /// \param server A pointer to the \c DNSServer object handling the client
- void resolve(const isc::dns::Question& question,
- isc::dns::MessagePtr answer_message,
- isc::dns::OutputBufferPtr buffer,
- DNSServer* server);
-
- /// \brief Set Test Server
- ///
- /// This method is *only* for unit testing the class. If set, it enables
- /// recursive behaviour but, regardless of responses received, sends every
- /// query to the test server.
- ///
- /// The test server is enabled by setting a non-zero port number.
- ///
- /// \param address IP address of the test server.
- /// \param port Port number of the test server
- void setTestServer(const std::string& address, uint16_t port);
-
-private:
- DNSService& dns_service_;
- boost::shared_ptr<std::vector<std::pair<std::string, uint16_t> > >
- upstream_;
- boost::shared_ptr<std::vector<std::pair<std::string, uint16_t> > >
- upstream_root_;
- std::pair<std::string, uint16_t> test_server_;
- int query_timeout_;
- int client_timeout_;
- int lookup_timeout_;
- unsigned retries_;
- // Cache. TODO: I think we want this initialized in Resolver class,
- // not here
- isc::cache::ResolverCache cache_;
-};
-
-} // namespace asiolink
-#endif // __ASIOLINK_RECURSIVE_QUERY_H
diff --git a/src/lib/asiolink/tcp_socket.h b/src/lib/asiolink/tcp_socket.h
index fcbf3b7..e6e0863 100644
--- a/src/lib/asiolink/tcp_socket.h
+++ b/src/lib/asiolink/tcp_socket.h
@@ -255,8 +255,8 @@ TCPSocket<C>::open(const IOEndpoint* endpoint, C& callback) {
// an exception if this is the case.
template <typename C> void
-TCPSocket<C>::asyncSend(const void* data, size_t length, const IOEndpoint*,
- C& callback)
+TCPSocket<C>::asyncSend(const void* data, size_t length,
+ const IOEndpoint*, C& callback)
{
if (isopen_) {
diff --git a/src/lib/asiolink/tests/Makefile.am b/src/lib/asiolink/tests/Makefile.am
index 3a229f3..7a4f639 100644
--- a/src/lib/asiolink/tests/Makefile.am
+++ b/src/lib/asiolink/tests/Makefile.am
@@ -25,8 +25,6 @@ run_unittests_SOURCES += io_fetch_unittest.cc
run_unittests_SOURCES += io_socket_unittest.cc
run_unittests_SOURCES += io_service_unittest.cc
run_unittests_SOURCES += interval_timer_unittest.cc
-run_unittests_SOURCES += recursive_query_unittest.cc
-run_unittests_SOURCES += recursive_query_unittest_2.cc
run_unittests_SOURCES += tcp_endpoint_unittest.cc
run_unittests_SOURCES += tcp_socket_unittest.cc
run_unittests_SOURCES += udp_endpoint_unittest.cc
diff --git a/src/lib/asiolink/tests/recursive_query_unittest.cc b/src/lib/asiolink/tests/recursive_query_unittest.cc
deleted file mode 100644
index f4fc2ac..0000000
--- a/src/lib/asiolink/tests/recursive_query_unittest.cc
+++ /dev/null
@@ -1,796 +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 <sys/socket.h>
-#include <sys/time.h>
-
-#include <string.h>
-
-#include <boost/lexical_cast.hpp>
-#include <boost/bind.hpp>
-#include <boost/date_time/posix_time/posix_time_types.hpp>
-
-#include <gtest/gtest.h>
-
-#include <exceptions/exceptions.h>
-
-#include <dns/tests/unittest_util.h>
-#include <dns/rcode.h>
-
-#include <dns/buffer.h>
-#include <dns/message.h>
-
-// IMPORTANT: We shouldn't directly use ASIO definitions in this test.
-// In particular, we must not include asio.hpp in this file.
-// The asiolink module is primarily intended to be a wrapper that hide the
-// details of the underlying implementations. We need to test the wrapper
-// level behaviors. In addition, some compilers reject to compile this file
-// if we include asio.hpp unless we specify a special compiler option.
-// If we need to test something at the level of underlying ASIO and need
-// their definition, that test should go to asiolink/internal/tests.
-#include <asiolink/recursive_query.h>
-#include <asiolink/io_socket.h>
-#include <asiolink/io_service.h>
-#include <asiolink/io_message.h>
-#include <asiolink/io_error.h>
-#include <asiolink/dns_lookup.h>
-#include <asiolink/simple_callback.h>
-
-using isc::UnitTestUtil;
-using namespace std;
-using namespace asiolink;
-using namespace isc::dns;
-
-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";
-// This data is intended to be valid as a DNS/TCP-like message: the first
-// two octets encode the length of the rest of the data. This is crucial
-// for the tests below.
-const uint8_t test_data[] = {0, 4, 1, 2, 3, 4};
-
-// This function returns an addrinfo structure for use by tests, using
-// different addresses and ports depending on whether we're testing
-// IPv4 or v6, TCP or UDP, and client or server operation.
-struct addrinfo*
-resolveAddress(const int family, const int protocol, const bool client) {
- const char* const addr = (family == AF_INET6) ?
- TEST_IPV6_ADDR : TEST_IPV4_ADDR;
- const char* const port = client ? TEST_CLIENT_PORT : TEST_SERVER_PORT;
-
- struct addrinfo hints;
- memset(&hints, 0, sizeof(hints));
- hints.ai_family = family;
- hints.ai_socktype = (protocol == IPPROTO_UDP) ? SOCK_DGRAM : SOCK_STREAM;
- hints.ai_protocol = protocol;
- hints.ai_flags = AI_NUMERICSERV;
-
- struct addrinfo* res;
- const int error = getaddrinfo(addr, port, &hints, &res);
- if (error != 0) {
- isc_throw(IOError, "getaddrinfo failed: " << gai_strerror(error));
- }
-
- return (res);
-}
-
-// This fixture is a framework for various types of network operations
-// using the ASIO interfaces. Each test case creates an IOService object,
-// opens a local "client" socket for testing, sends data via the local socket
-// to the service that would run in the IOService object.
-// A mock callback function (an ASIOCallBack object) is registered with the
-// IOService object, so the test code should be able to examine the data
-// received on the server side. It then checks the received data matches
-// expected parameters.
-// If initialization parameters of the IOService should be modified, the test
-// case can do it using the setDNSService() method.
-// Note: the set of tests in RecursiveQueryTest use actual network services and may
-// involve undesirable side effects such as blocking.
-class RecursiveQueryTest : public ::testing::Test {
-protected:
- RecursiveQueryTest();
- ~RecursiveQueryTest() {
- if (res_ != NULL) {
- freeaddrinfo(res_);
- }
- if (sock_ != -1) {
- close(sock_);
- }
- delete dns_service_;
- delete callback_;
- delete io_service_;
- }
-
- // Send a test UDP packet to a mock server
- void sendUDP(const int family) {
- res_ = resolveAddress(family, IPPROTO_UDP, false);
-
- sock_ = socket(res_->ai_family, res_->ai_socktype, res_->ai_protocol);
- if (sock_ < 0) {
- isc_throw(IOError, "failed to open test socket");
- }
- const int cc = sendto(sock_, test_data, sizeof(test_data), 0,
- res_->ai_addr, res_->ai_addrlen);
- if (cc != sizeof(test_data)) {
- isc_throw(IOError, "unexpected sendto result: " << cc);
- }
- io_service_->run();
- }
-
- // Send a test TCP packet to a mock server
- void sendTCP(const int family) {
- res_ = resolveAddress(family, IPPROTO_TCP, false);
-
- sock_ = socket(res_->ai_family, res_->ai_socktype, res_->ai_protocol);
- if (sock_ < 0) {
- isc_throw(IOError, "failed to open test socket");
- }
- if (connect(sock_, res_->ai_addr, res_->ai_addrlen) < 0) {
- isc_throw(IOError, "failed to connect to the test server");
- }
- const int cc = send(sock_, test_data, sizeof(test_data), 0);
- if (cc != sizeof(test_data)) {
- isc_throw(IOError, "unexpected send result: " << cc);
- }
- io_service_->run();
- }
-
- // Receive a UDP packet from a mock server; used for testing
- // recursive lookup. The caller must place a RecursiveQuery
- // on the IO Service queue before running this routine.
- void recvUDP(const int family, void* buffer, size_t& size) {
- res_ = resolveAddress(family, IPPROTO_UDP, true);
-
- sock_ = socket(res_->ai_family, res_->ai_socktype, res_->ai_protocol);
- if (sock_ < 0) {
- isc_throw(IOError, "failed to open test socket");
- }
-
- if (bind(sock_, res_->ai_addr, res_->ai_addrlen) < 0) {
- isc_throw(IOError, "bind failed: " << strerror(errno));
- }
-
- // The IO service queue should have a RecursiveQuery object scheduled
- // to run at this point. This call will cause it to begin an
- // async send, then return.
- io_service_->run_one();
-
- // ... and this one will block until the send has completed
- io_service_->run_one();
-
- // Now we attempt to recv() whatever was sent.
- // XXX: there's no guarantee the receiving socket can immediately get
- // the packet. Normally we can perform blocking recv to wait for it,
- // but in theory it's even possible that the packet is lost.
- // In order to prevent the test from hanging in such a worst case
- // we add an ad hoc timeout.
- const struct timeval timeo = { 10, 0 };
- int recv_options = 0;
- if (setsockopt(sock_, SOL_SOCKET, SO_RCVTIMEO, &timeo,
- sizeof(timeo))) {
- if (errno == ENOPROTOOPT) {
- // Workaround for Solaris: it doesn't accept SO_RCVTIMEO
- // with the error of ENOPROTOOPT. Since this is a workaround
- // for rare error cases anyway, we simply switch to the
- // "don't wait" mode. If we still find an error in recv()
- // can happen often we'll consider a more complete solution.
- recv_options = MSG_DONTWAIT;
- } else {
- isc_throw(IOError, "set RCVTIMEO failed: " << strerror(errno));
- }
- }
- const int ret = recv(sock_, buffer, size, recv_options);
- if (ret < 0) {
- isc_throw(IOError, "recvfrom failed: " << strerror(errno));
- }
-
- // Pass the message size back via the size parameter
- size = ret;
- }
-
-
- // Set up an IO Service queue using the specified address
- void setDNSService(const char& address) {
- delete dns_service_;
- dns_service_ = NULL;
- delete io_service_;
- io_service_ = new IOService();
- callback_ = new ASIOCallBack(this);
- dns_service_ = new DNSService(*io_service_, *TEST_SERVER_PORT, address, callback_, NULL, NULL);
- }
-
- // Set up an IO Service queue using the "any" address, on IPv4 if
- // 'use_ipv4' is true and on IPv6 if 'use_ipv6' is true.
- void setDNSService(const bool use_ipv4, const bool use_ipv6) {
- delete dns_service_;
- dns_service_ = NULL;
- delete io_service_;
- io_service_ = new IOService();
- callback_ = new ASIOCallBack(this);
- dns_service_ = new DNSService(*io_service_, *TEST_SERVER_PORT, use_ipv4, use_ipv6, callback_,
- NULL, NULL);
- }
-
- // Set up empty DNS Service
- // Set up an IO Service queue without any addresses
- void setDNSService() {
- delete dns_service_;
- dns_service_ = NULL;
- delete io_service_;
- io_service_ = new IOService();
- callback_ = new ASIOCallBack(this);
- dns_service_ = new DNSService(*io_service_, callback_, NULL, NULL);
- }
-
- // Run a simple server test, on either IPv4 or IPv6, and over either
- // UDP or TCP. Calls the sendUDP() or sendTCP() methods, which will
- // start the IO Service queue. The UDPServer or TCPServer that was
- // created by setIOService() will receive the test packet and issue a
- // callback, which enables us to check that the data it received
- // matches what we sent.
- void doTest(const int family, const int protocol) {
- if (protocol == IPPROTO_UDP) {
- sendUDP(family);
- } else {
- sendTCP(family);
- }
-
- // There doesn't seem to be an effective test for the validity of
- // 'native'.
- // One thing we are sure is it must be different from our local socket.
- EXPECT_NE(sock_, callback_native_);
- EXPECT_EQ(protocol, callback_protocol_);
- EXPECT_EQ(family == AF_INET6 ? TEST_IPV6_ADDR : TEST_IPV4_ADDR,
- callback_address_);
-
- const uint8_t* expected_data =
- protocol == IPPROTO_UDP ? test_data : test_data + 2;
- const size_t expected_datasize =
- protocol == IPPROTO_UDP ? sizeof(test_data) :
- sizeof(test_data) - 2;
- EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, &callback_data_[0],
- callback_data_.size(),
- expected_data, expected_datasize);
- }
-
-protected:
- // This is a nonfunctional mockup of a DNSServer object. Its purpose
- // is to resume after a recursive query or other asynchronous call
- // has completed.
- class MockServer : public DNSServer {
- public:
- explicit MockServer(IOService& io_service,
- SimpleCallback* checkin = NULL,
- DNSLookup* lookup = NULL,
- DNSAnswer* answer = NULL) :
- io_(io_service),
- done_(false),
- message_(new Message(Message::PARSE)),
- answer_message_(new Message(Message::RENDER)),
- respbuf_(new OutputBuffer(0)),
- checkin_(checkin), lookup_(lookup), answer_(answer)
- {}
-
- void operator()(asio::error_code ec = asio::error_code(),
- size_t length = 0)
- {}
-
- void resume(const bool) {
- // should never be called in our tests
- }
-
- DNSServer* clone() {
- MockServer* s = new MockServer(*this);
- return (s);
- }
-
- inline void asyncLookup() {
- if (lookup_) {
- (*lookup_)(*io_message_, message_, answer_message_,
- respbuf_, this);
- }
- }
-
- protected:
- IOService& io_;
- bool done_;
-
- private:
- // Currently unused; these will be used for testing
- // asynchronous lookup calls via the asyncLookup() method
- boost::shared_ptr<asiolink::IOMessage> io_message_;
- isc::dns::MessagePtr message_;
- isc::dns::MessagePtr answer_message_;
- isc::dns::OutputBufferPtr respbuf_;
-
- // Callback functions provided by the caller
- const SimpleCallback* checkin_;
- const DNSLookup* lookup_;
- const DNSAnswer* answer_;
- };
-
- // This version of mock server just stops the io_service when it is resumed
- class MockServerStop : public MockServer {
- public:
- explicit MockServerStop(IOService& io_service, bool* done) :
- MockServer(io_service),
- done_(done)
- {}
-
- void resume(const bool done) {
- *done_ = done;
- io_.stop();
- }
-
- DNSServer* clone() {
- return (new MockServerStop(*this));
- }
- private:
- bool* done_;
- };
-
- // This version of mock server just stops the io_service when it is resumed
- // the second time. (Used in the clientTimeout test, where resume
- // is called initially with the error answer, and later when the
- // lookup times out, it is called without an answer to send back)
- class MockServerStop2 : public MockServer {
- public:
- explicit MockServerStop2(IOService& io_service,
- bool* done1, bool* done2) :
- MockServer(io_service),
- done1_(done1),
- done2_(done2),
- stopped_once_(false)
- {}
-
- void resume(const bool done) {
- if (stopped_once_) {
- *done2_ = done;
- io_.stop();
- } else {
- *done1_ = done;
- stopped_once_ = true;
- }
- }
-
- DNSServer* clone() {
- return (new MockServerStop2(*this));
- }
- private:
- bool* done1_;
- bool* done2_;
- bool stopped_once_;
- };
-
-private:
- class ASIOCallBack : public SimpleCallback {
- public:
- ASIOCallBack(RecursiveQueryTest* test_obj) : test_obj_(test_obj) {}
- void operator()(const IOMessage& io_message) const {
- test_obj_->callBack(io_message);
- }
- private:
- RecursiveQueryTest* test_obj_;
- };
- void callBack(const IOMessage& io_message) {
- callback_protocol_ = io_message.getSocket().getProtocol();
- callback_native_ = io_message.getSocket().getNative();
- callback_address_ =
- io_message.getRemoteEndpoint().getAddress().toText();
- callback_data_.assign(
- static_cast<const uint8_t*>(io_message.getData()),
- static_cast<const uint8_t*>(io_message.getData()) +
- io_message.getDataSize());
- io_service_->stop();
- }
-protected:
- // We use a pointer for io_service_, because for some tests we
- // need to recreate a new one within one onstance of this class
- IOService* io_service_;
- DNSService* dns_service_;
- ASIOCallBack* callback_;
- int callback_protocol_;
- int callback_native_;
- string callback_address_;
- vector<uint8_t> callback_data_;
- int sock_;
- struct addrinfo* res_;
-};
-
-RecursiveQueryTest::RecursiveQueryTest() :
- dns_service_(NULL), callback_(NULL), callback_protocol_(0),
- callback_native_(-1), sock_(-1), res_(NULL)
-{
- io_service_ = new IOService();
- setDNSService(true, true);
-}
-
-TEST_F(RecursiveQueryTest, v6UDPSend) {
- doTest(AF_INET6, IPPROTO_UDP);
-}
-
-TEST_F(RecursiveQueryTest, v6TCPSend) {
- doTest(AF_INET6, IPPROTO_TCP);
-}
-
-TEST_F(RecursiveQueryTest, v4UDPSend) {
- doTest(AF_INET, IPPROTO_UDP);
-}
-
-TEST_F(RecursiveQueryTest, v4TCPSend) {
- doTest(AF_INET, IPPROTO_TCP);
-}
-
-TEST_F(RecursiveQueryTest, v6UDPSendSpecific) {
- // Explicitly set a specific address to be bound to the socket.
- // The subsequent test does not directly ensures the underlying socket
- // is bound to the expected address, but the success of the tests should
- // reasonably suggest it works as intended.
- // Specifying an address also implicitly means the service runs in a
- // single address-family mode. In tests using TCP we can confirm that
- // by trying to make a connection and seeing a failure. In UDP, it'd be
- // more complicated because we need to use a connected socket and catch
- // an error on a subsequent read operation. We could do it, but for
- // simplicity we only tests the easier cases for now.
-
- setDNSService(*TEST_IPV6_ADDR);
- doTest(AF_INET6, IPPROTO_UDP);
-}
-
-TEST_F(RecursiveQueryTest, v6TCPSendSpecific) {
- setDNSService(*TEST_IPV6_ADDR);
- doTest(AF_INET6, IPPROTO_TCP);
-
- EXPECT_THROW(sendTCP(AF_INET), IOError);
-}
-
-TEST_F(RecursiveQueryTest, v4UDPSendSpecific) {
- setDNSService(*TEST_IPV4_ADDR);
- doTest(AF_INET, IPPROTO_UDP);
-}
-
-TEST_F(RecursiveQueryTest, v4TCPSendSpecific) {
- setDNSService(*TEST_IPV4_ADDR);
- doTest(AF_INET, IPPROTO_TCP);
-
- EXPECT_THROW(sendTCP(AF_INET6), IOError);
-}
-
-TEST_F(RecursiveQueryTest, v6AddServer) {
- setDNSService();
- dns_service_->addServer(*TEST_SERVER_PORT, TEST_IPV6_ADDR);
- doTest(AF_INET6, IPPROTO_TCP);
-
- EXPECT_THROW(sendTCP(AF_INET), IOError);
-}
-
-TEST_F(RecursiveQueryTest, v4AddServer) {
- setDNSService();
- dns_service_->addServer(*TEST_SERVER_PORT, TEST_IPV4_ADDR);
- doTest(AF_INET, IPPROTO_TCP);
-
- EXPECT_THROW(sendTCP(AF_INET6), IOError);
-}
-
-TEST_F(RecursiveQueryTest, clearServers) {
- setDNSService();
- dns_service_->clearServers();
-
- EXPECT_THROW(sendTCP(AF_INET), IOError);
- EXPECT_THROW(sendTCP(AF_INET6), IOError);
-}
-
-TEST_F(RecursiveQueryTest, v6TCPOnly) {
- // Open only IPv6 TCP socket. A subsequent attempt of establishing an
- // IPv4/TCP connection should fail. See above for why we only test this
- // for TCP.
- setDNSService(false, true);
- EXPECT_THROW(sendTCP(AF_INET), IOError);
-}
-
-TEST_F(RecursiveQueryTest, v4TCPOnly) {
- setDNSService(true, false);
- EXPECT_THROW(sendTCP(AF_INET6), IOError);
-}
-
-vector<pair<string, uint16_t> >
-singleAddress(const string &address, uint16_t port) {
- vector<pair<string, uint16_t> > result;
- result.push_back(pair<string, uint16_t>(address, port));
- return (result);
-}
-
-TEST_F(RecursiveQueryTest, recursiveSetupV4) {
- setDNSService(true, false);
- uint16_t port = boost::lexical_cast<uint16_t>(TEST_CLIENT_PORT);
- EXPECT_NO_THROW(RecursiveQuery(*dns_service_,
- singleAddress(TEST_IPV4_ADDR, port),
- singleAddress(TEST_IPV4_ADDR, port)));
-}
-
-TEST_F(RecursiveQueryTest, recursiveSetupV6) {
- setDNSService(false, true);
- uint16_t port = boost::lexical_cast<uint16_t>(TEST_CLIENT_PORT);
- EXPECT_NO_THROW(RecursiveQuery(*dns_service_,
- singleAddress(TEST_IPV6_ADDR, port),
- singleAddress(TEST_IPV6_ADDR,port)));
-}
-
-// XXX:
-// This is very inadequate unit testing. It should be generalized into
-// a routine that can do this with variable address family, address, and
-// port, and with the various callbacks defined in such a way as to ensure
-// full code coverage including error cases.
-TEST_F(RecursiveQueryTest, forwarderSend) {
- setDNSService(true, false);
-
- // Note: We use the test prot plus one to ensure we aren't binding
- // to the same port as the actual server
- uint16_t port = boost::lexical_cast<uint16_t>(TEST_CLIENT_PORT);
-
- MockServer server(*io_service_);
- RecursiveQuery rq(*dns_service_,
- singleAddress(TEST_IPV4_ADDR, port),
- singleAddress(TEST_IPV4_ADDR, port));
-
- Question q(Name("example.com"), RRClass::IN(), RRType::TXT());
- OutputBufferPtr buffer(new OutputBuffer(0));
- MessagePtr answer(new Message(Message::RENDER));
- rq.resolve(q, answer, buffer, &server);
-
- char data[4096];
- size_t size = sizeof(data);
- ASSERT_NO_THROW(recvUDP(AF_INET, data, size));
-
- Message m(Message::PARSE);
- InputBuffer ibuf(data, size);
-
- // Make sure we can parse the message that was sent
- EXPECT_NO_THROW(m.parseHeader(ibuf));
- EXPECT_NO_THROW(m.fromWire(ibuf));
-
- // Check that the question sent matches the one we wanted
- QuestionPtr q2 = *m.beginQuestion();
- EXPECT_EQ(q.getName(), q2->getName());
- EXPECT_EQ(q.getType(), q2->getType());
- EXPECT_EQ(q.getClass(), q2->getClass());
-}
-
-int
-createTestSocket()
-{
- struct addrinfo* res_ = resolveAddress(AF_INET, IPPROTO_UDP, true);
- int sock_ = socket(res_->ai_family, res_->ai_socktype, res_->ai_protocol);
- if (sock_ < 0) {
- isc_throw(IOError, "failed to open test socket");
- }
- if (bind(sock_, res_->ai_addr, res_->ai_addrlen) < 0) {
- isc_throw(IOError, "failed to bind test socket");
- }
- return sock_;
-}
-
-int
-setSocketTimeout(int sock_, size_t tv_sec, size_t tv_usec) {
- const struct timeval timeo = { tv_sec, tv_usec };
- int recv_options = 0;
- if (setsockopt(sock_, SOL_SOCKET, SO_RCVTIMEO, &timeo, sizeof(timeo))) {
- if (errno == ENOPROTOOPT) { // see RecursiveQueryTest::recvUDP()
- recv_options = MSG_DONTWAIT;
- } else {
- isc_throw(IOError, "set RCVTIMEO failed: " << strerror(errno));
- }
- }
- return recv_options;
-}
-
-// try to read from the socket max time
-// *num is incremented for every succesfull read
-// returns true if it can read max times, false otherwise
-bool tryRead(int sock_, int recv_options, size_t max, int* num) {
- size_t i = 0;
- do {
- char inbuff[512];
- if (recv(sock_, inbuff, sizeof(inbuff), recv_options) < 0) {
- return false;
- } else {
- ++i;
- ++*num;
- }
- } while (i < max);
- return true;
-}
-
-
-// Test it tries the correct amount of times before giving up
-TEST_F(RecursiveQueryTest, forwardQueryTimeout) {
- // Prepare the service (we do not use the common setup, we do not answer
- setDNSService();
-
- // Prepare the socket
- sock_ = createTestSocket();
-
- // Prepare the server
- bool done(true);
- MockServerStop server(*io_service_, &done);
-
- // Do the answer
- const uint16_t port = boost::lexical_cast<uint16_t>(TEST_CLIENT_PORT);
- RecursiveQuery query(*dns_service_,
- singleAddress(TEST_IPV4_ADDR, port),
- singleAddress(TEST_IPV4_ADDR, port),
- 10, 4000, 3000, 2);
- Question question(Name("example.net"), RRClass::IN(), RRType::A());
- OutputBufferPtr buffer(new OutputBuffer(0));
- MessagePtr answer(new Message(Message::RENDER));
- query.resolve(question, answer, buffer, &server);
-
- // Run the test
- io_service_->run();
-
- // Read up to 3 packets. Use some ad hoc timeout to prevent an infinite
- // block (see also recvUDP()).
- int recv_options = setSocketTimeout(sock_, 10, 0);
- int num = 0;
- bool read_success = tryRead(sock_, recv_options, 3, &num);
-
- // The query should fail
- EXPECT_FALSE(done);
- EXPECT_EQ(3, num);
- EXPECT_TRUE(read_success);
-}
-
-// If we set client timeout to lower than querytimeout, we should
-// get a failure answer, but still see retries
-// (no actual answer is given here yet)
-TEST_F(RecursiveQueryTest, forwardClientTimeout) {
- // Prepare the service (we do not use the common setup, we do not answer
- setDNSService();
-
- sock_ = createTestSocket();
-
- // Prepare the server
- bool done1(true);
- bool done2(true);
- MockServerStop2 server(*io_service_, &done1, &done2);
-
- MessagePtr answer(new Message(Message::RENDER));
-
- // Do the answer
- const uint16_t port = boost::lexical_cast<uint16_t>(TEST_CLIENT_PORT);
- // Set it up to retry twice before client timeout fires
- // Since the lookup timer has not fired, it should retry
- // four times
- RecursiveQuery query(*dns_service_,
- singleAddress(TEST_IPV4_ADDR, port),
- singleAddress(TEST_IPV4_ADDR, port),
- 200, 480, 4000, 4);
- Question question(Name("example.net"), RRClass::IN(), RRType::A());
- OutputBufferPtr buffer(new OutputBuffer(0));
- query.resolve(question, answer, buffer, &server);
-
- // Run the test
- io_service_->run();
-
- // we know it'll fail, so make it a shorter timeout
- int recv_options = setSocketTimeout(sock_, 1, 0);
-
- // Try to read 5 times
- int num = 0;
- bool read_success = tryRead(sock_, recv_options, 5, &num);
-
- // The query should fail, but we should have kept on trying
- EXPECT_TRUE(done1);
- EXPECT_FALSE(done2);
- EXPECT_EQ(5, num);
- EXPECT_TRUE(read_success);
-}
-
-// If we set lookup timeout to lower than querytimeout*retries, we should
-// fail before the full amount of retries
-TEST_F(RecursiveQueryTest, forwardLookupTimeout) {
- // Prepare the service (we do not use the common setup, we do not answer
- setDNSService();
-
- // Prepare the socket
- sock_ = createTestSocket();
-
- // Prepare the server
- bool done(true);
- MockServerStop server(*io_service_, &done);
-
- MessagePtr answer(new Message(Message::RENDER));
-
- // Do the answer
- const uint16_t port = boost::lexical_cast<uint16_t>(TEST_CLIENT_PORT);
- // Set up the test so that it will retry 5 times, but the lookup
- // timeout will fire after only 3 normal timeouts
- RecursiveQuery query(*dns_service_,
- singleAddress(TEST_IPV4_ADDR, port),
- singleAddress(TEST_IPV4_ADDR, port),
- 200, 4000, 480, 5);
- Question question(Name("example.net"), RRClass::IN(), RRType::A());
- OutputBufferPtr buffer(new OutputBuffer(0));
- query.resolve(question, answer, buffer, &server);
-
- // Run the test
- io_service_->run();
-
- int recv_options = setSocketTimeout(sock_, 1, 0);
-
- // Try to read 5 times, should stop after 3 reads
- int num = 0;
- bool read_success = tryRead(sock_, recv_options, 5, &num);
-
- // The query should fail
- EXPECT_FALSE(done);
- EXPECT_EQ(3, num);
- EXPECT_FALSE(read_success);
-}
-
-// as mentioned above, we need a more better framework for this,
-// in addition to that, this sends out queries into the world
-// (which we should catch somehow and fake replies for)
-// for the skeleton code, it shouldn't be too much of a problem
-// Ok so even we don't all have access to the DNS world right now,
-// so disabling these tests too.
-TEST_F(RecursiveQueryTest, DISABLED_recursiveSendOk) {
- setDNSService(true, false);
- bool done;
-
- MockServerStop server(*io_service_, &done);
- vector<pair<string, uint16_t> > empty_vector;
- RecursiveQuery rq(*dns_service_, empty_vector, empty_vector, 10000, 0);
-
- Question q(Name("www.isc.org"), RRClass::IN(), RRType::A());
- OutputBufferPtr buffer(new OutputBuffer(0));
- MessagePtr answer(new Message(Message::RENDER));
- rq.resolve(q, answer, buffer, &server);
- io_service_->run();
-
- // Check that the answer we got matches the one we wanted
- EXPECT_EQ(Rcode::NOERROR(), answer->getRcode());
- ASSERT_EQ(1, answer->getRRCount(Message::SECTION_ANSWER));
- RRsetPtr a = *answer->beginSection(Message::SECTION_ANSWER);
- EXPECT_EQ(q.getName(), a->getName());
- EXPECT_EQ(q.getType(), a->getType());
- EXPECT_EQ(q.getClass(), a->getClass());
- EXPECT_EQ(1, a->getRdataCount());
-}
-
-// see comments at previous test
-TEST_F(RecursiveQueryTest, DISABLED_recursiveSendNXDOMAIN) {
- setDNSService(true, false);
- bool done;
-
- MockServerStop server(*io_service_, &done);
- vector<pair<string, uint16_t> > empty_vector;
- RecursiveQuery rq(*dns_service_, empty_vector, empty_vector, 10000, 0);
-
- Question q(Name("wwwdoesnotexist.isc.org"), RRClass::IN(), RRType::A());
- OutputBufferPtr buffer(new OutputBuffer(0));
- MessagePtr answer(new Message(Message::RENDER));
- rq.resolve(q, answer, buffer, &server);
- io_service_->run();
-
- // Check that the answer we got matches the one we wanted
- EXPECT_EQ(Rcode::NXDOMAIN(), answer->getRcode());
- EXPECT_EQ(0, answer->getRRCount(Message::SECTION_ANSWER));
-}
-
-}
diff --git a/src/lib/asiolink/tests/recursive_query_unittest_2.cc b/src/lib/asiolink/tests/recursive_query_unittest_2.cc
deleted file mode 100644
index ce51bbf..0000000
--- a/src/lib/asiolink/tests/recursive_query_unittest_2.cc
+++ /dev/null
@@ -1,642 +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 <algorithm>
-#include <cstdlib>
-#include <iomanip>
-#include <iostream>
-#include <string>
-
-#include <gtest/gtest.h>
-#include <boost/bind.hpp>
-
-
-#include <asio.hpp>
-
-#include <dns/buffer.h>
-#include <dns/question.h>
-#include <dns/message.h>
-#include <dns/messagerenderer.h>
-#include <dns/opcode.h>
-#include <dns/name.h>
-#include <dns/rcode.h>
-#include <dns/rrtype.h>
-#include <dns/rrset.h>
-#include <dns/rrttl.h>
-#include <dns/rdata.h>
-
-#include <asiolink/asiolink_utilities.h>
-#include <asiolink/dns_service.h>
-#include <asiolink/io_address.h>
-#include <asiolink/io_endpoint.h>
-#include <asiolink/io_fetch.h>
-#include <asiolink/io_service.h>
-#include <asiolink/recursive_query.h>
-#include <resolve/resolver_interface.h>
-
-using namespace asio;
-using namespace asio::ip;
-using namespace isc::dns;
-using namespace isc::dns::rdata;
-using namespace isc::resolve;
-using namespace std;
-
-/// RecursiveQuery Test - 2
-///
-/// The second part of the RecursiveQuery unit tests, this attempts to get the
-/// RecursiveQuery object to follow a set of referrals for "www.example.org" to
-/// and to invoke TCP fallback on one of the queries. In particular, we expect
-/// that the test will do the following in an attempt to resolve
-/// www.example.org:
-///
-/// - Send question over UDP to "root" - get referral to "org".
-/// - Send question over UDP to "org" - get referral to "example.org" with TC bit set.
-/// - Send question over TCP to "org" - get referral to "example.org".
-/// - Send question over UDP to "example.org" - get response for www.example.org.
-///
-/// (The order of queries is set in this way in order to also test that after a
-/// failover to TCP, queries revert to UDP).
-///
-/// By using the "test_server_" element of RecursiveQuery, all queries are
-/// directed to one or other of the "servers" in the RecursiveQueryTest2 class,
-/// regardless of the glue returned in referrals.
-
-namespace asiolink {
-
-const std::string TEST_ADDRESS = "127.0.0.1"; ///< Servers are on this address
-const uint16_t TEST_PORT = 5301; ///< ... and this port
-const size_t BUFFER_SIZE = 1024; ///< For all buffers
-const char* WWW_EXAMPLE_ORG = "192.0.2.254"; ///< Address of www.example.org
-
-// As the test is fairly long and complex, debugging "print" statements have
-// been left in although they are disabled. Set the following to "true" to
-// enable them.
-const bool DEBUG_PRINT = false;
-
-/// \brief Test fixture for the RecursiveQuery Test
-class RecursiveQueryTest2 : public virtual ::testing::Test
-{
-public:
-
- /// \brief Status of query
- ///
- /// Set before the query and then by each "server" when responding.
- enum QueryStatus {
- NONE = 0, ///< Default
- UDP_ROOT = 1, ///< Query root server over UDP
- UDP_ORG = 2, ///< Query ORG server over UDP
- TCP_ORG = 3, ///< Query ORG server over TCP
- UDP_EXAMPLE_ORG = 4, ///< Query EXAMPLE.ORG server over UDP
- COMPLETE = 5 ///< Query is complete
- };
-
- // Common stuff
- bool debug_; ///< Set true for debug print
- IOService service_; ///< Service to run everything
- DNSService dns_service_; ///< Resolver is part of "server"
- QuestionPtr question_; ///< What to ask
- QueryStatus last_; ///< What was the last state
- QueryStatus expected_; ///< Expected next state
- OutputBufferPtr question_buffer_; ///< Question we expect to receive
-
- // Data for TCP Server
- size_t tcp_cumulative_; ///< Cumulative TCP data received
- tcp::endpoint tcp_endpoint_; ///< Endpoint for TCP receives
- size_t tcp_length_; ///< Expected length value
- uint8_t tcp_receive_buffer_[BUFFER_SIZE]; ///< Receive buffer for TCP I/O
- OutputBufferPtr tcp_send_buffer_; ///< Send buffer for TCP I/O
- tcp::socket tcp_socket_; ///< Socket used by TCP server
-
- /// Data for UDP
- udp::endpoint udp_remote_; ///< Endpoint for UDP receives
- size_t udp_length_; ///< Expected length value
- uint8_t udp_receive_buffer_[BUFFER_SIZE]; ///< Receive buffer for UDP I/O
- OutputBufferPtr udp_send_buffer_; ///< Send buffer for UDP I/O
- udp::socket udp_socket_; ///< Socket used by UDP server
-
- /// \brief Constructor
- RecursiveQueryTest2() :
- debug_(DEBUG_PRINT),
- service_(),
- dns_service_(service_, NULL, NULL, NULL),
- question_(new Question(Name("www.example.org"), RRClass::IN(), RRType::A())),
- last_(NONE),
- expected_(NONE),
- question_buffer_(new OutputBuffer(BUFFER_SIZE)),
- tcp_cumulative_(0),
- tcp_endpoint_(asio::ip::address::from_string(TEST_ADDRESS), TEST_PORT),
- tcp_length_(0),
- tcp_receive_buffer_(),
- tcp_send_buffer_(new OutputBuffer(BUFFER_SIZE)),
- tcp_socket_(service_.get_io_service()),
- udp_remote_(),
- udp_length_(0),
- udp_receive_buffer_(),
- udp_send_buffer_(new OutputBuffer(BUFFER_SIZE)),
- udp_socket_(service_.get_io_service(), udp::v4())
- {}
-
- /// \brief Set Common Message Bits
- ///
- /// Sets up the common bits of a response message returned by the handlers.
- ///
- /// \param msg Message buffer in RENDER mode.
- /// \param qid QIT to set the message to
- void setCommonMessage(isc::dns::Message& msg, uint16_t qid = 0) {
- msg.setQid(qid);
- msg.setHeaderFlag(Message::HEADERFLAG_QR);
- msg.setOpcode(Opcode::QUERY());
- msg.setHeaderFlag(Message::HEADERFLAG_AA);
- msg.setRcode(Rcode::NOERROR());
- msg.addQuestion(*question_);
- }
-
- /// \brief Set Referral to "org"
- ///
- /// Sets up the passed-in message (expected to be in "RENDER" mode to
- /// indicate a referral to fictitious .org nameservers.
- ///
- /// \param msg Message to update with referral information.
- void setReferralOrg(isc::dns::Message& msg) {
- if (debug_) {
- cout << "setReferralOrg(): creating referral to .org nameservers" << endl;
- }
-
- // Do a referral to org. We'll define all NS records as "in-zone"
- // nameservers (and supply glue) to avoid the possibility of the
- // resolver starting another recursive query to resolve the address of
- // a nameserver.
- RRsetPtr org_ns(new RRset(Name("org."), RRClass::IN(), RRType::NS(), RRTTL(300)));
- org_ns->addRdata(createRdata(RRType::NS(), RRClass::IN(), "ns1.org."));
- org_ns->addRdata(createRdata(RRType::NS(), RRClass::IN(), "ns2.org."));
- msg.addRRset(Message::SECTION_AUTHORITY, org_ns);
-
- RRsetPtr org_ns1(new RRset(Name("ns1.org."), RRClass::IN(), RRType::A(), RRTTL(300)));
- org_ns1->addRdata(createRdata(RRType::A(), RRClass::IN(), "192.0.2.1"));
- msg.addRRset(Message::SECTION_ADDITIONAL, org_ns1);
-
- RRsetPtr org_ns2(new RRset(Name("ns2.org."), RRClass::IN(), RRType::A(), RRTTL(300)));
- org_ns2->addRdata(createRdata(RRType::A(), RRClass::IN(), "192.0.2.2"));
- msg.addRRset(Message::SECTION_ADDITIONAL, org_ns2);
- }
-
- /// \brief Set Referral to "example.org"
- ///
- /// Sets up the passed-in message (expected to be in "RENDER" mode to
- /// indicate a referral to fictitious example.org nameservers.
- ///
- /// \param msg Message to update with referral information.
- void setReferralExampleOrg(isc::dns::Message& msg) {
- if (debug_) {
- cout << "setReferralExampleOrg(): creating referral to example.org nameservers" << endl;
- }
-
- // Do a referral to example.org. As before, we'll define all NS
- // records as "in-zone" nameservers (and supply glue) to avoid the
- // possibility of the resolver starting another recursive query to look
- // up the address of the nameserver.
- RRsetPtr example_org_ns(new RRset(Name("example.org."), RRClass::IN(), RRType::NS(), RRTTL(300)));
- example_org_ns->addRdata(createRdata(RRType::NS(), RRClass::IN(), "ns1.example.org."));
- example_org_ns->addRdata(createRdata(RRType::NS(), RRClass::IN(), "ns2.example.org."));
- msg.addRRset(Message::SECTION_AUTHORITY, example_org_ns);
-
- RRsetPtr example_org_ns1(new RRset(Name("ns1.example.org."), RRClass::IN(), RRType::A(), RRTTL(300)));
- example_org_ns1->addRdata(createRdata(RRType::A(), RRClass::IN(), "192.0.2.11"));
- msg.addRRset(Message::SECTION_ADDITIONAL, example_org_ns1);
-
- RRsetPtr example_org_ns2(new RRset(Name("ns2.example.org."), RRClass::IN(), RRType::A(), RRTTL(300)));
- example_org_ns2->addRdata(createRdata(RRType::A(), RRClass::IN(), "192.0.2.21"));
- msg.addRRset(Message::SECTION_ADDITIONAL, example_org_ns2);
- }
-
- /// \brief Set Answer to "www.example.org"
- ///
- /// Sets up the passed-in message (expected to be in "RENDER" mode) to
- /// indicate an authoritative answer to www.example.org.
- ///
- /// \param msg Message to update with referral information.
- void setAnswerWwwExampleOrg(isc::dns::Message& msg) {
- if (debug_) {
- cout << "setAnswerWwwExampleOrg(): creating answer for www.example.org" << endl;
- }
-
- // Give a response for www.example.org.
- RRsetPtr www_example_org_a(new RRset(Name("www.example.org."), RRClass::IN(), RRType::A(), RRTTL(300)));
- www_example_org_a->addRdata(createRdata(RRType::A(), RRClass::IN(), WWW_EXAMPLE_ORG));
- msg.addRRset(Message::SECTION_ANSWER, www_example_org_a);
-
- // ... and add the Authority and Additional sections. (These are the
- // same as in the referral to example.org from the .org nameserver.)
- setReferralExampleOrg(msg);
- }
-
- /// \brief UDP Receive Handler
- ///
- /// This is invoked when a message is received over UDP from the
- /// RecursiveQuery object under test. It formats an answer and sends it
- /// asynchronously, with the UdpSendHandler method being specified as the
- /// completion handler.
- ///
- /// \param ec ASIO error code, completion code of asynchronous I/O issued
- /// by the "server" to receive data.
- /// \param length Amount of data received.
- void udpReceiveHandler(error_code ec = error_code(), size_t length = 0) {
- if (debug_) {
- cout << "udpReceiveHandler(): error = " << ec.value() <<
- ", length = " << length << ", last state = " << last_ <<
- ", expected state = " << expected_ << endl;
- }
-
- // Expected state should be one greater than the last state.
- EXPECT_EQ(static_cast<int>(expected_), static_cast<int>(last_) + 1);
- last_ = expected_;
-
- // The QID in the incoming data is random so set it to 0 for the
- // data comparison check. (It is set to 0 in the buffer containing
- // the expected data.)
- uint16_t qid = readUint16(udp_receive_buffer_);
- udp_receive_buffer_[0] = udp_receive_buffer_[1] = 0;
-
- // Check that question we received is what was expected.
- checkReceivedPacket(udp_receive_buffer_, length);
-
- // The message returned depends on what state we are in. Set up
- // common stuff first: bits not mentioned are set to 0.
- Message msg(Message::RENDER);
- setCommonMessage(msg, qid);
-
- // Set up state-dependent bits:
- switch (expected_) {
- case UDP_ROOT:
- // Return a referral to org. We then expect to query the "org"
- // nameservers over UDP next.
- setReferralOrg(msg);
- expected_ = UDP_ORG;
- break;
-
- case UDP_ORG:
- // Return a referral to example.org. We explicitly set the TC bit to
- // force a repeat query to the .org nameservers over TCP.
- setReferralExampleOrg(msg);
- if (debug_) {
- cout << "udpReceiveHandler(): setting TC bit" << endl;
- }
- msg.setHeaderFlag(Message::HEADERFLAG_TC);
- expected_ = TCP_ORG;
- break;
-
- case UDP_EXAMPLE_ORG:
- // Return the answer to the question.
- setAnswerWwwExampleOrg(msg);
- expected_ = COMPLETE;
- break;
-
- default:
- FAIL() << "UdpReceiveHandler called with unknown state";
- }
-
- // Convert to wire format
- udp_send_buffer_->clear();
- MessageRenderer renderer(*udp_send_buffer_);
- msg.toWire(renderer);
-
- // Return a message back to the IOFetch object (after setting the
- // expected length of data for the check in the send handler).
- udp_length_ = udp_send_buffer_->getLength();
- udp_socket_.async_send_to(asio::buffer(udp_send_buffer_->getData(),
- udp_send_buffer_->getLength()),
- udp_remote_,
- boost::bind(&RecursiveQueryTest2::udpSendHandler,
- this, _1, _2));
- }
-
- /// \brief UDP Send Handler
- ///
- /// Called when a send operation of the UDP server (i.e. a response
- /// being sent to the RecursiveQuery) has completed, this re-issues
- /// a read call.
- ///
- /// \param ec Completion error code of the send.
- /// \param length Actual number of bytes sent.
- void udpSendHandler(error_code ec = error_code(), size_t length = 0) {
- if (debug_) {
- cout << "udpSendHandler(): error = " << ec.value() <<
- ", length = " << length << endl;
- }
-
- // Check send was OK
- EXPECT_EQ(0, ec.value());
- EXPECT_EQ(udp_length_, length);
-
- // Reissue the receive call to await the next message.
- udp_socket_.async_receive_from(
- asio::buffer(udp_receive_buffer_, sizeof(udp_receive_buffer_)),
- udp_remote_,
- boost::bind(&RecursiveQueryTest2::udpReceiveHandler, this, _1, _2));
- }
-
- /// \brief Completion Handler for Accepting TCP Data
- ///
- /// Called when the remote system connects to the "TCP server". It issues
- /// an asynchronous read on the socket to read data.
- ///
- /// \param socket Socket on which data will be received
- /// \param ec Boost error code, value should be zero.
- void tcpAcceptHandler(error_code ec = error_code(), size_t length = 0) {
- if (debug_) {
- cout << "tcpAcceptHandler(): error = " << ec.value() <<
- ", length = " << length << endl;
- }
-
- // Expect that the accept completed without a problem.
- EXPECT_EQ(0, ec.value());
-
- // Initiate a read on the socket, indicating that nothing has yet been
- // received.
- tcp_cumulative_ = 0;
- tcp_socket_.async_receive(
- asio::buffer(tcp_receive_buffer_, sizeof(tcp_receive_buffer_)),
- boost::bind(&RecursiveQueryTest2::tcpReceiveHandler, this, _1, _2));
- }
-
- /// \brief Completion Handler for Receiving TCP Data
- ///
- /// Reads data from the RecursiveQuery object and loops, reissuing reads,
- /// until all the message has been read. It then returns an appropriate
- /// response.
- ///
- /// \param socket Socket to use to send the answer
- /// \param ec ASIO error code, completion code of asynchronous I/O issued
- /// by the "server" to receive data.
- /// \param length Amount of data received.
- void tcpReceiveHandler(error_code ec = error_code(), size_t length = 0) {
- if (debug_) {
- cout << "tcpReceiveHandler(): error = " << ec.value() <<
- ", length = " << length <<
- ", cumulative = " << tcp_cumulative_ << endl;
- }
-
- // Expect that the receive completed without a problem.
- EXPECT_EQ(0, ec.value());
-
- // Have we received all the data? We know this by checking if the two-
- // byte length count in the message is equal to the data received.
- tcp_cumulative_ += length;
- bool complete = false;
- if (tcp_cumulative_ > 2) {
- uint16_t dns_length = readUint16(tcp_receive_buffer_);
- complete = ((dns_length + 2) == tcp_cumulative_);
- }
-
- if (!complete) {
- if (debug_) {
- cout << "tcpReceiveHandler(): read not complete, " <<
- "issuing another read" << endl;
- }
-
- // Not complete yet, issue another read.
- tcp_socket_.async_receive(
- asio::buffer(tcp_receive_buffer_ + tcp_cumulative_,
- sizeof(tcp_receive_buffer_) - tcp_cumulative_),
- boost::bind(&RecursiveQueryTest2::tcpReceiveHandler, this, _1, _2));
- return;
- }
-
- // Have received a TCP message. Expected state should be one greater
- // than the last state.
- EXPECT_EQ(static_cast<int>(expected_), static_cast<int>(last_) + 1);
- last_ = expected_;
-
- // Check that question we received is what was expected. Note that we
- // have to ignore the two-byte header in order to parse the message.
- checkReceivedPacket(tcp_receive_buffer_ + 2, length - 2);
-
- // Return a message back. This is a referral to example.org, which
- // should result in another query over UDP. Note the setting of the
- // QID in the returned message with what was in the received message.
- Message msg(Message::RENDER);
- setCommonMessage(msg, readUint16(tcp_receive_buffer_));
- setReferralExampleOrg(msg);
-
- // Convert to wire format
- tcp_send_buffer_->clear();
- MessageRenderer renderer(*tcp_send_buffer_);
- msg.toWire(renderer);
-
- // Expected next state (when checked) is the UDP query to example.org.
- // Also, take this opportunity to clear the accumulated read count in
- // readiness for the next read. (If any - at present, there is only
- // one read in the test, although extensions to this test suite could
- // change that.)
- expected_ = UDP_EXAMPLE_ORG;
- tcp_cumulative_ = 0;
-
- // We'll write the message in two parts, the count and the message
- // itself. This saves having to prepend the count onto the start of a
- // buffer. When specifying the send handler, the expected size of the
- // data written is passed as the first parameter so that the handler
- // can check it.
- uint8_t count[2];
- writeUint16(tcp_send_buffer_->getLength(), count);
- tcp_socket_.async_send(asio::buffer(count, 2),
- boost::bind(&RecursiveQueryTest2::tcpSendHandler, this,
- 2, _1, _2));
- tcp_socket_.async_send(asio::buffer(tcp_send_buffer_->getData(),
- tcp_send_buffer_->getLength()),
- boost::bind(&RecursiveQueryTest2::tcpSendHandler, this,
- tcp_send_buffer_->getLength(), _1, _2));
- }
-
- /// \brief Completion Handler for Sending TCP data
- ///
- /// Called when the asynchronous send of data back to the RecursiveQuery
- /// by the TCP "server" in this class has completed. (This send has to
- /// be asynchronous because control needs to return to the caller in order
- /// for the IOService "run()" method to be called to run the handlers.)
- ///
- /// \param expected_length Number of bytes that were expected to have been sent.
- /// \param ec Boost error code, value should be zero.
- /// \param length Number of bytes sent.
- void tcpSendHandler(size_t expected_length = 0, error_code ec = error_code(),
- size_t length = 0)
- {
- if (debug_) {
- cout << "tcpSendHandler(): error = " << ec.value() <<
- ", length = " << length <<
- ", (expected length = " << expected_length << ")" << endl;
- }
- EXPECT_EQ(0, ec.value()); // Expect no error
- EXPECT_EQ(expected_length, length); // And that amount sent is as expected
- }
-
- /// \brief Check Received Packet
- ///
- /// Checks the packet received from the RecursiveQuery object to ensure
- /// that the question is what is expected.
- ///
- /// \param data Start of data. This is the start of the received buffer in
- /// the case of UDP data, and an offset into the buffer past the
- /// count field for TCP data.
- /// \param length Length of data.
- void checkReceivedPacket(uint8_t* data, size_t length) {
-
- // Decode the received buffer.
- InputBuffer buffer(data, length);
- Message message(Message::PARSE);
- message.fromWire(buffer);
-
- // Check the packet.
- EXPECT_FALSE(message.getHeaderFlag(Message::HEADERFLAG_QR));
-
- Question question = **(message.beginQuestion());
- EXPECT_TRUE(question == *question_);
- }
-};
-
-/// \brief Resolver Callback Object
-///
-/// Holds the success and failure callback methods for the resolver
-class ResolverCallback : public isc::resolve::ResolverInterface::Callback {
-public:
- /// \brief Constructor
- ResolverCallback(IOService& service) :
- service_(service), run_(false), status_(false), debug_(DEBUG_PRINT)
- {}
-
- /// \brief Destructor
- virtual ~ResolverCallback()
- {}
-
- /// \brief Resolver Callback Success
- ///
- /// Called if the resolver detects that the call has succeeded.
- ///
- /// \param response Answer to the question.
- virtual void success(const isc::dns::MessagePtr response) {
- if (debug_) {
- cout << "ResolverCallback::success(): answer received" << endl;
- }
-
- // There should be one RR each in the question and answer sections, and
- // two RRs in each of the the authority and additional sections.
- EXPECT_EQ(1, response->getRRCount(Message::SECTION_QUESTION));
- EXPECT_EQ(1, response->getRRCount(Message::SECTION_ANSWER));
- EXPECT_EQ(2, response->getRRCount(Message::SECTION_AUTHORITY));
- EXPECT_EQ(2, response->getRRCount(Message::SECTION_ADDITIONAL));
-
- // Check the answer - that the RRset is there...
- EXPECT_TRUE(response->hasRRset(Message::SECTION_ANSWER,
- RRsetPtr(new RRset(Name("www.example.org."),
- RRClass::IN(),
- RRType::A(),
- RRTTL(300)))));
- const RRsetIterator rrset_i = response->beginSection(Message::SECTION_ANSWER);
-
- // ... get iterator into the Rdata of this RRset and point to first
- // element...
- const RdataIteratorPtr rdata_i = (*rrset_i)->getRdataIterator();
- rdata_i->first();
-
- // ... and check it is what we expect.
- EXPECT_EQ(string(WWW_EXAMPLE_ORG), rdata_i->getCurrent().toText());
-
- // Flag completion
- run_ = true;
- status_ = true;
-
- service_.stop(); // Cause run() to exit.
- }
-
- /// \brief Resolver Failure Completion
- ///
- /// Called if the resolver detects that the resolution has failed.
- virtual void failure() {
- if (debug_) {
- cout << "ResolverCallback::success(): resolution failure" << endl;
- }
- FAIL() << "Resolver reported completion failure";
-
- // Flag completion
- run_ = true;
- status_ = false;
-
- service_.stop(); // Cause run() to exit.
- }
-
- /// \brief Return status of "run" flag
- bool getRun() const {
- return (run_);
- }
-
- /// \brief Return "status" flag
- bool getStatus() const {
- return (status_);
- }
-
-private:
- IOService& service_; ///< Service handling the run queue
- bool run_; ///< Set true when completion handler run
- bool status_; ///< Set true for success, false on error
- bool debug_; ///< Debug flag
-};
-
-// Sets up the UDP and TCP "servers", then tries a resolution.
-
-TEST_F(RecursiveQueryTest2, Resolve) {
-
- // Set up the UDP server and issue the first read. The endpoint from which
- // the query is sent is put in udp_endpoint_ when the read completes, which
- // is referenced in the callback as the place to which the response is sent.
- udp_socket_.set_option(socket_base::reuse_address(true));
- udp_socket_.bind(udp::endpoint(address::from_string(TEST_ADDRESS), TEST_PORT));
- udp_socket_.async_receive_from(asio::buffer(udp_receive_buffer_,
- sizeof(udp_receive_buffer_)),
- udp_remote_,
- boost::bind(&RecursiveQueryTest2::udpReceiveHandler,
- this, _1, _2));
-
- // Set up the TCP server and issue the accept. Acceptance will cause the
- // read to be issued.
- tcp::acceptor acceptor(service_.get_io_service(),
- tcp::endpoint(tcp::v4(), TEST_PORT));
- acceptor.async_accept(tcp_socket_,
- boost::bind(&RecursiveQueryTest2::tcpAcceptHandler,
- this, _1, 0));
-
- // Set up the RecursiveQuery object.
- std::vector<std::pair<std::string, uint16_t> > upstream; // Empty
- std::vector<std::pair<std::string, uint16_t> > upstream_root; // Empty
- RecursiveQuery query(dns_service_, upstream, upstream_root);
- query.setTestServer(TEST_ADDRESS, TEST_PORT);
-
- // Set up callback for the tor eceive notification that the query has
- // completed.
- ResolverInterface::CallbackPtr
- resolver_callback(new ResolverCallback(service_));
-
- // Kick off the resolution process. We expect the first question to go to
- // "root".
- expected_ = UDP_ROOT;
- query.resolve(question_, resolver_callback);
- service_.run();
-
- // Check what ran. (We have to cast the callback to ResolverCallback as we
- // lost the information on the derived class when we used a
- // ResolverInterface::CallbackPtr to store a pointer to it.)
- ResolverCallback* rc = static_cast<ResolverCallback*>(resolver_callback.get());
- EXPECT_TRUE(rc->getRun());
- EXPECT_TRUE(rc->getStatus());
-}
-
-} // namespace asiolink
diff --git a/src/lib/cache/tests/Makefile.am b/src/lib/cache/tests/Makefile.am
index 8c7b0af..bef8fef 100644
--- a/src/lib/cache/tests/Makefile.am
+++ b/src/lib/cache/tests/Makefile.am
@@ -53,6 +53,7 @@ endif
run_unittests_LDADD += $(top_builddir)/src/lib/cache/libcache.la
run_unittests_LDADD += $(top_builddir)/src/lib/nsas/libnsas.la
run_unittests_LDADD += $(top_builddir)/src/lib/dns/libdns++.la
+run_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libasiolink.la
run_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
endif
diff --git a/src/lib/nsas/address_entry.h b/src/lib/nsas/address_entry.h
index 148d479..8698017 100644
--- a/src/lib/nsas/address_entry.h
+++ b/src/lib/nsas/address_entry.h
@@ -21,7 +21,7 @@
/// convenience methods for accessing and updating the information.
#include <stdint.h>
-#include "asiolink.h"
+#include <asiolink/io_address.h>
namespace isc {
namespace nsas {
@@ -39,7 +39,7 @@ public:
{}
/// \return Address object
- asiolink::IOAddress getAddress() const {
+ const asiolink::IOAddress& getAddress() const {
return address_;
}
diff --git a/src/lib/nsas/asiolink.h b/src/lib/nsas/asiolink.h
index f5af192..d95868f 100644
--- a/src/lib/nsas/asiolink.h
+++ b/src/lib/nsas/asiolink.h
@@ -18,41 +18,4 @@
#include <string>
#include <sys/socket.h>
-namespace asiolink {
-
-/// \brief IO Address Dummy Class
-///
-/// As part of ther resolver, Evan has written the asiolink.h file, which
-/// encapsulates some of the boost::asio classes. Until these are checked
-/// into trunk and merged with this branch, these dummy classes should fulfill
-/// their function.
-
-class IOAddress {
-public:
- /// \param address_str String representing the address
- IOAddress(const std::string& address_str) : address_(address_str)
- {}
-
- /// \param Just a virtual destructor
- virtual ~ IOAddress() { }
-
- /// \return Textual representation of the address
- std::string toText() const
- {return address_;}
-
- /// \return Address family of the address
- virtual short getFamily() const {
- return ((address_.find(".") != std::string::npos) ? AF_INET : AF_INET6);
- }
-
- /// \return true if two addresses are equal
- bool equal(const IOAddress& address) const
- {return (toText() == address.toText());}
-
-private:
- std::string address_; ///< Address represented
-};
-
-} // namespace asiolink
-
#endif // __ASIOLINK_H
diff --git a/src/lib/nsas/nameserver_address.cc b/src/lib/nsas/nameserver_address.cc
index b2ed55c..b76d7b8 100644
--- a/src/lib/nsas/nameserver_address.cc
+++ b/src/lib/nsas/nameserver_address.cc
@@ -23,7 +23,9 @@ namespace nsas {
void
NameserverAddress::updateRTT(uint32_t rtt) const {
// We delegate it to the address entry inside the nameserver entry
- ns_->updateAddressRTT(rtt, address_.getAddress(), family_);
+ if (ns_) {
+ ns_->updateAddressRTT(rtt, address_.getAddress(), family_);
+ }
}
} // namespace nsas
diff --git a/src/lib/nsas/nameserver_address_store.cc b/src/lib/nsas/nameserver_address_store.cc
index 7bb0eee..4efe491 100644
--- a/src/lib/nsas/nameserver_address_store.cc
+++ b/src/lib/nsas/nameserver_address_store.cc
@@ -93,5 +93,18 @@ NameserverAddressStore::lookup(const string& zone, const RRClass& class_code,
zone_obj.second->addCallback(callback, family);
}
+void
+NameserverAddressStore::cancel(const string& zone,
+ const RRClass& class_code,
+ const boost::shared_ptr<AddressRequestCallback>& callback,
+ AddressFamily family)
+{
+ boost::shared_ptr<ZoneEntry> entry(zone_hash_->get(HashKey(zone,
+ class_code)));
+ if (entry) {
+ entry->removeCallback(callback, family);
+ }
+}
+
} // namespace nsas
} // namespace isc
diff --git a/src/lib/nsas/nameserver_address_store.h b/src/lib/nsas/nameserver_address_store.h
index f183871..9804a55 100644
--- a/src/lib/nsas/nameserver_address_store.h
+++ b/src/lib/nsas/nameserver_address_store.h
@@ -87,6 +87,13 @@ public:
boost::shared_ptr<AddressRequestCallback> callback, AddressFamily
family = ANY_OK);
+ /// \brief cancel the given lookup action
+ ///
+ /// \param callback Callback object that would be called
+ void cancel(const std::string& zone, const dns::RRClass& class_code,
+ const boost::shared_ptr<AddressRequestCallback>& callback,
+ AddressFamily family = ANY_OK);
+
/// \brief Protected Members
///
/// These members should be private. However, with so few public methods
diff --git a/src/lib/nsas/nameserver_entry.cc b/src/lib/nsas/nameserver_entry.cc
index 9522e81..5c7873e 100644
--- a/src/lib/nsas/nameserver_entry.cc
+++ b/src/lib/nsas/nameserver_entry.cc
@@ -35,6 +35,8 @@
#include <dns/question.h>
#include <resolve/resolver_interface.h>
+#include <asiolink/io_address.h>
+
#include "address_entry.h"
#include "nameserver_address.h"
#include "nameserver_entry.h"
@@ -140,7 +142,7 @@ NameserverEntry::setAddressRTT(const IOAddress& address, uint32_t rtt) {
AddressFamily family(V4_ONLY);
for (;;) {
BOOST_FOREACH(AddressEntry& entry, addresses_[family]) {
- if (entry.getAddress().equal(address)) {
+ if (entry.getAddress().equals(address)) {
entry.setRTT(rtt);
return;
}
@@ -181,7 +183,7 @@ NameserverEntry::updateAddressRTT(uint32_t rtt,
{
Lock lock(mutex_);
for (size_t i(0); i < addresses_[family].size(); ++ i) {
- if (addresses_[family][i].getAddress().equal(address)) {
+ if (addresses_[family][i].getAddress().equals(address)) {
updateAddressRTTAtIndex(rtt, i, family);
return;
}
@@ -226,8 +228,9 @@ class NameserverEntry::ResolverCallback :
response_message->getRcode() != isc::dns::Rcode::NOERROR() ||
response_message->getRRCount(isc::dns::Message::SECTION_ANSWER) == 0) {
failureInternal(lock);
+ return;
}
-
+
isc::dns::RRsetIterator rrsi =
response_message->beginSection(isc::dns::Message::SECTION_ANSWER);
const isc::dns::RRsetPtr response = *rrsi;
diff --git a/src/lib/nsas/tests/Makefile.am b/src/lib/nsas/tests/Makefile.am
index 530b730..9d9e61c 100644
--- a/src/lib/nsas/tests/Makefile.am
+++ b/src/lib/nsas/tests/Makefile.am
@@ -54,6 +54,7 @@ endif
run_unittests_LDADD += $(top_builddir)/src/lib/nsas/libnsas.la
run_unittests_LDADD += $(top_builddir)/src/lib/dns/libdns++.la
+run_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libasiolink.la
run_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
endif
diff --git a/src/lib/nsas/tests/address_entry_unittest.cc b/src/lib/nsas/tests/address_entry_unittest.cc
index 716068c..02fef51 100644
--- a/src/lib/nsas/tests/address_entry_unittest.cc
+++ b/src/lib/nsas/tests/address_entry_unittest.cc
@@ -24,12 +24,12 @@
#include <stdint.h>
-#include "../asiolink.h"
+#include <asiolink/io_address.h>
#include "../address_entry.h"
static std::string V4A_TEXT("1.2.3.4");
static std::string V4B_TEXT("5.6.7.8");
-static std::string V6A_TEXT("2001:dead:beef::0");
+static std::string V6A_TEXT("2001:dead:beef::");
static std::string V6B_TEXT("1984:1985::1986:1987");
using namespace asiolink;
diff --git a/src/lib/nsas/tests/nameserver_address_unittest.cc b/src/lib/nsas/tests/nameserver_address_unittest.cc
index 35a46f0..1f924b3 100644
--- a/src/lib/nsas/tests/nameserver_address_unittest.cc
+++ b/src/lib/nsas/tests/nameserver_address_unittest.cc
@@ -100,7 +100,7 @@ protected:
// Test that the address is equal to the address in NameserverEntry
TEST_F(NameserverAddressTest, Address) {
- EXPECT_TRUE(ns_address_.getAddress().equal( ns_sample_.getAddressAtIndex(TEST_ADDRESS_INDEX)));
+ EXPECT_TRUE(ns_address_.getAddress().equals( ns_sample_.getAddressAtIndex(TEST_ADDRESS_INDEX)));
boost::shared_ptr<NameserverEntry> empty_ne((NameserverEntry*)NULL);
// It will throw an NullNameserverEntryPointer exception with the empty NameserverEntry shared pointer
diff --git a/src/lib/nsas/tests/nameserver_entry_unittest.cc b/src/lib/nsas/tests/nameserver_entry_unittest.cc
index 9e4cec7..398c568 100644
--- a/src/lib/nsas/tests/nameserver_entry_unittest.cc
+++ b/src/lib/nsas/tests/nameserver_entry_unittest.cc
@@ -153,7 +153,7 @@ TEST_F(NameserverEntryTest, SetRTT) {
int matchcount = 0;
for (NameserverEntry::AddressVectorIterator i = newvec.begin();
i != newvec.end(); ++i) {
- if (i->getAddress().equal(first_address)) {
+ if (i->getAddress().equals(first_address)) {
++matchcount;
EXPECT_EQ(i->getAddressEntry().getRTT(), new_rtt);
}
@@ -188,7 +188,7 @@ TEST_F(NameserverEntryTest, Unreachable) {
int matchcount = 0;
for (NameserverEntry::AddressVectorIterator i = newvec.begin();
i != newvec.end(); ++i) {
- if (i->getAddress().equal(first_address)) {
+ if (i->getAddress().equals(first_address)) {
++matchcount;
EXPECT_TRUE(i->getAddressEntry().isUnreachable());
}
diff --git a/src/lib/nsas/tests/zone_entry_unittest.cc b/src/lib/nsas/tests/zone_entry_unittest.cc
index 8a3c6f2..d10f12d 100644
--- a/src/lib/nsas/tests/zone_entry_unittest.cc
+++ b/src/lib/nsas/tests/zone_entry_unittest.cc
@@ -165,9 +165,9 @@ protected:
EXPECT_EQ(failure_count, callback_->unreachable_count_);
EXPECT_EQ(success_count, callback_->successes_.size());
for (size_t i = 0; i < callback_->successes_.size(); ++ i) {
- EXPECT_TRUE(IOAddress("192.0.2.1").equal(
+ EXPECT_TRUE(IOAddress("192.0.2.1").equals(
callback_->successes_[i].getAddress()) ||
- IOAddress("2001:db8::1").equal(
+ IOAddress("2001:db8::1").equals(
callback_->successes_[i].getAddress()));
}
}
@@ -234,7 +234,7 @@ TEST_F(ZoneEntryTest, ChangedNS) {
EXPECT_NO_THROW(resolver_->answer(1, ns_name_, RRType::A(),
rdata::in::A("192.0.2.1")));
ASSERT_EQ(1, callback_->successes_.size());
- EXPECT_TRUE(IOAddress("192.0.2.1").equal(
+ EXPECT_TRUE(IOAddress("192.0.2.1").equals(
callback_->successes_[0].getAddress()));
EXPECT_NO_THROW(resolver_->answer(2, ns_name_, RRType::AAAA(),
rdata::in::AAAA("2001:db8::1")));
@@ -257,7 +257,7 @@ TEST_F(ZoneEntryTest, ChangedNS) {
EXPECT_NO_THROW(resolver_->answer(4, different_name, RRType::A(),
rdata::in::A("192.0.2.2")));
ASSERT_EQ(2, callback_->successes_.size());
- EXPECT_TRUE(IOAddress("192.0.2.2").equal(
+ EXPECT_TRUE(IOAddress("192.0.2.2").equals(
callback_->successes_[1].getAddress()));
// And now, switch back, as it timed out again
@@ -270,7 +270,7 @@ TEST_F(ZoneEntryTest, ChangedNS) {
EXPECT_EQ(7, resolver_->requests.size());
EXPECT_EQ(Fetchable::READY, zone->getState());
ASSERT_EQ(3, callback_->successes_.size());
- EXPECT_TRUE(IOAddress("192.0.2.1").equal(
+ EXPECT_TRUE(IOAddress("192.0.2.1").equals(
callback_->successes_[0].getAddress()));
}
@@ -306,9 +306,9 @@ TEST_F(ZoneEntryTest, CallbacksAnswered) {
rdata::in::A("192.0.2.1")));
// Two are answered (ANY and V4)
ASSERT_EQ(2, callback_->successes_.size());
- EXPECT_TRUE(IOAddress("192.0.2.1").equal(
+ EXPECT_TRUE(IOAddress("192.0.2.1").equals(
callback_->successes_[0].getAddress()));
- EXPECT_TRUE(IOAddress("192.0.2.1").equal(
+ EXPECT_TRUE(IOAddress("192.0.2.1").equals(
callback_->successes_[1].getAddress()));
// None are rejected
EXPECT_EQ(0, callback_->unreachable_count_);
@@ -318,7 +318,7 @@ TEST_F(ZoneEntryTest, CallbacksAnswered) {
// This should answer the third callback
EXPECT_EQ(0, callback_->unreachable_count_);
ASSERT_EQ(3, callback_->successes_.size());
- EXPECT_TRUE(IOAddress("2001:db8::1").equal(
+ EXPECT_TRUE(IOAddress("2001:db8::1").equals(
callback_->successes_[2].getAddress()));
// It should think it is ready
EXPECT_EQ(Fetchable::READY, zone->getState());
@@ -326,7 +326,7 @@ TEST_F(ZoneEntryTest, CallbacksAnswered) {
zone->addCallback(callback_, V4_ONLY);
EXPECT_EQ(3, resolver_->requests.size());
ASSERT_EQ(4, callback_->successes_.size());
- EXPECT_TRUE(IOAddress("192.0.2.1").equal(
+ EXPECT_TRUE(IOAddress("192.0.2.1").equals(
callback_->successes_[3].getAddress()));
EXPECT_EQ(0, callback_->unreachable_count_);
}
@@ -366,9 +366,9 @@ TEST_F(ZoneEntryTest, CallbacksAOnly) {
EXPECT_NO_THROW(resolver_->answer(1, ns_name_, RRType::A(),
rdata::in::A("192.0.2.1")));
ASSERT_EQ(2, callback_->successes_.size());
- EXPECT_TRUE(IOAddress("192.0.2.1").equal(
+ EXPECT_TRUE(IOAddress("192.0.2.1").equals(
callback_->successes_[0].getAddress()));
- EXPECT_TRUE(IOAddress("192.0.2.1").equal(
+ EXPECT_TRUE(IOAddress("192.0.2.1").equals(
callback_->successes_[1].getAddress()));
EXPECT_EQ(1, callback_->unreachable_count_);
// Everything arriwed, so we are ready
@@ -377,7 +377,7 @@ TEST_F(ZoneEntryTest, CallbacksAOnly) {
zone->addCallback(callback_, V4_ONLY);
EXPECT_EQ(3, resolver_->requests.size());
ASSERT_EQ(3, callback_->successes_.size());
- EXPECT_TRUE(IOAddress("192.0.2.1").equal(
+ EXPECT_TRUE(IOAddress("192.0.2.1").equals(
callback_->successes_[2].getAddress()));
EXPECT_EQ(1, callback_->unreachable_count_);
@@ -439,9 +439,9 @@ TEST_F(ZoneEntryTest, CallbackTwoNS) {
// The other callbacks should be answered now
EXPECT_EQ(2, callback_->unreachable_count_);
ASSERT_EQ(2, callback_->successes_.size());
- EXPECT_TRUE(IOAddress("2001:db8::1").equal(
+ EXPECT_TRUE(IOAddress("2001:db8::1").equals(
callback_->successes_[0].getAddress()));
- EXPECT_TRUE(IOAddress("2001:db8::1").equal(
+ EXPECT_TRUE(IOAddress("2001:db8::1").equals(
callback_->successes_[1].getAddress()));
}
@@ -534,7 +534,7 @@ TEST_F(ZoneEntryTest, AddressTimeout) {
rdata::in::A("192.0.2.1"), 0));
// It answers, not rejects
ASSERT_EQ(1, callback_->successes_.size());
- EXPECT_TRUE(IOAddress("192.0.2.1").equal(
+ EXPECT_TRUE(IOAddress("192.0.2.1").equals(
callback_->successes_[0].getAddress()));
EXPECT_EQ(0, callback_->unreachable_count_);
// As well with IPv6
@@ -551,7 +551,7 @@ TEST_F(ZoneEntryTest, AddressTimeout) {
rdata::in::A("192.0.2.1"), 0));
EXPECT_EQ(0, callback_->unreachable_count_);
ASSERT_EQ(2, callback_->successes_.size());
- EXPECT_TRUE(IOAddress("192.0.2.1").equal(
+ EXPECT_TRUE(IOAddress("192.0.2.1").equals(
callback_->successes_[1].getAddress()));
}
diff --git a/src/lib/nsas/zone_entry.cc b/src/lib/nsas/zone_entry.cc
index 77f3dad..b028ca4 100644
--- a/src/lib/nsas/zone_entry.cc
+++ b/src/lib/nsas/zone_entry.cc
@@ -261,6 +261,18 @@ ZoneEntry::addCallback(CallbackPtr callback, AddressFamily family) {
}
}
+void
+ZoneEntry::removeCallback(const CallbackPtr& callback, AddressFamily family) {
+ Lock lock(mutex_);
+ std::vector<boost::shared_ptr<AddressRequestCallback> >::iterator i =
+ callbacks_[family].begin();
+ for (; i != callbacks_[family].end(); ++i) {
+ if (*i == callback) {
+ callbacks_[family].erase(i);
+ }
+ }
+}
+
namespace {
// This just moves items from one container to another
diff --git a/src/lib/nsas/zone_entry.h b/src/lib/nsas/zone_entry.h
index 28a42ea..a1f12bc 100644
--- a/src/lib/nsas/zone_entry.h
+++ b/src/lib/nsas/zone_entry.h
@@ -101,6 +101,15 @@ public:
void addCallback(boost::shared_ptr<AddressRequestCallback>
callback, AddressFamily family);
+ /**
+ * \short Remove a callback from the list
+ *
+ * \param callback The callback itself.
+ * \param family Which address family is acceptable as an answer?
+ */
+ void removeCallback(const boost::shared_ptr<AddressRequestCallback>&
+ callback, AddressFamily family);
+
/// \short Protected members, so they can be accessed by tests.
//@{
protected:
diff --git a/src/lib/resolve/Makefile.am b/src/lib/resolve/Makefile.am
index c9f1326..c6f65a4 100644
--- a/src/lib/resolve/Makefile.am
+++ b/src/lib/resolve/Makefile.am
@@ -14,5 +14,6 @@ libresolve_la_SOURCES = resolve.h resolve.cc
libresolve_la_SOURCES += resolver_interface.h
libresolve_la_SOURCES += resolver_callback.h resolver_callback.cc
libresolve_la_SOURCES += response_classifier.cc response_classifier.h
+libresolve_la_SOURCES += recursive_query.cc recursive_query.h
libresolve_la_LIBADD = $(top_builddir)/src/lib/dns/libdns++.la
libresolve_la_LIBADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
diff --git a/src/lib/resolve/recursive_query.cc b/src/lib/resolve/recursive_query.cc
new file mode 100644
index 0000000..eee98ca
--- /dev/null
+++ b/src/lib/resolve/recursive_query.cc
@@ -0,0 +1,757 @@
+// 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 <netinet/in.h>
+#include <stdlib.h>
+#include <sys/socket.h>
+#include <unistd.h> // for some IPC/network system calls
+
+#include <boost/lexical_cast.hpp>
+#include <boost/bind.hpp>
+
+#include <config.h>
+
+#include <log/dummylog.h>
+
+#include <dns/question.h>
+#include <dns/message.h>
+#include <dns/opcode.h>
+
+#include <resolve/resolve.h>
+#include <cache/resolver_cache.h>
+#include <nsas/address_request_callback.h>
+#include <nsas/nameserver_address.h>
+
+#include <asio.hpp>
+#include <asiolink/dns_service.h>
+#include <asiolink/io_fetch.h>
+#include <asiolink/io_service.h>
+#include <resolve/recursive_query.h>
+
+using isc::log::dlog;
+using namespace isc::dns;
+
+namespace asiolink {
+
+typedef std::vector<std::pair<std::string, uint16_t> > AddressVector;
+
+// Here we do not use the typedef above, as the SunStudio compiler
+// 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,
+ isc::nsas::NameserverAddressStore& nsas,
+ isc::cache::ResolverCache& cache,
+ const std::vector<std::pair<std::string, uint16_t> >& upstream,
+ const std::vector<std::pair<std::string, uint16_t> >& upstream_root,
+ int query_timeout, int client_timeout, int lookup_timeout,
+ unsigned retries) :
+ dns_service_(dns_service),
+ nsas_(nsas), cache_(cache),
+ upstream_(new AddressVector(upstream)),
+ upstream_root_(new AddressVector(upstream_root)),
+ test_server_("", 0),
+ query_timeout_(query_timeout), client_timeout_(client_timeout),
+ lookup_timeout_(lookup_timeout), retries_(retries)
+{
+}
+
+// Set the test server - only used for unit testing.
+
+void
+RecursiveQuery::setTestServer(const std::string& address, uint16_t port) {
+ dlog("Setting test server to " + address + "(" +
+ boost::lexical_cast<std::string>(port) + ")");
+ test_server_.first = address;
+ test_server_.second = port;
+}
+
+
+namespace {
+
+typedef std::pair<std::string, uint16_t> addr_t;
+
+/*
+ * This is a query in progress. When a new query is made, this one holds
+ * the context information about it, like how many times we are allowed
+ * to retry on failure, what to do when we succeed, etc.
+ *
+ * Used by RecursiveQuery::sendQuery.
+ */
+class RunningQuery : public IOFetch::Callback {
+
+class ResolverNSASCallback : public isc::nsas::AddressRequestCallback {
+public:
+ ResolverNSASCallback(RunningQuery* rq) : rq_(rq) {}
+
+ void success(const isc::nsas::NameserverAddress& address) {
+ dlog("Found a nameserver, sending query to " + address.getAddress().toText());
+ rq_->nsasCallbackCalled();
+ rq_->sendTo(address);
+ }
+
+ void unreachable() {
+ dlog("Nameservers unreachable");
+ // Drop query or send servfail?
+ rq_->nsasCallbackCalled();
+ rq_->makeSERVFAIL();
+ rq_->callCallback(true);
+ rq_->stop();
+ }
+
+private:
+ RunningQuery* rq_;
+};
+
+
+private:
+ // The io service to handle async calls
+ IOService& io_;
+
+ // Info for (re)sending the query (the question and destination)
+ Question question_;
+
+ // This is where we build and store our final answer
+ MessagePtr answer_message_;
+
+ // currently we use upstream as the current list of NS records
+ // we should differentiate between forwarding and resolving
+ boost::shared_ptr<AddressVector> upstream_;
+
+ // Test server - only used for testing. This takes precedence over all
+ // other servers if the port is non-zero.
+ std::pair<std::string, uint16_t> test_server_;
+
+ // Buffer to store the intermediate results.
+ OutputBufferPtr buffer_;
+
+ // The callback will be called when we have either decided we
+ // are done, or when we give up
+ isc::resolve::ResolverInterface::CallbackPtr resolvercallback_;
+
+ // Protocol used for the last query. This is set to IOFetch::UDP when a
+ // new upstream query is initiated, and changed to IOFetch::TCP if a
+ // packet is returned with the TC bit set. It is stored here to detect the
+ // case of a TCP packet being returned with the TC bit set.
+ IOFetch::Protocol protocol_;
+
+ // To prevent both unreasonably long cname chains and cname loops,
+ // we simply keep a counter of the number of CNAMEs we have
+ // followed so far (and error if it exceeds RESOLVER_MAX_CNAME_CHAIN
+ // from lib/resolve/response_classifier.h)
+ unsigned cname_count_;
+
+ /*
+ * TODO Do something more clever with timeouts. In the long term, some
+ * computation of average RTT, increase with each retry, etc.
+ */
+ // Timeout information for outgoing queries
+ int query_timeout_;
+ unsigned retries_;
+
+ // normal query state
+
+ // Update the question that will be sent to the server
+ void setQuestion(const Question& new_question) {
+ question_ = new_question;
+ }
+
+ // TODO: replace by our wrapper
+ asio::deadline_timer client_timer;
+ asio::deadline_timer lookup_timer;
+
+ // If we timed out ourselves (lookup timeout), stop issuing queries
+ bool done_;
+
+ // If we have a client timeout, we call back with a failure message,
+ // but we do not stop yet. We use this variable to make sure we
+ // don't call back a second time later
+ bool callback_called_;
+
+ // Reference to our NSAS
+ isc::nsas::NameserverAddressStore& nsas_;
+
+ // Reference to our cache
+ isc::cache::ResolverCache& cache_;
+
+ // the 'current' zone we are in (i.e.) we start out at the root,
+ // and for each delegation this gets updated with the zone the
+ // delegation points to.
+ // TODO: make this a Name (it is a string right now because most
+ // of the call we use it in take a string, we need update those
+ // too).
+ std::string cur_zone_;
+
+ // This is the handler we pass on to the NSAS; it is called when
+ // the NSAS has an address for us to query
+ boost::shared_ptr<ResolverNSASCallback> nsas_callback_;
+
+ // this is set to true if we have asked the nsas to give us
+ // an address and we are waiting for it to call us back.
+ // We use is to cancel the outstanding callback in case we
+ // have a lookup timeout and decide to give up
+ bool nsas_callback_out_;
+
+ // This is the nameserver we have an outstanding query to.
+ // It is used to update the RTT once the query returns
+ isc::nsas::NameserverAddress current_ns_address;
+
+ // The moment in time we sent a query to the nameserver above.
+ struct timeval current_ns_qsent_time;
+
+ // RunningQuery deletes itself when it is done. In order for us
+ // to do this safely, we must make sure that there are no events
+ // that might call back to it. There are two types of events in
+ // this sense; the timers we set ourselves (lookup and client),
+ // and outstanding queries to nameservers. When each of these is
+ // started, we increase this value. When they fire, it is decreased
+ // again. We cannot delete ourselves until this value is back to 0.
+ //
+ // Note that the NSAS callback is *not* seen as an outstanding
+ // event; we can cancel the NSAS callback safely.
+ size_t outstanding_events_;
+
+ // perform a single lookup; first we check the cache to see
+ // if we have a response for our query stored already. if
+ // so, call handlerecursiveresponse(), if not, we call send()
+ void doLookup() {
+ dlog("doLookup: try cache");
+ Message cached_message(Message::RENDER);
+ isc::resolve::initResponseMessage(question_, cached_message);
+ if (cache_.lookup(question_.getName(), question_.getType(),
+ question_.getClass(), cached_message)) {
+ dlog("Message found in cache, continuing with that");
+ // Should these be set by the cache too?
+ cached_message.setOpcode(Opcode::QUERY());
+ cached_message.setRcode(Rcode::NOERROR());
+ cached_message.setHeaderFlag(Message::HEADERFLAG_QR);
+ if (handleRecursiveAnswer(cached_message)) {
+ callCallback(true);
+ stop();
+ }
+ } else {
+ cur_zone_ = ".";
+ send();
+ }
+
+ }
+
+ // Send the current question to the given nameserver address
+ void sendTo(const isc::nsas::NameserverAddress& address) {
+ // We need to keep track of the Address, so that we can update
+ // the RTT
+ current_ns_address = address;
+ gettimeofday(¤t_ns_qsent_time, NULL);
+ ++outstanding_events_;
+ IOFetch query(protocol_, io_, question_,
+ current_ns_address.getAddress(),
+ 53, buffer_, this,
+ query_timeout_);
+ io_.get_io_service().post(query);
+ }
+
+ // 'general' send; if we are in forwarder mode, send a query to
+ // a random nameserver in our forwarders list. If we are in
+ // recursive mode, ask the NSAS to give us an address.
+ void send(IOFetch::Protocol protocol = IOFetch::UDP) {
+ // If are in forwarder mode, send it to a random
+ // forwarder. If not, ask the NSAS for an address
+ const int uc = upstream_->size();
+ protocol_ = protocol; // Store protocol being used for this
+ if (test_server_.second != 0) {
+ dlog("Sending upstream query (" + question_.toText() +
+ ") to test server at " + test_server_.first);
+ ++outstanding_events_;
+ IOFetch query(protocol, io_, question_,
+ test_server_.first,
+ test_server_.second, buffer_, this,
+ query_timeout_);
+ io_.get_io_service().post(query);
+ } else if (uc > 0) {
+ // TODO: use boost, or rand()-utility function we provide
+ int serverIndex = rand() % uc;
+ dlog("Sending upstream query (" + question_.toText() +
+ ") to " + upstream_->at(serverIndex).first);
+ ++outstanding_events_;
+ IOFetch query(protocol, io_, question_,
+ upstream_->at(serverIndex).first,
+ upstream_->at(serverIndex).second, buffer_, this,
+ query_timeout_);
+ io_.get_io_service().post(query);
+ } else {
+ // Ask the NSAS for an address for the current zone,
+ // the callback will call the actual sendTo()
+ dlog("Look up nameserver for " + cur_zone_ + " in NSAS");
+ // Can we have multiple calls to nsas_out? Let's assume not
+ // for now
+ assert(!nsas_callback_out_);
+ nsas_callback_out_ = true;
+ nsas_.lookup(cur_zone_, question_.getClass(), nsas_callback_);
+ }
+ }
+
+ // Called by our NSAS callback handler so we know we do not have
+ // an outstanding NSAS call anymore.
+ void nsasCallbackCalled() {
+ nsas_callback_out_ = false;
+ }
+
+ // This function is called by operator() and lookup();
+ // We have an answer either from a nameserver or the cache, and
+ // we do not know yet if this is a final answer we can send back or
+ // that more recursive processing needs to be done.
+ // Depending on the content, we go on recursing or return
+ //
+ // This method also updates the cache, depending on the content
+ // of the message
+ //
+ // returns true if we are done (either we have an answer or an
+ // error message)
+ // returns false if we are not done
+ bool handleRecursiveAnswer(const Message& incoming) {
+ dlog("Handle response");
+ // In case we get a CNAME, we store the target
+ // here (classify() will set it when it walks through
+ // the cname chain to verify it).
+ Name cname_target(question_.getName());
+
+ isc::resolve::ResponseClassifier::Category category =
+ isc::resolve::ResponseClassifier::classify(
+ question_, incoming, cname_target, cname_count_);
+
+ bool found_ns_address = false;
+
+ switch (category) {
+ case isc::resolve::ResponseClassifier::ANSWER:
+ case isc::resolve::ResponseClassifier::ANSWERCNAME:
+ // Done. copy and return.
+ dlog("Response is an answer");
+ isc::resolve::copyResponseMessage(incoming, answer_message_);
+ cache_.update(*answer_message_);
+ return true;
+ break;
+ case isc::resolve::ResponseClassifier::CNAME:
+ dlog("Response is CNAME!");
+ // (unfinished) CNAME. We set our question_ to the CNAME
+ // target, then start over at the beginning (for now, that
+ // is, we reset our 'current servers' to the root servers).
+ if (cname_count_ >= RESOLVER_MAX_CNAME_CHAIN) {
+ // just give up
+ dlog("CNAME chain too long");
+ makeSERVFAIL();
+ return true;
+ }
+
+ answer_message_->appendSection(Message::SECTION_ANSWER,
+ incoming);
+
+ question_ = Question(cname_target, question_.getClass(),
+ question_.getType());
+
+ dlog("Following CNAME chain to " + question_.toText());
+ doLookup();
+ return false;
+ break;
+ case isc::resolve::ResponseClassifier::NXDOMAIN:
+ case isc::resolve::ResponseClassifier::NXRRSET:
+ dlog("Response is NXDOMAIN or NXRRSET");
+ // NXDOMAIN, just copy and return.
+ dlog(incoming.toText());
+ isc::resolve::copyResponseMessage(incoming, answer_message_);
+ // no negcache yet
+ //cache_.update(*answer_message_);
+ return true;
+ break;
+ case isc::resolve::ResponseClassifier::REFERRAL:
+ dlog("Response is referral");
+ cache_.update(incoming);
+ // Referral. For now we just take the first glue address
+ // we find and continue with that
+
+ // auth section should have at least one RRset
+ // and one of them should be an NS (otherwise
+ // classifier should have error'd)
+ // TODO: should we check if it really is subzone?
+ for (RRsetIterator rrsi = incoming.beginSection(Message::SECTION_AUTHORITY);
+ rrsi != incoming.endSection(Message::SECTION_AUTHORITY) && !found_ns_address;
+ ++rrsi) {
+ ConstRRsetPtr rrs = *rrsi;
+ if (rrs->getType() == RRType::NS()) {
+ // TODO: make cur_zone_ a Name instead of a string
+ // (this requires a few API changes in related
+ // libraries, so as not to need many conversions)
+ cur_zone_ = rrs->getName().toText();
+ dlog("Referred to zone " + cur_zone_);
+ found_ns_address = true;
+ break;
+ }
+ }
+
+ if (found_ns_address) {
+ // next resolver round
+ // we do NOT use doLookup() here, but send() (i.e. we
+ // skip the cache), since if we had the final answer
+ // instead of a delegation cached, we would have been
+ // there by now.
+ send();
+ return false;
+ } else {
+ dlog("No NS RRset in referral?");
+ // TODO this will result in answering with the delegation. oh well
+ isc::resolve::copyResponseMessage(incoming, answer_message_);
+ return true;
+ }
+ break;
+ case isc::resolve::ResponseClassifier::TRUNCATED:
+ // Truncated packet. If the protocol we used for the last one is
+ // UDP, re-query using TCP. Otherwise regard it as an error.
+ if (protocol_ == IOFetch::UDP) {
+ dlog("Response truncated, re-querying over TCP");
+ send(IOFetch::TCP);
+ return false;
+ }
+ // Was a TCP query so we have received a packet over TCP with the TC
+ // bit set: drop through to common error processing.
+ // TODO: Can we use what we have received instead of discarding it?
+
+ case isc::resolve::ResponseClassifier::EMPTY:
+ case isc::resolve::ResponseClassifier::EXTRADATA:
+ case isc::resolve::ResponseClassifier::INVNAMCLASS:
+ case isc::resolve::ResponseClassifier::INVTYPE:
+ case isc::resolve::ResponseClassifier::MISMATQUEST:
+ case isc::resolve::ResponseClassifier::MULTICLASS:
+ case isc::resolve::ResponseClassifier::NOTONEQUEST:
+ case isc::resolve::ResponseClassifier::NOTRESPONSE:
+ case isc::resolve::ResponseClassifier::NOTSINGLE:
+ case isc::resolve::ResponseClassifier::OPCODE:
+ case isc::resolve::ResponseClassifier::RCODE:
+ // Should we try a different server rather than SERVFAIL?
+ makeSERVFAIL();
+ return true;
+ break;
+ }
+
+ // Since we do not have a default in the switch above,
+ // the compiler should have errored on any missing case
+ // statements.
+ assert(false);
+ return true;
+ }
+
+public:
+ RunningQuery(IOService& io,
+ const Question& question,
+ MessagePtr answer_message,
+ boost::shared_ptr<AddressVector> upstream,
+ std::pair<std::string, uint16_t>& test_server,
+ OutputBufferPtr buffer,
+ isc::resolve::ResolverInterface::CallbackPtr cb,
+ int query_timeout, int client_timeout, int lookup_timeout,
+ unsigned retries,
+ isc::nsas::NameserverAddressStore& nsas,
+ isc::cache::ResolverCache& cache) :
+ io_(io),
+ question_(question),
+ answer_message_(answer_message),
+ upstream_(upstream),
+ test_server_(test_server),
+ buffer_(buffer),
+ resolvercallback_(cb),
+ protocol_(IOFetch::UDP),
+ cname_count_(0),
+ query_timeout_(query_timeout),
+ retries_(retries),
+ client_timer(io.get_io_service()),
+ lookup_timer(io.get_io_service()),
+ done_(false),
+ callback_called_(false),
+ nsas_(nsas),
+ cache_(cache),
+ nsas_callback_(new ResolverNSASCallback(this)),
+ nsas_callback_out_(false),
+ outstanding_events_(0)
+ {
+ // Setup the timer to stop trying (lookup_timeout)
+ if (lookup_timeout >= 0) {
+ lookup_timer.expires_from_now(
+ boost::posix_time::milliseconds(lookup_timeout));
+ ++outstanding_events_;
+ lookup_timer.async_wait(boost::bind(&RunningQuery::lookupTimeout, this));
+ }
+
+ // Setup the timer to send an answer (client_timeout)
+ if (client_timeout >= 0) {
+ client_timer.expires_from_now(
+ boost::posix_time::milliseconds(client_timeout));
+ ++outstanding_events_;
+ client_timer.async_wait(boost::bind(&RunningQuery::clientTimeout, this));
+ }
+
+ doLookup();
+ }
+
+ // called if we have a lookup timeout; if our callback has
+ // not been called, call it now. Then stop.
+ void lookupTimeout() {
+ if (!callback_called_) {
+ makeSERVFAIL();
+ callCallback(true);
+ }
+ assert(outstanding_events_ > 0);
+ --outstanding_events_;
+ stop();
+ }
+
+ // called if we have a client timeout; if our callback has
+ // not been called, call it now. But do not stop.
+ void clientTimeout() {
+ if (!callback_called_) {
+ makeSERVFAIL();
+ callCallback(true);
+ }
+ assert(outstanding_events_ > 0);
+ --outstanding_events_;
+ if (outstanding_events_ == 0) {
+ stop();
+ }
+ }
+
+ // If the callback has not been called yet, call it now
+ // If success is true, we call 'success' with our answer_message
+ // If it is false, we call failure()
+ void callCallback(bool success) {
+ if (!callback_called_) {
+ callback_called_ = true;
+
+ // There are two types of messages we could store in the
+ // cache;
+ // 1. answers to our fetches from authoritative servers,
+ // exactly as we receive them, and
+ // 2. answers to queries we received from clients, which
+ // have received additional processing (following CNAME
+ // chains, for instance)
+ //
+ // Doing only the first would mean we would have to re-do
+ // processing when we get data from our cache, and doing
+ // only the second would miss out on the side-effect of
+ // having nameserver data in our cache.
+ //
+ // So right now we do both. Since the cache (currently)
+ // stores Messages on their question section only, this
+ // does mean that we overwrite the messages we stored in
+ // the previous iteration if we are following a delegation.
+ if (success) {
+ resolvercallback_->success(answer_message_);
+ } else {
+ resolvercallback_->failure();
+ }
+ }
+ }
+
+ // We are done. If there are no more outstanding events, we delete
+ // ourselves. If there are any, we do not.
+ void stop() {
+ done_ = true;
+ if (nsas_callback_out_) {
+ nsas_.cancel(cur_zone_, question_.getClass(), nsas_callback_);
+ nsas_callback_out_ = false;
+ }
+ client_timer.cancel();
+ lookup_timer.cancel();
+ if (outstanding_events_ > 0) {
+ return;
+ } else {
+ delete this;
+ }
+ }
+
+ // This function is used as callback from DNSQuery.
+ virtual void operator()(IOFetch::Result result) {
+ // XXX is this the place for TCP retry?
+ assert(outstanding_events_ > 0);
+ --outstanding_events_;
+
+ if (!done_ && result != IOFetch::TIME_OUT) {
+ // we got an answer
+
+ // Update the NSAS with the time it took
+ struct timeval cur_time;
+ gettimeofday(&cur_time, NULL);
+ uint32_t rtt;
+ if (cur_time.tv_sec >= current_ns_qsent_time.tv_sec ||
+ cur_time.tv_usec > current_ns_qsent_time.tv_usec) {
+ rtt = 1000 * (cur_time.tv_sec - current_ns_qsent_time.tv_sec);
+ rtt += (cur_time.tv_usec - current_ns_qsent_time.tv_usec) / 1000;
+ } else {
+ rtt = 1;
+ }
+
+ dlog("RTT: " + boost::lexical_cast<std::string>(rtt));
+ current_ns_address.updateRTT(rtt);
+
+ Message incoming(Message::PARSE);
+ InputBuffer ibuf(buffer_->getData(), buffer_->getLength());
+ incoming.fromWire(ibuf);
+
+ buffer_->clear();
+ if (recursive_mode() &&
+ incoming.getRcode() == Rcode::NOERROR()) {
+ done_ = handleRecursiveAnswer(incoming);
+ } else {
+ isc::resolve::copyResponseMessage(incoming, answer_message_);
+ done_ = true;
+ }
+
+ if (done_) {
+ callCallback(true);
+ stop();
+ }
+ } else if (!done_ && retries_--) {
+ // Query timed out, but we have some retries, so send again
+ dlog("Timeout for " + question_.toText() + " to " + current_ns_address.getAddress().toText() + ", resending query");
+ if (recursive_mode()) {
+ current_ns_address.updateRTT(isc::nsas::AddressEntry::UNREACHABLE);
+ }
+ send();
+ } else {
+ // We are either already done, or out of retries
+ if (recursive_mode() && result == IOFetch::TIME_OUT) {
+ dlog("Timeout for " + question_.toText() + " to " + current_ns_address.getAddress().toText() + ", giving up");
+ current_ns_address.updateRTT(isc::nsas::AddressEntry::UNREACHABLE);
+ }
+ if (!callback_called_) {
+ makeSERVFAIL();
+ callCallback(true);
+ }
+ stop();
+ }
+ }
+
+ // Clear the answer parts of answer_message, and set the rcode
+ // to servfail
+ void makeSERVFAIL() {
+ isc::resolve::makeErrorMessage(answer_message_, Rcode::SERVFAIL());
+ }
+
+ // Returns true if we are in 'recursive' mode
+ // Returns false if we are in 'forwarding' mode
+ // (i.e. if we have anything in upstream_)
+ bool recursive_mode() const {
+ return upstream_->empty();
+ }
+};
+
+}
+
+void
+RecursiveQuery::resolve(const QuestionPtr& question,
+ const isc::resolve::ResolverInterface::CallbackPtr callback)
+{
+ IOService& io = dns_service_.getIOService();
+
+ MessagePtr answer_message(new Message(Message::RENDER));
+ isc::resolve::initResponseMessage(*question, *answer_message);
+
+ OutputBufferPtr buffer(new OutputBuffer(0));
+
+ dlog("Asked to resolve: " + question->toText());
+
+ dlog("Try out cache first (direct call to resolve)");
+ // First try to see if we have something cached in the messagecache
+ if (cache_.lookup(question->getName(), question->getType(),
+ question->getClass(), *answer_message) &&
+ answer_message->getRRCount(Message::SECTION_ANSWER) > 0) {
+ dlog("Message found in cache, returning that");
+ // TODO: err, should cache set rcode as well?
+ answer_message->setRcode(Rcode::NOERROR());
+ callback->success(answer_message);
+ } else {
+ // Perhaps we only have the one RRset?
+ // TODO: can we do this? should we check for specific types only?
+ RRsetPtr cached_rrset = cache_.lookup(question->getName(),
+ question->getType(),
+ question->getClass());
+ if (cached_rrset) {
+ dlog("Found single RRset in cache");
+ answer_message->addRRset(Message::SECTION_ANSWER,
+ cached_rrset);
+ answer_message->setRcode(Rcode::NOERROR());
+ callback->success(answer_message);
+ } else {
+ dlog("Message not found in cache, starting recursive query");
+ // It will delete itself when it is done
+ new RunningQuery(io, *question, answer_message, upstream_,
+ test_server_, buffer, callback,
+ query_timeout_, client_timeout_,
+ lookup_timeout_, retries_, nsas_, cache_);
+ }
+ }
+}
+
+void
+RecursiveQuery::resolve(const Question& question,
+ MessagePtr answer_message,
+ OutputBufferPtr buffer,
+ DNSServer* server)
+{
+ // XXX: eventually we will need to be able to determine whether
+ // the message should be sent via TCP or UDP, or sent initially via
+ // UDP and then fall back to TCP on failure, but for the moment
+ // we're only going to handle UDP.
+ IOService& io = dns_service_.getIOService();
+
+ isc::resolve::ResolverInterface::CallbackPtr crs(
+ new isc::resolve::ResolverCallbackServer(server));
+
+ // TODO: general 'prepareinitialanswer'
+ answer_message->setOpcode(isc::dns::Opcode::QUERY());
+ answer_message->addQuestion(question);
+
+ dlog("Asked to resolve: " + question.toText());
+
+ // First try to see if we have something cached in the messagecache
+ dlog("Try out cache first (started by incoming event)");
+ if (cache_.lookup(question.getName(), question.getType(),
+ question.getClass(), *answer_message) &&
+ answer_message->getRRCount(Message::SECTION_ANSWER) > 0) {
+ dlog("Message found in cache, returning that");
+ // TODO: err, should cache set rcode as well?
+ answer_message->setRcode(Rcode::NOERROR());
+ crs->success(answer_message);
+ } else {
+ // Perhaps we only have the one RRset?
+ // TODO: can we do this? should we check for specific types only?
+ RRsetPtr cached_rrset = cache_.lookup(question.getName(),
+ question.getType(),
+ question.getClass());
+ if (cached_rrset) {
+ dlog("Found single RRset in cache");
+ answer_message->addRRset(Message::SECTION_ANSWER,
+ cached_rrset);
+ answer_message->setRcode(Rcode::NOERROR());
+ crs->success(answer_message);
+ } else {
+ dlog("Message not found in cache, starting recursive query");
+ // It will delete itself when it is done
+ new RunningQuery(io, question, answer_message, upstream_,
+ test_server_, buffer, crs, query_timeout_,
+ client_timeout_, lookup_timeout_, retries_,
+ nsas_, cache_);
+ }
+ }
+}
+
+
+
+} // namespace asiolink
diff --git a/src/lib/resolve/recursive_query.h b/src/lib/resolve/recursive_query.h
new file mode 100644
index 0000000..1180d55
--- /dev/null
+++ b/src/lib/resolve/recursive_query.h
@@ -0,0 +1,133 @@
+// 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.
+
+#ifndef __RECURSIVE_QUERY_H
+#define __RECURSIVE_QUERY_H 1
+
+#include <asiolink/dns_service.h>
+#include <asiolink/dns_server.h>
+#include <dns/buffer.h>
+#include <nsas/nameserver_address_store.h>
+#include <cache/resolver_cache.h>
+
+namespace asiolink {
+/// \brief The \c RecursiveQuery class provides a layer of abstraction around
+/// the ASIO code that carries out an upstream query.
+///
+/// This design is very preliminary; currently it is only capable of
+/// handling simple forward requests to a single resolver.
+class RecursiveQuery {
+ ///
+ /// \name Constructors
+ ///
+ //@{
+public:
+ /// \brief Constructor
+ ///
+ /// This is currently the only way to construct \c RecursiveQuery
+ /// object. If the addresses of the forward nameservers is specified,
+ /// and every upstream query will be sent to one random address, and
+ /// the result sent back directly. If not, it will do full resolving.
+ ///
+ /// \param dns_service The DNS Service to perform the recursive
+ /// query on.
+ /// \param upstream Addresses and ports of the upstream servers
+ /// to forward queries to.
+ /// \param upstream_root Addresses and ports of the root servers
+ /// to use when resolving.
+ /// \param query_timeout Timeout value for queries we sent, in ms
+ /// \param client_timeout Timeout value for when we send back an
+ /// error, in ms
+ /// \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,
+ isc::nsas::NameserverAddressStore& nsas,
+ isc::cache::ResolverCache& cache,
+ const std::vector<std::pair<std::string, uint16_t> >&
+ upstream,
+ const std::vector<std::pair<std::string, uint16_t> >&
+ upstream_root,
+ int query_timeout = 2000,
+ int client_timeout = 4000,
+ int lookup_timeout = 30000,
+ unsigned retries = 3);
+ //@}
+
+ /// \brief Initiate resolving
+ ///
+ /// When sendQuery() is called, a (set of) message(s) is sent
+ /// asynchronously. If upstream servers are set, one is chosen
+ /// and the response (if any) from that server will be returned.
+ ///
+ /// If not upstream is set, a root server is chosen from the
+ /// root_servers, and the RunningQuery shall do a full resolve
+ /// (i.e. if the answer is a delegation, it will be followed, etc.)
+ /// until there is an answer or an error.
+ ///
+ /// When there is a response or an error and we give up, the given
+ /// CallbackPtr object shall be called (with either success() or
+ /// failure(). See ResolverInterface::Callback for more information.
+ ///
+ /// \param question The question being answered <qname/qclass/qtype>
+ /// \param callback Callback object. See
+ /// \c ResolverInterface::Callback for more information
+ void resolve(const isc::dns::QuestionPtr& question,
+ const isc::resolve::ResolverInterface::CallbackPtr callback);
+
+
+ /// \brief Initiates resolving for the given question.
+ ///
+ /// This actually calls the previous sendQuery() with a default
+ /// callback object, which calls resume() on the given DNSServer
+ /// object.
+ ///
+ /// \param question The question being answered <qname/qclass/qtype>
+ /// \param answer_message An output Message into which the final response will be copied
+ /// \param buffer An output buffer into which the intermediate responses will be copied
+ /// \param server A pointer to the \c DNSServer object handling the client
+ void resolve(const isc::dns::Question& question,
+ isc::dns::MessagePtr answer_message,
+ isc::dns::OutputBufferPtr buffer,
+ DNSServer* server);
+
+ /// \brief Set Test Server
+ ///
+ /// This method is *only* for unit testing the class. If set, it enables
+ /// recursive behaviour but, regardless of responses received, sends every
+ /// query to the test server.
+ ///
+ /// The test server is enabled by setting a non-zero port number.
+ ///
+ /// \param address IP address of the test server.
+ /// \param port Port number of the test server
+ void setTestServer(const std::string& address, uint16_t port);
+
+private:
+ DNSService& dns_service_;
+ isc::nsas::NameserverAddressStore& nsas_;
+ isc::cache::ResolverCache& cache_;
+ boost::shared_ptr<std::vector<std::pair<std::string, uint16_t> > >
+ upstream_;
+ boost::shared_ptr<std::vector<std::pair<std::string, uint16_t> > >
+ upstream_root_;
+ std::pair<std::string, uint16_t> test_server_;
+ int query_timeout_;
+ int client_timeout_;
+ int lookup_timeout_;
+ unsigned retries_;
+};
+
+} // namespace asiolink
+#endif // __RECURSIVE_QUERY_H
diff --git a/src/lib/resolve/response_classifier.cc b/src/lib/resolve/response_classifier.cc
index 45e9cbc..02808e4 100644
--- a/src/lib/resolve/response_classifier.cc
+++ b/src/lib/resolve/response_classifier.cc
@@ -114,13 +114,17 @@ ResponseClassifier::Category ResponseClassifier::classify(
);
// If there is nothing in the answer section, it is a referral - unless
- // there is nothing in the authority section
+ // there is no NS in the authority section
if (answer.empty()) {
if (authority.empty()) {
return (EMPTY);
- } else {
- return (REFERRAL);
}
+ for (int i = 0; i < authority.size(); ++i) {
+ if (authority[i]->getType() == RRType::NS()) {
+ return (REFERRAL);
+ }
+ }
+ return (NXRRSET);
}
// Look at two cases - one RRset in the answer and multiple RRsets in
diff --git a/src/lib/resolve/response_classifier.h b/src/lib/resolve/response_classifier.h
index bee0628..3821560 100644
--- a/src/lib/resolve/response_classifier.h
+++ b/src/lib/resolve/response_classifier.h
@@ -53,6 +53,7 @@ public:
ANSWERCNAME, ///< Response was a CNAME chain ending in an answer
CNAME, ///< Response was a CNAME
NXDOMAIN, ///< Response was an NXDOMAIN
+ NXRRSET, ///< Response was name exists, but type does not
REFERRAL, ///< Response contains a referral
// Codes indicating that a message is invalid. Note that the error()
diff --git a/src/lib/resolve/tests/Makefile.am b/src/lib/resolve/tests/Makefile.am
index e669384..da28f78 100644
--- a/src/lib/resolve/tests/Makefile.am
+++ b/src/lib/resolve/tests/Makefile.am
@@ -14,12 +14,19 @@ TESTS += run_unittests
run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
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 += resolve_unittest.cc
run_unittests_SOURCES += resolver_callback_unittest.cc
run_unittests_SOURCES += response_classifier_unittest.cc
+run_unittests_SOURCES += recursive_query_unittest.cc
+run_unittests_SOURCES += recursive_query_unittest_2.cc
run_unittests_LDADD = $(GTEST_LDADD)
run_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
+run_unittests_LDADD += $(top_builddir)/src/lib/nsas/libnsas.la
+run_unittests_LDADD += $(top_builddir)/src/lib/cache/libcache.la
+run_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libasiolink.la
run_unittests_LDADD += $(top_builddir)/src/lib/resolve/libresolve.la
run_unittests_LDADD += $(top_builddir)/src/lib/dns/libdns++.la
diff --git a/src/lib/resolve/tests/recursive_query_unittest.cc b/src/lib/resolve/tests/recursive_query_unittest.cc
new file mode 100644
index 0000000..ab1ffa3
--- /dev/null
+++ b/src/lib/resolve/tests/recursive_query_unittest.cc
@@ -0,0 +1,861 @@
+// 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 <sys/socket.h>
+#include <sys/time.h>
+
+#include <string.h>
+
+#include <boost/lexical_cast.hpp>
+#include <boost/bind.hpp>
+#include <boost/date_time/posix_time/posix_time_types.hpp>
+
+#include <gtest/gtest.h>
+
+#include <exceptions/exceptions.h>
+
+#include <dns/tests/unittest_util.h>
+#include <dns/rcode.h>
+
+#include <dns/buffer.h>
+#include <dns/message.h>
+
+#include <nsas/nameserver_address_store.h>
+#include <cache/resolver_cache.h>
+
+// IMPORTANT: We shouldn't directly use ASIO definitions in this test.
+// In particular, we must not include asio.hpp in this file.
+// The asiolink module is primarily intended to be a wrapper that hide the
+// details of the underlying implementations. We need to test the wrapper
+// level behaviors. In addition, some compilers reject to compile this file
+// if we include asio.hpp unless we specify a special compiler option.
+// If we need to test something at the level of underlying ASIO and need
+// their definition, that test should go to asiolink/internal/tests.
+#include <resolve/recursive_query.h>
+#include <asiolink/io_socket.h>
+#include <asiolink/io_service.h>
+#include <asiolink/io_message.h>
+#include <asiolink/io_error.h>
+#include <asiolink/dns_lookup.h>
+#include <asiolink/simple_callback.h>
+
+using isc::UnitTestUtil;
+using namespace std;
+using namespace asiolink;
+using namespace isc::dns;
+
+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";
+// This data is intended to be valid as a DNS/TCP-like message: the first
+// two octets encode the length of the rest of the data. This is crucial
+// for the tests below.
+const uint8_t test_data[] = {0, 4, 1, 2, 3, 4};
+
+// This function returns an addrinfo structure for use by tests, using
+// different addresses and ports depending on whether we're testing
+// IPv4 or v6, TCP or UDP, and client or server operation.
+struct addrinfo*
+resolveAddress(const int family, const int protocol, const bool client) {
+ const char* const addr = (family == AF_INET6) ?
+ TEST_IPV6_ADDR : TEST_IPV4_ADDR;
+ const char* const port = client ? TEST_CLIENT_PORT : TEST_SERVER_PORT;
+
+ struct addrinfo hints;
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = family;
+ hints.ai_socktype = (protocol == IPPROTO_UDP) ? SOCK_DGRAM : SOCK_STREAM;
+ hints.ai_protocol = protocol;
+ hints.ai_flags = AI_NUMERICSERV;
+
+ struct addrinfo* res;
+ const int error = getaddrinfo(addr, port, &hints, &res);
+ if (error != 0) {
+ isc_throw(IOError, "getaddrinfo failed: " << gai_strerror(error));
+ }
+
+ return (res);
+}
+
+// This fixture is a framework for various types of network operations
+// using the ASIO interfaces. Each test case creates an IOService object,
+// opens a local "client" socket for testing, sends data via the local socket
+// to the service that would run in the IOService object.
+// A mock callback function (an ASIOCallBack object) is registered with the
+// IOService object, so the test code should be able to examine the data
+// received on the server side. It then checks the received data matches
+// expected parameters.
+// If initialization parameters of the IOService should be modified, the test
+// case can do it using the setDNSService() method.
+// Note: the set of tests in RecursiveQueryTest use actual network services and may
+// involve undesirable side effects such as blocking.
+class RecursiveQueryTest : public ::testing::Test {
+protected:
+ RecursiveQueryTest();
+ ~RecursiveQueryTest() {
+ if (res_ != NULL) {
+ freeaddrinfo(res_);
+ }
+ if (sock_ != -1) {
+ close(sock_);
+ }
+ delete dns_service_;
+ delete callback_;
+ delete io_service_;
+ }
+
+ // Send a test UDP packet to a mock server
+ void sendUDP(const int family) {
+ res_ = resolveAddress(family, IPPROTO_UDP, false);
+
+ sock_ = socket(res_->ai_family, res_->ai_socktype, res_->ai_protocol);
+ if (sock_ < 0) {
+ isc_throw(IOError, "failed to open test socket");
+ }
+ const int cc = sendto(sock_, test_data, sizeof(test_data), 0,
+ res_->ai_addr, res_->ai_addrlen);
+ if (cc != sizeof(test_data)) {
+ isc_throw(IOError, "unexpected sendto result: " << cc);
+ }
+ io_service_->run();
+ }
+
+ // Send a test TCP packet to a mock server
+ void sendTCP(const int family) {
+ res_ = resolveAddress(family, IPPROTO_TCP, false);
+
+ sock_ = socket(res_->ai_family, res_->ai_socktype, res_->ai_protocol);
+ if (sock_ < 0) {
+ isc_throw(IOError, "failed to open test socket");
+ }
+ if (connect(sock_, res_->ai_addr, res_->ai_addrlen) < 0) {
+ isc_throw(IOError, "failed to connect to the test server");
+ }
+ const int cc = send(sock_, test_data, sizeof(test_data), 0);
+ if (cc != sizeof(test_data)) {
+ isc_throw(IOError, "unexpected send result: " << cc);
+ }
+ io_service_->run();
+ }
+
+ // Receive a UDP packet from a mock server; used for testing
+ // recursive lookup. The caller must place a RecursiveQuery
+ // on the IO Service queue before running this routine.
+ void recvUDP(const int family, void* buffer, size_t& size) {
+ res_ = resolveAddress(family, IPPROTO_UDP, true);
+
+ sock_ = socket(res_->ai_family, res_->ai_socktype, res_->ai_protocol);
+ if (sock_ < 0) {
+ isc_throw(IOError, "failed to open test socket");
+ }
+
+ if (bind(sock_, res_->ai_addr, res_->ai_addrlen) < 0) {
+ isc_throw(IOError, "bind failed: " << strerror(errno));
+ }
+
+ // The IO service queue should have a RecursiveQuery object scheduled
+ // to run at this point. This call will cause it to begin an
+ // async send, then return.
+ io_service_->run_one();
+
+ // ... and this one will block until the send has completed
+ io_service_->run_one();
+
+ // Now we attempt to recv() whatever was sent.
+ // XXX: there's no guarantee the receiving socket can immediately get
+ // the packet. Normally we can perform blocking recv to wait for it,
+ // but in theory it's even possible that the packet is lost.
+ // In order to prevent the test from hanging in such a worst case
+ // we add an ad hoc timeout.
+ const struct timeval timeo = { 10, 0 };
+ int recv_options = 0;
+ if (setsockopt(sock_, SOL_SOCKET, SO_RCVTIMEO, &timeo,
+ sizeof(timeo))) {
+ if (errno == ENOPROTOOPT) {
+ // Workaround for Solaris: it doesn't accept SO_RCVTIMEO
+ // with the error of ENOPROTOOPT. Since this is a workaround
+ // for rare error cases anyway, we simply switch to the
+ // "don't wait" mode. If we still find an error in recv()
+ // can happen often we'll consider a more complete solution.
+ recv_options = MSG_DONTWAIT;
+ } else {
+ isc_throw(IOError, "set RCVTIMEO failed: " << strerror(errno));
+ }
+ }
+ const int ret = recv(sock_, buffer, size, recv_options);
+ if (ret < 0) {
+ isc_throw(IOError, "recvfrom failed: " << strerror(errno));
+ }
+
+ // Pass the message size back via the size parameter
+ size = ret;
+ }
+
+
+ // Set up an IO Service queue using the specified address
+ void setDNSService(const char& address) {
+ delete dns_service_;
+ dns_service_ = NULL;
+ delete io_service_;
+ io_service_ = new IOService();
+ callback_ = new ASIOCallBack(this);
+ dns_service_ = new DNSService(*io_service_, *TEST_SERVER_PORT, address, callback_, NULL, NULL);
+ }
+
+ // Set up an IO Service queue using the "any" address, on IPv4 if
+ // 'use_ipv4' is true and on IPv6 if 'use_ipv6' is true.
+ void setDNSService(const bool use_ipv4, const bool use_ipv6) {
+ delete dns_service_;
+ dns_service_ = NULL;
+ delete io_service_;
+ io_service_ = new IOService();
+ callback_ = new ASIOCallBack(this);
+ dns_service_ = new DNSService(*io_service_, *TEST_SERVER_PORT, use_ipv4, use_ipv6, callback_,
+ NULL, NULL);
+ }
+
+ // Set up empty DNS Service
+ // Set up an IO Service queue without any addresses
+ void setDNSService() {
+ delete dns_service_;
+ dns_service_ = NULL;
+ delete io_service_;
+ io_service_ = new IOService();
+ callback_ = new ASIOCallBack(this);
+ dns_service_ = new DNSService(*io_service_, callback_, NULL, NULL);
+ }
+
+ // Run a simple server test, on either IPv4 or IPv6, and over either
+ // UDP or TCP. Calls the sendUDP() or sendTCP() methods, which will
+ // start the IO Service queue. The UDPServer or TCPServer that was
+ // created by setIOService() will receive the test packet and issue a
+ // callback, which enables us to check that the data it received
+ // matches what we sent.
+ void doTest(const int family, const int protocol) {
+ if (protocol == IPPROTO_UDP) {
+ sendUDP(family);
+ } else {
+ sendTCP(family);
+ }
+
+ // There doesn't seem to be an effective test for the validity of
+ // 'native'.
+ // One thing we are sure is it must be different from our local socket.
+ EXPECT_NE(sock_, callback_native_);
+ EXPECT_EQ(protocol, callback_protocol_);
+ EXPECT_EQ(family == AF_INET6 ? TEST_IPV6_ADDR : TEST_IPV4_ADDR,
+ callback_address_);
+
+ const uint8_t* expected_data =
+ protocol == IPPROTO_UDP ? test_data : test_data + 2;
+ const size_t expected_datasize =
+ protocol == IPPROTO_UDP ? sizeof(test_data) :
+ sizeof(test_data) - 2;
+ EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, &callback_data_[0],
+ callback_data_.size(),
+ expected_data, expected_datasize);
+ }
+
+protected:
+ // This is a nonfunctional mockup of a DNSServer object. Its purpose
+ // is to resume after a recursive query or other asynchronous call
+ // has completed.
+ class MockServer : public DNSServer {
+ public:
+ explicit MockServer(IOService& io_service,
+ SimpleCallback* checkin = NULL,
+ DNSLookup* lookup = NULL,
+ DNSAnswer* answer = NULL) :
+ io_(io_service),
+ done_(false),
+ message_(new Message(Message::PARSE)),
+ answer_message_(new Message(Message::RENDER)),
+ respbuf_(new OutputBuffer(0)),
+ checkin_(checkin), lookup_(lookup), answer_(answer)
+ {}
+
+ void operator()(asio::error_code ec = asio::error_code(),
+ size_t length = 0)
+ {}
+
+ void resume(const bool) {
+ // should never be called in our tests
+ }
+
+ DNSServer* clone() {
+ MockServer* s = new MockServer(*this);
+ return (s);
+ }
+
+ inline void asyncLookup() {
+ if (lookup_) {
+ (*lookup_)(*io_message_, message_, answer_message_,
+ respbuf_, this);
+ }
+ }
+
+ protected:
+ IOService& io_;
+ bool done_;
+
+ private:
+ // Currently unused; these will be used for testing
+ // asynchronous lookup calls via the asyncLookup() method
+ boost::shared_ptr<asiolink::IOMessage> io_message_;
+ isc::dns::MessagePtr message_;
+ isc::dns::MessagePtr answer_message_;
+ isc::dns::OutputBufferPtr respbuf_;
+
+ // Callback functions provided by the caller
+ const SimpleCallback* checkin_;
+ const DNSLookup* lookup_;
+ const DNSAnswer* answer_;
+ };
+
+ // This version of mock server just stops the io_service when it is resumed
+ class MockServerStop : public MockServer {
+ public:
+ explicit MockServerStop(IOService& io_service, bool* done) :
+ MockServer(io_service),
+ done_(done)
+ {}
+
+ void resume(const bool done) {
+ *done_ = done;
+ io_.stop();
+ }
+
+ DNSServer* clone() {
+ return (new MockServerStop(*this));
+ }
+ private:
+ bool* done_;
+ };
+
+ class MockResolver : public isc::resolve::ResolverInterface {
+ void resolve(const QuestionPtr& question,
+ const ResolverInterface::CallbackPtr& callback) {
+ }
+ };
+
+ // This version of mock server just stops the io_service when it is resumed
+ // the second time. (Used in the clientTimeout test, where resume
+ // is called initially with the error answer, and later when the
+ // lookup times out, it is called without an answer to send back)
+ class MockServerStop2 : public MockServer {
+ public:
+ explicit MockServerStop2(IOService& io_service,
+ bool* done1, bool* done2) :
+ MockServer(io_service),
+ done1_(done1),
+ done2_(done2),
+ stopped_once_(false)
+ {}
+
+ void resume(const bool done) {
+ if (stopped_once_) {
+ *done2_ = done;
+ io_.stop();
+ } else {
+ *done1_ = done;
+ stopped_once_ = true;
+ }
+ }
+
+ DNSServer* clone() {
+ return (new MockServerStop2(*this));
+ }
+ private:
+ bool* done1_;
+ bool* done2_;
+ bool stopped_once_;
+ };
+
+private:
+ class ASIOCallBack : public SimpleCallback {
+ public:
+ ASIOCallBack(RecursiveQueryTest* test_obj) : test_obj_(test_obj) {}
+ void operator()(const IOMessage& io_message) const {
+ test_obj_->callBack(io_message);
+ }
+ private:
+ RecursiveQueryTest* test_obj_;
+ };
+ void callBack(const IOMessage& io_message) {
+ callback_protocol_ = io_message.getSocket().getProtocol();
+ callback_native_ = io_message.getSocket().getNative();
+ callback_address_ =
+ io_message.getRemoteEndpoint().getAddress().toText();
+ callback_data_.assign(
+ static_cast<const uint8_t*>(io_message.getData()),
+ static_cast<const uint8_t*>(io_message.getData()) +
+ io_message.getDataSize());
+ io_service_->stop();
+ }
+protected:
+ // We use a pointer for io_service_, because for some tests we
+ // need to recreate a new one within one onstance of this class
+ IOService* io_service_;
+ DNSService* dns_service_;
+ isc::nsas::NameserverAddressStore* nsas_;
+ isc::cache::ResolverCache cache_;
+ ASIOCallBack* callback_;
+ int callback_protocol_;
+ int callback_native_;
+ string callback_address_;
+ vector<uint8_t> callback_data_;
+ int sock_;
+ struct addrinfo* res_;
+};
+
+RecursiveQueryTest::RecursiveQueryTest() :
+ dns_service_(NULL), callback_(NULL), callback_protocol_(0),
+ callback_native_(-1), sock_(-1), res_(NULL)
+{
+ io_service_ = new IOService();
+ setDNSService(true, true);
+ boost::shared_ptr<MockResolver>mock_resolver(new MockResolver());
+ nsas_ = new isc::nsas::NameserverAddressStore(mock_resolver);
+}
+
+TEST_F(RecursiveQueryTest, v6UDPSend) {
+ doTest(AF_INET6, IPPROTO_UDP);
+}
+
+TEST_F(RecursiveQueryTest, v6TCPSend) {
+ doTest(AF_INET6, IPPROTO_TCP);
+}
+
+TEST_F(RecursiveQueryTest, v4UDPSend) {
+ doTest(AF_INET, IPPROTO_UDP);
+}
+
+TEST_F(RecursiveQueryTest, v4TCPSend) {
+ doTest(AF_INET, IPPROTO_TCP);
+}
+
+TEST_F(RecursiveQueryTest, v6UDPSendSpecific) {
+ // Explicitly set a specific address to be bound to the socket.
+ // The subsequent test does not directly ensures the underlying socket
+ // is bound to the expected address, but the success of the tests should
+ // reasonably suggest it works as intended.
+ // Specifying an address also implicitly means the service runs in a
+ // single address-family mode. In tests using TCP we can confirm that
+ // by trying to make a connection and seeing a failure. In UDP, it'd be
+ // more complicated because we need to use a connected socket and catch
+ // an error on a subsequent read operation. We could do it, but for
+ // simplicity we only tests the easier cases for now.
+
+ setDNSService(*TEST_IPV6_ADDR);
+ doTest(AF_INET6, IPPROTO_UDP);
+}
+
+TEST_F(RecursiveQueryTest, v6TCPSendSpecific) {
+ setDNSService(*TEST_IPV6_ADDR);
+ doTest(AF_INET6, IPPROTO_TCP);
+
+ EXPECT_THROW(sendTCP(AF_INET), IOError);
+}
+
+TEST_F(RecursiveQueryTest, v4UDPSendSpecific) {
+ setDNSService(*TEST_IPV4_ADDR);
+ doTest(AF_INET, IPPROTO_UDP);
+}
+
+TEST_F(RecursiveQueryTest, v4TCPSendSpecific) {
+ setDNSService(*TEST_IPV4_ADDR);
+ doTest(AF_INET, IPPROTO_TCP);
+
+ EXPECT_THROW(sendTCP(AF_INET6), IOError);
+}
+
+TEST_F(RecursiveQueryTest, v6AddServer) {
+ setDNSService();
+ dns_service_->addServer(*TEST_SERVER_PORT, TEST_IPV6_ADDR);
+ doTest(AF_INET6, IPPROTO_TCP);
+
+ EXPECT_THROW(sendTCP(AF_INET), IOError);
+}
+
+TEST_F(RecursiveQueryTest, v4AddServer) {
+ setDNSService();
+ dns_service_->addServer(*TEST_SERVER_PORT, TEST_IPV4_ADDR);
+ doTest(AF_INET, IPPROTO_TCP);
+
+ EXPECT_THROW(sendTCP(AF_INET6), IOError);
+}
+
+TEST_F(RecursiveQueryTest, clearServers) {
+ setDNSService();
+ dns_service_->clearServers();
+
+ EXPECT_THROW(sendTCP(AF_INET), IOError);
+ EXPECT_THROW(sendTCP(AF_INET6), IOError);
+}
+
+TEST_F(RecursiveQueryTest, v6TCPOnly) {
+ // Open only IPv6 TCP socket. A subsequent attempt of establishing an
+ // IPv4/TCP connection should fail. See above for why we only test this
+ // for TCP.
+ setDNSService(false, true);
+ EXPECT_THROW(sendTCP(AF_INET), IOError);
+}
+
+TEST_F(RecursiveQueryTest, v4TCPOnly) {
+ setDNSService(true, false);
+ EXPECT_THROW(sendTCP(AF_INET6), IOError);
+}
+
+vector<pair<string, uint16_t> >
+singleAddress(const string &address, uint16_t port) {
+ vector<pair<string, uint16_t> > result;
+ result.push_back(pair<string, uint16_t>(address, port));
+ return (result);
+}
+
+TEST_F(RecursiveQueryTest, recursiveSetupV4) {
+ setDNSService(true, false);
+ uint16_t port = boost::lexical_cast<uint16_t>(TEST_CLIENT_PORT);
+ EXPECT_NO_THROW(RecursiveQuery(*dns_service_,
+ *nsas_, cache_,
+ singleAddress(TEST_IPV4_ADDR, port),
+ singleAddress(TEST_IPV4_ADDR, port)));
+}
+
+TEST_F(RecursiveQueryTest, recursiveSetupV6) {
+ setDNSService(false, true);
+ uint16_t port = boost::lexical_cast<uint16_t>(TEST_CLIENT_PORT);
+ EXPECT_NO_THROW(RecursiveQuery(*dns_service_,
+ *nsas_, cache_,
+ singleAddress(TEST_IPV6_ADDR, port),
+ singleAddress(TEST_IPV6_ADDR,port)));
+}
+
+// XXX:
+// This is very inadequate unit testing. It should be generalized into
+// a routine that can do this with variable address family, address, and
+// port, and with the various callbacks defined in such a way as to ensure
+// full code coverage including error cases.
+TEST_F(RecursiveQueryTest, forwarderSend) {
+ setDNSService(true, false);
+
+ // Note: We use the test prot plus one to ensure we aren't binding
+ // to the same port as the actual server
+ uint16_t port = boost::lexical_cast<uint16_t>(TEST_CLIENT_PORT);
+
+ MockServer server(*io_service_);
+ RecursiveQuery rq(*dns_service_,
+ *nsas_, cache_,
+ singleAddress(TEST_IPV4_ADDR, port),
+ singleAddress(TEST_IPV4_ADDR, port));
+
+ Question q(Name("example.com"), RRClass::IN(), RRType::TXT());
+ OutputBufferPtr buffer(new OutputBuffer(0));
+ MessagePtr answer(new Message(Message::RENDER));
+ rq.resolve(q, answer, buffer, &server);
+
+ char data[4096];
+ size_t size = sizeof(data);
+ ASSERT_NO_THROW(recvUDP(AF_INET, data, size));
+
+ Message m(Message::PARSE);
+ InputBuffer ibuf(data, size);
+
+ // Make sure we can parse the message that was sent
+ EXPECT_NO_THROW(m.parseHeader(ibuf));
+ EXPECT_NO_THROW(m.fromWire(ibuf));
+
+ // Check that the question sent matches the one we wanted
+ QuestionPtr q2 = *m.beginQuestion();
+ EXPECT_EQ(q.getName(), q2->getName());
+ EXPECT_EQ(q.getType(), q2->getType());
+ EXPECT_EQ(q.getClass(), q2->getClass());
+}
+
+int
+createTestSocket()
+{
+ struct addrinfo* res_ = resolveAddress(AF_INET, IPPROTO_UDP, true);
+ int sock_ = socket(res_->ai_family, res_->ai_socktype, res_->ai_protocol);
+ if (sock_ < 0) {
+ isc_throw(IOError, "failed to open test socket");
+ }
+ if (bind(sock_, res_->ai_addr, res_->ai_addrlen) < 0) {
+ isc_throw(IOError, "failed to bind test socket");
+ }
+ return sock_;
+}
+
+int
+setSocketTimeout(int sock_, size_t tv_sec, size_t tv_usec) {
+ const struct timeval timeo = { tv_sec, tv_usec };
+ int recv_options = 0;
+ if (setsockopt(sock_, SOL_SOCKET, SO_RCVTIMEO, &timeo, sizeof(timeo))) {
+ if (errno == ENOPROTOOPT) { // see RecursiveQueryTest::recvUDP()
+ recv_options = MSG_DONTWAIT;
+ } else {
+ isc_throw(IOError, "set RCVTIMEO failed: " << strerror(errno));
+ }
+ }
+ return recv_options;
+}
+
+// try to read from the socket max time
+// *num is incremented for every succesfull read
+// returns true if it can read max times, false otherwise
+bool tryRead(int sock_, int recv_options, size_t max, int* num) {
+ size_t i = 0;
+ do {
+ char inbuff[512];
+ if (recv(sock_, inbuff, sizeof(inbuff), recv_options) < 0) {
+ return false;
+ } else {
+ ++i;
+ ++*num;
+ }
+ } while (i < max);
+ return true;
+}
+
+
+// Test it tries the correct amount of times before giving up
+TEST_F(RecursiveQueryTest, forwardQueryTimeout) {
+ // Prepare the service (we do not use the common setup, we do not answer
+ setDNSService();
+
+ // Prepare the socket
+ sock_ = createTestSocket();
+
+ // Prepare the server
+ bool done(true);
+ MockServerStop server(*io_service_, &done);
+
+ // Do the answer
+ const uint16_t port = boost::lexical_cast<uint16_t>(TEST_CLIENT_PORT);
+ RecursiveQuery query(*dns_service_,
+ *nsas_, cache_,
+ singleAddress(TEST_IPV4_ADDR, port),
+ singleAddress(TEST_IPV4_ADDR, port),
+ 10, 4000, 3000, 2);
+ Question question(Name("example.net"), RRClass::IN(), RRType::A());
+ OutputBufferPtr buffer(new OutputBuffer(0));
+ MessagePtr answer(new Message(Message::RENDER));
+ query.resolve(question, answer, buffer, &server);
+
+ // Run the test
+ io_service_->run();
+
+ // Read up to 3 packets. Use some ad hoc timeout to prevent an infinite
+ // block (see also recvUDP()).
+ int recv_options = setSocketTimeout(sock_, 10, 0);
+ int num = 0;
+ bool read_success = tryRead(sock_, recv_options, 3, &num);
+
+ // The query should 'succeed' with an error response
+ EXPECT_TRUE(done);
+ EXPECT_EQ(3, num);
+ EXPECT_TRUE(read_success);
+}
+
+// If we set client timeout to lower than querytimeout, we should
+// get a failure answer, but still see retries
+// (no actual answer is given here yet)
+TEST_F(RecursiveQueryTest, forwardClientTimeout) {
+ // Prepare the service (we do not use the common setup, we do not answer
+ setDNSService();
+
+ sock_ = createTestSocket();
+
+ // Prepare the server
+ bool done1(true);
+ MockServerStop server(*io_service_, &done1);
+
+ MessagePtr answer(new Message(Message::RENDER));
+
+ // Do the answer
+ const uint16_t port = boost::lexical_cast<uint16_t>(TEST_CLIENT_PORT);
+ // Set it up to retry twice before client timeout fires
+ // Since the lookup timer has not fired, it should retry
+ // four times
+ RecursiveQuery query(*dns_service_,
+ *nsas_, cache_,
+ singleAddress(TEST_IPV4_ADDR, port),
+ singleAddress(TEST_IPV4_ADDR, port),
+ 200, 480, 4000, 4);
+ Question question(Name("example.net"), RRClass::IN(), RRType::A());
+ OutputBufferPtr buffer(new OutputBuffer(0));
+ query.resolve(question, answer, buffer, &server);
+
+ // Run the test
+ io_service_->run();
+
+ // we know it'll fail, so make it a shorter timeout
+ int recv_options = setSocketTimeout(sock_, 1, 0);
+
+ // Try to read 4 times
+ int num = 0;
+ bool read_success = tryRead(sock_, recv_options, 4, &num);
+
+ // The query should fail
+ EXPECT_TRUE(done1);
+ EXPECT_EQ(3, num);
+ EXPECT_FALSE(read_success);
+}
+
+// If we set lookup timeout to lower than querytimeout*retries, we should
+// fail before the full amount of retries
+TEST_F(RecursiveQueryTest, forwardLookupTimeout) {
+ // Prepare the service (we do not use the common setup, we do not answer
+ setDNSService();
+
+ // Prepare the socket
+ sock_ = createTestSocket();
+
+ // Prepare the server
+ bool done(true);
+ MockServerStop server(*io_service_, &done);
+
+ MessagePtr answer(new Message(Message::RENDER));
+
+ // Do the answer
+ const uint16_t port = boost::lexical_cast<uint16_t>(TEST_CLIENT_PORT);
+ // Set up the test so that it will retry 5 times, but the lookup
+ // timeout will fire after only 3 normal timeouts
+ RecursiveQuery query(*dns_service_,
+ *nsas_, cache_,
+ singleAddress(TEST_IPV4_ADDR, port),
+ singleAddress(TEST_IPV4_ADDR, port),
+ 200, 4000, 480, 5);
+ Question question(Name("example.net"), RRClass::IN(), RRType::A());
+ OutputBufferPtr buffer(new OutputBuffer(0));
+ query.resolve(question, answer, buffer, &server);
+
+ // Run the test
+ io_service_->run();
+
+ int recv_options = setSocketTimeout(sock_, 1, 0);
+
+ // Try to read 5 times, should stop after 3 reads
+ int num = 0;
+ bool read_success = tryRead(sock_, recv_options, 5, &num);
+
+ // The query should fail and respond with an error
+ EXPECT_TRUE(done);
+ EXPECT_EQ(3, num);
+ EXPECT_FALSE(read_success);
+}
+
+// Set everything very low and see if this doesn't cause weird
+// behaviour
+TEST_F(RecursiveQueryTest, lowtimeouts) {
+ // Prepare the service (we do not use the common setup, we do not answer
+ setDNSService();
+
+ // Prepare the socket
+ sock_ = createTestSocket();
+
+ // Prepare the server
+ bool done(true);
+ MockServerStop server(*io_service_, &done);
+
+ MessagePtr answer(new Message(Message::RENDER));
+
+ // Do the answer
+ const uint16_t port = boost::lexical_cast<uint16_t>(TEST_CLIENT_PORT);
+ // Set up the test so that it will retry 5 times, but the lookup
+ // timeout will fire after only 3 normal timeouts
+ RecursiveQuery query(*dns_service_,
+ *nsas_, cache_,
+ singleAddress(TEST_IPV4_ADDR, port),
+ singleAddress(TEST_IPV4_ADDR, port),
+ 1, 1, 1, 1);
+ Question question(Name("example.net"), RRClass::IN(), RRType::A());
+ OutputBufferPtr buffer(new OutputBuffer(0));
+ query.resolve(question, answer, buffer, &server);
+
+ // Run the test
+ io_service_->run();
+
+ int recv_options = setSocketTimeout(sock_, 1, 0);
+
+ // Try to read 5 times, should stop after 3 reads
+ int num = 0;
+ bool read_success = tryRead(sock_, recv_options, 5, &num);
+
+ // The query should fail and respond with an error
+ EXPECT_TRUE(done);
+ EXPECT_EQ(1, num);
+ EXPECT_FALSE(read_success);
+}
+
+// as mentioned above, we need a more better framework for this,
+// in addition to that, this sends out queries into the world
+// (which we should catch somehow and fake replies for)
+// for the skeleton code, it shouldn't be too much of a problem
+// Ok so even we don't all have access to the DNS world right now,
+// so disabling these tests too.
+TEST_F(RecursiveQueryTest, DISABLED_recursiveSendOk) {
+ setDNSService(true, false);
+ bool done;
+
+ MockServerStop server(*io_service_, &done);
+ vector<pair<string, uint16_t> > empty_vector;
+ RecursiveQuery rq(*dns_service_, *nsas_, cache_, empty_vector,
+ empty_vector, 10000, 0);
+
+ Question q(Name("www.isc.org"), RRClass::IN(), RRType::A());
+ OutputBufferPtr buffer(new OutputBuffer(0));
+ MessagePtr answer(new Message(Message::RENDER));
+ rq.resolve(q, answer, buffer, &server);
+ io_service_->run();
+
+ // Check that the answer we got matches the one we wanted
+ EXPECT_EQ(Rcode::NOERROR(), answer->getRcode());
+ ASSERT_EQ(1, answer->getRRCount(Message::SECTION_ANSWER));
+ RRsetPtr a = *answer->beginSection(Message::SECTION_ANSWER);
+ EXPECT_EQ(q.getName(), a->getName());
+ EXPECT_EQ(q.getType(), a->getType());
+ EXPECT_EQ(q.getClass(), a->getClass());
+ EXPECT_EQ(1, a->getRdataCount());
+}
+
+// see comments at previous test
+TEST_F(RecursiveQueryTest, DISABLED_recursiveSendNXDOMAIN) {
+ setDNSService(true, false);
+ bool done;
+
+ MockServerStop server(*io_service_, &done);
+ vector<pair<string, uint16_t> > empty_vector;
+ RecursiveQuery rq(*dns_service_, *nsas_, cache_, empty_vector,
+ empty_vector, 10000, 0);
+
+ Question q(Name("wwwdoesnotexist.isc.org"), RRClass::IN(), RRType::A());
+ OutputBufferPtr buffer(new OutputBuffer(0));
+ MessagePtr answer(new Message(Message::RENDER));
+ rq.resolve(q, answer, buffer, &server);
+ io_service_->run();
+
+ // Check that the answer we got matches the one we wanted
+ EXPECT_EQ(Rcode::NXDOMAIN(), answer->getRcode());
+ EXPECT_EQ(0, answer->getRRCount(Message::SECTION_ANSWER));
+}
+
+// TODO: add tests that check whether the cache is updated on succesfull
+// responses, and not updated on failures.
+
+}
diff --git a/src/lib/resolve/tests/recursive_query_unittest_2.cc b/src/lib/resolve/tests/recursive_query_unittest_2.cc
new file mode 100644
index 0000000..12ff3cf
--- /dev/null
+++ b/src/lib/resolve/tests/recursive_query_unittest_2.cc
@@ -0,0 +1,656 @@
+// 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 <algorithm>
+#include <cstdlib>
+#include <iomanip>
+#include <iostream>
+#include <string>
+
+#include <gtest/gtest.h>
+#include <boost/bind.hpp>
+
+
+#include <asio.hpp>
+
+#include <dns/buffer.h>
+#include <dns/question.h>
+#include <dns/message.h>
+#include <dns/messagerenderer.h>
+#include <dns/opcode.h>
+#include <dns/name.h>
+#include <dns/rcode.h>
+#include <dns/rrtype.h>
+#include <dns/rrset.h>
+#include <dns/rrttl.h>
+#include <dns/rdata.h>
+
+#include <asiolink/asiolink_utilities.h>
+#include <asiolink/dns_service.h>
+#include <asiolink/io_address.h>
+#include <asiolink/io_endpoint.h>
+#include <asiolink/io_fetch.h>
+#include <asiolink/io_service.h>
+#include <resolve/recursive_query.h>
+#include <resolve/resolver_interface.h>
+
+using namespace asio;
+using namespace asio::ip;
+using namespace isc::dns;
+using namespace isc::dns::rdata;
+using namespace isc::resolve;
+using namespace std;
+
+/// RecursiveQuery Test - 2
+///
+/// The second part of the RecursiveQuery unit tests, this attempts to get the
+/// RecursiveQuery object to follow a set of referrals for "www.example.org" to
+/// and to invoke TCP fallback on one of the queries. In particular, we expect
+/// that the test will do the following in an attempt to resolve
+/// www.example.org:
+///
+/// - Send question over UDP to "root" - get referral to "org".
+/// - Send question over UDP to "org" - get referral to "example.org" with TC bit set.
+/// - Send question over TCP to "org" - get referral to "example.org".
+/// - Send question over UDP to "example.org" - get response for www.example.org.
+///
+/// (The order of queries is set in this way in order to also test that after a
+/// failover to TCP, queries revert to UDP).
+///
+/// By using the "test_server_" element of RecursiveQuery, all queries are
+/// directed to one or other of the "servers" in the RecursiveQueryTest2 class,
+/// regardless of the glue returned in referrals.
+
+namespace asiolink {
+
+const std::string TEST_ADDRESS = "127.0.0.1"; ///< Servers are on this address
+const uint16_t TEST_PORT = 5301; ///< ... and this port
+const size_t BUFFER_SIZE = 1024; ///< For all buffers
+const char* WWW_EXAMPLE_ORG = "192.0.2.254"; ///< Address of www.example.org
+
+// As the test is fairly long and complex, debugging "print" statements have
+// been left in although they are disabled. Set the following to "true" to
+// enable them.
+const bool DEBUG_PRINT = false;
+
+class MockResolver : public isc::resolve::ResolverInterface {
+ void resolve(const QuestionPtr& question,
+ const ResolverInterface::CallbackPtr& callback) {
+ }
+};
+
+
+
+/// \brief Test fixture for the RecursiveQuery Test
+class RecursiveQueryTest2 : public virtual ::testing::Test
+{
+public:
+
+ /// \brief Status of query
+ ///
+ /// Set before the query and then by each "server" when responding.
+ enum QueryStatus {
+ NONE = 0, ///< Default
+ UDP_ROOT = 1, ///< Query root server over UDP
+ UDP_ORG = 2, ///< Query ORG server over UDP
+ TCP_ORG = 3, ///< Query ORG server over TCP
+ UDP_EXAMPLE_ORG = 4, ///< Query EXAMPLE.ORG server over UDP
+ COMPLETE = 5 ///< Query is complete
+ };
+
+ // Common stuff
+ bool debug_; ///< Set true for debug print
+ IOService service_; ///< Service to run everything
+ DNSService dns_service_; ///< Resolver is part of "server"
+ QuestionPtr question_; ///< What to ask
+ QueryStatus last_; ///< What was the last state
+ QueryStatus expected_; ///< Expected next state
+ OutputBufferPtr question_buffer_; ///< Question we expect to receive
+ isc::nsas::NameserverAddressStore* nsas_;
+ isc::cache::ResolverCache cache_;
+
+ // Data for TCP Server
+ size_t tcp_cumulative_; ///< Cumulative TCP data received
+ tcp::endpoint tcp_endpoint_; ///< Endpoint for TCP receives
+ size_t tcp_length_; ///< Expected length value
+ uint8_t tcp_receive_buffer_[BUFFER_SIZE]; ///< Receive buffer for TCP I/O
+ OutputBufferPtr tcp_send_buffer_; ///< Send buffer for TCP I/O
+ tcp::socket tcp_socket_; ///< Socket used by TCP server
+
+ /// Data for UDP
+ udp::endpoint udp_remote_; ///< Endpoint for UDP receives
+ size_t udp_length_; ///< Expected length value
+ uint8_t udp_receive_buffer_[BUFFER_SIZE]; ///< Receive buffer for UDP I/O
+ OutputBufferPtr udp_send_buffer_; ///< Send buffer for UDP I/O
+ udp::socket udp_socket_; ///< Socket used by UDP server
+
+ /// \brief Constructor
+ RecursiveQueryTest2() :
+ debug_(DEBUG_PRINT),
+ service_(),
+ dns_service_(service_, NULL, NULL, NULL),
+ question_(new Question(Name("www.example.org"), RRClass::IN(), RRType::A())),
+ last_(NONE),
+ expected_(NONE),
+ question_buffer_(new OutputBuffer(BUFFER_SIZE)),
+ tcp_cumulative_(0),
+ tcp_endpoint_(asio::ip::address::from_string(TEST_ADDRESS), TEST_PORT),
+ tcp_length_(0),
+ tcp_receive_buffer_(),
+ tcp_send_buffer_(new OutputBuffer(BUFFER_SIZE)),
+ tcp_socket_(service_.get_io_service()),
+ udp_remote_(),
+ udp_length_(0),
+ udp_receive_buffer_(),
+ udp_send_buffer_(new OutputBuffer(BUFFER_SIZE)),
+ udp_socket_(service_.get_io_service(), udp::v4())
+ {
+ boost::shared_ptr<MockResolver>mock_resolver(new MockResolver());
+ nsas_ = new isc::nsas::NameserverAddressStore(mock_resolver);
+ }
+
+ /// \brief Set Common Message Bits
+ ///
+ /// Sets up the common bits of a response message returned by the handlers.
+ ///
+ /// \param msg Message buffer in RENDER mode.
+ /// \param qid QIT to set the message to
+ void setCommonMessage(isc::dns::Message& msg, uint16_t qid = 0) {
+ msg.setQid(qid);
+ msg.setHeaderFlag(Message::HEADERFLAG_QR);
+ msg.setOpcode(Opcode::QUERY());
+ msg.setHeaderFlag(Message::HEADERFLAG_AA);
+ msg.setRcode(Rcode::NOERROR());
+ msg.addQuestion(*question_);
+ }
+
+ /// \brief Set Referral to "org"
+ ///
+ /// Sets up the passed-in message (expected to be in "RENDER" mode to
+ /// indicate a referral to fictitious .org nameservers.
+ ///
+ /// \param msg Message to update with referral information.
+ void setReferralOrg(isc::dns::Message& msg) {
+ if (debug_) {
+ cout << "setReferralOrg(): creating referral to .org nameservers" << endl;
+ }
+
+ // Do a referral to org. We'll define all NS records as "in-zone"
+ // nameservers (and supply glue) to avoid the possibility of the
+ // resolver starting another recursive query to resolve the address of
+ // a nameserver.
+ RRsetPtr org_ns(new RRset(Name("org."), RRClass::IN(), RRType::NS(), RRTTL(300)));
+ org_ns->addRdata(createRdata(RRType::NS(), RRClass::IN(), "ns1.org."));
+ org_ns->addRdata(createRdata(RRType::NS(), RRClass::IN(), "ns2.org."));
+ msg.addRRset(Message::SECTION_AUTHORITY, org_ns);
+
+ RRsetPtr org_ns1(new RRset(Name("ns1.org."), RRClass::IN(), RRType::A(), RRTTL(300)));
+ org_ns1->addRdata(createRdata(RRType::A(), RRClass::IN(), "192.0.2.1"));
+ msg.addRRset(Message::SECTION_ADDITIONAL, org_ns1);
+
+ RRsetPtr org_ns2(new RRset(Name("ns2.org."), RRClass::IN(), RRType::A(), RRTTL(300)));
+ org_ns2->addRdata(createRdata(RRType::A(), RRClass::IN(), "192.0.2.2"));
+ msg.addRRset(Message::SECTION_ADDITIONAL, org_ns2);
+ }
+
+ /// \brief Set Referral to "example.org"
+ ///
+ /// Sets up the passed-in message (expected to be in "RENDER" mode to
+ /// indicate a referral to fictitious example.org nameservers.
+ ///
+ /// \param msg Message to update with referral information.
+ void setReferralExampleOrg(isc::dns::Message& msg) {
+ if (debug_) {
+ cout << "setReferralExampleOrg(): creating referral to example.org nameservers" << endl;
+ }
+
+ // Do a referral to example.org. As before, we'll define all NS
+ // records as "in-zone" nameservers (and supply glue) to avoid the
+ // possibility of the resolver starting another recursive query to look
+ // up the address of the nameserver.
+ RRsetPtr example_org_ns(new RRset(Name("example.org."), RRClass::IN(), RRType::NS(), RRTTL(300)));
+ example_org_ns->addRdata(createRdata(RRType::NS(), RRClass::IN(), "ns1.example.org."));
+ example_org_ns->addRdata(createRdata(RRType::NS(), RRClass::IN(), "ns2.example.org."));
+ msg.addRRset(Message::SECTION_AUTHORITY, example_org_ns);
+
+ RRsetPtr example_org_ns1(new RRset(Name("ns1.example.org."), RRClass::IN(), RRType::A(), RRTTL(300)));
+ example_org_ns1->addRdata(createRdata(RRType::A(), RRClass::IN(), "192.0.2.11"));
+ msg.addRRset(Message::SECTION_ADDITIONAL, example_org_ns1);
+
+ RRsetPtr example_org_ns2(new RRset(Name("ns2.example.org."), RRClass::IN(), RRType::A(), RRTTL(300)));
+ example_org_ns2->addRdata(createRdata(RRType::A(), RRClass::IN(), "192.0.2.21"));
+ msg.addRRset(Message::SECTION_ADDITIONAL, example_org_ns2);
+ }
+
+ /// \brief Set Answer to "www.example.org"
+ ///
+ /// Sets up the passed-in message (expected to be in "RENDER" mode) to
+ /// indicate an authoritative answer to www.example.org.
+ ///
+ /// \param msg Message to update with referral information.
+ void setAnswerWwwExampleOrg(isc::dns::Message& msg) {
+ if (debug_) {
+ cout << "setAnswerWwwExampleOrg(): creating answer for www.example.org" << endl;
+ }
+
+ // Give a response for www.example.org.
+ RRsetPtr www_example_org_a(new RRset(Name("www.example.org."), RRClass::IN(), RRType::A(), RRTTL(300)));
+ www_example_org_a->addRdata(createRdata(RRType::A(), RRClass::IN(), WWW_EXAMPLE_ORG));
+ msg.addRRset(Message::SECTION_ANSWER, www_example_org_a);
+
+ // ... and add the Authority and Additional sections. (These are the
+ // same as in the referral to example.org from the .org nameserver.)
+ setReferralExampleOrg(msg);
+ }
+
+ /// \brief UDP Receive Handler
+ ///
+ /// This is invoked when a message is received over UDP from the
+ /// RecursiveQuery object under test. It formats an answer and sends it
+ /// asynchronously, with the UdpSendHandler method being specified as the
+ /// completion handler.
+ ///
+ /// \param ec ASIO error code, completion code of asynchronous I/O issued
+ /// by the "server" to receive data.
+ /// \param length Amount of data received.
+ void udpReceiveHandler(error_code ec = error_code(), size_t length = 0) {
+ if (debug_) {
+ cout << "udpReceiveHandler(): error = " << ec.value() <<
+ ", length = " << length << ", last state = " << last_ <<
+ ", expected state = " << expected_ << endl;
+ }
+
+ // Expected state should be one greater than the last state.
+ EXPECT_EQ(static_cast<int>(expected_), static_cast<int>(last_) + 1);
+ last_ = expected_;
+
+ // The QID in the incoming data is random so set it to 0 for the
+ // data comparison check. (It is set to 0 in the buffer containing
+ // the expected data.)
+ uint16_t qid = readUint16(udp_receive_buffer_);
+ udp_receive_buffer_[0] = udp_receive_buffer_[1] = 0;
+
+ // Check that question we received is what was expected.
+ checkReceivedPacket(udp_receive_buffer_, length);
+
+ // The message returned depends on what state we are in. Set up
+ // common stuff first: bits not mentioned are set to 0.
+ Message msg(Message::RENDER);
+ setCommonMessage(msg, qid);
+
+ // Set up state-dependent bits:
+ switch (expected_) {
+ case UDP_ROOT:
+ // Return a referral to org. We then expect to query the "org"
+ // nameservers over UDP next.
+ setReferralOrg(msg);
+ expected_ = UDP_ORG;
+ break;
+
+ case UDP_ORG:
+ // Return a referral to example.org. We explicitly set the TC bit to
+ // force a repeat query to the .org nameservers over TCP.
+ setReferralExampleOrg(msg);
+ if (debug_) {
+ cout << "udpReceiveHandler(): setting TC bit" << endl;
+ }
+ msg.setHeaderFlag(Message::HEADERFLAG_TC);
+ expected_ = TCP_ORG;
+ break;
+
+ case UDP_EXAMPLE_ORG:
+ // Return the answer to the question.
+ setAnswerWwwExampleOrg(msg);
+ expected_ = COMPLETE;
+ break;
+
+ default:
+ FAIL() << "UdpReceiveHandler called with unknown state";
+ }
+
+ // Convert to wire format
+ udp_send_buffer_->clear();
+ MessageRenderer renderer(*udp_send_buffer_);
+ msg.toWire(renderer);
+
+ // Return a message back to the IOFetch object (after setting the
+ // expected length of data for the check in the send handler).
+ udp_length_ = udp_send_buffer_->getLength();
+ udp_socket_.async_send_to(asio::buffer(udp_send_buffer_->getData(),
+ udp_send_buffer_->getLength()),
+ udp_remote_,
+ boost::bind(&RecursiveQueryTest2::udpSendHandler,
+ this, _1, _2));
+ }
+
+ /// \brief UDP Send Handler
+ ///
+ /// Called when a send operation of the UDP server (i.e. a response
+ /// being sent to the RecursiveQuery) has completed, this re-issues
+ /// a read call.
+ ///
+ /// \param ec Completion error code of the send.
+ /// \param length Actual number of bytes sent.
+ void udpSendHandler(error_code ec = error_code(), size_t length = 0) {
+ if (debug_) {
+ cout << "udpSendHandler(): error = " << ec.value() <<
+ ", length = " << length << endl;
+ }
+
+ // Check send was OK
+ EXPECT_EQ(0, ec.value());
+ EXPECT_EQ(udp_length_, length);
+
+ // Reissue the receive call to await the next message.
+ udp_socket_.async_receive_from(
+ asio::buffer(udp_receive_buffer_, sizeof(udp_receive_buffer_)),
+ udp_remote_,
+ boost::bind(&RecursiveQueryTest2::udpReceiveHandler, this, _1, _2));
+ }
+
+ /// \brief Completion Handler for Accepting TCP Data
+ ///
+ /// Called when the remote system connects to the "TCP server". It issues
+ /// an asynchronous read on the socket to read data.
+ ///
+ /// \param socket Socket on which data will be received
+ /// \param ec Boost error code, value should be zero.
+ void tcpAcceptHandler(error_code ec = error_code(), size_t length = 0) {
+ if (debug_) {
+ cout << "tcpAcceptHandler(): error = " << ec.value() <<
+ ", length = " << length << endl;
+ }
+
+ // Expect that the accept completed without a problem.
+ EXPECT_EQ(0, ec.value());
+
+ // Initiate a read on the socket, indicating that nothing has yet been
+ // received.
+ tcp_cumulative_ = 0;
+ tcp_socket_.async_receive(
+ asio::buffer(tcp_receive_buffer_, sizeof(tcp_receive_buffer_)),
+ boost::bind(&RecursiveQueryTest2::tcpReceiveHandler, this, _1, _2));
+ }
+
+ /// \brief Completion Handler for Receiving TCP Data
+ ///
+ /// Reads data from the RecursiveQuery object and loops, reissuing reads,
+ /// until all the message has been read. It then returns an appropriate
+ /// response.
+ ///
+ /// \param socket Socket to use to send the answer
+ /// \param ec ASIO error code, completion code of asynchronous I/O issued
+ /// by the "server" to receive data.
+ /// \param length Amount of data received.
+ void tcpReceiveHandler(error_code ec = error_code(), size_t length = 0) {
+ if (debug_) {
+ cout << "tcpReceiveHandler(): error = " << ec.value() <<
+ ", length = " << length <<
+ ", cumulative = " << tcp_cumulative_ << endl;
+ }
+
+ // Expect that the receive completed without a problem.
+ EXPECT_EQ(0, ec.value());
+
+ // Have we received all the data? We know this by checking if the two-
+ // byte length count in the message is equal to the data received.
+ tcp_cumulative_ += length;
+ bool complete = false;
+ if (tcp_cumulative_ > 2) {
+ uint16_t dns_length = readUint16(tcp_receive_buffer_);
+ complete = ((dns_length + 2) == tcp_cumulative_);
+ }
+
+ if (!complete) {
+ if (debug_) {
+ cout << "tcpReceiveHandler(): read not complete, " <<
+ "issuing another read" << endl;
+ }
+
+ // Not complete yet, issue another read.
+ tcp_socket_.async_receive(
+ asio::buffer(tcp_receive_buffer_ + tcp_cumulative_,
+ sizeof(tcp_receive_buffer_) - tcp_cumulative_),
+ boost::bind(&RecursiveQueryTest2::tcpReceiveHandler, this, _1, _2));
+ return;
+ }
+
+ // Have received a TCP message. Expected state should be one greater
+ // than the last state.
+ EXPECT_EQ(static_cast<int>(expected_), static_cast<int>(last_) + 1);
+ last_ = expected_;
+
+ // Check that question we received is what was expected. Note that we
+ // have to ignore the two-byte header in order to parse the message.
+ checkReceivedPacket(tcp_receive_buffer_ + 2, length - 2);
+
+ // Return a message back. This is a referral to example.org, which
+ // should result in another query over UDP. Note the setting of the
+ // QID in the returned message with what was in the received message.
+ Message msg(Message::RENDER);
+ setCommonMessage(msg, readUint16(tcp_receive_buffer_));
+ setReferralExampleOrg(msg);
+
+ // Convert to wire format
+ tcp_send_buffer_->clear();
+ MessageRenderer renderer(*tcp_send_buffer_);
+ msg.toWire(renderer);
+
+ // Expected next state (when checked) is the UDP query to example.org.
+ // Also, take this opportunity to clear the accumulated read count in
+ // readiness for the next read. (If any - at present, there is only
+ // one read in the test, although extensions to this test suite could
+ // change that.)
+ expected_ = UDP_EXAMPLE_ORG;
+ tcp_cumulative_ = 0;
+
+ // We'll write the message in two parts, the count and the message
+ // itself. This saves having to prepend the count onto the start of a
+ // buffer. When specifying the send handler, the expected size of the
+ // data written is passed as the first parameter so that the handler
+ // can check it.
+ uint8_t count[2];
+ writeUint16(tcp_send_buffer_->getLength(), count);
+ tcp_socket_.async_send(asio::buffer(count, 2),
+ boost::bind(&RecursiveQueryTest2::tcpSendHandler, this,
+ 2, _1, _2));
+ tcp_socket_.async_send(asio::buffer(tcp_send_buffer_->getData(),
+ tcp_send_buffer_->getLength()),
+ boost::bind(&RecursiveQueryTest2::tcpSendHandler, this,
+ tcp_send_buffer_->getLength(), _1, _2));
+ }
+
+ /// \brief Completion Handler for Sending TCP data
+ ///
+ /// Called when the asynchronous send of data back to the RecursiveQuery
+ /// by the TCP "server" in this class has completed. (This send has to
+ /// be asynchronous because control needs to return to the caller in order
+ /// for the IOService "run()" method to be called to run the handlers.)
+ ///
+ /// \param expected_length Number of bytes that were expected to have been sent.
+ /// \param ec Boost error code, value should be zero.
+ /// \param length Number of bytes sent.
+ void tcpSendHandler(size_t expected_length = 0, error_code ec = error_code(),
+ size_t length = 0)
+ {
+ if (debug_) {
+ cout << "tcpSendHandler(): error = " << ec.value() <<
+ ", length = " << length <<
+ ", (expected length = " << expected_length << ")" << endl;
+ }
+ EXPECT_EQ(0, ec.value()); // Expect no error
+ EXPECT_EQ(expected_length, length); // And that amount sent is as expected
+ }
+
+ /// \brief Check Received Packet
+ ///
+ /// Checks the packet received from the RecursiveQuery object to ensure
+ /// that the question is what is expected.
+ ///
+ /// \param data Start of data. This is the start of the received buffer in
+ /// the case of UDP data, and an offset into the buffer past the
+ /// count field for TCP data.
+ /// \param length Length of data.
+ void checkReceivedPacket(uint8_t* data, size_t length) {
+
+ // Decode the received buffer.
+ InputBuffer buffer(data, length);
+ Message message(Message::PARSE);
+ message.fromWire(buffer);
+
+ // Check the packet.
+ EXPECT_FALSE(message.getHeaderFlag(Message::HEADERFLAG_QR));
+
+ Question question = **(message.beginQuestion());
+ EXPECT_TRUE(question == *question_);
+ }
+};
+
+/// \brief Resolver Callback Object
+///
+/// Holds the success and failure callback methods for the resolver
+class ResolverCallback : public isc::resolve::ResolverInterface::Callback {
+public:
+ /// \brief Constructor
+ ResolverCallback(IOService& service) :
+ service_(service), run_(false), status_(false), debug_(DEBUG_PRINT)
+ {}
+
+ /// \brief Destructor
+ virtual ~ResolverCallback()
+ {}
+
+ /// \brief Resolver Callback Success
+ ///
+ /// Called if the resolver detects that the call has succeeded.
+ ///
+ /// \param response Answer to the question.
+ virtual void success(const isc::dns::MessagePtr response) {
+ if (debug_) {
+ cout << "ResolverCallback::success(): answer received" << endl;
+ }
+
+ // There should be one RR each in the question and answer sections, and
+ // two RRs in each of the the authority and additional sections.
+ EXPECT_EQ(1, response->getRRCount(Message::SECTION_QUESTION));
+ EXPECT_EQ(1, response->getRRCount(Message::SECTION_ANSWER));
+ EXPECT_EQ(2, response->getRRCount(Message::SECTION_AUTHORITY));
+ EXPECT_EQ(2, response->getRRCount(Message::SECTION_ADDITIONAL));
+
+ // Check the answer - that the RRset is there...
+ EXPECT_TRUE(response->hasRRset(Message::SECTION_ANSWER,
+ RRsetPtr(new RRset(Name("www.example.org."),
+ RRClass::IN(),
+ RRType::A(),
+ RRTTL(300)))));
+ const RRsetIterator rrset_i = response->beginSection(Message::SECTION_ANSWER);
+
+ // ... get iterator into the Rdata of this RRset and point to first
+ // element...
+ const RdataIteratorPtr rdata_i = (*rrset_i)->getRdataIterator();
+ rdata_i->first();
+
+ // ... and check it is what we expect.
+ EXPECT_EQ(string(WWW_EXAMPLE_ORG), rdata_i->getCurrent().toText());
+
+ // Flag completion
+ run_ = true;
+ status_ = true;
+
+ service_.stop(); // Cause run() to exit.
+ }
+
+ /// \brief Resolver Failure Completion
+ ///
+ /// Called if the resolver detects that the resolution has failed.
+ virtual void failure() {
+ if (debug_) {
+ cout << "ResolverCallback::success(): resolution failure" << endl;
+ }
+ FAIL() << "Resolver reported completion failure";
+
+ // Flag completion
+ run_ = true;
+ status_ = false;
+
+ service_.stop(); // Cause run() to exit.
+ }
+
+ /// \brief Return status of "run" flag
+ bool getRun() const {
+ return (run_);
+ }
+
+ /// \brief Return "status" flag
+ bool getStatus() const {
+ return (status_);
+ }
+
+private:
+ IOService& service_; ///< Service handling the run queue
+ bool run_; ///< Set true when completion handler run
+ bool status_; ///< Set true for success, false on error
+ bool debug_; ///< Debug flag
+};
+
+// Sets up the UDP and TCP "servers", then tries a resolution.
+
+TEST_F(RecursiveQueryTest2, Resolve) {
+
+ // Set up the UDP server and issue the first read. The endpoint from which
+ // the query is sent is put in udp_endpoint_ when the read completes, which
+ // is referenced in the callback as the place to which the response is sent.
+ udp_socket_.set_option(socket_base::reuse_address(true));
+ udp_socket_.bind(udp::endpoint(address::from_string(TEST_ADDRESS), TEST_PORT));
+ udp_socket_.async_receive_from(asio::buffer(udp_receive_buffer_,
+ sizeof(udp_receive_buffer_)),
+ udp_remote_,
+ boost::bind(&RecursiveQueryTest2::udpReceiveHandler,
+ this, _1, _2));
+
+ // Set up the TCP server and issue the accept. Acceptance will cause the
+ // read to be issued.
+ tcp::acceptor acceptor(service_.get_io_service(),
+ tcp::endpoint(tcp::v4(), TEST_PORT));
+ acceptor.async_accept(tcp_socket_,
+ boost::bind(&RecursiveQueryTest2::tcpAcceptHandler,
+ this, _1, 0));
+
+ // Set up the RecursiveQuery object.
+ std::vector<std::pair<std::string, uint16_t> > upstream; // Empty
+ std::vector<std::pair<std::string, uint16_t> > upstream_root; // Empty
+ RecursiveQuery query(dns_service_, *nsas_, cache_,
+ upstream, upstream_root);
+ query.setTestServer(TEST_ADDRESS, TEST_PORT);
+
+ // Set up callback for the tor eceive notification that the query has
+ // completed.
+ isc::resolve::ResolverInterface::CallbackPtr
+ resolver_callback(new ResolverCallback(service_));
+
+ // Kick off the resolution process. We expect the first question to go to
+ // "root".
+ expected_ = UDP_ROOT;
+ query.resolve(question_, resolver_callback);
+ service_.run();
+
+ // Check what ran. (We have to cast the callback to ResolverCallback as we
+ // lost the information on the derived class when we used a
+ // ResolverInterface::CallbackPtr to store a pointer to it.)
+ ResolverCallback* rc = static_cast<ResolverCallback*>(resolver_callback.get());
+ EXPECT_TRUE(rc->getRun());
+ EXPECT_TRUE(rc->getStatus());
+}
+
+} // namespace asiolink
diff --git a/src/lib/resolve/tests/response_classifier_unittest.cc b/src/lib/resolve/tests/response_classifier_unittest.cc
index b37ded7..23c8666 100644
--- a/src/lib/resolve/tests/response_classifier_unittest.cc
+++ b/src/lib/resolve/tests/response_classifier_unittest.cc
@@ -80,6 +80,8 @@ public:
RRType::CNAME(), RRTTL(300))),
rrs_in_ns_(new RRset(Name("example.com"), RRClass::IN(),
RRType::NS(), RRTTL(300))),
+ rrs_in_soa_(new RRset(Name("example.com"), RRClass::IN(),
+ RRType::SOA(), RRTTL(300))),
rrs_in_txt_www(new RRset(Name("www.example.com"), RRClass::IN(),
RRType::TXT(), RRTTL(300))),
cname_target("."),
@@ -115,6 +117,9 @@ public:
// Set up an imaginary NS RRset for an authority section
rrs_in_ns_->addRdata(ConstRdataPtr(new NS(Name("ns0.isc.org"))));
rrs_in_ns_->addRdata(ConstRdataPtr(new NS(Name("ns0.example.org"))));
+
+ // And an imaginary SOA
+ rrs_in_soa_->addRdata(ConstRdataPtr(new SOA(Name("ns0.example.org"), Name("root.example.org"), 1, 2, 3, 4, 5)));
// Set up the records for the www host
rrs_in_a_www->addRdata(ConstRdataPtr(new A("1.2.3.4")));
@@ -146,6 +151,7 @@ public:
RRsetPtr rrs_in_cname_www1; // www1.example.com IN CNAME
RRsetPtr rrs_in_cname_www2; // www2.example.com IN CNAME
RRsetPtr rrs_in_ns_; // example.com IN NS
+ RRsetPtr rrs_in_soa_; // example.com IN SOA
RRsetPtr rrs_in_txt_www; // www.example.com IN TXT
Name cname_target; // Used in response classifier to
// store the target of a possible
@@ -349,6 +355,17 @@ TEST_F(ResponseClassifierTest, EmptyAnswerReferral) {
}
+// Test if we get a NOERROR answer that contains neither an actual
+// answer nor a delegation
+TEST_F(ResponseClassifierTest, NoErrorNoData) {
+
+ msg_a.addRRset(Message::SECTION_AUTHORITY, rrs_in_soa_);
+ EXPECT_EQ(ResponseClassifier::NXRRSET,
+ ResponseClassifier::classify(qu_in_a_www, msg_a, cname_target,
+ cname_count));
+
+}
+
// Check the case where we have a simple answer in the answer section. This
// occurs when the QNAME/QTYPE/QCLASS matches one of the RRsets in the
// answer section - expect when the QTYPE is ANY, in which case the match
More information about the bind10-changes
mailing list