Browse Source

[master]Merge branch 'master' of ssh://git.bind10.isc.org/var/bind10/git/bind10

Jeremy C. Reed 13 years ago
parent
commit
54c1a257f7
44 changed files with 1677 additions and 429 deletions
  1. 22 0
      ChangeLog
  2. 19 1
      configure.ac
  3. 60 26
      src/bin/auth/auth_srv.cc
  4. 3 2
      src/bin/auth/auth_srv.h
  5. 5 1
      src/bin/auth/statistics.cc
  6. 83 22
      src/bin/auth/tests/auth_srv_unittest.cc
  7. 1 0
      src/bin/auth/tests/command_unittest.cc
  8. 11 4
      src/bin/auth/tests/config_unittest.cc
  9. 2 0
      src/bin/auth/tests/statistics_unittest.cc
  10. 6 6
      src/bin/resolver/resolver.cc
  11. 3 3
      src/bin/resolver/resolver.h
  12. 11 3
      src/bin/resolver/tests/resolver_config_unittest.cc
  13. 6 10
      src/bin/stats/b10-stats.8
  14. 9 15
      src/bin/stats/b10-stats.xml
  15. 109 14
      src/bin/stats/stats.py.in
  16. 7 0
      src/bin/stats/stats.spec
  17. 179 0
      src/bin/stats/tests/b10-stats_test.py
  18. 14 1
      src/bin/stats/tests/test_utils.py
  19. 49 58
      src/lib/asiodns/dns_service.cc
  20. 90 23
      src/lib/asiodns/dns_service.h
  21. 1 1
      src/lib/asiodns/tests/Makefile.am
  22. 323 0
      src/lib/asiodns/tests/dns_service_unittest.cc
  23. 0 123
      src/lib/asiodns/tests/io_service_unittest.cc
  24. 2 1
      src/lib/datasrc/data_source.cc
  25. 63 0
      src/lib/datasrc/database.h
  26. 4 2
      src/lib/datasrc/datasrc_messages.mes
  27. 138 11
      src/lib/datasrc/sqlite3_accessor.cc
  28. 13 0
      src/lib/datasrc/sqlite3_accessor.h
  29. 94 1
      src/lib/datasrc/tests/database_unittest.cc
  30. 102 0
      src/lib/datasrc/tests/sqlite3_accessor_unittest.cc
  31. 2 3
      src/lib/nsas/hash.cc
  32. 1 0
      src/lib/resolve/Makefile.am
  33. 1 1
      src/lib/resolve/recursive_query.cc
  34. 2 2
      src/lib/resolve/recursive_query.h
  35. 15 21
      src/lib/server_common/portconfig.cc
  36. 43 43
      src/lib/server_common/portconfig.h
  37. 3 3
      src/lib/server_common/tests/portconfig_unittest.cc
  38. 4 0
      src/lib/testutils/dnsmessage_test.h
  39. 57 0
      src/lib/testutils/mockups.h
  40. 3 3
      src/lib/testutils/portconfig.h
  41. 6 13
      src/lib/testutils/socket_request.h
  42. 4 0
      src/lib/testutils/srv_test.h
  43. 78 12
      tests/system/bindctl/tests.sh
  44. 29 0
      tests/tools/perfdhcp/perfdhcp.c

+ 22 - 0
ChangeLog

@@ -1,3 +1,25 @@
+406.	[bug]		muks
+	On platforms such as OpenBSD where pselect() is not available,
+	make a wrapper around select() in perfdhcp.
+	(Trac #1639, git 6ea0b1d62e7b8b6596209291aa6c8b34b8e73191)
+
+405.	[bug]		jinmei
+	Make sure disabling Boost threads if the default configuration is
+	to disable it for the system.  This fixes a crash and hang up
+	problem on OpenBSD, where the use of Boost thread could be
+	different in different program files depending on the order of
+	including various header files, and could introduce inconsistent
+	states between a library and a program.  Explicitly forcing the
+	original default throughout the BIND 10 build environment will
+	prevent this from happening.
+	(Trac #1727, git 23f9c3670b544c5f8105958ff148aeba050bc1b4)
+
+404.	[bug]		naokikambe
+	The statistic counters are now properly accumulated across multiple
+	instances of b10-auth (if there are multiple instances), instead of
+	providing result for random instance.
+	(Trac #1751, git 3285353a660e881ec2b645e1bc10d94e5020f357)
+
 403.	[build]*	jelte
 	The configure option for botan (--with-botan=PATH) is replaced by
 	--with-botan-config=PATH, which takes a full path to a botan-config

+ 19 - 1
configure.ac

@@ -780,7 +780,22 @@ if test "${boost_include_path}" ; then
 fi
 AC_CHECK_HEADERS([boost/shared_ptr.hpp boost/foreach.hpp boost/interprocess/sync/interprocess_upgradable_mutex.hpp boost/date_time/posix_time/posix_time_types.hpp boost/bind.hpp boost/function.hpp],,
   AC_MSG_ERROR([Missing required header files.]))
-CPPFLAGS="$CPPFLAGS_SAVES"
+
+# Detect whether Boost tries to use threads by default, and, if not,
+# make it sure explicitly.  In some systems the automatic detection
+# may depend on preceding header files, and if inconsistency happens
+# it could lead to a critical disruption.
+AC_MSG_CHECKING([whether Boost tries to use threads])
+AC_TRY_COMPILE([
+#include <boost/config.hpp>
+#ifdef BOOST_HAS_THREADS
+#error "boost will use threads"
+#endif],,
+[AC_MSG_RESULT(no)
+ CPPFLAGS_BOOST_THREADCONF="-DBOOST_DISABLE_THREADS=1"],
+[AC_MSG_RESULT(yes)])
+
+CPPFLAGS="$CPPFLAGS_SAVES $CPPFLAGS_BOOST_THREADCONF"
 AC_SUBST(BOOST_INCLUDES)
 
 # I can't get some of the #include <asio.hpp> right without this
@@ -930,6 +945,9 @@ EV_SET(&kevent, 0, 0, 0, 0, 0, udata);],
 	])
 fi
 
+# Check for functions that are not available on all platforms
+AC_CHECK_FUNCS([pselect])
+
 # perfdhcp: If the clock_gettime() function does not exist on the system,
 # use an alternative supplied in the code based on gettimeofday().
 CLOCK_GETTIME_LDFLAGS=

+ 60 - 26
src/bin/auth/auth_srv.cc

@@ -47,6 +47,8 @@
 #include <dns/message.h>
 #include <dns/tsig.h>
 
+#include <asiodns/dns_service.h>
+
 #include <datasrc/query.h>
 #include <datasrc/data_source.h>
 #include <datasrc/memory_datasrc.h>
@@ -78,6 +80,35 @@ using namespace isc::asiolink;
 using namespace isc::asiodns;
 using namespace isc::server_common::portconfig;
 
+namespace {
+// A helper class for cleaning up message renderer.
+//
+// A temporary object of this class is expected to be created before starting
+// response message rendering.  On construction, it (re)initialize the given
+// message renderer with the given buffer.  On destruction, it releases
+// the previously set buffer and then release any internal resource in the
+// renderer, no matter what happened during the rendering, especially even
+// when it resulted in an exception.
+//
+// Note: if we need this helper in many other places we might consider making
+// it visible to other modules.  As of this implementation this is the only
+// user of this class, so we hide it within the implementation.
+class RendererHolder {
+public:
+    RendererHolder(MessageRenderer& renderer, OutputBuffer* buffer) :
+        renderer_(renderer)
+    {
+        renderer.setBuffer(buffer);
+    }
+    ~RendererHolder() {
+        renderer_.setBuffer(NULL);
+        renderer_.clear();
+    }
+private:
+    MessageRenderer& renderer_;
+};
+}
+
 class AuthSrvImpl {
 private:
     // prohibit copy
@@ -277,8 +308,8 @@ public:
 };
 
 void
-makeErrorMessage(Message& message, OutputBuffer& buffer,
-                 const Rcode& rcode,
+makeErrorMessage(MessageRenderer& renderer, Message& message,
+                 OutputBuffer& buffer, const Rcode& rcode,
                  std::auto_ptr<TSIGContext> tsig_context =
                  std::auto_ptr<TSIGContext>())
 {
@@ -311,14 +342,12 @@ makeErrorMessage(Message& message, OutputBuffer& buffer,
 
     message.setRcode(rcode);
     
-    MessageRenderer renderer;
-    renderer.setBuffer(&buffer);
+    RendererHolder holder(renderer, &buffer);
     if (tsig_context.get() != NULL) {
         message.toWire(renderer, *tsig_context);
     } else {
         message.toWire(renderer);
     }
-    renderer.setBuffer(NULL);
     LOG_DEBUG(auth_logger, DBG_AUTH_MESSAGES, AUTH_SEND_ERROR_RESPONSE)
               .arg(renderer.getLength()).arg(message);
 }
@@ -447,13 +476,13 @@ AuthSrv::processMessage(const IOMessage& io_message, Message& message,
     } catch (const DNSProtocolError& error) {
         LOG_DEBUG(auth_logger, DBG_AUTH_DETAIL, AUTH_PACKET_PROTOCOL_ERROR)
                   .arg(error.getRcode().toText()).arg(error.what());
-        makeErrorMessage(message, buffer, error.getRcode());
+        makeErrorMessage(impl_->renderer_, message, buffer, error.getRcode());
         impl_->resumeServer(server, message, true);
         return;
     } catch (const Exception& ex) {
         LOG_DEBUG(auth_logger, DBG_AUTH_DETAIL, AUTH_PACKET_PARSE_ERROR)
                   .arg(ex.what());
-        makeErrorMessage(message, buffer, Rcode::SERVFAIL());
+        makeErrorMessage(impl_->renderer_, message, buffer, Rcode::SERVFAIL());
         impl_->resumeServer(server, message, true);
         return;
     } // other exceptions will be handled at a higher layer.
@@ -480,7 +509,8 @@ AuthSrv::processMessage(const IOMessage& io_message, Message& message,
     }
 
     if (tsig_error != TSIGError::NOERROR()) {
-        makeErrorMessage(message, buffer, tsig_error.toRcode(), tsig_context);
+        makeErrorMessage(impl_->renderer_, message, buffer,
+                         tsig_error.toRcode(), tsig_context);
         impl_->resumeServer(server, message, true);
         return;
     }
@@ -497,9 +527,11 @@ AuthSrv::processMessage(const IOMessage& io_message, Message& message,
         } else if (message.getOpcode() != Opcode::QUERY()) {
             LOG_DEBUG(auth_logger, DBG_AUTH_DETAIL, AUTH_UNSUPPORTED_OPCODE)
                       .arg(message.getOpcode().toText());
-            makeErrorMessage(message, buffer, Rcode::NOTIMP(), tsig_context);
+            makeErrorMessage(impl_->renderer_, message, buffer,
+                             Rcode::NOTIMP(), tsig_context);
         } else if (message.getRRCount(Message::SECTION_QUESTION) != 1) {
-            makeErrorMessage(message, buffer, Rcode::FORMERR(), tsig_context);
+            makeErrorMessage(impl_->renderer_, message, buffer,
+                             Rcode::FORMERR(), tsig_context);
         } else {
             ConstQuestionPtr question = *message.beginQuestion();
             const RRType &qtype = question->getType();
@@ -517,10 +549,10 @@ AuthSrv::processMessage(const IOMessage& io_message, Message& message,
     } catch (const std::exception& ex) {
         LOG_DEBUG(auth_logger, DBG_AUTH_DETAIL, AUTH_RESPONSE_FAILURE)
                   .arg(ex.what());
-        makeErrorMessage(message, buffer, Rcode::SERVFAIL());
+        makeErrorMessage(impl_->renderer_, message, buffer, Rcode::SERVFAIL());
     } catch (...) {
         LOG_DEBUG(auth_logger, DBG_AUTH_DETAIL, AUTH_RESPONSE_FAILURE_UNKNOWN);
-        makeErrorMessage(message, buffer, Rcode::SERVFAIL());
+        makeErrorMessage(impl_->renderer_, message, buffer, Rcode::SERVFAIL());
     }
     impl_->resumeServer(server, message, send_answer);
 }
@@ -563,13 +595,11 @@ AuthSrvImpl::processNormalQuery(const IOMessage& io_message, Message& message,
         }
     } catch (const Exception& ex) {
         LOG_ERROR(auth_logger, AUTH_PROCESS_FAIL).arg(ex.what());
-        makeErrorMessage(message, buffer, Rcode::SERVFAIL());
+        makeErrorMessage(renderer_, message, buffer, Rcode::SERVFAIL());
         return (true);
     }
 
-    renderer_.clear();
-    renderer_.setBuffer(&buffer);
-    
+    RendererHolder holder(renderer_, &buffer);
     const bool udp_buffer =
         (io_message.getSocket().getProtocol() == IPPROTO_UDP);
     renderer_.setLengthLimit(udp_buffer ? remote_bufsize : 65535);
@@ -578,7 +608,6 @@ AuthSrvImpl::processNormalQuery(const IOMessage& io_message, Message& message,
     } else {
         message.toWire(renderer_);
     }
-    renderer_.setBuffer(NULL);
     LOG_DEBUG(auth_logger, DBG_AUTH_MESSAGES, AUTH_SEND_NORMAL_RESPONSE)
               .arg(renderer_.getLength()).arg(message);
     return (true);
@@ -594,7 +623,8 @@ AuthSrvImpl::processXfrQuery(const IOMessage& io_message, Message& message,
 
     if (io_message.getSocket().getProtocol() == IPPROTO_UDP) {
         LOG_DEBUG(auth_logger, DBG_AUTH_DETAIL, AUTH_AXFR_UDP);
-        makeErrorMessage(message, buffer, Rcode::FORMERR(), tsig_context);
+        makeErrorMessage(renderer_, message, buffer, Rcode::FORMERR(),
+                         tsig_context);
         return (true);
     }
 
@@ -619,7 +649,8 @@ AuthSrvImpl::processXfrQuery(const IOMessage& io_message, Message& message,
 
         LOG_DEBUG(auth_logger, DBG_AUTH_DETAIL, AUTH_AXFR_ERROR)
                   .arg(err.what());
-        makeErrorMessage(message, buffer, Rcode::SERVFAIL(), tsig_context);
+        makeErrorMessage(renderer_, message, buffer, Rcode::SERVFAIL(),
+                         tsig_context);
         return (true);
     }
 
@@ -636,14 +667,16 @@ AuthSrvImpl::processNotify(const IOMessage& io_message, Message& message,
     if (message.getRRCount(Message::SECTION_QUESTION) != 1) {
         LOG_DEBUG(auth_logger, DBG_AUTH_DETAIL, AUTH_NOTIFY_QUESTIONS)
                   .arg(message.getRRCount(Message::SECTION_QUESTION));
-        makeErrorMessage(message, buffer, Rcode::FORMERR(), tsig_context);
+        makeErrorMessage(renderer_, message, buffer, Rcode::FORMERR(),
+                         tsig_context);
         return (true);
     }
     ConstQuestionPtr question = *message.beginQuestion();
     if (question->getType() != RRType::SOA()) {
         LOG_DEBUG(auth_logger, DBG_AUTH_DETAIL, AUTH_NOTIFY_RRTYPE)
                   .arg(question->getType().toText());
-        makeErrorMessage(message, buffer, Rcode::FORMERR(), tsig_context);
+        makeErrorMessage(renderer_, message, buffer, Rcode::FORMERR(),
+                         tsig_context);
         return (true);
     }
 
@@ -698,14 +731,12 @@ AuthSrvImpl::processNotify(const IOMessage& io_message, Message& message,
     message.setHeaderFlag(Message::HEADERFLAG_AA);
     message.setRcode(Rcode::NOERROR());
 
-    renderer_.clear();
-    renderer_.setBuffer(&buffer);
+    RendererHolder holder(renderer_, &buffer);
     if (tsig_context.get() != NULL) {
         message.toWire(renderer_, *tsig_context);
     } else {
         message.toWire(renderer_);
     }
-    renderer_.setBuffer(NULL);
     return (true);
 }
 
@@ -837,11 +868,14 @@ AuthSrv::getListenAddresses() const {
 
 void
 AuthSrv::setListenAddresses(const AddressList& addresses) {
-    installListenAddresses(addresses, impl_->listen_addresses_, *dnss_);
+    // For UDP servers we specify the "SYNC_OK" option because in our usage
+    // it can act in the synchronous mode.
+    installListenAddresses(addresses, impl_->listen_addresses_, *dnss_,
+                           DNSService::SERVER_SYNC_OK);
 }
 
 void
-AuthSrv::setDNSService(isc::asiodns::DNSService& dnss) {
+AuthSrv::setDNSService(isc::asiodns::DNSServiceBase& dnss) {
     dnss_ = &dnss;
 }
 

+ 3 - 2
src/bin/auth/auth_srv.h

@@ -28,6 +28,7 @@
 #include <util/buffer.h>
 
 #include <asiodns/dns_server.h>
+#include <asiodns/dns_service.h>
 #include <asiodns/dns_lookup.h>
 #include <asiodns/dns_answer.h>
 #include <asiolink/io_message.h>
@@ -384,7 +385,7 @@ public:
         const;
 
     /// \brief Assign an ASIO DNS Service queue to this Auth object
-    void setDNSService(isc::asiodns::DNSService& dnss);
+    void setDNSService(isc::asiodns::DNSServiceBase& dnss);
 
     /// \brief Sets the keyring used for verifying and signing
     ///
@@ -400,7 +401,7 @@ private:
     isc::asiolink::SimpleCallback* checkin_;
     isc::asiodns::DNSLookup* dns_lookup_;
     isc::asiodns::DNSAnswer* dns_answer_;
-    isc::asiodns::DNSService* dnss_;
+    isc::asiodns::DNSServiceBase* dnss_;
 };
 
 #endif // __AUTH_SRV_H

+ 5 - 1
src/bin/auth/statistics.cc

@@ -112,9 +112,13 @@ AuthCountersImpl::submitStatistics() const {
         return (false);
     }
     std::stringstream statistics_string;
+    // add pid in order for stats to identify which auth sends
+    // statistics in the situation that multiple auth instances are
+    // working
     statistics_string << "{\"command\": [\"set\","
                       <<   "{ \"owner\": \"Auth\","
-                      <<   "  \"data\":"
+                      <<   "  \"pid\":" << getpid()
+                      <<   ", \"data\":"
                       <<     "{ \"queries.udp\": "
                       <<     server_counter_.get(AuthCounters::SERVER_UDP_QUERY)
                       <<     ", \"queries.tcp\": "

+ 83 - 22
src/bin/auth/tests/auth_srv_unittest.cc

@@ -41,6 +41,7 @@
 #include <dns/tests/unittest_util.h>
 #include <testutils/dnsmessage_test.h>
 #include <testutils/srv_test.h>
+#include <testutils/mockups.h>
 #include <testutils/portconfig.h>
 #include <testutils/socket_request.h>
 
@@ -75,7 +76,7 @@ const char* const CONFIG_INMEMORY_EXAMPLE =
 class AuthSrvTest : public SrvTestBase {
 protected:
     AuthSrvTest() :
-        dnss_(ios_, NULL, NULL, NULL),
+        dnss_(),
         server(true, xfrout),
         rrclass(RRClass::IN()),
         // The empty string is expected value of the parameter of
@@ -135,8 +136,7 @@ protected:
                     opcode.getCode(), QR_FLAG, 1, 0, 0, 0);
     }
 
-    IOService ios_;
-    DNSService dnss_;
+    MockDNSService dnss_;
     MockSession statistics_session;
     MockXfroutClient xfrout;
     AuthSrv server;
@@ -1079,10 +1079,11 @@ TEST_F(AuthSrvTest, processNormalQuery_reuseRenderer1) {
     UnitTestUtil::createRequestMessage(request_message, Opcode::QUERY(),
                                        default_qid, Name("example.com"),
                                        RRClass::IN(), RRType::NS());
-    
+
     request_message.setHeaderFlag(Message::HEADERFLAG_AA);
     createRequestPacket(request_message, IPPROTO_UDP);
-    server.processMessage(*io_message, *parse_message, *response_obuffer, &dnsserv);
+    server.processMessage(*io_message, *parse_message, *response_obuffer,
+                          &dnsserv);
     EXPECT_NE(request_message.getRcode(), parse_message->getRcode());
 }
 
@@ -1090,12 +1091,14 @@ TEST_F(AuthSrvTest, processNormalQuery_reuseRenderer2) {
     UnitTestUtil::createRequestMessage(request_message, Opcode::QUERY(),
                                        default_qid, Name("example.com"),
                                        RRClass::IN(), RRType::SOA());
-    
+
     request_message.setHeaderFlag(Message::HEADERFLAG_AA);
     createRequestPacket(request_message, IPPROTO_UDP);
-    server.processMessage(*io_message, *parse_message, *response_obuffer, &dnsserv);
+    server.processMessage(*io_message, *parse_message, *response_obuffer,
+                          &dnsserv);
     ConstQuestionPtr question = *parse_message->beginQuestion();
-    EXPECT_STRNE(question->getType().toText().c_str(),RRType::NS().toText().c_str());
+    EXPECT_STRNE(question->getType().toText().c_str(),
+                 RRType::NS().toText().c_str());
 }
 //
 // Tests for catching exceptions in various stages of the query processing
@@ -1138,11 +1141,12 @@ checkThrow(ThrowWhen method, ThrowWhen throw_at, bool isc_exception) {
 class FakeZoneFinder : public isc::datasrc::ZoneFinder {
 public:
     FakeZoneFinder(isc::datasrc::ZoneFinderPtr zone_finder,
-                   ThrowWhen throw_when,
-                   bool isc_exception) :
+                   ThrowWhen throw_when, bool isc_exception,
+                   ConstRRsetPtr fake_rrset) :
         real_zone_finder_(zone_finder),
         throw_when_(throw_when),
-        isc_exception_(isc_exception)
+        isc_exception_(isc_exception),
+        fake_rrset_(fake_rrset)
     {}
 
     virtual isc::dns::Name
@@ -1162,7 +1166,18 @@ public:
          const isc::dns::RRType& type,
          isc::datasrc::ZoneFinder::FindOptions options)
     {
+        using namespace isc::datasrc;
         checkThrow(THROW_AT_FIND, throw_when_, isc_exception_);
+        // If faked RRset was specified on construction and it matches the
+        // query, return it instead of searching the real data source.
+        if (fake_rrset_ && fake_rrset_->getName() == name &&
+            fake_rrset_->getType() == type)
+        {
+            return (ZoneFinderContextPtr(new ZoneFinder::Context(
+                                             *this, options,
+                                             ResultContext(SUCCESS,
+                                                           fake_rrset_))));
+        }
         return (real_zone_finder_->find(name, type, options));
     }
 
@@ -1190,6 +1205,7 @@ private:
     isc::datasrc::ZoneFinderPtr real_zone_finder_;
     ThrowWhen throw_when_;
     bool isc_exception_;
+    ConstRRsetPtr fake_rrset_;
 };
 
 /// \brief Proxy InMemoryClient that can throw exceptions at specified times
@@ -1206,12 +1222,15 @@ public:
     ///        class or the related FakeZoneFinder)
     /// \param isc_exception if true, throw isc::Exception, otherwise,
     ///                      throw std::exception
+    /// \param fake_rrset If non NULL, it will be used as an answer to
+    /// find() for that name and type.
     FakeInMemoryClient(AuthSrv::InMemoryClientPtr real_client,
-                       ThrowWhen throw_when,
-                       bool isc_exception) :
+                       ThrowWhen throw_when, bool isc_exception,
+                       ConstRRsetPtr fake_rrset = ConstRRsetPtr()) :
         real_client_(real_client),
         throw_when_(throw_when),
-        isc_exception_(isc_exception)
+        isc_exception_(isc_exception),
+        fake_rrset_(fake_rrset)
     {}
 
     /// \brief proxy call for findZone
@@ -1226,14 +1245,16 @@ public:
         const FindResult result = real_client_->findZone(name);
         return (FindResult(result.code, isc::datasrc::ZoneFinderPtr(
                                         new FakeZoneFinder(result.zone_finder,
-                                        throw_when_,
-                                        isc_exception_))));
+                                                           throw_when_,
+                                                           isc_exception_,
+                                                           fake_rrset_))));
     }
 
 private:
     AuthSrv::InMemoryClientPtr real_client_;
     ThrowWhen throw_when_;
     bool isc_exception_;
+    ConstRRsetPtr fake_rrset_;
 };
 
 } // end anonymous namespace for throwing proxy classes
@@ -1248,9 +1269,7 @@ TEST_F(AuthSrvTest, queryWithInMemoryClientProxy) {
 
     AuthSrv::InMemoryClientPtr fake_client(
         new FakeInMemoryClient(server.getInMemoryClient(rrclass),
-                               THROW_NEVER,
-                               false));
-
+                               THROW_NEVER, false));
     ASSERT_NE(AuthSrv::InMemoryClientPtr(), server.getInMemoryClient(rrclass));
     server.setInMemoryClient(rrclass, fake_client);
 
@@ -1267,9 +1286,11 @@ TEST_F(AuthSrvTest, queryWithInMemoryClientProxy) {
 // to throw in the given method
 // If isc_exception is true, it will throw isc::Exception, otherwise
 // it will throw std::exception
+// If non null rrset is given, it will be passed to the proxy so it can
+// return some faked response.
 void
 setupThrow(AuthSrv* server, const char *config, ThrowWhen throw_when,
-                   bool isc_exception)
+           bool isc_exception, ConstRRsetPtr rrset = ConstRRsetPtr())
 {
     // Set real inmem client to proxy
     updateConfig(server, config, true);
@@ -1279,8 +1300,7 @@ setupThrow(AuthSrv* server, const char *config, ThrowWhen throw_when,
     AuthSrv::InMemoryClientPtr fake_client(
         new FakeInMemoryClient(
             server->getInMemoryClient(isc::dns::RRClass::IN()),
-            throw_when,
-            isc_exception));
+            throw_when, isc_exception, rrset));
 
     ASSERT_NE(AuthSrv::InMemoryClientPtr(),
               server->getInMemoryClient(isc::dns::RRClass::IN()));
@@ -1324,4 +1344,45 @@ TEST_F(AuthSrvTest, queryWithInMemoryClientProxyGetClass) {
                 opcode.getCode(), QR_FLAG | AA_FLAG, 1, 1, 2, 1);
 }
 
+TEST_F(AuthSrvTest, queryWithThrowingInToWire) {
+    // Set up a faked data source.  It will return an empty RRset for the
+    // query.
+    ConstRRsetPtr empty_rrset(new RRset(Name("foo.example"),
+                                        RRClass::IN(), RRType::TXT(),
+                                        RRTTL(0)));
+    setupThrow(&server, CONFIG_INMEMORY_EXAMPLE, THROW_NEVER, true,
+               empty_rrset);
+
+    // Repeat the query processing two times.  Due to the faked RRset,
+    // toWire() should throw, and it should result in SERVFAIL.
+    OutputBufferPtr orig_buffer;
+    for (int i = 0; i < 2; ++i) {
+        UnitTestUtil::createDNSSECRequestMessage(request_message, opcode,
+                                                 default_qid,
+                                                 Name("foo.example."),
+                                                 RRClass::IN(), RRType::TXT());
+        createRequestPacket(request_message, IPPROTO_UDP);
+        server.processMessage(*io_message, *parse_message, *response_obuffer,
+                              &dnsserv);
+        headerCheck(*parse_message, default_qid, Rcode::SERVFAIL(),
+                    opcode.getCode(), QR_FLAG, 1, 0, 0, 0);
+
+        // Make a backup of the original buffer for latest tests and replace
+        // it with a new one
+        if (!orig_buffer) {
+            orig_buffer = response_obuffer;
+            response_obuffer.reset(new OutputBuffer(0));
+        }
+        request_message.clear(Message::RENDER);
+        parse_message->clear(Message::PARSE);
+    }
+
+    // Now check if the original buffer is intact
+    parse_message->clear(Message::PARSE);
+    InputBuffer ibuffer(orig_buffer->getData(), orig_buffer->getLength());
+    parse_message->fromWire(ibuffer);
+    headerCheck(*parse_message, default_qid, Rcode::SERVFAIL(),
+                opcode.getCode(), QR_FLAG, 1, 0, 0, 0);
+}
+
 }

+ 1 - 0
src/bin/auth/tests/command_unittest.cc

@@ -49,6 +49,7 @@ using namespace isc::dns;
 using namespace isc::data;
 using namespace isc::datasrc;
 using namespace isc::config;
+using namespace isc::testutils;
 
 namespace {
 class AuthCommandTest : public ::testing::Test {

+ 11 - 4
src/bin/auth/tests/config_unittest.cc

@@ -37,13 +37,13 @@ using namespace isc::dns;
 using namespace isc::data;
 using namespace isc::datasrc;
 using namespace isc::asiodns;
-using namespace isc::asiolink;
+using namespace isc::testutils;
 
 namespace {
 class AuthConfigTest : public ::testing::Test {
 protected:
     AuthConfigTest() :
-        dnss_(ios_, NULL, NULL, NULL),
+        dnss_(),
         rrclass(RRClass::IN()),
         server(true, xfrout),
         // The empty string is expected value of the parameter of
@@ -53,8 +53,7 @@ protected:
     {
         server.setDNSService(dnss_);
     }
-    IOService ios_;
-    DNSService dnss_;
+    MockDNSService dnss_;
     const RRClass rrclass;
     MockXfroutClient xfrout;
     AuthSrv server;
@@ -146,6 +145,14 @@ TEST_F(AuthConfigTest, invalidListenAddressConfig) {
 // Try setting addresses trough config
 TEST_F(AuthConfigTest, listenAddressConfig) {
     isc::testutils::portconfig::listenAddressConfig(server);
+
+    // listenAddressConfig should have attempted to create 4 DNS server
+    // objects: two IP addresses, TCP and UDP for each.  For UDP, the "SYNC_OK"
+    // option should have been specified.
+    EXPECT_EQ(2, dnss_.getTCPFdParams().size());
+    EXPECT_EQ(2, dnss_.getUDPFdParams().size());
+    EXPECT_EQ(DNSService::SERVER_SYNC_OK, dnss_.getUDPFdParams().at(0).options);
+    EXPECT_EQ(DNSService::SERVER_SYNC_OK, dnss_.getUDPFdParams().at(1).options);
 }
 
 class MemoryDatasrcConfigTest : public AuthConfigTest {

+ 2 - 0
src/bin/auth/tests/statistics_unittest.cc

@@ -281,6 +281,8 @@ TEST_F(AuthCountersTest, submitStatisticsWithoutValidator) {
                          ->get(0)->stringValue());
     EXPECT_EQ("Auth", statistics_session_.sent_msg->get("command")
                          ->get(1)->get("owner")->stringValue());
+    EXPECT_EQ(statistics_session_.sent_msg->get("command")
+              ->get(1)->get("pid")->intValue(), getpid());
     ConstElementPtr statistics_data = statistics_session_.sent_msg
                                           ->get("command")->get(1)
                                           ->get("data");

+ 6 - 6
src/bin/resolver/resolver.cc

@@ -92,7 +92,7 @@ public:
         queryShutdown();
     }
 
-    void querySetup(DNSService& dnss,
+    void querySetup(DNSServiceBase& dnss,
                     isc::nsas::NameserverAddressStore& nsas,
                     isc::cache::ResolverCache& cache)
     {
@@ -121,10 +121,10 @@ public:
     }
 
     void setForwardAddresses(const AddressList& upstream,
-        DNSService *dnss)
+                             DNSServiceBase* dnss)
     {
         upstream_ = upstream;
-        if (dnss) {
+        if (dnss != NULL) {
             if (!upstream_.empty()) {
                 BOOST_FOREACH(const AddressPair& address, upstream) {
                     LOG_INFO(resolver_logger, RESOLVER_FORWARD_ADDRESS)
@@ -137,10 +137,10 @@ public:
     }
 
     void setRootAddresses(const AddressList& upstream_root,
-                          DNSService *dnss)
+                          DNSServiceBase* dnss)
     {
         upstream_root_ = upstream_root;
-        if (dnss) {
+        if (dnss != NULL) {
             if (!upstream_root_.empty()) {
                 BOOST_FOREACH(const AddressPair& address, upstream_root) {
                     LOG_INFO(resolver_logger, RESOLVER_SET_ROOT_ADDRESS)
@@ -377,7 +377,7 @@ Resolver::~Resolver() {
 }
 
 void
-Resolver::setDNSService(isc::asiodns::DNSService& dnss) {
+Resolver::setDNSService(isc::asiodns::DNSServiceBase& dnss) {
     dnss_ = &dnss;
 }
 

+ 3 - 3
src/bin/resolver/resolver.h

@@ -104,7 +104,7 @@ public:
                                             bool startup = false);
 
     /// \brief Assign an ASIO IO Service queue to this Resolver object
-    void setDNSService(isc::asiodns::DNSService& dnss);
+    void setDNSService(isc::asiodns::DNSServiceBase& dnss);
 
     /// \brief Assign a NameserverAddressStore to this Resolver object
     void setNameserverAddressStore(isc::nsas::NameserverAddressStore &nsas);
@@ -113,7 +113,7 @@ public:
     void setCache(isc::cache::ResolverCache& cache);
 
     /// \brief Return this object's ASIO IO Service queue
-    isc::asiodns::DNSService& getDNSService() const { return (*dnss_); }
+    isc::asiodns::DNSServiceBase& getDNSService() const { return (*dnss_); }
 
     /// \brief Returns this object's NSAS
     isc::nsas::NameserverAddressStore& getNameserverAddressStore() const {
@@ -258,7 +258,7 @@ public:
 
 private:
     ResolverImpl* impl_;
-    isc::asiodns::DNSService* dnss_;
+    isc::asiodns::DNSServiceBase* dnss_;
     isc::asiolink::SimpleCallback* checkin_;
     isc::asiodns::DNSLookup* dns_lookup_;
     isc::asiodns::DNSAnswer* dns_answer_;

+ 11 - 3
src/bin/resolver/tests/resolver_config_unittest.cc

@@ -48,6 +48,7 @@
 
 #include <dns/tests/unittest_util.h>
 #include <testutils/srv_test.h>
+#include <testutils/mockups.h>
 #include <testutils/portconfig.h>
 #include <testutils/socket_request.h>
 
@@ -76,15 +77,13 @@ public:
 
 class ResolverConfig : public ::testing::Test {
 protected:
-    IOService ios;
-    DNSService dnss;
+    MockDNSService dnss;
     Resolver server;
     scoped_ptr<const IOEndpoint> endpoint;
     scoped_ptr<const IOMessage> query_message;
     scoped_ptr<const Client> client;
     scoped_ptr<const RequestContext> request;
     ResolverConfig() :
-        dnss(ios, NULL, NULL, NULL),
         // The empty string is expected value of the parameter of
         // requestSocket, not the app_name (there's no fallback, it checks
         // the empty string is passed).
@@ -320,6 +319,15 @@ TEST_F(ResolverConfig, invalidForwardAddresses) {
 // Try setting the addresses directly
 TEST_F(ResolverConfig, listenAddresses) {
     isc::testutils::portconfig::listenAddresses(server);
+
+    // listenAddressConfig should have attempted to create 4 DNS server
+    // objects: two IP addresses, TCP and UDP for each.  For UDP, the "SYNC_OK"
+    // option (or anything else) should have NOT been specified.
+    EXPECT_EQ(2, dnss.getTCPFdParams().size());
+    EXPECT_EQ(2, dnss.getUDPFdParams().size());
+    EXPECT_EQ(DNSService::SERVER_DEFAULT, dnss.getUDPFdParams().at(0).options);
+    EXPECT_EQ(DNSService::SERVER_DEFAULT, dnss.getUDPFdParams().at(1).options);
+
     // Check it requests the correct addresses
     const char* tokens[] = {
         "TCP:127.0.0.1:53210:1",

+ 6 - 10
src/bin/stats/b10-stats.8

@@ -59,23 +59,19 @@ command does not have any configurable settings\&.
 The configuration commands are:
 .PP
 
-
-\fBremove\fR
-removes the named statistics name and data\&.
-.PP
-
-
-\fBreset\fR
-will reset all statistics data to default values except for constant names\&. This may re\-add previously removed statistics names\&.
-.PP
-
 \fBset\fR
+will set new statistics data specified in arguments\&. Statistics data to be set and the module name which owns statistics data are required in argument\&. Pid of the module in argument is optional\&.
 .PP
 
 \fBshow\fR
 will send the statistics data in JSON format\&. By default, it outputs all the statistics data it has collected\&. An optional item name may be specified to receive individual output\&.
 .PP
 
+\fBshowschema\fR
+will send the schema of the statistics data in JSON format\&. The output is equivalent to the statistics part of
+stats\&.spec\&.
+.PP
+
 \fBshutdown\fR
 will shutdown the
 \fBb10\-stats\fR

+ 9 - 15
src/bin/stats/b10-stats.xml

@@ -101,20 +101,10 @@
     </para>
 
     <para>
-<!-- TODO: remove is removed in trac930 -->
-      <command>remove</command> removes the named statistics name and data.
-    </para>
-
-    <para>
-<!-- TODO: reset is removed in trac930 -->
-      <command>reset</command> will reset all statistics data to
-      default values except for constant names.
-      This may re-add previously removed statistics names.
-    </para>
-
-    <para>
-      <command>set</command>
-<!-- TODO: document this -->
+      <command>set</command> will set new statistics data specified in
+      arguments. Statistics data to be set and the module name which owns
+      statistics data are required in argument. Pid of the module in argument
+      is optional.
     </para>
 
     <para>
@@ -124,7 +114,11 @@
       An optional item name may be specified to receive individual output.
     </para>
 
-<!-- TODO: document showschema -->
+    <para>
+      <command>showschema</command> will send the schema of the statistics data
+      in JSON format. The output is equivalent to the statistics part
+      of <filename>stats.spec</filename>.
+    </para>
 
     <para>
       <command>shutdown</command> will shutdown the

+ 109 - 14
src/bin/stats/stats.py.in

@@ -129,6 +129,8 @@ class Stats:
         self.module_name = self.mccs.get_module_spec().get_module_name()
         self.modules = {}
         self.statistics_data = {}
+        # statistics data by each pid
+        self.statistics_data_bypid = {}
         # get commands spec
         self.commands_spec = self.mccs.get_module_spec().get_commands_spec()
         # add event handler related command_handler of ModuleCCSession
@@ -265,36 +267,129 @@ class Stats:
                          + "owner: " + str(owner) + ", "
                          + "name: " + str(name))
 
-    def update_statistics_data(self, owner=None, **data):
+    def update_statistics_data(self, owner=None, pid=-1, **data):
         """
         change statistics date of specified module into specified
         data. It updates information of each module first, and it
         updates statistics data. If specified data is invalid for
         statistics spec of specified owner, it returns a list of error
-        messeges. If there is no error or if neither owner nor data is
-        specified in args, it returns None.
+        messages. If there is no error or if neither owner nor data is
+        specified in args, it returns None. pid is the process id of
+        the sender module in order for stats to identify which
+        instance sends statistics data in the situation that multiple
+        instances are working.
         """
+        # Note:
+        # The fix of #1751 is for multiple instances working. It is
+        # assumed here that they send different statistics data with
+        # each PID. Stats should save their statistics data by
+        # PID. The statistics data, which is the existing variable, is
+        # preserved by accumlating from statistics data by PID. This
+        # is an ad-hoc fix because administrators can not see
+        # statistics by each instance via bindctl or HTTP/XML. These
+        # interfaces aren't changed in this fix.
+
+        def _accum_bymodule(statistics_data_bypid):
+            # This is an internal function for the superordinate
+            # function. It accumulates statistics data of each PID by
+            # module. It returns the accumulation result.
+            def _accum(a, b):
+                # If the first arg is dict or list type, two values
+                # would be merged and accumlated.
+                if type(a) is dict:
+                    return dict([ (k, _accum(v, b[k])) \
+                                      if k in b else (k, v) \
+                                      for (k, v) in a.items() ] \
+                                    + [ (k, v) \
+                                            for (k, v) in b.items() \
+                                            if k not in a ])
+                elif type(a) is list:
+                    return [ _accum(a[i], b[i]) \
+                                 if len(b) > i else a[i] \
+                                 for i in range(len(a)) ] \
+                                 + [ b[i] \
+                                         for i in range(len(b)) \
+                                         if len(a) <= i ]
+                # If the first arg is integer or float type, two
+                # values are just added.
+                elif type(a) is int or type(a) is float:
+                    return a + b
+                # If the first arg is str or other types than above,
+                # then it just returns the first arg which is assumed
+                # to be the newer value.
+                return a
+            ret = {}
+            for data in statistics_data_bypid.values():
+                ret.update(_accum(data, ret))
+            return ret
+
+        # Firstly, it gets default statistics data in each spec file.
         self.update_modules()
         statistics_data = {}
         for (name, module) in self.modules.items():
             value = get_spec_defaults(module.get_statistics_spec())
             if module.validate_statistics(True, value):
                 statistics_data[name] = value
-        for (name, value) in self.statistics_data.items():
-            if name in statistics_data:
-                statistics_data[name].update(value)
-            else:
-                statistics_data[name] = value
         self.statistics_data = statistics_data
+
+        # If the "owner" and "data" arguments in this function are
+        # specified, then the variable of statistics data of each pid
+        # would be updated.
+        errors = []
         if owner and data:
-            errors = []
             try:
                 if self.modules[owner].validate_statistics(False, data, errors):
-                    self.statistics_data[owner].update(data)
-                    return
+                    if owner in self.statistics_data_bypid:
+                        if pid in self.statistics_data_bypid[owner]:
+                            self.statistics_data_bypid[owner][pid].update(data)
+                        else:
+                            self.statistics_data_bypid[owner][pid] = data
+                    else:
+                        self.statistics_data_bypid[owner] = { pid : data }
             except KeyError:
                 errors.append("unknown module name: " + str(owner))
-            return errors
+
+        # If there are inactive instances, which was actually running
+        # on the system before, their statistics data would be
+        # removed. To find inactive instances, it invokes the
+        # "show_processes" command to Boss via the cc session. Then it
+        # gets active instance list and compares its PIDs with PIDs in
+        # statistics data which it already has. If inactive instances
+        # are found, it would remove their statistics data.
+        seq = self.cc_session.group_sendmsg(
+            isc.config.ccsession.create_command("show_processes", None),
+            "Boss")
+        (answer, env) = self.cc_session.group_recvmsg(False, seq)
+        if answer:
+            (rcode, value) = isc.config.ccsession.parse_answer(answer)
+            if rcode == 0:
+                if type(value) is list and len(value) > 0 \
+                        and type(value[0]) is list and len(value[0]) > 1:
+                    mlist = [ k for k in self.statistics_data_bypid.keys() ]
+                    for m in mlist:
+                        # PID list which it has before except for -1
+                        plist1 = [ p for p in self.statistics_data_bypid[m]\
+                                       .keys() if p != -1]
+                        # PID list of active instances which is
+                        # received from Boss
+                        plist2 = [ v[0] for v in value \
+                                       if v[1].lower().find(m.lower()) \
+                                       >= 0 ]
+                        # get inactive instance list by the difference
+                        # between plist1 and plist2
+                        nplist = set(plist1).difference(set(plist2))
+                        for p in nplist:
+                            self.statistics_data_bypid[m].pop(p)
+                        if self.statistics_data_bypid[m]:
+                            if m in self.statistics_data:
+                                self.statistics_data[m].update(
+                                    _accum_bymodule(
+                                        self.statistics_data_bypid[m]))
+                        # remove statistics data of the module with no
+                        # PID
+                        else:
+                            self.statistics_data_bypid.pop(m)
+        if errors: return errors
 
     def command_status(self):
         """
@@ -379,11 +474,11 @@ class Stats:
                 1, "specified arguments are incorrect: " \
                     + "owner: " + str(owner) + ", name: " + str(name))
 
-    def command_set(self, owner, data):
+    def command_set(self, owner, pid=-1, data={}):
         """
         handle set command
         """
-        errors = self.update_statistics_data(owner, **data)
+        errors = self.update_statistics_data(owner, pid, **data)
         if errors:
             return isc.config.create_answer(
                 1, "errors while setting statistics data: " \

+ 7 - 0
src/bin/stats/stats.spec

@@ -72,6 +72,13 @@
             "item_description": "module name of the owner of the statistics data"
           },
 	  {
+	    "item_name": "pid",
+            "item_type": "integer",
+            "item_optional": true,
+            "item_default": -1,
+            "item_description": "process id of the owner module"
+          },
+	  {
 	    "item_name": "data",
             "item_type": "map",
             "item_optional": false,

+ 179 - 0
src/bin/stats/tests/b10-stats_test.py

@@ -373,6 +373,68 @@ class TestStats(unittest.TestCase):
         self.assertEqual(self.stats.update_statistics_data(owner='Dummy', foo='bar'),
                          ['unknown module name: Dummy'])
 
+    def test_update_statistics_data_withpid(self):
+        # one pid of Auth
+        self.stats.update_statistics_data(owner='Auth',
+                                          pid=9999,
+                                          **{'queries.tcp':1001})
+        self.assertTrue('Auth' in self.stats.statistics_data)
+        self.assertTrue('queries.tcp' in self.stats.statistics_data['Auth'])
+        self.assertEqual(self.stats.statistics_data['Auth']['queries.tcp'], 1001)
+        self.assertTrue('Auth' in self.stats.statistics_data_bypid)
+        self.assertTrue(9999 in self.stats.statistics_data_bypid['Auth'])
+        self.assertTrue('queries.tcp' in self.stats.statistics_data_bypid['Auth'][9999])
+        self.assertEqual(self.stats.statistics_data_bypid['Auth'][9999]['queries.tcp'], 1001)
+        self.assertEqual(self.stats.statistics_data_bypid,
+                         {'Auth': {9999: {'queries.tcp': 1001}}})
+        # non-existent pid of Auth, but no changes in statistics data
+        self.stats.update_statistics_data(owner='Auth',
+                                          pid=10000,
+                                          **{'queries.tcp':2001})
+        self.assertTrue('Auth' in self.stats.statistics_data)
+        self.assertTrue('queries.tcp' in self.stats.statistics_data['Auth'])
+        self.assertEqual(self.stats.statistics_data['Auth']['queries.tcp'], 1001)
+        self.assertTrue('Auth' in self.stats.statistics_data_bypid)
+        self.assertTrue(9999 in self.stats.statistics_data_bypid['Auth'])
+        self.assertTrue('queries.tcp' in self.stats.statistics_data_bypid['Auth'][9999])
+        self.assertEqual(self.stats.statistics_data_bypid['Auth'][9999]['queries.tcp'], 1001)
+        self.assertEqual(self.stats.statistics_data_bypid,
+                         {'Auth': {9999: {'queries.tcp': 1001}}})
+        # kill running Auth, then statistics is reset
+        self.assertEqual(self.base.boss.server.pid_list[0][0], 9999)
+        killed = self.base.boss.server.pid_list.pop(0)
+        self.stats.update_statistics_data()
+        self.assertTrue('Auth' in self.stats.statistics_data)
+        self.assertTrue('queries.tcp' in self.stats.statistics_data['Auth'])
+        self.assertTrue('queries.udp' in self.stats.statistics_data['Auth'])
+        self.assertEqual(self.stats.statistics_data['Auth']['queries.tcp'], 0)
+        self.assertEqual(self.stats.statistics_data['Auth']['queries.udp'], 0)
+        self.assertFalse('Auth' in self.stats.statistics_data_bypid)
+        # restore statistics data of killed auth
+        self.base.boss.server.pid_list = [ killed ] + self.base.boss.server.pid_list[:]
+        self.stats.update_statistics_data(owner='Auth',
+                                          pid=9999,
+                                          **{'queries.tcp':1001})
+        # another pid of Auth
+        self.stats.update_statistics_data(owner='Auth',
+                                          pid=9998,
+                                          **{'queries.tcp':1002,
+                                             'queries.udp':1003})
+        self.assertTrue('Auth' in self.stats.statistics_data)
+        self.assertTrue('queries.tcp' in self.stats.statistics_data['Auth'])
+        self.assertTrue('queries.udp' in self.stats.statistics_data['Auth'])
+        self.assertEqual(self.stats.statistics_data['Auth']['queries.tcp'], 2003)
+        self.assertEqual(self.stats.statistics_data['Auth']['queries.udp'], 1003)
+        self.assertTrue('Auth' in self.stats.statistics_data_bypid)
+        self.assertTrue(9999 in self.stats.statistics_data_bypid['Auth'])
+        self.assertTrue(9998 in self.stats.statistics_data_bypid['Auth'])
+        self.assertTrue('queries.tcp' in self.stats.statistics_data_bypid['Auth'][9999])
+        self.assertTrue('queries.udp' in self.stats.statistics_data_bypid['Auth'][9998])
+        self.assertTrue('queries.udp' in self.stats.statistics_data_bypid['Auth'][9998])
+        self.assertEqual(self.stats.statistics_data_bypid['Auth'][9999]['queries.tcp'], 1001)
+        self.assertEqual(self.stats.statistics_data_bypid['Auth'][9998]['queries.tcp'], 1002)
+        self.assertEqual(self.stats.statistics_data_bypid['Auth'][9998]['queries.udp'], 1003)
+
     def test_commands(self):
         # status
         self.assertEqual(self.stats.command_status(),
@@ -710,6 +772,123 @@ class TestStats(unittest.TestCase):
         self.assertRaises(stats.StatsError,
                           self.stats.command_set, owner='Stats', data={ 'dummy' : '_xxxx_yyyy_zzz_' })
 
+    def test_command_set_withpid(self):
+        # one pid of Auth
+        retval = isc.config.ccsession.parse_answer(
+            self.stats.command_set(owner='Auth',
+                                   pid=9997,
+                                   data={ 'queries.tcp' : 1001,
+                                          'queries.perzone':
+                                              [{ 'zonename': 'test1.example',
+                                                 'queries.tcp': 1 },
+                                               { 'zonename': 'test2.example',
+                                                 'queries.tcp': 2,
+                                                 'queries.udp': 3 }]}))
+        self.assertEqual(retval, (0,None))
+        self.assertTrue('Auth' in self.stats.statistics_data)
+        self.assertTrue('queries.tcp' in self.stats.statistics_data['Auth'])
+        self.assertEqual(self.stats.statistics_data['Auth']['queries.tcp'], 1001)
+        self.assertEqual(self.stats.statistics_data['Auth']['queries.perzone'],
+                         [{ 'zonename': 'test1.example',
+                            'queries.tcp': 1 },
+                          { 'zonename': 'test2.example',
+                            'queries.tcp': 2,
+                            'queries.udp': 3 }])
+        self.assertTrue('Stats' in self.stats.statistics_data)
+        self.assertTrue('last_update_time' in self.stats.statistics_data['Stats'])
+        self.assertTrue('Auth' in self.stats.statistics_data_bypid)
+        self.assertTrue(9997 in self.stats.statistics_data_bypid['Auth'])
+        self.assertTrue('queries.tcp' in self.stats.statistics_data_bypid['Auth'][9997])
+        self.assertTrue('queries.perzone' in self.stats.statistics_data_bypid['Auth'][9997])
+        self.assertEqual(self.stats.statistics_data_bypid['Auth'][9997]['queries.tcp'], 1001)
+        self.assertEqual(self.stats.statistics_data_bypid['Auth'][9997]['queries.perzone'],
+                         [{ 'zonename': 'test1.example',
+                            'queries.tcp': 1 },
+                          { 'zonename': 'test2.example',
+                            'queries.tcp': 2,
+                            'queries.udp': 3 }])
+        # non-existent pid of Auth, but no changes in statistics data
+        retval = isc.config.ccsession.parse_answer(
+            self.stats.command_set(owner='Auth',
+                                   pid=10000,
+                                   data={ 'queries.tcp' : 2001,
+                                          'queries.perzone':
+                                              [{ 'zonename': 'test1.example',
+                                                 'queries.tcp': 101 },
+                                               { 'zonename': 'test2.example',
+                                                 'queries.tcp': 102,
+                                                 'queries.udp': 103 }]}))
+        self.assertEqual(retval, (0,None))
+        self.assertTrue('Auth' in self.stats.statistics_data)
+        self.assertTrue('queries.tcp' in self.stats.statistics_data['Auth'])
+        self.assertEqual(self.stats.statistics_data['Auth']['queries.tcp'], 1001)
+        self.assertEqual(self.stats.statistics_data['Auth']['queries.perzone'],
+                         [{ 'zonename': 'test1.example',
+                            'queries.tcp': 1 },
+                          { 'zonename': 'test2.example',
+                            'queries.tcp': 2,
+                            'queries.udp': 3 }])
+        self.assertTrue('Auth' in self.stats.statistics_data_bypid)
+        self.assertTrue(9997 in self.stats.statistics_data_bypid['Auth'])
+        self.assertTrue('queries.tcp' in self.stats.statistics_data_bypid['Auth'][9997])
+        self.assertEqual(self.stats.statistics_data_bypid['Auth'][9997]['queries.tcp'], 1001)
+        self.assertEqual(self.stats.statistics_data_bypid['Auth'][9997]['queries.perzone'],
+                         [{ 'zonename': 'test1.example',
+                            'queries.tcp': 1 },
+                          { 'zonename': 'test2.example',
+                            'queries.tcp': 2,
+                            'queries.udp': 3 }])
+        # another pid of Auth
+        retval = isc.config.ccsession.parse_answer(
+            self.stats.command_set(owner='Auth',
+                                   pid=9996,
+                                   data={ 'queries.tcp' : 1002,
+                                          'queries.udp' : 1003,
+                                          'queries.perzone':
+                                              [{ 'zonename': 'test1.example',
+                                                 'queries.tcp': 10,
+                                                 'queries.udp': 11},
+                                               { 'zonename': 'test2.example',
+                                                 'queries.tcp': 12,
+                                                 'queries.udp': 13 }]}))
+        self.assertEqual(retval, (0,None))
+        self.assertTrue('Auth' in self.stats.statistics_data)
+        self.assertTrue('queries.tcp' in self.stats.statistics_data['Auth'])
+        self.assertTrue('queries.udp' in self.stats.statistics_data['Auth'])
+        self.assertTrue('queries.perzone' in self.stats.statistics_data['Auth'])
+        self.assertEqual(self.stats.statistics_data['Auth']['queries.tcp'], 2003)
+        self.assertEqual(self.stats.statistics_data['Auth']['queries.udp'], 1003)
+        self.assertEqual(self.stats.statistics_data['Auth']['queries.perzone'],
+                         [{ 'zonename': 'test1.example',
+                            'queries.tcp': 11,
+                            'queries.udp': 11},
+                          { 'zonename': 'test2.example',
+                            'queries.tcp': 14,
+                            'queries.udp': 16 }])
+        self.assertTrue('Auth' in self.stats.statistics_data_bypid)
+        self.assertTrue(9997 in self.stats.statistics_data_bypid['Auth'])
+        self.assertTrue(9996 in self.stats.statistics_data_bypid['Auth'])
+        self.assertTrue('queries.tcp' in self.stats.statistics_data_bypid['Auth'][9997])
+        self.assertTrue('queries.udp' in self.stats.statistics_data_bypid['Auth'][9996])
+        self.assertTrue('queries.udp' in self.stats.statistics_data_bypid['Auth'][9996])
+        self.assertTrue('queries.perzone' in self.stats.statistics_data_bypid['Auth'][9996])
+        self.assertEqual(self.stats.statistics_data_bypid['Auth'][9997]['queries.tcp'], 1001)
+        self.assertEqual(self.stats.statistics_data_bypid['Auth'][9997]['queries.perzone'],
+                         [{ 'zonename': 'test1.example',
+                            'queries.tcp': 1 },
+                          { 'zonename': 'test2.example',
+                            'queries.tcp': 2,
+                            'queries.udp': 3 }])
+        self.assertEqual(self.stats.statistics_data_bypid['Auth'][9996]['queries.tcp'], 1002)
+        self.assertEqual(self.stats.statistics_data_bypid['Auth'][9996]['queries.udp'], 1003)
+        self.assertEqual(self.stats.statistics_data_bypid['Auth'][9996]['queries.perzone'],
+                         [{ 'zonename': 'test1.example',
+                            'queries.tcp': 10,
+                            'queries.udp': 11},
+                          { 'zonename': 'test2.example',
+                            'queries.tcp': 12,
+                            'queries.udp': 13 }])
+
 class TestOSEnv(unittest.TestCase):
     def test_osenv(self):
         """

+ 14 - 1
src/bin/stats/tests/test_utils.py

@@ -150,6 +150,11 @@ class MockBoss:
         "command_name": "sendstats",
         "command_description": "Send data to a statistics module at once",
         "command_args": []
+      },
+      {
+        "command_name": "show_processes",
+        "command_description": "List the running BIND 10 processes",
+        "command_args": []
       }
     ],
     "statistics": [
@@ -180,6 +185,10 @@ class MockBoss:
         self.spec_file.close()
         self.cc_session = self.mccs._session
         self.got_command_name = ''
+        self.pid_list = [[ 9999, "b10-auth"   ],
+                         [ 9998, "b10-auth-2" ],
+                         [ 9997, "b10-auth-3" ],
+                         [ 9996, "b10-auth-4" ]]
 
     def run(self):
         self.mccs.start()
@@ -210,6 +219,10 @@ class MockBoss:
             return isc.config.create_answer(0)
         elif command == 'getstats':
             return isc.config.create_answer(0, params)
+        elif command == 'show_processes':
+            # Return dummy pids
+            return isc.config.create_answer(
+                0, self.pid_list)
         return isc.config.create_answer(1, "Unknown Command")
 
 class MockAuth:
@@ -340,7 +353,7 @@ class MockAuth:
             params = { "owner": "Auth",
                        "data": { 'queries.tcp': self.queries_tcp,
                                  'queries.udp': self.queries_udp,
-                                 'queries.per-zone' : self.queries_per_zone } }
+                                 'queries.perzone' : self.queries_per_zone } }
             return send_command("set", "Stats", params=params, session=self.cc_session)
         return isc.config.create_answer(1, "Unknown Command")
 

+ 49 - 58
src/lib/asiodns/dns_service.cc

@@ -22,6 +22,8 @@
 
 #include <log/dummylog.h>
 
+#include <exceptions/exceptions.h>
+
 #include <asio.hpp>
 #include <dns_service.h>
 #include <asiolink/io_service.h>
@@ -63,11 +65,10 @@ convertAddr(const std::string& address) {
 class DNSServiceImpl {
 public:
     DNSServiceImpl(IOService& io_service, const char& port,
-                  const asio::ip::address* v4addr,
-                  const asio::ip::address* v6addr,
-                  SimpleCallback* checkin, DNSLookup* lookup,
-                  DNSAnswer* answer,
-                  const UDPVersion param_flags);
+                   const asio::ip::address* v4addr,
+                   const asio::ip::address* v6addr,
+                   SimpleCallback* checkin, DNSLookup* lookup,
+                   DNSAnswer* answe);
 
     IOService& io_service_;
 
@@ -87,38 +88,24 @@ public:
         servers_.push_back(server);
     }
 
-    void addServer(uint16_t port, const asio::ip::address& address,
-                   const UDPVersion param_flags) {
+    void addServer(uint16_t port, const asio::ip::address& address) {
         try {
-            dlog(std::string("Initialize TCP server at ") + address.to_string() + ":" + boost::lexical_cast<std::string>(port));
+            dlog(std::string("Initialize TCP server at ") +
+                 address.to_string() + ":" +
+                 boost::lexical_cast<std::string>(port));
             TCPServerPtr tcpServer(new TCPServer(io_service_.get_io_service(),
-                address, port, checkin_, lookup_, answer_));
+                                                 address, port, checkin_,
+                                                 lookup_, answer_));
             (*tcpServer)();
             servers_.push_back(tcpServer);
-            dlog(std::string("Initialize UDP server at ") + address.to_string() + ":" + boost::lexical_cast<std::string>(port));
-            // Use param_flags to generate diff UDPServers.    
-            switch(param_flags) {
-                case SYNC_: {
-                    SyncUDPServerPtr syncUdpServer(new SyncUDPServer(io_service_.get_io_service(),
-                                                   address, port, checkin_, lookup_, answer_));
-                    (*syncUdpServer)();
-                    servers_.push_back(syncUdpServer);
-                    break;
-                }
-                case ASYNC_: {
-                    UDPServerPtr udpServer(new UDPServer(io_service_.get_io_service(),
-                                           address, port, checkin_, lookup_, answer_));
-                    (*udpServer)();
-                    servers_.push_back(udpServer);
-                    break;
-                }
-                default:
-                    // If nerther asyn UDPServer nor sync UDNServer, it throws.
-                    isc_throw(IOError, "Bad UDPServer Version!");
-                    break;
-            }
-        }
-        catch (const asio::system_error& err) {
+            dlog(std::string("Initialize UDP server at ") +
+                 address.to_string() + ":" +
+                 boost::lexical_cast<std::string>(port));
+            UDPServerPtr udpServer(new UDPServer(io_service_.get_io_service(),
+                address, port, checkin_, lookup_, answer_));
+            (*udpServer)();
+            servers_.push_back(udpServer);
+        } catch (const asio::system_error& err) {
             // We need to catch and convert any ASIO level exceptions.
             // This can happen for unavailable address, binding a privilege port
             // without the privilege, etc.
@@ -126,8 +113,7 @@ public:
                       err.what());
         }
     }
-    void addServer(const char& port, const asio::ip::address& address,
-                   const UDPVersion param_flags) {
+    void addServer(const char& port, const asio::ip::address& address) {
         uint16_t portnum;
         try {
             // XXX: SunStudio with stlport4 doesn't reject some invalid
@@ -143,7 +129,7 @@ public:
             isc_throw(IOError, "Invalid port number '" << &port << "': " <<
                       ex.what());
         }
-        addServer(portnum, address,param_flags);
+        addServer(portnum, address);
     }
 };
 
@@ -153,8 +139,7 @@ DNSServiceImpl::DNSServiceImpl(IOService& io_service,
                                const asio::ip::address* const v6addr,
                                SimpleCallback* checkin,
                                DNSLookup* lookup,
-                               DNSAnswer* answer,
-                               const UDPVersion param_flags):
+                               DNSAnswer* answer) :
     io_service_(io_service),
     checkin_(checkin),
     lookup_(lookup),
@@ -162,10 +147,10 @@ DNSServiceImpl::DNSServiceImpl(IOService& io_service,
 {
 
     if (v4addr) {
-        addServer(port, *v4addr,param_flags);
+        addServer(port, *v4addr);
     }
     if (v6addr) {
-        addServer(port, *v6addr,param_flags);
+        addServer(port, *v6addr);
     }
 }
 
@@ -173,12 +158,12 @@ DNSService::DNSService(IOService& io_service,
                        const char& port, const char& address,
                        SimpleCallback* checkin,
                        DNSLookup* lookup,
-                       DNSAnswer* answer,
-                       const UDPVersion param_flags) :
+                       DNSAnswer* answer) :
     impl_(new DNSServiceImpl(io_service, port, NULL, NULL, checkin, lookup,
-        answer,param_flags)), io_service_(io_service)
+                             answer)),
+    io_service_(io_service)
 {
-    addServer(port, &address,param_flags);
+    addServer(port, &address);
 }
 
 DNSService::DNSService(IOService& io_service,
@@ -186,8 +171,7 @@ DNSService::DNSService(IOService& io_service,
                        const bool use_ipv4, const bool use_ipv6,
                        SimpleCallback* checkin,
                        DNSLookup* lookup,
-                       DNSAnswer* answer,
-                       const UDPVersion param_flags) :
+                       DNSAnswer* answer) :
     impl_(NULL), io_service_(io_service)
 {
     const asio::ip::address v4addr_any =
@@ -196,13 +180,14 @@ DNSService::DNSService(IOService& io_service,
     const asio::ip::address v6addr_any =
         asio::ip::address(asio::ip::address_v6::any());
     const asio::ip::address* const v6addrp = use_ipv6 ? &v6addr_any : NULL;
-    impl_ = new DNSServiceImpl(io_service, port, v4addrp, v6addrp, checkin, lookup, answer,param_flags);
+    impl_ = new DNSServiceImpl(io_service, port, v4addrp, v6addrp, checkin,
+                               lookup, answer);
 }
 
 DNSService::DNSService(IOService& io_service, SimpleCallback* checkin,
-    DNSLookup* lookup, DNSAnswer *answer,const UDPVersion param_flags) :
+    DNSLookup* lookup, DNSAnswer *answer) :
     impl_(new DNSServiceImpl(io_service, *"0", NULL, NULL, checkin, lookup,
-        answer,param_flags)), io_service_(io_service)
+                             answer)), io_service_(io_service)
 {
 }
 
@@ -211,24 +196,30 @@ DNSService::~DNSService() {
 }
 
 void
-DNSService::addServer(const char& port, const std::string& address,UDPVersion param_flags) {
-    impl_->addServer(port, convertAddr(address),param_flags);
+DNSService::addServer(const char& port, const std::string& address) {
+    impl_->addServer(port, convertAddr(address));
 }
 
 void
-DNSService::addServer(uint16_t port, const std::string& address,UDPVersion param_flags) {
-    impl_->addServer(port, convertAddr(address),param_flags);
+DNSService::addServer(uint16_t port, const std::string& address) {
+    impl_->addServer(port, convertAddr(address));
 }
 
 void DNSService::addServerTCPFromFD(int fd, int af) {
     impl_->addServerFromFD<DNSServiceImpl::TCPServerPtr, TCPServer>(fd, af);
 }
 
-void DNSService::addServerUDPFromFD(int fd, int af,const UDPVersion param_flags) {
-    if(SYNC_ == param_flags) { 
-        impl_->addServerFromFD<DNSServiceImpl::SyncUDPServerPtr, SyncUDPServer>(fd, af);
-    } else if(ASYNC_ == param_flags) {
-        impl_->addServerFromFD<DNSServiceImpl::UDPServerPtr, UDPServer>(fd, af);
+void DNSService::addServerUDPFromFD(int fd, int af, ServerFlag options) {
+    if ((~SERVER_DEFINED_FLAGS & static_cast<unsigned int>(options)) != 0) {
+        isc_throw(isc::InvalidParameter, "Invalid DNS/UDP server option: "
+                  << options);
+    }
+    if ((options & SERVER_SYNC_OK) != 0) {
+        impl_->addServerFromFD<DNSServiceImpl::SyncUDPServerPtr,
+            SyncUDPServer>(fd, af);
+    } else {
+        impl_->addServerFromFD<DNSServiceImpl::UDPServerPtr, UDPServer>(
+            fd, af);
     }
 }
 

+ 90 - 23
src/lib/asiodns/dns_service.h

@@ -27,13 +27,64 @@ class DNSLookup;
 class DNSAnswer;
 class DNSServiceImpl;
 
-
-/// Codes for UDPServers used in addServer()method.
+/// \brief A base class for common \c DNSService interfaces.
+///
+/// This class is defined mainly for test code so it can use a faked/mock
+/// version of a derived class and test scenarios that would involve
+/// \c DNSService without actually instantiating the real service class.
 ///
-/// Note: the codes only used in how to create the UDPServers.
-enum UDPVersion {
-      SYNC_  = 1,     ///< used synchronous UDPServer
-      ASYNC_ = 2     ///< used asynchronous UDPServer
+/// It doesn't intend to be a customization for other purposes - we generally
+/// expect non test code only use \c DNSService directly.
+/// For this reason most of the detailed description are given in the
+/// \c DNSService class.  See that for further details of specific methods
+/// and class behaviors.
+class DNSServiceBase {
+protected:
+    /// \brief Default constructor.
+    ///
+    /// This is protected so this class couldn't be accidentally instantiated
+    /// directly, even if there were no pure virtual functions.
+    DNSServiceBase() {}
+
+public:
+    /// \brief Flags for optional server properties.
+    ///
+    /// The values of this enumerable type are intended to be used to specify
+    /// a particular property of the server created via the \c addServer
+    /// variants.  As we see need for more such properties, a compound
+    /// form of flags (i.e., a single value generated by bitwise OR'ed
+    /// multiple flag values) will be allowed.
+    ///
+    /// Note: the description is given here because it's used in the method
+    /// signature.  It essentially belongs to the derived \c DNSService
+    /// class.
+    enum ServerFlag {
+        SERVER_DEFAULT = 0, ///< The default flag (no particular property)
+        SERVER_SYNC_OK = 1 ///< The server can act in the "synchronous" mode.
+                           ///< In this mode, the client ensures that the
+                           ///< lookup provider always completes the query
+                           ///< process and it immediately releases the
+                           ///< ownership of the given buffer.  This allows
+                           ///< the server implementation to introduce some
+                           ///< optimization such as omitting unnecessary
+                           ///< operation or reusing internal resources.
+                           ///< Note that in functionality the non
+                           ///< "synchronous" mode is compatible with the
+                           ///< synchronous mode; it's up to the server
+                           ///< implementation whether it exploits the
+                           ///< information given by the client.
+    };
+
+public:
+    /// \brief The destructor.
+    virtual ~DNSServiceBase() {}
+
+    virtual void addServerTCPFromFD(int fd, int af) = 0;
+    virtual void addServerUDPFromFD(int fd, int af,
+                                    ServerFlag options = SERVER_DEFAULT) = 0;
+    virtual void clearServers() = 0;
+
+    virtual asiolink::IOService& getIOService() = 0;
 };
 
 /// \brief Handle DNS Queries
@@ -43,7 +94,7 @@ enum UDPVersion {
 /// logic that is shared between the authoritative and the recursive
 /// server implementations. As such, it handles asio, including config
 /// updates (through the 'Checkinprovider'), and listening sockets.
-class DNSService {
+class DNSService : public DNSServiceBase {
     ///
     /// \name Constructors and Destructor
     ///
@@ -54,6 +105,12 @@ private:
     DNSService(const DNSService& source);
     DNSService& operator=(const DNSService& source);
 
+private:
+    // Bit or'ed all defined \c ServerFlag values.  Used internally for
+    // compatibility check.  Note that this doesn't have to be used by
+    // applications, and doesn't have to be defined in the "base" class.
+    static const unsigned int SERVER_DEFINED_FLAGS = 1;
+
 public:
     /// \brief The constructor with a specific IP address and port on which
     /// the services listen on.
@@ -66,8 +123,8 @@ public:
     /// \param answer The answer provider (see \c DNSAnswer)
     DNSService(asiolink::IOService& io_service, const char& port,
                const char& address, isc::asiolink::SimpleCallback* checkin,
-               DNSLookup* lookup, DNSAnswer* answer,
-               const UDPVersion param_flags = SYNC_);
+               DNSLookup* lookup, DNSAnswer* answer);
+
     /// \brief The constructor with a specific port on which the services
     /// listen on.
     ///
@@ -85,23 +142,21 @@ public:
     DNSService(asiolink::IOService& io_service, const char& port,
                const bool use_ipv4, const bool use_ipv6,
                isc::asiolink::SimpleCallback* checkin, DNSLookup* lookup,
-               DNSAnswer* answer,
-               const UDPVersion param_flags = SYNC_);
+               DNSAnswer* answer);
     /// \brief The constructor without any servers.
     ///
     /// Use addServer() to add some servers.
-    DNSService(asiolink::IOService& io_service, isc::asiolink::SimpleCallback* checkin,
-               DNSLookup* lookup, DNSAnswer* answer,
-               const UDPVersion param_flags = SYNC_);
+    DNSService(asiolink::IOService& io_service,
+               isc::asiolink::SimpleCallback* checkin,
+               DNSLookup* lookup, DNSAnswer* answer);
+
     /// \brief The destructor.
-    ~DNSService();
+    virtual ~DNSService();
     //@}
 
     /// \brief Add another server to the service
-    void addServer(uint16_t port, const std::string &address,
-                   const UDPVersion param_flags = SYNC_);
-    void addServer(const char &port, const std::string &address,
-                   const UDPVersion param_flags = SYNC_);
+    void addServer(uint16_t port, const std::string &address);
+    void addServer(const char& port, const std::string& address);
 
     /// \brief Add another TCP server/listener to the service from already
     /// opened file descriptor
@@ -113,13 +168,17 @@ public:
     /// specific address) and is ready for listening to new connection
     /// requests but has not actually started listening.
     ///
+    /// At the moment, TCP servers don't support any optional properties;
+    /// so unlike the UDP version of the method it doesn't have an \c options
+    /// argument.
+    ///
     /// \param fd the file descriptor to be used.
     /// \param af the address family of the file descriptor. Must be either
     ///     AF_INET or AF_INET6.
     /// \throw isc::InvalidParameter if af is neither AF_INET nor AF_INET6.
     /// \throw isc::asiolink::IOError when a low-level error happens, like the
     ///     fd is not a valid descriptor or it can't be listened on.
-    void addServerTCPFromFD(int fd, int af);
+    virtual void addServerTCPFromFD(int fd, int af);
 
     /// \brief Add another UDP server to the service from already opened
     ///    file descriptor
@@ -133,10 +192,14 @@ public:
     /// \param fd the file descriptor to be used.
     /// \param af the address family of the file descriptor. Must be either
     ///     AF_INET or AF_INET6.
-    /// \throw isc::InvalidParameter if af is neither AF_INET nor AF_INET6.
+    /// \param options Optional properties of the server (see ServerFlag).
+    ///
+    /// \throw isc::InvalidParameter if af is neither AF_INET nor AF_INET6,
+    ///     or the given \c options include an unsupported or invalid value.
     /// \throw isc::asiolink::IOError when a low-level error happens, like the
     ///     fd is not a valid descriptor or it can't be listened on.
-    void addServerUDPFromFD(int fd, int af,const UDPVersion param_flags = ASYNC_);
+    virtual void addServerUDPFromFD(int fd, int af,
+                                    ServerFlag options = SERVER_DEFAULT);
 
     /// \brief Remove all servers from the service
     void clearServers();
@@ -152,7 +215,7 @@ public:
     /// \brief Return the IO Service Object
     ///
     /// \return IOService object for this DNS service.
-    asiolink::IOService& getIOService() { return (io_service_);}
+    virtual asiolink::IOService& getIOService() { return (io_service_);}
 
 private:
     DNSServiceImpl* impl_;
@@ -162,3 +225,7 @@ private:
 } // namespace asiodns
 } // namespace isc
 #endif // __ASIOLINK_DNS_SERVICE_H
+
+// Local Variables:
+// mode: c++
+// End:

+ 1 - 1
src/lib/asiodns/tests/Makefile.am

@@ -18,7 +18,7 @@ TESTS += run_unittests
 run_unittests_SOURCES  = run_unittests.cc
 run_unittests_SOURCES += $(top_srcdir)/src/lib/dns/tests/unittest_util.h
 run_unittests_SOURCES += $(top_srcdir)/src/lib/dns/tests/unittest_util.cc
-run_unittests_SOURCES += io_service_unittest.cc
+run_unittests_SOURCES += dns_service_unittest.cc
 run_unittests_SOURCES += dns_server_unittest.cc
 run_unittests_SOURCES += io_fetch_unittest.cc
 

+ 323 - 0
src/lib/asiodns/tests/dns_service_unittest.cc

@@ -0,0 +1,323 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+#include <gtest/gtest.h>
+
+#include <exceptions/exceptions.h>
+
+#include <asio.hpp>
+#include <asiolink/asiolink.h>
+#include <asiodns/asiodns.h>
+
+#include <boost/scoped_ptr.hpp>
+#include <boost/lexical_cast.hpp>
+
+#include <csignal>
+
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include <unistd.h>
+#include <netdb.h>
+
+using namespace isc::asiolink;
+using namespace isc::asiodns;
+using boost::scoped_ptr;
+using boost::lexical_cast;
+
+namespace {
+const char* const TEST_SERVER_PORT = "53535";
+const char* const TEST_CLIENT_PORT = "53536";
+const char* const TEST_IPV6_ADDR = "::1";
+const char* const TEST_IPV4_ADDR = "127.0.0.1";
+
+TEST(IOServiceTest, badPort) {
+    IOService io_service;
+    EXPECT_THROW(DNSService(io_service, *"65536", true, false, NULL, NULL, NULL), IOError);
+    EXPECT_THROW(DNSService(io_service, *"53210.0", true, false, NULL, NULL, NULL), IOError);
+    EXPECT_THROW(DNSService(io_service, *"-1", true, false, NULL, NULL, NULL), IOError);
+    EXPECT_THROW(DNSService(io_service, *"domain", true, false, NULL, NULL, NULL), IOError);
+}
+
+TEST(IOServiceTest, badAddress) {
+    IOService io_service;
+    EXPECT_THROW(DNSService(io_service, *TEST_SERVER_PORT, *"192.0.2.1.1", NULL, NULL, NULL), IOError);
+    EXPECT_THROW(DNSService(io_service, *TEST_SERVER_PORT, *"2001:db8:::1", NULL, NULL, NULL), IOError);
+    EXPECT_THROW(DNSService(io_service, *TEST_SERVER_PORT, *"localhost", NULL, NULL, NULL), IOError);
+}
+
+TEST(IOServiceTest, unavailableAddress) {
+    IOService io_service;
+    // These addresses should generally be unavailable as a valid local
+    // address, although there's no guarantee in theory.
+    EXPECT_THROW(DNSService(io_service, *TEST_SERVER_PORT, *"192.0.2.0", NULL, NULL, NULL), IOError);
+
+    // Some OSes would simply reject binding attempt for an AF_INET6 socket
+    // to an IPv4-mapped IPv6 address.  Even if those that allow it, since
+    // the corresponding IPv4 address is the same as the one used in the
+    // AF_INET socket case above, it should at least show the same result
+    // as the previous one.
+    EXPECT_THROW(DNSService(io_service, *TEST_SERVER_PORT, *"::ffff:192.0.2.0", NULL, NULL, NULL), IOError);
+}
+
+TEST(IOServiceTest, duplicateBind_v6) {
+    // In each sub test case, second attempt should fail due to duplicate bind
+    IOService io_service;
+
+    // IPv6, "any" address
+    DNSService* dns_service = new DNSService(io_service, *TEST_SERVER_PORT, false, true, NULL, NULL, NULL);
+    EXPECT_THROW(DNSService(io_service, *TEST_SERVER_PORT, false, true, NULL, NULL, NULL), IOError);
+    delete dns_service;
+
+}
+
+TEST(IOServiceTest, duplicateBind_v6_address) {
+    // In each sub test case, second attempt should fail due to duplicate bind
+    IOService io_service;
+
+    // IPv6, specific address
+    DNSService* dns_service = new DNSService(io_service, *TEST_SERVER_PORT, *TEST_IPV6_ADDR, NULL, NULL, NULL);
+    EXPECT_THROW(DNSService(io_service, *TEST_SERVER_PORT, *TEST_IPV6_ADDR, NULL, NULL, NULL), IOError);
+    delete dns_service;
+
+}
+
+TEST(IOServiceTest, duplicateBind_v4) {
+    // In each sub test case, second attempt should fail due to duplicate bind
+    IOService io_service;
+
+    // IPv4, "any" address
+    DNSService* dns_service = new DNSService(io_service, *TEST_SERVER_PORT, true, false, NULL, NULL, NULL);
+    EXPECT_THROW(DNSService(io_service, *TEST_SERVER_PORT, true, false, NULL, NULL, NULL), IOError);
+    delete dns_service;
+
+}
+
+TEST(IOServiceTest, duplicateBind_v4_address) {
+    // In each sub test case, second attempt should fail due to duplicate bind
+    IOService io_service;
+
+    // IPv4, specific address
+    DNSService* dns_service = new DNSService(io_service, *TEST_SERVER_PORT, *TEST_IPV4_ADDR, NULL, NULL, NULL);
+    EXPECT_THROW(DNSService(io_service, *TEST_SERVER_PORT, *TEST_IPV4_ADDR, NULL, NULL, NULL), IOError);
+    delete dns_service;
+}
+
+// Disabled because IPv4-mapped addresses don't seem to be working with
+// the IOService constructor
+TEST(IOServiceTest, DISABLED_IPv4MappedDuplicateBind) {
+    IOService io_service;
+    // Duplicate bind on IPv4-mapped IPv6 address
+    DNSService* dns_service = new DNSService(io_service, *TEST_SERVER_PORT, *"127.0.0.1", NULL, NULL, NULL);
+    EXPECT_THROW(DNSService(io_service, *TEST_SERVER_PORT, *"::ffff:127.0.0.1", NULL, NULL, NULL), IOError);
+    delete dns_service;
+
+    // XXX:
+    // Currently, this throws an "invalid argument" exception.  I have
+    // not been able to get IPv4-mapped addresses to work.
+    dns_service = new DNSService(io_service, *TEST_SERVER_PORT, *"::ffff:127.0.0.1", NULL, NULL, NULL);
+    EXPECT_THROW(DNSService(io_service, *TEST_SERVER_PORT, *"127.0.0.1", NULL, NULL, NULL), IOError);
+    delete dns_service;
+}
+
+// A simple lookup callback for DNS services.  It records the pointer value of
+// to given output buffer each time the callback is called (up to two times)
+// for the main tests.  At the end of the second callback it stops the server.
+// The sender of the data doesn't expect to get a response, so it simply
+// discards any received data.
+class TestLookup : public DNSLookup {
+public:
+    TestLookup(isc::util::OutputBuffer** b1, isc::util::OutputBuffer** b2,
+               IOService& io_service) :
+        first_buffer_(b1), second_buffer_(b2), io_service_(io_service)
+    {}
+    void operator()(const IOMessage&, isc::dns::MessagePtr,
+                    isc::dns::MessagePtr, isc::util::OutputBufferPtr buffer,
+                    DNSServer* server) const
+    {
+        server->resume(false);
+        if (*first_buffer_ == NULL) {
+            *first_buffer_ = buffer.get();
+        } else {
+            assert(*second_buffer_ == NULL);
+            *second_buffer_ = buffer.get();
+            server->stop();
+            io_service_.stop();
+        }
+    }
+    isc::util::OutputBuffer** first_buffer_;
+    isc::util::OutputBuffer** second_buffer_;
+    IOService& io_service_;
+};
+
+// A test fixture to check creation of UDP servers from a socket FD, changing
+// options.
+class UDPDNSServiceTest : public::testing::Test {
+private:
+    static const unsigned int IO_SERVICE_TIME_OUT = 5;
+
+protected:
+    UDPDNSServiceTest() :
+        first_buffer_(NULL), second_buffer_(NULL),
+        lookup(&first_buffer_, &second_buffer_, io_service),
+        dns_service(io_service, NULL, &lookup, NULL),
+        client_socket(io_service.get_io_service(), asio::ip::udp::v6()),
+        server_ep(asio::ip::address::from_string(TEST_IPV6_ADDR),
+                  lexical_cast<uint16_t>(TEST_SERVER_PORT)),
+        asio_service(io_service.get_io_service())
+    {
+        current_service = &io_service;
+        // Content shouldn't matter for the tests, but initialize anyway
+        memset(data, 1, sizeof(data));
+    }
+
+    ~UDPDNSServiceTest() {
+        current_service = NULL;
+    }
+
+    void runService() {
+        io_service_is_time_out = false;
+
+        // Send two UDP packets, which will be passed to the TestLookup
+        // callback.  They are not expected to be responded, so it simply
+        // closes the socket right after that.
+        client_socket.send_to(asio::buffer(data, sizeof(data)), server_ep);
+        client_socket.send_to(asio::buffer(data, sizeof(data)), server_ep);
+        client_socket.close();
+
+        // set a signal-based alarm to prevent the test from hanging up
+        // due to a bug.
+        void (*prev_handler)(int) =
+            std::signal(SIGALRM, UDPDNSServiceTest::stopIOService);
+        current_service = &io_service;
+        alarm(IO_SERVICE_TIME_OUT);
+        io_service.run();
+        io_service.get_io_service().reset();
+        //cancel scheduled alarm
+        alarm(0);
+        std::signal(SIGALRM, prev_handler);
+    }
+
+    // last resort service stopper by signal
+    static void stopIOService(int) {
+        io_service_is_time_out = true;
+        if (current_service != NULL) {
+            current_service->stop();
+        }
+    }
+
+    bool serverStopSucceed() const {
+        return (!io_service_is_time_out);
+    }
+
+    isc::util::OutputBuffer* first_buffer_;
+    isc::util::OutputBuffer* second_buffer_;
+    IOService io_service;
+    TestLookup lookup;
+    DNSService dns_service;
+private:
+    asio::ip::udp::socket client_socket;
+    const asio::ip::udp::endpoint server_ep;
+    char data[4];
+
+    // To access them in signal handle function, the following
+    // variables have to be static.
+    static IOService* current_service;
+    static bool io_service_is_time_out;
+
+    asio::io_service& asio_service;
+};
+
+// Need to define the non-const static members outside of the class
+// declaration
+IOService* UDPDNSServiceTest::current_service;
+bool UDPDNSServiceTest::io_service_is_time_out;
+
+// A helper socket FD creator for given address and port.  It's generally
+// expected to succeed; on failure it simply throws an exception to make
+// the test fail.
+int
+getSocketFD(int family, const char* const address, const char* const port) {
+    struct addrinfo hints, *res = NULL;
+    memset(&hints, 0, sizeof(hints));
+    hints.ai_family = family;
+    hints.ai_socktype = SOCK_DGRAM;
+    hints.ai_protocol = IPPROTO_UDP;
+    hints.ai_flags = AI_NUMERICHOST | AI_NUMERICSERV;
+    int s = -1;
+    int error = getaddrinfo(address, port, &hints, &res);
+    if (error == 0) {
+        // If getaddrinfo returns 0, res should be set to a non NULL valid
+        // pointer, but some variants of cppcheck reportedly complains about
+        // it, so we satisfy them here.
+        if (res != NULL) {
+            s = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
+            if (s >= 0) {
+                error = bind(s, res->ai_addr, res->ai_addrlen);
+            }
+            freeaddrinfo(res);
+        }
+    }
+    if (error != 0) {
+        if (s >= 0) {
+            close(s);
+        }
+        isc_throw(isc::Unexpected, "failed to open test socket");
+    }
+    return (s);
+}
+
+TEST_F(UDPDNSServiceTest, defaultUDPServerFromFD) {
+    // If no option is explicitly specified, an asynchronous server should be
+    // created.  So the two output buffers should be different.
+    dns_service.addServerUDPFromFD(getSocketFD(AF_INET6, TEST_IPV6_ADDR,
+                                               TEST_SERVER_PORT), AF_INET6);
+    runService();
+    EXPECT_TRUE(serverStopSucceed());
+    EXPECT_NE(first_buffer_, second_buffer_);
+}
+
+TEST_F(UDPDNSServiceTest, explicitDefaultUDPServerFromFD) {
+    // If "default" option is explicitly specified, the effect should be the
+    // same as the previous case.
+    dns_service.addServerUDPFromFD(getSocketFD(AF_INET6, TEST_IPV6_ADDR,
+                                               TEST_SERVER_PORT),
+                                   AF_INET6, DNSService::SERVER_DEFAULT);
+    runService();
+    EXPECT_TRUE(serverStopSucceed());
+    EXPECT_NE(first_buffer_, second_buffer_);
+}
+
+TEST_F(UDPDNSServiceTest, syncUDPServerFromFD) {
+    // If "SYNC_OK" option is specified, a synchronous server should be
+    // created.  It will reuse the output buffer, so the recorded two pointer
+    // should be identical.
+    dns_service.addServerUDPFromFD(getSocketFD(AF_INET6, TEST_IPV6_ADDR,
+                                               TEST_SERVER_PORT),
+                                   AF_INET6, DNSService::SERVER_SYNC_OK);
+    runService();
+    EXPECT_TRUE(serverStopSucceed());
+    EXPECT_EQ(first_buffer_, second_buffer_);
+}
+
+TEST_F(UDPDNSServiceTest, addUDPServerFromFDWithUnknownOption) {
+    // Use of undefined/incompatible options should result in an exception.
+    EXPECT_THROW(dns_service.addServerUDPFromFD(
+                     getSocketFD(AF_INET6, TEST_IPV6_ADDR, TEST_SERVER_PORT),
+                     AF_INET6, static_cast<DNSService::ServerFlag>(2)),
+                 isc::InvalidParameter);
+}
+
+} // unnamed namespace

+ 0 - 123
src/lib/asiodns/tests/io_service_unittest.cc

@@ -1,123 +0,0 @@
-// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-#include <config.h>
-#include <gtest/gtest.h>
-
-#include <asio.hpp>
-#include <asiolink/asiolink.h>
-#include <asiodns/asiodns.h>
-
-using namespace isc::asiolink;
-using namespace isc::asiodns;
-
-const char* const TEST_SERVER_PORT = "53535";
-const char* const TEST_CLIENT_PORT = "53536";
-const char* const TEST_IPV6_ADDR = "::1";
-const char* const TEST_IPV4_ADDR = "127.0.0.1";
-
-TEST(IOServiceTest, badPort) {
-    IOService io_service;
-    EXPECT_THROW(DNSService(io_service, *"65536", true, false, NULL, NULL, NULL), IOError);
-    EXPECT_THROW(DNSService(io_service, *"53210.0", true, false, NULL, NULL, NULL), IOError);
-    EXPECT_THROW(DNSService(io_service, *"-1", true, false, NULL, NULL, NULL), IOError);
-    EXPECT_THROW(DNSService(io_service, *"domain", true, false, NULL, NULL, NULL), IOError);
-}
-
-TEST(IOServiceTest, badAddress) {
-    IOService io_service;
-    EXPECT_THROW(DNSService(io_service, *TEST_SERVER_PORT, *"192.0.2.1.1", NULL, NULL, NULL), IOError);
-    EXPECT_THROW(DNSService(io_service, *TEST_SERVER_PORT, *"2001:db8:::1", NULL, NULL, NULL), IOError);
-    EXPECT_THROW(DNSService(io_service, *TEST_SERVER_PORT, *"localhost", NULL, NULL, NULL), IOError);
-}
-
-TEST(IOServiceTest, unavailableAddress) {
-    IOService io_service;
-    // These addresses should generally be unavailable as a valid local
-    // address, although there's no guarantee in theory.
-    EXPECT_THROW(DNSService(io_service, *TEST_SERVER_PORT, *"192.0.2.0", NULL, NULL, NULL), IOError);
-
-    // Some OSes would simply reject binding attempt for an AF_INET6 socket
-    // to an IPv4-mapped IPv6 address.  Even if those that allow it, since
-    // the corresponding IPv4 address is the same as the one used in the
-    // AF_INET socket case above, it should at least show the same result
-    // as the previous one.
-    EXPECT_THROW(DNSService(io_service, *TEST_SERVER_PORT, *"::ffff:192.0.2.0", NULL, NULL, NULL), IOError);
-}
-
-TEST(IOServiceTest, duplicateBind_v6) {
-    // In each sub test case, second attempt should fail due to duplicate bind
-    IOService io_service;
-
-    // IPv6, "any" address
-    DNSService* dns_service = new DNSService(io_service, *TEST_SERVER_PORT, false, true, NULL, NULL, NULL);
-    EXPECT_THROW(DNSService(io_service, *TEST_SERVER_PORT, false, true, NULL, NULL, NULL), IOError);
-    delete dns_service;
-
-}
-
-TEST(IOServiceTest, duplicateBind_v6_address) {
-    // In each sub test case, second attempt should fail due to duplicate bind
-    IOService io_service;
-
-    // IPv6, specific address
-    DNSService* dns_service = new DNSService(io_service, *TEST_SERVER_PORT, *TEST_IPV6_ADDR, NULL, NULL, NULL);
-    EXPECT_THROW(DNSService(io_service, *TEST_SERVER_PORT, *TEST_IPV6_ADDR, NULL, NULL, NULL), IOError);
-    delete dns_service;
-
-}
-
-TEST(IOServiceTest, duplicateBind_v4) {
-    // In each sub test case, second attempt should fail due to duplicate bind
-    IOService io_service;
-
-    // IPv4, "any" address
-    DNSService* dns_service = new DNSService(io_service, *TEST_SERVER_PORT, true, false, NULL, NULL, NULL);
-    EXPECT_THROW(DNSService(io_service, *TEST_SERVER_PORT, true, false, NULL, NULL, NULL), IOError);
-    delete dns_service;
-
-}
-
-TEST(IOServiceTest, duplicateBind_v4_address) {
-    // In each sub test case, second attempt should fail due to duplicate bind
-    IOService io_service;
-
-    // IPv4, specific address
-    DNSService* dns_service = new DNSService(io_service, *TEST_SERVER_PORT, *TEST_IPV4_ADDR, NULL, NULL, NULL);
-    EXPECT_THROW(DNSService(io_service, *TEST_SERVER_PORT, *TEST_IPV4_ADDR, NULL, NULL, NULL), IOError);
-    delete dns_service;
-}
-
-// Disabled because IPv4-mapped addresses don't seem to be working with
-// the IOService constructor
-TEST(IOServiceTest, DISABLED_IPv4MappedDuplicateBind) {
-    IOService io_service;
-    // Duplicate bind on IPv4-mapped IPv6 address
-    DNSService* dns_service = new DNSService(io_service, *TEST_SERVER_PORT, *"127.0.0.1", NULL, NULL, NULL);
-    EXPECT_THROW(DNSService(io_service, *TEST_SERVER_PORT, *"::ffff:127.0.0.1", NULL, NULL, NULL), IOError);
-    delete dns_service;
-
-    // XXX:
-    // Currently, this throws an "invalid argument" exception.  I have
-    // not been able to get IPv4-mapped addresses to work.
-    dns_service = new DNSService(io_service, *TEST_SERVER_PORT, *"::ffff:127.0.0.1", NULL, NULL, NULL);
-    EXPECT_THROW(DNSService(io_service, *TEST_SERVER_PORT, *"127.0.0.1", NULL, NULL, NULL), IOError);
-    delete dns_service;
-}
-
-TEST(IOServiceTest, BadUdpServerVersion) {
-    IOService io_service;
-    DNSService* dns_service = new DNSService(io_service, NULL, NULL, NULL);
-    EXPECT_THROW(dns_service->addServer(*TEST_SERVER_PORT, "127.0.0.1", UDPVersion(3)), IOError);
-}

+ 2 - 1
src/lib/datasrc/data_source.cc

@@ -384,7 +384,8 @@ doQueryTask(QueryTask& task, ZoneInfo& zoneinfo, RRsetList& target) {
     const Name* const zonename = zoneinfo.getEnclosingZone();
     if (ds == NULL) {
         task.flags |= DataSrc::NO_SUCH_ZONE;
-        logger.info(DATASRC_QUERY_NO_ZONE).arg(task.qname).arg(task.qclass);
+        LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_QUERY_NO_ZONE).
+            arg(task.qname).arg(task.qclass);
         return (DataSrc::SUCCESS);
     }
 

+ 63 - 0
src/lib/datasrc/database.h

@@ -234,6 +234,41 @@ public:
                                           int id,
                                           bool subdomains = false) const = 0;
 
+    /// \brief Creates an iterator context for the records of NSEC3 namespace
+    ///     for the given hash
+    ///
+    /// Returns an Iteratorcontextptr that contains all the records of the given
+    /// hash in the NSEC3 namespace of the given zone.
+    ///
+    /// The implementation of the iterator that is returned may leave the
+    /// NAME_COLUMN column of the array passed to getNext() untouched,
+    /// as that name is easy to construct on the caller side (both the
+    /// hash and the name of the zone is known). The SIGTYPE_COLUMN can
+    /// be omitted as well, as it would be always empty for NSEC3 RRs or
+    /// contained "NSEC3" in case of RRSIG RRs.
+    ///
+    /// The iterator will contain both the NSEC3 records and the corresponding
+    /// RRSIGs, in arbitrary order.
+    ///
+    /// The iterator might be empty (containing no RRs) in case the zone is not
+    /// signed by NSEC3.
+    ///
+    /// \note In case there are multiple NSEC3 chains and they collide
+    ///     (unlikely, but it can happen), this can return multiple NSEC3
+    ///     records.
+    /// \exception any Since any implementaion can be used, the caller should
+    ///     expect any exception to be thrown.
+    /// \exception isc::NotImplemented in case the database does not support
+    ///     NSEC3
+    ///
+    /// \param hash The hash part of the NSEC3 name (eg. for a name of NSEC3
+    ///     RKBUCQT8T78GV6QBCGBHCHC019LG73SJ.example.com., we the hash would be
+    ///     RKBUCQT8T78GV6QBCGBHCHC019LG73SJ).
+    /// \param id The id of te zone, as returned from getZone().
+    /// \return Newly created iterator context. Must not be NULL.
+    virtual IteratorContextPtr getNSEC3Records(const std::string& hash,
+                                               int id) const = 0;
+
     /// \brief Creates an iterator context for the whole zone.
     ///
     /// Returns an IteratorContextPtr that contains all records of the
@@ -643,6 +678,34 @@ public:
     ///     apex of the zone).
     virtual std::string findPreviousName(int zone_id,
                                          const std::string& rname) const = 0;
+
+    /// \brief It returns the previous hash in the NSEC3 chain.
+    ///
+    /// This is used to find previous NSEC3 hashes, to find covering NSEC3 in
+    /// case none match exactly.
+    ///
+    /// In case a hash before before the lowest or the lowest is provided,
+    /// this should return the largest one in the zone (NSEC3 needs a
+    /// wrap-around semantics).
+    ///
+    /// \param zone_id Specifies the zone to look into, as returned by getZone.
+    /// \param hash The hash to look before.
+    /// \return The nearest smaller hash than the provided one, or the largest
+    ///     hash in the zone if something smaller or equal to the lowest one
+    ///     is provided.
+    /// \note If the zone contains multiple NSEC3 chains, you should check that
+    ///     the returned result contains the NSEC3 for correct parameters. If
+    ///     not, query again and get something smaller - this will eventually
+    ///     get to the correct one. This interface and semantics might change
+    ///     in future.
+    ///
+    /// \throw DataSourceError if there's a problem with the database or if
+    ///     this zone is not signed with NSEC3.
+    /// \throw NotImplemented if this database doesn't support NSEC3.
+    /// \throw anything else, as this might be any implementation.
+    virtual std::string findPreviousNSEC3Hash(int zone_id,
+                                              const std::string& hash)
+        const = 0;
 };
 
 /// \brief Concrete data source client oriented at database backends.

+ 4 - 2
src/lib/datasrc/datasrc_messages.mes

@@ -569,8 +569,10 @@ An attempt to add a NSEC3 record into the message failed, because the zone does
 not have any DS record. This indicates problem with the provided data.
 
 % DATASRC_QUERY_NO_ZONE no zone containing '%1' in class '%2'
-Lookup of domain failed because the data have no zone that contain the
-domain. Maybe someone sent a query to the wrong server for some reason.
+Debug information. Lookup of domain failed because the datasource
+has no zone that contains the domain. Maybe someone sent a query
+to the wrong server for some reason. This may also happen when
+looking in the datasource for addresses for NS records.
 
 % DATASRC_QUERY_PROCESS processing query '%1/%2' in the '%3' class
 Debug information. A sure query is being processed now.

+ 138 - 11
src/lib/datasrc/sqlite3_accessor.cc

@@ -54,7 +54,10 @@ enum StatementID {
     LOW_DIFF_ID = 13,
     HIGH_DIFF_ID = 14,
     DIFF_RECS = 15,
-    NUM_STATEMENTS = 16
+    NSEC3 = 16,
+    NSEC3_PREVIOUS = 17,
+    NSEC3_LAST = 18,
+    NUM_STATEMENTS = 19
 };
 
 const char* const text_statements[NUM_STATEMENTS] = {
@@ -83,7 +86,7 @@ const char* const text_statements[NUM_STATEMENTS] = {
      */
     "SELECT name FROM records " // FIND_PREVIOUS
         "WHERE zone_id=?1 AND rdtype = 'NSEC' AND "
-        "rname < $2 ORDER BY rname DESC LIMIT 1",
+        "rname < ?2 ORDER BY rname DESC LIMIT 1",
     "INSERT INTO diffs "        // ADD_RECORD_DIFF
         "(zone_id, version, operation, name, rrtype, ttl, rdata) "
         "VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)",
@@ -103,7 +106,21 @@ const char* const text_statements[NUM_STATEMENTS] = {
     // that the columns match the column IDs passed to the iterator
     "SELECT rrtype, ttl, id, rdata, name FROM diffs "   // DIFF_RECS
         "WHERE zone_id=?1 AND id>=?2 and id<=?3 "
-        "ORDER BY id ASC"
+        "ORDER BY id ASC",
+
+    // Query to get the NSEC3 records
+    //
+    // The "1" in SELECT is for positioning the rdata column to the
+    // expected position, so we can reuse the same code as for other
+    // lookups.
+    "SELECT rdtype, ttl, 1, rdata FROM nsec3 WHERE zone_id=?1 AND "
+        "hash=?2",
+    // For getting the previous NSEC3 hash
+    "SELECT DISTINCT hash FROM nsec3 WHERE zone_id=?1 AND hash < ?2 "
+        "ORDER BY hash DESC LIMIT 1",
+    // And for wrap-around
+    "SELECT DISTINCT hash FROM nsec3 WHERE zone_id=?1 "
+        "ORDER BY hash DESC LIMIT 1",
 };
 
 struct SQLite3Parameters {
@@ -482,19 +499,46 @@ public:
         bindZoneId(id);
     }
 
+    // What kind of query it is - selection of the statement for DB
+    enum QueryType {
+        QT_ANY, // Directly for a domain
+        QT_SUBDOMAINS, // Subdomains of a given domain
+        QT_NSEC3 // Domain in the NSEC3 namespace (the name is is the hash,
+                 // not the whole name)
+    };
+
     // Construct an iterator for records with a specific name. When constructed
     // this way, the getNext() call will copy all fields except name
     Context(const boost::shared_ptr<const SQLite3Accessor>& accessor, int id,
-            const std::string& name, bool subdomains) :
-        iterator_type_(ITT_NAME),
+            const std::string& name, QueryType qtype) :
+        iterator_type_(qtype == QT_NSEC3 ? ITT_NSEC3 : ITT_NAME),
         accessor_(accessor),
         statement_(NULL),
         name_(name)
     {
+        // Choose the statement text depending on the query type
+        const char* statement(NULL);
+        switch (qtype) {
+            case QT_ANY:
+                statement = text_statements[ANY];
+                break;
+            case QT_SUBDOMAINS:
+                statement = text_statements[ANY_SUB];
+                break;
+            case QT_NSEC3:
+                statement = text_statements[NSEC3];
+                break;
+            default:
+                // Can Not Happen - there isn't any other type of query
+                // and all the calls to the constructor are from this
+                // file. Therefore no way to test it throws :-(.
+                isc_throw(Unexpected,
+                          "Invalid qtype passed - unreachable code branch "
+                          "reached");
+        }
+
         // We create the statement now and then just keep getting data from it
-        statement_ = prepare(accessor->dbparameters_->db_,
-                             subdomains ? text_statements[ANY_SUB] :
-                             text_statements[ANY]);
+        statement_ = prepare(accessor->dbparameters_->db_, statement);
         bindZoneId(id);
         bindName(name_);
     }
@@ -511,7 +555,11 @@ public:
             // For both types, we copy the first four columns
             copyColumn(data, TYPE_COLUMN);
             copyColumn(data, TTL_COLUMN);
-            copyColumn(data, SIGTYPE_COLUMN);
+            // The NSEC3 lookup does not provide the SIGTYPE, it is not
+            // necessary and not contained in the table.
+            if (iterator_type_ != ITT_NSEC3) {
+                copyColumn(data, SIGTYPE_COLUMN);
+            }
             copyColumn(data, RDATA_COLUMN);
             // Only copy Name if we are iterating over every record
             if (iterator_type_ == ITT_ALL) {
@@ -537,7 +585,8 @@ private:
     // See description of getNext() and the constructors
     enum IteratorType {
         ITT_ALL,
-        ITT_NAME
+        ITT_NAME,
+        ITT_NSEC3
     };
 
     void copyColumn(std::string (&data)[COLUMN_COUNT], int column) {
@@ -584,7 +633,15 @@ SQLite3Accessor::getRecords(const std::string& name, int id,
                             bool subdomains) const
 {
     return (IteratorContextPtr(new Context(shared_from_this(), id, name,
-                                           subdomains)));
+                                           subdomains ?
+                                           Context::QT_SUBDOMAINS :
+                                           Context::QT_ANY)));
+}
+
+DatabaseAccessor::IteratorContextPtr
+SQLite3Accessor::getNSEC3Records(const std::string& hash, int id) const {
+    return (IteratorContextPtr(new Context(shared_from_this(), id, hash,
+                                           Context::QT_NSEC3)));
 }
 
 DatabaseAccessor::IteratorContextPtr
@@ -1092,5 +1149,75 @@ SQLite3Accessor::findPreviousName(int zone_id, const std::string& rname)
     return (result);
 }
 
+std::string
+SQLite3Accessor::findPreviousNSEC3Hash(int zone_id, const std::string& hash)
+    const
+{
+    sqlite3_stmt* const stmt = dbparameters_->getStatement(NSEC3_PREVIOUS);
+    sqlite3_reset(stmt);
+    sqlite3_clear_bindings(stmt);
+
+    if (sqlite3_bind_int(stmt, 1, zone_id) != SQLITE_OK) {
+        isc_throw(SQLite3Error, "Could not bind zone ID " << zone_id <<
+                  " to SQL statement (find previous NSEC3): " <<
+                  sqlite3_errmsg(dbparameters_->db_));
+    }
+    if (sqlite3_bind_text(stmt, 2, hash.c_str(), -1, SQLITE_STATIC) !=
+        SQLITE_OK) {
+        isc_throw(SQLite3Error, "Could not bind hash " << hash <<
+                  " to SQL statement (find previous NSEC3): " <<
+                  sqlite3_errmsg(dbparameters_->db_));
+    }
+
+    std::string result;
+    const int rc = sqlite3_step(stmt);
+    if (rc == SQLITE_ROW) {
+        // We found it
+        result = convertToPlainChar(sqlite3_column_text(stmt, 0),
+                                    dbparameters_->db_);
+    }
+    sqlite3_reset(stmt);
+
+    if (rc != SQLITE_ROW && rc != SQLITE_DONE) {
+        // Some kind of error
+        isc_throw(SQLite3Error, "Could not get data for previous hash");
+    }
+
+    if (rc == SQLITE_DONE) {
+        // No NSEC3 records before this hash. This means we should wrap
+        // around and take the last one.
+        sqlite3_stmt* const stmt = dbparameters_->getStatement(NSEC3_LAST);
+        sqlite3_reset(stmt);
+        sqlite3_clear_bindings(stmt);
+
+        if (sqlite3_bind_int(stmt, 1, zone_id) != SQLITE_OK) {
+            isc_throw(SQLite3Error, "Could not bind zone ID " << zone_id <<
+                      " to SQL statement (find last NSEC3): " <<
+                      sqlite3_errmsg(dbparameters_->db_));
+        }
+
+        const int rc = sqlite3_step(stmt);
+        if (rc == SQLITE_ROW) {
+            // We found it
+            result = convertToPlainChar(sqlite3_column_text(stmt, 0),
+                                        dbparameters_->db_);
+        }
+        sqlite3_reset(stmt);
+
+        if (rc != SQLITE_ROW && rc != SQLITE_DONE) {
+            // Some kind of error
+            isc_throw(SQLite3Error, "Could not get data for last hash");
+        }
+
+        if (rc == SQLITE_DONE) {
+            // No NSEC3 at all in the zone. Well, bad luck, but you should not
+            // have asked in the first place.
+            isc_throw(DataSourceError, "No NSEC3 in this zone");
+        }
+    }
+
+    return (result);
+}
+
 } // end of namespace datasrc
 } // end of namespace isc

+ 13 - 0
src/lib/datasrc/sqlite3_accessor.h

@@ -141,6 +141,14 @@ public:
                                           int id,
                                           bool subdomains = false) const;
 
+    /// \brief Look up NSEC3 records for the given hash
+    ///
+    /// This implements the getNSEC3Records of DatabaseAccessor.
+    ///
+    /// \todo Actually implement, currently throws NotImplemented.
+    virtual IteratorContextPtr getNSEC3Records(const std::string& hash,
+                                               int id) const;
+
     /** \brief Look up all resource records for a zone
      *
      * This implements the getRecords() method from DatabaseAccessor
@@ -231,6 +239,11 @@ public:
     virtual std::string findPreviousName(int zone_id, const std::string& rname)
         const;
 
+    /// \brief Conrete implemantion of the pure virtual method of
+    /// DatabaseAccessor
+    virtual std::string findPreviousNSEC3Hash(int zone_id,
+                                              const std::string& hash) const;
+
 private:
     /// \brief Private database data
     boost::scoped_ptr<SQLite3Parameters> dbparameters_;

+ 94 - 1
src/lib/datasrc/tests/database_unittest.cc

@@ -205,6 +205,13 @@ const char* const TEST_RECORDS[][5] = {
     {NULL, NULL, NULL, NULL, NULL},
 };
 
+// FIXME: Taken from a different test. Fill with proper data when creating a test.
+const char* TEST_NSEC3_RECORDS[][5] = {
+    {"1BB7SO0452U1QHL98UISNDD9218GELR5", "NSEC3", "3600", "", "1 0 10 FEEDABEE 4KLSVDE8KH8G95VU68R7AHBE1CPQN38J"},
+    {"1BB7SO0452U1QHL98UISNDD9218GELR5", "RRSIG", "3600", "", "NSEC3 5 4 7200 20100410172647 20100311172647 63192 example.org. gNIVj4T8t51fEU6kOPpvK7HOGBFZGbalN5ZK mInyrww6UWZsUNdw07ge6/U6HfG+/s61RZ/L is2M6yUWHyXbNbj/QqwqgadG5dhxTArfuR02 xP600x0fWX8LXzW4yLMdKVxGbzYT+vvGz71o 8gHSY5vYTtothcZQa4BMKhmGQEk="},
+    {NULL, NULL, NULL, NULL, NULL}
+};
+
 /*
  * An accessor with minimum implementation, keeping the original
  * "NotImplemented" methods.
@@ -251,11 +258,16 @@ public:
 
     virtual IteratorContextPtr getRecords(const std::string&, int, bool)
         const
-        {
+    {
         isc_throw(isc::NotImplemented,
                   "This database datasource can't be iterated");
     }
 
+    virtual IteratorContextPtr getNSEC3Records(const std::string&, int) const {
+        isc_throw(isc::NotImplemented, "This test database datasource won't "
+                  "give you any NSEC3. Ever. Ask someone else.");
+    }
+
     virtual IteratorContextPtr getAllRecords(int) const {
         isc_throw(isc::NotImplemented,
                   "This database datasource can't be iterated");
@@ -270,6 +282,11 @@ public:
         isc_throw(isc::NotImplemented,
                   "This data source doesn't support DNSSEC");
     }
+
+    virtual std::string findPreviousNSEC3Hash(int, const std::string&) const {
+        isc_throw(isc::NotImplemented,
+                  "This test database knows nothing about NSEC3 nor order");
+    }
 private:
     const std::string database_name_;
 
@@ -378,6 +395,28 @@ public:
     }
 
 private:
+    class DomainIterator : public IteratorContext {
+    public:
+        DomainIterator(const std::vector<std::vector<std::string> >& domain) :
+            domain_(domain),
+            position_(domain_.begin())
+        {}
+        virtual bool getNext(std::string (&columns)[COLUMN_COUNT]) {
+            if (position_ == domain_.end()) {
+                return (false);
+            } else {
+                for (size_t i(0); i < COLUMN_COUNT; ++ i) {
+                    columns[i] = (*position_)[i];
+                }
+                ++ position_;
+                return (true);
+            }
+        }
+    private:
+        const std::vector<std::vector<std::string> > domain_;
+        std::vector<std::vector<std::string> >::const_iterator position_;
+    };
+
     class MockNameIteratorContext : public IteratorContext {
     public:
         MockNameIteratorContext(const MockAccessor& mock_accessor, int zone_id,
@@ -610,6 +649,17 @@ public:
         }
     }
 
+    virtual IteratorContextPtr getNSEC3Records(const std::string& hash,
+                                               int) const
+    {
+        Domains::const_iterator it(nsec3_namespace_.find(hash));
+        if (it == nsec3_namespace_.end()) {
+            return (IteratorContextPtr(new EmptyIteratorContext()));
+        } else {
+            return (IteratorContextPtr(new DomainIterator(it->second)));
+        }
+    }
+
     virtual pair<bool, int> startUpdateZone(const std::string& zone_name,
                                             bool replace)
     {
@@ -747,6 +797,21 @@ public:
             isc_throw(isc::Unexpected, "Unknown zone ID");
         }
     }
+    virtual std::string findPreviousNSEC3Hash(int,
+                                              const std::string& hash) const
+    {
+        // TODO: Provide some broken data, but it is not known yet how broken
+        // they'll have to be.
+        Domains::const_iterator it(nsec3_namespace_.lower_bound(hash));
+        // We got just after the one we want
+        if (it == nsec3_namespace_.begin()) {
+            // Hmm, we got something really small. So we wrap around.
+            // This is one after the last, so after decreasing it we'll get
+            // the biggest.
+            it = nsec3_namespace_.end();
+        }
+        return ((--it)->first);
+    }
     virtual void addRecordDiff(int id, uint32_t serial,
                                DiffOperation operation,
                                const std::string (&data)[DIFF_PARAM_COUNT])
@@ -830,6 +895,9 @@ private:
     const Domains empty_records_master_;
     const Domains* empty_records_;
 
+    // The NSEC3 namespace. The above trick will be added once it is needed.
+    Domains nsec3_namespace_;
+
     // The journal data
     std::vector<JournalEntry> journal_entries_master_;
     std::vector<JournalEntry>* journal_entries_;
@@ -890,6 +958,20 @@ private:
         cur_name_.clear();
     }
 
+    // Works in a similar way to addCurName, but it is added to
+    // the NSEC3 namespace. You don't provide the full name, only
+    // the hash part.
+    void addCurHash(const std::string& hash) {
+        ASSERT_EQ(0, nsec3_namespace_.count(hash));
+        // Append the name to all of them
+        for (std::vector<std::vector<std::string> >::iterator
+             i = cur_name_.begin(); i != cur_name_.end(); ++ i) {
+            i->push_back(hash);
+        }
+        (*readonly_records_)[hash] = cur_name_;
+        cur_name_.clear();
+    }
+
     // Fills the database with zone data.
     // This method constructs a number of resource records (with addRecord),
     // which will all be added for one domain name to the fake database
@@ -912,6 +994,17 @@ private:
                       TEST_RECORDS[i][3], TEST_RECORDS[i][4]);
         }
         addCurName(prev_name);
+        prev_name = NULL;
+        for (int i = 0; TEST_NSEC3_RECORDS[i][0] != NULL; ++i) {
+            if (prev_name != NULL &&
+                strcmp(prev_name, TEST_NSEC3_RECORDS[i][0]) != 0) {
+                addCurHash(prev_name);
+            }
+            prev_name = TEST_NSEC3_RECORDS[i][0];
+            addRecord(TEST_NSEC3_RECORDS[i][1], TEST_NSEC3_RECORDS[i][2],
+                      TEST_NSEC3_RECORDS[i][3], TEST_NSEC3_RECORDS[i][4]);
+        }
+        addCurHash(prev_name);
     }
 };
 

+ 102 - 0
src/lib/datasrc/tests/sqlite3_accessor_unittest.cc

@@ -173,6 +173,108 @@ TEST_F(SQLite3AccessorTest, iterator) {
     EXPECT_FALSE(context->getNext(data));
 }
 
+// This tests getting NSEC3 records
+TEST_F(SQLite3AccessorTest, nsec3) {
+    const std::pair<bool, int>
+        zone_info(accessor->getZone("sql2.example.com."));
+    ASSERT_TRUE(zone_info.first);
+
+    DatabaseAccessor::IteratorContextPtr
+        context(accessor->getNSEC3Records("1BB7SO0452U1QHL98UISNDD9218GELR5",
+                                          zone_info.second));
+    // This relies on specific ordering in the DB. Is it OK?
+    // The name field is empty, as well as the sigtype one. This is OK, as
+    // both are not needed and the interface allows it.
+    checkRR(context, "", "7200", "NSEC3",
+            "1 0 10 FEEDABEE 4KLSVDE8KH8G95VU68R7AHBE1CPQN38J");
+    checkRR(context, "", "7200", "RRSIG",
+            "NSEC3 5 4 7200 20100410172647 20100311172647 63192 "
+            "sql2.example.com. gNIVj4T8t51fEU6kOPpvK7HOGBFZGbalN5ZK "
+            "mInyrww6UWZsUNdw07ge6/U6HfG+/s61RZ/L is2M6yUWHyXbNbj/"
+            "QqwqgadG5dhxTArfuR02 xP600x0fWX8LXzW4yLMdKVxGbzYT+vvGz71o "
+            "8gHSY5vYTtothcZQa4BMKhmGQEk=");
+
+    // And that's all
+    std::string data[DatabaseAccessor::COLUMN_COUNT];
+    EXPECT_FALSE(context->getNext(data));
+
+    // Calling again won't hurt
+    EXPECT_FALSE(context->getNext(data));
+
+    // This one should be empty ‒ no data here
+    context = accessor->getNSEC3Records("NO_SUCH_HASH", zone_info.second);
+    EXPECT_FALSE(context->getNext(data));
+    // Still nothing? ;-)
+    EXPECT_FALSE(context->getNext(data));
+}
+
+// This tests getting a previoeus hash in the NSEC3 namespace of a zone,
+// including a wrap-around and asking for a hash that does not exist in the.
+// zone at all.
+TEST_F(SQLite3AccessorTest, nsec3Previous) {
+    // Get the zone
+    const std::pair<bool, int>
+        zone_info(accessor->getZone("sql2.example.com."));
+    ASSERT_TRUE(zone_info.first);
+
+    std::string data[DatabaseAccessor::COLUMN_COUNT];
+
+    // Test a previous hash for something that is in the zone
+    // (ensuring it is really there)
+    DatabaseAccessor::IteratorContextPtr
+        context(accessor->getNSEC3Records("703OOGCKF8VEV1N7U64D1JG19URETN8N",
+                                          zone_info.second));
+    EXPECT_TRUE(context->getNext(data));
+    EXPECT_EQ("56IEQ664LHDAKVPE2FL179MSM3QAOFVC", accessor->
+              findPreviousNSEC3Hash(zone_info.second,
+                                    "703OOGCKF8VEV1N7U64D1JG19URETN8N"));
+
+    // Test a previous hash for something that is not in the
+    // zone
+    context = accessor->getNSEC3Records("702OOGCKF8VEV1N7U64D1JG19URETN8N",
+                                        zone_info.second);
+    EXPECT_FALSE(context->getNext(data));
+    EXPECT_EQ("56IEQ664LHDAKVPE2FL179MSM3QAOFVC", accessor->
+              findPreviousNSEC3Hash(zone_info.second,
+                                    "702OOGCKF8VEV1N7U64D1JG19URETN8N"));
+
+    // Search at the first item, should wrap around
+    context = accessor->getNSEC3Records("1BB7SO0452U1QHL98UISNDD9218GELR5",
+                                        zone_info.second);
+    EXPECT_TRUE(context->getNext(data));
+    EXPECT_EQ("RKBUCQT8T78GV6QBCGBHCHC019LG73SJ", accessor->
+              findPreviousNSEC3Hash(zone_info.second,
+                                    "1BB7SO0452U1QHL98UISNDD9218GELR5"));
+
+    // Search before the first item, should wrap around
+    context = accessor->getNSEC3Records("0BB7SO0452U1QHL98UISNDD9218GELR5",
+                                        zone_info.second);
+    EXPECT_FALSE(context->getNext(data));
+    EXPECT_EQ("RKBUCQT8T78GV6QBCGBHCHC019LG73SJ", accessor->
+              findPreviousNSEC3Hash(zone_info.second,
+                                    "0BB7SO0452U1QHL98UISNDD9218GELR5"));
+
+    // Search after the last item (should return the last one)
+    context = accessor->getNSEC3Records("RRBUCQT8T78GV6QBCGBHCHC019LG73SJ",
+                                        zone_info.second);
+    EXPECT_FALSE(context->getNext(data));
+    EXPECT_EQ("RKBUCQT8T78GV6QBCGBHCHC019LG73SJ", accessor->
+              findPreviousNSEC3Hash(zone_info.second,
+                                    "RRBUCQT8T78GV6QBCGBHCHC019LG73SJ"));
+}
+
+// Check it throws when we want a previous NSEC3 hash in an unsigned zone
+TEST_F(SQLite3AccessorTest, nsec3PreviousUnsigned) {
+    // This zone did not look signed in the test file.
+    const std::pair<bool, int>
+        unsigned_zone_info(accessor->getZone("example.com."));
+
+    EXPECT_THROW(accessor->
+                 findPreviousNSEC3Hash(unsigned_zone_info.second,
+                                       "0BB7SO0452U1QHL98UISNDD9218GELR5"),
+                 DataSourceError);
+}
+
 // This tests the difference iterator context
 
 // Test that at attempt to create a difference iterator for a serial number

+ 2 - 3
src/lib/nsas/hash.cc

@@ -99,9 +99,8 @@ Hash::Hash(uint32_t tablesize, uint32_t maxkeylen, bool randomise) :
 
     if (randomise) {
         init_value.curtime = time(NULL);
-    }
-    else {
-        init_value.seed = 0;
+    } else {
+        init_value.seed = 1;
     }
     srandom(init_value.seed);
 

+ 1 - 0
src/lib/resolve/Makefile.am

@@ -34,6 +34,7 @@ nodist_libresolve_la_SOURCES = resolve_messages.h resolve_messages.cc
 libresolve_la_LIBADD = $(top_builddir)/src/lib/dns/libdns++.la
 libresolve_la_LIBADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
 libresolve_la_LIBADD += $(top_builddir)/src/lib/log/liblog.la
+libresolve_la_LIBADD += $(top_builddir)/src/lib/asiodns/libasiodns.la
 
 # The message file should be in the distribution.
 EXTRA_DIST = resolve_messages.mes

+ 1 - 1
src/lib/resolve/recursive_query.cc

@@ -128,7 +128,7 @@ typedef std::vector<std::pair<std::string, uint16_t> > AddressVector;
 // mishandles this in its name mangling, and wouldn't compile.
 // We can probably use a typedef, but need to move it to a central
 // location and use it consistently.
-RecursiveQuery::RecursiveQuery(DNSService& dns_service,
+RecursiveQuery::RecursiveQuery(DNSServiceBase& dns_service,
     isc::nsas::NameserverAddressStore& nsas,
     isc::cache::ResolverCache& cache,
     const std::vector<std::pair<std::string, uint16_t> >& upstream,

+ 2 - 2
src/lib/resolve/recursive_query.h

@@ -87,7 +87,7 @@ public:
     /// \param lookup_timeout Timeout value for when we give up, in ms
     /// \param retries how many times we try again (0 means just send and
     ///     and return if it returs).
-    RecursiveQuery(DNSService& dns_service,
+    RecursiveQuery(DNSServiceBase& dns_service,
                    isc::nsas::NameserverAddressStore& nsas,
                    isc::cache::ResolverCache& cache,
                    const std::vector<std::pair<std::string, uint16_t> >&
@@ -178,7 +178,7 @@ public:
     void setTestServer(const std::string& address, uint16_t port);
 
 private:
-    DNSService& dns_service_;
+    DNSServiceBase& dns_service_;
     isc::nsas::NameserverAddressStore& nsas_;
     isc::cache::ResolverCache& cache_;
     boost::shared_ptr<std::vector<std::pair<std::string, uint16_t> > >

+ 15 - 21
src/lib/server_common/portconfig.cc

@@ -31,11 +31,6 @@ namespace isc {
 namespace server_common {
 namespace portconfig {
 
-// This flags disables pushing the sockets to the DNSService. It prevents
-// the clearServers() method to close the file descriptors we made up.
-// It is not presented in any header, but we use it from the tests anyway.
-bool test_mode(false);
-
 AddressList
 parseAddresses(isc::data::ConstElementPtr addresses,
                const std::string& elemName)
@@ -84,7 +79,9 @@ namespace {
 vector<string> current_sockets;
 
 void
-setAddresses(DNSService& service, const AddressList& addresses) {
+setAddresses(DNSServiceBase& service, const AddressList& addresses,
+             DNSService::ServerFlag server_options)
+{
     service.clearServers();
     BOOST_FOREACH(const string& token, current_sockets) {
         socketRequestor().releaseSocket(token);
@@ -99,35 +96,32 @@ setAddresses(DNSService& service, const AddressList& addresses) {
                                                 address.first, address.second,
                                                 SocketRequestor::SHARE_SAME));
         current_sockets.push_back(tcp.second);
-        if (!test_mode) {
-            service.addServerTCPFromFD(tcp.first, af);
-        }
+        service.addServerTCPFromFD(tcp.first, af);
         const SocketRequestor::SocketID
             udp(socketRequestor().requestSocket(SocketRequestor::UDP,
                                                 address.first, address.second,
                                                 SocketRequestor::SHARE_SAME));
         current_sockets.push_back(udp.second);
-        if (!test_mode) {
-            service.addServerUDPFromFD(udp.first, af);
-        }
+        service.addServerUDPFromFD(udp.first, af, server_options);
     }
 }
 
 }
 
 void
-installListenAddresses(const AddressList& newAddresses,
-                       AddressList& addressStore,
-                       isc::asiodns::DNSService& service)
+installListenAddresses(const AddressList& new_addresses,
+                       AddressList& address_store,
+                       DNSServiceBase& service,
+                       DNSService::ServerFlag server_options)
 {
     try {
         LOG_DEBUG(logger, DBG_TRACE_BASIC, SRVCOMM_SET_LISTEN);
-        BOOST_FOREACH(const AddressPair& addr, newAddresses) {
+        BOOST_FOREACH(const AddressPair& addr, new_addresses) {
             LOG_DEBUG(logger, DBG_TRACE_VALUES, SRVCOMM_ADDRESS_VALUE).
                 arg(addr.first).arg(addr.second);
         }
-        setAddresses(service, newAddresses);
-        addressStore = newAddresses;
+        setAddresses(service, new_addresses, server_options);
+        address_store = new_addresses;
     } catch (const SocketRequestor::NonFatalSocketError& e) {
         /*
          * If one of the addresses isn't set successfully, we will restore
@@ -144,14 +138,14 @@ installListenAddresses(const AddressList& newAddresses,
          */
         LOG_ERROR(logger, SRVCOMM_ADDRESS_FAIL).arg(e.what());
         try {
-            setAddresses(service, addressStore);
+            setAddresses(service, address_store, server_options);
         } catch (const SocketRequestor::NonFatalSocketError& e2) {
             LOG_FATAL(logger, SRVCOMM_ADDRESS_UNRECOVERABLE).arg(e2.what());
             // If we can't set the new ones, nor the old ones, at least
             // releasing everything should work. If it doesn't, there isn't
             // anything else we could do.
-            setAddresses(service, AddressList());
-            addressStore.clear();
+            setAddresses(service, AddressList(), server_options);
+            address_store.clear();
         }
         //Anyway the new configure has problem, we need to notify configure
         //manager the new configure doesn't work

+ 43 - 43
src/lib/server_common/portconfig.h

@@ -15,22 +15,15 @@
 #ifndef ISC_SERVER_COMMON_PORTCONFIG_H
 #define ISC_SERVER_COMMON_PORTCONFIG_H
 
+#include <cc/data.h>
+
+#include <asiodns/dns_service.h>
+
 #include <utility>
 #include <string>
 #include <stdint.h>
 #include <vector>
 
-#include <cc/data.h>
-
-/*
- * Some forward declarations.
- */
-namespace isc {
-namespace asiodns {
-class DNSService;
-}
-}
-
 namespace isc {
 namespace server_common {
 /**
@@ -88,42 +81,49 @@ AddressList
 parseAddresses(isc::data::ConstElementPtr addresses,
                const std::string& elemName);
 
-/**
- * \brief Changes current listening addresses and ports.
- *
- * Removes all sockets we currently listen on and starts listening on the
- * addresses and ports requested in newAddresses.
- *
- * If it fails to set up the new addresses, it attempts to roll back to the
- * previous addresses (but it still propagates the exception). If the rollback
- * fails as well, it doesn't abort the application (to allow reconfiguration),
- * but removes all the sockets it listened on. One of the exceptions is
- * propagated.
- *
- * The ports are requested from the socket creator through boss. Therefore
- * you need to initialize the SocketRequestor before using this function.
- *
- * \param newAddresses are the addresses you want to listen on.
- * \param addressStore is the place you store your current addresses. It is
- *     used when there's a need for rollback. The newAddresses are copied here
- *     when the change is successful.
- * \param dnsService is the DNSService object we use now. The requests from
- *     the new sockets are handled using this dnsService (and all current
- *     sockets on the service are closed first).
- * \throw asiolink::IOError when initialization or closing of socket fails.
- * \throw isc::server_common::SocketRequestor::Socket error when the
- *     boss/socket creator doesn't want to give us the socket.
- * \throw std::bad_alloc when allocation fails.
- * \throw isc::InvalidOperation when the function is called and the
- *     SocketRequestor isn't initialized yet.
- */
+/// \brief Changes current listening addresses and ports.
+///
+/// Removes all sockets we currently listen on and starts listening on the
+/// addresses and ports requested in new_addresses.
+///
+/// If it fails to set up the new addresses, it attempts to roll back to the
+/// previous addresses (but it still propagates the exception). If the rollback
+/// fails as well, it doesn't abort the application (to allow reconfiguration),
+/// but removes all the sockets it listened on. One of the exceptions is
+/// propagated.
+///
+/// The ports are requested from the socket creator through boss. Therefore
+/// you need to initialize the SocketRequestor before using this function.
+///
+/// \param new_addresses are the addresses you want to listen on.
+/// \param address_store is the place you store your current addresses. It is
+///     used when there's a need for rollback. The new_addresses are copied
+///     here when the change is successful.
+/// \param dns_service is the DNSService object we use now. The requests from
+///     the new sockets are handled using this dns_service (and all current
+///     sockets on the service are closed first).
+/// \param server_options specifies optional properties for the servers
+///        created via \c dns_service.
+///
+/// \throw asiolink::IOError when initialization or closing of socket fails.
+/// \throw isc::server_common::SocketRequestor::Socket error when the
+///     boss/socket creator doesn't want to give us the socket.
+/// \throw std::bad_alloc when allocation fails.
+/// \throw isc::InvalidOperation when the function is called and the
+///     SocketRequestor isn't initialized yet.
 void
-installListenAddresses(const AddressList& newAddresses,
-                       AddressList& addressStore,
-                       asiodns::DNSService& dnsService);
+installListenAddresses(const AddressList& new_addresses,
+                       AddressList& address_store,
+                       asiodns::DNSServiceBase& dns_service,
+                       asiodns::DNSService::ServerFlag server_options =
+                       asiodns::DNSService::SERVER_DEFAULT);
 
 }
 }
 }
 
 #endif
+
+// Local Variables:
+// mode: c++
+// End:

+ 3 - 3
src/lib/server_common/tests/portconfig_unittest.cc

@@ -14,6 +14,7 @@
 
 #include <server_common/portconfig.h>
 #include <testutils/socket_request.h>
+#include <testutils/mockups.h>
 
 #include <cc/data.h>
 #include <exceptions/exceptions.h>
@@ -30,6 +31,7 @@ using namespace isc;
 using namespace std;
 using namespace isc::asiolink;
 using namespace isc::asiodns;
+using namespace isc::testutils;
 using boost::lexical_cast;
 
 namespace {
@@ -132,7 +134,6 @@ TEST_F(ParseAddresses, invalid) {
 // Test fixture for installListenAddresses
 struct InstallListenAddresses : public ::testing::Test {
     InstallListenAddresses() :
-        dnss_(ios_, NULL, NULL, NULL),
         // The empty string is expected parameter of requestSocket,
         // not app_name - the request does not fall back to this, it
         // is checked to be the same.
@@ -143,8 +144,7 @@ struct InstallListenAddresses : public ::testing::Test {
         invalid_.push_back(AddressPair("127.0.0.1", 5288));
         invalid_.push_back(AddressPair("192.0.2.2", 1));
     }
-    IOService ios_;
-    DNSService dnss_;
+    MockDNSService dnss_;
     AddressList store_;
     isc::testutils::TestSocketRequestor sock_requestor_;
     // We should be able to bind to these addresses

+ 4 - 0
src/lib/testutils/dnsmessage_test.h

@@ -12,6 +12,9 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 
+#ifndef __ISC_TESTUTILS_DNSMESSAGETEST_H
+#define __ISC_TESTUTILS_DNSMESSAGETEST_H 1
+
 #include <algorithm>
 #include <functional>
 #include <iosfwd>
@@ -339,6 +342,7 @@ rrsetsCheck(const std::string& expected,
 
 } // end of namespace testutils
 } // end of namespace isc
+#endif  // __ISC_TESTUTILS_DNSMESSAGETEST_H
 
 // Local Variables:
 // mode: c++

+ 57 - 0
src/lib/testutils/mockups.h

@@ -12,8 +12,13 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 
+#ifndef __ISC_TESTUTILS_MOCKUPS_H
+#define __ISC_TESTUTILS_MOCKUPS_H 1
+
 #include <config.h>
 
+#include <exceptions/exceptions.h>
+
 #include <cc/data.h>
 #include <cc/session.h>
 
@@ -21,6 +26,12 @@
 
 #include <asiodns/asiodns.h>
 
+#include <utility>
+#include <vector>
+
+namespace isc {
+namespace testutils {
+
 // A minimal mock configuration session.  Most the methods are
 // stubbed out, except for a very basic group_sendmsg() and
 // group_recvmsg().  hasQueuedMessages() always returns false.
@@ -93,6 +104,45 @@ private:
     bool receive_ok_;
 };
 
+// This mock object does nothing except for recording passed parameters
+// to addServerXXX methods so the test code subsequently checks the parameters.
+class MockDNSService : public isc::asiodns::DNSServiceBase {
+public:
+    // A helper tuple of parameters passed to addServerUDPFromFD().
+    struct UDPFdParams {
+        int fd;
+        int af;
+        ServerFlag options;
+    };
+
+    virtual void addServerTCPFromFD(int fd, int af) {
+        tcp_fd_params_.push_back(std::pair<int, int>(fd, af));
+    }
+    virtual void addServerUDPFromFD(int fd, int af, ServerFlag options) {
+        UDPFdParams params = { fd, af, options };
+        udp_fd_params_.push_back(params);
+    }
+    virtual void clearServers() {}
+
+    virtual asiolink::IOService& getIOService() {
+        isc_throw(isc::Unexpected,
+                  "MockDNSService::getIOService() shouldn't be called");
+    }
+
+    // These two allow the tests to check how the servers have been created
+    // through this object.
+    const std::vector<std::pair<int, int> >& getTCPFdParams() const {
+        return (tcp_fd_params_);
+    }
+    const std::vector<UDPFdParams>& getUDPFdParams() const {
+        return (udp_fd_params_);
+    }
+
+private:
+    std::vector<std::pair<int, int> > tcp_fd_params_;
+    std::vector<UDPFdParams> udp_fd_params_;
+};
+
 // A nonoperative DNSServer object to be used in calls to processMessage().
 class MockServer : public isc::asiodns::DNSServer {
 public:
@@ -149,3 +199,10 @@ private:
     bool disconnect_ok_;
 };
 
+} // end of testutils
+} // end of isc
+#endif  // __ISC_TESTUTILS_MOCKUPS_H
+
+// Local Variables:
+// mode: c++
+// End:

+ 3 - 3
src/lib/testutils/portconfig.h

@@ -12,8 +12,8 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 
-#ifndef TESTUTILS_PORTCONFIG_H
-#define TESTUTILS_PORTCONFIG_H
+#ifndef __ISC_TESTUTILS_PORTCONFIG_H
+#define __ISC_TESTUTILS_PORTCONFIG_H
 
 #include <gtest/gtest.h>
 #include <cc/data.h>
@@ -186,4 +186,4 @@ invalidListenAddressConfig(Server& server) {
 }
 }
 
-#endif
+#endif  // __ISC_TESTUTILS_PORTCONFIG_H

+ 6 - 13
src/lib/testutils/socket_request.h

@@ -12,6 +12,9 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 
+#ifndef __ISC_TESTUTILS_SOCKETREQUEST_H
+#define __ISC_TESTUTILS_SOCKETREQUEST_H 1
+
 #include <server_common/socket_request.h>
 #include <server_common/portconfig.h>
 
@@ -24,13 +27,6 @@
 #include <string>
 
 namespace isc {
-namespace server_common {
-namespace portconfig {
-// Access the private hidden flag
-extern bool test_mode;
-}
-}
-
 namespace testutils {
 
 /// \brief A testcase part for faking the SocketRequestor in tests
@@ -64,7 +60,7 @@ public:
     ///     not fall back to this value if its share_name is left empty, if
     ///     you want to check the code relies on the requestor to use the
     ///     app name, you set this to empty string.
-    TestSocketRequestor(asiodns::DNSService& dnss,
+    TestSocketRequestor(asiodns::DNSServiceBase& dnss,
                         server_common::portconfig::AddressList& store,
                         uint16_t expect_port,
                         const std::string& expected_app) :
@@ -74,8 +70,6 @@ public:
     {
         // Prepare the requestor (us) for the test
         server_common::initTestSocketRequestor(this);
-        // Don't manipulate the real sockets
-        server_common::portconfig::test_mode = true;
     }
 
     /// \brief Destructor
@@ -90,8 +84,6 @@ public:
         server_common::portconfig::installListenAddresses(list, store_, dnss_);
         // Don't leave invalid pointers here
         server_common::initTestSocketRequestor(NULL);
-        // And return the mode
-        server_common::portconfig::test_mode = false;
     }
 
     /// \brief Tokens released by releaseSocket
@@ -216,7 +208,7 @@ public:
     }
 
 private:
-    asiodns::DNSService& dnss_;
+    asiodns::DNSServiceBase& dnss_;
     server_common::portconfig::AddressList& store_;
     const uint16_t expect_port_;
     const std::string expected_app_;
@@ -224,3 +216,4 @@ private:
 
 }
 }
+#endif  // __ISC_TESTUTILS_SOCKETREQUEST_H

+ 4 - 0
src/lib/testutils/srv_test.h

@@ -12,6 +12,9 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 
+#ifndef __ISC_TESTUTILS_SRVTEST_H
+#define __ISC_TESTUTILS_SRVTEST_H 1
+
 #include <util/buffer.h>
 #include <dns/name.h>
 #include <dns/message.h>
@@ -106,6 +109,7 @@ protected:
 };
 } // end of namespace testutils
 } // end of namespace isc
+#endif  // __ISC_TESTUTILS_SRVTEST_H
 
 // Local Variables: 
 // mode: c++

+ 78 - 12
tests/system/bindctl/tests.sh

@@ -25,9 +25,12 @@ status=0
 n=0
 
 # TODO: consider consistency with statistics definition in auth.spec
-auth_queries_tcp="\<queries\.tcp\>"
-auth_queries_udp="\<queries\.udp\>"
-auth_opcode_queries="\<opcode\.query\>"
+cnt_name1="\<queries\.tcp\>"
+cnt_name2="\<queries\.udp\>"
+cnt_name3="\<opcode\.query\>"
+cnt_value1=0
+cnt_value2=0
+cnt_value3=0
 
 echo "I:Checking b10-auth is working by default ($n)"
 $DIG +norec @10.53.0.1 -p 53210 ns.example.com. A >dig.out.$n || status=1
@@ -45,9 +48,12 @@ echo 'Stats show
 	--csv-file-dir=$BINDCTL_CSV_DIR > bindctl.out.$n || status=1
 # the server should have received 1 UDP and 1 TCP queries (TCP query was
 # sent from the server startup script)
-grep $auth_queries_tcp".*\<1\>" bindctl.out.$n > /dev/null || status=1
-grep $auth_queries_udp".*\<1\>" bindctl.out.$n > /dev/null || status=1
-grep $auth_opcode_queries".*\<2\>" bindctl.out.$n > /dev/null || status=1
+cnt_value1=`expr $cnt_value1 + 1`
+cnt_value2=`expr $cnt_value2 + 1`
+cnt_value3=`expr $cnt_value1 + $cnt_value2`
+grep $cnt_name1".*\<"$cnt_value1"\>" bindctl.out.$n > /dev/null || status=1
+grep $cnt_name2".*\<"$cnt_value2"\>" bindctl.out.$n > /dev/null || status=1
+grep $cnt_name3".*\<"$cnt_value3"\>" bindctl.out.$n > /dev/null || status=1
 if [ $status != 0 ]; then echo "I:failed"; fi
 n=`expr $n + 1`
 
@@ -80,9 +86,12 @@ echo 'Stats show
 ' | $RUN_BINDCTL \
 	--csv-file-dir=$BINDCTL_CSV_DIR > bindctl.out.$n || status=1
 # The statistics counters should have been reset while stop/start.
-grep $auth_queries_tcp".*\<0\>" bindctl.out.$n > /dev/null || status=1
-grep $auth_queries_udp".*\<1\>" bindctl.out.$n > /dev/null || status=1
-grep $auth_opcode_queries".*\<1\>" bindctl.out.$n > /dev/null || status=1
+cnt_value1=0
+cnt_value2=1
+cnt_value3=`expr $cnt_value1 + $cnt_value2`
+grep $cnt_name1".*\<"$cnt_value1"\>" bindctl.out.$n > /dev/null || status=1
+grep $cnt_name2".*\<"$cnt_value2"\>" bindctl.out.$n > /dev/null || status=1
+grep $cnt_name3".*\<"$cnt_value3"\>" bindctl.out.$n > /dev/null || status=1
 if [ $status != 0 ]; then echo "I:failed"; fi
 n=`expr $n + 1`
 
@@ -105,11 +114,68 @@ echo 'Stats show
 ' | $RUN_BINDCTL \
 	--csv-file-dir=$BINDCTL_CSV_DIR > bindctl.out.$n || status=1
 # The statistics counters shouldn't be reset due to hot-swapping datasource.
-grep $auth_queries_tcp".*\<0\>" bindctl.out.$n > /dev/null || status=1
-grep $auth_queries_udp".*\<2\>" bindctl.out.$n > /dev/null || status=1
-grep $auth_opcode_queries".*\<2\>" bindctl.out.$n > /dev/null || status=1
+cnt_value1=`expr $cnt_value1 + 0`
+cnt_value2=`expr $cnt_value2 + 1`
+cnt_value3=`expr $cnt_value1 + $cnt_value2`
+grep $cnt_name1".*\<"$cnt_value1"\>" bindctl.out.$n > /dev/null || status=1
+grep $cnt_name2".*\<"$cnt_value2"\>" bindctl.out.$n > /dev/null || status=1
+grep $cnt_name3".*\<"$cnt_value3"\>" bindctl.out.$n > /dev/null || status=1
 if [ $status != 0 ]; then echo "I:failed"; fi
 n=`expr $n + 1`
 
+echo "I:Starting more b10-auths and checking that ($n)"
+for i in 2 3
+do
+    echo 'config add Boss/components b10-auth-'$i'
+config set Boss/components/b10-auth-'$i' { "special": "auth", "kind": "needed" }
+config commit
+quit
+' | $RUN_BINDCTL \
+	--csv-file-dir=$BINDCTL_CSV_DIR 2>&1 > /dev/null || status=1
+done
+$DIG +norec @10.53.0.1 -p 53210 ns.example.com. A >dig.out.$n || status=1
+grep 192.0.2.2 dig.out.$n > /dev/null || status=1
+if [ $status != 0 ]; then echo "I:failed"; fi
+n=`expr $n + 1`
+
+echo "I:Rechecking BIND 10 statistics consistency after a pause ($n)"
+sleep 2
+cnt_value1=`expr $cnt_value1 + 0`
+cnt_value2=`expr $cnt_value2 + 1`
+cnt_value3=`expr $cnt_value1 + $cnt_value2`
+# Rechecking some times
+for i in 1 2 3 4
+do
+    echo 'Stats show
+' | $RUN_BINDCTL \
+	--csv-file-dir=$BINDCTL_CSV_DIR > bindctl.out.$n || status=1
+    # The statistics counters should keep being consistent even while
+    # multiple b10-auths are running.
+    grep $cnt_name1".*\<"$cnt_value1"\>" bindctl.out.$n > /dev/null || status=1
+    grep $cnt_name2".*\<"$cnt_value2"\>" bindctl.out.$n > /dev/null || status=1
+    grep $cnt_name3".*\<"$cnt_value3"\>" bindctl.out.$n > /dev/null || status=1
+    if [ $status != 0 ]; then echo "I:failed "; break ; fi
+done
+n=`expr $n + 1`
+
+echo "I:Stopping extra b10-auths and checking that ($n)"
+for i in 3 2
+do
+    echo 'config remove Boss/components b10-auth-'$i'
+config commit
+quit
+' | $RUN_BINDCTL \
+	--csv-file-dir=$BINDCTL_CSV_DIR 2>&1 > /dev/null || status=1
+done
+$DIG +norec @10.53.0.1 -p 53210 ns.example.com. A >dig.out.$n || status=1
+grep 192.0.2.2 dig.out.$n > /dev/null || status=1
+if [ $status != 0 ]; then echo "I:failed"; fi
+n=`expr $n + 1`
+
+# The statistics counters can not be rechecked here because the auth
+# instance seems to hang up after one of the multiple auth instances
+# was removed via bindctl. This reason seems to be the same reason as
+# #1703.
+
 echo "I:exit status: $status"
 exit $status

+ 29 - 0
tests/tools/perfdhcp/perfdhcp.c

@@ -66,6 +66,35 @@ main(const int argc, char* const argv[])
 #include <time.h>
 #include <unistd.h>
 
+#ifndef HAVE_PSELECT
+
+#include <assert.h>
+
+/* Platforms such as OpenBSD don't provide a pselect(), so we use our
+   own implementation for this testcase, which wraps around select() and
+   hence doesn't implement the high precision timer. This implementation
+   is fine for our purpose. */
+
+static int
+pselect (int nfds, fd_set *readfds, fd_set *writefds,
+         fd_set *exceptfds, const struct timespec *timeout,
+         const sigset_t *sigmask)
+{
+	struct timeval my_timeout;
+
+	/* Our particular usage of pselect() doesn't use these fields. */
+	assert(writefds == NULL);
+	assert(exceptfds == NULL);
+	assert(sigmask == NULL);
+
+	my_timeout.tv_sec = timeout->tv_sec;
+	my_timeout.tv_usec = timeout->tv_nsec / 1000;
+
+	return (select(nfds, readfds, writefds, exceptfds, &my_timeout));
+}
+
+#endif /* HAVE_PSELECT */
+
 /* DHCPv4 defines (to be moved/shared) */
 
 #define DHCP_OFF_OPCODE		0