Browse Source

[trac554] Merge branch 'master' into trac554

Conflicts:
	src/lib/asiolink/Makefile.am
	src/lib/asiolink/asiolink.cc
	src/lib/asiolink/internal/udpdns.h
	src/lib/asiolink/tests/udp_query_unittest.cc
	src/lib/asiolink/udpdns.cc
Stephen Morris 14 years ago
parent
commit
bbb0031f3a
100 changed files with 9472 additions and 2923 deletions
  1. 39 0
      ChangeLog
  2. 62 2
      configure.ac
  3. 4 4
      doc/guide/bind10-guide.html
  4. 2 0
      doc/guide/bind10-guide.xml
  5. 0 0
      ext/coroutine/coroutine.h
  6. 9 3
      src/bin/auth/auth_srv.cc
  7. 2 1
      src/bin/auth/auth_srv.h
  8. 1 1
      src/bin/auth/b10-auth.xml
  9. 1 0
      src/bin/auth/benchmarks/Makefile.am
  10. 6 1
      src/bin/auth/config.cc
  11. 50 9
      src/bin/auth/query.cc
  12. 1 0
      src/bin/auth/tests/Makefile.am
  13. 1 1
      src/bin/auth/tests/command_unittest.cc
  14. 4 0
      src/bin/auth/tests/config_unittest.cc
  15. 162 13
      src/bin/auth/tests/query_unittest.cc
  16. 1 1
      src/bin/bind10/bind10.xml
  17. 1 1
      src/bin/resolver/Makefile.am
  18. 19 3
      src/bin/resolver/resolver.cc
  19. 16 3
      src/bin/resolver/resolver.h
  20. 189 0
      src/bin/resolver/response_scrubber.cc
  21. 422 0
      src/bin/resolver/response_scrubber.h
  22. 13 5
      src/bin/resolver/tests/Makefile.am
  23. 542 0
      src/bin/resolver/tests/response_scrubber_unittest.cc
  24. 2 1
      src/bin/stats/b10-stats.xml
  25. 1 1
      src/bin/usermgr/b10-cmdctl-usermgr.py.in
  26. 1 1
      src/bin/xfrin/b10-xfrin.xml
  27. 2 2
      src/lib/Makefile.am
  28. 20 10
      src/lib/asiolink/Makefile.am
  29. 0 735
      src/lib/asiolink/asiolink.cc
  30. 13 614
      src/lib/asiolink/asiolink.h
  31. 73 0
      src/lib/asiolink/dns_answer.h
  32. 81 0
      src/lib/asiolink/dns_lookup.h
  33. 152 0
      src/lib/asiolink/dns_server.h
  34. 191 0
      src/lib/asiolink/dns_service.cc
  35. 106 0
      src/lib/asiolink/dns_service.h
  36. 0 1
      src/lib/asiolink/internal/Makefile.am
  37. 0 37
      src/lib/asiolink/internal/tests/Makefile.am
  38. 0 244
      src/lib/asiolink/internal/udpdns.h
  39. 133 0
      src/lib/asiolink/interval_timer.cc
  40. 133 0
      src/lib/asiolink/interval_timer.h
  41. 0 0
      src/lib/asiolink/io_address.cc
  42. 40 1
      src/lib/asiolink/ioaddress.h
  43. 5 3
      src/lib/asiolink/ioendpoint.cc
  44. 1 0
      src/lib/asiolink/ioendpoint.h
  45. 2 2
      src/lib/asiolink/iomessage.h
  46. 95 0
      src/lib/asiolink/io_service.cc
  47. 77 0
      src/lib/asiolink/io_service.h
  48. 1 1
      src/lib/asiolink/iosocket.cc
  49. 0 0
      src/lib/asiolink/io_socket.h
  50. 454 0
      src/lib/asiolink/recursive_query.cc
  51. 113 0
      src/lib/asiolink/recursive_query.h
  52. 71 0
      src/lib/asiolink/simple_callback.h
  53. 98 0
      src/lib/asiolink/tcp_endpoint.h
  54. 11 14
      src/lib/asiolink/tcpdns.cc
  55. 9 114
      src/lib/asiolink/internal/tcpdns.h
  56. 52 0
      src/lib/asiolink/tcp_socket.h
  57. 12 2
      src/lib/asiolink/tests/Makefile.am
  58. 292 0
      src/lib/asiolink/tests/interval_timer_unittest.cc
  59. 115 0
      src/lib/asiolink/tests/io_service_unittest.cc
  60. 57 0
      src/lib/asiolink/tests/ioaddress_unittest.cc
  61. 67 0
      src/lib/asiolink/tests/ioendpoint_unittest.cc
  62. 29 0
      src/lib/asiolink/tests/iosocket_unittest.cc
  63. 81 490
      src/lib/asiolink/tests/asiolink_unittest.cc
  64. 21 22
      src/lib/asiolink/internal/tests/udpdns_unittest.cc
  65. 89 0
      src/lib/asiolink/udp_endpoint.h
  66. 186 0
      src/lib/asiolink/udp_query.cc
  67. 88 0
      src/lib/asiolink/udp_query.h
  68. 281 0
      src/lib/asiolink/udp_server.cc
  69. 102 0
      src/lib/asiolink/udp_server.h
  70. 48 0
      src/lib/asiolink/udp_socket.h
  71. 0 181
      src/lib/asiolink/udpdns.cc
  72. 242 70
      src/lib/datasrc/memory_datasrc.cc
  73. 556 157
      src/lib/datasrc/rbtree.h
  74. 259 63
      src/lib/datasrc/tests/memory_datasrc_unittest.cc
  75. 292 106
      src/lib/datasrc/tests/rbtree_unittest.cc
  76. 59 0
      src/lib/dns/message.cc
  77. 35 1
      src/lib/dns/message.h
  78. 171 0
      src/lib/dns/tests/message_unittest.cc
  79. 41 2
      src/lib/log/Makefile.am
  80. 20 0
      src/lib/log/compiler/Makefile.am
  81. 450 0
      src/lib/log/compiler/message.cc
  82. 31 0
      src/lib/log/dbglevels.h
  83. 371 0
      src/lib/log/documentation.txt
  84. 140 0
      src/lib/log/filename.cc
  85. 163 0
      src/lib/log/filename.h
  86. 307 0
      src/lib/log/logger.cc
  87. 327 0
      src/lib/log/logger.h
  88. 116 0
      src/lib/log/logger_support.cc
  89. 43 0
      src/lib/log/logger_support.h
  90. 116 0
      src/lib/log/message_dictionary.cc
  91. 150 0
      src/lib/log/message_dictionary.h
  92. 28 0
      src/lib/log/message_exception.cc
  93. 90 0
      src/lib/log/message_exception.h
  94. 32 0
      src/lib/log/message_initializer.cc
  95. 63 0
      src/lib/log/message_initializer.h
  96. 184 0
      src/lib/log/message_reader.cc
  97. 175 0
      src/lib/log/message_reader.h
  98. 32 0
      src/lib/log/message_types.h
  99. 27 0
      src/lib/log/messagedef.cc
  100. 0 0
      src/lib/log/messagedef.h

+ 39 - 0
ChangeLog

@@ -1,3 +1,42 @@
+  166.  [func]      jelte
+	The resolver now sends back a SERVFAIL when there is a client
+	timeout (timeout_client config setting), but it will not stop
+	resolving (until there is a lookup timeout or a result).
+	(Trac #497 and #489, git af0e5cd93bebb27cb5c4457f7759d12c8bf953a6)
+
+  165.  [func]      jelte
+	The resolver now handles CNAMEs, it will follow them, and include
+	them in the answer. The maximum length of CNAME chains that is
+	supported is 16.
+	(Trac #497, git af0e5cd93bebb27cb5c4457f7759d12c8bf953a6)
+
+  164.  [bug]           y-aharen
+	IntervalTimer: Modified the interface to accept interval in
+	milliseconds. It shortens the time of the tests of IntervalTimer.
+	(Trac #452, git c9f6acc81e24c4b8f0eb351123dc7b43f64e0914)
+
+  163.  [func]      vorner
+	The pimpl design pattern is used in UDPServer, with a shared pointer. This
+	makes it smaller to copy (which is done a lot as a sideeffect of being
+	coroutine) and speeds applications of this class (notably b10-auth) up by
+	around 10%.
+	(Trac #537, git 94cb95b1d508541201fc064302ba836164d3cbe6)
+
+  162.  [func]		stephen
+	Added C++ logging, allowing logging at different severities.
+	Code specifies the message to be logged via a symbol, and the
+	logging code picks up the message from an in-built dictionary.
+	The contents of the dictionary can be replaced at run-time by
+	locale-specific messages.  A message compiler program is provided
+	to create message header files and supply the default messages.
+	(Trac #438, git 7b1606cea7af15dc71f5ec1d70d958b00aa98af7)
+
+  161.  [func]		stephen
+	Added ResponseScrubber class to examine response from
+	a server and to remove out-of-bailiwick RRsets.  Also
+	does cross-section checks to ensure consistency.
+	(Trac #496, git b9296ca023cc9e76cda48a7eeebb0119166592c5)
+
   160.  [func]		jelte
   	Updated the resolver to take 3 different timeout values;
 	timeout_query for outstanding queries we sent while resolving

+ 62 - 2
configure.ac

@@ -363,6 +363,57 @@ if test "$lcov" != "no"; then
 fi
 AC_SUBST(USE_LCOV)
 
+# Configure log4cxx header and library path
+#
+# If explicitly specified, use it.
+
+AC_ARG_WITH([log4cxx],
+  AC_HELP_STRING([--with-log4cxx=PATH],
+    [specify directory where log4cxx is installed]),
+  [
+   log4cxx_include_path="${withval}/include";
+   log4cxx_library_path="${withval}/lib"
+  ])
+
+# This is an urgent fix to avoid regression due to log4cxx on some
+# platforms.  It should be cleaned up with a better fix.
+if test "X$with_log4cxx" != "Xno"; then
+
+# If not specified, try some common paths.  These default to
+# /usr/include and /usr/lib if not found
+
+if test -z "$with_log4cxx"; then
+	log4cxxdirs="/usr/local /usr/pkg /opt /opt/local"
+	for d in $log4cxxdirs
+	do
+		if test -d $d/include/log4cxx; then
+			log4cxx_include_path=$d/include
+			log4cxx_library_path=$d/lib
+			break
+		fi
+	done
+fi
+
+CPPFLAGS_SAVES="$CPPFLAGS"
+if test "${log4cxx_include_path}" ; then
+	LOG4CXX_INCLUDES="-I${log4cxx_include_path}"
+	CPPFLAGS="$CPPFLAGS $LOG4CXX_INCLUDES"
+fi
+AC_CHECK_HEADER([log4cxx/logger.h],, AC_MSG_ERROR([Missing log4cxx header files.]))
+CPPFLAGS="$CPPFLAGS_SAVES"
+AC_SUBST(LOG4CXX_INCLUDES)
+
+LOG4CXX_LDFLAGS="-llog4cxx";
+if test "${log4cxx_library_path}"; then
+    LOG4CXX_LDFLAGS="-L${log4cxx_library_path} -llog4cxx"
+fi
+AC_SUBST(LOG4CXX_LDFLAGS)
+
+# The following two lines are part of the urgent fix, and should be cleaned
+# up with a better fix.
+fi
+AM_CONDITIONAL(USE_LOG4CXX, test "X${with_log4cxx}" != "Xno")
+
 #
 # Configure Boost header path
 #
@@ -522,6 +573,9 @@ AC_SUBST(MULTITHREADING_FLAG)
 #
 CPPFLAGS="$CPPFLAGS -I\$(top_srcdir)/ext/asio"
 #
+# Use our 'coroutine' header from ext
+CPPFLAGS="$CPPFLAGS -I\$(top_srcdir)/ext/coroutine"
+#
 # Disable threads: Currently we don't use them.
 CPPFLAGS="$CPPFLAGS -DASIO_DISABLE_THREADS=1"
 #
@@ -615,8 +669,6 @@ AC_CONFIG_FILES([Makefile
                  src/lib/Makefile
                  src/lib/asiolink/Makefile
                  src/lib/asiolink/tests/Makefile
-                 src/lib/asiolink/internal/Makefile
-                 src/lib/asiolink/internal/tests/Makefile
                  src/lib/bench/Makefile
                  src/lib/bench/example/Makefile
                  src/lib/bench/tests/Makefile
@@ -652,6 +704,10 @@ AC_CONFIG_FILES([Makefile
                  src/lib/datasrc/tests/Makefile
                  src/lib/xfr/Makefile
                  src/lib/log/Makefile
+                 src/lib/log/compiler/Makefile
+                 src/lib/log/tests/Makefile
+                 src/lib/resolve/Makefile
+                 src/lib/resolve/tests/Makefile
                  src/lib/testutils/Makefile
                  src/lib/testutils/testdata/Makefile
                  src/lib/nsas/Makefile
@@ -711,6 +767,7 @@ AC_OUTPUT([doc/version.ent
            src/lib/dns/tests/testdata/gen-wiredata.py
            src/lib/cc/session_config.h.pre
            src/lib/cc/tests/session_unittests_config.h
+           src/lib/log/tests/run_time_init_test.sh
           ], [
            chmod +x src/bin/cmdctl/run_b10-cmdctl.sh
            chmod +x src/bin/xfrin/run_b10-xfrin.sh
@@ -734,6 +791,7 @@ AC_OUTPUT([doc/version.ent
            chmod +x src/bin/msgq/tests/msgq_test
            chmod +x src/lib/dns/gen-rdatacode.py
            chmod +x src/lib/dns/tests/testdata/gen-wiredata.py
+           chmod +x src/lib/log/tests/run_time_init_test.sh
           ])
 AC_OUTPUT
 
@@ -761,6 +819,8 @@ dnl includes too
                  ${PYTHON_LDFLAGS}
                  ${PYTHON_LIB}
   Boost:         ${BOOST_INCLUDES}
+  log4cxx:       ${LOG4CXX_INCLUDES}
+                 ${LOG4CXX_LDFLAGS}
   SQLite:        $SQLITE_CFLAGS
                  $SQLITE_LIBS
 

File diff suppressed because it is too large
+ 4 - 4
doc/guide/bind10-guide.html


+ 2 - 0
doc/guide/bind10-guide.xml

@@ -982,6 +982,8 @@ accounts_file
       <para>
         The control commands are:
 print_settings
+<!-- TODO: remove that -->
+
 shutdown
       </para>
 <!-- TODO -->

src/lib/asiolink/internal/coroutine.h → ext/coroutine/coroutine.h


+ 9 - 3
src/bin/auth/auth_srv.cc

@@ -354,7 +354,7 @@ AuthSrv::setMemoryDataSrc(const isc::dns::RRClass& rrclass,
 
 uint32_t
 AuthSrv::getStatisticsTimerInterval() const {
-    return (impl_->statistics_timer_.getInterval());
+    return (impl_->statistics_timer_.getInterval() / 1000);
 }
 
 void
@@ -362,11 +362,17 @@ AuthSrv::setStatisticsTimerInterval(uint32_t interval) {
     if (interval == impl_->statistics_timer_.getInterval()) {
         return;
     }
+    if (interval > 86400) {
+        // It can't occur since the value is checked in
+        // statisticsIntervalConfig::build().
+        isc_throw(InvalidParameter, "Too long interval: " << interval);
+    }
     if (interval == 0) {
         impl_->statistics_timer_.cancel();
     } else {
-        impl_->statistics_timer_.setupTimer(
-            boost::bind(&AuthSrv::submitStatistics, this), interval);
+        impl_->statistics_timer_.setup(boost::bind(&AuthSrv::submitStatistics,
+                                                   this),
+                                       interval * 1000);
     }
     if (impl_->verbose_mode_) {
         if (interval == 0) {

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

@@ -318,7 +318,8 @@ public:
     /// If the specified value is non 0, the \c AuthSrv object will submit
     /// its statistics to the statistics module every \c interval seconds.
     /// If it's 0, and \c AuthSrv currently submits statistics, the submission
-    /// will be disabled.
+    /// will be disabled. \c interval must be equal to or shorter than 86400
+    /// seconds (1 day).
     ///
     /// This method should normally not throw an exception; however, its
     /// underlying library routines may involve resource allocation, and

+ 1 - 1
src/bin/auth/b10-auth.xml

@@ -134,7 +134,7 @@
         <listitem><para>
           The port number it listens on.
           The default is 5300.</para>
-	  <note><simpara>The Y1 prototype runs on all interfaces
+	  <note><simpara>This prototype runs on all interfaces
 	  and on this nonstandard port.</simpara></note>
         </listitem>
       </varlistentry>

+ 1 - 0
src/bin/auth/benchmarks/Makefile.am

@@ -20,5 +20,6 @@ query_bench_LDADD += $(top_builddir)/src/lib/datasrc/libdatasrc.la
 query_bench_LDADD += $(top_builddir)/src/lib/config/libcfgclient.la
 query_bench_LDADD += $(top_builddir)/src/lib/cc/libcc.la
 query_bench_LDADD += $(top_builddir)/src/lib/xfr/libxfr.la
+query_bench_LDADD += $(top_builddir)/src/lib/log/liblog.la
 query_bench_LDADD += $(top_builddir)/src/lib/asiolink/libasiolink.la
 query_bench_LDADD += $(SQLITE_LIBS)

+ 6 - 1
src/bin/auth/config.cc

@@ -179,9 +179,14 @@ public:
     virtual void build(ConstElementPtr config_value) {
         const int32_t config_interval = config_value->intValue();
         if (config_interval < 0) {
-            isc_throw(AuthConfigError, "negative statistics-interval value: "
+            isc_throw(AuthConfigError, "Negative statistics interval value: "
                       << config_interval);
         }
+        if (config_interval > 86400) {
+            isc_throw(AuthConfigError, "Statistics interval value "
+                      << config_interval
+                      << " must be equal to or shorter than 86400");
+        }
         interval_ = config_interval;
     }
     virtual void commit() {

+ 50 - 9
src/bin/auth/query.cc

@@ -141,13 +141,56 @@ Query::process() const {
 
     // Found a zone which is the nearest ancestor to QNAME, set the AA bit
     response_.setHeaderFlag(Message::HEADERFLAG_AA);
+    response_.setRcode(Rcode::NOERROR());
     while (keep_doing) {
         keep_doing = false;
         std::auto_ptr<RRsetList> target(qtype_is_any ? new RRsetList : NULL);
-        Zone::FindResult db_result =
-            result.zone->find(qname_, qtype_, target.get());
+        const Zone::FindResult db_result(result.zone->find(qname_, qtype_,
+            target.get()));
 
         switch (db_result.code) {
+            case Zone::DNAME: {
+                // First, put the dname into the answer
+                response_.addRRset(Message::SECTION_ANSWER,
+                    boost::const_pointer_cast<RRset>(db_result.rrset));
+                /*
+                 * Empty DNAME should never get in, as it is impossible to
+                 * create one in master file.
+                 *
+                 * FIXME: Other way to prevent this should be done
+                 */
+                assert(db_result.rrset->getRdataCount() > 0);
+                // Get the data of DNAME
+                const rdata::generic::DNAME& dname(
+                    dynamic_cast<const rdata::generic::DNAME&>(
+                    db_result.rrset->getRdataIterator()->getCurrent()));
+                // The yet unmatched prefix dname
+                const Name prefix(qname_.split(0, qname_.getLabelCount() -
+                    db_result.rrset->getName().getLabelCount()));
+                // If we put it together, will it be too long?
+                // (The prefix contains trailing ., which will be removed
+                if (prefix.getLength() - Name::ROOT_NAME().getLength() +
+                    dname.getDname().getLength() > Name::MAX_WIRE) {
+                    /*
+                     * In case the synthesized name is too long, section 4.1
+                     * of RFC 2672 mandates we return YXDOMAIN.
+                     */
+                    response_.setRcode(Rcode::YXDOMAIN());
+                    return;
+                }
+                // The new CNAME we are creating (it will be unsigned even
+                // with DNSSEC, the DNAME is signed and it can be validated
+                // by that)
+                RRsetPtr cname(new RRset(qname_, db_result.rrset->getClass(),
+                    RRType::CNAME(), db_result.rrset->getTTL()));
+                // Construct the new target by replacing the end
+                cname->addRdata(rdata::generic::CNAME(qname_.split(0,
+                    qname_.getLabelCount() -
+                    db_result.rrset->getName().getLabelCount()).
+                    concatenate(dname.getDname())));
+                response_.addRRset(Message::SECTION_ANSWER, cname);
+                break;
+            }
             case Zone::CNAME:
                 /*
                  * We don't do chaining yet. Therefore handling a CNAME is
@@ -155,10 +198,13 @@ Query::process() const {
                  * what we expected. It means no exceptions in ANY or NS
                  * on the origin (though CNAME in origin is probably
                  * forbidden anyway).
+                 *
+                 * So, just put it there.
                  */
-                // No break; here, fall trough.
+                response_.addRRset(Message::SECTION_ANSWER,
+                    boost::const_pointer_cast<RRset>(db_result.rrset));
+                break;
             case Zone::SUCCESS:
-                response_.setRcode(Rcode::NOERROR());
                 if (qtype_is_any) {
                     // If quety type is ANY, insert all RRs under the domain
                     // into answer section.
@@ -184,7 +230,6 @@ Query::process() const {
                 break;
             case Zone::DELEGATION:
                 response_.setHeaderFlag(Message::HEADERFLAG_AA, false);
-                response_.setRcode(Rcode::NOERROR());
                 response_.addRRset(Message::SECTION_AUTHORITY,
                     boost::const_pointer_cast<RRset>(db_result.rrset));
                 getAdditional(*result.zone, *db_result.rrset);
@@ -196,12 +241,8 @@ Query::process() const {
                 break;
             case Zone::NXRRSET:
                 // Just empty answer with SOA in authority section
-                response_.setRcode(Rcode::NOERROR());
                 putSOA(*result.zone);
                 break;
-            case Zone::DNAME:
-                // TODO : replace qname, continue lookup
-                break;
         }
     }
 }

+ 1 - 0
src/bin/auth/tests/Makefile.am

@@ -44,6 +44,7 @@ run_unittests_LDADD += $(top_builddir)/src/lib/config/libcfgclient.la
 run_unittests_LDADD += $(top_builddir)/src/lib/cc/libcc.la
 run_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
 run_unittests_LDADD += $(top_builddir)/src/lib/xfr/libxfr.la
+run_unittests_LDADD += $(top_builddir)/src/lib/log/liblog.la
 endif
 
 noinst_PROGRAMS = $(TESTS)

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

@@ -98,7 +98,7 @@ AuthConmmandTest::stopServer() {
 
 TEST_F(AuthConmmandTest, shutdown) {
     asiolink::IntervalTimer itimer(server.getIOService());
-    itimer.setupTimer(boost::bind(&AuthConmmandTest::stopServer, this), 1);
+    itimer.setup(boost::bind(&AuthConmmandTest::stopServer, this), 1);
     server.getIOService().run();
     EXPECT_EQ(0, rcode);
 }

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

@@ -365,5 +365,9 @@ TEST_F(StatisticsIntervalConfigTest, badInterval) {
     EXPECT_THROW(parser->build(Element::fromJSON("2.5")),
                  isc::data::TypeError);
     EXPECT_THROW(parser->build(Element::fromJSON("-1")), AuthConfigError);
+    // bounds check: interval value must be equal to or shorter than
+    // 86400 seconds (1 day)
+    EXPECT_NO_THROW(parser->build(Element::fromJSON("86400")));
+    EXPECT_THROW(parser->build(Element::fromJSON("86401")), AuthConfigError);
 }
 }

+ 162 - 13
src/bin/auth/tests/query_unittest.cc

@@ -75,6 +75,17 @@ const char* const cname_nxdom_txt =
 // CNAME Leading out of zone
 const char* const cname_out_txt =
     "cnameout.example.com. 3600 IN CNAME www.example.org.\n";
+// The DNAME to do tests against
+const char* const dname_txt =
+    "dname.example.com. 3600 IN DNAME "
+    "somethinglong.dnametarget.example.com.\n";
+// Some data at the dname node (allowed by RFC 2672)
+const char* const dname_a_txt =
+    "dname.example.com. 3600 IN A 192.0.2.5\n";
+// This is not inside the zone, this is created at runtime
+const char* const synthetized_cname_txt =
+    "www.dname.example.com. 3600 IN CNAME "
+    "www.somethinglong.dnametarget.example.com.\n";
 // The rest of data won't be referenced from the test cases.
 const char* const other_zone_rrs =
     "cnamemailer.example.com. 3600 IN CNAME www.example.com.\n"
@@ -88,13 +99,16 @@ const char* const other_zone_rrs =
 // behavior.
 // For simplicity, most names are assumed to be "in zone"; there's only
 // one zone cut at the point of name "delegation.example.com".
-// It doesn't handle empty non terminal nodes (if we need to test such cases
-// find() should have specialized code for it).
+// Another special name is "dname.example.com".  Query names under this name
+// will result in DNAME.
+// This mock zone doesn't handle empty non terminal nodes (if we need to test
+// such cases find() should have specialized code for it).
 class MockZone : public Zone {
 public:
     MockZone() :
         origin_(Name("example.com")),
         delegation_name_("delegation.example.com"),
+        dname_name_("dname.example.com"),
         has_SOA_(true),
         has_apex_NS_(true),
         rrclass_(RRClass::IN())
@@ -102,7 +116,8 @@ public:
         stringstream zone_stream;
         zone_stream << soa_txt << zone_ns_txt << ns_addrs_txt <<
             delegation_txt << mx_txt << www_a_txt << cname_txt <<
-            cname_nxdom_txt << cname_out_txt << other_zone_rrs;
+            cname_nxdom_txt << cname_out_txt << dname_txt << dname_a_txt <<
+            other_zone_rrs;
 
         masterLoad(zone_stream, origin_, rrclass_,
                    boost::bind(&MockZone::loadRRset, this, _1));
@@ -131,14 +146,20 @@ private:
         if (rrset->getName() == delegation_name_ &&
             rrset->getType() == RRType::NS()) {
             delegation_rrset_ = rrset;
+        } else if (rrset->getName() == dname_name_ &&
+            rrset->getType() == RRType::DNAME()) {
+            dname_rrset_ = rrset;
         }
     }
 
     const Name origin_;
+    // Names where we delegate somewhere else
     const Name delegation_name_;
+    const Name dname_name_;
     bool has_SOA_;
     bool has_apex_NS_;
     ConstRRsetPtr delegation_rrset_;
+    ConstRRsetPtr dname_rrset_;
     const RRClass rrclass_;
 };
 
@@ -160,6 +181,10 @@ MockZone::find(const Name& name, const RRType& type,
          name.compare(delegation_name_).getRelation() ==
          NameComparisonResult::SUBDOMAIN)) {
         return (FindResult(DELEGATION, delegation_rrset_));
+    // And under DNAME
+    } else if (name.compare(dname_name_).getRelation() ==
+        NameComparisonResult::SUBDOMAIN) {
+        return (FindResult(DNAME, dname_rrset_));
     }
 
     // normal cases.  names are searched for only per exact-match basis
@@ -176,8 +201,7 @@ MockZone::find(const Name& name, const RRType& type,
         // If not found but we have a target, fill it with all RRsets here
         if (!found_domain->second.empty() && target != NULL) {
             for (found_rrset = found_domain->second.begin();
-                 found_rrset != found_domain->second.end(); found_rrset++)
-            {
+                 found_rrset != found_domain->second.end(); found_rrset++) {
                 // Insert RRs under the domain name into target
                 target->addRRset(
                     boost::const_pointer_cast<RRset>(found_rrset->second));
@@ -443,8 +467,8 @@ TEST_F(QueryTest, CNAME) {
     Query(memory_datasrc, Name("cname.example.com"), RRType::A(),
         response).process();
 
-    responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
-        cname_txt, zone_ns_txt, ns_addrs_txt);
+    responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 0, 0,
+        cname_txt, NULL, NULL);
 }
 
 TEST_F(QueryTest, explicitCNAME) {
@@ -465,8 +489,8 @@ TEST_F(QueryTest, CNAME_NX_RRSET) {
     Query(memory_datasrc, Name("cname.example.com"), RRType::TXT(),
         response).process();
 
-    responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
-        cname_txt, zone_ns_txt, ns_addrs_txt);
+    responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 0, 0,
+        cname_txt, NULL, NULL);
 }
 
 TEST_F(QueryTest, explicitCNAME_NX_RRSET) {
@@ -488,8 +512,8 @@ TEST_F(QueryTest, CNAME_NX_DOMAIN) {
     Query(memory_datasrc, Name("cnamenxdom.example.com"), RRType::A(),
         response).process();
 
-    responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
-        cname_nxdom_txt, zone_ns_txt, ns_addrs_txt);
+    responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 0, 0,
+        cname_nxdom_txt, NULL, NULL);
 }
 
 TEST_F(QueryTest, explicitCNAME_NX_DOMAIN) {
@@ -513,8 +537,8 @@ TEST_F(QueryTest, CNAME_OUT) {
     Query(memory_datasrc, Name("cnameout.example.com"), RRType::A(),
         response).process();
 
-    responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
-        cname_out_txt, zone_ns_txt, ns_addrs_txt);
+    responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 0, 0,
+        cname_out_txt, NULL, NULL);
 }
 
 TEST_F(QueryTest, explicitCNAME_OUT) {
@@ -526,4 +550,129 @@ TEST_F(QueryTest, explicitCNAME_OUT) {
         cname_out_txt, zone_ns_txt, ns_addrs_txt);
 }
 
+/*
+ * Test a query under a domain with DNAME. We should get a synthetized CNAME
+ * as well as the DNAME.
+ *
+ * TODO: Once we have CNAME chaining, check it works with synthetized CNAMEs
+ * as well. This includes tests pointing inside the zone, outside the zone,
+ * pointing to NXRRSET and NXDOMAIN cases (similarly as with CNAME).
+ */
+TEST_F(QueryTest, DNAME) {
+    Query(memory_datasrc, Name("www.dname.example.com"), RRType::A(),
+        response).process();
+
+    responseCheck(response, Rcode::NOERROR(), AA_FLAG, 2, 0, 0,
+        (string(dname_txt) + synthetized_cname_txt).c_str(),
+        NULL, NULL);
+}
+
+/*
+ * Ask an ANY query below a DNAME. Should return the DNAME and synthetized
+ * CNAME.
+ *
+ * ANY is handled specially sometimes. We check it is not the case with
+ * DNAME.
+ */
+TEST_F(QueryTest, DNAME_ANY) {
+    Query(memory_datasrc, Name("www.dname.example.com"), RRType::ANY(),
+        response).process();
+
+    responseCheck(response, Rcode::NOERROR(), AA_FLAG, 2, 0, 0,
+        (string(dname_txt) + synthetized_cname_txt).c_str(), NULL, NULL);
+}
+
+// Test when we ask for DNAME explicitly, it does no synthetizing.
+TEST_F(QueryTest, explicitDNAME) {
+    Query(memory_datasrc, Name("dname.example.com"), RRType::DNAME(),
+        response).process();
+
+    responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
+        dname_txt, zone_ns_txt, ns_addrs_txt);
+}
+
+/*
+ * Request a RRset at the domain with DNAME. It should not synthetize
+ * the CNAME, it should return the RRset.
+ */
+TEST_F(QueryTest, DNAME_A) {
+    Query(memory_datasrc, Name("dname.example.com"), RRType::A(),
+        response).process();
+
+    responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
+        dname_a_txt, zone_ns_txt, ns_addrs_txt);
+}
+
+/*
+ * Request a RRset at the domain with DNAME that is not there (NXRRSET).
+ * It should not synthetize the CNAME.
+ */
+TEST_F(QueryTest, DNAME_NX_RRSET) {
+    EXPECT_NO_THROW(Query(memory_datasrc, Name("dname.example.com"),
+        RRType::TXT(), response).process());
+
+    responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 1, 0,
+        NULL, soa_txt, NULL, mock_zone->getOrigin());
+}
+
+/*
+ * Constructing the CNAME will result in a name that is too long. This,
+ * however, should not throw (and crash the server), but respond with
+ * YXDOMAIN.
+ */
+TEST_F(QueryTest, LongDNAME) {
+    // A name that is as long as it can be
+    Name longname(
+        "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
+        "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
+        "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
+        "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
+        "dname.example.com.");
+    EXPECT_NO_THROW(Query(memory_datasrc, longname, RRType::A(),
+        response).process());
+
+    responseCheck(response, Rcode::YXDOMAIN(), AA_FLAG, 1, 0, 0,
+        dname_txt, NULL, NULL);
+}
+
+/*
+ * Constructing the CNAME will result in a name of maximal length.
+ * This tests that we don't reject valid one by some kind of off by
+ * one mistake.
+ */
+TEST_F(QueryTest, MaxLenDNAME) {
+    Name longname(
+        "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
+        "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
+        "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
+        "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
+        "dname.example.com.");
+    EXPECT_NO_THROW(Query(memory_datasrc, longname, RRType::A(),
+        response).process());
+
+    // Check the answer is OK
+    responseCheck(response, Rcode::NOERROR(), AA_FLAG, 2, 0, 0,
+        NULL, NULL, NULL);
+
+    // Check that the CNAME has the maximal length.
+    bool ok(false);
+    for (RRsetIterator i(response.beginSection(Message::SECTION_ANSWER));
+        i != response.endSection(Message::SECTION_ANSWER); ++ i) {
+        if ((*i)->getType() == RRType::CNAME()) {
+            ok = true;
+            RdataIteratorPtr ci((*i)->getRdataIterator());
+            ASSERT_FALSE(ci->isLast()) << "The CNAME is empty";
+            /*
+             * Does anybody have a clue why, if the Name::MAX_WIRE is put
+             * directly inside ASSERT_EQ, it fails to link and complains
+             * it is unresolved external?
+             */
+            const size_t max_len(Name::MAX_WIRE);
+            ASSERT_EQ(max_len, dynamic_cast<const rdata::generic::CNAME&>(
+                ci->getCurrent()).getCname().getLength());
+        }
+    }
+    EXPECT_TRUE(ok) << "The synthetized CNAME not found";
+}
+
 }

+ 1 - 1
src/bin/bind10/bind10.xml

@@ -131,7 +131,7 @@
             daemon to listen on.
             The default is 5300.</para>
 <!-- TODO: -->
-	    <note><simpara>The Y1 prototype release uses a non-default
+	    <note><simpara>This prototype release uses a non-default
 	    port for domain service.</simpara></note>
          </listitem>
       </varlistentry>

+ 1 - 1
src/bin/resolver/Makefile.am

@@ -37,7 +37,7 @@ spec_config.h: spec_config.h.pre
 BUILT_SOURCES = spec_config.h 
 pkglibexec_PROGRAMS = b10-resolver
 b10_resolver_SOURCES = resolver.cc resolver.h
-b10_resolver_SOURCES += response_classifier.cc response_classifier.h
+b10_resolver_SOURCES += response_scrubber.cc response_scrubber.h
 b10_resolver_SOURCES += $(top_builddir)/src/bin/auth/change_user.h
 b10_resolver_SOURCES += $(top_builddir)/src/bin/auth/common.h
 b10_resolver_SOURCES += main.cc

+ 19 - 3
src/bin/resolver/resolver.cc

@@ -21,7 +21,6 @@
 #include <cassert>
 
 #include <asiolink/asiolink.h>
-#include <asiolink/ioaddress.h>
 
 #include <boost/foreach.hpp>
 #include <boost/lexical_cast.hpp>
@@ -131,6 +130,9 @@ public:
         }
     }
 
+    void resolve(const isc::dns::QuestionPtr& question,
+        const isc::resolve::ResolverInterface::CallbackPtr& callback);
+
     void processNormalQuery(const Question& question,
                             MessagePtr answer_message,
                             OutputBufferPtr buffer,
@@ -333,7 +335,6 @@ Resolver::~Resolver() {
     delete checkin_;
     delete dns_lookup_;
     delete dns_answer_;
-    dlog("Deleting the Resolver",true);
 }
 
 void
@@ -352,6 +353,14 @@ Resolver::getConfigSession() const {
 }
 
 void
+Resolver::resolve(const isc::dns::QuestionPtr& question,
+    const isc::resolve::ResolverInterface::CallbackPtr& callback)
+{
+    impl_->resolve(question, callback);
+}
+
+
+void
 Resolver::processMessage(const IOMessage& io_message,
                          MessagePtr query_message,
                          MessagePtr answer_message,
@@ -435,13 +444,20 @@ Resolver::processMessage(const IOMessage& io_message,
 }
 
 void
+ResolverImpl::resolve(const QuestionPtr& question,
+    const isc::resolve::ResolverInterface::CallbackPtr& callback)
+{
+    rec_query_->resolve(question, callback);
+}
+
+void
 ResolverImpl::processNormalQuery(const Question& question,
                                  MessagePtr answer_message,
                                  OutputBufferPtr buffer,
                                  DNSServer* server)
 {
     dlog("Processing normal query");
-    rec_query_->sendQuery(question, answer_message, buffer, server);
+    rec_query_->resolve(question, answer_message, buffer, server);
 }
 
 namespace {

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

@@ -24,6 +24,8 @@
 
 #include <asiolink/asiolink.h>
 
+#include <resolve/resolver_interface.h>
+
 class ResolverImpl;
 
 /**
@@ -35,7 +37,7 @@ class ResolverImpl;
  * answer. It doesn't really know about chasing referrals and similar, it
  * simply plugs the parts that know into the network handling code.
  */
-class Resolver {
+class Resolver : public isc::resolve::ResolverInterface {
     ///
     /// \name Constructors, Assignment Operator and Destructor.
     ///
@@ -51,6 +53,10 @@ public:
     ~Resolver();
     //@}
 
+    virtual void resolve(
+        const isc::dns::QuestionPtr& question,
+        const isc::resolve::ResolverInterface::CallbackPtr& callback);
+
     /// \brief Process an incoming DNS message, then signal 'server' to resume 
     ///
     /// A DNS query (or other message) has been received by a \c DNSServer
@@ -59,7 +65,10 @@ public:
     /// send the reply.
     ///
     /// \param io_message The raw message received
-    /// \param message Pointer to the \c Message object
+    /// \param query_message Pointer to the query Message object we
+    /// received from the client
+    /// \param answer_message Pointer to the anwer Message object we
+    /// shall return to the client
     /// \param buffer Pointer to an \c OutputBuffer for the resposne
     /// \param server Pointer to the \c DNSServer
     void processMessage(const asiolink::IOMessage& io_message,
@@ -140,7 +149,11 @@ public:
      * \short Set options related to timeouts.
      *
      * This sets the time of timeout and number of retries.
-     * \param timeout The time in milliseconds. The value -1 disables timeouts.
+     * \param query_timeout The timeout we use for queries we send
+     * \param client_timeout The timeout at which point we send back a
+     * SERVFAIL (while continuing to resolve the query)
+     * \param lookup_timeout The timeout at which point we give up and
+     * stop.
      * \param retries The number of retries (0 means try the first time only,
      *     do not retry).
      */

+ 189 - 0
src/bin/resolver/response_scrubber.cc

@@ -0,0 +1,189 @@
+
+// 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 <iostream>
+#include <vector>
+#include <dns/message.h>
+#include <dns/rrset.h>
+#include <dns/name.h>
+#include "response_scrubber.h"
+
+using namespace isc::dns;
+using namespace std;
+
+// Compare addresses etc.
+
+ResponseScrubber::Category ResponseScrubber::addressCheck(
+    const asiolink::IOEndpoint& to, const asiolink::IOEndpoint& from)
+{
+    if (from.getProtocol() == to.getProtocol()) {
+        if (from.getAddress() == to.getAddress()) {
+            if (from.getPort() == to.getPort()) {
+                return (ResponseScrubber::SUCCESS);
+            } else {
+                return (ResponseScrubber::PORT);
+            }
+        } else {
+            return (ResponseScrubber::ADDRESS);
+        }
+    }
+    return (ResponseScrubber::PROTOCOL);
+}
+
+// Do a general scrubbing.  The QNAMES of RRsets in the specified section are
+// compared against the list of name given and if they are not equal and not in
+// the specified relationship (generally superdomain or subdomain) to at least
+// of of the given names, they are removed.
+
+unsigned int
+ResponseScrubber::scrubSection(Message& message,
+    const vector<const Name*>& names,
+    const NameComparisonResult::NameRelation connection, 
+    const Message::Section section)
+{
+    unsigned int count = 0;     // Count of RRsets removed
+    unsigned int kept = 0;      // Count of RRsets kept
+    bool removed = true;        // Set true if RRset removed in a pass
+
+    // Need to go through the section multiple times as when an RRset is
+    // removed, all iterators into the section are invalidated.  This condition
+    // is flagged by "remove" being set true when an RRset is removed.
+
+    while (removed) {
+        RRsetIterator i = message.beginSection(section);
+
+        // Skips the ones that have been checked (and retained) in a previous
+        // pass through the "while" loop.  (Although RRset removal invalidates
+        // iterators, it does not change the relative order of the retained
+        // RRsets in the section.)
+        for (int j = 0; j < kept; ++j) {
+            ++i;
+        }
+
+        // Start looking at the remaining entries in the section.
+        removed = false;
+        for (; (i != message.endSection(section)) && (!removed); ++i) {
+
+            // Loop through the list of names given and see if any are in the
+            // given relationship with the QNAME of this RRset
+            bool nomatch = true;
+            for (vector<const Name*>::const_iterator n = names.begin();
+                ((n != names.end()) && nomatch); ++n) {
+                NameComparisonResult result = (*i)->getName().compare(**n);
+                NameComparisonResult::NameRelation relationship =
+                    result.getRelation();
+                if ((relationship == NameComparisonResult::EQUAL) ||
+                   (relationship == connection)) {
+                    
+                    // RRset in the specified relationship, so a match has
+                    // been found
+                    nomatch = false;
+                }
+            }
+
+            // Remove the RRset if there was no match to one of the given names.
+            if (nomatch) {
+                message.removeRRset(section, i);
+                ++count;            // One more RRset removed
+                removed = true;     // Something was removed
+             } else {
+
+                // There was a match so this is one more entry we can skip next
+                // time.
+                ++kept;
+             }
+        }
+    }
+
+    return count;
+}
+
+// Perform the scrubbing of all sections of the message.
+
+unsigned int
+ResponseScrubber::scrubAllSections(Message& message, const Name& bailiwick) {
+
+    // Leave the question section alone.  Just go through the RRsets in the
+    // answer, authority and additional sections.
+    unsigned int count = 0;
+    const vector<const Name*> bailiwick_names(1, &bailiwick);
+    count += scrubSection(message, bailiwick_names,
+            NameComparisonResult::SUBDOMAIN, Message::SECTION_ANSWER);
+    count += scrubSection(message, bailiwick_names,
+            NameComparisonResult::SUBDOMAIN, Message::SECTION_AUTHORITY);
+    count += scrubSection(message, bailiwick_names,
+            NameComparisonResult::SUBDOMAIN, Message::SECTION_ADDITIONAL);
+
+    return count;
+}
+
+// Scrub across sections.
+
+unsigned int
+ResponseScrubber::scrubCrossSections(isc::dns::Message& message) {
+
+    // Get a list of the names in the answer section or, failing this, the
+    // question section.  Note that pointers to the names within "message" are
+    // stored; this is OK as the relevant sections in "message" will not change
+    // during the lifetime of this method (it only affects the authority
+    // section).
+    vector<const Name*> source;
+    if (message.getRRCount(Message::SECTION_ANSWER) != 0) {
+        for (RRsetIterator i = message.beginSection(Message::SECTION_ANSWER);
+            i != message.endSection(Message::SECTION_ANSWER); ++i) {
+            const Name& qname = (*i)->getName();
+            source.push_back(&qname);
+        }
+
+    } else {
+        for (QuestionIterator i = message.beginQuestion();
+            i != message.endQuestion(); ++i) {
+            const Name& qname = (*i)->getName();
+            source.push_back(&qname);
+        }
+    }
+
+    if (source.empty()) {
+        // TODO: Log the fact - should be at least a question present
+        return (0);
+    }
+
+    // Could be duplicates, especially in the answer section, so sort the
+    // names and remove them.
+    sort(source.begin(), source.end(), ResponseScrubber::compareNameLt);
+    vector<const Name*>::iterator endunique =
+        unique(source.begin(), source.end(), ResponseScrubber::compareNameEq);
+    source.erase(endunique, source.end());
+
+    // Now purge the authority section of RRsets that are not equal to or a
+    // superdomain of the names in the question/answer section.
+    return (scrubSection(message, source,
+        NameComparisonResult::SUPERDOMAIN, Message::SECTION_AUTHORITY));
+
+}
+
+// Scrub a message
+
+unsigned int
+ResponseScrubber::scrub(const isc::dns::MessagePtr& message,
+    const isc::dns::Name& bailiwick)
+{
+    unsigned int sections_removed = scrubAllSections(*message, bailiwick);
+    sections_removed += scrubCrossSections(*message);
+
+    return sections_removed;
+}
+
+

+ 422 - 0
src/bin/resolver/response_scrubber.h

@@ -0,0 +1,422 @@
+// 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.
+
+// $Id$
+
+#ifndef __RESPONSE_SCRUBBER_H
+#define __RESPONSE_SCRUBBER_H
+
+/// \page DataScrubbing Data Scrubbing
+/// \section DataScrubbingIntro Introduction
+/// When a response is received from an authoritative server, it should be
+/// checked to ensure that the data contained in it is valid.  Signed data is
+/// not a problem - validating the signatures is a sufficient check.  But
+/// unsigned data in a response is more of a problem. (Note that even data from
+/// signed zones may be not be signed, e.g. delegations are not signed.) In
+/// particular, how do we know that the server from which the response was
+/// received was authoritive for the data it returned?
+///
+/// The part of the code that checks for this is the "Data Scrubbing" module.
+/// Although it includes the checking of IP addresses and ports, it is called
+/// "Scrubbing" because it "scrubs" the returned message and removes doubtful
+/// information.
+///
+/// \section DataScrubbingBasic Basic Checks
+/// The first part - how do we know that the response comes from the correct
+/// server - is relatively trivial, albeit not foolproof (which is why DNSSEC
+/// was developed).  The following are checked:
+///
+/// - The IP address from which the response was received is the same as the
+///   one to which the query was sent.
+/// - The port on which the response was received is the same as the one from
+///   which the query was sent.
+///
+/// (These tests need not not done for a TCP connection - if data is received
+/// over the TCP stream, it is assumed that it comes from the address and port
+/// to which a connection was made.)
+///
+/// - The protocol used to send the question is the same as the protocol on
+///   which an answer was received.
+///
+/// (Strictly speaking, if this check fails it is a programming error - the
+/// code should not mix up UPD and TCP messages.)
+///
+/// - The QID in the response message is the same as the QID in the query
+///   message sent.
+///
+/// If the conditions are met, then the data - in all three response sections -
+/// is scanned and out of bailiwick data is removed ("scrubbed").
+///
+/// \section DataScrubbingBailiwick Bailiwick
+/// Bailiwick means "district or jurisdiction of bailie or bailiff" (Concise
+/// Oxford Dictionary, 7th Edition).  It is not a term mentioned in any RFC
+/// (or at least, any RFC up to RFC 5997) but is widely used in DNS literature.
+/// In this context it is taken to mean the data for which a DNS server has
+/// authority.  So when we speak of the information being "in bailiwick", we
+/// mean that the the server is the ultimate source of authority for that data.
+///
+/// In practice, determining this from the response alone is difficult.  In
+/// particular, as a server may be authoritative for many zones, it could in
+/// theory be authoritative for any combination of RRsets that appear in a
+/// response.
+///
+/// For this reason, bailiwick is dependent on the query.  If, for example, a
+/// query for www.example.com is sent to the nameservers for example.com
+/// (because of a referral of from the com. servers), the bailiwick for the
+/// query is example.com.  This means that any information returned on domains
+/// other than example.com may not be authoritative.  More exactly, it may be
+/// authoritative (because the server is also authoritative for the zone
+/// concerned), but based on the information available (in this example, that
+/// the response originated from a nameserver for the zone example.com) it is
+/// not possible to be certain.
+///
+/// Ideally, out of bailiwick data should be excluded from further processing
+/// as it may be incorrect and corrupt the cache.  In practice, there are
+/// two cases to consider:
+///
+/// The first is when the data has a qname that is not example.com or a
+/// subdomain of it (e.g. xyz.com, www.example.net).  In this case the data can
+/// be retrieved by an independent query - no path from the root zone to the
+/// data goes through the current bailiwick, so there is no chance of ending up
+/// in a loop.  In this case, data that appears to be out of bailiwick can be
+/// dropped from the response.
+///
+/// The second case is when the QNAME of the data is a subdomain of the
+/// bailiwick.  Here the server may or may not be authoritative for the data.
+/// For example, if the name queried for were www.sub.example.com and the
+/// example.com nameservers supplied an answer:
+///
+/// - The answer could be authoritative - www.sub.example.com could be
+///   in the example.com zone.
+/// - The answer might not be authoritative - the zone sub.example.com may have
+///   been delegated, so the authoritative answer should come from
+///   sub.example.com's nameservers.
+/// - The answer might be authoritative even though zone sub.example.com has
+///   been delegated, because the nameserver for example.com is the same as
+///   that for sub.example.com.
+///
+/// Unlike the previous case, it is not possible to err on the side of caution
+/// and drop such data.  Any independent query for it will pass through the
+/// current bailiwick and the same question will be asked again.  For this
+/// reason, any data in the response that has a QNAME equal to a subdomain of
+/// the bailiwick has to be accepted.
+///
+/// In summary then, data in a response that has a QNAME equal to or a subdomain
+/// of the bailiwick is considered in-bailiwick.  Anything else is out of of
+/// bailiwick.
+///
+/// \subsection DataScrubbingCrossSection Cross-Section Scrubbing
+/// Even with the bailiwick checks above, there are some additional cleaning
+/// that can be done with the packet.  In particular:
+///
+/// - The QNAMEs of the RRsets in the authority section must be equal to or
+///   superdomains of a QNAME of an RRset in the answer.  Any that are not
+///   should be removed.
+/// - If there is no answer section, the QNAMES of RRsets in the authority
+///   section must be equal to or superdomains of the QNAME of the RRset in the
+///   question.
+///
+/// Although previous checks should have removed some inconsistencies, it
+/// will not trap obscure cases (e.g. bailiwick: "example.com", answer:
+/// "www.example.com", authority: sub.example.com).  These checks do just that.
+///
+/// (Note that not included here is QNAME of question not equal to or a
+/// superdomain of the answer; that check is made in the ResponseClassifier
+/// class.)
+///
+/// \section DataScrubbingExample Examples
+/// Some examples should make this clear: they all use the notation
+/// Qu = Question, Zo = Zone being queried, An = Answer, Au = Authority,
+/// Ad = Additional.
+///
+/// \subsection DataScrubbingEx1 Example 1: Simple Query
+/// Querying a nameserver for the zone "example.com" for www.example.com and
+/// receiving the answer "www.example.com A 1.2.3.4" with two nameservers quoted
+/// as authority and both their addresses in the additional section:
+///
+/// Qu: www.example.com\n
+/// Zo: example.com
+///
+/// An: www.example.com A 192.0.2.1
+///
+/// Au(1): example.com NS ns0.example.com\n
+/// Au(2): example.com NS ns1.example.net
+///
+/// Ad(1): ns0.example.com A 192.0.2.100\n
+/// Ad(2): ns1.example.net A 192.0.2.200
+///
+/// This answer could be returned by a properly configured server.  All resource
+/// records in the answer - with the exception of Ad(2) - are in bailiwick
+/// because the QNAME is equal to or a subdomain of the zone being queried.
+///
+/// It is permissible for Ad(2) to be returned by a properly configured server
+/// as a hint to resolvers.  However the example.com nameservers are not
+/// authoritative for addresses of domains in example.net; that record could
+/// be out of date or incorrect.  Indeed, it might even be a deliberate attempt
+/// at a spoof by getting us to cache an invalid address for ns1.example.net.
+/// The safest thing to do is to drop the A record and to get the address of
+/// ns1.example.net by querying for that name through the .net nameservers.
+///
+/// \subsection DataScrubbingEx2 Example 2: Multiple Zones on Same Nameserver
+/// Assume now that example.com and sub.example.com are hosted on the same
+/// nameserver and that from the .com zone the resolver has received a referral
+/// to example.com.  Suppose that the query is for www.sub.example.com and that
+/// the following response is received:
+///
+/// Qu: www.sub.example.com\n
+/// Zo: example.com
+///
+/// An: <nothing>
+///
+/// Au(1): sub.example.com NS ns0.sub.example.com\n
+/// Au(2): sub.example.com NS ns1.example.net
+///
+/// Ad(1): ns0.sub.example.com A 192.0.2.101\n
+/// Ad(2): ns1.example.net A 192.0.2.201
+///
+/// Although we asked the example.com nameservers for information, we got the
+/// nameservers for sub.example.com in the authority section.  This is valid
+/// because if BIND-10 hosts multiple zones, it will look up the data in the
+/// zone that most closely matches the query.
+///
+/// Using the criteria above, the data in the additional section can therefore
+/// be regarded as in bailiwick because sub.example.com is a subdomain of
+/// example.com.  As before though, the address for ns1.example.net in the
+/// additional section is not in bailiwick because ns1.example.net is now a
+/// subdomain of example.com.
+///
+/// \subsection DataScrubbingEx3 Example 3: Deliberate Spoof Attempt
+/// Qu: www.example.com\n
+/// Zo: example.com
+///
+/// An: www.example.com A 192.0.2.1
+///
+/// Au(1): com NS ns0.example.com\n
+/// Au(2): com NS ns1.example.net
+///
+/// Ad(1): ns0.example.com A 192.0.2.100\n
+/// Ad(2): ns1.example.net A 192.0.2.200
+///
+/// This is a deliberately invalid response.  The query is being sent to the
+/// nameservers for example.com (presumably because a referral to example.com
+/// was received from the com nameservers), but the response is an attempt
+/// to get the specified nameservers cached as the nameservers for com - for
+/// which example.com is not authoritative.
+///
+/// Note though that this response is only invalid because, due to the previous
+/// referral, the query was sent to the example.com nameservers.  Had the
+/// referral been to the com nameservers, it would be a valid response; the com
+/// zone could well be serving all the data for example.com.  Having said that,
+/// the A record for ns1.example.net would still be regarded as being out of
+/// bailiwick becase the nameserver is not authoritative for the .net zone.
+///
+/// \subsection DataScrubbingEx4 Example 4: Inconsistent Answer Section
+/// Qu: www.example.com\n
+/// Zo: example.com
+///
+/// An: www.example.com A 192.0.2.1
+///
+/// Au(1): alpha.example.com NS ns0.example.com\n
+/// Au(2): alpha.example.com NS ns1.example.net
+///
+/// Ad(1): ns0.example.com A 192.0.2.100\n
+/// Ad(2): ns1.example.net A 192.0.2.200
+///
+/// Here, everything in the answer and authority sections is in bailiwick for
+/// the example.com server. And although the zone example.com was queried, it
+/// is permissible for the authority section to contain nameservers with a
+/// qname that is a subdomain of example.com (e.g. see \ref DataScrubbingEx2).
+/// However, only servers with a qname that is equal to or a superdomain of
+/// the answer are authoritative for the answer.  So in this case, both
+/// Au(1) and Au(2) (as well as Ad(2), for reasons given earlier) will be
+/// scrubbed.
+
+#include <config.h>
+#include <asiolink/io_endpoint.h>
+#include <dns/message.h>
+#include <dns/name.h>
+
+/// \brief Response Data Scrubbing
+///
+/// This is the class that implements the data scrubbing.  Given a response
+/// message and some additional information, it checks the information using
+/// the rules given in \ref DataScrubbing and either rejects the packet or
+/// modifies it to remove non-conforming RRsets.
+///
+/// TODO: Examine the additional records and remove all cases where the
+/// QNAME does not match the RDATA of records in the authority section.
+
+class ResponseScrubber {
+public:
+
+    /// \brief Response Code for Address Check
+    enum Category {
+        SUCCESS = 0,            ///< Packet is OK
+
+        // Error categories
+
+        ADDRESS = 1,            ///< Mismatching IP address
+        PORT = 2,               ///< Mismatching port
+        PROTOCOL = 3            ///< Mismatching protocol
+    };
+
+    /// \brief Check IP Address
+    ///
+    /// Compares the address to which the query was sent, the port it was
+    /// sent from, and the protocol used for communication with the (address,
+    /// port, protocol) from which the response was received.
+    ///
+    /// \param to Endpoint representing the address to which the query was sent.
+    /// \param from Endpoint from which the response was received.
+    ///
+    /// \return SUCCESS if the two endpoints match, otherwise an error status
+    /// indicating what was incorrect.
+    static Category addressCheck(const asiolink::IOEndpoint& to,
+        const asiolink::IOEndpoint& from);
+
+    /// \brief Check QID
+    ///
+    /// Compares the QID in the sent message with the QID in the response.
+    ///
+    /// \param sent Message sent to the authoritative server
+    /// \param received Message received from the authoritative server
+    ///
+    /// \return true if the QIDs match, false otherwise.
+    static bool qidCheck(const isc::dns::Message& sent,
+        const isc::dns::Message& received) {
+        return (sent.getQid() == received.getQid());
+    }
+
+    /// \brief Generalised Scrub Message Section
+    ///
+    /// When scrubbing a message given the bailiwick of the server, RRsets are
+    /// retained in the message section if the QNAME is equal to or a subdomain
+    /// of the bailiwick.  However, when checking QNAME of RRsets in the
+    /// authority section against the QNAME of the question or answers, RRsets
+    /// are retained only if their QNAME is equal to or a superdomain of the
+    /// name in question.
+    ///
+    /// This method provides the generalised scrubbing whereby the RRsets in
+    /// a section are tested against a given name, and RRsets kept if their
+    /// QNAME is equal to or in the supplied relationship with the given name.
+    ///
+    /// \param section Section of the message to be scrubbed.
+    /// \param zone Names against which RRsets should be checked.  Note that
+    /// this is a vector of pointers to Name objects; they are assumed to
+    /// independently exist, and the caller retains ownership of them and is
+    /// assumed to destroy them when needed.
+    /// \param connection Relationship required for retention, i.e. the QNAME of
+    /// an RRset in the specified section must be equal to or a "connection"
+    /// (SUPERDOMAIN/SUBDOMAIN) of "name" for the RRset to be retained.
+    /// \param message Message to be scrubbed.
+    ///
+    /// \return Count of the number of RRsets removed from the section.
+    static unsigned int scrubSection(isc::dns::Message& message,
+        const std::vector<const isc::dns::Name*>& names,
+        const isc::dns::NameComparisonResult::NameRelation connection,
+        const isc::dns::Message::Section section);
+
+    /// \brief Scrub All Sections of a Message
+    ///
+    /// Scrubs each of the answer, authority and additional sections of the
+    /// message.
+    ///
+    /// No distinction is made between RRsets legitimately in the message (e.g.
+    /// glue for authorities that are not in bailiwick) and ones that could be
+    /// considered as attempts of spoofing (e.g. non-bailiwick RRsets in the
+    /// additional section that are not related to the query).
+    ///
+    /// The resultant packet returned to the caller may be invalid.  If so, it
+    /// is up to the caller to detect that.
+    ///
+    /// \param message Message to be scrubbed.
+    /// \param bailiwick Name of the zone whose authoritative servers were
+    /// queried.
+    ///
+    /// \return Count of the number of RRsets removed from the message.
+    static unsigned int scrubAllSections(isc::dns::Message& message,
+        const isc::dns::Name& bailiwick);
+
+    /// \brief Scrub Across Message Sections
+    ///
+    /// Does some cross-section comparisons and removes inconsistent RRs.  In
+    /// particular it:
+    ///
+    /// - If an answer is present, checks that the qname of the authority RRs
+    ///   are equal to or superdomain of the qname answer RRsets.  Any that are
+    ///   not are removed.
+    /// - If an answer is not present, checks that the authority RRs are
+    ///   equal to or superdomains of the question.  If not, the authority RRs
+    ///   are removed.
+    ///
+    /// Note that the scrubbing does not check:
+    ///
+    /// - that the question is in the bailiwick of the server; that check is
+    ///   assumed to have been done prior to the query being sent (else why
+    ///   was the query sent there in the first place?)
+    /// - that the qname of one of the RRsets in the answer (if present) is
+    ///   equal to the qname of the question (that check is done in the
+    ///   response classification code).
+    ///
+    /// \param message Message to be scrubbed.
+    ///
+    /// \return Count of the number of RRsets removed from the section.
+    static unsigned int scrubCrossSections(isc::dns::Message& message);
+    
+    /// \brief Main Scrubbing Entry Point
+    ///
+    /// The single entry point to the module to sanitise the message.  All
+    /// it does is call the various other scrubbing methods.
+    ///
+    /// \param message Pointer to the message to be scrubbed. (This is a
+    /// pointer - as opposed to a Message as in other methods in this class -
+    /// as the external code is expected to be mainly using message pointers
+    /// to access messages.)
+    /// \param bailiwick Name of the zone whose authoritative servers were
+    /// queried.
+    ///
+    /// \return Count of the number of RRsets removed from the message.
+    static unsigned int scrub(const isc::dns::MessagePtr& message,
+        const isc::dns::Name& bailiwick);
+
+    /// \brief Comparison Function for Sorting Name Pointers
+    ///
+    /// Utility method called to sorts pointers to names in lexical order.
+    ///
+    /// \param n1 Pointer to first Name object
+    /// \param n2 Pointer to second Name object
+    ///
+    /// \return true if n1 is less than n2, false otherwise.
+    static bool compareNameLt(const isc::dns::Name* n1,
+        const isc::dns::Name* n2)
+    {
+        return (*n1 < *n2);
+    }
+
+    /// \brief Function for Comparing Name Pointers
+    ///
+    /// Utility method called to sorts pointers to names in lexical order.
+    ///
+    /// \param n1 Pointer to first Name object
+    /// \param n2 Pointer to second Name object
+    ///
+    /// \return true if n1 is equal to n2, false otherwise.
+    static bool compareNameEq(const isc::dns::Name* n1,
+        const isc::dns::Name* n2)
+    {
+        return (*n1 == *n2);
+    }
+};
+
+#endif // __RESPONSE_SCRUBBER_H

+ 13 - 5
src/bin/resolver/tests/Makefile.am

@@ -4,7 +4,6 @@ AM_CPPFLAGS += -I$(top_builddir)/src/lib/cc
 AM_CPPFLAGS += -DTEST_DATA_DIR=\"$(top_srcdir)/src/lib/testutils/testdata\"
 AM_CPPFLAGS += -DTEST_DATA_BUILDDIR=\"$(abs_top_builddir)/src/lib/testutils/testdata\"
 AM_CPPFLAGS += $(BOOST_INCLUDES)
-AM_CPPFLAGS += $(BOOST_INCLUDES)
 
 AM_CXXFLAGS = $(B10_CXXFLAGS)
 
@@ -20,24 +19,33 @@ TESTS += run_unittests
 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 += ../resolver.h ../resolver.cc
-run_unittests_SOURCES += ../response_classifier.h ../response_classifier.cc
+run_unittests_SOURCES += ../response_scrubber.h ../response_scrubber.cc
 run_unittests_SOURCES += resolver_unittest.cc
 run_unittests_SOURCES += resolver_config_unittest.cc
-run_unittests_SOURCES += response_classifier_unittest.cc
+run_unittests_SOURCES += response_scrubber_unittest.cc
 run_unittests_SOURCES += run_unittests.cc
 run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
 run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
 run_unittests_LDADD = $(GTEST_LDADD)
 run_unittests_LDADD += $(SQLITE_LIBS)
 run_unittests_LDADD += $(top_builddir)/src/lib/testutils/libtestutils.la
-run_unittests_LDADD +=  $(top_builddir)/src/lib/datasrc/libdatasrc.la
-run_unittests_LDADD +=  $(top_builddir)/src/lib/dns/libdns++.la
+run_unittests_LDADD += $(top_builddir)/src/lib/datasrc/libdatasrc.la
+run_unittests_LDADD += $(top_builddir)/src/lib/dns/libdns++.la
 run_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libasiolink.la
 run_unittests_LDADD += $(top_builddir)/src/lib/config/libcfgclient.la
 run_unittests_LDADD += $(top_builddir)/src/lib/cc/libcc.la
 run_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
 run_unittests_LDADD += $(top_builddir)/src/lib/xfr/libxfr.la
 run_unittests_LDADD += $(top_builddir)/src/lib/log/liblog.la
+
+# Note the ordering matters: -Wno-... must follow -Wextra (defined in
+# B10_CXXFLAGS
+run_unittests_CXXFLAGS = $(AM_CXXFLAGS)
+if USE_GXX
+run_unittests_CXXFLAGS += -Wno-unused-parameter
+endif
 endif
 
+
+
 noinst_PROGRAMS = $(TESTS)

+ 542 - 0
src/bin/resolver/tests/response_scrubber_unittest.cc

@@ -0,0 +1,542 @@
+// 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.
+
+// $Id$
+
+#include <string>
+#include <iostream>
+
+#include <gtest/gtest.h>
+
+#include <config.h>
+
+#include <asiolink/io_endpoint.h>
+#include <asiolink/io_address.h>
+#include <netinet/in.h>
+
+#include <dns/name.h>
+#include <dns/opcode.h>
+#include <dns/question.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+#include <dns/rcode.h>
+#include <dns/rrclass.h>
+#include <dns/rrset.h>
+#include <dns/rrtype.h>
+#include <dns/rrttl.h>
+#include <resolver/response_scrubber.h>
+
+
+// Class for endpoint checks.  The family of the endpoint is set in the
+// constructor; the address family by the string provided for the address.
+
+namespace asiolink {
+
+class GenericEndpoint : public IOEndpoint {
+public:
+    GenericEndpoint(const std::string& address, uint16_t port, short protocol) :
+        address_(address), port_(port), protocol_(protocol)
+    {}
+    virtual ~GenericEndpoint()
+    {}
+
+    virtual IOAddress getAddress() const {
+        return address_;
+    }
+
+    virtual uint16_t getPort() const {
+        return port_;
+    }
+
+    virtual short getProtocol() const {
+        return protocol_;
+    }
+
+    virtual short getFamily() const {
+        return address_.getFamily();
+    }
+
+private:
+    IOAddress   address_;        // Address of endpoint
+    uint16_t    port_;          // Port number of endpoint
+    short       protocol_;      // Protocol of the endpoint
+    };
+}
+
+using namespace asio::ip;
+using namespace isc::dns;
+using namespace rdata;
+using namespace isc::dns::rdata::generic;
+using namespace isc::dns::rdata::in;
+using namespace asiolink;
+
+// Test class
+
+namespace {
+class ResponseScrubberTest : public ::testing::Test {
+public:
+    ResponseScrubberTest() :
+        bailiwick("example.com"),
+
+        qu_in_any_www(Name("www.example.com"), RRClass::IN(), RRType::ANY()),
+        qu_in_a_www(Name("www.example.com"), RRClass::IN(), RRType::A()),
+        qu_in_ns(Name("example.com"), RRClass::IN(), RRType::NS()),
+        qu_in_txt_www(Name("www.example.com"), RRClass::IN(), RRType::TXT()),
+        rrs_in_a_org(new RRset(Name("mail.example.org"), RRClass::IN(),
+            RRType::A(), RRTTL(300))),
+
+        rrs_in_a_net(new RRset(Name("mail.example.net"), RRClass::IN(),
+            RRType::A(), RRTTL(300))),
+        rrs_in_a_www(new RRset(Name("www.example.com"), RRClass::IN(),
+            RRType::A(), RRTTL(300))),
+        rrs_in_cname_www(new RRset(Name("www.example.com"), RRClass::IN(),
+            RRType::CNAME(), RRTTL(300))),
+        rrs_in_a_wwwnet(new RRset(Name("www.example.net"), RRClass::IN(),
+            RRType::A(), RRTTL(300))),
+        rrs_in_ns(new RRset(Name("example.com"), RRClass::IN(),
+            RRType::NS(), RRTTL(300))),
+        rrs_in_ns_com(new RRset(Name("com"), RRClass::IN(),
+            RRType::NS(), RRTTL(300))),
+        rrs_in_ns_net(new RRset(Name("example.net"), RRClass::IN(),
+            RRType::NS(), RRTTL(300))),
+        rrs_in_ns_sub(new RRset(Name("subdomain.example.com"), RRClass::IN(),
+            RRType::NS(), RRTTL(300))),
+        rrs_in_ns_sub2(new RRset(Name("subdomain2.example.com"), RRClass::IN(),
+            RRType::NS(), RRTTL(300))),
+        rrs_in_a_ns0(new RRset(Name("ns0.example.com"), RRClass::IN(),
+            RRType::A(), RRTTL(300))),
+        rrs_in_a_ns1(new RRset(Name("ns1.com"), RRClass::IN(),
+            RRType::A(), RRTTL(300))),
+        rrs_in_a_ns2(new RRset(Name("ns2.example.net"), RRClass::IN(),
+            RRType::A(), RRTTL(300))),
+        rrs_in_a_ns3(new RRset(Name("ns3.subdomain.example.com"), RRClass::IN(),
+            RRType::A(), RRTTL(300))),
+        rrs_in_txt_www(new RRset(Name("www.example.com"), RRClass::IN(),
+            RRType::TXT(), RRTTL(300)))
+    {}
+    Name        bailiwick;          // Bailiwick of the server queried
+    Question    qu_in_any_www;      // www.example.com IN ANY
+    Question    qu_in_a_www;        // www.example.com IN A
+    Question    qu_in_ns;           // example.com IN NS
+    Question    qu_in_txt_www;      // www.example.com IN TXT
+    RRsetPtr    rrs_in_a_org;       // mail.example.org IN A
+    RRsetPtr    rrs_in_a_net;       // mail.example.org IN A
+    RRsetPtr    rrs_in_a_www;       // www.example.com IN A
+    RRsetPtr    rrs_in_cname_www;   // www.example.com IN CNAME
+    RRsetPtr    rrs_in_a_wwwnet;    // www.example.net IN A
+    RRsetPtr    rrs_in_ns;          // example.com IN NS
+    RRsetPtr    rrs_in_ns_com;      // com IN NS
+    RRsetPtr    rrs_in_ns_net;      // example.net IN NS
+    RRsetPtr    rrs_in_ns_sub;      // subdomain.example.com IN NS
+    RRsetPtr    rrs_in_ns_sub2;     // subdomain2.example.com IN NS
+    RRsetPtr    rrs_in_a_ns0;       // ns0.example.com IN A
+    RRsetPtr    rrs_in_a_ns1;       // ns1.com IN A
+    RRsetPtr    rrs_in_a_ns2;       // ns2.example.net IN A
+    RRsetPtr    rrs_in_a_ns3;       // ns3.subdomain.example.net IN A
+    RRsetPtr    rrs_in_txt_www;     // www.example.com IN TXT
+};
+
+
+// Check that the IP addresses/ports/protocol for the packets sent and received
+// both match if both types are IP V4.
+
+TEST_F(ResponseScrubberTest, UDPv4) {
+
+    // Basic UDP Endpoint
+    GenericEndpoint udp_a("192.0.2.1", 12345, IPPROTO_UDP);
+
+    // Same address, port
+    GenericEndpoint udp_b("192.0.2.1", 12345, IPPROTO_UDP);
+    EXPECT_EQ(ResponseScrubber::SUCCESS,
+        ResponseScrubber::addressCheck(udp_a, udp_b));
+
+    // Different address, same port
+    GenericEndpoint udp_c("192.0.2.2", 12345, IPPROTO_UDP);
+    EXPECT_EQ(ResponseScrubber::ADDRESS,
+        ResponseScrubber::addressCheck(udp_a, udp_c));
+
+    // Same address, different port
+    GenericEndpoint udp_d("192.0.2.1", 12346, IPPROTO_UDP);
+    EXPECT_EQ(ResponseScrubber::PORT,
+        ResponseScrubber::addressCheck(udp_a, udp_d));
+
+    // Different address, different port
+    GenericEndpoint udp_e("192.0.2.3", 12347, IPPROTO_UDP);
+    EXPECT_EQ(ResponseScrubber::ADDRESS,
+        ResponseScrubber::addressCheck(udp_a, udp_e));
+
+}
+
+// Repeat the tests for TCP
+
+TEST_F(ResponseScrubberTest, TCPv4) {
+
+    // Basic TCP Endpoint
+    GenericEndpoint tcp_a("192.0.2.1", 12345, IPPROTO_TCP);
+
+    // Same address, port
+    GenericEndpoint tcp_b("192.0.2.1", 12345, IPPROTO_TCP);
+    EXPECT_EQ(ResponseScrubber::SUCCESS,
+        ResponseScrubber::addressCheck(tcp_a, tcp_b));
+
+    // Different address, same port
+    GenericEndpoint tcp_c("192.0.2.2", 12345, IPPROTO_TCP);
+    EXPECT_EQ(ResponseScrubber::ADDRESS,
+        ResponseScrubber::addressCheck(tcp_a, tcp_c));
+
+    // Same address, different port
+    GenericEndpoint tcp_d("192.0.2.1", 12346, IPPROTO_TCP);
+    EXPECT_EQ(ResponseScrubber::PORT,
+        ResponseScrubber::addressCheck(tcp_a, tcp_d));
+
+    // Different address, different port
+    GenericEndpoint tcp_e("192.0.2.3", 12347, IPPROTO_TCP);
+    EXPECT_EQ(ResponseScrubber::ADDRESS,
+        ResponseScrubber::addressCheck(tcp_a, tcp_e));
+
+}
+
+// Repeat the tests for UDP/IPv6
+
+TEST_F(ResponseScrubberTest, UDPv6) {
+
+    // Basic UDP Endpoint
+    GenericEndpoint  udp_a("2001:db8::1", 12345, IPPROTO_UDP);
+
+    // Same address and port
+    GenericEndpoint  udp_b("2001:db8::1", 12345, IPPROTO_UDP);
+    EXPECT_EQ(ResponseScrubber::SUCCESS,
+        ResponseScrubber::addressCheck(udp_a, udp_b));
+
+    // Different address, same port
+    GenericEndpoint  udp_c("2001:db8::3", 12345, IPPROTO_UDP);
+    EXPECT_EQ(ResponseScrubber::ADDRESS,
+        ResponseScrubber::addressCheck(udp_a, udp_c));
+
+    // Same address, different port
+    GenericEndpoint  udp_d("2001:db8::1", 12346, IPPROTO_UDP);
+    EXPECT_EQ(ResponseScrubber::PORT,
+        ResponseScrubber::addressCheck(udp_a, udp_d));
+
+    // Different address, different port
+    GenericEndpoint  udp_e("2001:db8::3", 12347, IPPROTO_UDP);
+    EXPECT_EQ(ResponseScrubber::ADDRESS,
+        ResponseScrubber::addressCheck(udp_a, udp_e));
+
+}
+
+// Same again for TCP/IPv6
+
+TEST_F(ResponseScrubberTest, TCPv6) {
+
+    // Basic TCP Endpoint
+    GenericEndpoint  tcp_a("2001:db8::1", 12345, IPPROTO_TCP);
+
+    // Same address and port
+    GenericEndpoint  tcp_b("2001:db8::1", 12345, IPPROTO_TCP);
+    EXPECT_EQ(ResponseScrubber::SUCCESS,
+        ResponseScrubber::addressCheck(tcp_a, tcp_b));
+
+    // Different address, same port
+    GenericEndpoint  tcp_c("2001:db8::3", 12345, IPPROTO_TCP);
+    EXPECT_EQ(ResponseScrubber::ADDRESS,
+        ResponseScrubber::addressCheck(tcp_a, tcp_c));
+
+    // Same address, different port
+    GenericEndpoint  tcp_d("2001:db8::1", 12346, IPPROTO_TCP);
+    EXPECT_EQ(ResponseScrubber::PORT,
+        ResponseScrubber::addressCheck(tcp_a, tcp_d));
+
+    // Different address, different port
+    GenericEndpoint  tcp_e("2001:db8::3", 12347, IPPROTO_TCP);
+    EXPECT_EQ(ResponseScrubber::ADDRESS,
+        ResponseScrubber::addressCheck(tcp_a, tcp_e));
+
+}
+
+// Ensure that mixed IPv4/6 addresses don't match.
+
+TEST_F(ResponseScrubberTest, v4v6) {
+
+    // UDP
+    GenericEndpoint  udp_a("2001:db8::1", 12345, IPPROTO_UDP);
+    GenericEndpoint  udp_b("192.0.2.1", 12345, IPPROTO_UDP);
+    EXPECT_EQ(ResponseScrubber::ADDRESS,
+        ResponseScrubber::addressCheck(udp_a, udp_b));
+
+    // TCP
+    GenericEndpoint  tcp_a("2001:db8::1", 12345, IPPROTO_TCP);
+    GenericEndpoint  tcp_b("192.0.2.1", 12345, IPPROTO_TCP);
+    EXPECT_EQ(ResponseScrubber::ADDRESS,
+        ResponseScrubber::addressCheck(udp_a, udp_b));
+}
+
+// Check mixed protocols are detected
+
+TEST_F(ResponseScrubberTest, Protocol) {
+    GenericEndpoint  udp_a("2001:db8::1", 12345, IPPROTO_UDP);
+    GenericEndpoint  tcp_a("2001:db8::1", 12345, IPPROTO_TCP);
+    EXPECT_EQ(ResponseScrubber::PROTOCOL,
+        ResponseScrubber::addressCheck(udp_a, tcp_a));
+}
+
+// Check that the QIDs check OK
+
+TEST_F(ResponseScrubberTest, Qid) {
+    Message a(Message::RENDER);
+    a.setQid(27);
+
+    Message b(Message::RENDER);
+    b.setQid(27);
+    EXPECT_TRUE(ResponseScrubber::qidCheck(a, b));
+
+    Message c(Message::RENDER);
+    c.setQid(28);
+    EXPECT_FALSE(ResponseScrubber::qidCheck(a, c));
+}
+
+// Check the scrubAllSections() method. As this operates by calling the
+// scrubSection() method (with a SUBDOMAIN argument), this is also a check of
+// the latter.
+
+TEST_F(ResponseScrubberTest, ScrubAllSectionsValid) {
+    Message valid(Message::RENDER);
+
+    // Valid message with nothing out of bailiwick
+    valid.addQuestion(qu_in_a_www);
+    valid.addRRset(Message::SECTION_ANSWER, rrs_in_a_www);
+    valid.addRRset(Message::SECTION_AUTHORITY, rrs_in_ns);
+    valid.addRRset(Message::SECTION_ADDITIONAL, rrs_in_a_ns0);
+
+    // Scrub the message and expect nothing to have been removed.
+    int removed = ResponseScrubber::scrubAllSections(valid, bailiwick);
+    EXPECT_EQ(0, removed);
+
+    // ... and check that this is the case
+    EXPECT_TRUE(valid.hasRRset(Message::SECTION_ANSWER, rrs_in_a_www));
+    EXPECT_TRUE(valid.hasRRset(Message::SECTION_AUTHORITY, rrs_in_ns));
+    EXPECT_TRUE(valid.hasRRset(Message::SECTION_ADDITIONAL, rrs_in_a_ns0));
+
+    // Add out-of-bailiwick glue to the additional section (pretend that the
+    // NS RRset contained an out-of-domain server.
+    valid.addRRset(Message::SECTION_ADDITIONAL, rrs_in_a_ns2);
+    EXPECT_TRUE(valid.hasRRset(Message::SECTION_ADDITIONAL, rrs_in_a_ns2));
+
+    // ... and check that it is removed when scrubbed
+    removed = ResponseScrubber::scrubAllSections(valid, bailiwick);
+    EXPECT_EQ(1, removed);
+    EXPECT_TRUE(valid.hasRRset(Message::SECTION_ANSWER, rrs_in_a_www));
+    EXPECT_TRUE(valid.hasRRset(Message::SECTION_AUTHORITY, rrs_in_ns));
+    EXPECT_TRUE(valid.hasRRset(Message::SECTION_ADDITIONAL, rrs_in_a_ns0));
+    EXPECT_FALSE(valid.hasRRset(Message::SECTION_ADDITIONAL, rrs_in_a_ns2));
+ }
+
+TEST_F(ResponseScrubberTest, ScrubAllSectionsInvalid) {
+    Message invalid(Message::RENDER);
+
+    // Invalid message, with various things in and out of bailiwick.
+
+    invalid.addQuestion(qu_in_a_www);
+
+    // Answer section
+    //
+    // rrs_in_a_www - "www.example.com A", in bailiwick
+    // rrs_in_txt_www - "www.example.com TXT", in bailiwick
+    // rrs_in_a_org - "mail.example.org A", out of bailiwick - the qname is
+    //     related to the bailiwick name by having a common ancestor at the root
+    // rrs_in_a_net - "mail.example.net A", out of bailiwick - the qname is
+    //     related to the bailiwick name by having a common ancestor at the root
+    invalid.addRRset(Message::SECTION_ANSWER, rrs_in_a_www);
+    invalid.addRRset(Message::SECTION_ANSWER, rrs_in_txt_www);
+    invalid.addRRset(Message::SECTION_ANSWER, rrs_in_a_org);
+    invalid.addRRset(Message::SECTION_ANSWER, rrs_in_a_net);
+
+    // Authority section
+    //
+    // rrs_in_ns - "example.com NS", in bailiwick (qname is bailiwick name)
+    // rrs_in_ns_com - "com NS", out of bailiwick as the qname is a superdomain
+    //     (direct ancestor) of the bailiwick name
+    // rrs_in_ns_net - "example.net NS", out of bailiwick - the qname is related
+    //     to the bailiwick name by having a common ancestor at the root
+    // rrs_in_ns_sub - "subdomain.example.com", in bailiwick as the qname is
+    //     a subdomain of the bailiwick name
+    invalid.addRRset(Message::SECTION_AUTHORITY, rrs_in_ns);
+    invalid.addRRset(Message::SECTION_AUTHORITY, rrs_in_ns_com);
+    invalid.addRRset(Message::SECTION_AUTHORITY, rrs_in_ns_net);
+    invalid.addRRset(Message::SECTION_AUTHORITY, rrs_in_ns_sub);
+
+    // Additional section
+    //
+    // rrs_in_a_ns0 - "ns0.example.com", in bailiwick because the qname is
+    //     a subdomain of the bailiwick name
+    // rrs_in_a_ns1 - "ns1.com", out of bailiwick because the qname is a
+    //     sibling to the bailiwick name
+    // rrs_in_a_ns2 - "ns2.example.net", out of bailiwick because qname is
+    //     related by having a common ancestor and the root.
+    // rrs_in_a_ns3 - "ns3.subdomain.example.com", in bailiwick because the
+    //     qname is a direct descendent of the bailiwick name.
+    invalid.addRRset(Message::SECTION_ADDITIONAL, rrs_in_a_ns0);
+    invalid.addRRset(Message::SECTION_ADDITIONAL, rrs_in_a_ns1);
+    invalid.addRRset(Message::SECTION_ADDITIONAL, rrs_in_a_ns2);
+    invalid.addRRset(Message::SECTION_ADDITIONAL, rrs_in_a_ns3);
+
+    // Scrub the message
+    int removed = ResponseScrubber::scrubAllSections(invalid, bailiwick);
+    EXPECT_EQ(6, removed);
+
+    // ... and check the sections.  Answer...
+    EXPECT_TRUE(invalid.hasRRset(Message::SECTION_ANSWER, rrs_in_a_www));
+    EXPECT_TRUE(invalid.hasRRset(Message::SECTION_ANSWER, rrs_in_txt_www));
+    EXPECT_FALSE(invalid.hasRRset(Message::SECTION_ANSWER, rrs_in_a_org));
+    EXPECT_FALSE(invalid.hasRRset(Message::SECTION_ANSWER, rrs_in_a_net));
+
+    // ... authority...
+    EXPECT_TRUE(invalid.hasRRset(Message::SECTION_AUTHORITY, rrs_in_ns));
+    EXPECT_FALSE(invalid.hasRRset(Message::SECTION_AUTHORITY, rrs_in_ns_com));
+    EXPECT_FALSE(invalid.hasRRset(Message::SECTION_AUTHORITY, rrs_in_ns_net));
+    EXPECT_TRUE(invalid.hasRRset(Message::SECTION_AUTHORITY, rrs_in_ns_sub));
+
+    // ... additional.
+    EXPECT_TRUE(invalid.hasRRset(Message::SECTION_ADDITIONAL, rrs_in_a_ns0));
+    EXPECT_FALSE(invalid.hasRRset(Message::SECTION_ADDITIONAL, rrs_in_a_ns1));
+    EXPECT_FALSE(invalid.hasRRset(Message::SECTION_ADDITIONAL, rrs_in_a_ns2));
+    EXPECT_TRUE(invalid.hasRRset(Message::SECTION_ADDITIONAL, rrs_in_a_ns3));
+}
+
+// An empty message
+
+TEST_F(ResponseScrubberTest, ScrubAllSectionsEmpty) {
+    Message empty(Message::RENDER);
+
+    EXPECT_EQ(0, empty.getRRCount(Message::SECTION_QUESTION));
+    EXPECT_EQ(0, empty.getRRCount(Message::SECTION_ANSWER));
+    EXPECT_EQ(0, empty.getRRCount(Message::SECTION_AUTHORITY));
+    EXPECT_EQ(0, empty.getRRCount(Message::SECTION_ADDITIONAL));
+
+    int removed = ResponseScrubber::scrubAllSections(empty, bailiwick);
+    EXPECT_EQ(0, removed);
+
+    EXPECT_EQ(0, empty.getRRCount(Message::SECTION_QUESTION));
+    EXPECT_EQ(0, empty.getRRCount(Message::SECTION_ANSWER));
+    EXPECT_EQ(0, empty.getRRCount(Message::SECTION_AUTHORITY));
+    EXPECT_EQ(0, empty.getRRCount(Message::SECTION_ADDITIONAL));
+
+}
+
+// Check the cross-section scrubbing (checks the general scrubSection()
+// method with a SUPERDOMAIN argument.)
+
+// Empty message (apart from question)
+
+TEST_F(ResponseScrubberTest, CrossSectionEmpty) {
+
+    Message message1(Message::RENDER);
+    message1.addQuestion(qu_in_a_www);
+    int removed = ResponseScrubber::scrubCrossSections(message1);
+    EXPECT_EQ(0, removed);
+}
+
+// Valid answer section
+
+TEST_F(ResponseScrubberTest, CrossSectionAnswer) {
+
+    // Valid message with nothing out of bailiwick, but the authority
+    // (subdomain.example.com) is not authoritative for the answer.
+    //
+    // TODO: Test the case where the additional section does not match
+    // with something in the authority section.
+    Message message1(Message::RENDER);
+    message1.addQuestion(qu_in_a_www);
+    message1.addRRset(Message::SECTION_ANSWER, rrs_in_a_www);
+    message1.addRRset(Message::SECTION_AUTHORITY, rrs_in_ns_sub);
+    message1.addRRset(Message::SECTION_ADDITIONAL, rrs_in_a_ns3);
+    int removed = ResponseScrubber::scrubCrossSections(message1);
+    EXPECT_EQ(1, removed);
+    EXPECT_TRUE(message1.hasRRset(Message::SECTION_ANSWER, rrs_in_a_www));
+    EXPECT_FALSE(message1.hasRRset(Message::SECTION_AUTHORITY, rrs_in_ns_sub));
+    EXPECT_TRUE(message1.hasRRset(Message::SECTION_ADDITIONAL, rrs_in_a_ns3));
+
+    // A repeat of the test, this time with a mixture of incorrect and correct
+    // authorities.
+    Message message2(Message::RENDER);
+    message2.addQuestion(qu_in_a_www);
+    message2.addRRset(Message::SECTION_ANSWER, rrs_in_a_www);
+    message2.addRRset(Message::SECTION_AUTHORITY, rrs_in_ns_sub);
+    message2.addRRset(Message::SECTION_AUTHORITY, rrs_in_ns);
+    message2.addRRset(Message::SECTION_AUTHORITY, rrs_in_ns_sub2);
+    message2.addRRset(Message::SECTION_ADDITIONAL, rrs_in_a_ns3);
+    removed = ResponseScrubber::scrubCrossSections(message2);
+    EXPECT_EQ(2, removed);
+    EXPECT_TRUE(message2.hasRRset(Message::SECTION_ANSWER, rrs_in_a_www));
+    EXPECT_FALSE(message2.hasRRset(Message::SECTION_AUTHORITY, rrs_in_ns_sub));
+    EXPECT_TRUE(message2.hasRRset(Message::SECTION_AUTHORITY, rrs_in_ns));
+    EXPECT_FALSE(message2.hasRRset(Message::SECTION_AUTHORITY, rrs_in_ns_sub2));
+    EXPECT_TRUE(message2.hasRRset(Message::SECTION_ADDITIONAL, rrs_in_a_ns3));
+}
+
+// Test the main "scrub" method.  This is a single to ensure that the
+// combination of methods
+
+TEST_F(ResponseScrubberTest, All) {
+    MessagePtr mptr(new Message(Message::RENDER));
+
+    // Question is "www.example.com IN A" sent to a nameserver with the
+    // bailiwick of "example.com".
+    mptr->addQuestion(qu_in_a_www);
+
+    // Answer section.
+
+    // "www.example.com IN CNAME www.example.net" - should be kept
+    mptr->addRRset(Message::SECTION_ANSWER, rrs_in_cname_www);
+
+    // "www.example.net IN A a.b.c.d" - should be removed, out of bailiwick
+    mptr->addRRset(Message::SECTION_ANSWER, rrs_in_a_wwwnet);
+
+    // Authority section.
+
+    // "example.net IN NS xxxx" - should be removed, out of bailiwick.
+    mptr->addRRset(Message::SECTION_AUTHORITY, rrs_in_ns_net);
+
+    // "example.com IN NS xxx" - kept
+    mptr->addRRset(Message::SECTION_AUTHORITY, rrs_in_ns);
+
+    // "com IN NS xxx" - removed, out of bailiwick
+    mptr->addRRset(Message::SECTION_AUTHORITY, rrs_in_ns_com);
+
+    // "subdomain.example.com IN NS xxx" - removed, not a superdomain of the
+    // answer.
+    mptr->addRRset(Message::SECTION_AUTHORITY, rrs_in_ns_sub);
+
+    // Additional section
+
+    // "ns2.example.net IN A a.b.c.d" - removed, out of bailiwick
+    mptr->addRRset(Message::SECTION_ADDITIONAL, rrs_in_a_ns2);
+
+    // "ns3.subdomain.example.com IN A a.b.c.d" - retained.
+    mptr->addRRset(Message::SECTION_ADDITIONAL, rrs_in_a_ns3);
+
+    unsigned int removed = ResponseScrubber::scrub(mptr, bailiwick);
+    EXPECT_EQ(5, removed);
+
+    EXPECT_TRUE(mptr->hasRRset(Message::SECTION_ANSWER, rrs_in_cname_www));
+    EXPECT_FALSE(mptr->hasRRset(Message::SECTION_ANSWER, rrs_in_a_wwwnet));
+    EXPECT_FALSE(mptr->hasRRset(Message::SECTION_AUTHORITY, rrs_in_ns_net));
+    EXPECT_TRUE(mptr->hasRRset(Message::SECTION_AUTHORITY, rrs_in_ns));
+    EXPECT_FALSE(mptr->hasRRset(Message::SECTION_AUTHORITY, rrs_in_ns_com));
+    EXPECT_FALSE(mptr->hasRRset(Message::SECTION_AUTHORITY, rrs_in_ns_sub));
+    EXPECT_FALSE(mptr->hasRRset(Message::SECTION_ADDITIONAL, rrs_in_a_ns2));
+    EXPECT_TRUE(mptr->hasRRset(Message::SECTION_ADDITIONAL, rrs_in_a_ns3));
+
+}
+} // Anonymous namespace

+ 2 - 1
src/bin/stats/b10-stats.xml

@@ -89,7 +89,8 @@
     <para><filename>/usr/local/share/bind10-devel/stats.spec</filename>
       &mdash; This is a spec file for <command>b10-stats</command>. It
       contains definitions of statistics items of BIND 10 and commands
-      received vi bindctl.
+      received via
+      <refentrytitle>bindctl</refentrytitle><manvolnum>1</manvolnum>.
     </para>
   </refsect1>
 

+ 1 - 1
src/bin/usermgr/b10-cmdctl-usermgr.py.in

@@ -24,7 +24,7 @@ from hashlib import sha1
 import csv
 import getpass
 import getopt
-import sys
+import sys; sys.path.append ('@@PYTHONPATH@@')
 import isc.util.process
 
 isc.util.process.rename()

+ 1 - 1
src/bin/xfrin/b10-xfrin.xml

@@ -63,7 +63,7 @@
     </para>
 
     <note><simpara>
-      The Y1 prototype release only supports AXFR. IXFR is not implemented.
+      This prototype release only supports AXFR. IXFR is not implemented.
     </simpara></note>
 
     <para>

+ 2 - 2
src/lib/Makefile.am

@@ -1,2 +1,2 @@
-SUBDIRS = exceptions dns cc config datasrc python xfr bench log asiolink \
-    testutils nsas
+SUBDIRS = exceptions dns cc config datasrc python xfr bench log \
+          resolve asiolink testutils nsas

+ 20 - 10
src/lib/asiolink/Makefile.am

@@ -1,4 +1,4 @@
-SUBDIRS = . tests internal
+SUBDIRS = . tests
 
 AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
 AM_CPPFLAGS += $(BOOST_INCLUDES)
@@ -12,15 +12,24 @@ CLEANFILES = *.gcno *.gcda
 # have some code fragments that would hit gcc's unused-parameter warning,
 # which would make the build fail with -Werror (our default setting).
 lib_LTLIBRARIES = libasiolink.la
-libasiolink_la_SOURCES = asiolink.cc asiolink.h
-libasiolink_la_SOURCES += iosocket.cc iosocket.h
-libasiolink_la_SOURCES += iomessage.h
-libasiolink_la_SOURCES += ioaddress.cc ioaddress.h
-libasiolink_la_SOURCES += ioendpoint.cc ioendpoint.h
-libasiolink_la_SOURCES += udpdns.cc internal/udpdns.h
-libasiolink_la_SOURCES += tcpdns.cc internal/tcpdns.h
-libasiolink_la_SOURCES += iofetch.cc internal/iofetch.h
-libasiolink_la_SOURCES += internal/coroutine.h
+libasiolink_la_SOURCES = asiolink.h
+libasiolink_la_SOURCES += io_service.cc io_service.h
+libasiolink_la_SOURCES += dns_service.cc dns_service.h
+libasiolink_la_SOURCES += dns_server.h
+libasiolink_la_SOURCES += dns_lookup.h
+libasiolink_la_SOURCES += dns_answer.h
+libasiolink_la_SOURCES += simple_callback.h
+libasiolink_la_SOURCES += interval_timer.h interval_timer.cc
+libasiolink_la_SOURCES += recursive_query.h recursive_query.cc
+libasiolink_la_SOURCES += io_socket.cc io_socket.h
+libasiolink_la_SOURCES += io_message.h
+libasiolink_la_SOURCES += io_address.cc io_address.h
+libasiolink_la_SOURCES += io_endpoint.cc io_endpoint.h
+libasiolink_la_SOURCES += udp_endpoint.h udp_socket.h
+libasiolink_la_SOURCES += udp_server.h udp_server.cc
+libasiolink_la_SOURCES += udp_query.h udp_query.cc
+libasiolink_la_SOURCES += tcp_endpoint.h tcp_socket.h
+libasiolink_la_SOURCES += tcp_server.h tcp_server.cc
 # Note: the ordering matters: -Wno-... must follow -Wextra (defined in
 # B10_CXXFLAGS)
 libasiolink_la_CXXFLAGS = $(AM_CXXFLAGS)
@@ -33,3 +42,4 @@ libasiolink_la_CXXFLAGS += -Wno-error
 endif
 libasiolink_la_CPPFLAGS = $(AM_CPPFLAGS)
 libasiolink_la_LIBADD = $(top_builddir)/src/lib/log/liblog.la
+libasiolink_la_LIBADD += $(top_builddir)/src/lib/resolve/libresolve.la

+ 0 - 735
src/lib/asiolink/asiolink.cc

@@ -1,735 +0,0 @@
-// Copyright (C) 2010  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 <cstdlib> // For rand(), temporary until better forwarding is done
-
-#include <unistd.h>             // for some IPC/network system calls
-#include <sys/socket.h>
-#include <netinet/in.h>
-
-#include <vector>
-#include <asio.hpp>
-#include <boost/lexical_cast.hpp>
-#include <boost/bind.hpp>
-#include <boost/date_time/posix_time/posix_time_types.hpp>
-
-#include <boost/shared_ptr.hpp>
-
-#include <dns/buffer.h>
-#include <dns/message.h>
-#include <dns/rcode.h>
-
-#include <asiolink/asiolink.h>
-#include <asiolink/internal/tcpdns.h>
-#include <asiolink/internal/udpdns.h>
-#include <asiolink/internal/iofetch.h>
-
-#include <log/dummylog.h>
-
-
-using namespace asio;
-using asio::ip::udp;
-using asio::ip::tcp;
-
-using namespace std;
-using namespace isc::dns;
-using isc::log::dlog;
-using namespace boost;
-
-// Is this something we can use in libdns++?
-namespace {
-    class SectionInserter {
-    public:
-        SectionInserter(MessagePtr message, const Message::Section sect) :
-            message_(message), section_(sect)
-        {}
-        void operator()(const RRsetPtr rrset) {
-            message_->addRRset(section_, rrset, true);
-        }
-        MessagePtr message_;
-        const Message::Section section_;
-    };
-
-
-    /// \brief Copies the parts relevant for a DNS answer to the
-    /// target message
-    ///
-    /// This adds all the RRsets in the answer, authority and
-    /// additional sections to the target, as well as the response
-    /// code
-    void copyAnswerMessage(const Message& source, MessagePtr target) {
-        target->setRcode(source.getRcode());
-
-        for_each(source.beginSection(Message::SECTION_ANSWER),
-                 source.endSection(Message::SECTION_ANSWER),
-                 SectionInserter(target, Message::SECTION_ANSWER));
-        for_each(source.beginSection(Message::SECTION_AUTHORITY),
-                 source.endSection(Message::SECTION_AUTHORITY),
-                 SectionInserter(target, Message::SECTION_AUTHORITY));
-        for_each(source.beginSection(Message::SECTION_ADDITIONAL),
-                 source.endSection(Message::SECTION_ADDITIONAL),
-                 SectionInserter(target, Message::SECTION_ADDITIONAL));
-    }
-}
-
-namespace asiolink {
-
-typedef pair<string, uint16_t> addr_t;
-
-class IOServiceImpl {
-private:
-    IOServiceImpl(const IOService& source);
-    IOServiceImpl& operator=(const IOService& source);
-public:
-    /// \brief The constructor
-    IOServiceImpl() :
-        io_service_(),
-        work_(io_service_)
-    {};
-    /// \brief The destructor.
-    ~IOServiceImpl() {};
-    //@}
-
-    /// \brief Start the underlying event loop.
-    ///
-    /// This method does not return control to the caller until
-    /// the \c stop() method is called via some handler.
-    void run() { io_service_.run(); };
-
-    /// \brief Run the underlying event loop for a single event.
-    ///
-    /// This method return control to the caller as soon as the
-    /// first handler has completed.  (If no handlers are ready when
-    /// it is run, it will block until one is.)
-    void run_one() { io_service_.run_one();} ;
-
-    /// \brief Stop the underlying event loop.
-    ///
-    /// This will return the control to the caller of the \c run() method.
-    void stop() { io_service_.stop();} ;
-
-    /// \brief Return the native \c io_service object used in this wrapper.
-    ///
-    /// This is a short term work around to support other BIND 10 modules
-    /// that share the same \c io_service with the authoritative server.
-    /// It will eventually be removed once the wrapper interface is
-    /// generalized.
-    asio::io_service& get_io_service() { return io_service_; };
-private:
-    asio::io_service io_service_;
-    asio::io_service::work work_;
-};
-
-IOService::IOService() {
-    io_impl_ = new IOServiceImpl();
-}
-
-IOService::~IOService() {
-    delete io_impl_;
-}
-
-void
-IOService::run() {
-    io_impl_->run();
-}
-
-void
-IOService::run_one() {
-    io_impl_->run_one();
-}
-
-void
-IOService::stop() {
-    io_impl_->stop();
-}
-
-asio::io_service&
-IOService::get_io_service() {
-    return (io_impl_->get_io_service());
-}
-
-class DNSServiceImpl {
-public:
-    DNSServiceImpl(IOService& io_service, const char& port,
-                  const ip::address* v4addr, const ip::address* v6addr,
-                  SimpleCallback* checkin, DNSLookup* lookup,
-                  DNSAnswer* answer);
-
-    IOService& io_service_;
-
-    typedef boost::shared_ptr<UDPServer> UDPServerPtr;
-    typedef boost::shared_ptr<TCPServer> TCPServerPtr;
-    typedef boost::shared_ptr<DNSServer> DNSServerPtr;
-    vector<DNSServerPtr> servers_;
-    SimpleCallback *checkin_;
-    DNSLookup *lookup_;
-    DNSAnswer *answer_;
-
-    void addServer(uint16_t port, const ip::address& address) {
-        try {
-            dlog(std::string("Initialize TCP server at ") + address.to_string() + ":" + boost::lexical_cast<string>(port));
-            TCPServerPtr tcpServer(new TCPServer(io_service_.get_io_service(),
-                address, port, checkin_, lookup_, answer_));
-            (*tcpServer)();
-            servers_.push_back(tcpServer);
-            dlog(std::string("Initialize UDP server at ") + address.to_string() + ":" + boost::lexical_cast<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.
-            isc_throw(IOError, "Failed to initialize network servers: " <<
-                      err.what());
-        }
-    }
-    void addServer(const char& port, const ip::address& address) {
-        uint16_t portnum;
-        try {
-            // XXX: SunStudio with stlport4 doesn't reject some invalid
-            // representation such as "-1" by lexical_cast<uint16_t>, so
-            // we convert it into a signed integer of a larger size and perform
-            // range check ourselves.
-            const int32_t portnum32 = boost::lexical_cast<int32_t>(&port);
-            if (portnum32 < 0 || portnum32 > 65535) {
-                isc_throw(IOError, "Invalid port number '" << &port);
-            }
-            portnum = portnum32;
-        } catch (const boost::bad_lexical_cast& ex) {
-            isc_throw(IOError, "Invalid port number '" << &port << "': " <<
-                      ex.what());
-        }
-        addServer(portnum, address);
-    }
-};
-
-DNSServiceImpl::DNSServiceImpl(IOService& io_service,
-                               const char& port,
-                               const ip::address* const v4addr,
-                               const ip::address* const v6addr,
-                               SimpleCallback* checkin,
-                               DNSLookup* lookup,
-                               DNSAnswer* answer) :
-    io_service_(io_service),
-    checkin_(checkin),
-    lookup_(lookup),
-    answer_(answer)
-{
-
-    if (v4addr) {
-        addServer(port, *v4addr);
-    }
-    if (v6addr) {
-        addServer(port, *v6addr);
-    }
-}
-
-DNSService::DNSService(IOService& io_service,
-                       const char& port, const char& address,
-                       SimpleCallback* checkin,
-                       DNSLookup* lookup,
-                       DNSAnswer* answer) :
-    impl_(new DNSServiceImpl(io_service, port, NULL, NULL, checkin, lookup,
-        answer)), io_service_(io_service)
-{
-    addServer(port, &address);
-}
-
-DNSService::DNSService(IOService& io_service,
-                       const char& port,
-                       const bool use_ipv4, const bool use_ipv6,
-                       SimpleCallback* checkin,
-                       DNSLookup* lookup,
-                       DNSAnswer* answer) :
-    impl_(NULL), io_service_(io_service)
-{
-    const ip::address v4addr_any = ip::address(ip::address_v4::any());
-    const ip::address* const v4addrp = use_ipv4 ? &v4addr_any : NULL; 
-    const ip::address v6addr_any = ip::address(ip::address_v6::any());
-    const ip::address* const v6addrp = use_ipv6 ? &v6addr_any : NULL;
-    impl_ = new DNSServiceImpl(io_service, port, v4addrp, v6addrp, checkin, lookup, answer);
-}
-
-DNSService::DNSService(IOService& io_service, SimpleCallback* checkin,
-    DNSLookup* lookup, DNSAnswer *answer) :
-    impl_(new DNSServiceImpl(io_service, *"0", NULL, NULL, checkin, lookup,
-        answer)), io_service_(io_service)
-{
-}
-
-DNSService::~DNSService() {
-    delete impl_;
-}
-
-namespace {
-
-typedef std::vector<std::pair<std::string, uint16_t> > AddressVector;
-
-}
-
-// Here we do not use the typedef above, as the SunStudio compiler
-// mishandles this in its name mangling, and wouldn't compile.
-// We can probably use a typedef, but need to move it to a central
-// location and use it consistently.
-RecursiveQuery::RecursiveQuery(DNSService& dns_service,
-    const std::vector<std::pair<std::string, uint16_t> >& upstream,
-    const std::vector<std::pair<std::string, uint16_t> >& upstream_root,
-    int query_timeout, int client_timeout, int lookup_timeout,
-    unsigned retries) :
-    dns_service_(dns_service), upstream_(new AddressVector(upstream)),
-    upstream_root_(new AddressVector(upstream_root)),
-    query_timeout_(query_timeout), client_timeout_(client_timeout),
-    lookup_timeout_(lookup_timeout), retries_(retries)
-{}
-
-namespace {
-
-ip::address
-convertAddr(const string& address) {
-    error_code err;
-    ip::address addr = ip::address::from_string(address, err);
-    if (err) {
-        isc_throw(IOError, "Invalid IP address '" << &address << "': "
-            << err.message());
-    }
-    return (addr);
-}
-
-}
-
-void
-DNSService::addServer(const char& port, const string& address) {
-    impl_->addServer(port, convertAddr(address));
-}
-
-void
-DNSService::addServer(uint16_t port, const string& address) {
-    impl_->addServer(port, convertAddr(address));
-}
-
-void
-DNSService::clearServers() {
-    // FIXME: This does not work, it does not close the socket.
-    // How is it done?
-    impl_->servers_.clear();
-}
-
-namespace {
-
-/*
- * This is a query in progress. When a new query is made, this one holds
- * the context information about it, like how many times we are allowed
- * to retry on failure, what to do when we succeed, etc.
- *
- * Used by RecursiveQuery::sendQuery.
- */
-class RunningQuery : public IOFetch::Callback {
-private:
-    // The io service to handle async calls
-    asio::io_service& io_;
-
-    // Info for (re)sending the query (the question and destination)
-    Question question_;
-
-    // This is where we build and store our final answer
-    MessagePtr answer_message_;
-
-    // currently we use upstream as the current list of NS records
-    // we should differentiate between forwarding and resolving
-    shared_ptr<AddressVector> upstream_;
-
-    // root servers...just copied over to the zone_servers_
-    shared_ptr<AddressVector> upstream_root_;
-
-    // Buffer to store the result.
-    OutputBufferPtr buffer_;
-
-    // Server to notify when we succeed or fail
-    shared_ptr<DNSServer> server_;
-
-    /*
-     * TODO Do something more clever with timeouts. In the long term, some
-     *     computation of average RTT, increase with each retry, etc.
-     */
-    // Timeout information
-    int query_timeout_;
-    unsigned retries_;
-
-    // normal query state
-
-    // Not using NSAS at this moment, so we keep a list
-    // of 'current' zone servers
-    vector<addr_t> zone_servers_;
-
-    // Update the question that will be sent to the server
-    void setQuestion(const Question& new_question) {
-        question_ = new_question;
-    }
-
-    deadline_timer client_timer;
-    deadline_timer lookup_timer;
-
-    size_t queries_out_;
-
-    // If we timed out ourselves (lookup timeout), stop issuing queries
-    bool done_;
-
-    // (re)send the query to the server.
-    void send() {
-        const int uc = upstream_->size();
-        const int zs = zone_servers_.size();
-        buffer_->clear();
-        if (uc > 0) {
-            int serverIndex = rand() % uc;
-            dlog("Sending upstream query (" + question_.toText() +
-                ") to " + upstream_->at(serverIndex).first);
-            IOFetch query(io_, question_,
-                upstream_->at(serverIndex).first,
-                upstream_->at(serverIndex).second, buffer_, this,
-                query_timeout_);
-            ++queries_out_;
-            io_.post(query);
-        } else if (zs > 0) {
-            int serverIndex = rand() % zs;
-            dlog("Sending query to zone server (" + question_.toText() +
-                ") to " + zone_servers_.at(serverIndex).first);
-            IOFetch query(io_, question_,
-                zone_servers_.at(serverIndex).first,
-                zone_servers_.at(serverIndex).second, buffer_, this,
-                query_timeout_);
-            ++queries_out_;
-            io_.post(query);
-        } else {
-            dlog("Error, no upstream servers to send to.");
-        }
-    }
-    
-    // This function is called by operator() if there is an actual
-    // answer from a server and we are in recursive mode
-    // depending on the contents, we go on recursing or return
-    //
-    // Note that the footprint may change as this function may
-    // need to append data to the answer we are building later.
-    //
-    // returns true if we are done
-    // returns false if we are not done
-    bool handleRecursiveAnswer(const Message& incoming) {
-        if (incoming.getRRCount(Message::SECTION_ANSWER) > 0) {
-            dlog("Got final result, copying answer.");
-            copyAnswerMessage(incoming, answer_message_);
-            return true;
-        } else {
-            dlog("Got delegation, continuing");
-            // ok we need to do some more processing.
-            // the ns list should contain all nameservers
-            // while the additional may contain addresses for
-            // them.
-            // this needs to tie into NSAS of course
-            // for this very first mockup, hope there is an
-            // address in additional and just use that
-
-            // send query to the addresses in the delegation
-            bool found_ns_address = false;
-            zone_servers_.clear();
-
-            for (RRsetIterator rrsi = incoming.beginSection(Message::SECTION_ADDITIONAL);
-                 rrsi != incoming.endSection(Message::SECTION_ADDITIONAL) && !found_ns_address;
-                 rrsi++) {
-                ConstRRsetPtr rrs = *rrsi;
-                if (rrs->getType() == RRType::A()) {
-                    // found address
-                    RdataIteratorPtr rdi = rrs->getRdataIterator();
-                    // just use the first for now
-                    if (!rdi->isLast()) {
-                        std::string addr_str = rdi->getCurrent().toText();
-                        dlog("[XX] first address found: " + addr_str);
-                        // now we have one address, simply
-                        // resend that exact same query
-                        // to that address and yield, when it
-                        // returns, loop again.
-                        
-                        // should use NSAS
-                        zone_servers_.push_back(addr_t(addr_str, 53));
-                        found_ns_address = true;
-                    }
-                }
-            }
-            if (found_ns_address) {
-                // next resolver round
-                send();
-                return false;
-            } else {
-                dlog("[XX] no ready-made addresses in additional. need nsas.");
-                // this will result in answering with the delegation. oh well
-                copyAnswerMessage(incoming, answer_message_);
-                return true;
-            }
-        }
-    }
-    
-
-public:
-    RunningQuery(asio::io_service& io, const Question &question,
-        MessagePtr answer_message, shared_ptr<AddressVector> upstream,
-        shared_ptr<AddressVector> upstream_root,
-        OutputBufferPtr buffer, DNSServer* server,
-        int query_timeout, int client_timeout, int lookup_timeout,
-        unsigned retries) :
-        io_(io),
-        question_(question),
-        answer_message_(answer_message),
-        upstream_(upstream),
-        upstream_root_(upstream_root),
-        buffer_(buffer),
-        server_(server->clone()),
-        query_timeout_(query_timeout),
-        retries_(retries),
-        client_timer(io),
-        lookup_timer(io),
-        queries_out_(0),
-        done_(false)
-    {
-        // Setup the timer to stop trying (lookup_timeout)
-        if (lookup_timeout >= 0) {
-            lookup_timer.expires_from_now(
-                boost::posix_time::milliseconds(lookup_timeout));
-            lookup_timer.async_wait(boost::bind(&RunningQuery::stop, this, false));
-        }
-        
-        // Setup the timer to send an answer (client_timeout)
-        if (client_timeout >= 0) {
-            client_timer.expires_from_now(
-                boost::posix_time::milliseconds(client_timeout));
-            client_timer.async_wait(boost::bind(&RunningQuery::clientTimeout, this));
-        }
-        
-        // should use NSAS for root servers
-        // Adding root servers if not a forwarder
-        if (upstream_->empty()) {
-            if (upstream_root_->empty()) { //if no root ips given, use this
-                zone_servers_.push_back(addr_t("192.5.5.241", 53));
-            }
-            else
-            {
-              //copy the list
-              dlog("Size is " + 
-                    boost::lexical_cast<string>(upstream_root_->size()) + 
-                    "\n");
-              //Use BOOST_FOREACH here? Is it faster?
-              for(AddressVector::iterator it = upstream_root_->begin();
-                   it < upstream_root_->end(); it++) {
-                zone_servers_.push_back(addr_t(it->first,it->second));
-                dlog("Put " + zone_servers_.back().first + "into root list\n");
-              }
-            }
-        }
-
-        send();
-    }
-
-    virtual void clientTimeout() {
-        // right now, just stop (should make SERVFAIL and send that
-        // back, but not stop)
-        stop(false);
-    }
-
-    virtual void stop(bool resume) {
-        // if we cancel our timers, we will still get an event for
-        // that, so we cannot delete ourselves just yet (those events
-        // would be bound to a deleted object)
-        // cancel them one by one, both cancels should get us back
-        // here again.
-        // same goes if we have an outstanding query (can't delete
-        // until that one comes back to us)
-        done_ = true;
-        server_->resume(resume);
-        if (lookup_timer.cancel() != 0) {
-            return;
-        }
-        if (client_timer.cancel() != 0) {
-            return;
-        }
-        if (queries_out_ > 0) {
-            return;
-        }
-        delete this;
-    }
-
-    // This function is used as callback from DNSQuery.
-    virtual void operator()(IOFetch::Result result) {
-        // XXX is this the place for TCP retry?
-        --queries_out_;
-        if (!done_ && result != IOFetch::TIME_OUT) {
-            // we got an answer
-            Message incoming(Message::PARSE);
-            InputBuffer ibuf(buffer_->getData(), buffer_->getLength());
-            incoming.fromWire(ibuf);
-
-            if (incoming.getHeaderFlag(Message::HEADERFLAG_TC)) {
-              //truncate bit is set, so retry via tcp
-            
-            }
-
-            if (upstream_->size() == 0 &&
-                incoming.getRcode() == Rcode::NOERROR()) {
-                done_ = handleRecursiveAnswer(incoming);
-            } else {
-                copyAnswerMessage(incoming, answer_message_);
-                done_ = true;
-            }
-            
-            if (done_) {
-                stop(result == IOFetch::SUCCESS);
-            }
-        } else if (!done_ && retries_--) {
-            // We timed out, but we have some retries, so send again
-            dlog("Timeout, resending query");
-            send();
-        } else {
-            // We are done
-            stop(false);
-        }
-    }
-};
-
-}
-
-void
-RecursiveQuery::sendQuery(const Question& question,
-                          MessagePtr answer_message,
-                          OutputBufferPtr buffer,
-                          DNSServer* server)
-{
-    // XXX: eventually we will need to be able to determine whether
-    // the message should be sent via TCP or UDP, or sent initially via
-    // UDP and then fall back to TCP on failure, but for the moment
-    // we're only going to handle UDP.
-    asio::io_service& io = dns_service_.get_io_service();
-    // It will delete itself when it is done
-    new RunningQuery(io, question, answer_message, upstream_, upstream_root_,
-                         buffer, server, query_timeout_, client_timeout_,
-                         lookup_timeout_, retries_);
-}
-
-class IntervalTimerImpl {
-private:
-    // prohibit copy
-    IntervalTimerImpl(const IntervalTimerImpl& source);
-    IntervalTimerImpl& operator=(const IntervalTimerImpl& source);
-public:
-    IntervalTimerImpl(IOService& io_service);
-    ~IntervalTimerImpl();
-    void setupTimer(const IntervalTimer::Callback& cbfunc,
-                    const uint32_t interval);
-    void callback(const asio::error_code& error);
-    void cancel() {
-        timer_.cancel();
-        interval_ = 0;
-    }
-    uint32_t getInterval() const { return (interval_); }
-private:
-    // a function to update timer_ when it expires
-    void updateTimer();
-    // a function to call back when timer_ expires
-    IntervalTimer::Callback cbfunc_;
-    // interval in seconds
-    uint32_t interval_;
-    // asio timer
-    asio::deadline_timer timer_;
-};
-
-IntervalTimerImpl::IntervalTimerImpl(IOService& io_service) :
-    interval_(0), timer_(io_service.get_io_service())
-{}
-
-IntervalTimerImpl::~IntervalTimerImpl()
-{}
-
-void
-IntervalTimerImpl::setupTimer(const IntervalTimer::Callback& cbfunc,
-                              const uint32_t interval)
-{
-    // Interval should not be 0.
-    if (interval == 0) {
-        isc_throw(isc::BadValue, "Interval should not be 0");
-    }
-    // Call back function should not be empty.
-    if (cbfunc.empty()) {
-        isc_throw(isc::InvalidParameter, "Callback function is empty");
-    }
-    cbfunc_ = cbfunc;
-    interval_ = interval;
-    // Set initial expire time.
-    // At this point the timer is not running yet and will not expire.
-    // After calling IOService::run(), the timer will expire.
-    updateTimer();
-    return;
-}
-
-void
-IntervalTimerImpl::updateTimer() {
-    if (interval_ == 0) {
-        // timer has been canceled.  Do nothing.
-        return;
-    }
-    try {
-        // Update expire time to (current time + interval_).
-        timer_.expires_from_now(boost::posix_time::seconds(interval_));
-    } catch (const asio::system_error& e) {
-        isc_throw(isc::Unexpected, "Failed to update timer");
-    }
-    // Reset timer.
-    timer_.async_wait(boost::bind(&IntervalTimerImpl::callback, this, _1));
-}
-
-void
-IntervalTimerImpl::callback(const asio::error_code& cancelled) {
-    // Do not call cbfunc_ in case the timer was cancelled.
-    // The timer will be canelled in the destructor of asio::deadline_timer.
-    if (!cancelled) {
-        cbfunc_();
-        // Set next expire time.
-        updateTimer();
-    }
-}
-
-IntervalTimer::IntervalTimer(IOService& io_service) {
-    impl_ = new IntervalTimerImpl(io_service);
-}
-
-IntervalTimer::~IntervalTimer() {
-    delete impl_;
-}
-
-void
-IntervalTimer::setupTimer(const Callback& cbfunc, const uint32_t interval) {
-    return (impl_->setupTimer(cbfunc, interval));
-}
-
-void
-IntervalTimer::cancel() {
-    impl_->cancel();
-}
-
-uint32_t
-IntervalTimer::getInterval() const {
-    return (impl_->getInterval());
-}
-
-}

+ 13 - 614
src/lib/asiolink/asiolink.h

@@ -18,31 +18,20 @@
 // IMPORTANT NOTE: only very few ASIO headers files can be included in
 // this file.  In particular, asio.hpp should never be included here.
 // See the description of the namespace below.
-#include <unistd.h>             // for some network system calls
-#include <asio/ip/address.hpp>
-#include <boost/shared_ptr.hpp>
-#include <boost/function.hpp>
 
-#include <functional>
-#include <string>
-#include <vector>
-#include <utility>
+#include <asiolink/io_service.h>
+#include <asiolink/dns_service.h>
+#include <asiolink/dns_server.h>
+#include <asiolink/dns_lookup.h>
+#include <asiolink/dns_answer.h>
+#include <asiolink/simple_callback.h>
+#include <asiolink/recursive_query.h>
+#include <asiolink/interval_timer.h>
 
-#include <dns/buffer.h>
-#include <dns/message.h>
-#include <dns/question.h>
-
-#include <exceptions/exceptions.h>
-
-#include <asiolink/ioaddress.h>
-#include <asiolink/ioendpoint.h>
-#include <asiolink/iomessage.h>
-#include <asiolink/iosocket.h>
-
-namespace asio {
-// forward declaration for IOService::get_io_service() below
-class io_service;
-}
+#include <asiolink/io_address.h>
+#include <asiolink/io_endpoint.h>
+#include <asiolink/io_message.h>
+#include <asiolink/io_socket.h>
 
 /// \namespace asiolink
 /// \brief A wrapper interface for the ASIO library.
@@ -95,9 +84,7 @@ class io_service;
 /// http://think-async.com/Asio/asio-1.3.1/doc/asio/reference/asio_handler_allocate.html
 
 namespace asiolink {
-class DNSServiceImpl;
-struct IOServiceImpl;
-struct IntervalTimerImpl;
+
 
 /// \brief An exception that is thrown if an error occurs within the IO
 /// module.  This is mainly intended to be a wrapper exception class for
@@ -108,594 +95,6 @@ public:
         isc::Exception(file, line, what) {}
 };
 
-/// \brief Forward declarations for classes used below
-class SimpleCallback;
-class DNSLookup;
-class DNSAnswer;
-
-/// \brief The \c IOService class is a wrapper for the ASIO \c io_service
-/// class.
-///
-class IOService {
-    ///
-    /// \name Constructors and Destructor
-    ///
-    /// Note: The copy constructor and the assignment operator are
-    /// intentionally defined as private, making this class non-copyable.
-    //@{
-private:
-    IOService(const IOService& source);
-    IOService& operator=(const IOService& source);
-public:
-    /// \brief The constructor
-    IOService();
-    /// \brief The destructor.
-    ~IOService();
-    //@}
-
-    /// \brief Start the underlying event loop.
-    ///
-    /// This method does not return control to the caller until
-    /// the \c stop() method is called via some handler.
-    void run();
-
-    /// \brief Run the underlying event loop for a single event.
-    ///
-    /// This method return control to the caller as soon as the
-    /// first handler has completed.  (If no handlers are ready when
-    /// it is run, it will block until one is.)
-    void run_one();
-
-    /// \brief Stop the underlying event loop.
-    ///
-    /// This will return the control to the caller of the \c run() method.
-    void stop();
-
-    /// \brief Return the native \c io_service object used in this wrapper.
-    ///
-    /// This is a short term work around to support other BIND 10 modules
-    /// that share the same \c io_service with the authoritative server.
-    /// It will eventually be removed once the wrapper interface is
-    /// generalized.
-    asio::io_service& get_io_service();
-
-private:
-    IOServiceImpl* io_impl_;
-};
-
-///
-/// DNSService is the service that handles DNS queries and answers with
-/// a given IOService. This class is mainly intended to hold all the
-/// 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 {
-    ///
-    /// \name Constructors and Destructor
-    ///
-    /// Note: The copy constructor and the assignment operator are
-    /// intentionally defined as private, making this class non-copyable.
-    //@{
-private:
-    DNSService(const DNSService& source);
-    DNSService& operator=(const DNSService& source);
-
-public:
-    /// \brief The constructor with a specific IP address and port on which
-    /// the services listen on.
-    ///
-    /// \param io_service The IOService to work with
-    /// \param port the port to listen on
-    /// \param address the IP address to listen on
-    /// \param checkin Provider for cc-channel events (see \c SimpleCallback)
-    /// \param lookup The lookup provider (see \c DNSLookup)
-    /// \param answer The answer provider (see \c DNSAnswer)
-    DNSService(IOService& io_service, const char& port,
-               const char& address, SimpleCallback* checkin,
-               DNSLookup* lookup, DNSAnswer* answer);
-    /// \brief The constructor with a specific port on which the services
-    /// listen on.
-    ///
-    /// It effectively listens on "any" IPv4 and/or IPv6 addresses.
-    /// IPv4/IPv6 services will be available if and only if \c use_ipv4
-    /// or \c use_ipv6 is \c true, respectively.
-    ///
-    /// \param io_service The IOService to work with
-    /// \param port the port to listen on
-    /// \param ipv4 If true, listen on ipv4 'any'
-    /// \param ipv6 If true, listen on ipv6 'any'
-    /// \param checkin Provider for cc-channel events (see \c SimpleCallback)
-    /// \param lookup The lookup provider (see \c DNSLookup)
-    /// \param answer The answer provider (see \c DNSAnswer)
-    DNSService(IOService& io_service, const char& port,
-               const bool use_ipv4, const bool use_ipv6,
-               SimpleCallback* checkin, DNSLookup* lookup,
-               DNSAnswer* answer);
-    /// \brief The constructor without any servers.
-    ///
-    /// Use addServer() to add some servers.
-    DNSService(IOService& io_service, SimpleCallback* checkin,
-               DNSLookup* lookup, DNSAnswer* answer);
-    /// \brief The destructor.
-    ~DNSService();
-    //@}
-
-    /// \brief Add another server to the service
-    void addServer(uint16_t port, const std::string &address);
-    void addServer(const char &port, const std::string &address);
-    /// \brief Remove all servers from the service
-    void clearServers();
-
-    /// \brief Return the native \c io_service object used in this wrapper.
-    ///
-    /// This is a short term work around to support other BIND 10 modules
-    /// that share the same \c io_service with the authoritative server.
-    /// It will eventually be removed once the wrapper interface is
-    /// generalized.
-    asio::io_service& get_io_service() { return io_service_.get_io_service(); }
-private:
-    DNSServiceImpl* impl_;
-    IOService& io_service_;
-};
-
-/// \brief The \c DNSServer class is a wrapper (and base class) for
-/// classes which provide DNS server functionality.
-/// 
-/// The classes derived from this one, \c TCPServer and \c UDPServer,
-/// act as the interface layer between clients sending queries, and
-/// functions defined elsewhere that provide answers to those queries.
-/// Those functions are described in more detail below under
-/// \c SimpleCallback, \c DNSLookup, and \c DNSAnswer.
-///
-/// Notes to developers:
-/// When constructed, this class (and its derived classes) will have its
-/// "self_" member set to point to "this".  Objects of this class (as
-/// instantiated through a base class) are sometimes passed by
-/// reference (as this superclass); calls to methods in the base
-/// class are then rerouted via this pointer to methods in the derived
-/// class.  This allows code from outside asiolink, with no specific
-/// knowledge of \c TCPServer or \c UDPServer, to access their methods.
-///
-/// This class is both assignable and copy-constructable.  Its subclasses
-/// use the "stackless coroutine" pattern, meaning that it will copy itself
-/// when "forking", and that instances will be posted as ASIO handler
-/// objects, which are always copied.
-///
-/// Because these objects are frequently copied, it is recommended 
-/// that derived classes be kept small to reduce copy overhead.
-class DNSServer {
-protected: 
-    ///
-    /// \name Constructors and destructors
-    ///
-    /// This is intentionally defined as \c protected, as this base class
-    /// should never be instantiated except as part of a derived class.
-    //@{
-    DNSServer() : self_(this) {}
-public:
-    /// \brief The destructor
-    virtual ~DNSServer() {}
-    //@}
-
-    ///
-    /// \name Class methods
-    ///
-    /// These methods all make their calls indirectly via the "self_"
-    /// pointer, ensuring that the functions ultimately invoked will be
-    /// the ones in the derived class.  This makes it possible to pass
-    /// instances of derived classes as references to this base class
-    /// without losing access to derived class data.
-    /// 
-    //@{
-    /// \brief The funtion operator
-    virtual void operator()(asio::error_code ec = asio::error_code(),
-                            size_t length = 0)
-    {
-        (*self_)(ec, length);
-    }
-
-    /// \brief Resume processing of the server coroutine after an 
-    /// asynchronous call (e.g., to the DNS Lookup provider) has completed.
-    ///
-    /// \param done If true, this signals the system there is an answer
-    ///             to return.
-    virtual void resume(const bool done) { self_->resume(done); }
-
-    /// \brief Indicate whether the server is able to send an answer
-    /// to a query.
-    /// 
-    /// This is presently used only for testing purposes.
-    virtual bool hasAnswer() { return (self_->hasAnswer()); }
-
-    /// \brief Returns the current value of the 'coroutine' object
-    ///
-    /// This is a temporary method, intended to be used for debugging
-    /// purposes during development and removed later.  It allows
-    /// callers from outside the coroutine object to retrieve information
-    /// about its current state.
-    ///
-    /// \return The value of the 'coroutine' object
-    virtual int value() { return (self_->value()); }
-
-    /// \brief Returns a pointer to a clone of this DNSServer object.
-    ///
-    /// When a \c DNSServer object is copied or assigned, the result will
-    /// normally be another \c DNSServer object containing a copy
-    /// of the original "self_" pointer.  Calling clone() guarantees
-    /// that the underlying object is also correctly copied.
-    ///
-    /// \return A deep copy of this DNSServer object
-    virtual DNSServer* clone() { return (self_->clone()); }
-    //@}
-
-protected:
-    /// \brief Lookup handler object.
-    ///
-    /// This is a protected class; it can only be instantiated
-    /// from within a derived class of \c DNSServer.
-    ///
-    /// A server object that has received a query creates an instance
-    /// of this class and scheudles it on the ASIO service queue
-    /// using asio::io_service::post().  When the handler executes, it
-    /// calls the asyncLookup() method in the server object to start a
-    /// DNS lookup.  When the lookup is complete, the server object is
-    /// scheduled to resume, again using io_service::post().
-    ///
-    /// Note that the calling object is copied into the handler object,
-    /// not referenced.  This is because, once the calling object yields
-    /// control to the handler, it falls out of scope and may disappear
-    template <typename T>
-    class AsyncLookup {
-    public:
-        AsyncLookup(T& caller) : caller_(caller) {}
-        void operator()() { caller_.asyncLookup(); }
-    private:
-        T caller_;
-    };
-
-    /// \brief Carries out a DNS lookup.
-    ///
-    /// This function calls the \c DNSLookup object specified by the
-    /// DNS server when the \c IOService was created, passing along
-    /// the details of the query and a pointer back to the current
-    /// server object.  It is called asynchronously via the AsyncLookup
-    /// handler class.
-    virtual void asyncLookup() { self_->asyncLookup(); }
-
-private:
-    DNSServer* self_;
-};
-
-/// \brief The \c DNSLookup class is an abstract base class for a DNS
-/// Lookup provider function.
-///
-/// Specific derived class implementations are hidden within the
-/// implementation.  Instances of the derived classes can be called
-/// as functions via the operator() interface.  Pointers to these
-/// instances can then be provided to the \c IOService class
-/// via its constructor.
-///
-/// A DNS Lookup provider function obtains the data needed to answer
-/// a DNS query (e.g., from authoritative data source, cache, or upstream
-/// query).  After it has run, the OutputBuffer object passed to it
-/// should contain the answer to the query, in an internal representation.
-class DNSLookup {
-    ///
-    /// \name Constructors and Destructor
-    ///
-    /// Note: The copy constructor and the assignment operator are
-    /// intentionally defined as private, making this class non-copyable.
-    //@{
-private:
-    DNSLookup(const DNSLookup& source);
-    DNSLookup& operator=(const DNSLookup& source);
-protected:
-    /// \brief The default constructor.
-    ///
-    /// This is intentionally defined as \c protected as this base class
-    /// should never be instantiated (except as part of a derived class).
-    DNSLookup() : self_(this) {}
-public:
-    /// \brief The destructor
-    virtual ~DNSLookup() {}
-    //@}
-    /// \brief The function operator
-    ///
-    /// This makes its call indirectly via the "self" pointer, ensuring
-    /// that the function ultimately invoked will be the one in the derived
-    /// class.
-    ///
-    /// \param io_message The event message to handle
-    /// \param message The DNS MessagePtr that needs handling
-    /// \param buffer The final answer is put here
-    /// \param DNSServer DNSServer object to use
-    virtual void operator()(const IOMessage& io_message,
-                            isc::dns::MessagePtr message,
-                            isc::dns::MessagePtr answer_message,
-                            isc::dns::OutputBufferPtr buffer,
-                            DNSServer* server) const
-    {
-        (*self_)(io_message, message, answer_message, buffer, server);
-    }
-private:
-    DNSLookup* self_;
-};
-
-/// \brief The \c DNSAnswer class is an abstract base class for a DNS
-/// Answer provider function.
-///
-/// Specific derived class implementations are hidden within the
-/// implementation.  Instances of the derived classes can be called
-/// as functions via the operator() interface.  Pointers to these
-/// instances can then be provided to the \c IOService class
-/// via its constructor.
-///
-/// A DNS Answer provider function takes answer data that has been obtained
-/// from a DNS Lookup provider functon and readies it to be sent to the
-/// client.  After it has run, the OutputBuffer object passed to it should
-/// contain the answer to the query rendered into wire format.
-class DNSAnswer {
-    ///
-    /// \name Constructors and Destructor
-    ///
-    /// Note: The copy constructor and the assignment operator are
-    /// intentionally defined as private, making this class non-copyable.
-    //@{
-private:
-    DNSAnswer(const DNSAnswer& source);
-    DNSAnswer& operator=(const DNSAnswer& source);
-protected:
-    /// \brief The default constructor.
-    ///
-    /// This is intentionally defined as \c protected as this base class
-    /// should never be instantiated (except as part of a derived class).
-    DNSAnswer() {}
-public:
-    /// \brief The destructor
-    virtual ~DNSAnswer() {}
-    //@}
-    /// \brief The function operator
-    ///
-    /// This makes its call indirectly via the "self" pointer, ensuring
-    /// that the function ultimately invoked will be the one in the derived
-    /// class.
-    ///
-    /// \param io_message The event message to handle
-    /// \param message The DNS MessagePtr that needs handling
-    /// \param buffer The result is put here
-    virtual void operator()(const IOMessage& io_message,
-                            isc::dns::MessagePtr message,
-                            isc::dns::MessagePtr answer_message,
-                            isc::dns::OutputBufferPtr buffer) const = 0;
-};
-
-/// \brief The \c SimpleCallback class is an abstract base class for a
-/// simple callback function with the signature:
-///
-/// void simpleCallback(const IOMessage& io_message) const;
-///
-/// Specific derived class implementations are hidden within the
-/// implementation.  Instances of the derived classes can be called
-/// as functions via the operator() interface.  Pointers to these
-/// instances can then be provided to the \c IOService class
-/// via its constructor.
-///
-/// The \c SimpleCallback is expected to be used for basic, generic
-/// tasks such as checking for configuration changes.  It may also be
-/// used for testing purposes.
-class SimpleCallback {
-    ///
-    /// \name Constructors and Destructor
-    ///
-    /// Note: The copy constructor and the assignment operator are
-    /// intentionally defined as private, making this class non-copyable.
-    //@{
-private:
-    SimpleCallback(const SimpleCallback& source);
-    SimpleCallback& operator=(const SimpleCallback& source);
-protected:
-    /// \brief The default constructor.
-    ///
-    /// This is intentionally defined as \c protected as this base class
-    /// should never be instantiated (except as part of a derived class).
-    SimpleCallback() : self_(this) {}
-public:
-    /// \brief The destructor
-    virtual ~SimpleCallback() {}
-    /// \brief The function operator
-    //@}
-    ///
-    /// This makes its call indirectly via the "self" pointer, ensuring
-    /// that the function ultimately invoked will be the one in the derived
-    /// class.
-    ///
-    /// \param io_message The event message to handle
-    virtual void operator()(const IOMessage& io_message) const {
-        (*self_)(io_message);
-    }
-private:
-    SimpleCallback* self_;
-};
-
-/// \brief The \c RecursiveQuery class provides a layer of abstraction around
-/// the ASIO code that carries out an upstream query.
-///
-/// This design is very preliminary; currently it is only capable of
-/// handling simple forward requests to a single resolver.
-class RecursiveQuery {
-    ///
-    /// \name Constructors
-    ///
-    //@{
-public:
-    /// \brief Constructor for use when acting as a forwarder
-    ///
-    /// This is currently the only way to construct \c RecursiveQuery
-    /// object.  The addresses of the forward nameservers is specified,
-    /// and every upstream query will be sent to one random address.
-    /// \param dns_service The DNS Service to perform the recursive
-    ///        query on.
-    /// \param upstream Addresses and ports of the upstream servers
-    ///        to forward queries to.
-    /// \param upstream_root Addresses and ports of the root servers
-    ///        to use when resolving.
-    /// \param timeout How long to timeout the query, in ms
-    ///     -1 means never timeout (but do not use that).
-    ///     TODO: This should be computed somehow dynamically in future
-    /// \param retries how many times we try again (0 means just send and
-    ///     and return if it returs).
-    RecursiveQuery(DNSService& dns_service,
-                   const std::vector<std::pair<std::string, uint16_t> >&
-                   upstream, 
-                   const std::vector<std::pair<std::string, uint16_t> >&
-                   upstream_root, 
-                   int query_timeout = 2000,
-                   int client_timeout = 4000,
-                   int lookup_timeout = 30000,
-                   unsigned retries = 3);
-    //@}
-
-    /// \brief Initiates an upstream query in the \c RecursiveQuery object.
-    ///
-    /// When sendQuery() is called, a message is sent asynchronously to
-    /// the upstream name server.  When a reply arrives, 'server'
-    /// is placed on the ASIO service queue via io_service::post(), so
-    /// that the original \c DNSServer objct can resume processing.
-    ///
-    /// \param question The question being answered <qname/qclass/qtype>
-    /// \param buffer An output buffer into which the response can be copied
-    /// \param server A pointer to the \c DNSServer object handling the client
-    void sendQuery(const isc::dns::Question& question,
-                   isc::dns::MessagePtr answer_message,
-                   isc::dns::OutputBufferPtr buffer,
-                   DNSServer* server);
-private:
-    DNSService& dns_service_;
-    boost::shared_ptr<std::vector<std::pair<std::string, uint16_t> > >
-        upstream_;
-    boost::shared_ptr<std::vector<std::pair<std::string, uint16_t> > >
-        upstream_root_;
-    int query_timeout_;
-    int client_timeout_;
-    int lookup_timeout_;
-    unsigned retries_;
-};
-
-/// \brief The \c IntervalTimer class is a wrapper for the ASIO
-/// \c asio::deadline_timer class.
-///
-/// This class is implemented to use \c asio::deadline_timer as
-/// interval timer.
-///
-/// \c setupTimer() sets a timer to expire on (now + interval) and
-/// a call back function.
-///
-/// \c IntervalTimerImpl::callback() is called by the timer when
-/// it expires.
-///
-/// The function calls the call back function set by \c setupTimer()
-/// and updates the timer to expire in (now + interval) seconds.
-/// The type of call back function is \c void(void).
-/// 
-/// The call back function will not be called if the instance of this
-/// class is destructed before the timer is expired.
-///
-/// Note: Destruction of an instance of this class while call back
-/// is pending causes throwing an exception from \c IOService.
-///
-/// Sample code:
-/// \code
-///  void function_to_call_back() {
-///      // this function will be called periodically
-///  }
-///  int interval_in_seconds = 1;
-///  IOService io_service;
-///
-///  IntervalTimer intervalTimer(io_service);
-///  intervalTimer.setupTimer(function_to_call_back, interval_in_seconds);
-///  io_service.run();
-/// \endcode
-///
-class IntervalTimer {
-public:
-    /// \name The type of timer callback function
-    typedef boost::function<void()> Callback;
-
-    ///
-    /// \name Constructors and Destructor
-    ///
-    /// Note: The copy constructor and the assignment operator are
-    /// intentionally defined as private, making this class non-copyable.
-    //@{
-private:
-    IntervalTimer(const IntervalTimer& source);
-    IntervalTimer& operator=(const IntervalTimer& source);
-public:
-    /// \brief The constructor with \c IOService.
-    ///
-    /// This constructor may throw a standard exception if
-    /// memory allocation fails inside the method.
-    /// This constructor may also throw \c asio::system_error.
-    ///
-    /// \param io_service A reference to an instance of IOService
-    ///
-    IntervalTimer(IOService& io_service);
-
-    /// \brief The destructor.
-    ///
-    /// This destructor never throws an exception.
-    ///
-    /// On the destruction of this class the timer will be canceled
-    /// inside \c asio::deadline_timer.
-    ///
-    ~IntervalTimer();
-    //@}
-
-    /// \brief Register timer callback function and interval.
-    ///
-    /// This function sets callback function and interval in seconds.
-    /// Timer will actually start after calling \c IOService::run().
-    ///
-    /// \param cbfunc A reference to a function \c void(void) to call back
-    /// when the timer is expired (should not be an empty functor)
-    /// \param interval Interval in seconds (greater than 0)
-    ///
-    /// Note: IntervalTimer will not pass \c asio::error_code to
-    /// call back function. In case the timer is cancelled, the function
-    /// will not be called.
-    ///
-    /// \throw isc::InvalidParameter cbfunc is empty
-    /// \throw isc::BadValue interval is 0
-    /// \throw isc::Unexpected ASIO library error
-    ///
-    void setupTimer(const Callback& cbfunc, const uint32_t interval);
-
-    /// Cancel the timer.
-    ///
-    /// If the timer has been set up, this method cancels any asynchronous
-    /// events waiting on the timer and stops the timer itself.
-    /// If the timer has already been canceled, this method effectively does
-    /// nothing.
-    ///
-    /// This method never throws an exception.
-    void cancel();
-
-    /// Return the timer interval.
-    ///
-    /// This method returns the timer interval in seconds if it's running;
-    /// if the timer has been canceled it returns 0.
-    ///
-    /// This method never throws an exception.
-    ///
-    /// Note: We may want to change the granularity of the timer to
-    /// milliseconds or even finer.  If and when this happens the semantics
-    /// of the return value of this method will be changed accordingly.
-    uint32_t getInterval() const;
-
-private:
-    IntervalTimerImpl* impl_;
-};
 
 }      // asiolink
 #endif // __ASIOLINK_H

+ 73 - 0
src/lib/asiolink/dns_answer.h

@@ -0,0 +1,73 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef __ASIOLINK_DNS_ANSWER_H
+#define __ASIOLINK_DNS_ANSWER_H 1
+
+#include <asiolink/io_message.h>
+
+namespace asiolink {
+
+/// \brief The \c DNSAnswer class is an abstract base class for a DNS
+/// Answer provider function.
+///
+/// Specific derived class implementations are hidden within the
+/// implementation.  Instances of the derived classes can be called
+/// as functions via the operator() interface.  Pointers to these
+/// instances can then be provided to the \c IOService class
+/// via its constructor.
+///
+/// A DNS Answer provider function takes answer data that has been obtained
+/// from a DNS Lookup provider functon and readies it to be sent to the
+/// client.  After it has run, the OutputBuffer object passed to it should
+/// contain the answer to the query rendered into wire format.
+class DNSAnswer {
+    ///
+    /// \name Constructors and Destructor
+    ///
+    /// Note: The copy constructor and the assignment operator are
+    /// intentionally defined as private, making this class non-copyable.
+    //@{
+private:
+    DNSAnswer(const DNSAnswer& source);
+    DNSAnswer& operator=(const DNSAnswer& source);
+protected:
+    /// \brief The default constructor.
+    ///
+    /// This is intentionally defined as \c protected as this base class
+    /// should never be instantiated (except as part of a derived class).
+    DNSAnswer() {}
+public:
+    /// \brief The destructor
+    virtual ~DNSAnswer() {}
+    //@}
+    /// \brief The function operator
+    ///
+    /// This makes its call indirectly via the "self" pointer, ensuring
+    /// that the function ultimately invoked will be the one in the derived
+    /// class.
+    ///
+    /// \param io_message The event message to handle
+    /// \param query_message The DNS MessagePtr of the original query
+    /// \param answer_message The DNS MessagePtr of the answer we are
+    /// building
+    /// \param buffer Intermediate data results are put here
+    virtual void operator()(const IOMessage& io_message,
+                            isc::dns::MessagePtr query_message,
+                            isc::dns::MessagePtr answer_message,
+                            isc::dns::OutputBufferPtr buffer) const = 0;
+};
+
+}      // namespace asiolink
+#endif // __ASIOLINK_DNS_ANSWER_H

+ 81 - 0
src/lib/asiolink/dns_lookup.h

@@ -0,0 +1,81 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef __ASIOLINK_DNS_LOOKUP_H
+#define __ASIOLINK_DNS_LOOKUP_H 1
+
+#include <asiolink/io_message.h>
+#include <asiolink/dns_server.h>
+#include <dns/buffer.h>
+#include <dns/message.h>
+
+namespace asiolink {
+
+/// \brief The \c DNSLookup class is an abstract base class for a DNS
+/// Lookup provider function.
+///
+/// Specific derived class implementations are hidden within the
+/// implementation.  Instances of the derived classes can be called
+/// as functions via the operator() interface.  Pointers to these
+/// instances can then be provided to the \c IOService class
+/// via its constructor.
+///
+/// A DNS Lookup provider function obtains the data needed to answer
+/// a DNS query (e.g., from authoritative data source, cache, or upstream
+/// query).  After it has run, the OutputBuffer object passed to it
+/// should contain the answer to the query, in an internal representation.
+class DNSLookup {
+    ///
+    /// \name Constructors and Destructor
+    ///
+    /// Note: The copy constructor and the assignment operator are
+    /// intentionally defined as private, making this class non-copyable.
+    //@{
+private:
+    DNSLookup(const DNSLookup& source);
+    DNSLookup& operator=(const DNSLookup& source);
+protected:
+    /// \brief The default constructor.
+    ///
+    /// This is intentionally defined as \c protected as this base class
+    /// should never be instantiated (except as part of a derived class).
+    DNSLookup() : self_(this) {}
+public:
+    /// \brief The destructor
+    virtual ~DNSLookup() {}
+    //@}
+    /// \brief The function operator
+    ///
+    /// This makes its call indirectly via the "self" pointer, ensuring
+    /// that the function ultimately invoked will be the one in the derived
+    /// class.
+    ///
+    /// \param io_message The event message to handle
+    /// \param message The DNS MessagePtr that needs handling
+    /// \param buffer The final answer is put here
+    /// \param DNSServer DNSServer object to use
+    virtual void operator()(const IOMessage& io_message,
+                            isc::dns::MessagePtr message,
+                            isc::dns::MessagePtr answer_message,
+                            isc::dns::OutputBufferPtr buffer,
+                            DNSServer* server) const
+    {
+        (*self_)(io_message, message, answer_message, buffer, server);
+    }
+private:
+    DNSLookup* self_;
+};
+
+}      // namespace asiolink
+#endif // __ASIOLINK_DNS_LOOKUP_H

+ 152 - 0
src/lib/asiolink/dns_server.h

@@ -0,0 +1,152 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef __ASIOLINK_DNS_SERVER_H
+#define __ASIOLINK_DNS_SERVER_H 1
+
+#include <asiolink/io_message.h>
+
+namespace asiolink {
+
+/// \brief The \c DNSServer class is a wrapper (and base class) for
+/// classes which provide DNS server functionality.
+/// 
+/// The classes derived from this one, \c TCPServer and \c UDPServer,
+/// act as the interface layer between clients sending queries, and
+/// functions defined elsewhere that provide answers to those queries.
+/// Those functions are described in more detail below under
+/// \c SimpleCallback, \c DNSLookup, and \c DNSAnswer.
+///
+/// Notes to developers:
+/// When constructed, this class (and its derived classes) will have its
+/// "self_" member set to point to "this".  Objects of this class (as
+/// instantiated through a base class) are sometimes passed by
+/// reference (as this superclass); calls to methods in the base
+/// class are then rerouted via this pointer to methods in the derived
+/// class.  This allows code from outside asiolink, with no specific
+/// knowledge of \c TCPServer or \c UDPServer, to access their methods.
+///
+/// This class is both assignable and copy-constructable.  Its subclasses
+/// use the "stackless coroutine" pattern, meaning that it will copy itself
+/// when "forking", and that instances will be posted as ASIO handler
+/// objects, which are always copied.
+///
+/// Because these objects are frequently copied, it is recommended 
+/// that derived classes be kept small to reduce copy overhead.
+class DNSServer {
+protected: 
+    ///
+    /// \name Constructors and destructors
+    ///
+    /// This is intentionally defined as \c protected, as this base class
+    /// should never be instantiated except as part of a derived class.
+    //@{
+    DNSServer() : self_(this) {}
+public:
+    /// \brief The destructor
+    virtual ~DNSServer() {}
+    //@}
+
+    ///
+    /// \name Class methods
+    ///
+    /// These methods all make their calls indirectly via the "self_"
+    /// pointer, ensuring that the functions ultimately invoked will be
+    /// the ones in the derived class.  This makes it possible to pass
+    /// instances of derived classes as references to this base class
+    /// without losing access to derived class data.
+    /// 
+    //@{
+    /// \brief The funtion operator
+    virtual void operator()(asio::error_code ec = asio::error_code(),
+                            size_t length = 0)
+    {
+        (*self_)(ec, length);
+    }
+
+    /// \brief Resume processing of the server coroutine after an 
+    /// asynchronous call (e.g., to the DNS Lookup provider) has completed.
+    ///
+    /// \param done If true, this signals the system there is an answer
+    ///             to return.
+    virtual void resume(const bool done) { self_->resume(done); }
+
+    /// \brief Indicate whether the server is able to send an answer
+    /// to a query.
+    /// 
+    /// This is presently used only for testing purposes.
+    virtual bool hasAnswer() { return (self_->hasAnswer()); }
+
+    /// \brief Returns the current value of the 'coroutine' object
+    ///
+    /// This is a temporary method, intended to be used for debugging
+    /// purposes during development and removed later.  It allows
+    /// callers from outside the coroutine object to retrieve information
+    /// about its current state.
+    ///
+    /// \return The value of the 'coroutine' object
+    virtual int value() { return (self_->value()); }
+
+    /// \brief Returns a pointer to a clone of this DNSServer object.
+    ///
+    /// When a \c DNSServer object is copied or assigned, the result will
+    /// normally be another \c DNSServer object containing a copy
+    /// of the original "self_" pointer.  Calling clone() guarantees
+    /// that the underlying object is also correctly copied.
+    ///
+    /// \return A deep copy of this DNSServer object
+    virtual DNSServer* clone() { return (self_->clone()); }
+    //@}
+
+protected:
+    /// \brief Lookup handler object.
+    ///
+    /// This is a protected class; it can only be instantiated
+    /// from within a derived class of \c DNSServer.
+    ///
+    /// A server object that has received a query creates an instance
+    /// of this class and scheudles it on the ASIO service queue
+    /// using asio::io_service::post().  When the handler executes, it
+    /// calls the asyncLookup() method in the server object to start a
+    /// DNS lookup.  When the lookup is complete, the server object is
+    /// scheduled to resume, again using io_service::post().
+    ///
+    /// Note that the calling object is copied into the handler object,
+    /// not referenced.  This is because, once the calling object yields
+    /// control to the handler, it falls out of scope and may disappear
+    template <typename T>
+    class AsyncLookup {
+    public:
+        AsyncLookup(T& caller) : caller_(caller) {}
+        void operator()() { caller_.asyncLookup(); }
+    private:
+        T caller_;
+    };
+
+    /// \brief Carries out a DNS lookup.
+    ///
+    /// This function calls the \c DNSLookup object specified by the
+    /// DNS server when the \c IOService was created, passing along
+    /// the details of the query and a pointer back to the current
+    /// server object.  It is called asynchronously via the AsyncLookup
+    /// handler class.
+    virtual void asyncLookup() { self_->asyncLookup(); }
+
+private:
+    DNSServer* self_;
+};
+
+
+}      // asiolink
+#endif // __ASIOLINK_DNS_SERVER_H

+ 191 - 0
src/lib/asiolink/dns_service.cc

@@ -0,0 +1,191 @@
+// 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 <asiolink/io_service.h>
+
+#include <asio/ip/address.hpp>
+
+#include <asio.hpp>
+#include <asiolink/tcp_server.h>
+#include <asiolink/udp_server.h>
+
+#include <log/dummylog.h>
+
+#include <boost/lexical_cast.hpp>
+
+using isc::log::dlog;
+
+namespace asiolink {
+
+class SimpleCallback;
+class DNSLookup;
+class DNSAnswer;
+
+namespace {
+
+asio::ip::address
+convertAddr(const std::string& address) {
+    asio::error_code err;
+    asio::ip::address addr = asio::ip::address::from_string(address, err);
+    if (err) {
+        isc_throw(IOError, "Invalid IP address '" << &address << "': "
+            << err.message());
+    }
+    return (addr);
+}
+
+}
+
+
+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);
+
+    IOService& io_service_;
+
+    typedef boost::shared_ptr<UDPServer> UDPServerPtr;
+    typedef boost::shared_ptr<TCPServer> TCPServerPtr;
+    typedef boost::shared_ptr<DNSServer> DNSServerPtr;
+    std::vector<DNSServerPtr> servers_;
+    SimpleCallback *checkin_;
+    DNSLookup *lookup_;
+    DNSAnswer *answer_;
+
+    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));
+            TCPServerPtr tcpServer(new TCPServer(io_service_.get_io_service(),
+                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));
+            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.
+            isc_throw(IOError, "Failed to initialize network servers: " <<
+                      err.what());
+        }
+    }
+    void addServer(const char& port, const asio::ip::address& address) {
+        uint16_t portnum;
+        try {
+            // XXX: SunStudio with stlport4 doesn't reject some invalid
+            // representation such as "-1" by lexical_cast<uint16_t>, so
+            // we convert it into a signed integer of a larger size and perform
+            // range check ourselves.
+            const int32_t portnum32 = boost::lexical_cast<int32_t>(&port);
+            if (portnum32 < 0 || portnum32 > 65535) {
+                isc_throw(IOError, "Invalid port number '" << &port);
+            }
+            portnum = portnum32;
+        } catch (const boost::bad_lexical_cast& ex) {
+            isc_throw(IOError, "Invalid port number '" << &port << "': " <<
+                      ex.what());
+        }
+        addServer(portnum, address);
+    }
+};
+
+DNSServiceImpl::DNSServiceImpl(IOService& io_service,
+                               const char& port,
+                               const asio::ip::address* const v4addr,
+                               const asio::ip::address* const v6addr,
+                               SimpleCallback* checkin,
+                               DNSLookup* lookup,
+                               DNSAnswer* answer) :
+    io_service_(io_service),
+    checkin_(checkin),
+    lookup_(lookup),
+    answer_(answer)
+{
+
+    if (v4addr) {
+        addServer(port, *v4addr);
+    }
+    if (v6addr) {
+        addServer(port, *v6addr);
+    }
+}
+
+DNSService::DNSService(IOService& io_service,
+                       const char& port, const char& address,
+                       SimpleCallback* checkin,
+                       DNSLookup* lookup,
+                       DNSAnswer* answer) :
+    impl_(new DNSServiceImpl(io_service, port, NULL, NULL, checkin, lookup,
+        answer)), io_service_(io_service)
+{
+    addServer(port, &address);
+}
+
+DNSService::DNSService(IOService& io_service,
+                       const char& port,
+                       const bool use_ipv4, const bool use_ipv6,
+                       SimpleCallback* checkin,
+                       DNSLookup* lookup,
+                       DNSAnswer* answer) :
+    impl_(NULL), io_service_(io_service)
+{
+    const asio::ip::address v4addr_any =
+        asio::ip::address(asio::ip::address_v4::any());
+    const asio::ip::address* const v4addrp = use_ipv4 ? &v4addr_any : NULL; 
+    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);
+}
+
+DNSService::DNSService(IOService& io_service, SimpleCallback* checkin,
+    DNSLookup* lookup, DNSAnswer *answer) :
+    impl_(new DNSServiceImpl(io_service, *"0", NULL, NULL, checkin, lookup,
+        answer)), io_service_(io_service)
+{
+}
+
+DNSService::~DNSService() {
+    delete impl_;
+}
+
+void
+DNSService::addServer(const char& port, const std::string& address) {
+    impl_->addServer(port, convertAddr(address));
+}
+
+void
+DNSService::addServer(uint16_t port, const std::string& address) {
+    impl_->addServer(port, convertAddr(address));
+}
+
+void
+DNSService::clearServers() {
+    // FIXME: This does not work, it does not close the socket.
+    // How is it done?
+    impl_->servers_.clear();
+}
+
+
+
+} // namespace asiolink

+ 106 - 0
src/lib/asiolink/dns_service.h

@@ -0,0 +1,106 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef __ASIOLINK_DNS_SERVICE_H
+#define __ASIOLINK_DNS_SERVICE_H 1
+
+#include <resolve/resolver_interface.h>
+
+#include <asiolink/io_service.h>
+
+namespace asiolink {
+
+class SimpleCallback;
+class DNSLookup;
+class DNSAnswer;
+class DNSServiceImpl;
+
+///
+/// DNSService is the service that handles DNS queries and answers with
+/// a given IOService. This class is mainly intended to hold all the
+/// 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 {
+    ///
+    /// \name Constructors and Destructor
+    ///
+    /// Note: The copy constructor and the assignment operator are
+    /// intentionally defined as private, making this class non-copyable.
+    //@{
+private:
+    DNSService(const DNSService& source);
+    DNSService& operator=(const DNSService& source);
+
+public:
+    /// \brief The constructor with a specific IP address and port on which
+    /// the services listen on.
+    ///
+    /// \param io_service The IOService to work with
+    /// \param port the port to listen on
+    /// \param address the IP address to listen on
+    /// \param checkin Provider for cc-channel events (see \c SimpleCallback)
+    /// \param lookup The lookup provider (see \c DNSLookup)
+    /// \param answer The answer provider (see \c DNSAnswer)
+    DNSService(IOService& io_service, const char& port,
+               const char& address, SimpleCallback* checkin,
+               DNSLookup* lookup, DNSAnswer* answer);
+    /// \brief The constructor with a specific port on which the services
+    /// listen on.
+    ///
+    /// It effectively listens on "any" IPv4 and/or IPv6 addresses.
+    /// IPv4/IPv6 services will be available if and only if \c use_ipv4
+    /// or \c use_ipv6 is \c true, respectively.
+    ///
+    /// \param io_service The IOService to work with
+    /// \param port the port to listen on
+    /// \param ipv4 If true, listen on ipv4 'any'
+    /// \param ipv6 If true, listen on ipv6 'any'
+    /// \param checkin Provider for cc-channel events (see \c SimpleCallback)
+    /// \param lookup The lookup provider (see \c DNSLookup)
+    /// \param answer The answer provider (see \c DNSAnswer)
+    DNSService(IOService& io_service, const char& port,
+               const bool use_ipv4, const bool use_ipv6,
+               SimpleCallback* checkin, DNSLookup* lookup,
+               DNSAnswer* answer);
+    /// \brief The constructor without any servers.
+    ///
+    /// Use addServer() to add some servers.
+    DNSService(IOService& io_service, SimpleCallback* checkin,
+               DNSLookup* lookup, DNSAnswer* answer);
+    /// \brief The destructor.
+    ~DNSService();
+    //@}
+
+    /// \brief Add another server to the service
+    void addServer(uint16_t port, const std::string &address);
+    void addServer(const char &port, const std::string &address);
+    /// \brief Remove all servers from the service
+    void clearServers();
+
+    /// \brief Return the native \c io_service object used in this wrapper.
+    ///
+    /// This is a short term work around to support other BIND 10 modules
+    /// that share the same \c io_service with the authoritative server.
+    /// It will eventually be removed once the wrapper interface is
+    /// generalized.
+    asio::io_service& get_io_service() { return io_service_.get_io_service(); }
+private:
+    DNSServiceImpl* impl_;
+    IOService& io_service_;
+};
+
+}      // namespace asiolink
+#endif // __ASIOLINK_DNS_SERVICE_H

+ 0 - 1
src/lib/asiolink/internal/Makefile.am

@@ -1 +0,0 @@
-SUBDIRS = tests

+ 0 - 37
src/lib/asiolink/internal/tests/Makefile.am

@@ -1,37 +0,0 @@
-AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
-AM_CPPFLAGS += $(BOOST_INCLUDES)
-
-AM_CXXFLAGS = $(B10_CXXFLAGS)
-
-if USE_STATIC_LINK
-AM_LDFLAGS = -static
-endif
-
-CLEANFILES = *.gcno *.gcda
-
-TESTS =
-if HAVE_GTEST
-TESTS += run_unittests
-run_unittests_SOURCES = udpdns_unittest.cc
-run_unittests_SOURCES += run_unittests.cc
-run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
-run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
-run_unittests_LDADD = $(GTEST_LDADD)
-run_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libasiolink.la
-run_unittests_LDADD += $(top_builddir)/src/lib/dns/libdns++.la
-run_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
-run_unittests_LDADD += $(top_builddir)/src/lib/log/liblog.la
-# Note: the ordering matters: -Wno-... must follow -Wextra (defined in
-# B10_CXXFLAGS)
-run_unittests_CXXFLAGS = $(AM_CXXFLAGS)
-if USE_GXX
-run_unittests_CXXFLAGS += -Wno-unused-parameter
-endif
-if USE_CLANGPP
-# We need to disable -Werror for any test that uses internal definitions of
-# ASIO when using clang++
-run_unittests_CXXFLAGS += -Wno-error
-endif
-endif
-
-noinst_PROGRAMS = $(TESTS)

+ 0 - 244
src/lib/asiolink/internal/udpdns.h

@@ -1,244 +0,0 @@
-// Copyright (C) 2010  Internet Systems Consortium, Inc. ("ISC")
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-#ifndef __UDPDNS_H
-#define __UDPDNS_H 1
-
-#include <config.h>
-
-#include <asio.hpp>
-#include <boost/shared_array.hpp>
-#include <boost/shared_ptr.hpp>
-
-#include <dns/buffer.h>
-#include <dns/message.h>
-#include <dns/messagerenderer.h>
-
-#include <asiolink/asiolink.h>
-#include <asiolink/internal/coroutine.h>
-
-// This file contains UDP-specific implementations of generic classes 
-// defined in asiolink.h.  It is *not* intended to be part of the public
-// API.
-
-namespace asiolink {
-/// \brief The \c UDPEndpoint class is a concrete derived class of
-/// \c IOEndpoint that represents an endpoint of a UDP packet.
-///
-/// Other notes about \c TCPEndpoint applies to this class, too.
-class UDPEndpoint : public IOEndpoint {
-public:
-    ///
-    /// \name Constructors and Destructor.
-    ///
-    //@{
-    /// \brief Constructor from a pair of address and port.
-    ///
-    /// \param address The IP address of the endpoint.
-    /// \param port The UDP port number of the endpoint.
-    UDPEndpoint(const IOAddress& address, const unsigned short port) :
-        asio_endpoint_placeholder_(
-            new asio::ip::udp::endpoint(asio::ip::address::from_string(address.toText()),
-                              port)),
-        asio_endpoint_(*asio_endpoint_placeholder_)
-    {}
-
-    /// \brief Constructor from an ASIO UDP endpoint.
-    ///
-    /// This constructor is designed to be an efficient wrapper for the
-    /// corresponding ASIO class, \c udp::endpoint.
-    ///
-    /// \param asio_endpoint The ASIO representation of the UDP endpoint.
-    UDPEndpoint(const asio::ip::udp::endpoint& asio_endpoint) :
-        asio_endpoint_placeholder_(NULL), asio_endpoint_(asio_endpoint)
-    {}
-
-    /// \brief The destructor.
-    ~UDPEndpoint() { delete asio_endpoint_placeholder_; }
-    //@}
-
-    inline IOAddress getAddress() const {
-        return (asio_endpoint_.address());
-    }
-
-    inline uint16_t getPort() const {
-        return (asio_endpoint_.port());
-    }
-
-    inline short getProtocol() const {
-        return (asio_endpoint_.protocol().protocol());
-    }
-
-    inline short getFamily() const {
-        return (asio_endpoint_.protocol().family());
-    }
-
-    // This is not part of the exosed IOEndpoint API but allows
-    // direct access to the ASIO implementation of the endpoint
-    inline const asio::ip::udp::endpoint& getASIOEndpoint() const {
-        return (asio_endpoint_);
-    }
-
-private:
-    const asio::ip::udp::endpoint* asio_endpoint_placeholder_;
-    const asio::ip::udp::endpoint& asio_endpoint_;
-};
-
-/// \brief The \c UDPSocket class is a concrete derived class of
-/// \c IOSocket that represents a UDP socket.
-///
-/// Other notes about \c TCPSocket applies to this class, too.
-class UDPSocket : public IOSocket {
-private:
-    UDPSocket(const UDPSocket& source);
-    UDPSocket& operator=(const UDPSocket& source);
-public:
-    /// \brief Constructor from an ASIO UDP socket.
-    ///
-    /// \param socket The ASIO representation of the UDP socket.
-    UDPSocket(asio::ip::udp::socket& socket) : socket_(socket) {}
-
-    virtual int getNative() const { return (socket_.native()); }
-    virtual int getProtocol() const { return (IPPROTO_UDP); }
-
-private:
-    asio::ip::udp::socket& socket_;
-};
-
-//
-// Asynchronous UDP server coroutine
-//
-///
-/// \brief This class implements the coroutine to handle UDP
-///        DNS query event. As such, it is both a \c DNSServer and
-///        a \c coroutine
-///
-class UDPServer : public virtual DNSServer, public virtual coroutine {
-public:
-    /// \brief Constructor
-    /// \param io_service the asio::io_service to work with
-    /// \param addr the IP address to listen for queries on
-    /// \param port the port to listen for queries on
-    /// \param checkin the callbackprovider for non-DNS events
-    /// \param lookup the callbackprovider for DNS lookup events
-    /// \param answer the callbackprovider for DNS answer events
-    explicit UDPServer(asio::io_service& io_service,
-                       const asio::ip::address& addr, const uint16_t port,
-                       SimpleCallback* checkin = NULL,
-                       DNSLookup* lookup = NULL,
-                       DNSAnswer* answer = NULL);
-
-    /// \brief The function operator
-    void operator()(asio::error_code ec = asio::error_code(),
-                    size_t length = 0);
-
-    /// \brief Calls the lookup callback
-    void asyncLookup();
-
-    /// \brief Resume operation
-    ///
-    /// \param done Set this to true if the lookup action is done and
-    ///        we have an answer
-    void resume(const bool done);
-
-    /// \brief Check if we have an answer
-    ///
-    /// \return true if we have an answer
-    bool hasAnswer() { return (done_); }
-
-    /// \brief Returns the coroutine state value
-    ///
-    /// \return the coroutine state value
-    int value() { return (get_value()); }
-
-    /// \brief Clones the object
-    ///
-    /// \return a newly allocated copy of this object
-    DNSServer* clone() {
-        UDPServer* s = new UDPServer(*this);
-        return (s);
-    }
-
-private:
-    enum { MAX_LENGTH = 4096 };
-
-    // The ASIO service object
-    asio::io_service& io_;
-
-    // Class member variables which are dynamic, and changes to which
-    // need to accessible from both sides of a coroutine fork or from
-    // outside of the coroutine (i.e., from an asynchronous I/O call),
-    // should be declared here as pointers and allocated in the
-    // constructor or in the coroutine.  This allows state information
-    // to persist when an individual copy of the coroutine falls out
-    // scope while waiting for an event, *so long as* there is another
-    // object that is referencing the same data.  As a side-benefit, using
-    // pointers also reduces copy overhead for coroutine objects.
-    //
-    // Note: Currently these objects are allocated by "new" in the
-    // constructor, or in the function operator while processing a query.
-    // Repeated allocations from the heap for every incoming query is
-    // clearly a performance issue; this must be optimized in the future.
-    // The plan is to have a structure pre-allocate several "server state"
-    // objects which can be pulled off a free list and placed on an in-use
-    // list whenever a query comes in.  This will serve the dual purpose
-    // of improving performance and guaranteeing that state information
-    // will *not* be destroyed when any one instance of the coroutine
-    // falls out of scope while waiting for an event.
-    //
-    // Socket used to for listen for queries.  Created in the
-    // constructor and stored in a shared_ptr because socket objects
-    // are not copyable.
-    boost::shared_ptr<asio::ip::udp::socket> socket_;
-
-    // The ASIO-enternal endpoint object representing the client
-    boost::shared_ptr<asio::ip::udp::endpoint> sender_;
-
-    // \c IOMessage and \c Message objects to be passed to the
-    // DNS lookup and answer providers
-    boost::shared_ptr<asiolink::IOMessage> io_message_;
-
-    // The original query as sent by the client
-    isc::dns::MessagePtr query_message_;
-
-    // The response message we are building
-    isc::dns::MessagePtr answer_message_;
-
-    // The buffer into which the response is written
-    isc::dns::OutputBufferPtr respbuf_;
-    
-    // The buffer into which the query packet is written
-    boost::shared_array<char> data_;
-
-    // State information that is entirely internal to a given instance
-    // of the coroutine can be declared here.
-    size_t bytes_;
-    bool done_;
-
-    // Callback functions provided by the caller
-    const SimpleCallback* checkin_callback_;
-    const DNSLookup* lookup_callback_;
-    const DNSAnswer* answer_callback_;
-
-    boost::shared_ptr<IOEndpoint> peer_;
-    boost::shared_ptr<IOSocket> iosock_;
-};
-}
-
-
-#endif // __UDPDNS_H
-
-// Local Variables: 
-// mode: c++
-// End: 

+ 133 - 0
src/lib/asiolink/interval_timer.cc

@@ -0,0 +1,133 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+
+#include <asio.hpp>
+
+#include <exceptions/exceptions.h>
+
+#include <asiolink/interval_timer.h>
+#include <asiolink/io_service.h>
+
+#include <boost/bind.hpp>
+
+namespace asiolink {
+
+class IntervalTimerImpl {
+private:
+    // prohibit copy
+    IntervalTimerImpl(const IntervalTimerImpl& source);
+    IntervalTimerImpl& operator=(const IntervalTimerImpl& source);
+public:
+    IntervalTimerImpl(IOService& io_service);
+    ~IntervalTimerImpl();
+    void setup(const IntervalTimer::Callback& cbfunc, const long interval);
+    void callback(const asio::error_code& error);
+    void cancel() {
+        timer_.cancel();
+        interval_ = 0;
+    }
+    long getInterval() const { return (interval_); }
+private:
+    // a function to update timer_ when it expires
+    void update();
+    // a function to call back when timer_ expires
+    IntervalTimer::Callback cbfunc_;
+    // interval in milliseconds
+    long interval_;
+    // asio timer
+    asio::deadline_timer timer_;
+};
+
+IntervalTimerImpl::IntervalTimerImpl(IOService& io_service) :
+    interval_(0), timer_(io_service.get_io_service())
+{}
+
+IntervalTimerImpl::~IntervalTimerImpl()
+{}
+
+void
+IntervalTimerImpl::setup(const IntervalTimer::Callback& cbfunc,
+                         const long interval)
+{
+    // Interval should not be less than or equal to 0.
+    if (interval <= 0) {
+        isc_throw(isc::BadValue, "Interval should not be less than or "
+                                 "equal to 0");
+    }
+    // Call back function should not be empty.
+    if (cbfunc.empty()) {
+        isc_throw(isc::InvalidParameter, "Callback function is empty");
+    }
+    cbfunc_ = cbfunc;
+    interval_ = interval;
+    // Set initial expire time.
+    // At this point the timer is not running yet and will not expire.
+    // After calling IOService::run(), the timer will expire.
+    update();
+    return;
+}
+
+void
+IntervalTimerImpl::update() {
+    if (interval_ == 0) {
+        // timer has been canceled.  Do nothing.
+        return;
+    }
+    try {
+        // Update expire time to (current time + interval_).
+        timer_.expires_from_now(boost::posix_time::millisec(interval_));
+    } catch (const asio::system_error& e) {
+        isc_throw(isc::Unexpected, "Failed to update timer");
+    }
+    // Reset timer.
+    timer_.async_wait(boost::bind(&IntervalTimerImpl::callback, this, _1));
+}
+
+void
+IntervalTimerImpl::callback(const asio::error_code& cancelled) {
+    // Do not call cbfunc_ in case the timer was cancelled.
+    // The timer will be canelled in the destructor of asio::deadline_timer.
+    if (!cancelled) {
+        cbfunc_();
+        // Set next expire time.
+        update();
+    }
+}
+
+IntervalTimer::IntervalTimer(IOService& io_service) {
+    impl_ = new IntervalTimerImpl(io_service);
+}
+
+IntervalTimer::~IntervalTimer() {
+    delete impl_;
+}
+
+void
+IntervalTimer::setup(const Callback& cbfunc, const long interval) {
+    return (impl_->setup(cbfunc, interval));
+}
+
+void
+IntervalTimer::cancel() {
+    impl_->cancel();
+}
+
+long
+IntervalTimer::getInterval() const {
+    return (impl_->getInterval());
+}
+
+}

+ 133 - 0
src/lib/asiolink/interval_timer.h

@@ -0,0 +1,133 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef __ASIOLINK_INTERVAL_TIMER_H
+#define __ASIOLINK_INTERVAL_TIMER_H 1
+
+#include <boost/function.hpp>
+
+#include <asiolink/io_service.h>
+
+namespace asiolink {
+
+struct IntervalTimerImpl;
+
+/// \brief The \c IntervalTimer class is a wrapper for the ASIO
+/// \c asio::deadline_timer class.
+///
+/// This class is implemented to use \c asio::deadline_timer as interval
+/// timer.
+///
+/// \c setup() sets a timer to expire on (now + interval) and a call back
+/// function.
+///
+/// \c IntervalTimerImpl::callback() is called by the timer when it expires.
+///
+/// The function calls the call back function set by \c setup() and updates
+/// the timer to expire in (now + interval) milliseconds.
+/// The type of call back function is \c void(void).
+/// 
+/// The call back function will not be called if the instance of this class is
+/// destroyed before the timer is expired.
+///
+/// Note: Destruction of an instance of this class while call back is pending
+/// causes throwing an exception from \c IOService.
+///
+/// Sample code:
+/// \code
+///  void function_to_call_back() {
+///      // this function will be called periodically
+///  }
+///  int interval_in_milliseconds = 1000;
+///  IOService io_service;
+///
+///  IntervalTimer intervalTimer(io_service);
+///  intervalTimer.setup(function_to_call_back, interval_in_milliseconds);
+///  io_service.run();
+/// \endcode
+class IntervalTimer {
+public:
+    /// \name The type of timer callback function
+    typedef boost::function<void()> Callback;
+
+    ///
+    /// \name Constructors and Destructor
+    ///
+    /// Note: The copy constructor and the assignment operator are
+    /// intentionally defined as private, making this class non-copyable.
+    //@{
+private:
+    IntervalTimer(const IntervalTimer& source);
+    IntervalTimer& operator=(const IntervalTimer& source);
+public:
+    /// \brief The constructor with \c IOService.
+    ///
+    /// This constructor may throw a standard exception if
+    /// memory allocation fails inside the method.
+    /// This constructor may also throw \c asio::system_error.
+    ///
+    /// \param io_service A reference to an instance of IOService
+    IntervalTimer(IOService& io_service);
+
+    /// \brief The destructor.
+    ///
+    /// This destructor never throws an exception.
+    ///
+    /// On the destruction of this class the timer will be canceled
+    /// inside \c asio::deadline_timer.
+    ~IntervalTimer();
+    //@}
+
+    /// \brief Register timer callback function and interval.
+    ///
+    /// This function sets callback function and interval in milliseconds.
+    /// Timer will actually start after calling \c IOService::run().
+    ///
+    /// \param cbfunc A reference to a function \c void(void) to call back
+    /// when the timer is expired (should not be an empty functor)
+    /// \param interval Interval in milliseconds (greater than 0)
+    ///
+    /// Note: IntervalTimer will not pass \c asio::error_code to
+    /// call back function. In case the timer is cancelled, the function
+    /// will not be called.
+    ///
+    /// \throw isc::InvalidParameter cbfunc is empty
+    /// \throw isc::BadValue interval is less than or equal to 0
+    /// \throw isc::Unexpected ASIO library error
+    void setup(const Callback& cbfunc, const long interval);
+
+    /// Cancel the timer.
+    ///
+    /// If the timer has been set up, this method cancels any asynchronous
+    /// events waiting on the timer and stops the timer itself.
+    /// If the timer has already been canceled, this method effectively does
+    /// nothing.
+    ///
+    /// This method never throws an exception.
+    void cancel();
+
+    /// Return the timer interval.
+    ///
+    /// This method returns the timer interval in milliseconds if it's running;
+    /// if the timer has been canceled it returns 0.
+    ///
+    /// This method never throws an exception.
+    long getInterval() const;
+
+private:
+    IntervalTimerImpl* impl_;
+};
+
+}      // namespace asiolink
+#endif // __ASIOLINK_INTERVAL_TIMER_H

src/lib/asiolink/ioaddress.cc → src/lib/asiolink/io_address.cc


+ 40 - 1
src/lib/asiolink/ioaddress.h

@@ -73,9 +73,48 @@ public:
     /// \return A string representation of the address.
     std::string toText() const;
 
-    /// \brief Returns the address family.
+    /// \brief Returns the address family
+    ///
+    /// \return AF_INET for IPv4 or AF_INET6 for IPv6.
     short getFamily() const;
 
+    /// \brief Compare addresses for equality
+    ///
+    /// \param other Address to compare against.
+    ///
+    /// \return true if addresses are equal, false if not.
+    bool equals(const IOAddress& other) const {
+        return (asio_address_ == other.asio_address_);
+    }
+
+    /// \brief Compare addresses for equality
+    ///
+    /// \param other Address to compare against.
+    ///
+    /// \return true if addresses are equal, false if not.
+    bool operator==(const IOAddress& other) const {
+        return equals(other);
+    }
+
+    // \brief Compare addresses for inequality
+    ///
+    /// \param other Address to compare against.
+    ///
+    /// \return false if addresses are equal, true if not.
+    bool nequals(const IOAddress& other) const {
+        return (!equals(other));
+    }
+
+    // \brief Compare addresses for inequality
+    ///
+    /// \param other Address to compare against.
+    ///
+    /// \return false if addresses are equal, true if not.
+    bool operator!=(const IOAddress& other) const {
+        return (nequals(other));
+    }
+
+
 private:
     asio::ip::address asio_address_;
 };

+ 5 - 3
src/lib/asiolink/ioendpoint.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2010  Internet Systems Consortium, Inc. ("ISC")
+// 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
@@ -18,9 +18,11 @@
 #include <sys/socket.h>
 #include <netinet/in.h>
 
+#include <asio.hpp>
+
 #include <asiolink/asiolink.h>
-#include <internal/tcpdns.h>
-#include <internal/udpdns.h>
+#include <asiolink/tcp_endpoint.h>
+#include <asiolink/udp_endpoint.h>
 
 using namespace std;
 

+ 1 - 0
src/lib/asiolink/ioendpoint.h

@@ -24,6 +24,7 @@
 #include <string>
 
 #include <exceptions/exceptions.h>
+#include <asiolink/io_address.h>
 
 namespace asiolink {
 

+ 2 - 2
src/lib/asiolink/iomessage.h

@@ -25,8 +25,8 @@
 
 #include <exceptions/exceptions.h>
 
-#include <asiolink/ioendpoint.h>
-#include <asiolink/iosocket.h>
+#include <asiolink/io_endpoint.h>
+#include <asiolink/io_socket.h>
 
 namespace asiolink {
 

+ 95 - 0
src/lib/asiolink/io_service.cc

@@ -0,0 +1,95 @@
+// 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 <asio.hpp>
+
+#include <asiolink/io_service.h>
+
+namespace asiolink {
+
+class IOServiceImpl {
+private:
+    IOServiceImpl(const IOService& source);
+    IOServiceImpl& operator=(const IOService& source);
+public:
+    /// \brief The constructor
+    IOServiceImpl() :
+        io_service_(),
+        work_(io_service_)
+    {};
+    /// \brief The destructor.
+    ~IOServiceImpl() {};
+    //@}
+
+    /// \brief Start the underlying event loop.
+    ///
+    /// This method does not return control to the caller until
+    /// the \c stop() method is called via some handler.
+    void run() { io_service_.run(); };
+
+    /// \brief Run the underlying event loop for a single event.
+    ///
+    /// This method return control to the caller as soon as the
+    /// first handler has completed.  (If no handlers are ready when
+    /// it is run, it will block until one is.)
+    void run_one() { io_service_.run_one();} ;
+
+    /// \brief Stop the underlying event loop.
+    ///
+    /// This will return the control to the caller of the \c run() method.
+    void stop() { io_service_.stop();} ;
+
+    /// \brief Return the native \c io_service object used in this wrapper.
+    ///
+    /// This is a short term work around to support other BIND 10 modules
+    /// that share the same \c io_service with the authoritative server.
+    /// It will eventually be removed once the wrapper interface is
+    /// generalized.
+    asio::io_service& get_io_service() { return io_service_; };
+private:
+    asio::io_service io_service_;
+    asio::io_service::work work_;
+};
+
+IOService::IOService() {
+    io_impl_ = new IOServiceImpl();
+}
+
+IOService::~IOService() {
+    delete io_impl_;
+}
+
+void
+IOService::run() {
+    io_impl_->run();
+}
+
+void
+IOService::run_one() {
+    io_impl_->run_one();
+}
+
+void
+IOService::stop() {
+    io_impl_->stop();
+}
+
+asio::io_service&
+IOService::get_io_service() {
+    return (io_impl_->get_io_service());
+}
+
+} // namepsace asiolink

+ 77 - 0
src/lib/asiolink/io_service.h

@@ -0,0 +1,77 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef __ASIOLINK_IO_SERVICE_H
+#define __ASIOLINK_IO_SERVICE_H 1
+
+namespace asio {
+    class io_service;
+}
+
+namespace asiolink {
+
+struct IOServiceImpl;
+
+/// \brief The \c IOService class is a wrapper for the ASIO \c io_service
+/// class.
+///
+class IOService {
+    ///
+    /// \name Constructors and Destructor
+    ///
+    /// Note: The copy constructor and the assignment operator are
+    /// intentionally defined as private, making this class non-copyable.
+    //@{
+private:
+    IOService(const IOService& source);
+    IOService& operator=(const IOService& source);
+public:
+    /// \brief The constructor
+    IOService();
+    /// \brief The destructor.
+    ~IOService();
+    //@}
+
+    /// \brief Start the underlying event loop.
+    ///
+    /// This method does not return control to the caller until
+    /// the \c stop() method is called via some handler.
+    void run();
+
+    /// \brief Run the underlying event loop for a single event.
+    ///
+    /// This method return control to the caller as soon as the
+    /// first handler has completed.  (If no handlers are ready when
+    /// it is run, it will block until one is.)
+    void run_one();
+
+    /// \brief Stop the underlying event loop.
+    ///
+    /// This will return the control to the caller of the \c run() method.
+    void stop();
+
+    /// \brief Return the native \c io_service object used in this wrapper.
+    ///
+    /// This is a short term work around to support other BIND 10 modules
+    /// that share the same \c io_service with the authoritative server.
+    /// It will eventually be removed once the wrapper interface is
+    /// generalized.
+    asio::io_service& get_io_service();
+
+private:
+    IOServiceImpl* io_impl_;
+};
+
+}      // namespace asiolink
+#endif // __ASIOLINK_IO_SERVICE_H

+ 1 - 1
src/lib/asiolink/iosocket.cc

@@ -14,7 +14,7 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 
-#include "iosocket.h"
+#include "io_socket.h"
 
 #include <asio.hpp>
 

src/lib/asiolink/iosocket.h → src/lib/asiolink/io_socket.h


+ 454 - 0
src/lib/asiolink/recursive_query.cc

@@ -0,0 +1,454 @@
+// 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 <asio/ip/address.hpp>
+
+#include <asio.hpp>
+
+#include <asiolink/recursive_query.h>
+#include <asiolink/dns_service.h>
+#include <asiolink/udp_query.h>
+
+#include <log/dummylog.h>
+
+#include <boost/lexical_cast.hpp>
+#include <boost/bind.hpp>
+
+#include <dns/question.h>
+#include <dns/message.h>
+
+#include <resolve/resolve.h>
+
+using isc::log::dlog;
+using namespace isc::dns;
+
+namespace asiolink {
+
+typedef std::vector<std::pair<std::string, uint16_t> > AddressVector;
+
+// Here we do not use the typedef above, as the SunStudio compiler
+// mishandles this in its name mangling, and wouldn't compile.
+// We can probably use a typedef, but need to move it to a central
+// location and use it consistently.
+RecursiveQuery::RecursiveQuery(DNSService& dns_service,
+    const std::vector<std::pair<std::string, uint16_t> >& upstream,
+    const std::vector<std::pair<std::string, uint16_t> >& upstream_root,
+    int query_timeout, int client_timeout, int lookup_timeout,
+    unsigned retries) :
+    dns_service_(dns_service), upstream_(new AddressVector(upstream)),
+    upstream_root_(new AddressVector(upstream_root)),
+    query_timeout_(query_timeout), client_timeout_(client_timeout),
+    lookup_timeout_(lookup_timeout), retries_(retries)
+{}
+
+namespace {
+
+typedef std::pair<std::string, uint16_t> addr_t;
+
+/*
+ * This is a query in progress. When a new query is made, this one holds
+ * the context information about it, like how many times we are allowed
+ * to retry on failure, what to do when we succeed, etc.
+ *
+ * Used by RecursiveQuery::sendQuery.
+ */
+class RunningQuery : public UDPQuery::Callback {
+private:
+    // The io service to handle async calls
+    asio::io_service& io_;
+
+    // Info for (re)sending the query (the question and destination)
+    Question question_;
+
+    // This is where we build and store our final answer
+    MessagePtr answer_message_;
+
+    // currently we use upstream as the current list of NS records
+    // we should differentiate between forwarding and resolving
+    boost::shared_ptr<AddressVector> upstream_;
+
+    // root servers...just copied over to the zone_servers_
+    boost::shared_ptr<AddressVector> upstream_root_;
+
+    // Buffer to store the result.
+    OutputBufferPtr buffer_;
+
+    // Server to notify when we succeed or fail
+    //shared_ptr<DNSServer> server_;
+    isc::resolve::ResolverInterface::CallbackPtr resolvercallback_;
+
+    // To prevent both unreasonably long cname chains and cname loops,
+    // we simply keep a counter of the number of CNAMEs we have
+    // followed so far (and error if it exceeds RESOLVER_MAX_CNAME_CHAIN
+    // from lib/resolve/response_classifier.h)
+    unsigned cname_count_;
+
+    /*
+     * TODO Do something more clever with timeouts. In the long term, some
+     *     computation of average RTT, increase with each retry, etc.
+     */
+    // Timeout information
+    int query_timeout_;
+    unsigned retries_;
+
+    // normal query state
+
+    // Not using NSAS at this moment, so we keep a list
+    // of 'current' zone servers
+    std::vector<addr_t> zone_servers_;
+
+    // Update the question that will be sent to the server
+    void setQuestion(const Question& new_question) {
+        question_ = new_question;
+    }
+
+    // TODO: replace by our wrapper
+    asio::deadline_timer client_timer;
+    asio::deadline_timer lookup_timer;
+
+    size_t queries_out_;
+
+    // If we timed out ourselves (lookup timeout), stop issuing queries
+    bool done_;
+
+    // If we have a client timeout, we send back an answer, but don't
+    // stop. We use this variable to make sure we don't send another
+    // answer if we do find one later (or if we have a lookup_timeout)
+    bool answer_sent_;
+
+    // (re)send the query to the server.
+    void send() {
+        const int uc = upstream_->size();
+        const int zs = zone_servers_.size();
+        buffer_->clear();
+        if (uc > 0) {
+            int serverIndex = rand() % uc;
+            dlog("Sending upstream query (" + question_.toText() +
+                ") to " + upstream_->at(serverIndex).first);
+            UDPQuery query(io_, question_,
+                upstream_->at(serverIndex).first,
+                upstream_->at(serverIndex).second, buffer_, this,
+                query_timeout_);
+            ++queries_out_;
+            io_.post(query);
+        } else if (zs > 0) {
+            int serverIndex = rand() % zs;
+            dlog("Sending query to zone server (" + question_.toText() +
+                ") to " + zone_servers_.at(serverIndex).first);
+            UDPQuery query(io_, question_,
+                zone_servers_.at(serverIndex).first,
+                zone_servers_.at(serverIndex).second, buffer_, this,
+                query_timeout_);
+            ++queries_out_;
+            io_.post(query);
+        } else {
+            dlog("Error, no upstream servers to send to.");
+        }
+    }
+    
+    // This function is called by operator() if there is an actual
+    // answer from a server and we are in recursive mode
+    // depending on the contents, we go on recursing or return
+    //
+    // Note that the footprint may change as this function may
+    // need to append data to the answer we are building later.
+    //
+    // returns true if we are done (either we have an answer or an
+    //              error message)
+    // returns false if we are not done
+    bool handleRecursiveAnswer(const Message& incoming) {
+        dlog("Handle response");
+        // In case we get a CNAME, we store the target
+        // here (classify() will set it when it walks through
+        // the cname chain to verify it).
+        Name cname_target(question_.getName());
+        
+        isc::resolve::ResponseClassifier::Category category =
+            isc::resolve::ResponseClassifier::classify(
+                question_, incoming, cname_target, cname_count_, true);
+
+        bool found_ns_address = false;
+
+        switch (category) {
+        case isc::resolve::ResponseClassifier::ANSWER:
+        case isc::resolve::ResponseClassifier::ANSWERCNAME:
+            // Done. copy and return.
+            isc::resolve::copyResponseMessage(incoming, answer_message_);
+            return true;
+            break;
+        case isc::resolve::ResponseClassifier::CNAME:
+            dlog("Response is CNAME!");
+            // (unfinished) CNAME. We set our question_ to the CNAME
+            // target, then start over at the beginning (for now, that
+            // is, we reset our 'current servers' to the root servers).
+            if (cname_count_ >= RESOLVER_MAX_CNAME_CHAIN) {
+                // just give up
+                dlog("CNAME chain too long");
+                isc::resolve::makeErrorMessage(answer_message_,
+                                               Rcode::SERVFAIL());
+                return true;
+            }
+
+            answer_message_->appendSection(Message::SECTION_ANSWER,
+                                           incoming);
+            setZoneServersToRoot();
+
+            question_ = Question(cname_target, question_.getClass(),
+                                 question_.getType());
+
+            dlog("Following CNAME chain to " + question_.toText());
+            send();
+            return false;
+            break;
+        case isc::resolve::ResponseClassifier::NXDOMAIN:
+            // NXDOMAIN, just copy and return.
+            isc::resolve::copyResponseMessage(incoming, answer_message_);
+            return true;
+            break;
+        case isc::resolve::ResponseClassifier::REFERRAL:
+            // Referral. For now we just take the first glue address
+            // we find and continue with that
+            zone_servers_.clear();
+
+            for (RRsetIterator rrsi = incoming.beginSection(Message::SECTION_ADDITIONAL);
+                 rrsi != incoming.endSection(Message::SECTION_ADDITIONAL) && !found_ns_address;
+                 rrsi++) {
+                ConstRRsetPtr rrs = *rrsi;
+                if (rrs->getType() == RRType::A()) {
+                    // found address
+                    RdataIteratorPtr rdi = rrs->getRdataIterator();
+                    // just use the first for now
+                    if (!rdi->isLast()) {
+                        std::string addr_str = rdi->getCurrent().toText();
+                        dlog("[XX] first address found: " + addr_str);
+                        // now we have one address, simply
+                        // resend that exact same query
+                        // to that address and yield, when it
+                        // returns, loop again.
+                        
+                        // TODO should use NSAS
+                        zone_servers_.push_back(addr_t(addr_str, 53));
+                        found_ns_address = true;
+                    }
+                }
+            }
+            if (found_ns_address) {
+                // next resolver round
+                send();
+                return false;
+            } else {
+                dlog("[XX] no ready-made addresses in additional. need nsas.");
+                // TODO this will result in answering with the delegation. oh well
+                isc::resolve::copyResponseMessage(incoming, answer_message_);
+                return true;
+            }
+            break;
+        case isc::resolve::ResponseClassifier::EMPTY:
+        case isc::resolve::ResponseClassifier::EXTRADATA:
+        case isc::resolve::ResponseClassifier::INVNAMCLASS:
+        case isc::resolve::ResponseClassifier::INVTYPE:
+        case isc::resolve::ResponseClassifier::MISMATQUEST:
+        case isc::resolve::ResponseClassifier::MULTICLASS:
+        case isc::resolve::ResponseClassifier::NOTONEQUEST:
+        case isc::resolve::ResponseClassifier::NOTRESPONSE:
+        case isc::resolve::ResponseClassifier::NOTSINGLE:
+        case isc::resolve::ResponseClassifier::OPCODE:
+        case isc::resolve::ResponseClassifier::RCODE:
+        case isc::resolve::ResponseClassifier::TRUNCATED:
+            // Should we try a different server rather than SERVFAIL?
+            isc::resolve::makeErrorMessage(answer_message_,
+                                           Rcode::SERVFAIL());
+            return true;
+            break;
+        }
+        // should not be reached. assert here?
+        dlog("[FATAL] unreachable code");
+        return true;
+    }
+    
+public:
+    RunningQuery(asio::io_service& io,
+        const Question &question,
+        MessagePtr answer_message,
+        boost::shared_ptr<AddressVector> upstream,
+        boost::shared_ptr<AddressVector> upstream_root,
+        OutputBufferPtr buffer,
+        isc::resolve::ResolverInterface::CallbackPtr cb,
+        int query_timeout, int client_timeout, int lookup_timeout,
+        unsigned retries) :
+        io_(io),
+        question_(question),
+        answer_message_(answer_message),
+        upstream_(upstream),
+        upstream_root_(upstream_root),
+        buffer_(buffer),
+        resolvercallback_(cb),
+        cname_count_(0),
+        query_timeout_(query_timeout),
+        retries_(retries),
+        client_timer(io),
+        lookup_timer(io),
+        queries_out_(0),
+        done_(false),
+        answer_sent_(false)
+    {
+        // Setup the timer to stop trying (lookup_timeout)
+        if (lookup_timeout >= 0) {
+            lookup_timer.expires_from_now(
+                boost::posix_time::milliseconds(lookup_timeout));
+            lookup_timer.async_wait(boost::bind(&RunningQuery::stop, this, false));
+        }
+        
+        // Setup the timer to send an answer (client_timeout)
+        if (client_timeout >= 0) {
+            client_timer.expires_from_now(
+                boost::posix_time::milliseconds(client_timeout));
+            client_timer.async_wait(boost::bind(&RunningQuery::clientTimeout, this));
+        }
+        
+        // should use NSAS for root servers
+        // Adding root servers if not a forwarder
+        if (upstream_->empty()) {
+            setZoneServersToRoot();
+        }
+
+        send();
+    }
+
+    void setZoneServersToRoot() {
+        zone_servers_.clear();
+        if (upstream_root_->empty()) { //if no root ips given, use this
+            zone_servers_.push_back(addr_t("192.5.5.241", 53));
+        } else {
+            // copy the list
+            dlog("Size is " + 
+                boost::lexical_cast<std::string>(upstream_root_->size()) + 
+                "\n");
+            for(AddressVector::iterator it = upstream_root_->begin();
+                it < upstream_root_->end(); ++it) {
+            zone_servers_.push_back(addr_t(it->first,it->second));
+            dlog("Put " + zone_servers_.back().first + "into root list\n");
+            }
+        }
+    }
+    virtual void clientTimeout() {
+        // Return a SERVFAIL, but do not stop until
+        // we have an answer or timeout ourselves
+        isc::resolve::makeErrorMessage(answer_message_,
+                                       Rcode::SERVFAIL());
+        resolvercallback_->success(answer_message_);
+        answer_sent_ = true;
+    }
+
+    virtual void stop(bool resume) {
+        // if we cancel our timers, we will still get an event for
+        // that, so we cannot delete ourselves just yet (those events
+        // would be bound to a deleted object)
+        // cancel them one by one, both cancels should get us back
+        // here again.
+        // same goes if we have an outstanding query (can't delete
+        // until that one comes back to us)
+        done_ = true;
+        if (resume && !answer_sent_) {
+            resolvercallback_->success(answer_message_);
+        } else {
+            resolvercallback_->failure();
+        }
+        if (lookup_timer.cancel() != 0) {
+            return;
+        }
+        if (client_timer.cancel() != 0) {
+            return;
+        }
+        if (queries_out_ > 0) {
+            return;
+        }
+        delete this;
+    }
+
+    // This function is used as callback from DNSQuery.
+    virtual void operator()(UDPQuery::Result result) {
+        // XXX is this the place for TCP retry?
+        --queries_out_;
+        if (!done_ && result != UDPQuery::TIME_OUT) {
+            // we got an answer
+            Message incoming(Message::PARSE);
+            InputBuffer ibuf(buffer_->getData(), buffer_->getLength());
+            incoming.fromWire(ibuf);
+
+            if (upstream_->size() == 0 &&
+                incoming.getRcode() == Rcode::NOERROR()) {
+                done_ = handleRecursiveAnswer(incoming);
+            } else {
+                isc::resolve::copyResponseMessage(incoming, answer_message_);
+                done_ = true;
+            }
+            
+            if (done_) {
+                stop(true);
+            }
+        } else if (!done_ && retries_--) {
+            // We timed out, but we have some retries, so send again
+            dlog("Timeout, resending query");
+            send();
+        } else {
+            // out of retries, give up for now
+            stop(false);
+        }
+    }
+};
+
+}
+
+void
+RecursiveQuery::resolve(const QuestionPtr& question,
+    const isc::resolve::ResolverInterface::CallbackPtr callback)
+{
+    asio::io_service& io = dns_service_.get_io_service();
+
+    MessagePtr answer_message(new Message(Message::RENDER));
+    OutputBufferPtr buffer(new OutputBuffer(0));
+    
+    // It will delete itself when it is done
+    new RunningQuery(io, *question, answer_message, upstream_,
+                     upstream_root_, buffer, callback, query_timeout_,
+                     client_timeout_, lookup_timeout_, retries_);
+}
+
+void
+RecursiveQuery::resolve(const Question& question,
+                        MessagePtr answer_message,
+                        OutputBufferPtr buffer,
+                        DNSServer* server)
+{
+    // XXX: eventually we will need to be able to determine whether
+    // the message should be sent via TCP or UDP, or sent initially via
+    // UDP and then fall back to TCP on failure, but for the moment
+    // we're only going to handle UDP.
+    asio::io_service& io = dns_service_.get_io_service();
+
+    isc::resolve::ResolverInterface::CallbackPtr crs(
+        new isc::resolve::ResolverCallbackServer(server));
+    
+    // It will delete itself when it is done
+    new RunningQuery(io, question, answer_message, upstream_, upstream_root_,
+                         buffer, crs, query_timeout_, client_timeout_,
+                         lookup_timeout_, retries_);
+}
+
+
+
+} // namespace asiolink

+ 113 - 0
src/lib/asiolink/recursive_query.h

@@ -0,0 +1,113 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef __ASIOLINK_RECURSIVE_QUERY_H
+#define __ASIOLINK_RECURSIVE_QUERY_H 1
+
+#include <asiolink/dns_service.h>
+#include <asiolink/dns_server.h>
+#include <dns/buffer.h>
+
+namespace asiolink {
+/// \brief The \c RecursiveQuery class provides a layer of abstraction around
+/// the ASIO code that carries out an upstream query.
+///
+/// This design is very preliminary; currently it is only capable of
+/// handling simple forward requests to a single resolver.
+class RecursiveQuery {
+    ///
+    /// \name Constructors
+    ///
+    //@{
+public:
+    /// \brief Constructor
+    ///
+    /// This is currently the only way to construct \c RecursiveQuery
+    /// object. If the addresses of the forward nameservers is specified,
+    /// and every upstream query will be sent to one random address, and
+    /// the result sent back directly. If not, it will do full resolving.
+    ///
+    /// \param dns_service The DNS Service to perform the recursive
+    ///        query on.
+    /// \param upstream Addresses and ports of the upstream servers
+    ///        to forward queries to.
+    /// \param upstream_root Addresses and ports of the root servers
+    ///        to use when resolving.
+    /// \param query_timeout Timeout value for queries we sent, in ms
+    /// \param client_timeout Timeout value for when we send back an
+    ///        error, in ms
+    /// \param lookup_timeout Timeout value for when we give up, in ms
+    /// \param retries how many times we try again (0 means just send and
+    ///     and return if it returs).
+    RecursiveQuery(DNSService& dns_service,
+                   const std::vector<std::pair<std::string, uint16_t> >&
+                   upstream, 
+                   const std::vector<std::pair<std::string, uint16_t> >&
+                   upstream_root, 
+                   int query_timeout = 2000,
+                   int client_timeout = 4000,
+                   int lookup_timeout = 30000,
+                   unsigned retries = 3);
+    //@}
+
+    /// \brief Initiate resolving
+    /// 
+    /// When sendQuery() is called, a (set of) message(s) is sent
+    /// asynchronously. If upstream servers are set, one is chosen
+    /// and the response (if any) from that server will be returned.
+    ///
+    /// If not upstream is set, a root server is chosen from the
+    /// root_servers, and the RunningQuery shall do a full resolve
+    /// (i.e. if the answer is a delegation, it will be followed, etc.)
+    /// until there is an answer or an error.
+    ///
+    /// When there is a response or an error and we give up, the given
+    /// CallbackPtr object shall be called (with either success() or
+    /// failure(). See ResolverInterface::Callback for more information.
+    ///
+    /// \param question The question being answered <qname/qclass/qtype>
+    /// \param callback Callback object. See
+    ///        \c ResolverInterface::Callback for more information
+    void resolve(const isc::dns::QuestionPtr& question,
+                 const isc::resolve::ResolverInterface::CallbackPtr callback);
+
+
+    /// \brief Initiates resolving for the given question.
+    ///
+    /// This actually calls the previous sendQuery() with a default
+    /// callback object, which calls resume() on the given DNSServer
+    /// object.
+    ///
+    /// \param question The question being answered <qname/qclass/qtype>
+    /// \param answer_message An output Message into which the final response will be copied
+    /// \param buffer An output buffer into which the intermediate responses will be copied
+    /// \param server A pointer to the \c DNSServer object handling the client
+    void resolve(const isc::dns::Question& question,
+                 isc::dns::MessagePtr answer_message,
+                 isc::dns::OutputBufferPtr buffer,
+                 DNSServer* server);
+private:
+    DNSService& dns_service_;
+    boost::shared_ptr<std::vector<std::pair<std::string, uint16_t> > >
+        upstream_;
+    boost::shared_ptr<std::vector<std::pair<std::string, uint16_t> > >
+        upstream_root_;
+    int query_timeout_;
+    int client_timeout_;
+    int lookup_timeout_;
+    unsigned retries_;
+};
+
+}      // namespace asiolink
+#endif // __ASIOLINK_RECURSIVE_QUERY_H

+ 71 - 0
src/lib/asiolink/simple_callback.h

@@ -0,0 +1,71 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef __ASIOLINK_SIMPLE_CALLBACK_H
+#define __ASIOLINK_SIMPLE_CALLBACK_H 1
+
+#include <asiolink/io_message.h>
+
+namespace asiolink {
+
+/// \brief The \c SimpleCallback class is an abstract base class for a
+/// simple callback function with the signature:
+///
+/// void simpleCallback(const IOMessage& io_message) const;
+///
+/// Specific derived class implementations are hidden within the
+/// implementation.  Instances of the derived classes can be called
+/// as functions via the operator() interface.  Pointers to these
+/// instances can then be provided to the \c IOService class
+/// via its constructor.
+///
+/// The \c SimpleCallback is expected to be used for basic, generic
+/// tasks such as checking for configuration changes.  It may also be
+/// used for testing purposes.
+class SimpleCallback {
+    ///
+    /// \name Constructors and Destructor
+    ///
+    /// Note: The copy constructor and the assignment operator are
+    /// intentionally defined as private, making this class non-copyable.
+    //@{
+private:
+    SimpleCallback(const SimpleCallback& source);
+    SimpleCallback& operator=(const SimpleCallback& source);
+protected:
+    /// \brief The default constructor.
+    ///
+    /// This is intentionally defined as \c protected as this base class
+    /// should never be instantiated (except as part of a derived class).
+    SimpleCallback() : self_(this) {}
+public:
+    /// \brief The destructor
+    virtual ~SimpleCallback() {}
+    /// \brief The function operator
+    //@}
+    ///
+    /// This makes its call indirectly via the "self" pointer, ensuring
+    /// that the function ultimately invoked will be the one in the derived
+    /// class.
+    ///
+    /// \param io_message The event message to handle
+    virtual void operator()(const IOMessage& io_message) const {
+        (*self_)(io_message);
+    }
+private:
+    SimpleCallback* self_;
+};
+
+}      // namespace asiolink
+#endif // __ASIOLINK_SIMPLE_CALLBACK_H

+ 98 - 0
src/lib/asiolink/tcp_endpoint.h

@@ -0,0 +1,98 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef __TCP_ENDPOINT_H
+#define __TCP_ENDPOINT_H 1
+
+#ifndef ASIO_HPP
+#error "asio.hpp must be included before including this, see asiolink.h as to why"
+#endif
+
+#include <asiolink/io_endpoint.h>
+
+namespace asiolink {
+
+/// \brief The \c TCPEndpoint class is a concrete derived class of
+/// \c IOEndpoint that represents an endpoint of a TCP connection.
+///
+/// In the current implementation, an object of this class is always
+/// instantiated within the wrapper routines.  Applications are expected to
+/// get access to the object via the abstract base class, \c IOEndpoint.
+/// This design may be changed when we generalize the wrapper interface.
+///
+/// Note: this implementation is optimized for the case where this object
+/// is created from an ASIO endpoint object in a receiving code path
+/// by avoiding to make a copy of the base endpoint.  For TCP it may not be
+/// a big deal, but when we receive UDP packets at a high rate, the copy
+/// overhead might be significant.
+class TCPEndpoint : public IOEndpoint {
+public:
+    ///
+    /// \name Constructors and Destructor
+    ///
+    //@{
+    /// \brief Constructor from a pair of address and port.
+    ///
+    /// \param address The IP address of the endpoint.
+    /// \param port The TCP port number of the endpoint.
+    TCPEndpoint(const IOAddress& address, const unsigned short port) :
+        asio_endpoint_placeholder_(
+            new asio::ip::tcp::endpoint(
+                asio::ip::address::from_string(address.toText()), port)),
+        asio_endpoint_(*asio_endpoint_placeholder_)
+    {}
+
+    /// \brief Constructor from an ASIO TCP endpoint.
+    ///
+    /// This constructor is designed to be an efficient wrapper for the
+    /// corresponding ASIO class, \c tcp::endpoint.
+    ///
+    /// \param asio_endpoint The ASIO representation of the TCP endpoint.
+    TCPEndpoint(const asio::ip::tcp::endpoint& asio_endpoint) :
+        asio_endpoint_placeholder_(NULL), asio_endpoint_(asio_endpoint)
+    {}
+
+    /// \brief The destructor.
+    ~TCPEndpoint() { delete asio_endpoint_placeholder_; }
+    //@}
+
+    IOAddress getAddress() const {
+        return (asio_endpoint_.address());
+    }
+
+    uint16_t getPort() const {
+        return (asio_endpoint_.port());
+    }
+
+    short getProtocol() const {
+        return (asio_endpoint_.protocol().protocol());
+    }
+
+    short getFamily() const {
+        return (asio_endpoint_.protocol().family());
+    }
+
+    // This is not part of the exosed IOEndpoint API but allows
+    // direct access to the ASIO implementation of the endpoint
+    const asio::ip::tcp::endpoint& getASIOEndpoint() const {
+        return (asio_endpoint_);
+    }
+
+private:
+    const asio::ip::tcp::endpoint* asio_endpoint_placeholder_;
+    const asio::ip::tcp::endpoint& asio_endpoint_;
+};
+
+}      // namespace asiolink
+#endif // __TCP_ENDPOINT_H

+ 11 - 14
src/lib/asiolink/tcpdns.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2010  Internet Systems Consortium, Inc. ("ISC")
+// 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
@@ -14,22 +14,17 @@
 
 #include <config.h>
 
-#include <unistd.h>             // for some IPC/network system calls
-#include <sys/socket.h>
-#include <netinet/in.h>
+#include <boost/shared_array.hpp>
 
 #include <asio.hpp>
-#include <asio/ip/address.hpp>
 
-#include <boost/array.hpp>
-#include <boost/shared_ptr.hpp>
+#include <log/dummylog.h>
 
-#include <dns/buffer.h>
-#include <dns/message.h>
+#include <asiolink/tcp_endpoint.h>
+#include <asiolink/tcp_socket.h>
+
+#include <asiolink/tcp_server.h>
 
-#include <asiolink.h>
-#include <internal/coroutine.h>
-#include <internal/tcpdns.h>
 
 using namespace asio;
 using asio::ip::udp;
@@ -39,7 +34,8 @@ using namespace std;
 using namespace isc::dns;
 
 namespace asiolink {
-/// The following functions implement the \c UDPServer class.
+
+/// The following functions implement the \c TCPServer class.
 ///
 /// The constructor
 TCPServer::TCPServer(io_service& io_service,
@@ -191,4 +187,5 @@ TCPServer::resume(const bool done) {
     io_.post(*this);
 }
 
-}
+} // namespace asiolink
+

+ 9 - 114
src/lib/asiolink/internal/tcpdns.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2010  Internet Systems Consortium, Inc. ("ISC")
+// 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
@@ -12,121 +12,21 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 
-#ifndef __TCPDNS_H
-#define __TCPDNS_H 1
+#ifndef __TCP_SERVER_H
+#define __TCP_SERVER_H 1
 
-#include <config.h>
+#ifndef ASIO_HPP
+#error "asio.hpp must be included before including this, see asiolink.h as to why"
+#endif
 
-
-#include <asio.hpp>
 #include <boost/shared_array.hpp>
 #include <boost/shared_ptr.hpp>
 
-#include <dns/buffer.h>
-#include <dns/message.h>
-
 #include <asiolink/asiolink.h>
-#include <asiolink/internal/coroutine.h>
+#include <coroutine.h>
 
-// This file contains TCP-specific implementations of generic classes 
-// defined in asiolink.h.  It is *not* intended to be part of the public
-// API.
 
 namespace asiolink {
-/// \brief The \c TCPEndpoint class is a concrete derived class of
-/// \c IOEndpoint that represents an endpoint of a TCP connection.
-///
-/// In the current implementation, an object of this class is always
-/// instantiated within the wrapper routines.  Applications are expected to
-/// get access to the object via the abstract base class, \c IOEndpoint.
-/// This design may be changed when we generalize the wrapper interface.
-///
-/// Note: this implementation is optimized for the case where this object
-/// is created from an ASIO endpoint object in a receiving code path
-/// by avoiding to make a copy of the base endpoint.  For TCP it may not be
-/// a big deal, but when we receive UDP packets at a high rate, the copy
-/// overhead might be significant.
-class TCPEndpoint : public IOEndpoint {
-public:
-    ///
-    /// \name Constructors and Destructor
-    ///
-    //@{
-    /// \brief Constructor from a pair of address and port.
-    ///
-    /// \param address The IP address of the endpoint.
-    /// \param port The TCP port number of the endpoint.
-    TCPEndpoint(const IOAddress& address, const unsigned short port) :
-        asio_endpoint_placeholder_(
-            new asio::ip::tcp::endpoint(
-                asio::ip::address::from_string(address.toText()), port)),
-        asio_endpoint_(*asio_endpoint_placeholder_)
-    {}
-
-    /// \brief Constructor from an ASIO TCP endpoint.
-    ///
-    /// This constructor is designed to be an efficient wrapper for the
-    /// corresponding ASIO class, \c tcp::endpoint.
-    ///
-    /// \param asio_endpoint The ASIO representation of the TCP endpoint.
-    TCPEndpoint(const asio::ip::tcp::endpoint& asio_endpoint) :
-        asio_endpoint_placeholder_(NULL), asio_endpoint_(asio_endpoint)
-    {}
-
-    /// \brief The destructor.
-    ~TCPEndpoint() { delete asio_endpoint_placeholder_; }
-    //@}
-
-    IOAddress getAddress() const {
-        return (asio_endpoint_.address());
-    }
-
-    uint16_t getPort() const {
-        return (asio_endpoint_.port());
-    }
-
-    short getProtocol() const {
-        return (asio_endpoint_.protocol().protocol());
-    }
-
-    short getFamily() const {
-        return (asio_endpoint_.protocol().family());
-    }
-
-    // This is not part of the exosed IOEndpoint API but allows
-    // direct access to the ASIO implementation of the endpoint
-    const asio::ip::tcp::endpoint& getASIOEndpoint() const {
-        return (asio_endpoint_);
-    }
-
-private:
-    const asio::ip::tcp::endpoint* asio_endpoint_placeholder_;
-    const asio::ip::tcp::endpoint& asio_endpoint_;
-};
-
-/// \brief The \c TCPSocket class is a concrete derived class of
-/// \c IOSocket that represents a TCP socket.
-///
-/// In the current implementation, an object of this class is always
-/// instantiated within the wrapper routines.  Applications are expected to
-/// get access to the object via the abstract base class, \c IOSocket.
-/// This design may be changed when we generalize the wrapper interface.
-class TCPSocket : public IOSocket {
-private:
-    TCPSocket(const TCPSocket& source);
-    TCPSocket& operator=(const TCPSocket& source);
-public:
-    /// \brief Constructor from an ASIO TCP socket.
-    ///
-    /// \param socket The ASIO representation of the TCP socket.
-    TCPSocket(asio::ip::tcp::socket& socket) : socket_(socket) {}
-
-    int getNative() const { return (socket_.native()); }
-    int getProtocol() const { return (IPPROTO_TCP); }
-
-private:
-    asio::ip::tcp::socket& socket_;
-};
 
 /// \brief A TCP-specific \c DNSServer object.
 ///
@@ -215,10 +115,5 @@ private:
     boost::shared_ptr<IOSocket> iosock_;
 };
 
-}
-
-#endif // __TCPDNS_H
-
-// Local Variables: 
-// mode: c++
-// End: 
+}      // namespace asiolink
+#endif // __TCP_SERVER_H

+ 52 - 0
src/lib/asiolink/tcp_socket.h

@@ -0,0 +1,52 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef __TCP_SOCKET_H
+#define __TCP_SOCKET_H 1
+
+#ifndef ASIO_HPP
+#error "asio.hpp must be included before including this, see asiolink.h as to why"
+#endif
+
+#include <asiolink/io_socket.h>
+
+namespace asiolink {
+
+/// \brief The \c TCPSocket class is a concrete derived class of
+/// \c IOSocket that represents a TCP socket.
+///
+/// In the current implementation, an object of this class is always
+/// instantiated within the wrapper routines.  Applications are expected to
+/// get access to the object via the abstract base class, \c IOSocket.
+/// This design may be changed when we generalize the wrapper interface.
+class TCPSocket : public IOSocket {
+private:
+    TCPSocket(const TCPSocket& source);
+    TCPSocket& operator=(const TCPSocket& source);
+public:
+    /// \brief Constructor from an ASIO TCP socket.
+    ///
+    /// \param socket The ASIO representation of the TCP socket.
+    TCPSocket(asio::ip::tcp::socket& socket) : socket_(socket) {}
+
+    int getNative() const { return (socket_.native()); }
+    int getProtocol() const { return (IPPROTO_TCP); }
+
+private:
+    asio::ip::tcp::socket& socket_;
+};
+
+
+}      // namespace asiolink
+#endif // __TCP_SOCKET_H

+ 12 - 2
src/lib/asiolink/tests/Makefile.am

@@ -17,10 +17,16 @@ if HAVE_GTEST
 TESTS += run_unittests
 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 += asiolink_unittest.cc
+run_unittests_SOURCES += udp_query_unittest.cc
+run_unittests_SOURCES += ioaddress_unittest.cc
+run_unittests_SOURCES += ioendpoint_unittest.cc
+run_unittests_SOURCES += iosocket_unittest.cc
+run_unittests_SOURCES += io_service_unittest.cc
+run_unittests_SOURCES += interval_timer_unittest.cc
+run_unittests_SOURCES += recursive_query_unittest.cc
 run_unittests_SOURCES += run_unittests.cc
 run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
-run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
+run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS) $(LOG4CXX_LDFLAGS)
 run_unittests_LDADD = $(GTEST_LDADD)
 run_unittests_LDADD += $(SQLITE_LIBS)
 run_unittests_LDADD +=  $(top_builddir)/src/lib/dns/libdns++.la
@@ -33,6 +39,10 @@ run_unittests_CXXFLAGS = $(AM_CXXFLAGS)
 if USE_GXX
 run_unittests_CXXFLAGS += -Wno-unused-parameter
 endif
+if USE_CLANGPP
+# Same for clang++, but we need to turn off -Werror completely.
+run_unittests_CXXFLAGS += -Wno-error
+endif
 endif
 
 noinst_PROGRAMS = $(TESTS)

+ 292 - 0
src/lib/asiolink/tests/interval_timer_unittest.cc

@@ -0,0 +1,292 @@
+// 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 <asiolink/asiolink.h>
+
+#include <boost/date_time/posix_time/posix_time_types.hpp>
+
+namespace {
+// TODO: Consider this margin
+const boost::posix_time::time_duration TIMER_MARGIN_MSEC =
+    boost::posix_time::milliseconds(50);
+}
+
+using namespace asiolink;
+
+// This fixture is for testing IntervalTimer. Some callback functors are 
+// registered as callback function of the timer to test if they are called
+// or not.
+class IntervalTimerTest : public ::testing::Test {
+protected:
+    IntervalTimerTest() : io_service_() {}
+    ~IntervalTimerTest() {}
+    class TimerCallBack : public std::unary_function<void, void> {
+    public:
+        TimerCallBack(IntervalTimerTest* test_obj) : test_obj_(test_obj) {}
+        void operator()() const {
+            test_obj_->timer_called_ = true;
+            test_obj_->io_service_.stop();
+            return;
+        }
+    private:
+        IntervalTimerTest* test_obj_;
+    };
+    class TimerCallBackCounter : public std::unary_function<void, void> {
+    public:
+        TimerCallBackCounter(IntervalTimerTest* test_obj) : test_obj_(test_obj) {
+            counter_ = 0;
+        }
+        void operator()() {
+            ++counter_;
+            return;
+        }
+        int counter_;
+    private:
+        IntervalTimerTest* test_obj_;
+    };
+    class TimerCallBackCancelDeleter : public std::unary_function<void, void> {
+    public:
+        TimerCallBackCancelDeleter(IntervalTimerTest* test_obj,
+                                   IntervalTimer* timer,
+                                   TimerCallBackCounter& counter)
+            : test_obj_(test_obj), timer_(timer), counter_(counter), count_(0)
+        {}
+        void operator()() {
+            ++count_;
+            if (count_ == 1) {
+                // First time of call back.
+                // Store the value of counter_.counter_.
+                prev_counter_ = counter_.counter_;
+                delete timer_;
+            } else if (count_ == 2) {
+                // Second time of call back.
+                // Stop io_service to stop all timers.
+                test_obj_->io_service_.stop();
+                // Compare the value of counter_.counter_ with stored one.
+                // If TimerCallBackCounter was not called (expected behavior),
+                // they are same.
+                if (counter_.counter_ == prev_counter_) {
+                    test_obj_->timer_cancel_success_ = true;
+                }
+            }
+            return;
+        }
+    private:
+        IntervalTimerTest* test_obj_;
+        IntervalTimer* timer_;
+        TimerCallBackCounter& counter_;
+        int count_;
+        int prev_counter_;
+    };
+    class TimerCallBackCanceller {
+    public:
+        TimerCallBackCanceller(unsigned int& counter, IntervalTimer& itimer) :
+            counter_(counter), itimer_(itimer)
+        {}
+        void operator()() {
+            ++counter_;
+            itimer_.cancel();
+        }
+    private:
+        unsigned int& counter_;
+        IntervalTimer& itimer_;
+    };
+    class TimerCallBackOverwriter : public std::unary_function<void, void> {
+    public:
+        TimerCallBackOverwriter(IntervalTimerTest* test_obj,
+                                IntervalTimer& timer)
+            : test_obj_(test_obj), timer_(timer), count_(0)
+        {}
+        void operator()() {
+            ++count_;
+            if (count_ == 1) {
+                // First time of call back.
+                // Call setup() to update callback function to TimerCallBack.
+                test_obj_->timer_called_ = false;
+                timer_.setup(TimerCallBack(test_obj_), 100);
+            } else if (count_ == 2) {
+                // Second time of call back.
+                // If it reaches here, re-setup() is failed (unexpected).
+                // We should stop here.
+                test_obj_->io_service_.stop();
+            }
+            return;
+        }
+    private:
+        IntervalTimerTest* test_obj_;
+        IntervalTimer& timer_;
+        int count_;
+    };
+protected:
+    IOService io_service_;
+    bool timer_called_;
+    bool timer_cancel_success_;
+};
+
+TEST_F(IntervalTimerTest, invalidArgumentToIntervalTimer) {
+    // Create asio_link::IntervalTimer and setup.
+    IntervalTimer itimer(io_service_);
+    // expect throw if call back function is empty
+    EXPECT_THROW(itimer.setup(IntervalTimer::Callback(), 1),
+                 isc::InvalidParameter);
+    // expect throw if interval is not greater than 0
+    EXPECT_THROW(itimer.setup(TimerCallBack(this), 0), isc::BadValue);
+    EXPECT_THROW(itimer.setup(TimerCallBack(this), -1), isc::BadValue);
+}
+
+TEST_F(IntervalTimerTest, startIntervalTimer) {
+    // Create asio_link::IntervalTimer and setup.
+    // Then run IOService and test if the callback function is called.
+    IntervalTimer itimer(io_service_);
+    timer_called_ = false;
+    // store start time
+    boost::posix_time::ptime start;
+    start = boost::posix_time::microsec_clock::universal_time();
+    // setup timer
+    itimer.setup(TimerCallBack(this), 100);
+    EXPECT_EQ(100, itimer.getInterval());
+    io_service_.run();
+    // reaches here after timer expired
+    // delta: difference between elapsed time and 100 milliseconds.
+    boost::posix_time::time_duration delta =
+        (boost::posix_time::microsec_clock::universal_time() - start)
+         - boost::posix_time::millisec(100);
+    if (delta.is_negative()) {
+        delta.invert_sign();
+    }
+    // expect TimerCallBack is called; timer_called_ is true
+    EXPECT_TRUE(timer_called_);
+    // expect interval is 100 milliseconds +/- TIMER_MARGIN_MSEC.
+    EXPECT_TRUE(delta < TIMER_MARGIN_MSEC);
+}
+
+TEST_F(IntervalTimerTest, destructIntervalTimer) {
+    // This code isn't exception safe, but we'd rather keep the code
+    // simpler and more readable as this is only for tests and if it throws
+    // the program would immediately terminate anyway.
+
+    // The call back function will not be called after the timer is
+    // destroyed.
+    //
+    // There are two timers:
+    //  itimer_counter (A)
+    //   (Calls TimerCallBackCounter)
+    //     - increments internal counter in callback function
+    //  itimer_canceller (B)
+    //   (Calls TimerCallBackCancelDeleter)
+    //     - first time of callback, it stores the counter value of
+    //       callback_canceller and destroys itimer_counter
+    //     - second time of callback, it compares the counter value of
+    //       callback_canceller with stored value
+    //       if they are same the timer was not called; expected result
+    //       if they are different the timer was called after destroyed
+    //
+    //     0  100  200  300  400  500  600 (ms)
+    // (A) i--------+----x
+    //                   ^
+    //                   |destroy itimer_counter
+    // (B) i-------------+--------------s
+    //                                  ^stop io_service
+    //                                   and check if itimer_counter have been
+    //                                   stopped
+
+    // itimer_counter will be deleted in TimerCallBackCancelDeleter
+    IntervalTimer* itimer_counter = new IntervalTimer(io_service_);
+    IntervalTimer itimer_canceller(io_service_);
+    timer_cancel_success_ = false;
+    TimerCallBackCounter callback_canceller(this);
+    itimer_counter->setup(callback_canceller, 200);
+    itimer_canceller.setup(
+        TimerCallBackCancelDeleter(this, itimer_counter, callback_canceller),
+        300);
+    io_service_.run();
+    EXPECT_TRUE(timer_cancel_success_);
+}
+
+TEST_F(IntervalTimerTest, cancel) {
+    // Similar to destructIntervalTimer test, but the first timer explicitly
+    // cancels itself on first callback.
+    IntervalTimer itimer_counter(io_service_);
+    IntervalTimer itimer_watcher(io_service_);
+    unsigned int counter = 0;
+    itimer_counter.setup(TimerCallBackCanceller(counter, itimer_counter), 100);
+    itimer_watcher.setup(TimerCallBack(this), 200);
+    io_service_.run();
+    EXPECT_EQ(1, counter);
+    EXPECT_EQ(0, itimer_counter.getInterval());
+
+    // canceling an already canceled timer shouldn't cause any surprise.
+    EXPECT_NO_THROW(itimer_counter.cancel());
+}
+
+TEST_F(IntervalTimerTest, overwriteIntervalTimer) {
+    // Calling setup() multiple times updates call back function and interval.
+    //
+    // There are two timers:
+    //  itimer (A)
+    //   (Calls TimerCallBackCounter / TimerCallBack)
+    //     - increments internal counter in callback function
+    //       (TimerCallBackCounter)
+    //       interval: 300 milliseconds
+    //     - io_service_.stop() (TimerCallBack)
+    //       interval: 100 milliseconds
+    //  itimer_overwriter (B)
+    //   (Calls TimerCallBackOverwriter)
+    //     - first time of callback, it calls setup() to change call back
+    //       function to TimerCallBack and interval of itimer to 100
+    //       milliseconds
+    //       after 300 + 100 milliseconds from the beginning of this test,
+    //       TimerCallBack() will be called and io_service_ stops.
+    //     - second time of callback, it means the test fails.
+    //
+    //     0  100  200  300  400  500  600  700  800 (ms)
+    // (A) i-------------+----C----s
+    //                        ^    ^stop io_service
+    //                        |change call back function
+    // (B) i------------------+-------------------S
+    //                                            ^(stop io_service on fail)
+    //
+
+    IntervalTimer itimer(io_service_);
+    IntervalTimer itimer_overwriter(io_service_);
+    // store start time
+    boost::posix_time::ptime start;
+    start = boost::posix_time::microsec_clock::universal_time();
+    itimer.setup(TimerCallBackCounter(this), 300);
+    itimer_overwriter.setup(TimerCallBackOverwriter(this, itimer), 400);
+    io_service_.run();
+    // reaches here after timer expired
+    // if interval is updated, it takes
+    //   400 milliseconds for TimerCallBackOverwriter
+    //   + 100 milliseconds for TimerCallBack (stop)
+    //   = 500 milliseconds.
+    // otherwise (test fails), it takes
+    //   400 milliseconds for TimerCallBackOverwriter
+    //   + 400 milliseconds for TimerCallBackOverwriter (stop)
+    //   = 800 milliseconds.
+    // delta: difference between elapsed time and 400 + 100 milliseconds
+    boost::posix_time::time_duration delta =
+        (boost::posix_time::microsec_clock::universal_time() - start)
+         - boost::posix_time::millisec(400 + 100);
+    if (delta.is_negative()) {
+        delta.invert_sign();
+    }
+    // expect callback function is updated: TimerCallBack is called
+    EXPECT_TRUE(timer_called_);
+    // expect interval is updated
+    EXPECT_TRUE(delta < TIMER_MARGIN_MSEC);
+}

+ 115 - 0
src/lib/asiolink/tests/io_service_unittest.cc

@@ -0,0 +1,115 @@
+// 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 <asiolink/asiolink.h>
+
+using namespace asiolink;
+
+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, *"5300.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;
+}
+

+ 57 - 0
src/lib/asiolink/tests/ioaddress_unittest.cc

@@ -0,0 +1,57 @@
+// 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 <asiolink/asiolink.h>
+
+using namespace asiolink;
+
+TEST(IOAddressTest, fromText) {
+    IOAddress io_address_v4("192.0.2.1");
+    EXPECT_EQ("192.0.2.1", io_address_v4.toText());
+
+    IOAddress io_address_v6("2001:db8::1234");
+    EXPECT_EQ("2001:db8::1234", io_address_v6.toText());
+
+    // bogus IPv4 address-like input
+    EXPECT_THROW(IOAddress("192.0.2.2.1"), IOError);
+
+    // bogus IPv4 address-like input: out-of-range octet
+    EXPECT_THROW(IOAddress("192.0.2.300"), IOError);
+
+    // bogus IPv6 address-like input
+    EXPECT_THROW(IOAddress("2001:db8:::1234"), IOError);
+
+    // bogus IPv6 address-like input
+    EXPECT_THROW(IOAddress("2001:db8::efgh"), IOError);
+}
+
+TEST(IOAddressTest, Equality) {
+    EXPECT_TRUE(IOAddress("192.0.2.1") == IOAddress("192.0.2.1"));
+    EXPECT_FALSE(IOAddress("192.0.2.1") != IOAddress("192.0.2.1"));
+
+    EXPECT_TRUE(IOAddress("192.0.2.1") != IOAddress("192.0.2.2"));
+    EXPECT_FALSE(IOAddress("192.0.2.1") == IOAddress("192.0.2.2"));
+
+    EXPECT_TRUE(IOAddress("2001:db8::12") == IOAddress("2001:0DB8:0:0::0012"));
+    EXPECT_FALSE(IOAddress("2001:db8::12") != IOAddress("2001:0DB8:0:0::0012"));
+
+    EXPECT_TRUE(IOAddress("2001:db8::1234") != IOAddress("2001:db8::1235"));
+    EXPECT_FALSE(IOAddress("2001:db8::1234") == IOAddress("2001:db8::1235"));
+
+    EXPECT_TRUE(IOAddress("2001:db8::1234") != IOAddress("192.0.2.3"));
+    EXPECT_FALSE(IOAddress("2001:db8::1234") == IOAddress("192.0.2.3"));
+}

+ 67 - 0
src/lib/asiolink/tests/ioendpoint_unittest.cc

@@ -0,0 +1,67 @@
+// 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 <asiolink/asiolink.h>
+
+using namespace asiolink;
+
+TEST(IOEndpointTest, createUDPv4) {
+    const IOEndpoint* ep;
+    ep = IOEndpoint::create(IPPROTO_UDP, IOAddress("192.0.2.1"), 5300);
+    EXPECT_EQ("192.0.2.1", ep->getAddress().toText());
+    EXPECT_EQ(5300, ep->getPort());
+    EXPECT_EQ(AF_INET, ep->getFamily());
+    EXPECT_EQ(AF_INET, ep->getAddress().getFamily());
+    EXPECT_EQ(IPPROTO_UDP, ep->getProtocol());
+}
+
+TEST(IOEndpointTest, createTCPv4) {
+    const IOEndpoint* ep;
+    ep = IOEndpoint::create(IPPROTO_TCP, IOAddress("192.0.2.1"), 5301);
+    EXPECT_EQ("192.0.2.1", ep->getAddress().toText());
+    EXPECT_EQ(5301, ep->getPort());
+    EXPECT_EQ(AF_INET, ep->getFamily());
+    EXPECT_EQ(AF_INET, ep->getAddress().getFamily());
+    EXPECT_EQ(IPPROTO_TCP, ep->getProtocol());
+}
+
+TEST(IOEndpointTest, createUDPv6) {
+    const IOEndpoint* ep;
+    ep = IOEndpoint::create(IPPROTO_UDP, IOAddress("2001:db8::1234"), 5302);
+    EXPECT_EQ("2001:db8::1234", ep->getAddress().toText());
+    EXPECT_EQ(5302, ep->getPort());
+    EXPECT_EQ(AF_INET6, ep->getFamily());
+    EXPECT_EQ(AF_INET6, ep->getAddress().getFamily());
+    EXPECT_EQ(IPPROTO_UDP, ep->getProtocol());
+}
+
+TEST(IOEndpointTest, createTCPv6) {
+    const IOEndpoint* ep;
+    ep = IOEndpoint::create(IPPROTO_TCP, IOAddress("2001:db8::1234"), 5303);
+    EXPECT_EQ("2001:db8::1234", ep->getAddress().toText());
+    EXPECT_EQ(5303, ep->getPort());
+    EXPECT_EQ(AF_INET6, ep->getFamily());
+    EXPECT_EQ(AF_INET6, ep->getAddress().getFamily());
+    EXPECT_EQ(IPPROTO_TCP, ep->getProtocol());
+}
+
+TEST(IOEndpointTest, createIPProto) {
+    EXPECT_THROW(IOEndpoint::create(IPPROTO_IP, IOAddress("192.0.2.1"),
+                                    5300)->getAddress().toText(),
+                 IOError);
+}
+

+ 29 - 0
src/lib/asiolink/tests/iosocket_unittest.cc

@@ -0,0 +1,29 @@
+// 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 <asiolink/asiolink.h>
+
+using namespace asiolink;
+
+TEST(IOSocketTest, dummySockets) {
+    EXPECT_EQ(IPPROTO_UDP, IOSocket::getDummyUDPSocket().getProtocol());
+    EXPECT_EQ(IPPROTO_TCP, IOSocket::getDummyTCPSocket().getProtocol());
+    EXPECT_EQ(-1, IOSocket::getDummyUDPSocket().getNative());
+    EXPECT_EQ(-1, IOSocket::getDummyTCPSocket().getNative());
+}
+
+

+ 81 - 490
src/lib/asiolink/tests/asiolink_unittest.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2010  Internet Systems Consortium, Inc. ("ISC")
+// 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
@@ -42,7 +42,7 @@
 // If we need to test something at the level of underlying ASIO and need
 // their definition, that test should go to asiolink/internal/tests.
 #include <asiolink/asiolink.h>
-#include <asiolink/iosocket.h>
+#include <asiolink/io_socket.h>
 
 using isc::UnitTestUtil;
 using namespace std;
@@ -58,171 +58,6 @@ const char* const TEST_IPV4_ADDR = "127.0.0.1";
 // two octets encode the length of the rest of the data.  This is crucial
 // for the tests below.
 const uint8_t test_data[] = {0, 4, 1, 2, 3, 4};
-// TODO: Consider this margin
-const boost::posix_time::time_duration TIMER_MARGIN_MSEC =
-    boost::posix_time::milliseconds(50);
-
-TEST(IOAddressTest, fromText) {
-    IOAddress io_address_v4("192.0.2.1");
-    EXPECT_EQ("192.0.2.1", io_address_v4.toText());
-
-    IOAddress io_address_v6("2001:db8::1234");
-    EXPECT_EQ("2001:db8::1234", io_address_v6.toText());
-
-    // bogus IPv4 address-like input
-    EXPECT_THROW(IOAddress("192.0.2.2.1"), IOError);
-
-    // bogus IPv4 address-like input: out-of-range octet
-    EXPECT_THROW(IOAddress("192.0.2.300"), IOError);
-
-    // bogus IPv6 address-like input
-    EXPECT_THROW(IOAddress("2001:db8:::1234"), IOError);
-
-    // bogus IPv6 address-like input
-    EXPECT_THROW(IOAddress("2001:db8::efgh"), IOError);
-}
-
-TEST(IOEndpointTest, createUDPv4) {
-    const IOEndpoint* ep;
-    ep = IOEndpoint::create(IPPROTO_UDP, IOAddress("192.0.2.1"), 5300);
-    EXPECT_EQ("192.0.2.1", ep->getAddress().toText());
-    EXPECT_EQ(5300, ep->getPort());
-    EXPECT_EQ(AF_INET, ep->getFamily());
-    EXPECT_EQ(AF_INET, ep->getAddress().getFamily());
-    EXPECT_EQ(IPPROTO_UDP, ep->getProtocol());
-}
-
-TEST(IOEndpointTest, createTCPv4) {
-    const IOEndpoint* ep;
-    ep = IOEndpoint::create(IPPROTO_TCP, IOAddress("192.0.2.1"), 5301);
-    EXPECT_EQ("192.0.2.1", ep->getAddress().toText());
-    EXPECT_EQ(5301, ep->getPort());
-    EXPECT_EQ(AF_INET, ep->getFamily());
-    EXPECT_EQ(AF_INET, ep->getAddress().getFamily());
-    EXPECT_EQ(IPPROTO_TCP, ep->getProtocol());
-}
-
-TEST(IOEndpointTest, createUDPv6) {
-    const IOEndpoint* ep;
-    ep = IOEndpoint::create(IPPROTO_UDP, IOAddress("2001:db8::1234"), 5302);
-    EXPECT_EQ("2001:db8::1234", ep->getAddress().toText());
-    EXPECT_EQ(5302, ep->getPort());
-    EXPECT_EQ(AF_INET6, ep->getFamily());
-    EXPECT_EQ(AF_INET6, ep->getAddress().getFamily());
-    EXPECT_EQ(IPPROTO_UDP, ep->getProtocol());
-}
-
-TEST(IOEndpointTest, createTCPv6) {
-    const IOEndpoint* ep;
-    ep = IOEndpoint::create(IPPROTO_TCP, IOAddress("2001:db8::1234"), 5303);
-    EXPECT_EQ("2001:db8::1234", ep->getAddress().toText());
-    EXPECT_EQ(5303, ep->getPort());
-    EXPECT_EQ(AF_INET6, ep->getFamily());
-    EXPECT_EQ(AF_INET6, ep->getAddress().getFamily());
-    EXPECT_EQ(IPPROTO_TCP, ep->getProtocol());
-}
-
-TEST(IOEndpointTest, createIPProto) {
-    EXPECT_THROW(IOEndpoint::create(IPPROTO_IP, IOAddress("192.0.2.1"),
-                                    5300)->getAddress().toText(),
-                 IOError);
-}
-
-TEST(IOSocketTest, dummySockets) {
-    EXPECT_EQ(IPPROTO_UDP, IOSocket::getDummyUDPSocket().getProtocol());
-    EXPECT_EQ(IPPROTO_TCP, IOSocket::getDummyTCPSocket().getProtocol());
-    EXPECT_EQ(-1, IOSocket::getDummyUDPSocket().getNative());
-    EXPECT_EQ(-1, IOSocket::getDummyTCPSocket().getNative());
-}
-
-TEST(IOServiceTest, badPort) {
-    IOService io_service;
-    EXPECT_THROW(DNSService(io_service, *"65536", true, false, NULL, NULL, NULL), IOError);
-    EXPECT_THROW(DNSService(io_service, *"5300.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;
-}
 
 // This function returns an addrinfo structure for use by tests, using
 // different addresses and ports depending on whether we're testing
@@ -259,12 +94,12 @@ resolveAddress(const int family, const int protocol, const bool client) {
 // expected parameters.
 // If initialization parameters of the IOService should be modified, the test
 // case can do it using the setDNSService() method.
-// Note: the set of tests in ASIOLinkTest use actual network services and may
+// Note: the set of tests in RecursiveQueryTest use actual network services and may
 // involve undesirable side effects such as blocking.
-class ASIOLinkTest : public ::testing::Test {
+class RecursiveQueryTest : public ::testing::Test {
 protected:
-    ASIOLinkTest();
-    ~ASIOLinkTest() {
+    RecursiveQueryTest();
+    ~RecursiveQueryTest() {
         if (res_ != NULL) {
             freeaddrinfo(res_);
         }
@@ -503,15 +338,48 @@ protected:
             bool* done_;
     };
 
+    // This version of mock server just stops the io_service when it is resumed
+    // the second time. (Used in the clientTimeout test, where resume
+    // is called initially with the error answer, and later when the
+    // lookup times out, it is called without an answer to send back)
+    class MockServerStop2 : public MockServer {
+        public:
+            explicit MockServerStop2(IOService& io_service,
+                                     bool* done1, bool* done2) :
+                MockServer(io_service),
+                done1_(done1),
+                done2_(done2),
+                stopped_once_(false)
+            {}
+
+            void resume(const bool done) {
+                if (stopped_once_) {
+                    *done2_ = done;
+                    io_.stop();
+                } else {
+                    *done1_ = done;
+                    stopped_once_ = true;
+                }
+            }
+
+            DNSServer* clone() {
+                return (new MockServerStop2(*this));
+            }
+        private:
+            bool* done1_;
+            bool* done2_;
+            bool stopped_once_;
+    };
+
 private:
     class ASIOCallBack : public SimpleCallback {
     public:
-        ASIOCallBack(ASIOLinkTest* test_obj) : test_obj_(test_obj) {}
+        ASIOCallBack(RecursiveQueryTest* test_obj) : test_obj_(test_obj) {}
         void operator()(const IOMessage& io_message) const {
             test_obj_->callBack(io_message);
         }
     private:
-        ASIOLinkTest* test_obj_;
+        RecursiveQueryTest* test_obj_;
     };
     void callBack(const IOMessage& io_message) {
         callback_protocol_ = io_message.getSocket().getProtocol();
@@ -538,30 +406,30 @@ protected:
     struct addrinfo* res_;
 };
 
-ASIOLinkTest::ASIOLinkTest() :
+RecursiveQueryTest::RecursiveQueryTest() :
     dns_service_(NULL), callback_(NULL), sock_(-1), res_(NULL)
 {
     io_service_ = new IOService();
     setDNSService(true, true);
 }
 
-TEST_F(ASIOLinkTest, v6UDPSend) {
+TEST_F(RecursiveQueryTest, v6UDPSend) {
     doTest(AF_INET6, IPPROTO_UDP);
 }
 
-TEST_F(ASIOLinkTest, v6TCPSend) {
+TEST_F(RecursiveQueryTest, v6TCPSend) {
     doTest(AF_INET6, IPPROTO_TCP);
 }
 
-TEST_F(ASIOLinkTest, v4UDPSend) {
+TEST_F(RecursiveQueryTest, v4UDPSend) {
     doTest(AF_INET, IPPROTO_UDP);
 }
 
-TEST_F(ASIOLinkTest, v4TCPSend) {
+TEST_F(RecursiveQueryTest, v4TCPSend) {
     doTest(AF_INET, IPPROTO_TCP);
 }
 
-TEST_F(ASIOLinkTest, v6UDPSendSpecific) {
+TEST_F(RecursiveQueryTest, v6UDPSendSpecific) {
     // Explicitly set a specific address to be bound to the socket.
     // The subsequent test does not directly ensures the underlying socket
     // is bound to the expected address, but the success of the tests should
@@ -577,26 +445,26 @@ TEST_F(ASIOLinkTest, v6UDPSendSpecific) {
     doTest(AF_INET6, IPPROTO_UDP);
 }
 
-TEST_F(ASIOLinkTest, v6TCPSendSpecific) {
+TEST_F(RecursiveQueryTest, v6TCPSendSpecific) {
     setDNSService(*TEST_IPV6_ADDR);
     doTest(AF_INET6, IPPROTO_TCP);
 
     EXPECT_THROW(sendTCP(AF_INET), IOError);
 }
 
-TEST_F(ASIOLinkTest, v4UDPSendSpecific) {
+TEST_F(RecursiveQueryTest, v4UDPSendSpecific) {
     setDNSService(*TEST_IPV4_ADDR);
     doTest(AF_INET, IPPROTO_UDP);
 }
 
-TEST_F(ASIOLinkTest, v4TCPSendSpecific) {
+TEST_F(RecursiveQueryTest, v4TCPSendSpecific) {
     setDNSService(*TEST_IPV4_ADDR);
     doTest(AF_INET, IPPROTO_TCP);
 
     EXPECT_THROW(sendTCP(AF_INET6), IOError);
 }
 
-TEST_F(ASIOLinkTest, v6AddServer) {
+TEST_F(RecursiveQueryTest, v6AddServer) {
     setDNSService();
     dns_service_->addServer(*TEST_SERVER_PORT, TEST_IPV6_ADDR);
     doTest(AF_INET6, IPPROTO_TCP);
@@ -604,7 +472,7 @@ TEST_F(ASIOLinkTest, v6AddServer) {
     EXPECT_THROW(sendTCP(AF_INET), IOError);
 }
 
-TEST_F(ASIOLinkTest, v4AddServer) {
+TEST_F(RecursiveQueryTest, v4AddServer) {
     setDNSService();
     dns_service_->addServer(*TEST_SERVER_PORT, TEST_IPV4_ADDR);
     doTest(AF_INET, IPPROTO_TCP);
@@ -612,7 +480,7 @@ TEST_F(ASIOLinkTest, v4AddServer) {
     EXPECT_THROW(sendTCP(AF_INET6), IOError);
 }
 
-TEST_F(ASIOLinkTest, DISABLED_clearServers) {
+TEST_F(RecursiveQueryTest, DISABLED_clearServers) {
     // FIXME: Enable when clearServers actually close the sockets
     //    See #388
     setDNSService();
@@ -622,7 +490,7 @@ TEST_F(ASIOLinkTest, DISABLED_clearServers) {
     EXPECT_THROW(sendTCP(AF_INET6), IOError);
 }
 
-TEST_F(ASIOLinkTest, v6TCPOnly) {
+TEST_F(RecursiveQueryTest, v6TCPOnly) {
     // Open only IPv6 TCP socket.  A subsequent attempt of establishing an
     // IPv4/TCP connection should fail.  See above for why we only test this
     // for TCP.
@@ -630,7 +498,7 @@ TEST_F(ASIOLinkTest, v6TCPOnly) {
     EXPECT_THROW(sendTCP(AF_INET), IOError);
 }
 
-TEST_F(ASIOLinkTest, v4TCPOnly) {
+TEST_F(RecursiveQueryTest, v4TCPOnly) {
     setDNSService(true, false);
     EXPECT_THROW(sendTCP(AF_INET6), IOError);
 }
@@ -642,7 +510,7 @@ singleAddress(const string &address, uint16_t port) {
     return (result);
 }
 
-TEST_F(ASIOLinkTest, recursiveSetupV4) {
+TEST_F(RecursiveQueryTest, recursiveSetupV4) {
     setDNSService(true, false);
     uint16_t port = boost::lexical_cast<uint16_t>(TEST_CLIENT_PORT);
     EXPECT_NO_THROW(RecursiveQuery(*dns_service_,
@@ -650,7 +518,7 @@ TEST_F(ASIOLinkTest, recursiveSetupV4) {
                                    singleAddress(TEST_IPV4_ADDR, port)));
 }
 
-TEST_F(ASIOLinkTest, recursiveSetupV6) {
+TEST_F(RecursiveQueryTest, recursiveSetupV6) {
     setDNSService(false, true);
     uint16_t port = boost::lexical_cast<uint16_t>(TEST_CLIENT_PORT);
     EXPECT_NO_THROW(RecursiveQuery(*dns_service_,
@@ -663,7 +531,7 @@ TEST_F(ASIOLinkTest, recursiveSetupV6) {
 // a routine that can do this with variable address family, address, and
 // port, and with the various callbacks defined in such a way as to ensure
 // full code coverage including error cases.
-TEST_F(ASIOLinkTest, forwarderSend) {
+TEST_F(RecursiveQueryTest, forwarderSend) {
     setDNSService(true, false);
 
     // Note: We use the test prot plus one to ensure we aren't binding
@@ -678,7 +546,7 @@ TEST_F(ASIOLinkTest, forwarderSend) {
     Question q(Name("example.com"), RRClass::IN(), RRType::TXT());
     OutputBufferPtr buffer(new OutputBuffer(0));
     MessagePtr answer(new Message(Message::RENDER));
-    rq.sendQuery(q, answer, buffer, &server);
+    rq.resolve(q, answer, buffer, &server);
 
     char data[4096];
     size_t size = sizeof(data);
@@ -717,7 +585,7 @@ setSocketTimeout(int sock_, size_t tv_sec, size_t tv_usec) {
     const struct timeval timeo = { tv_sec, tv_usec };
     int recv_options = 0;
     if (setsockopt(sock_, SOL_SOCKET, SO_RCVTIMEO, &timeo, sizeof(timeo))) {
-        if (errno == ENOPROTOOPT) { // see ASIOLinkTest::recvUDP()
+        if (errno == ENOPROTOOPT) { // see RecursiveQueryTest::recvUDP()
             recv_options = MSG_DONTWAIT;
         } else {
             isc_throw(IOError, "set RCVTIMEO failed: " << strerror(errno));
@@ -745,7 +613,7 @@ bool tryRead(int sock_, int recv_options, size_t max, int* num) {
 
 
 // Test it tries the correct amount of times before giving up
-TEST_F(ASIOLinkTest, forwardQueryTimeout) {
+TEST_F(RecursiveQueryTest, forwardQueryTimeout) {
     // Prepare the service (we do not use the common setup, we do not answer
     setDNSService();
 
@@ -765,7 +633,7 @@ TEST_F(ASIOLinkTest, forwardQueryTimeout) {
     Question question(Name("example.net"), RRClass::IN(), RRType::A());
     OutputBufferPtr buffer(new OutputBuffer(0));
     MessagePtr answer(new Message(Message::RENDER));
-    query.sendQuery(question, answer, buffer, &server);
+    query.resolve(question, answer, buffer, &server);
 
     // Run the test
     io_service_->run();
@@ -785,15 +653,16 @@ TEST_F(ASIOLinkTest, forwardQueryTimeout) {
 // If we set client timeout to lower than querytimeout, we should
 // get a failure answer, but still see retries
 // (no actual answer is given here yet)
-TEST_F(ASIOLinkTest, forwardClientTimeout) {
+TEST_F(RecursiveQueryTest, forwardClientTimeout) {
     // Prepare the service (we do not use the common setup, we do not answer
     setDNSService();
 
     sock_ = createTestSocket();
 
     // Prepare the server
-    bool done(true);
-    MockServerStop server(*io_service_, &done);
+    bool done1(true);
+    bool done2(true);
+    MockServerStop2 server(*io_service_, &done1, &done2);
 
     MessagePtr answer(new Message(Message::RENDER));
 
@@ -801,14 +670,14 @@ TEST_F(ASIOLinkTest, forwardClientTimeout) {
     const uint16_t port = boost::lexical_cast<uint16_t>(TEST_CLIENT_PORT);
     // Set it up to retry twice before client timeout fires
     // Since the lookup timer has not fired, it should retry
-    // a third time
+    // four times
     RecursiveQuery query(*dns_service_,
                          singleAddress(TEST_IPV4_ADDR, port),
                          singleAddress(TEST_IPV4_ADDR, port),
-                         50, 120, 1000, 3);
+                         50, 120, 1000, 4);
     Question question(Name("example.net"), RRClass::IN(), RRType::A());
     OutputBufferPtr buffer(new OutputBuffer(0));
-    query.sendQuery(question, answer, buffer, &server);
+    query.resolve(question, answer, buffer, &server);
 
     // Run the test
     io_service_->run();
@@ -816,22 +685,20 @@ TEST_F(ASIOLinkTest, forwardClientTimeout) {
     // we know it'll fail, so make it a shorter timeout
     int recv_options = setSocketTimeout(sock_, 1, 0);
 
-    // Try to read 5 times, should stop after 3 reads
+    // Try to read 5 times
     int num = 0;
     bool read_success = tryRead(sock_, recv_options, 5, &num);
 
-    // The query should fail (for resolver it should send back servfail,
-    // but currently, and perhaps for forwarder in general, the effect
-    // will be the same as on a lookup timeout, i.e. no answer is sent
-    // back)
-    EXPECT_FALSE(done);
-    EXPECT_EQ(3, num);
-    EXPECT_FALSE(read_success);
+    // The query should fail, but we should have kept on trying
+    EXPECT_TRUE(done1);
+    EXPECT_FALSE(done2);
+    EXPECT_EQ(5, num);
+    EXPECT_TRUE(read_success);
 }
 
 // If we set lookup timeout to lower than querytimeout*retries, we should
 // fail before the full amount of retries
-TEST_F(ASIOLinkTest, forwardLookupTimeout) {
+TEST_F(RecursiveQueryTest, forwardLookupTimeout) {
     // Prepare the service (we do not use the common setup, we do not answer
     setDNSService();
 
@@ -854,7 +721,7 @@ TEST_F(ASIOLinkTest, forwardLookupTimeout) {
                          50, 4000, 120, 5);
     Question question(Name("example.net"), RRClass::IN(), RRType::A());
     OutputBufferPtr buffer(new OutputBuffer(0));
-    query.sendQuery(question, answer, buffer, &server);
+    query.resolve(question, answer, buffer, &server);
 
     // Run the test
     io_service_->run();
@@ -877,7 +744,7 @@ TEST_F(ASIOLinkTest, forwardLookupTimeout) {
 // for the skeleton code, it shouldn't be too much of a problem
 // Ok so even we don't all have access to the DNS world right now,
 // so disabling these tests too.
-TEST_F(ASIOLinkTest, DISABLED_recursiveSendOk) {
+TEST_F(RecursiveQueryTest, DISABLED_recursiveSendOk) {
     setDNSService(true, false);
     bool done;
     
@@ -888,7 +755,7 @@ TEST_F(ASIOLinkTest, DISABLED_recursiveSendOk) {
     Question q(Name("www.isc.org"), RRClass::IN(), RRType::A());
     OutputBufferPtr buffer(new OutputBuffer(0));
     MessagePtr answer(new Message(Message::RENDER));
-    rq.sendQuery(q, answer, buffer, &server);
+    rq.resolve(q, answer, buffer, &server);
     io_service_->run();
 
     // Check that the answer we got matches the one we wanted
@@ -902,7 +769,7 @@ TEST_F(ASIOLinkTest, DISABLED_recursiveSendOk) {
 }
 
 // see comments at previous test
-TEST_F(ASIOLinkTest, DISABLED_recursiveSendNXDOMAIN) {
+TEST_F(RecursiveQueryTest, DISABLED_recursiveSendNXDOMAIN) {
     setDNSService(true, false);
     bool done;
     
@@ -913,7 +780,7 @@ TEST_F(ASIOLinkTest, DISABLED_recursiveSendNXDOMAIN) {
     Question q(Name("wwwdoesnotexist.isc.org"), RRClass::IN(), RRType::A());
     OutputBufferPtr buffer(new OutputBuffer(0));
     MessagePtr answer(new Message(Message::RENDER));
-    rq.sendQuery(q, answer, buffer, &server);
+    rq.resolve(q, answer, buffer, &server);
     io_service_->run();
 
     // Check that the answer we got matches the one we wanted
@@ -921,280 +788,4 @@ TEST_F(ASIOLinkTest, DISABLED_recursiveSendNXDOMAIN) {
     EXPECT_EQ(0, answer->getRRCount(Message::SECTION_ANSWER));
 }
 
-
-
-// This fixture is for testing IntervalTimer. Some callback functors are 
-// registered as callback function of the timer to test if they are called
-// or not.
-class IntervalTimerTest : public ::testing::Test {
-protected:
-    IntervalTimerTest() : io_service_() {}
-    ~IntervalTimerTest() {}
-    class TimerCallBack : public std::unary_function<void, void> {
-    public:
-        TimerCallBack(IntervalTimerTest* test_obj) : test_obj_(test_obj) {}
-        void operator()() const {
-            test_obj_->timer_called_ = true;
-            test_obj_->io_service_.stop();
-            return;
-        }
-    private:
-        IntervalTimerTest* test_obj_;
-    };
-    class TimerCallBackCounter : public std::unary_function<void, void> {
-    public:
-        TimerCallBackCounter(IntervalTimerTest* test_obj) : test_obj_(test_obj) {
-            counter_ = 0;
-        }
-        void operator()() {
-            ++counter_;
-            return;
-        }
-        int counter_;
-    private:
-        IntervalTimerTest* test_obj_;
-    };
-    class TimerCallBackCancelDeleter : public std::unary_function<void, void> {
-    public:
-        TimerCallBackCancelDeleter(IntervalTimerTest* test_obj,
-                                   IntervalTimer* timer,
-                                   TimerCallBackCounter& counter)
-            : test_obj_(test_obj), timer_(timer), counter_(counter), count_(0)
-        {}
-        void operator()() {
-            ++count_;
-            if (count_ == 1) {
-                // First time of call back.
-                // Store the value of counter_.counter_.
-                prev_counter_ = counter_.counter_;
-                delete timer_;
-            } else if (count_ == 2) {
-                // Second time of call back.
-                // Stop io_service to stop all timers.
-                test_obj_->io_service_.stop();
-                // Compare the value of counter_.counter_ with stored one.
-                // If TimerCallBackCounter was not called (expected behavior),
-                // they are same.
-                if (counter_.counter_ == prev_counter_) {
-                    test_obj_->timer_cancel_success_ = true;
-                }
-            }
-            return;
-        }
-    private:
-        IntervalTimerTest* test_obj_;
-        IntervalTimer* timer_;
-        TimerCallBackCounter& counter_;
-        int count_;
-        int prev_counter_;
-    };
-    class TimerCallBackCanceller {
-    public:
-        TimerCallBackCanceller(unsigned int& counter, IntervalTimer& itimer) :
-            counter_(counter), itimer_(itimer)
-        {}
-        void operator()() {
-            ++counter_;
-            itimer_.cancel();
-        }
-    private:
-        unsigned int& counter_;
-        IntervalTimer& itimer_;
-    };
-    class TimerCallBackOverwriter : public std::unary_function<void, void> {
-    public:
-        TimerCallBackOverwriter(IntervalTimerTest* test_obj,
-                                IntervalTimer& timer)
-            : test_obj_(test_obj), timer_(timer), count_(0)
-        {}
-        void operator()() {
-            ++count_;
-            if (count_ == 1) {
-                // First time of call back.
-                // Call setupTimer() to update callback function
-                // to TimerCallBack.
-                test_obj_->timer_called_ = false;
-                timer_.setupTimer(TimerCallBack(test_obj_), 1);
-            } else if (count_ == 2) {
-                // Second time of call back.
-                // If it reaches here, re-setupTimer() is failed (unexpected).
-                // We should stop here.
-                test_obj_->io_service_.stop();
-            }
-            return;
-        }
-    private:
-        IntervalTimerTest* test_obj_;
-        IntervalTimer& timer_;
-        int count_;
-    };
-protected:
-    IOService io_service_;
-    bool timer_called_;
-    bool timer_cancel_success_;
-};
-
-TEST_F(IntervalTimerTest, invalidArgumentToIntervalTimer) {
-    // Create asio_link::IntervalTimer and setup.
-    IntervalTimer itimer(io_service_);
-    // expect throw if call back function is empty
-    EXPECT_THROW(itimer.setupTimer(IntervalTimer::Callback(), 1),
-                     isc::InvalidParameter);
-    // expect throw if interval is 0
-    EXPECT_THROW(itimer.setupTimer(TimerCallBack(this), 0), isc::BadValue);
-}
-
-TEST_F(IntervalTimerTest, startIntervalTimer) {
-    // Create asio_link::IntervalTimer and setup.
-    // Then run IOService and test if the callback function is called.
-    IntervalTimer itimer(io_service_);
-    timer_called_ = false;
-    // store start time
-    boost::posix_time::ptime start;
-    start = boost::posix_time::microsec_clock::universal_time();
-    // setup timer
-    itimer.setupTimer(TimerCallBack(this), 1);
-    EXPECT_EQ(1, itimer.getInterval());
-    io_service_.run();
-    // reaches here after timer expired
-    // delta: difference between elapsed time and 1 second
-    boost::posix_time::time_duration delta =
-        (boost::posix_time::microsec_clock::universal_time() - start)
-         - boost::posix_time::seconds(1);
-    if (delta.is_negative()) {
-        delta.invert_sign();
-    }
-    // expect TimerCallBack is called; timer_called_ is true
-    EXPECT_TRUE(timer_called_);
-    // expect interval is 1 second +/- TIMER_MARGIN_MSEC.
-    EXPECT_TRUE(delta < TIMER_MARGIN_MSEC);
-}
-
-TEST_F(IntervalTimerTest, destructIntervalTimer) {
-    // Note: This test currently takes 6 seconds. The timer should have
-    // finer granularity and timer periods in this test should be shorter
-    // in the future.
-    // This code isn't exception safe, but we'd rather keep the code
-    // simpler and more readable as this is only for tests and if it throws
-    // the program would immediately terminate anyway.
-
-    // The call back function will not be called after the timer is
-    // destructed.
-    //
-    // There are two timers:
-    //  itimer_counter (A)
-    //   (Calls TimerCallBackCounter)
-    //     - increments internal counter in callback function
-    //  itimer_canceller (B)
-    //   (Calls TimerCallBackCancelDeleter)
-    //     - first time of callback, it stores the counter value of
-    //       callback_canceller and destructs itimer_counter
-    //     - second time of callback, it compares the counter value of
-    //       callback_canceller with stored value
-    //       if they are same the timer was not called; expected result
-    //       if they are different the timer was called after destructed
-    //
-    //     0  1  2  3  4  5  6 (s)
-    // (A) i-----+--x
-    //              ^
-    //              |destruct itimer_counter
-    // (B) i--------+--------s
-    //                       ^stop io_service
-    //                        and test itimer_counter have been stopped
-    //
-
-    // itimer_counter will be deleted in TimerCallBackCancelDeleter
-    IntervalTimer* itimer_counter = new IntervalTimer(io_service_);
-    IntervalTimer itimer_canceller(io_service_);
-    timer_cancel_success_ = false;
-    TimerCallBackCounter callback_canceller(this);
-    itimer_counter->setupTimer(callback_canceller, 2);
-    itimer_canceller.setupTimer(
-        TimerCallBackCancelDeleter(this, itimer_counter,
-                                   callback_canceller),
-        3);
-    io_service_.run();
-    EXPECT_TRUE(timer_cancel_success_);
-}
-
-TEST_F(IntervalTimerTest, cancel) {
-    // Similar to destructIntervalTimer test, but the first timer explicitly
-    // cancels itself on first callback.
-    IntervalTimer itimer_counter(io_service_);
-    IntervalTimer itimer_watcher(io_service_);
-    unsigned int counter = 0;
-    itimer_counter.setupTimer(TimerCallBackCanceller(counter, itimer_counter),
-                              1);
-    itimer_watcher.setupTimer(TimerCallBack(this), 3);
-    io_service_.run();
-    EXPECT_EQ(1, counter);
-    EXPECT_EQ(0, itimer_counter.getInterval());
-
-    // canceling an already canceled timer shouldn't cause any surprise.
-    EXPECT_NO_THROW(itimer_counter.cancel());
-}
-
-TEST_F(IntervalTimerTest, overwriteIntervalTimer) {
-    // Note: This test currently takes 4 seconds. The timer should have
-    // finer granularity and timer periods in this test should be shorter
-    // in the future.
-
-    // Calling setupTimer() multiple times updates call back function
-    // and interval.
-    //
-    // There are two timers:
-    //  itimer (A)
-    //   (Calls TimerCallBackCounter / TimerCallBack)
-    //     - increments internal counter in callback function
-    //       (TimerCallBackCounter)
-    //       interval: 2 seconds
-    //     - io_service_.stop() (TimerCallBack)
-    //       interval: 1 second
-    //  itimer_overwriter (B)
-    //   (Calls TimerCallBackOverwriter)
-    //     - first time of callback, it calls setupTimer() to change
-    //       call back function and interval of itimer to
-    //       TimerCallBack / 1 second
-    //       after 3 + 1 seconds from the beginning of this test,
-    //       TimerCallBack() will be called and io_service_ stops.
-    //     - second time of callback, it means the test fails.
-    //
-    //     0  1  2  3  4  5  6 (s)
-    // (A) i-----+--C--s
-    //              ^  ^stop io_service
-    //              |change call back function
-    // (B) i--------+--------S
-    //                       ^(stop io_service on fail)
-    //
-
-    IntervalTimer itimer(io_service_);
-    IntervalTimer itimer_overwriter(io_service_);
-    // store start time
-    boost::posix_time::ptime start;
-    start = boost::posix_time::microsec_clock::universal_time();
-    itimer.setupTimer(TimerCallBackCounter(this), 2);
-    itimer_overwriter.setupTimer(TimerCallBackOverwriter(this, itimer), 3);
-    io_service_.run();
-    // reaches here after timer expired
-    // if interval is updated, it takes
-    //   3 seconds for TimerCallBackOverwriter
-    //   + 1 second for TimerCallBack (stop)
-    //   = 4 seconds.
-    // otherwise (test fails), it takes
-    //   3 seconds for TimerCallBackOverwriter
-    //   + 3 seconds for TimerCallBackOverwriter (stop)
-    //   = 6 seconds.
-    // delta: difference between elapsed time and 3 + 1 seconds
-    boost::posix_time::time_duration delta =
-        (boost::posix_time::microsec_clock::universal_time() - start)
-         - boost::posix_time::seconds(3 + 1);
-    if (delta.is_negative()) {
-        delta.invert_sign();
-    }
-    // expect callback function is updated: TimerCallBack is called
-    EXPECT_TRUE(timer_called_);
-    // expect interval is updated
-    EXPECT_TRUE(delta < TIMER_MARGIN_MSEC);
-}
-
 }

+ 21 - 22
src/lib/asiolink/internal/tests/udpdns_unittest.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2010  CZ.NIC
+// 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
@@ -19,8 +19,7 @@
 
 #include <dns/question.h>
 
-#include <asiolink/internal/udpdns.h>
-#include <asiolink/internal/iofetch.h>
+#include <asiolink/udp_query.h>
 
 using namespace asio;
 using namespace isc::dns;
@@ -33,25 +32,25 @@ const uint16_t TEST_PORT(5301);
 // FIXME Shouldn't we send something that is real message?
 const char TEST_DATA[] = "TEST DATA";
 
-// Test fixture for the asiolink::IOFetch.
-class IOFetchTest : public ::testing::Test,
-    public asiolink::IOFetch::Callback
+// Test fixture for the asiolink::UDPQuery.
+class UDPQueryTest : public ::testing::Test,
+    public asiolink::UDPQuery::Callback
 {
     public:
         // Expected result of the callback
-        asiolink::IOFetch::Result expected_;
+        asiolink::UDPQuery::Result expected_;
         // Did the callback run already?
         bool run_;
         // We use an io_service to run the query
         io_service service_;
         // Something to ask
         Question question_;
-        // Buffer where the IOFetch will store response
+        // Buffer where the UDPQuery will store response
         OutputBufferPtr buffer_;
         // The query we are testing
-        asiolink::IOFetch query_;
+        asiolink::UDPQuery query_;
 
-        IOFetchTest() :
+        UDPQueryTest() :
             run_(false),
             question_(Name("example.net"), RRClass::IN(), RRType::A()),
             buffer_(new OutputBuffer(512)),
@@ -60,7 +59,7 @@ class IOFetchTest : public ::testing::Test,
         { }
 
         // This is the callback's (), so it can be called.
-        void operator()(asiolink::IOFetch::Result result) {
+        void operator()(asiolink::UDPQuery::Result result) {
             // We check the query returns the correct result
             EXPECT_EQ(expected_, result);
             // Check it is called only once
@@ -84,14 +83,14 @@ class IOFetchTest : public ::testing::Test,
  * That is why stop() is posted to the service_ as well instead
  * of calling it.
  */
-TEST_F(IOFetchTest, stop) {
-    expected_ = asiolink::IOFetch::STOPPED;
+TEST_F(UDPQueryTest, stop) {
+    expected_ = asiolink::UDPQuery::STOPPED;
     // Post the query
     service_.post(query_);
     // Post query_.stop() (yes, the boost::bind thing is just
     // query_.stop()).
-    service_.post(boost::bind(&asiolink::IOFetch::stop, query_,
-        asiolink::IOFetch::STOPPED));
+    service_.post(boost::bind(&asiolink::UDPQuery::stop, query_,
+        asiolink::UDPQuery::STOPPED));
     // Run both of them
     service_.run();
     EXPECT_TRUE(run_);
@@ -102,8 +101,8 @@ TEST_F(IOFetchTest, stop) {
  * before it gets executed, it acts sanely as well (eg. has the
  * same result as running stop() after - calls the callback).
  */
-TEST_F(IOFetchTest, prematureStop) {
-    expected_ = asiolink::IOFetch::STOPPED;
+TEST_F(UDPQueryTest, prematureStop) {
+    expected_ = asiolink::UDPQuery::STOPPED;
     // Stop before it is started
     query_.stop();
     service_.post(query_);
@@ -114,8 +113,8 @@ TEST_F(IOFetchTest, prematureStop) {
 /*
  * Test that it will timeout when no answer will arrive.
  */
-TEST_F(IOFetchTest, timeout) {
-    expected_ = asiolink::IOFetch::TIME_OUT;
+TEST_F(UDPQueryTest, timeout) {
+    expected_ = asiolink::UDPQuery::TIME_OUT;
     service_.post(query_);
     service_.run();
     EXPECT_TRUE(run_);
@@ -127,15 +126,15 @@ TEST_F(IOFetchTest, timeout) {
  *
  * This is done through a real socket on loopback address.
  */
-TEST_F(IOFetchTest, receive) {
-    expected_ = asiolink::IOFetch::SUCCESS;
+TEST_F(UDPQueryTest, receive) {
+    expected_ = asiolink::UDPQuery::SUCCESS;
     udp::socket socket(service_, udp::v4());
     socket.set_option(socket_base::reuse_address(true));
     socket.bind(udp::endpoint(TEST_HOST, TEST_PORT));
     char inbuff[512];
     udp::endpoint remote;
     socket.async_receive_from(asio::buffer(inbuff, 512), remote, boost::bind(
-        &IOFetchTest::respond, this, &remote, &socket));
+        &UDPQueryTest::respond, this, &remote, &socket));
     service_.post(query_);
     service_.run();
     EXPECT_TRUE(run_);

+ 89 - 0
src/lib/asiolink/udp_endpoint.h

@@ -0,0 +1,89 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef __UDP_ENDPOINT_H
+#define __UDP_ENDPOINT_H 1
+
+#ifndef ASIO_HPP
+#error "asio.hpp must be included before including this, see asiolink.h as to why"
+#endif
+
+#include <asiolink/io_endpoint.h>
+
+namespace asiolink {
+
+/// \brief The \c UDPEndpoint class is a concrete derived class of
+/// \c IOEndpoint that represents an endpoint of a UDP packet.
+///
+/// Other notes about \c TCPEndpoint applies to this class, too.
+class UDPEndpoint : public IOEndpoint {
+public:
+    ///
+    /// \name Constructors and Destructor.
+    ///
+    //@{
+    /// \brief Constructor from a pair of address and port.
+    ///
+    /// \param address The IP address of the endpoint.
+    /// \param port The UDP port number of the endpoint.
+    UDPEndpoint(const IOAddress& address, const unsigned short port) :
+        asio_endpoint_placeholder_(
+            new asio::ip::udp::endpoint(asio::ip::address::from_string(address.toText()),
+                              port)),
+        asio_endpoint_(*asio_endpoint_placeholder_)
+    {}
+
+    /// \brief Constructor from an ASIO UDP endpoint.
+    ///
+    /// This constructor is designed to be an efficient wrapper for the
+    /// corresponding ASIO class, \c udp::endpoint.
+    ///
+    /// \param asio_endpoint The ASIO representation of the UDP endpoint.
+    UDPEndpoint(const asio::ip::udp::endpoint& asio_endpoint) :
+        asio_endpoint_placeholder_(NULL), asio_endpoint_(asio_endpoint)
+    {}
+
+    /// \brief The destructor.
+    ~UDPEndpoint() { delete asio_endpoint_placeholder_; }
+    //@}
+
+    inline IOAddress getAddress() const {
+        return (asio_endpoint_.address());
+    }
+
+    inline uint16_t getPort() const {
+        return (asio_endpoint_.port());
+    }
+
+    inline short getProtocol() const {
+        return (asio_endpoint_.protocol().protocol());
+    }
+
+    inline short getFamily() const {
+        return (asio_endpoint_.protocol().family());
+    }
+
+    // This is not part of the exosed IOEndpoint API but allows
+    // direct access to the ASIO implementation of the endpoint
+    inline const asio::ip::udp::endpoint& getASIOEndpoint() const {
+        return (asio_endpoint_);
+    }
+
+private:
+    const asio::ip::udp::endpoint* asio_endpoint_placeholder_;
+    const asio::ip::udp::endpoint& asio_endpoint_;
+};
+
+}      // namespace asiolink
+#endif // __UDP_ENDPOINT_H

+ 186 - 0
src/lib/asiolink/udp_query.cc

@@ -0,0 +1,186 @@
+// 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 <asio.hpp>
+
+#include <boost/bind.hpp>
+#include <boost/shared_array.hpp>
+
+#include <dns/messagerenderer.h>
+#include <dns/opcode.h>
+#include <dns/rcode.h>
+
+#include <log/dummylog.h>
+
+#include <asio.hpp>
+
+#include <asiolink.h>
+
+#include <coroutine.h>
+#include <asiolink/udp_endpoint.h>
+
+#include <asiolink/udp_query.h>
+
+using namespace asio;
+using asio::ip::udp;
+using asio::ip::tcp;
+using isc::log::dlog;
+
+using namespace std;
+using namespace isc::dns;
+
+namespace asiolink {
+
+
+// Private UDPQuery data (see internal/udpdns.h for reasons)
+struct UDPQuery::PrivateData {
+    // Socket we send query to and expect reply from there
+    udp::socket socket;
+    // Where was the query sent
+    udp::endpoint remote;
+    // What we ask the server
+    Question question;
+    // We will store the answer here
+    OutputBufferPtr buffer;
+    OutputBufferPtr msgbuf;
+    // Temporary buffer for answer
+    boost::shared_array<char> data;
+    // This will be called when the data arrive or timeouts
+    Callback* callback;
+    // Did we already stop operating (data arrived, we timed out, someone
+    // called stop). This can be so when we are cleaning up/there are
+    // still pointers to us.
+    bool stopped;
+    // Timer to measure timeouts.
+    deadline_timer timer;
+    // How many milliseconds are we willing to wait for answer?
+    int timeout;
+
+    PrivateData(io_service& service,
+        const udp::socket::protocol_type& protocol, const Question &q,
+        OutputBufferPtr b, Callback *c) :
+        socket(service, protocol),
+        question(q),
+        buffer(b),
+        msgbuf(new OutputBuffer(512)),
+        callback(c),
+        stopped(false),
+        timer(service)
+    { }
+};
+
+/// The following functions implement the \c UDPQuery class.
+///
+/// The constructor
+UDPQuery::UDPQuery(io_service& io_service,
+                   const Question& q, const IOAddress& addr, uint16_t port,
+                   OutputBufferPtr buffer, Callback *callback, int timeout) :
+    data_(new PrivateData(io_service,
+        addr.getFamily() == AF_INET ? udp::v4() : udp::v6(), q, buffer,
+        callback))
+{
+    data_->remote = UDPEndpoint(addr, port).getASIOEndpoint();
+    data_->timeout = timeout;
+}
+
+/// The function operator is implemented with the "stackless coroutine"
+/// pattern; see internal/coroutine.h for details.
+void
+UDPQuery::operator()(error_code ec, size_t length) {
+    if (ec || data_->stopped) {
+        return;
+    }
+
+    CORO_REENTER (this) {
+        /// Generate the upstream query and render it to wire format
+        /// This is done in a different scope to allow inline variable
+        /// declarations.
+        {
+            Message msg(Message::RENDER);
+            
+            // XXX: replace with boost::random or some other suitable PRNG
+            msg.setQid(0);
+            msg.setOpcode(Opcode::QUERY());
+            msg.setRcode(Rcode::NOERROR());
+            msg.setHeaderFlag(Message::HEADERFLAG_RD);
+            msg.addQuestion(data_->question);
+            MessageRenderer renderer(*data_->msgbuf);
+            msg.toWire(renderer);
+            dlog("Sending " + msg.toText() + " to " +
+                data_->remote.address().to_string());
+        }
+
+
+        // If we timeout, we stop, which will shutdown everything and
+        // cancel all other attempts to run inside the coroutine
+        if (data_->timeout != -1) {
+            data_->timer.expires_from_now(boost::posix_time::milliseconds(
+                data_->timeout));
+            data_->timer.async_wait(boost::bind(&UDPQuery::stop, *this,
+                TIME_OUT));
+        }
+
+        // Begin an asynchronous send, and then yield.  When the
+        // send completes, we will resume immediately after this point.
+        CORO_YIELD data_->socket.async_send_to(buffer(data_->msgbuf->getData(),
+            data_->msgbuf->getLength()), data_->remote, *this);
+
+        /// Allocate space for the response.  (XXX: This should be
+        /// optimized by maintaining a free list of pre-allocated blocks)
+        data_->data.reset(new char[MAX_LENGTH]);
+
+        /// Begin an asynchronous receive, and yield.  When the receive
+        /// completes, we will resume immediately after this point.
+        CORO_YIELD data_->socket.async_receive_from(buffer(data_->data.get(),
+            MAX_LENGTH), data_->remote, *this);
+        // The message is not rendered yet, so we can't print it easilly
+        dlog("Received response from " + data_->remote.address().to_string());
+
+        /// Copy the answer into the response buffer.  (XXX: If the
+        /// OutputBuffer object were made to meet the requirements of
+        /// a MutableBufferSequence, then it could be written to directly
+        /// by async_recieve_from() and this additional copy step would
+        /// be unnecessary.)
+        data_->buffer->writeData(data_->data.get(), length);
+
+        /// We are done
+        stop(SUCCESS);
+    }
+}
+
+void
+UDPQuery::stop(Result result) {
+    if (!data_->stopped) {
+        switch (result) {
+            case TIME_OUT:
+                dlog("Query timed out");
+                break;
+            case STOPPED:
+                dlog("Query stopped");
+                break;
+            default:;
+        }
+        data_->stopped = true;
+        data_->socket.cancel();
+        data_->socket.close();
+        data_->timer.cancel();
+        if (data_->callback) {
+            (*data_->callback)(result);
+        }
+    }
+}
+
+} // namespace asiolink

+ 88 - 0
src/lib/asiolink/udp_query.h

@@ -0,0 +1,88 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef __UDP_QUERY_H
+#define __UDP_QUERY_H 1
+
+#ifndef ASIO_HPP
+#error "asio.hpp must be included before including this, see asiolink.h as to why"
+#endif
+
+#include <dns/buffer.h>
+
+#include <asiolink/io_address.h>
+#include <coroutine.h>
+
+namespace asiolink {
+
+//
+// Asynchronous UDP coroutine for upstream queries
+//
+class UDPQuery : public coroutine {
+public:
+    // TODO Maybe this should be more generic than just for UDPQuery?
+    ///
+    /// \brief Result of the query
+    ///
+    /// This is related only to contacting the remote server. If the answer
+    ///indicates error, it is still counted as SUCCESS here, if it comes back.
+    ///
+    enum Result {
+        SUCCESS,
+        TIME_OUT,
+        STOPPED
+    };
+    /// Abstract callback for the UDPQuery.
+    class Callback {
+    public:
+        virtual ~Callback() {}
+
+        /// This will be called when the UDPQuery is completed
+        virtual void operator()(Result result) = 0;
+    };
+    ///
+    /// \brief Constructor.
+    ///
+    /// It creates the query.
+    /// @param callback will be called when we terminate. It is your task to
+    ///        delete it if allocated on heap.
+    ///@param timeout in ms.
+    ///
+    explicit UDPQuery(asio::io_service& io_service,
+                      const isc::dns::Question& q,
+                      const IOAddress& addr, uint16_t port,
+                      isc::dns::OutputBufferPtr buffer,
+                      Callback* callback, int timeout = -1);
+    void operator()(asio::error_code ec = asio::error_code(),
+                    size_t length = 0);
+    /// Terminate the query.
+    void stop(Result reason = STOPPED);
+private:
+    enum { MAX_LENGTH = 4096 };
+
+    ///
+    /// \short Private data
+    ///
+    /// They are not private because of stability of the
+    /// interface (this is private class anyway), but because this class
+    /// will be copyed often (it is used as a coroutine and passed as callback
+    /// to many async_*() functions) and we want keep the same data. Some of
+    /// the data is not copyable too.
+    ///
+    struct PrivateData;
+    boost::shared_ptr<PrivateData> data_;
+};
+
+}      // namespace asiolink
+#endif // __UDP_QUERY_H

+ 281 - 0
src/lib/asiolink/udp_server.cc

@@ -0,0 +1,281 @@
+// 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 <boost/shared_array.hpp>
+
+#include <asio.hpp>
+
+#include <log/dummylog.h>
+
+#include <asiolink/udp_endpoint.h>
+#include <asiolink/udp_socket.h>
+
+#include <asiolink/udp_server.h>
+
+using namespace asio;
+using asio::ip::udp;
+using asio::ip::tcp;
+using isc::log::dlog;
+
+using namespace std;
+using namespace isc::dns;
+
+namespace asiolink {
+
+/*
+ * Some of the member variables here are shared_ptrs and some are
+ * auto_ptrs. There will be one instance of Data for the lifetime
+ * of packet. The variables that are state only for a single packet
+ * use auto_ptr, as it is more lightweight. In the case of shared
+ * configuration (eg. the callbacks, socket), we use shared_ptrs.
+ */
+struct UDPServer::Data {
+    /*
+     * Constructor from parameters passed to UDPServer constructor.
+     * This instance will not be used to retrieve and answer the actual
+     * query, it will only hold parameters until we wait for the
+     * first packet. But we do initialize the socket in here.
+     */
+    Data(io_service& io_service, const ip::address& addr, const uint16_t port,
+        SimpleCallback* checkin, DNSLookup* lookup, DNSAnswer* answer) :
+        io_(io_service), done_(false), checkin_callback_(checkin),
+        lookup_callback_(lookup), answer_callback_(answer)
+    {
+        // We must use different instantiations for v4 and v6;
+        // otherwise ASIO will bind to both
+        udp proto = addr.is_v4() ? udp::v4() : udp::v6();
+        socket_.reset(new udp::socket(io_service, proto));
+        socket_->set_option(socket_base::reuse_address(true));
+        if (addr.is_v6()) {
+            socket_->set_option(asio::ip::v6_only(true));
+        }
+        socket_->bind(udp::endpoint(addr, port));
+    }
+
+    /*
+     * Copy constructor. Default one would probably do, but it is unnecessary
+     * to copy many of the member variables every time we fork to handle
+     * another packet.
+     *
+     * We also allocate data for receiving the packet here.
+     */
+    Data(const Data& other) :
+        io_(other.io_), socket_(other.socket_), done_(false),
+        checkin_callback_(other.checkin_callback_),
+        lookup_callback_(other.lookup_callback_),
+        answer_callback_(other.answer_callback_)
+    {
+        // Instantiate the data buffer and endpoint that will
+        // be used by the asynchronous receive call.
+        data_.reset(new char[MAX_LENGTH]);
+        sender_.reset(new udp::endpoint());
+    }
+
+    // The ASIO service object
+    asio::io_service& io_;
+
+    // Class member variables which are dynamic, and changes to which
+    // need to accessible from both sides of a coroutine fork or from
+    // outside of the coroutine (i.e., from an asynchronous I/O call),
+    // should be declared here as pointers and allocated in the
+    // constructor or in the coroutine.  This allows state information
+    // to persist when an individual copy of the coroutine falls out
+    // scope while waiting for an event, *so long as* there is another
+    // object that is referencing the same data.  As a side-benefit, using
+    // pointers also reduces copy overhead for coroutine objects.
+    //
+    // Note: Currently these objects are allocated by "new" in the
+    // constructor, or in the function operator while processing a query.
+    // Repeated allocations from the heap for every incoming query is
+    // clearly a performance issue; this must be optimized in the future.
+    // The plan is to have a structure pre-allocate several "Data"
+    // objects which can be pulled off a free list and placed on an in-use
+    // list whenever a query comes in.  This will serve the dual purpose
+    // of improving performance and guaranteeing that state information
+    // will *not* be destroyed when any one instance of the coroutine
+    // falls out of scope while waiting for an event.
+    //
+    // Socket used to for listen for queries.  Created in the
+    // constructor and stored in a shared_ptr because socket objects
+    // are not copyable.
+    boost::shared_ptr<asio::ip::udp::socket> socket_;
+
+    // The ASIO-internal endpoint object representing the client
+    std::auto_ptr<asio::ip::udp::endpoint> sender_;
+
+    // \c IOMessage and \c Message objects to be passed to the
+    // DNS lookup and answer providers
+    std::auto_ptr<asiolink::IOMessage> io_message_;
+
+    // The original query as sent by the client
+    isc::dns::MessagePtr query_message_;
+
+    // The response message we are building
+    isc::dns::MessagePtr answer_message_;
+
+    // The buffer into which the response is written
+    isc::dns::OutputBufferPtr respbuf_;
+
+    // The buffer into which the query packet is written
+    boost::shared_array<char> data_;
+
+    // State information that is entirely internal to a given instance
+    // of the coroutine can be declared here.
+    size_t bytes_;
+    bool done_;
+
+    // Callback functions provided by the caller
+    const SimpleCallback* checkin_callback_;
+    const DNSLookup* lookup_callback_;
+    const DNSAnswer* answer_callback_;
+
+    std::auto_ptr<IOEndpoint> peer_;
+    std::auto_ptr<IOSocket> iosock_;
+};
+
+/// The following functions implement the \c UDPServer class.
+///
+/// The constructor. It just creates new internal state object
+/// and lets it handle the initialization.
+UDPServer::UDPServer(io_service& io_service, const ip::address& addr,
+    const uint16_t port, SimpleCallback* checkin, DNSLookup* lookup,
+    DNSAnswer* answer) :
+    data_(new Data(io_service, addr, port, checkin, lookup, answer))
+{ }
+
+/// The function operator is implemented with the "stackless coroutine"
+/// pattern; see internal/coroutine.h for details.
+void
+UDPServer::operator()(error_code ec, size_t length) {
+    /// Because the coroutine reeentry block is implemented as
+    /// a switch statement, inline variable declarations are not
+    /// permitted.  Certain variables used below can be declared here.
+
+    CORO_REENTER (this) {
+        do {
+            /*
+             * This is preparation for receiving a packet. We get a new
+             * state object for the lifetime of the next packet to come.
+             * It allocates the buffers to receive data into.
+             */
+            data_.reset(new Data(*data_));
+
+            do {
+                // Begin an asynchronous receive, then yield.
+                // When the receive event is posted, the coroutine
+                // will resume immediately after this point.
+                CORO_YIELD data_->socket_->async_receive_from(
+                    buffer(data_->data_.get(), MAX_LENGTH), *data_->sender_,
+                    *this);
+            } while (ec || length == 0);
+
+            data_->bytes_ = length;
+
+            /*
+             * We fork the coroutine now. One (the child) will keep
+             * the current state and handle the packet, then die and
+             * drop ownership of the state. The other (parent) will just
+             * go into the loop again and replace the current state with
+             * a new one for a new object.
+             *
+             * Actually, both of the coroutines will be a copy of this
+             * one, but that's just internal implementation detail.
+             */
+            CORO_FORK data_->io_.post(UDPServer(*this));
+        } while (is_parent());
+
+        // Create an \c IOMessage object to store the query.
+        //
+        // (XXX: It would be good to write a factory function
+        // that would quickly generate an IOMessage object without
+        // all these calls to "new".)
+        data_->peer_.reset(new UDPEndpoint(*data_->sender_));
+        data_->iosock_.reset(new UDPSocket(*data_->socket_));
+        data_->io_message_.reset(new IOMessage(data_->data_.get(),
+            data_->bytes_, *data_->iosock_, *data_->peer_));
+
+        // Perform any necessary operations prior to processing an incoming
+        // query (e.g., checking for queued configuration messages).
+        //
+        // (XXX: it may be a performance issue to check in for every single
+        // incoming query; we may wish to throttle this in the future.)
+        if (data_->checkin_callback_ != NULL) {
+            (*data_->checkin_callback_)(*data_->io_message_);
+        }
+
+        // If we don't have a DNS Lookup provider, there's no point in
+        // continuing; we exit the coroutine permanently.
+        if (data_->lookup_callback_ == NULL) {
+            CORO_YIELD return;
+        }
+
+        // Instantiate objects that will be needed by the
+        // asynchronous DNS lookup and/or by the send call.
+        data_->respbuf_.reset(new OutputBuffer(0));
+        data_->query_message_.reset(new Message(Message::PARSE));
+        data_->answer_message_.reset(new Message(Message::RENDER));
+
+        // Schedule a DNS lookup, and yield.  When the lookup is
+        // finished, the coroutine will resume immediately after
+        // this point.
+        CORO_YIELD data_->io_.post(AsyncLookup<UDPServer>(*this));
+
+        dlog("[XX] got an answer");
+
+        // The 'done_' flag indicates whether we have an answer
+        // to send back.  If not, exit the coroutine permanently.
+        if (!data_->done_) {
+            CORO_YIELD return;
+        }
+
+        // Call the DNS answer provider to render the answer into
+        // wire format
+        (*data_->answer_callback_)(*data_->io_message_, data_->query_message_,
+            data_->answer_message_, data_->respbuf_);
+
+        // Begin an asynchronous send, and then yield.  When the
+        // send completes, we will resume immediately after this point
+        // (though we have nothing further to do, so the coroutine
+        // will simply exit at that time).
+        CORO_YIELD data_->socket_->async_send_to(
+            buffer(data_->respbuf_->getData(), data_->respbuf_->getLength()),
+            *data_->sender_, *this);
+    }
+}
+
+/// Call the DNS lookup provider.  (Expected to be called by the
+/// AsyncLookup<UDPServer> handler.)
+void
+UDPServer::asyncLookup() {
+    (*data_->lookup_callback_)(*data_->io_message_,
+        data_->query_message_, data_->answer_message_, data_->respbuf_, this);
+}
+
+/// Post this coroutine on the ASIO service queue so that it will
+/// resume processing where it left off.  The 'done' parameter indicates
+/// whether there is an answer to return to the client.
+void
+UDPServer::resume(const bool done) {
+    data_->done_ = done;
+    data_->io_.post(*this);
+}
+
+bool
+UDPServer::hasAnswer() {
+    return (data_->done_);
+}
+
+} // namespace asiolink

+ 102 - 0
src/lib/asiolink/udp_server.h

@@ -0,0 +1,102 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef __UDP_SERVER_H
+#define __UDP_SERVER_H 1
+
+#ifndef ASIO_HPP
+#error "asio.hpp must be included before including this, see asiolink.h as to why"
+#endif
+
+#include <asiolink/dns_server.h>
+#include <asiolink/simple_callback.h>
+#include <asiolink/dns_lookup.h>
+#include <asiolink/dns_answer.h>
+
+#include <coroutine.h>
+
+namespace asiolink {
+
+//
+// Asynchronous UDP server coroutine
+//
+///
+/// \brief This class implements the coroutine to handle UDP
+///        DNS query event. As such, it is both a \c DNSServer and
+///        a \c coroutine
+///
+class UDPServer : public virtual DNSServer, public virtual coroutine {
+public:
+    /// \brief Constructor
+    /// \param io_service the asio::io_service to work with
+    /// \param addr the IP address to listen for queries on
+    /// \param port the port to listen for queries on
+    /// \param checkin the callbackprovider for non-DNS events
+    /// \param lookup the callbackprovider for DNS lookup events
+    /// \param answer the callbackprovider for DNS answer events
+    explicit UDPServer(asio::io_service& io_service,
+                       const asio::ip::address& addr, const uint16_t port,
+                       SimpleCallback* checkin = NULL,
+                       DNSLookup* lookup = NULL,
+                       DNSAnswer* answer = NULL);
+
+    /// \brief The function operator
+    void operator()(asio::error_code ec = asio::error_code(),
+                    size_t length = 0);
+
+    /// \brief Calls the lookup callback
+    void asyncLookup();
+
+    /// \brief Resume operation
+    ///
+    /// \param done Set this to true if the lookup action is done and
+    ///        we have an answer
+    void resume(const bool done);
+
+    /// \brief Check if we have an answer
+    ///
+    /// \return true if we have an answer
+    bool hasAnswer();
+
+    /// \brief Returns the coroutine state value
+    ///
+    /// \return the coroutine state value
+    int value() { return (get_value()); }
+
+    /// \brief Clones the object
+    ///
+    /// \return a newly allocated copy of this object
+    DNSServer* clone() {
+        UDPServer* s = new UDPServer(*this);
+        return (s);
+    }
+
+private:
+    enum { MAX_LENGTH = 4096 };
+
+    /**
+     * \brief Internal state and data.
+     *
+     * We use the pimple design pattern, but not because we need to hide
+     * internal data. This class and whole header is for private use anyway.
+     * It turned out that UDPServer is copied a lot, because it is a coroutine.
+     * This way the overhead of copying is lower, we copy only one shared
+     * pointer instead of about 10 of them.
+     */
+    class Data;
+    boost::shared_ptr<Data> data_;
+};
+
+}      // namespace asiolink
+#endif // __UDP_SERVER_H

+ 48 - 0
src/lib/asiolink/udp_socket.h

@@ -0,0 +1,48 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef __UDP_SOCKET_H
+#define __UDP_SOCKET_H 1
+
+#ifndef ASIO_HPP
+#error "asio.hpp must be included before including this, see asiolink.h as to why"
+#endif
+
+#include <asiolink/io_socket.h>
+
+namespace asiolink {
+
+/// \brief The \c UDPSocket class is a concrete derived class of
+/// \c IOSocket that represents a UDP socket.
+///
+/// Other notes about \c TCPSocket applies to this class, too.
+class UDPSocket : public IOSocket {
+private:
+    UDPSocket(const UDPSocket& source);
+    UDPSocket& operator=(const UDPSocket& source);
+public:
+    /// \brief Constructor from an ASIO UDP socket.
+    ///
+    /// \param socket The ASIO representation of the UDP socket.
+    UDPSocket(asio::ip::udp::socket& socket) : socket_(socket) {}
+
+    virtual int getNative() const { return (socket_.native()); }
+    virtual int getProtocol() const { return (IPPROTO_UDP); }
+
+private:
+    asio::ip::udp::socket& socket_;
+};
+
+}      // namespace asiolink
+#endif // __UDP_SOCKET_H

+ 0 - 181
src/lib/asiolink/udpdns.cc

@@ -1,181 +0,0 @@
-// Copyright (C) 2010  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 <unistd.h>             // for some IPC/network system calls
-#include <sys/socket.h>
-#include <netinet/in.h>
-
-#include <boost/bind.hpp>
-
-#include <asio.hpp>
-#include <asio/deadline_timer.hpp>
-
-#include <boost/shared_ptr.hpp>
-#include <boost/date_time/posix_time/posix_time_types.hpp>
-
-#include <dns/buffer.h>
-#include <dns/message.h>
-#include <dns/messagerenderer.h>
-#include <log/dummylog.h>
-#include <dns/opcode.h>
-#include <dns/rcode.h>
-
-#include <asiolink.h>
-#include <internal/coroutine.h>
-#include <internal/udpdns.h>
-
-using namespace asio;
-using asio::ip::udp;
-using asio::ip::tcp;
-using isc::log::dlog;
-
-using namespace std;
-using namespace isc::dns;
-
-namespace asiolink {
-/// The following functions implement the \c UDPServer class.
-///
-/// The constructor
-UDPServer::UDPServer(io_service& io_service,
-                     const ip::address& addr, const uint16_t port,
-                     SimpleCallback* checkin,
-                     DNSLookup* lookup,
-                     DNSAnswer* answer) :
-    io_(io_service), done_(false),
-    checkin_callback_(checkin),
-    lookup_callback_(lookup),
-    answer_callback_(answer)
-{
-    // We must use different instantiations for v4 and v6;
-    // otherwise ASIO will bind to both
-    udp proto = addr.is_v4() ? udp::v4() : udp::v6();
-    socket_.reset(new udp::socket(io_service, proto));
-    socket_->set_option(socket_base::reuse_address(true));
-    if (addr.is_v6()) {
-        socket_->set_option(asio::ip::v6_only(true));
-    }
-    socket_->bind(udp::endpoint(addr, port));
-}
-
-/// The function operator is implemented with the "stackless coroutine"
-/// pattern; see internal/coroutine.h for details.
-void
-UDPServer::operator()(error_code ec, size_t length) {
-    /// Because the coroutine reeentry block is implemented as
-    /// a switch statement, inline variable declarations are not
-    /// permitted.  Certain variables used below can be declared here.
-
-    CORO_REENTER (this) {
-        do {
-            // Instantiate the data buffer and endpoint that will
-            // be used by the asynchronous receive call.
-            data_.reset(new char[MAX_LENGTH]);
-            sender_.reset(new udp::endpoint());
-
-            do {
-                // Begin an asynchronous receive, then yield.
-                // When the receive event is posted, the coroutine
-                // will resume immediately after this point.
-                CORO_YIELD socket_->async_receive_from(buffer(data_.get(),
-                                                              MAX_LENGTH),
-                                                  *sender_, *this);
-            } while (ec || length == 0);
-
-            bytes_ = length;
-
-            /// Fork the coroutine by creating a copy of this one and
-            /// scheduling it on the ASIO service queue.  The parent
-            /// will continue listening for DNS packets while the child
-            /// processes the one that has just arrived.
-            CORO_FORK io_.post(UDPServer(*this));
-        } while (is_parent());
-
-        // Create an \c IOMessage object to store the query.
-        //
-        // (XXX: It would be good to write a factory function
-        // that would quickly generate an IOMessage object without
-        // all these calls to "new".)
-        peer_.reset(new UDPEndpoint(*sender_));
-        iosock_.reset(new UDPSocket(*socket_));
-        io_message_.reset(new IOMessage(data_.get(), bytes_, *iosock_, *peer_));
-
-        // Perform any necessary operations prior to processing an incoming
-        // query (e.g., checking for queued configuration messages).
-        //
-        // (XXX: it may be a performance issue to check in for every single
-        // incoming query; we may wish to throttle this in the future.)
-        if (checkin_callback_ != NULL) {
-            (*checkin_callback_)(*io_message_);
-        }
-
-        // If we don't have a DNS Lookup provider, there's no point in
-        // continuing; we exit the coroutine permanently.
-        if (lookup_callback_ == NULL) {
-            CORO_YIELD return;
-        }
-
-        // Instantiate objects that will be needed by the
-        // asynchronous DNS lookup and/or by the send call.
-        respbuf_.reset(new OutputBuffer(0));
-        query_message_.reset(new Message(Message::PARSE));
-        answer_message_.reset(new Message(Message::RENDER));
-
-        // Schedule a DNS lookup, and yield.  When the lookup is
-        // finished, the coroutine will resume immediately after
-        // this point.
-        CORO_YIELD io_.post(AsyncLookup<UDPServer>(*this));
-
-        dlog("[XX] got an answer");
-
-        // The 'done_' flag indicates whether we have an answer
-        // to send back.  If not, exit the coroutine permanently.
-        if (!done_) {
-            CORO_YIELD return;
-        }
-
-        // Call the DNS answer provider to render the answer into
-        // wire format
-        (*answer_callback_)(*io_message_, query_message_,
-                            answer_message_, respbuf_);
-
-        // Begin an asynchronous send, and then yield.  When the
-        // send completes, we will resume immediately after this point
-        // (though we have nothing further to do, so the coroutine
-        // will simply exit at that time).
-        CORO_YIELD socket_->async_send_to(buffer(respbuf_->getData(),
-                                                 respbuf_->getLength()),
-                                     *sender_, *this);
-    }
-}
-
-/// Call the DNS lookup provider.  (Expected to be called by the
-/// AsyncLookup<UDPServer> handler.)
-void
-UDPServer::asyncLookup() {
-    (*lookup_callback_)(*io_message_, query_message_, answer_message_,
-                        respbuf_, this);
-}
-
-/// Post this coroutine on the ASIO service queue so that it will
-/// resume processing where it left off.  The 'done' parameter indicates
-/// whether there is an answer to return to the client.
-void
-UDPServer::resume(const bool done) {
-    done_ = done;
-    io_.post(*this);
-}
-
-}

+ 242 - 70
src/lib/datasrc/memory_datasrc.cc

@@ -35,13 +35,14 @@ namespace datasrc {
 struct MemoryZone::MemoryZoneImpl {
     // Constructor
     MemoryZoneImpl(const RRClass& zone_class, const Name& origin) :
-        zone_class_(zone_class), origin_(origin)
-    {}
-
-    // Information about the zone
-    RRClass zone_class_;
-    Name origin_;
-    string file_name_;
+        zone_class_(zone_class), origin_(origin), origin_data_(NULL),
+        domains_(true)
+    {
+        // We create the node for origin (it needs to exist anyway in future)
+        domains_.insert(origin, &origin_data_);
+        DomainPtr origin_domain(new Domain);
+        origin_data_->setData(origin_domain);
+    }
 
     // Some type aliases
     /*
@@ -61,48 +62,169 @@ struct MemoryZone::MemoryZoneImpl {
     // The tree stores domains
     typedef RBTree<Domain> DomainTree;
     typedef RBNode<Domain> DomainNode;
+    static const DomainNode::Flags DOMAINFLAG_WILD = DomainNode::FLAG_USER1;
+
+    // Information about the zone
+    RRClass zone_class_;
+    Name origin_;
+    DomainNode* origin_data_;
+    string file_name_;
+
     // The actual zone data
     DomainTree domains_;
 
+    // Add the necessary magic for any wildcard contained in 'name'
+    // (including itself) to be found in the zone.
+    //
+    // In order for wildcard matching to work correctly in find(),
+    // we must ensure that a node for the wildcarding level exists in the
+    // backend RBTree.
+    // E.g. if the wildcard name is "*.sub.example." then we must ensure
+    // that "sub.example." exists and is marked as a wildcard level.
+    // Note: the "wildcarding level" is for the parent name of the wildcard
+    // name (such as "sub.example.").
+    //
+    // We also perform the same trick for empty wild card names possibly
+    // contained in 'name' (e.g., '*.foo.example' in 'bar.*.foo.example').
+    void addWildcards(DomainTree& domains, const Name& name) {
+        Name wname(name);
+        const unsigned int labels(wname.getLabelCount());
+        const unsigned int origin_labels(origin_.getLabelCount());
+        for (unsigned int l = labels;
+             l > origin_labels;
+             --l, wname = wname.split(1)) {
+            if (wname.isWildcard()) {
+                // Ensure a separate level exists for the wildcard name.
+                // Note: for 'name' itself we do this later anyway, but the
+                // overhead should be marginal because wildcard names should
+                // be rare.
+                DomainNode* node;
+                DomainTree::Result result(domains.insert(wname.split(1),
+                                                         &node));
+                assert(result == DomainTree::SUCCESS ||
+                       result == DomainTree::ALREADYEXISTS);
+
+                // Ensure a separate level exists for the "wildcarding" name,
+                // and mark the node as "wild".
+                result = domains.insert(wname, &node);
+                assert(result == DomainTree::SUCCESS ||
+                       result == DomainTree::ALREADYEXISTS);
+                node->setFlag(DOMAINFLAG_WILD);
+            }
+        }
+    }
+
     /*
-     * Implementation of longer methods. We put them here, because the
-     * access is without the impl_-> and it will get inlined anyway.
+     * Does some checks in context of the data that are already in the zone.
+     * Currently checks for forbidden combinations of RRsets in the same
+     * domain (CNAME+anything, DNAME+NS).
+     *
+     * If such condition is found, it throws AddError.
      */
-    // Implementation of MemoryZone::add
-    result::Result add(const ConstRRsetPtr& rrset, DomainTree* domains) {
-        // Sanitize input
+    void contextCheck(const ConstRRsetPtr& rrset,
+                      const DomainPtr& domain) const {
+        // Ensure CNAME and other type of RR don't coexist for the same
+        // owner name.
+        if (rrset->getType() == RRType::CNAME()) {
+            // XXX: this check will become incorrect when we support DNSSEC
+            // (depending on how we support DNSSEC).  We should revisit it
+            // at that point.
+            if (!domain->empty()) {
+                isc_throw(AddError, "CNAME can't be added with other data for "
+                          << rrset->getName());
+            }
+        } else if (domain->find(RRType::CNAME()) != domain->end()) {
+            isc_throw(AddError, "CNAME and " << rrset->getType() <<
+                      " can't coexist for " << rrset->getName());
+        }
+
+        /*
+         * Similar with DNAME, but it must not coexist only with NS and only in
+         * non-apex domains.
+         * RFC 2672 section 3 mentions that it is implied from it and RFC 2181
+         */
+        if (rrset->getName() != origin_ &&
+            // Adding DNAME, NS already there
+            ((rrset->getType() == RRType::DNAME() &&
+            domain->find(RRType::NS()) != domain->end()) ||
+            // Adding NS, DNAME already there
+            (rrset->getType() == RRType::NS() &&
+            domain->find(RRType::DNAME()) != domain->end())))
+        {
+            isc_throw(AddError, "DNAME can't coexist with NS in non-apex "
+                "domain " << rrset->getName());
+        }
+    }
+
+    // Validate rrset before adding it to the zone.  If something is wrong
+    // it throws an exception.  It doesn't modify the zone, and provides
+    // the strong exception guarantee.
+    void addValidation(const ConstRRsetPtr rrset) {
         if (!rrset) {
             isc_throw(NullRRset, "The rrset provided is NULL");
         }
-        if (rrset->getType() == RRType::CNAME() &&
-            rrset->getRdataCount() > 1) {
-            // XXX: this is not only for CNAME.  We should generalize this
-            // code for all other "singleton RR types" (such as SOA) in a
+        // Check for singleton RRs. It should probably handled at a different
+        // in future.
+        if ((rrset->getType() == RRType::CNAME() ||
+            rrset->getType() == RRType::DNAME()) &&
+            rrset->getRdataCount() > 1)
+        {
+            // XXX: this is not only for CNAME or DNAME. We should generalize
+            // this code for all other "singleton RR types" (such as SOA) in a
             // separate task.
             isc_throw(AddError, "multiple RRs of singleton type for "
                       << rrset->getName());
         }
 
-        Name name(rrset->getName());
-        NameComparisonResult compare(origin_.compare(name));
+        NameComparisonResult compare(origin_.compare(rrset->getName()));
         if (compare.getRelation() != NameComparisonResult::SUPERDOMAIN &&
             compare.getRelation() != NameComparisonResult::EQUAL)
         {
-            isc_throw(OutOfZone, "The name " << name <<
+            isc_throw(OutOfZone, "The name " << rrset->getName() <<
                 " is not contained in zone " << origin_);
         }
+
+        // Some RR types do not really work well with a wildcard.
+        // Even though the protocol specifically doesn't completely ban such
+        // usage, we refuse to load a zone containing such RR in order to
+        // keep the lookup logic simpler and more predictable.
+        // See RFC4592 and (for DNAME) draft-ietf-dnsext-rfc2672bis-dname
+        // for more technical background.  Note also that BIND 9 refuses
+        // NS at a wildcard, so in that sense we simply provide compatible
+        // behavior.
+        if (rrset->getName().isWildcard()) {
+            if (rrset->getType() == RRType::NS()) {
+                isc_throw(AddError, "Invalid NS owner name (wildcard): " <<
+                          rrset->getName());
+            }
+            if (rrset->getType() == RRType::DNAME()) {
+                isc_throw(AddError, "Invalid DNAME owner name (wildcard): " <<
+                          rrset->getName());
+            }
+        }
+    }
+
+    /*
+     * Implementation of longer methods. We put them here, because the
+     * access is without the impl_-> and it will get inlined anyway.
+     */
+    // Implementation of MemoryZone::add
+    result::Result add(const ConstRRsetPtr& rrset, DomainTree* domains) {
+        // Sanitize input
+        addValidation(rrset);
+
+        // Add wildcards possibly contained in the owner name to the domain
+        // tree.
+        // Note: this can throw an exception, breaking strong exception
+        // guarantee.  (see also the note for contextCheck() below).
+        addWildcards(*domains, rrset->getName());
+
         // Get the node
         DomainNode* node;
-        switch (domains->insert(name, &node)) {
-            // Just check it returns reasonable results
-            case DomainTree::SUCCESS:
-            case DomainTree::ALREADYEXISTS:
-                break;
-            // Something odd got out
-            default:
-                assert(0);
-        }
-        assert(node != NULL);
+        DomainTree::Result result = domains->insert(rrset->getName(), &node);
+        // Just check it returns reasonable results
+        assert((result == DomainTree::SUCCESS ||
+                result == DomainTree::ALREADYEXISTS) && node!= NULL);
 
         // Now get the domain
         DomainPtr domain;
@@ -114,24 +236,12 @@ struct MemoryZone::MemoryZoneImpl {
             domain = node->getData();
         }
 
-        // Ensure CNAME and other type of RR don't coexist for the same
-        // owner name.
+        // Checks related to the surrounding data.
         // Note: when the check fails and the exception is thrown, it may
         // break strong exception guarantee.  At the moment we prefer
         // code simplicity and don't bother to introduce complicated
         // recovery code.
-        if (rrset->getType() == RRType::CNAME()) {
-            // XXX: this check will become incorrect when we support DNSSEC
-            // (depending on how we support DNSSEC).  We should revisit it
-            // at that point.
-            if (!domain->empty()) {
-                isc_throw(AddError, "CNAME can't be added with other data for "
-                          << rrset->getName());
-            }
-        } else if (domain->find(RRType::CNAME()) != domain->end()) {
-            isc_throw(AddError, "CNAME and " << rrset->getType() <<
-                      " can't coexist for " << rrset->getName());
-        }
+        contextCheck(rrset, domain);
 
         // Try inserting the rrset there
         if (domain->insert(DomainPair(rrset->getType(), rrset)).second) {
@@ -139,10 +249,12 @@ struct MemoryZone::MemoryZoneImpl {
 
             // If this RRset creates a zone cut at this node, mark the node
             // indicating the need for callback in find().
-            // TBD: handle DNAME, too
             if (rrset->getType() == RRType::NS() &&
                 rrset->getName() != origin_) {
-                node->enableCallback();
+                node->setFlag(DomainNode::FLAG_CALLBACK);
+            // If it is DNAME, we have a callback as well here
+            } else if (rrset->getType() == RRType::DNAME()) {
+                node->setFlag(DomainNode::FLAG_CALLBACK);
             }
 
             return (result::SUCCESS);
@@ -174,31 +286,56 @@ struct MemoryZone::MemoryZoneImpl {
     /// It will be passed to \c zonecutCallback() and record a possible
     /// zone cut node and related RRset (normally NS or DNAME).
     struct FindState {
-        FindState(FindOptions options) : zonecut_node_(NULL),
-                                         options_(options)
+        FindState(FindOptions options) :
+            zonecut_node_(NULL),
+            dname_node_(NULL),
+            options_(options)
         {}
         const DomainNode* zonecut_node_;
+        const DomainNode* dname_node_;
         ConstRRsetPtr rrset_;
         const FindOptions options_;
     };
 
-    // A callback called from possible zone cut nodes.  This will be passed
-    // from the \c find() method to \c RBTree::find().
-    static bool zonecutCallback(const DomainNode& node, FindState* state) {
-        // We perform callback check only for the highest zone cut in the
-        // rare case of nested zone cuts.
-        if (state->zonecut_node_ != NULL) {
-            return (false);
+    // A callback called from possible zone cut nodes and nodes with DNAME.
+    // This will be passed from the \c find() method to \c RBTree::find().
+    static bool cutCallback(const DomainNode& node, FindState* state) {
+        // We need to look for DNAME first, there's allowed case where
+        // DNAME and NS coexist in the apex. DNAME is the one to notice,
+        // the NS is authoritative, not delegation (corner case explicitly
+        // allowed by section 3 of 2672)
+        const Domain::const_iterator foundDNAME(node.getData()->find(
+            RRType::DNAME()));
+        if (foundDNAME != node.getData()->end()) {
+            state->dname_node_ = &node;
+            state->rrset_ = foundDNAME->second;
+            // No more processing below the DNAME (RFC 2672, section 3
+            // forbids anything to exist below it, so there's no need
+            // to actually search for it). This is strictly speaking
+            // a different way than described in 4.1 of that RFC,
+            // but because of the assumption in section 3, it has the
+            // same behaviour.
+            return (true);
         }
 
-        const Domain::const_iterator found(node.getData()->find(RRType::NS()));
-        if (found != node.getData()->end()) {
-            // BIND 9 checks if this node is not the origin.  But it cannot
-            // be the origin because we don't enable the callback at the
-            // origin node (see MemoryZoneImpl::add()).  Or should we do a
-            // double check for it?
+        // Look for NS
+        const Domain::const_iterator foundNS(node.getData()->find(
+            RRType::NS()));
+        if (foundNS != node.getData()->end()) {
+            // We perform callback check only for the highest zone cut in the
+            // rare case of nested zone cuts.
+            if (state->zonecut_node_ != NULL) {
+                return (false);
+            }
+
+            // BIND 9 checks if this node is not the origin.  That's probably
+            // because it can support multiple versions for dynamic updates
+            // and IXFR, and it's possible that the callback is called at
+            // the apex and the DNAME doesn't exist for a particular version.
+            // It cannot happen for us (at least for now), so we don't do
+            // that check.
             state->zonecut_node_ = &node;
-            state->rrset_ = found->second;
+            state->rrset_ = foundNS->second;
 
             // Unless glue is allowed the search stops here, so we return
             // false; otherwise return true to continue the search.
@@ -222,15 +359,45 @@ struct MemoryZone::MemoryZoneImpl {
         // Get the node
         DomainNode* node(NULL);
         FindState state(options);
-        switch (domains_.find(name, &node, zonecutCallback, &state)) {
+        RBTreeNodeChain<Domain> node_path;
+        switch (domains_.find(name, &node, node_path, cutCallback, &state)) {
             case DomainTree::PARTIALMATCH:
+                /*
+                 * In fact, we could use a single variable instead of
+                 * dname_node_ and zonecut_node_. But then we would need
+                 * to distinquish these two cases by something else and
+                 * it seemed little more confusing to me when I wrote it.
+                 *
+                 * Usually at most one of them will be something else than
+                 * NULL (it might happen both are NULL, in which case we
+                 * consider it NOT FOUND). There's one corner case when
+                 * both might be something else than NULL and it is in case
+                 * there's a DNAME under a zone cut and we search in
+                 * glue OK mode ‒ in that case we don't stop on the domain
+                 * with NS and ignore it for the answer, but it gets set
+                 * anyway. Then we find the DNAME and we need to act by it,
+                 * therefore we first check for DNAME and then for NS. In
+                 * all other cases it doesn't matter, as at least one of them
+                 * is NULL.
+                 */
+                if (state.dname_node_ != NULL) {
+                    // We were traversing a DNAME node (and wanted to go
+                    // lower below it), so return the DNAME
+                    return (FindResult(DNAME, state.rrset_));
+                }
                 if (state.zonecut_node_ != NULL) {
                     return (FindResult(DELEGATION, state.rrset_));
                 }
-                // TODO: we should also cover empty non-terminal cases, which
-                // will require non trivial code and is deferred for later
-                // development.  For now, we regard any partial match that
-                // didn't hit a zone cut as "not found".
+
+                // If the RBTree search stopped at a node for a super domain
+                // of the search name, it means the search name exists in
+                // the zone but is empty.  Treat it as NXRRSET.
+                if (node_path.getLastComparisonResult().getRelation() ==
+                    NameComparisonResult::SUPERDOMAIN) {
+                    return (FindResult(NXRRSET, ConstRRsetPtr()));
+                }
+
+                // fall through
             case DomainTree::NOTFOUND:
                 return (FindResult(NXDOMAIN, ConstRRsetPtr()));
             case DomainTree::EXACTMATCH: // This one is OK, handle it
@@ -238,14 +405,19 @@ struct MemoryZone::MemoryZoneImpl {
             default:
                 assert(0);
         }
-        assert(node);
-        assert(!node->isEmpty());
+        assert(node != NULL);
+
+        // If there is an exact match but the node is empty, it's equivalent
+        // to NXRRSET.
+        if (node->isEmpty()) {
+            return (FindResult(NXRRSET, ConstRRsetPtr()));
+        }
 
         Domain::const_iterator found;
 
         // If the node callback is enabled, this may be a zone cut.  If it
-        // has a NS RR, we should return a delegation.
-        if (node->isCallbackEnabled()) {
+        // has a NS RR, we should return a delegation, but not in the apex.
+        if (node->getFlag(DomainNode::FLAG_CALLBACK) && node != origin_data_) {
             found = node->getData()->find(RRType::NS());
             if (found != node->getData()->end()) {
                 return (FindResult(DELEGATION, found->second));

File diff suppressed because it is too large
+ 556 - 157
src/lib/datasrc/rbtree.h


+ 259 - 63
src/lib/datasrc/tests/memory_datasrc_unittest.cc

@@ -1,4 +1,5 @@
 // Copyright (C) 2010  Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011  CZ NIC
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -12,8 +13,14 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 
+#include <sstream>
+#include <vector>
+
+#include <boost/bind.hpp>
+
 #include <exceptions/exceptions.h>
 
+#include <dns/masterload.h>
 #include <dns/name.h>
 #include <dns/rdata.h>
 #include <dns/rdataclass.h>
@@ -26,6 +33,7 @@
 
 #include <gtest/gtest.h>
 
+using namespace std;
 using namespace isc::dns;
 using namespace isc::dns::rdata;
 using namespace isc::datasrc;
@@ -139,51 +147,86 @@ TEST_F(MemoryDataSrcTest, getZoneCount) {
     EXPECT_EQ(2, memory_datasrc.getZoneCount());
 }
 
+// A helper callback of masterLoad() used in MemoryZoneTest.
+void
+setRRset(RRsetPtr rrset, vector<RRsetPtr*>::iterator& it) {
+    *(*it) = rrset;
+    ++it;
+}
+
 /// \brief Test fixture for the MemoryZone class
 class MemoryZoneTest : public ::testing::Test {
+    // A straightforward pair of textual RR(set) and a RRsetPtr variable
+    // to store the RRset.  Used to build test data below.
+    struct RRsetData {
+        const char* const text; // textual representation of an RRset
+        RRsetPtr* rrset;
+    };
 public:
     MemoryZoneTest() :
         class_(RRClass::IN()),
         origin_("example.org"),
-        ns_name_("ns.example.org"),
-        cname_name_("cname.example.org"),
-        child_ns_name_("child.example.org"),
-        child_glue_name_("ns.child.example.org"),
-        grandchild_ns_name_("grand.child.example.org"),
-        grandchild_glue_name_("ns.grand.child.example.org"),
-        zone_(class_, origin_),
-        rr_out_(new RRset(Name("example.com"), class_, RRType::A(),
-            RRTTL(300))),
-        rr_ns_(new RRset(origin_, class_, RRType::NS(), RRTTL(300))),
-        rr_ns_a_(new RRset(ns_name_, class_, RRType::A(), RRTTL(300))),
-        rr_ns_aaaa_(new RRset(ns_name_, class_, RRType::AAAA(), RRTTL(300))),
-        rr_a_(new RRset(origin_, class_, RRType::A(), RRTTL(300))),
-        rr_cname_(new RRset(cname_name_, class_, RRType::CNAME(), RRTTL(300))),
-        rr_cname_a_(new RRset(cname_name_, class_, RRType::A(), RRTTL(300))),
-        rr_child_ns_(new RRset(child_ns_name_, class_, RRType::NS(),
-                               RRTTL(300))),
-        rr_child_glue_(new RRset(child_glue_name_, class_, RRType::A(),
-                              RRTTL(300))),
-        rr_grandchild_ns_(new RRset(grandchild_ns_name_, class_, RRType::NS(),
-                                    RRTTL(300))),
-        rr_grandchild_glue_(new RRset(grandchild_glue_name_, class_,
-                                      RRType::AAAA(), RRTTL(300)))
+        zone_(class_, origin_)
     {
+        // Build test RRsets.  Below, we construct an RRset for
+        // each textual RR(s) of zone_data, and assign it to the corresponding
+        // rr_xxx.
+        const RRsetData zone_data[] = {
+            {"example.org. 300 IN NS ns.example.org.", &rr_ns_},
+            {"example.org. 300 IN A 192.0.2.1", &rr_a_},
+            {"ns.example.org. 300 IN A 192.0.2.2", &rr_ns_a_},
+            {"ns.example.org. 300 IN AAAA 2001:db8::2", &rr_ns_aaaa_},
+            {"cname.example.org. 300 IN CNAME canonical.example.org",
+             &rr_cname_},
+            {"cname.example.org. 300 IN A 192.0.2.3", &rr_cname_a_},
+            {"dname.example.org. 300 IN DNAME target.example.org.",
+             &rr_dname_},
+            {"dname.example.org. 300 IN A 192.0.2.39", &rr_dname_a_},
+            {"dname.example.org. 300 IN NS ns.dname.example.org.",
+             &rr_dname_ns_},
+            {"example.org. 300 IN DNAME example.com.", &rr_dname_apex_},
+            {"child.example.org. 300 IN NS ns.child.example.org.",
+             &rr_child_ns_},
+            {"ns.child.example.org. 300 IN A 192.0.2.153",
+             &rr_child_glue_},
+            {"grand.child.example.org. 300 IN NS ns.grand.child.example.org.",
+             &rr_grandchild_ns_},
+            {"ns.grand.child.example.org. 300 IN AAAA 2001:db8::253",
+             &rr_grandchild_glue_},
+            {"dname.child.example.org. 300 IN DNAME example.com.",
+             &rr_child_dname_},
+            {"example.com. 300 IN A 192.0.2.10", &rr_out_},
+            {"*.wild.example.org. 300 IN A 192.0.2.1", &rr_wild_},
+            {"wild.*.foo.example.org. 300 IN A 192.0.2.1", &rr_emptywild_},
+            {"wild.*.foo.*.bar.example.org. 300 IN A 192.0.2.1",
+             &rr_nested_emptywild_},
+            {"*.nswild.example.org. 300 IN NS nswild.example.", &rr_nswild_},
+            {"*.dnamewild.example.org. 300 IN DNAME dnamewild.example.",
+             &rr_dnamewild_},
+            {NULL, NULL}
+        };
+
+        stringstream zone_data_stream;
+        vector<RRsetPtr*> rrsets;
+        for (unsigned int i = 0; zone_data[i].text != NULL; ++i) {
+            zone_data_stream << zone_data[i].text << "\n";
+            rrsets.push_back(zone_data[i].rrset);
+        }
+
+        vector<RRsetPtr*>::iterator it = rrsets.begin();
+        masterLoad(zone_data_stream, Name::ROOT_NAME(), class_,
+                   boost::bind(setRRset, _1, it));
     }
     // Some data to test with
     const RRClass class_;
-    const Name origin_, ns_name_, cname_name_, child_ns_name_,
-        child_glue_name_, grandchild_ns_name_, grandchild_glue_name_;
+    const Name origin_;
     // The zone to torture by tests
     MemoryZone zone_;
 
     /*
      * Some RRsets to put inside the zone.
-     * They are empty, but the MemoryZone does not have a reason to look
-     * inside anyway. We will check it finds them and does not change
-     * the pointer.
      */
-    ConstRRsetPtr
+    RRsetPtr
         // Out of zone RRset
         rr_out_,
         // NS of example.org
@@ -195,11 +238,20 @@ public:
         // A of example.org
         rr_a_;
     RRsetPtr rr_cname_;         // CNAME in example.org (RDATA will be added)
-    ConstRRsetPtr rr_cname_a_; // for mixed CNAME + A case
-    ConstRRsetPtr rr_child_ns_; // NS of a child domain (for delegation)
-    ConstRRsetPtr rr_child_glue_; // glue RR of the child domain
-    ConstRRsetPtr rr_grandchild_ns_; // NS below a zone cut (unusual)
-    ConstRRsetPtr rr_grandchild_glue_; // glue RR below a deeper zone cut
+    RRsetPtr rr_cname_a_; // for mixed CNAME + A case
+    RRsetPtr rr_dname_;         // DNAME in example.org (RDATA will be added)
+    RRsetPtr rr_dname_a_; // for mixed DNAME + A case
+    RRsetPtr rr_dname_ns_; // for mixed DNAME + NS case
+    RRsetPtr rr_dname_apex_; // for mixed DNAME + NS case in the apex
+    RRsetPtr rr_child_ns_; // NS of a child domain (for delegation)
+    RRsetPtr rr_child_glue_; // glue RR of the child domain
+    RRsetPtr rr_grandchild_ns_; // NS below a zone cut (unusual)
+    RRsetPtr rr_grandchild_glue_; // glue RR below a deeper zone cut
+    RRsetPtr rr_child_dname_; // A DNAME under NS
+    RRsetPtr rr_wild_;
+    RRsetPtr rr_emptywild_;
+    RRsetPtr rr_nested_emptywild_;
+    RRsetPtr rr_nswild_, rr_dnamewild_;
 
     /**
      * \brief Test one find query to the zone.
@@ -275,33 +327,30 @@ TEST_F(MemoryZoneTest, add) {
 }
 
 TEST_F(MemoryZoneTest, addMultipleCNAMEs) {
-    rr_cname_->addRdata(generic::CNAME("canonical1.example.org."));
     rr_cname_->addRdata(generic::CNAME("canonical2.example.org."));
     EXPECT_THROW(zone_.add(rr_cname_), MemoryZone::AddError);
 }
 
 TEST_F(MemoryZoneTest, addCNAMEThenOther) {
-    rr_cname_->addRdata(generic::CNAME("canonical.example.org."));
     EXPECT_EQ(SUCCESS, zone_.add(rr_cname_));
     EXPECT_THROW(zone_.add(rr_cname_a_), MemoryZone::AddError);
 }
 
 TEST_F(MemoryZoneTest, addOtherThenCNAME) {
-    rr_cname_->addRdata(generic::CNAME("canonical.example.org."));
     EXPECT_EQ(SUCCESS, zone_.add(rr_cname_a_));
     EXPECT_THROW(zone_.add(rr_cname_), MemoryZone::AddError);
 }
 
 TEST_F(MemoryZoneTest, findCNAME) {
     // install CNAME RR
-    rr_cname_->addRdata(generic::CNAME("canonical.example.org."));
     EXPECT_EQ(SUCCESS, zone_.add(rr_cname_));
 
     // Find A RR of the same.  Should match the CNAME
-    findTest(cname_name_, RRType::NS(), Zone::CNAME, true, rr_cname_);
+    findTest(rr_cname_->getName(), RRType::NS(), Zone::CNAME, true, rr_cname_);
 
     // Find the CNAME itself.  Should result in normal SUCCESS
-    findTest(cname_name_, RRType::CNAME(), Zone::SUCCESS, true, rr_cname_);
+    findTest(rr_cname_->getName(), RRType::CNAME(), Zone::SUCCESS, true,
+             rr_cname_);
 }
 
 TEST_F(MemoryZoneTest, findCNAMEUnderZoneCut) {
@@ -318,6 +367,80 @@ TEST_F(MemoryZoneTest, findCNAMEUnderZoneCut) {
              Zone::FIND_GLUE_OK);
 }
 
+// Two DNAMEs at single domain are disallowed by RFC 2672, section 3)
+// Having a CNAME there is disallowed too, but it is tested by
+// addOtherThenCNAME and addCNAMEThenOther.
+TEST_F(MemoryZoneTest, addMultipleDNAMEs) {
+    rr_dname_->addRdata(generic::DNAME("target2.example.org."));
+    EXPECT_THROW(zone_.add(rr_dname_), MemoryZone::AddError);
+}
+
+/*
+ * These two tests ensure that we can't have DNAME and NS at the same
+ * node with the exception of the apex of zone (forbidden by RFC 2672)
+ */
+TEST_F(MemoryZoneTest, addDNAMEThenNS) {
+    EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_.add(rr_dname_)));
+    EXPECT_THROW(zone_.add(rr_dname_ns_), MemoryZone::AddError);
+}
+
+TEST_F(MemoryZoneTest, addNSThenDNAME) {
+    EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_.add(rr_dname_ns_)));
+    EXPECT_THROW(zone_.add(rr_dname_), MemoryZone::AddError);
+}
+
+// It is allowed to have NS and DNAME at apex
+TEST_F(MemoryZoneTest, DNAMEAndNSAtApex) {
+    EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_.add(rr_dname_apex_)));
+    EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_.add(rr_ns_)));
+
+    // The NS should be possible to be found, below should be DNAME, not
+    // delegation
+    findTest(origin_, RRType::NS(), Zone::SUCCESS, true, rr_ns_);
+    findTest(rr_child_ns_->getName(), RRType::A(), Zone::DNAME, true,
+             rr_dname_apex_);
+}
+
+TEST_F(MemoryZoneTest, NSAndDNAMEAtApex) {
+    EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_.add(rr_ns_)));
+    EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_.add(rr_dname_apex_)));
+}
+
+// TODO: Test (and implement) adding data under DNAME. That is forbidden by
+// 2672 as well.
+
+// Search under a DNAME record. It should return the DNAME
+TEST_F(MemoryZoneTest, findBelowDNAME) {
+    EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_.add(rr_dname_)));
+    findTest(Name("below.dname.example.org"), RRType::A(), Zone::DNAME, true,
+        rr_dname_);
+}
+
+// Search at the domain with DNAME. It should act as DNAME isn't there, DNAME
+// influences only the data below (see RFC 2672, section 3)
+TEST_F(MemoryZoneTest, findAtDNAME) {
+    EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_.add(rr_dname_)));
+    EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_.add(rr_dname_a_)));
+
+    const Name dname_name(rr_dname_->getName());
+    findTest(dname_name, RRType::A(), Zone::SUCCESS, true, rr_dname_a_);
+    findTest(dname_name, RRType::DNAME(), Zone::SUCCESS, true, rr_dname_);
+    findTest(dname_name, RRType::TXT(), Zone::NXRRSET, true);
+}
+
+// Try searching something that is both under NS and DNAME, without and with
+// GLUE_OK mode (it should stop at the NS and DNAME respectively).
+TEST_F(MemoryZoneTest, DNAMEUnderNS) {
+    zone_.add(rr_child_ns_);
+    zone_.add(rr_child_dname_);
+
+    Name lowName("below.dname.child.example.org.");
+
+    findTest(lowName, RRType::A(), Zone::DELEGATION, true, rr_child_ns_);
+    findTest(lowName, RRType::A(), Zone::DNAME, true, rr_child_dname_, NULL,
+        NULL, Zone::FIND_GLUE_OK);
+}
+
 // Test adding child zones and zone cut handling
 TEST_F(MemoryZoneTest, delegationNS) {
     // add in-zone data
@@ -366,7 +489,7 @@ TEST_F(MemoryZoneTest, findAny) {
     EXPECT_EQ(0, out_rrsets.size());
 
     RRsetList glue_child_rrsets;
-    findTest(child_glue_name_, RRType::ANY(), Zone::SUCCESS, true,
+    findTest(rr_child_glue_->getName(), RRType::ANY(), Zone::SUCCESS, true,
                 ConstRRsetPtr(), &glue_child_rrsets);
     EXPECT_EQ(rr_child_glue_, glue_child_rrsets.findRRset(RRType::A(),
                                                      RRClass::IN()));
@@ -380,13 +503,13 @@ TEST_F(MemoryZoneTest, findAny) {
 
     // zone cut
     RRsetList child_rrsets;
-    findTest(child_ns_name_, RRType::ANY(), Zone::DELEGATION, true,
+    findTest(rr_child_ns_->getName(), RRType::ANY(), Zone::DELEGATION, true,
              rr_child_ns_, &child_rrsets);
     EXPECT_EQ(0, child_rrsets.size());
 
     // glue for this zone cut
     RRsetList new_glue_child_rrsets;
-    findTest(child_glue_name_, RRType::ANY(), Zone::DELEGATION, true,
+    findTest(rr_child_glue_->getName(), RRType::ANY(), Zone::DELEGATION, true,
                 rr_child_ns_, &new_glue_child_rrsets);
     EXPECT_EQ(0, new_glue_child_rrsets.size());
 }
@@ -403,16 +526,16 @@ TEST_F(MemoryZoneTest, glue) {
     EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_.add(rr_grandchild_glue_)));
 
     // by default glue is hidden due to the zone cut
-    findTest(child_glue_name_, RRType::A(), Zone::DELEGATION, true,
+    findTest(rr_child_glue_->getName(), RRType::A(), Zone::DELEGATION, true,
              rr_child_ns_);
 
 
     // If we do it in the "glue OK" mode, we should find the exact match.
-    findTest(child_glue_name_, RRType::A(), Zone::SUCCESS, true,
+    findTest(rr_child_glue_->getName(), RRType::A(), Zone::SUCCESS, true,
              rr_child_glue_, NULL, NULL, Zone::FIND_GLUE_OK);
 
     // glue OK + NXRRSET case
-    findTest(child_glue_name_, RRType::AAAA(), Zone::NXRRSET, true,
+    findTest(rr_child_glue_->getName(), RRType::AAAA(), Zone::NXRRSET, true,
              ConstRRsetPtr(), NULL, NULL, Zone::FIND_GLUE_OK);
 
     // glue OK + NXDOMAIN case
@@ -425,7 +548,8 @@ TEST_F(MemoryZoneTest, glue) {
     // (This case cannot be tested yet)
 
     // nested cut case.  The glue should be found.
-    findTest(grandchild_glue_name_, RRType::AAAA(), Zone::SUCCESS,
+    findTest(rr_grandchild_glue_->getName(), RRType::AAAA(),
+             Zone::SUCCESS,
              true, rr_grandchild_glue_, NULL, NULL, Zone::FIND_GLUE_OK);
 
     // A non-existent name in nested cut.  This should result in delegation
@@ -435,18 +559,6 @@ TEST_F(MemoryZoneTest, glue) {
              Zone::FIND_GLUE_OK);
 }
 
-// Test adding DNAMEs and resulting delegation handling
-// Listing ideas only for now
-TEST_F(MemoryZoneTest, delegationDNAME) {
-    // apex DNAME: allowed by spec.  No DNAME delegation at the apex;
-    // descendants are subject to delegation.
-
-    // Other cases of NS and DNAME mixture are prohibited.
-    // BIND 9 doesn't reject such cases at load time, however.
-
-    // DNAME and ordinary types (allowed by spec)
-}
-
 /**
  * \brief Test searching.
  *
@@ -464,17 +576,56 @@ TEST_F(MemoryZoneTest, find) {
 
     // These two should be successful
     findTest(origin_, RRType::NS(), Zone::SUCCESS, true, rr_ns_);
-    findTest(ns_name_, RRType::A(), Zone::SUCCESS, true, rr_ns_a_);
+    findTest(rr_ns_a_->getName(), RRType::A(), Zone::SUCCESS, true, rr_ns_a_);
 
     // These domain exist but don't have the provided RRType
     findTest(origin_, RRType::AAAA(), Zone::NXRRSET);
-    findTest(ns_name_, RRType::NS(), Zone::NXRRSET);
+    findTest(rr_ns_a_->getName(), RRType::NS(), Zone::NXRRSET);
 
     // These domains don't exist (and one is out of the zone)
     findTest(Name("nothere.example.org"), RRType::A(), Zone::NXDOMAIN);
     findTest(Name("example.net"), RRType::A(), Zone::NXDOMAIN);
 }
 
+TEST_F(MemoryZoneTest, emptyNode) {
+    /*
+     * The backend RBTree for this test should look like as follows:
+     *          example.org
+     *               |
+     *              baz (empty; easy case)
+     *            /  |  \
+     *          bar  |  x.foo ('foo' part is empty; a bit trickier)
+     *              bbb
+     *             /
+     *           aaa
+     */
+
+    // Construct the test zone
+    const char* const names[] = {
+        "bar.example.org", "x.foo.example.org", "aaa.baz.example.org",
+        "bbb.baz.example.org.", NULL};
+    for (int i = 0; names[i] != NULL; ++i) {
+        ConstRRsetPtr rrset(new RRset(Name(names[i]), class_, RRType::A(),
+                                      RRTTL(300)));
+        EXPECT_EQ(SUCCESS, zone_.add(rrset));
+    }
+
+    // empty node matching, easy case: the node for 'baz' exists with
+    // no data.
+    findTest(Name("baz.example.org"), RRType::A(), Zone::NXRRSET);
+
+    // empty node matching, a trickier case: the node for 'foo' is part of
+    // "x.foo", which should be considered an empty node.
+    findTest(Name("foo.example.org"), RRType::A(), Zone::NXRRSET);
+
+    // "org" is contained in "example.org", but it shouldn't be treated as
+    // NXRRSET because it's out of zone.
+    // Note: basically we don't expect such a query to be performed (the common
+    // operation is to identify the best matching zone first then perform
+    // search it), but we shouldn't be confused even in the unexpected case.
+    findTest(Name("org"), RRType::A(), Zone::NXDOMAIN);
+}
+
 TEST_F(MemoryZoneTest, load) {
     // Put some data inside the zone
     EXPECT_NO_THROW(EXPECT_EQ(result::SUCCESS, zone_.add(rr_ns_)));
@@ -497,14 +648,59 @@ TEST_F(MemoryZoneTest, load) {
     findTest(Name("a.root-servers.net."), RRType::A(), Zone::SUCCESS, false,
         ConstRRsetPtr(), NULL, &rootzone);
     // But this should no longer be here
-    findTest(ns_name_, RRType::AAAA(), Zone::NXDOMAIN, true, ConstRRsetPtr(),
-        NULL, &rootzone);
+    findTest(rr_ns_a_->getName(), RRType::AAAA(), Zone::NXDOMAIN, true,
+             ConstRRsetPtr(), NULL, &rootzone);
 
     // Try loading zone that is wrong in a different way
     EXPECT_THROW(zone_.load(TEST_DATA_DIR "/duplicate_rrset.zone"),
         MasterLoadError);
 }
 
+// Note: once #507 is merged, findTest() would succeed whether or not
+// we load the wildcard correctly, so the test will become meaningless.
+// The plan is to clean them up when we complete #551 (then the effect of
+// load will be indirectly tested via find() tests).
+TEST_F(MemoryZoneTest, loadWildcard) {
+    /*
+     *            example.org.
+     *                 |
+     *                wild (not *.wild, should have wild mark)
+     *                 |
+     *                 *
+     */
+    EXPECT_EQ(SUCCESS, zone_.add(rr_wild_));
+    findTest(Name("wild.example.org"), RRType::A(), Zone::NXRRSET);
+}
+
+// same note as loadWildcard applies.
+TEST_F(MemoryZoneTest, loadEmptyWildcard) {
+    /*
+     *            example.org.
+     *                foo
+     *                 *
+     *               wild
+     */
+    EXPECT_EQ(SUCCESS, zone_.add(rr_emptywild_));
+    findTest(Name("*.foo.example.org"), RRType::A(), Zone::NXRRSET);
+    findTest(Name("foo.example.org"), RRType::A(), Zone::NXRRSET);
+}
+
+// same note as loadWildcard applies.
+TEST_F(MemoryZoneTest, loadNestedEmptyWildcard) {
+    EXPECT_EQ(SUCCESS, zone_.add(rr_nested_emptywild_));
+    findTest(Name("*.foo.*.bar.example.org"), RRType::A(), Zone::NXRRSET);
+    findTest(Name("foo.*.bar.example.org"), RRType::A(), Zone::NXRRSET);
+    findTest(Name("*.bar.example.org"), RRType::A(), Zone::NXRRSET);
+    findTest(Name("bar.example.org"), RRType::A(), Zone::NXRRSET);
+}
+
+TEST_F(MemoryZoneTest, loadBadWildcard) {
+    // We reject loading the zone if it contains a wildcard name for
+    // NS or DNAME.
+    EXPECT_THROW(zone_.add(rr_nswild_), MemoryZone::AddError);
+    EXPECT_THROW(zone_.add(rr_dnamewild_), MemoryZone::AddError);
+}
+
 TEST_F(MemoryZoneTest, swap) {
     // build one zone with some data
     MemoryZone zone1(class_, origin_);

+ 292 - 106
src/lib/datasrc/tests/rbtree_unittest.cc

@@ -12,9 +12,10 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 
-
 #include <gtest/gtest.h>
 
+#include <exceptions/exceptions.h>
+
 #include <dns/name.h>
 #include <dns/rrclass.h>
 #include <dns/rrset.h>
@@ -26,10 +27,15 @@
 #include <dns/tests/unittest_util.h>
 
 using namespace std;
+using namespace isc;
 using namespace isc::dns;
 using isc::UnitTestUtil;
 using namespace isc::datasrc;
 
+// XXX: some compilers cannot find class static constants used in
+// EXPECT_xxx macros, for which we need an explicit empty definition.
+const size_t Name::MAX_LABELS;
+
 /* The initial structure of rbtree
  *
  *             b
@@ -50,9 +56,10 @@ using namespace isc::datasrc;
 namespace {
 class RBTreeTest : public::testing::Test {
 protected:
-    RBTreeTest() : rbtree() {
-        const char * domain_names[] = {"c", "b", "a", "x.d.e.f", "z.d.e.f", "g.h", "i.g.h", 
-            "o.w.y.d.e.f", "j.z.d.e.f", "p.w.y.d.e.f", "q.w.y.d.e.f"};
+    RBTreeTest() : rbtree_expose_empty_node(true) {
+        const char* const domain_names[] = {
+            "c", "b", "a", "x.d.e.f", "z.d.e.f", "g.h", "i.g.h", "o.w.y.d.e.f",
+            "j.z.d.e.f", "p.w.y.d.e.f", "q.w.y.d.e.f"};
         int name_count = sizeof(domain_names) / sizeof(domain_names[0]);
         for (int i = 0; i < name_count; ++i) {
             rbtree.insert(Name(domain_names[i]), &rbtnode);
@@ -65,8 +72,7 @@ protected:
     }
 
     RBTree<int> rbtree;
-    typedef RBTree<int, true> ExposeRBTree;
-    ExposeRBTree rbtree_expose_empty_node;
+    RBTree<int> rbtree_expose_empty_node;
     RBNode<int>* rbtnode;
     const RBNode<int>* crbtnode;
 };
@@ -82,69 +88,34 @@ TEST_F(RBTreeTest, setGetData) {
 }
 
 TEST_F(RBTreeTest, insertNames) {
-    //if don't expose empty node, even the node already exsit which is caused by node fission
-    //we will return succeed
-    EXPECT_EQ(RBTree<int>::SUCCESS, rbtree.insert(Name("d.e.f"), &rbtnode));
+    EXPECT_EQ(RBTree<int>::ALREADYEXISTS, rbtree.insert(Name("d.e.f"),
+                                                        &rbtnode));
     EXPECT_EQ(Name("d.e.f"), rbtnode->getName());
     EXPECT_EQ(13, rbtree.getNodeCount());
 
-    EXPECT_EQ(ExposeRBTree::ALREADYEXISTS,
-            rbtree_expose_empty_node.insert(Name("d.e.f"), &rbtnode));
-    EXPECT_EQ(Name("d.e.f"), rbtnode->getName());
-    EXPECT_EQ(13, rbtree_expose_empty_node.getNodeCount());
-
-
     //insert not exist node
     EXPECT_EQ(RBTree<int>::SUCCESS, rbtree.insert(Name("."), &rbtnode));
     EXPECT_EQ(Name("."), rbtnode->getName());
     EXPECT_EQ(14, rbtree.getNodeCount());
 
-    EXPECT_EQ(ExposeRBTree::SUCCESS, rbtree_expose_empty_node.insert(
-        Name("."), &rbtnode));
-    EXPECT_EQ(Name("."), rbtnode->getName());
-    EXPECT_EQ(14, rbtree_expose_empty_node.getNodeCount());
-
     EXPECT_EQ(RBTree<int>::SUCCESS, rbtree.insert(Name("example.com"), &rbtnode));
     EXPECT_EQ(15, rbtree.getNodeCount());
     rbtnode->setData(RBNode<int>::NodeDataPtr(new int(12)));
 
-    EXPECT_EQ(ExposeRBTree::SUCCESS, rbtree_expose_empty_node.insert(
-        Name("example.com"), &rbtnode));
-    EXPECT_EQ(15, rbtree_expose_empty_node.getNodeCount());
-    rbtnode->setData(RBNode<int>::NodeDataPtr(new int(12)));
-
-
     // return ALREADYEXISTS, since node "example.com" already has been explicitly inserted
     EXPECT_EQ(RBTree<int>::ALREADYEXISTS, rbtree.insert(Name("example.com"), &rbtnode));
     EXPECT_EQ(15, rbtree.getNodeCount());
-    EXPECT_EQ(ExposeRBTree::ALREADYEXISTS,
-        rbtree_expose_empty_node.insert(Name("example.com"), &rbtnode));
-    EXPECT_EQ(15, rbtree_expose_empty_node.getNodeCount());
-
 
     // split the node "d.e.f"
     EXPECT_EQ(RBTree<int>::SUCCESS, rbtree.insert(Name("k.e.f"), &rbtnode));
     EXPECT_EQ(Name("k"), rbtnode->getName());
     EXPECT_EQ(17, rbtree.getNodeCount());
 
-    EXPECT_EQ(ExposeRBTree::SUCCESS,
-        rbtree_expose_empty_node.insert(Name("k.e.f"), &rbtnode));
-    EXPECT_EQ(Name("k"), rbtnode->getName());
-    EXPECT_EQ(17, rbtree_expose_empty_node.getNodeCount());
-
-
     // split the node "g.h"
-    EXPECT_EQ(RBTree<int>::SUCCESS, rbtree.insert(Name("h"), &rbtnode));
+    EXPECT_EQ(RBTree<int>::ALREADYEXISTS, rbtree.insert(Name("h"), &rbtnode));
     EXPECT_EQ(Name("h"), rbtnode->getName());
     EXPECT_EQ(18, rbtree.getNodeCount());
 
-    //node fission will create node "h"
-    EXPECT_EQ(ExposeRBTree::ALREADYEXISTS,
-        rbtree_expose_empty_node.insert(Name("h"), &rbtnode));
-    EXPECT_EQ(Name("h"), rbtnode->getName());
-    EXPECT_EQ(18, rbtree_expose_empty_node.getNodeCount());
-
-
     // add child domain
     EXPECT_EQ(RBTree<int>::SUCCESS, rbtree.insert(Name("m.p.w.y.d.e.f"), &rbtnode));
     EXPECT_EQ(Name("m"), rbtnode->getName());
@@ -153,41 +124,18 @@ TEST_F(RBTreeTest, insertNames) {
     EXPECT_EQ(Name("n"), rbtnode->getName());
     EXPECT_EQ(20, rbtree.getNodeCount());
 
-    EXPECT_EQ(ExposeRBTree::SUCCESS,
-        rbtree_expose_empty_node.insert(Name("m.p.w.y.d.e.f"), &rbtnode));
-    EXPECT_EQ(Name("m"), rbtnode->getName());
-    EXPECT_EQ(19, rbtree_expose_empty_node.getNodeCount());
-    EXPECT_EQ(ExposeRBTree::SUCCESS,
-        rbtree_expose_empty_node.insert(Name("n.p.w.y.d.e.f"), &rbtnode));
-    EXPECT_EQ(Name("n"), rbtnode->getName());
-    EXPECT_EQ(20, rbtree_expose_empty_node.getNodeCount());
-
-
     EXPECT_EQ(RBTree<int>::SUCCESS, rbtree.insert(Name("l.a"), &rbtnode));
     EXPECT_EQ(Name("l"), rbtnode->getName());
     EXPECT_EQ(21, rbtree.getNodeCount());
 
-    EXPECT_EQ(ExposeRBTree::SUCCESS,
-        rbtree_expose_empty_node.insert(Name("l.a"), &rbtnode));
-    EXPECT_EQ(Name("l"), rbtnode->getName());
-    EXPECT_EQ(21, rbtree_expose_empty_node.getNodeCount());
-
     EXPECT_EQ(RBTree<int>::SUCCESS, rbtree.insert(Name("r.d.e.f"), &rbtnode));
     EXPECT_EQ(RBTree<int>::SUCCESS, rbtree.insert(Name("s.d.e.f"), &rbtnode));
     EXPECT_EQ(23, rbtree.getNodeCount());
 
-    EXPECT_EQ(ExposeRBTree::SUCCESS,
-        rbtree_expose_empty_node.insert(Name("r.d.e.f"), &rbtnode));
-    EXPECT_EQ(ExposeRBTree::SUCCESS,
-        rbtree_expose_empty_node.insert(Name("s.d.e.f"), &rbtnode));
-    EXPECT_EQ(23, rbtree_expose_empty_node.getNodeCount());
-
     EXPECT_EQ(RBTree<int>::SUCCESS, rbtree.insert(Name("h.w.y.d.e.f"), &rbtnode));
-    EXPECT_EQ(ExposeRBTree::SUCCESS,
-        rbtree_expose_empty_node.insert(Name("h.w.y.d.e.f"), &rbtnode));
 
     // add more nodes one by one to cover leftRotate and rightRotate
-    EXPECT_EQ(RBTree<int>::SUCCESS, rbtree.insert(Name("f"), &rbtnode));
+    EXPECT_EQ(RBTree<int>::ALREADYEXISTS, rbtree.insert(Name("f"), &rbtnode));
     EXPECT_EQ(RBTree<int>::SUCCESS, rbtree.insert(Name("m"), &rbtnode));
     EXPECT_EQ(RBTree<int>::SUCCESS, rbtree.insert(Name("nm"), &rbtnode));
     EXPECT_EQ(RBTree<int>::SUCCESS, rbtree.insert(Name("om"), &rbtnode));
@@ -198,32 +146,8 @@ TEST_F(RBTreeTest, insertNames) {
     EXPECT_EQ(RBTree<int>::SUCCESS, rbtree.insert(Name("i"), &rbtnode));
     EXPECT_EQ(RBTree<int>::SUCCESS, rbtree.insert(Name("ae"), &rbtnode));
     EXPECT_EQ(RBTree<int>::SUCCESS, rbtree.insert(Name("n"), &rbtnode));
-    
-    EXPECT_EQ(ExposeRBTree::ALREADYEXISTS,
-        rbtree_expose_empty_node.insert(Name("f"), &rbtnode));
-    EXPECT_EQ(ExposeRBTree::SUCCESS,
-        rbtree_expose_empty_node.insert(Name("m"), &rbtnode));
-    EXPECT_EQ(ExposeRBTree::SUCCESS,
-        rbtree_expose_empty_node.insert(Name("nm"), &rbtnode));
-    EXPECT_EQ(ExposeRBTree::SUCCESS,
-        rbtree_expose_empty_node.insert(Name("om"), &rbtnode));
-    EXPECT_EQ(ExposeRBTree::SUCCESS,
-        rbtree_expose_empty_node.insert(Name("k"), &rbtnode));
-    EXPECT_EQ(ExposeRBTree::SUCCESS,
-        rbtree_expose_empty_node.insert(Name("l"), &rbtnode));
-    EXPECT_EQ(ExposeRBTree::SUCCESS,
-        rbtree_expose_empty_node.insert(Name("fe"), &rbtnode));
-    EXPECT_EQ(ExposeRBTree::SUCCESS,
-        rbtree_expose_empty_node.insert(Name("ge"), &rbtnode));
-    EXPECT_EQ(ExposeRBTree::SUCCESS,
-        rbtree_expose_empty_node.insert(Name("i"), &rbtnode));
-    EXPECT_EQ(ExposeRBTree::SUCCESS,
-        rbtree_expose_empty_node.insert(Name("ae"), &rbtnode));
-    EXPECT_EQ(ExposeRBTree::SUCCESS,
-        rbtree_expose_empty_node.insert(Name("n"), &rbtnode));
 }
 
-   
 TEST_F(RBTreeTest, findName) {
     // find const rbtnode
     // exact match
@@ -236,15 +160,57 @@ TEST_F(RBTreeTest, findName) {
     EXPECT_EQ(RBTree<int>::NOTFOUND, rbtree.find(Name("x"), &crbtnode));
     EXPECT_EQ(RBTree<int>::NOTFOUND, rbtree.find(Name("m.n"), &crbtnode));
 
+    // if we expose empty node, we can get the empty node created during insert
+    EXPECT_EQ(RBTree<int>::EXACTMATCH,
+              rbtree_expose_empty_node.find(Name("d.e.f"), &crbtnode));
+    EXPECT_EQ(RBTree<int>::EXACTMATCH,
+              rbtree_expose_empty_node.find(Name("w.y.d.e.f"), &crbtnode));
+
     // partial match
     EXPECT_EQ(RBTree<int>::PARTIALMATCH, rbtree.find(Name("m.b"), &crbtnode));
     EXPECT_EQ(Name("b"), crbtnode->getName());
+    EXPECT_EQ(RBTree<int>::PARTIALMATCH,
+              rbtree_expose_empty_node.find(Name("m.d.e.f"), &crbtnode));
 
     // find rbtnode
     EXPECT_EQ(RBTree<int>::EXACTMATCH, rbtree.find(Name("q.w.y.d.e.f"), &rbtnode));
     EXPECT_EQ(Name("q"), rbtnode->getName());
 }
 
+TEST_F(RBTreeTest, findError) {
+    // For the version that takes a node chain, the chain must be empty.
+    RBTreeNodeChain<int> chain;
+    EXPECT_EQ(RBTree<int>::EXACTMATCH, rbtree.find<void*>(Name("a"), &crbtnode,
+                                                          chain, NULL, NULL));
+    // trying to reuse the same chain.  it should result in an exception.
+    EXPECT_THROW(rbtree.find<void*>(Name("a"), &crbtnode, chain, NULL, NULL),
+                 BadValue);
+}
+
+TEST_F(RBTreeTest, flags) {
+    EXPECT_EQ(RBTree<int>::SUCCESS, rbtree.insert(Name("flags.example"),
+                                                  &rbtnode));
+
+    // by default, flags are all off
+    EXPECT_FALSE(rbtnode->getFlag(RBNode<int>::FLAG_CALLBACK));
+
+    // set operation, by default it enables the flag
+    rbtnode->setFlag(RBNode<int>::FLAG_CALLBACK);
+    EXPECT_TRUE(rbtnode->getFlag(RBNode<int>::FLAG_CALLBACK));
+
+    // try disable the flag explicitly
+    rbtnode->setFlag(RBNode<int>::FLAG_CALLBACK, false);
+    EXPECT_FALSE(rbtnode->getFlag(RBNode<int>::FLAG_CALLBACK));
+
+    // try enable the flag explicitly
+    rbtnode->setFlag(RBNode<int>::FLAG_CALLBACK, true);
+    EXPECT_TRUE(rbtnode->getFlag(RBNode<int>::FLAG_CALLBACK));
+
+    // setting an unknown flag will trigger an exception
+    EXPECT_THROW(rbtnode->setFlag(static_cast<RBNode<int>::Flags>(2), true),
+                 isc::InvalidParameter);
+}
+
 bool
 testCallback(const RBNode<int>&, bool* callack_checker) {
     *callack_checker = true;
@@ -256,50 +222,271 @@ TEST_F(RBTreeTest, callback) {
     EXPECT_EQ(RBTree<int>::SUCCESS, rbtree.insert(Name("callback.example"),
                                                   &rbtnode));
     rbtnode->setData(RBNode<int>::NodeDataPtr(new int(1)));
-    EXPECT_FALSE(rbtnode->isCallbackEnabled());
+    EXPECT_FALSE(rbtnode->getFlag(RBNode<int>::FLAG_CALLBACK));
 
     // enable/re-disable callback
-    rbtnode->enableCallback();
-    EXPECT_TRUE(rbtnode->isCallbackEnabled());
-    rbtnode->disableCallback();
-    EXPECT_FALSE(rbtnode->isCallbackEnabled());
+    rbtnode->setFlag(RBNode<int>::FLAG_CALLBACK);
+    EXPECT_TRUE(rbtnode->getFlag(RBNode<int>::FLAG_CALLBACK));
+    rbtnode->setFlag(RBNode<int>::FLAG_CALLBACK, false);
+    EXPECT_FALSE(rbtnode->getFlag(RBNode<int>::FLAG_CALLBACK));
 
     // enable again for subsequent tests
-    rbtnode->enableCallback();
+    rbtnode->setFlag(RBNode<int>::FLAG_CALLBACK);
     // add more levels below and above the callback node for partial match.
     RBNode<int>* subrbtnode;
     EXPECT_EQ(RBTree<int>::SUCCESS, rbtree.insert(Name("sub.callback.example"),
                                                   &subrbtnode));
     subrbtnode->setData(RBNode<int>::NodeDataPtr(new int(2)));
     RBNode<int>* parentrbtnode;
-    EXPECT_EQ(RBTree<int>::SUCCESS, rbtree.insert(Name("example"),
+    EXPECT_EQ(RBTree<int>::ALREADYEXISTS, rbtree.insert(Name("example"),
                                                        &parentrbtnode));
     //  the chilld/parent nodes shouldn't "inherit" the callback flag.
     // "rbtnode" may be invalid due to the insertion, so we need to re-find
     // it.
     EXPECT_EQ(RBTree<int>::EXACTMATCH, rbtree.find(Name("callback.example"),
                                                    &rbtnode));
-    EXPECT_TRUE(rbtnode->isCallbackEnabled());
-    EXPECT_FALSE(subrbtnode->isCallbackEnabled());
-    EXPECT_FALSE(parentrbtnode->isCallbackEnabled());
+    EXPECT_TRUE(rbtnode->getFlag(RBNode<int>::FLAG_CALLBACK));
+    EXPECT_FALSE(subrbtnode->getFlag(RBNode<int>::FLAG_CALLBACK));
+    EXPECT_FALSE(parentrbtnode->getFlag(RBNode<int>::FLAG_CALLBACK));
 
     // check if the callback is called from find()
+    RBTreeNodeChain<int> node_path1;
     bool callback_called = false;
     EXPECT_EQ(RBTree<int>::EXACTMATCH,
-              rbtree.find(Name("sub.callback.example"), &crbtnode,
+              rbtree.find(Name("sub.callback.example"), &crbtnode, node_path1,
                           testCallback, &callback_called));
     EXPECT_TRUE(callback_called);
 
     // enable callback at the parent node, but it doesn't have data so
     // the callback shouldn't be called.
-    parentrbtnode->enableCallback();
+    RBTreeNodeChain<int> node_path2;
+    parentrbtnode->setFlag(RBNode<int>::FLAG_CALLBACK);
     callback_called = false;
     EXPECT_EQ(RBTree<int>::EXACTMATCH,
-              rbtree.find(Name("callback.example"), &crbtnode,
+              rbtree.find(Name("callback.example"), &crbtnode, node_path2,
                           testCallback, &callback_called));
     EXPECT_FALSE(callback_called);
 }
 
+TEST_F(RBTreeTest, chainLevel) {
+    RBTreeNodeChain<int> chain;
+
+    // by default there should be no level in the chain.
+    EXPECT_EQ(0, chain.getLevelCount());
+
+    // insert one node to the tree and find it.  there should be exactly
+    // one level in the chain.
+    RBTree<int> tree(true);
+    Name node_name(Name::ROOT_NAME());
+    EXPECT_EQ(RBTree<int>::SUCCESS, tree.insert(node_name, &rbtnode));
+    EXPECT_EQ(RBTree<int>::EXACTMATCH,
+              tree.find<void*>(node_name, &crbtnode, chain, NULL, NULL));
+    EXPECT_EQ(1, chain.getLevelCount());
+
+    /*
+     * Now creating a possibly deepest tree with MAX_LABELS - 1 levels.
+     * it should look like:
+     *            a
+     *           /|
+     *         (.)a
+     *            |
+     *            a
+     *            : (MAX_LABELS - 1) "a"'s
+     *
+     * then confirm that find() for the deepest name succeeds without any
+     * disruption, and the resulting chain has the expected level.
+     * Note that "a." and the root name (".") belong to the same level.
+     * So the possible maximum level is MAX_LABELS - 1, not MAX_LABELS.
+     */
+    for (unsigned int i = 1; i < Name::MAX_LABELS; ++i) {
+        node_name = Name("a.").concatenate(node_name);
+        EXPECT_EQ(RBTree<int>::SUCCESS, tree.insert(node_name, &rbtnode));
+        RBTreeNodeChain<int> found_chain;
+        EXPECT_EQ(RBTree<int>::EXACTMATCH,
+                  tree.find<void*>(node_name, &crbtnode, found_chain,
+                                   NULL, NULL));
+        EXPECT_EQ(i, found_chain.getLevelCount());
+    }
+
+    // Confirm the last inserted name has the possible maximum length with
+    // maximum label count.  This confirms the rbtree and chain level cannot
+    // be larger.
+    EXPECT_EQ(Name::MAX_LABELS, node_name.getLabelCount());
+    EXPECT_THROW(node_name.concatenate(Name("a.")), TooLongName);
+}
+
+TEST_F(RBTreeTest, getAbsoluteNameError) {
+    // an empty chain isn't allowed.
+    RBTreeNodeChain<int> chain;
+    EXPECT_THROW(chain.getAbsoluteName(), BadValue);
+}
+
+/*
+ *the domain order should be:
+ * a, b, c, d.e.f, x.d.e.f, w.y.d.e.f, o.w.y.d.e.f, p.w.y.d.e.f, q.w.y.d.e.f,
+ * z.d.e.f, j.z.d.e.f, g.h, i.g.h
+ *             b
+ *           /   \
+ *          a    d.e.f
+ *              /  |   \
+ *             c   |    g.h
+ *                 |     |
+ *                w.y    i
+ *              /  |  \
+ *             x   |   z
+ *                 |   |
+ *                 p   j
+ *               /   \
+ *              o     q
+ */
+TEST_F(RBTreeTest, nextNode) {
+    const char* const names[] = {
+        "a", "b", "c", "d.e.f", "x.d.e.f", "w.y.d.e.f", "o.w.y.d.e.f",
+        "p.w.y.d.e.f", "q.w.y.d.e.f", "z.d.e.f", "j.z.d.e.f", "g.h", "i.g.h"};
+    const int name_count = sizeof(names) / sizeof(names[0]);
+    RBTreeNodeChain<int> node_path;
+    const RBNode<int>* node = NULL;
+    EXPECT_EQ(RBTree<int>::EXACTMATCH,
+              rbtree.find<void*>(Name(names[0]), &node, node_path, NULL,
+                                 NULL));
+    for (int i = 0; i < name_count; ++i) {
+        EXPECT_NE(static_cast<void*>(NULL), node);
+        EXPECT_EQ(Name(names[i]), node_path.getAbsoluteName());
+        node = rbtree.nextNode(node_path);
+    }
+
+    // We should have reached the end of the tree.
+    EXPECT_EQ(static_cast<void*>(NULL), node);
+}
+
+TEST_F(RBTreeTest, nextNodeError) {
+    // Empty chain for nextNode() is invalid.
+    RBTreeNodeChain<int> chain;
+    EXPECT_THROW(rbtree.nextNode(chain), BadValue);
+}
+
+// A helper function for getLastComparedNode() below.
+void
+comparisonChecks(const RBTreeNodeChain<int>& chain,
+                 int expected_order, int expected_common_labels,
+                 NameComparisonResult::NameRelation expected_reln)
+{
+    if (expected_order > 0) {
+        EXPECT_LT(0, chain.getLastComparisonResult().getOrder());
+    } else if (expected_order < 0) {
+        EXPECT_GT(0, chain.getLastComparisonResult().getOrder());
+    } else {
+        EXPECT_EQ(0, chain.getLastComparisonResult().getOrder());
+    }
+    EXPECT_EQ(expected_common_labels,
+              chain.getLastComparisonResult().getCommonLabels());
+    EXPECT_EQ(expected_reln,
+              chain.getLastComparisonResult().getRelation());
+}
+
+TEST_F(RBTreeTest, getLastComparedNode) {
+    RBTree<int>& tree = rbtree_expose_empty_node; // use the "empty OK" mode
+    RBTreeNodeChain<int> chain;
+
+    // initially there should be no 'last compared'.
+    EXPECT_EQ(static_cast<void*>(NULL), chain.getLastComparedNode());
+
+    // A search for an empty tree should result in no 'last compared', too.
+    RBTree<int> empty_tree;
+    EXPECT_EQ(RBTree<int>::NOTFOUND,
+              empty_tree.find<void*>(Name("a"), &crbtnode, chain, NULL, NULL));
+    EXPECT_EQ(static_cast<void*>(NULL), chain.getLastComparedNode());
+    chain.clear();
+
+    const RBNode<int>* expected_node;
+
+    // Exact match case.  The returned node should be last compared.
+    EXPECT_EQ(RBTree<int>::EXACTMATCH,
+              tree.find<void*>(Name("x.d.e.f"), &expected_node, chain,
+                               NULL, NULL));
+    EXPECT_EQ(expected_node, chain.getLastComparedNode());
+    // 2 = # labels of "x."
+    comparisonChecks(chain, 0, 2, NameComparisonResult::EQUAL);
+    chain.clear();
+
+    // Partial match, search stopped at the matching node, which should be
+    // the last compared node.
+    EXPECT_EQ(RBTree<int>::EXACTMATCH,
+              tree.find(Name("i.g.h"), &expected_node));
+    EXPECT_EQ(RBTree<int>::PARTIALMATCH,
+              tree.find<void*>(Name("x.i.g.h"), &crbtnode, chain,
+                                 NULL, NULL));
+    EXPECT_EQ(expected_node, chain.getLastComparedNode());
+    // i.g.h < x.i.g.h, 2 = # labels of "i."
+    comparisonChecks(chain, 1, 2, NameComparisonResult::SUBDOMAIN);
+    chain.clear();
+
+    // Partial match, search stopped in the subtree below the matching node
+    // after following a left branch.
+    EXPECT_EQ(RBTree<int>::EXACTMATCH,
+              tree.find(Name("x.d.e.f"), &expected_node));
+    EXPECT_EQ(RBTree<int>::PARTIALMATCH,
+              tree.find<void*>(Name("a.d.e.f"), &crbtnode, chain,
+                                 NULL, NULL));
+    EXPECT_EQ(expected_node, chain.getLastComparedNode());
+    // a < x, 1 = # labels of "." (trailing dot)
+    comparisonChecks(chain, -1, 1, NameComparisonResult::COMMONANCESTOR);
+    chain.clear();
+
+    // Partial match, search stopped in the subtree below the matching node
+    // after following a right branch.
+    EXPECT_EQ(RBTree<int>::EXACTMATCH,
+              tree.find(Name("z.d.e.f"), &expected_node));
+    EXPECT_EQ(RBTree<int>::PARTIALMATCH,
+              tree.find<void*>(Name("zz.d.e.f"), &crbtnode, chain,
+                                 NULL, NULL));
+    EXPECT_EQ(expected_node, chain.getLastComparedNode());
+    // zz > z, 1 = # labels of "." (trailing dot)
+    comparisonChecks(chain, 1, 1, NameComparisonResult::COMMONANCESTOR);
+    chain.clear();
+
+    // Partial match, search stopped at a node for a super domain of the
+    // search name in the subtree below the matching node.
+    EXPECT_EQ(RBTree<int>::EXACTMATCH,
+              tree.find(Name("w.y.d.e.f"), &expected_node));
+    EXPECT_EQ(RBTree<int>::PARTIALMATCH,
+              tree.find<void*>(Name("y.d.e.f"), &crbtnode, chain,
+                                 NULL, NULL));
+    EXPECT_EQ(expected_node, chain.getLastComparedNode());
+    // y < w.y, 2 = # labels of "y."
+    comparisonChecks(chain, -1, 2, NameComparisonResult::SUPERDOMAIN);
+    chain.clear();
+
+    // Partial match, search stopped at a node that share a common ancestor
+    // with the search name in the subtree below the matching node.
+    // (the expected node is the same as the previous case)
+    EXPECT_EQ(RBTree<int>::PARTIALMATCH,
+              tree.find<void*>(Name("z.y.d.e.f"), &crbtnode, chain,
+                                 NULL, NULL));
+    EXPECT_EQ(expected_node, chain.getLastComparedNode());
+    // z.y > w.y, 2 = # labels of "y."
+    comparisonChecks(chain, 1, 2, NameComparisonResult::COMMONANCESTOR);
+    chain.clear();
+
+    // Search stops in the highest level after following a left branch.
+    EXPECT_EQ(RBTree<int>::EXACTMATCH, tree.find(Name("c"), &expected_node));
+    EXPECT_EQ(RBTree<int>::NOTFOUND,
+              tree.find<void*>(Name("bb"), &crbtnode, chain, NULL, NULL));
+    EXPECT_EQ(expected_node, chain.getLastComparedNode());
+    // bb < c, 1 = # labels of "." (trailing dot)
+    comparisonChecks(chain, -1, 1, NameComparisonResult::COMMONANCESTOR);
+    chain.clear();
+
+    // Search stops in the highest level after following a right branch.
+    // (the expected node is the same as the previous case)
+    EXPECT_EQ(RBTree<int>::NOTFOUND,
+              tree.find<void*>(Name("d"), &crbtnode, chain, NULL, NULL));
+    EXPECT_EQ(expected_node, chain.getLastComparedNode());
+    // d > c, 1 = # labels of "." (trailing dot)
+    comparisonChecks(chain, 1, 1, NameComparisonResult::COMMONANCESTOR);
+    chain.clear();
+}
+
 TEST_F(RBTreeTest, dumpTree) {
     std::ostringstream str;
     std::ostringstream str2;
@@ -336,5 +523,4 @@ TEST_F(RBTreeTest, swap) {
     tree2.dumpTree(out);
     ASSERT_EQ(str1.str(), out.str());
 }
-
 }

+ 59 - 0
src/lib/dns/message.cc

@@ -309,6 +309,44 @@ Message::hasRRset(const Section section, const Name& name,
     return (false);
 }
 
+bool
+Message::hasRRset(const Section section, const RRsetPtr& rrset) {
+    return (hasRRset(section, rrset->getName(), rrset->getClass(), rrset->getType()));
+}
+
+bool
+Message::removeRRset(const Section section, RRsetIterator& iterator) {
+    if (section >= MessageImpl::NUM_SECTIONS) {
+        isc_throw(OutOfRange, "Invalid message section: " << section);
+    }
+
+    bool removed = false;
+    for (vector<RRsetPtr>::iterator i = impl_->rrsets_[section].begin();
+            i != impl_->rrsets_[section].end(); ++i) {
+        if (((*i)->getName() == (*iterator)->getName()) &&
+            ((*i)->getClass() == (*iterator)->getClass()) &&
+            ((*i)->getType() == (*iterator)->getType())) {
+
+            // Found the matching RRset so remove it & ignore rest
+            impl_->counts_[section] -= (*iterator)->getRdataCount();
+            impl_->rrsets_[section].erase(i);
+            removed = true;
+            break;
+        }
+    }
+
+    return (removed);
+}
+
+void
+Message::clearSection(const Section section) {
+    if (section >= MessageImpl::NUM_SECTIONS) {
+        isc_throw(OutOfRange, "Invalid message section: " << section);
+    }
+    impl_->rrsets_[section].clear();
+    impl_->counts_[section] = 0;
+}
+
 void
 Message::addQuestion(const QuestionPtr question) {
     if (impl_->mode_ != Message::RENDER) {
@@ -739,6 +777,27 @@ Message::clear(Mode mode) {
 }
 
 void
+Message::appendSection(const Section section, const Message& source) {
+    if (section >= MessageImpl::NUM_SECTIONS) {
+        isc_throw(OutOfRange, "Invalid message section: " << section);
+    }
+
+    if (section == SECTION_QUESTION) {
+        for (QuestionIterator qi = source.beginQuestion();
+             qi != source.endQuestion();
+             ++qi) {
+            addQuestion(*qi);
+        }
+    } else {
+        for (RRsetIterator rrsi = source.beginSection(section);
+             rrsi != source.endSection(section);
+             ++rrsi) {
+            addRRset(section, *rrsi);
+        }
+    }
+}
+
+void
 Message::makeResponse() {
     if (impl_->mode_ != Message::PARSE) {
         isc_throw(InvalidMessageOperation,

+ 35 - 1
src/lib/dns/message.h

@@ -460,9 +460,36 @@ public:
     bool hasRRset(const Section section, const Name& name,
                   const RRClass& rrclass, const RRType& rrtype);
 
+    /// \brief Determine whether the given section already has an RRset
+    /// matching the one pointed to by the argumet
+    ///
+    /// \c section must be a valid constant of the \c Section type;
+    /// otherwise, an exception of class \c OutOfRange will be thrown.
+    bool hasRRset(const Section section, const RRsetPtr& rrset);
+
+    /// \brief Remove RRSet from Message
+    ///
+    /// Removes the RRset identified by the section iterator from the message.
+    /// Note: if,.for some reason, the RRset is duplicated in the section, only
+    /// one occurrence is removed.
+    ///
+    /// If the operation is successful, all iterators into the section are
+    /// invalidated.
+    ///
+    /// \param section Section to which the iterator belongs
+    /// \param iterator Iterator pointing to the element to be removed
+    ///
+    /// \return true if the element was removed, false if the iterator was not
+    /// found in the specified section.
+    bool removeRRset(const Section section, RRsetIterator& iterator);
+
+    /// \brief Remove all RRSets from the given Section
+    ///
+    /// \param section Section to remove all rrsets from
+    void clearSection(const Section section);
+
     // The following methods are not currently implemented.
     //void removeQuestion(QuestionPtr question);
-    //void removeRRset(const Section section, RRsetPtr rrset);
     // notyet:
     //void addRR(const Section section, const RR& rr);
     //void removeRR(const Section section, const RR& rr);
@@ -471,6 +498,13 @@ public:
     /// specified mode.
     void clear(Mode mode);
 
+    /// \brief Adds all rrsets from the source the given section in the
+    /// source message to the same section of this message
+    ///
+    /// \param section the section to append
+    /// \param target The source Message
+    void appendSection(const Section section, const Message& source);
+
     /// \brief Prepare for making a response from a request.
     ///
     /// This will clear the DNS header except those fields that should be kept

+ 171 - 0
src/lib/dns/tests/message_unittest.cc

@@ -250,8 +250,122 @@ TEST_F(MessageTest, hasRRset) {
     EXPECT_THROW(message_render.hasRRset(bogus_section, test_name,
                                          RRClass::IN(), RRType::A()),
                  OutOfRange);
+
+    // Repeat the checks having created an RRset of the appropriate type.
+
+    RRsetPtr rrs1(new RRset(test_name, RRClass::IN(), RRType::A(), RRTTL(60)));
+    EXPECT_TRUE(message_render.hasRRset(Message::SECTION_ANSWER, rrs1));
+    EXPECT_FALSE(message_render.hasRRset(Message::SECTION_ADDITIONAL, rrs1));
+
+    RRsetPtr rrs2(new RRset(Name("nomatch.example"), RRClass::IN(), RRType::A(),
+        RRTTL(5)));
+    EXPECT_FALSE(message_render.hasRRset(Message::SECTION_ANSWER, rrs2));
+
+    RRsetPtr rrs3(new RRset(test_name, RRClass::CH(), RRType::A(), RRTTL(60)));
+    EXPECT_FALSE(message_render.hasRRset(Message::SECTION_ANSWER, rrs3));
+
+    RRsetPtr rrs4(new RRset(test_name, RRClass::IN(), RRType::AAAA(), RRTTL(5)));
+    EXPECT_FALSE(message_render.hasRRset(Message::SECTION_ANSWER, rrs4));
+
+    RRsetPtr rrs5(new RRset(test_name, RRClass::IN(), RRType::AAAA(), RRTTL(5)));
+    EXPECT_FALSE(message_render.hasRRset(Message::SECTION_ANSWER, rrs4));
+
+    EXPECT_THROW(message_render.hasRRset(bogus_section, rrs1), OutOfRange);
+}
+
+TEST_F(MessageTest, removeRRset) {
+    message_render.addRRset(Message::SECTION_ANSWER, rrset_a);
+    message_render.addRRset(Message::SECTION_ANSWER, rrset_aaaa);
+    EXPECT_TRUE(message_render.hasRRset(Message::SECTION_ANSWER, test_name,
+        RRClass::IN(), RRType::A()));
+    EXPECT_TRUE(message_render.hasRRset(Message::SECTION_ANSWER, test_name,
+        RRClass::IN(), RRType::AAAA()));
+    EXPECT_EQ(3, message_render.getRRCount(Message::SECTION_ANSWER));
+
+    // Locate the AAAA RRset and remove it; this has one RR in it.
+    RRsetIterator i = message_render.beginSection(Message::SECTION_ANSWER);
+    if ((*i)->getType() == RRType::A()) {
+        ++i;
+    }
+    EXPECT_EQ(RRType::AAAA(), (*i)->getType());
+    message_render.removeRRset(Message::SECTION_ANSWER, i);
+
+    EXPECT_TRUE(message_render.hasRRset(Message::SECTION_ANSWER, test_name,
+        RRClass::IN(), RRType::A()));
+    EXPECT_FALSE(message_render.hasRRset(Message::SECTION_ANSWER, test_name,
+        RRClass::IN(), RRType::AAAA()));
+    EXPECT_EQ(2, message_render.getRRCount(Message::SECTION_ANSWER));
+}
+
+TEST_F(MessageTest, clearQuestionSection) {
+    QuestionPtr q(new Question(Name("www.example.com"), RRClass::IN(),
+                               RRType::A()));
+    message_render.addQuestion(q);
+    ASSERT_EQ(1, message_render.getRRCount(Message::SECTION_QUESTION));
+
+    message_render.clearSection(Message::SECTION_QUESTION);
+    EXPECT_EQ(0, message_render.getRRCount(Message::SECTION_QUESTION));
+}
+
+
+TEST_F(MessageTest, clearAnswerSection) {
+    // Add two RRsets, check they are present, clear the section,
+    // check if they are gone.
+    message_render.addRRset(Message::SECTION_ANSWER, rrset_a);
+    message_render.addRRset(Message::SECTION_ANSWER, rrset_aaaa);
+    ASSERT_TRUE(message_render.hasRRset(Message::SECTION_ANSWER, test_name,
+        RRClass::IN(), RRType::A()));
+    ASSERT_TRUE(message_render.hasRRset(Message::SECTION_ANSWER, test_name,
+        RRClass::IN(), RRType::AAAA()));
+    ASSERT_EQ(3, message_render.getRRCount(Message::SECTION_ANSWER));
+
+    message_render.clearSection(Message::SECTION_ANSWER);
+    EXPECT_FALSE(message_render.hasRRset(Message::SECTION_ANSWER, test_name,
+        RRClass::IN(), RRType::A()));
+    EXPECT_FALSE(message_render.hasRRset(Message::SECTION_ANSWER, test_name,
+        RRClass::IN(), RRType::AAAA()));
+    EXPECT_EQ(0, message_render.getRRCount(Message::SECTION_ANSWER));
 }
 
+TEST_F(MessageTest, clearAuthoritySection) {
+    // Add two RRsets, check they are present, clear the section,
+    // check if they are gone.
+    message_render.addRRset(Message::SECTION_AUTHORITY, rrset_a);
+    message_render.addRRset(Message::SECTION_AUTHORITY, rrset_aaaa);
+    ASSERT_TRUE(message_render.hasRRset(Message::SECTION_AUTHORITY, test_name,
+        RRClass::IN(), RRType::A()));
+    ASSERT_TRUE(message_render.hasRRset(Message::SECTION_AUTHORITY, test_name,
+        RRClass::IN(), RRType::AAAA()));
+    ASSERT_EQ(3, message_render.getRRCount(Message::SECTION_AUTHORITY));
+
+    message_render.clearSection(Message::SECTION_AUTHORITY);
+    EXPECT_FALSE(message_render.hasRRset(Message::SECTION_AUTHORITY, test_name,
+        RRClass::IN(), RRType::A()));
+    EXPECT_FALSE(message_render.hasRRset(Message::SECTION_AUTHORITY, test_name,
+        RRClass::IN(), RRType::AAAA()));
+    EXPECT_EQ(0, message_render.getRRCount(Message::SECTION_AUTHORITY));
+}
+
+TEST_F(MessageTest, clearAdditionalSection) {
+    // Add two RRsets, check they are present, clear the section,
+    // check if they are gone.
+    message_render.addRRset(Message::SECTION_ADDITIONAL, rrset_a);
+    message_render.addRRset(Message::SECTION_ADDITIONAL, rrset_aaaa);
+    ASSERT_TRUE(message_render.hasRRset(Message::SECTION_ADDITIONAL, test_name,
+        RRClass::IN(), RRType::A()));
+    ASSERT_TRUE(message_render.hasRRset(Message::SECTION_ADDITIONAL, test_name,
+        RRClass::IN(), RRType::AAAA()));
+    ASSERT_EQ(3, message_render.getRRCount(Message::SECTION_ADDITIONAL));
+
+    message_render.clearSection(Message::SECTION_ADDITIONAL);
+    EXPECT_FALSE(message_render.hasRRset(Message::SECTION_ADDITIONAL, test_name,
+        RRClass::IN(), RRType::A()));
+    EXPECT_FALSE(message_render.hasRRset(Message::SECTION_ADDITIONAL, test_name,
+        RRClass::IN(), RRType::AAAA()));
+    EXPECT_EQ(0, message_render.getRRCount(Message::SECTION_ADDITIONAL));
+}
+
+
 TEST_F(MessageTest, badBeginSection) {
     // valid cases are tested via other tests
     EXPECT_THROW(message_render.beginSection(Message::SECTION_QUESTION),
@@ -266,6 +380,63 @@ TEST_F(MessageTest, badEndSection) {
     EXPECT_THROW(message_render.endSection(bogus_section), OutOfRange);
 }
 
+TEST_F(MessageTest, appendSection) {
+    Message target(Message::RENDER);
+
+    // Section check
+    EXPECT_THROW(target.appendSection(bogus_section, message_render),
+                 OutOfRange);
+
+    // Make sure nothing is copied if there is nothing to copy
+    target.appendSection(Message::SECTION_QUESTION, message_render);
+    EXPECT_EQ(0, target.getRRCount(Message::SECTION_QUESTION));
+    target.appendSection(Message::SECTION_ANSWER, message_render);
+    EXPECT_EQ(0, target.getRRCount(Message::SECTION_ANSWER));
+    target.appendSection(Message::SECTION_AUTHORITY, message_render);
+    EXPECT_EQ(0, target.getRRCount(Message::SECTION_AUTHORITY));
+    target.appendSection(Message::SECTION_ADDITIONAL, message_render);
+    EXPECT_EQ(0, target.getRRCount(Message::SECTION_ADDITIONAL));
+
+    // Now add some data, copy again, and see if it got added
+    message_render.addQuestion(Question(Name("test.example.com"),
+                                        RRClass::IN(), RRType::A()));
+    message_render.addRRset(Message::SECTION_ANSWER, rrset_a);
+    message_render.addRRset(Message::SECTION_AUTHORITY, rrset_a);
+    message_render.addRRset(Message::SECTION_ADDITIONAL, rrset_a);
+    message_render.addRRset(Message::SECTION_ADDITIONAL, rrset_aaaa);
+
+    target.appendSection(Message::SECTION_QUESTION, message_render);
+    EXPECT_EQ(1, target.getRRCount(Message::SECTION_QUESTION));
+
+    target.appendSection(Message::SECTION_ANSWER, message_render);
+    EXPECT_EQ(2, target.getRRCount(Message::SECTION_ANSWER));
+    EXPECT_TRUE(target.hasRRset(Message::SECTION_ANSWER, test_name,
+        RRClass::IN(), RRType::A()));
+
+    target.appendSection(Message::SECTION_AUTHORITY, message_render);
+    EXPECT_EQ(2, target.getRRCount(Message::SECTION_AUTHORITY));
+    EXPECT_TRUE(target.hasRRset(Message::SECTION_AUTHORITY, test_name,
+        RRClass::IN(), RRType::A()));
+
+    target.appendSection(Message::SECTION_ADDITIONAL, message_render);
+    EXPECT_EQ(3, target.getRRCount(Message::SECTION_ADDITIONAL));
+    EXPECT_TRUE(target.hasRRset(Message::SECTION_ADDITIONAL, test_name,
+        RRClass::IN(), RRType::A()));
+    EXPECT_TRUE(target.hasRRset(Message::SECTION_ADDITIONAL, test_name,
+        RRClass::IN(), RRType::AAAA()));
+
+    // One more test, test to see if the section gets added, not replaced
+    Message source2(Message::RENDER);
+    source2.addRRset(Message::SECTION_ANSWER, rrset_aaaa);
+    target.appendSection(Message::SECTION_ANSWER, source2);
+    EXPECT_EQ(3, target.getRRCount(Message::SECTION_ANSWER));
+    EXPECT_TRUE(target.hasRRset(Message::SECTION_ANSWER, test_name,
+        RRClass::IN(), RRType::A()));
+    EXPECT_TRUE(target.hasRRset(Message::SECTION_ANSWER, test_name,
+        RRClass::IN(), RRType::AAAA()));
+    
+}
+
 TEST_F(MessageTest, fromWire) {
     factoryFromFile(message_parse, "message_fromWire1");
     EXPECT_EQ(0x1035, message_parse.getQid());

+ 41 - 2
src/lib/log/Makefile.am

@@ -1,4 +1,43 @@
-AM_CXXFLAGS = $(B10_CXXFLAGS)
+if USE_LOG4CXX
+SUBDIRS = . compiler tests
+endif
+
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+AM_CPPFLAGS += $(LOG4CXX_INCLUDES)
+AM_CPPFLAGS += -I$(top_srcdir)/src/lib/log -I$(top_builddir)/src/lib/log
+
+CLEANFILES = *.gcno *.gcda
 
 lib_LTLIBRARIES = liblog.la
-liblog_la_SOURCES = dummylog.cc dummylog.h
+liblog_la_SOURCES  =
+liblog_la_SOURCES += dbglevels.h
+liblog_la_SOURCES += dummylog.h dummylog.cc
+if USE_LOG4CXX
+liblog_la_SOURCES += filename.h filename.cc
+liblog_la_SOURCES += logger.cc logger.h
+liblog_la_SOURCES += logger_support.cc logger_support.h
+liblog_la_SOURCES += messagedef.cc messagedef.h
+liblog_la_SOURCES += message_dictionary.cc message_dictionary.h
+liblog_la_SOURCES += message_exception.h message_exception.cc
+liblog_la_SOURCES += message_initializer.cc message_initializer.h
+liblog_la_SOURCES += message_reader.cc message_reader.h
+liblog_la_SOURCES += message_types.h
+liblog_la_SOURCES += root_logger_name.cc root_logger_name.h
+liblog_la_SOURCES += strutil.h strutil.cc
+liblog_la_SOURCES += xdebuglevel.cc xdebuglevel.h
+
+liblog_la_LDFLAGS = $(LOG4CXX_LDFLAGS)
+endif
+
+# Note: the ordering matters: -Wno-... must follow -Wextra (defined in
+# B10_CXXFLAGS)
+liblog_la_CXXFLAGS = $(AM_CXXFLAGS)
+if USE_GXX
+liblog_la_CXXFLAGS += -Wno-unused-parameter
+endif
+if USE_CLANGPP
+# Same for clang++, but we need to turn off -Werror completely.
+liblog_la_CXXFLAGS += -Wno-error
+endif
+liblog_la_CPPFLAGS = $(AM_CPPFLAGS)

+ 20 - 0
src/lib/log/compiler/Makefile.am

@@ -0,0 +1,20 @@
+SUBDIRS = .
+
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
+AM_CPPFLAGS += -I$(top_srcdir)/src/lib/log -I$(top_builddir)/src/lib/log
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+
+AM_CXXFLAGS = $(B10_CXXFLAGS)
+
+if USE_STATIC_LINK
+AM_LDFLAGS = -static
+endif
+
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+
+CLEANFILES = *.gcno *.gcda
+
+pkglibexec_PROGRAMS = message
+message_SOURCES = message.cc
+message_LDADD  = $(top_builddir)/src/lib/log/liblog.la
+

+ 450 - 0
src/lib/log/compiler/message.cc

@@ -0,0 +1,450 @@
+// Copyright (C) 2010  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.
+
+// $Id$
+
+#include <cctype>
+#include <fstream>
+#include <iostream>
+#include <string>
+#include <vector>
+
+#include <errno.h>
+#include <getopt.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <log/filename.h>
+#include <log/message_dictionary.h>
+#include <log/message_exception.h>
+#include <log/message_reader.h>
+#include <log/messagedef.h>
+#include <log/strutil.h>
+
+#include <log/logger.h>
+
+using namespace std;
+using namespace isc::log;
+
+static const char* VERSION = "1.0-0";
+
+/// \brief Message Compiler
+///
+/// \b Overview<BR>
+/// This is the program that takes as input a message file and produces:
+///
+/// \li A .h file containing message definition
+/// \li A .cc file containing code that adds the messages to the program's
+/// message disctionary at start-up time.
+///
+/// Alternatively, the program can produce a .py file that contains the
+/// message definitions.
+///
+
+/// \b Invocation<BR>
+/// The program is invoked with the command:
+///
+/// <tt>message [-p] \<message-file\></tt>
+///
+/// It reads the message file and writes out two files of the same name but with
+/// extensions of .h and .cc.
+///
+/// If \c -p is specified, the C++ files are not written; instead a Python file
+/// of the same name (but with the file extension .py) is written.
+
+
+/// \brief Print Version
+///
+/// Prints the program's version number.
+
+static void version() {
+    cout << VERSION << "\n";
+}
+
+/// \brief Print Usage
+///
+/// Prints program usage to stdout.
+
+static void usage() {
+    cout <<
+        "Usage: message [-h] [-p] [-v] <message-file>\n" <<
+        "\n" <<
+        "-h       Print this message and exit\n" <<
+        "-p       Output a Python module holding the message definitions.\n" <<
+        "         By default a C++ header file and implementation file are\n" <<
+
+
+        "         written.\n" <<
+        "-v       Print the program version and exit\n" <<
+        "\n" <<
+        "<message-file> is the name of the input message file.\n";
+}
+
+
+/// \brief Create Time
+///
+/// Returns the current time as a suitably-formatted string.
+///
+/// \return Current time
+
+static string currentTime() {
+
+    // Get the current time.
+    time_t curtime;
+    time(&curtime);
+
+    // Format it
+    char buffer[32];
+    ctime_r(&curtime, buffer);
+
+    // Convert to string and strip out the trailing newline
+    string current_time = buffer;
+    return isc::strutil::trim(current_time);
+}
+
+
+
+
+/// \brief Create Header Sentinel
+///
+/// Given the name of a file, create an #ifdef sentinel name.  The name is
+/// __<name>_<ext>, where <name> is the name of the file, and <ext> is the
+/// extension less the leading period.  The sentinel will be upper-case.
+///
+/// \param file Filename object representing the file.
+///
+/// \return Sentinel name
+
+static string sentinel(Filename& file) {
+
+    string name = file.name();
+    string ext = file.extension();
+    string sentinel_text = "__" + name + "_" + ext.substr(1);
+    isc::strutil::uppercase(sentinel_text);
+    return sentinel_text;
+}
+
+
+/// \brief Quote String
+///
+/// Inserts an escape character (a backslash) prior to any double quote
+/// characters.  This is used to handle the fact that the input file does not
+/// contain quotes, yet the string will be included in a C++ literal string.
+
+string quoteString(const string& instring) {
+
+    // Create the output string and reserve the space needed to hold the input
+    // string. (Most input strings will not contain quotes, so this single
+    // reservation should be all that is needed.) 
+    string outstring;
+    outstring.reserve(instring.size());
+
+    // Iterate through the input string, preceding quotes with a slash.
+    for (size_t i = 0; i < instring.size(); ++i) {
+        if (instring[i] == '"') {
+            outstring += '\\';
+        }
+        outstring += instring[i];
+    }
+
+    return outstring;
+}
+
+
+/// \brief Sorted Identifiers
+///
+/// Given a dictionary, return a vector holding the message IDs in sorted
+/// order.
+///
+/// \param dictionary Dictionary to examine
+///
+/// \return Sorted list of message IDs
+
+vector<MessageID> sortedIdentifiers(MessageDictionary* dictionary) {
+    vector<MessageID> ident;
+
+    for (MessageDictionary::const_iterator i = dictionary->begin();
+         i != dictionary->end(); ++i) {
+        ident.push_back(i->first);
+    }
+    sort(ident.begin(), ident.end());
+
+    return ident;
+}
+
+
+/// \brief Write Header File
+///
+/// Writes the C++ header file containing the symbol definitions.
+///
+/// \param file Name of the message file.  The header file is written to a
+/// file of the same name but with a .h suffix.
+/// \param prefix Prefix string to use in symbols
+/// \param dictionary Dictionary holding the message definitions.
+
+void writeHeaderFile(const string& file, const string& prefix,
+    MessageDictionary* dictionary)
+{
+    Filename message_file(file);
+    Filename header_file(message_file.useAsDefault(".h"));
+
+    // Text to use as the sentinels.
+    string sentinel_text = sentinel(header_file);
+
+    // Open the output file for writing
+    ofstream hfile(header_file.fullName().c_str());
+
+    try {
+        if (hfile.fail()) {
+            throw MessageException(MSG_OPENOUT, header_file.fullName(),
+                strerror(errno));
+        }
+
+        // Write the header preamble.  If there is an error, we'll pick it up
+        // after the last write.
+
+        hfile <<
+            "// File created from " << message_file.fullName() << " on " <<
+                currentTime() << "\n" <<
+             "\n" <<
+             "#ifndef " << sentinel_text << "\n" <<
+             "#define "  << sentinel_text << "\n" <<
+             "\n" <<
+             "#include <log/message_types.h>\n" <<
+             "\n" <<
+             "namespace {\n" <<
+             "\n";
+
+        vector<MessageID> idents = sortedIdentifiers(dictionary);
+        for (vector<MessageID>::const_iterator j = idents.begin();
+            j != idents.end(); ++j) {
+            hfile << "isc::log::MessageID " << prefix << *j <<
+                " = \"" << *j << "\";\n";
+        }
+
+        // ... and finally the postamble
+        hfile <<
+            "\n" <<
+            "} // Anonymous namespace\n" <<
+            "\n" <<
+            "#endif // " << sentinel_text << "\n";
+
+        // Report errors (if any) and exit
+        if (hfile.fail()) {
+            throw MessageException(MSG_WRITERR, header_file.fullName(),
+                strerror(errno));
+        }
+
+        hfile.close();
+    }
+    catch (MessageException&) {
+        hfile.close();
+        throw;
+    }
+}
+
+
+/// \brief Convert Non Alpha-Numeric Characters to Underscores
+///
+/// Simple function for use in a call to transform
+
+char replaceNonAlphaNum(char c) {
+    return (isalnum(c) ? c : '_');
+}
+
+
+/// \brief Write Program File
+///
+/// Writes the C++ source code file.  This defines an external objects whose
+/// constructor is run at initialization time.  The constructor adds the message
+/// definitions to the main global dictionary.
+
+void writeProgramFile(const string& file, MessageDictionary* dictionary)
+{
+    Filename message_file(file);
+    Filename program_file(message_file.useAsDefault(".cc"));
+
+    // Open the output file for writing
+    ofstream ccfile(program_file.fullName().c_str());
+    try {
+        if (ccfile.fail()) {
+            throw MessageException(MSG_OPENOUT, program_file.fullName(),
+                strerror(errno));
+        }
+
+        // Write the preamble.  If there is an error, we'll pick it up after
+        // the last write.
+
+        ccfile <<
+            "// File created from " << message_file.fullName() << " on " <<
+                currentTime() << "\n" <<
+             "\n" <<
+             "#include <cstddef>\n" <<
+             "#include <log/message_initializer.h>\n" <<
+             "\n" <<
+             "using namespace isc::log;\n" <<
+             "\n" <<
+             "namespace {\n" <<
+             "\n" <<
+             "const char* values[] = {\n";
+
+        // Output the identifiers and the associated text.
+        vector<MessageID> idents = sortedIdentifiers(dictionary);
+        for (vector<MessageID>::const_iterator i = idents.begin();
+            i != idents.end(); ++i) {
+                ccfile << "    \"" << *i << "\", \"" <<
+                    quoteString(dictionary->getText(*i)) << "\",\n";
+        }
+
+        // ... and the postamble
+        ccfile <<
+            "    NULL\n" <<
+            "};\n" <<
+            "\n" <<
+            "} // Anonymous namespace\n" <<
+            "\n";
+
+        // Now construct a unique name.  We don't put the message initializer as
+        // a static variable or in an anonymous namespace lest the C++
+        // compiler's optimizer decides it can optimise it away.
+        string unique_name = program_file.name() + program_file.extension() +
+            "_" + currentTime();
+        transform(unique_name.begin(), unique_name.end(), unique_name.begin(),
+            replaceNonAlphaNum);
+
+        // ... and write the initialization code
+        ccfile <<
+            "MessageInitializer " << unique_name << "(values);\n";
+
+        // Report errors (if any) and exit
+        if (ccfile.fail()) {
+            throw MessageException(MSG_WRITERR, program_file.fullName(),
+                strerror(errno));
+        }
+
+        ccfile.close();
+    }
+    catch (MessageException&) {
+        ccfile.close();
+        throw;
+    }
+}
+
+
+/// \brief Warn of Duplicate Entries
+///
+/// If the input file contained duplicate message IDs, only the first will be
+/// processed.  However, we should warn about it.
+///
+/// \param reader Message Reader used to read the file
+
+static void warnDuplicates(MessageReader& reader) {
+
+    // Get the duplicates (the overflow) and, if present, sort them into some
+    // order and remove those which occur more than once (which mean that they
+    // occur more than twice in the input file).
+    MessageReader::MessageIDCollection duplicates = reader.getNotAdded();
+    if (duplicates.size() > 0) {
+        cout << "Warning: the following duplicate IDs were found:\n";
+
+        sort(duplicates.begin(), duplicates.end());
+        MessageReader::MessageIDCollection::iterator new_end =
+            unique(duplicates.begin(), duplicates.end());
+        for (MessageReader::MessageIDCollection::iterator i = duplicates.begin();
+            i != new_end; ++i) {
+            cout << "    " << *i << "\n";
+        }
+    }
+}
+
+
+/// \brief Main Program
+///
+/// Parses the options then dispatches to the appropriate function.  See the
+/// main file header for the invocation.
+
+int main(int argc, char** argv) {
+    
+    const struct option loptions[] = {          // Long options
+        {"help",    no_argument, NULL, 'h'},
+        {"version", no_argument, NULL, 'v'},
+        {NULL,      0,           NULL, 0  }
+    };
+    const char* soptions = "hv";               // Short options
+
+    optind = 1;             // Ensure we start a new scan
+    int  opt;               // Value of the option
+
+    while ((opt = getopt_long(argc, argv, soptions, loptions, NULL)) != -1) {
+        switch (opt) {
+            case 'h':
+                usage();
+                return 0;
+
+            case 'v':
+                version();
+                return 0;
+
+            default:
+                // A message will have already been output about the error.
+                return 1;
+        }
+    }
+
+    // Do we have the message file?
+    if (optind < (argc - 1)) {
+        cout << "Error: excess arguments in command line\n";
+        usage();
+        return 1;
+    } else if (optind >= argc) {
+        cout << "Error: missing message file\n";
+        usage();
+        return 1;
+    }
+    string message_file = argv[optind];
+
+    try {
+        // Have identified the file, so process it.  First create a local
+        // dictionary into which the data will be put.
+        MessageDictionary dictionary;
+
+        // Read the data into it.
+        MessageReader reader(&dictionary);
+        reader.readFile(message_file);
+
+        // Now write the header file.
+        writeHeaderFile(message_file, reader.getPrefix(), &dictionary);
+
+        // ... and the message text file.
+        writeProgramFile(message_file, &dictionary);
+
+        // Finally, warn of any duplicates encountered.
+        warnDuplicates(reader);
+    }
+    catch (MessageException& e) {
+        // Create an error message from the ID and the text
+        MessageDictionary* global = MessageDictionary::globalDictionary();
+        string text = e.id() + ", " + global->getText(e.id());
+
+        // Format with arguments
+        text = isc::strutil::format(text, e.arguments());
+        cerr << text << "\n";
+
+        return 1;
+    }
+
+    return 0;
+
+}

+ 31 - 0
src/lib/log/dbglevels.h

@@ -0,0 +1,31 @@
+// Copyright (C) 2010  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.
+
+// $Id$
+
+#ifndef __DBGLEVELS_H
+#define __DBGLEVELS_H
+
+/// \brief Defines Debug Levels
+///
+/// Defines the maximum and minimum debug levels and the number of levels.
+/// These are defined using #define as they are referenced in the construction
+/// of variables declared outside execution units.  (In this way we avoid the
+/// "static initialization fiasco" problem.)
+
+#define MIN_DEBUG_LEVEL (0)
+#define MAX_DEBUG_LEVEL (99)
+#define NUM_DEBUG_LEVEL (MAX_DEBUG_LEVEL - MIN_DEBUG_LEVEL + 1)
+
+#endif // __DBGLEVELS_H

+ 371 - 0
src/lib/log/documentation.txt

@@ -0,0 +1,371 @@
+This directory holds the first release of the logging system.
+
+Basic Ideas
+===========
+The BIND-10 logging system merges two ideas:
+
+* A hierarchical logging system similar to that used in Java (i.e. log4j)
+* Separation of message definitions and text
+
+
+Hierarchical Logging System
+===========================
+When a program writes a message to the logging system, it does so using an
+instance of the Logger class.  As well as performing the write of the message,
+the logger identifies the source of the message: different sources can write
+to different destinations and can log different severities of messages.  For
+example, the "cache" logger could write messages of DEBUG severity or above
+to a file while all other components write messages of "INFO" severity or above
+to the Syslog file.
+
+The loggers are hierarchical in that each logger is the child of another logger.
+The top of the hierarchy is the root logger, which does not have a parent.  The
+point of the hierarchy is that unless a logger is explicitly assigned an
+attribute (such as severity of message being logger), it picks it up from the
+parent.  (In BIND-10, there is the root logger (named after the program) and
+every other logger is a child of that.)  So in the example above, the
+INFO/Syslog attributes could be associated with the root logger while the
+DEBUG/file attributes are associated with the "cache" logger.
+
+
+Separation of Messages Definitions And Text
+===========================================
+The reason for this is to allow the message text to be overridden by versions
+in a local language.  To do this, each message is identified by an identifier
+e.g. "OPENIN".  Within the program, this is the symbol passed to the logging
+system.  The logger system uses the symbol as an index into a dictionary to
+retrieve the message associated with it (e.g. "unable to open %s for input").
+substitutes any message parameters (in this example, the string that is an
+invalid filename) and logs it to the destination.
+
+In the BIND-10 system, a set of default messages are linked into the program.
+At run-time. each program reads a message file, updating the stored definitions;
+this updated text is logged.  However, to aid support, the message identifier
+so in the example above, the message finally logged would be something like:
+
+    OPENIN, unable to open a.txt for input
+
+
+Using The System
+================
+The steps in using the system are:
+
+1. Create a message file.  This defines messages by an identification - a
+   mnemonic for the message, typically 6-12 characters long - and a message.
+   The file is described in more detail below.
+
+   Ideally the file should have a file type of ".msg".
+
+2. Run it through the message compiler to produce the .h and .cc files.  It
+   is intended that this step be included in the build process.  However, for
+   not run the compiler (found in the "compiler" subdirectory) manually.  The
+   only argument is the name of the message file: it will produce as output
+   two files, having the same name as the input file but with file types of
+   ".h" and ".cc".
+
+   The compiler is built in the "compiler" subdirectory of the "src/lib/log"
+   directory.
+
+3. Include the .h file in your source code to define message symbols, and
+   make sure that the .cc file is compiled and linked into your program -
+   static initialization will add the symbols to the global dictionary.
+
+4. Declare loggers in your code and use them to log messages.  This is described
+   in more detail below.
+
+5. To set the debug level and run-time message file, call runTimeInit (declared
+   in logger_support.h) in the main program unit.  This is a temporary solution
+   for Year 2, and will be replaced at a later date, the information coming from
+   the configuration database.
+
+
+Message Files
+=============
+
+File Contents and Format
+------------------------
+A message file is a file containing message definitions.  Typically there will
+be one message file for each component that declares message symbols.  An
+example file could be:
+
+-- BEGIN --
+
+# Example message file
+# $ID:$
+
+$PREFIX TEST_
+TEST1       message %s is much too large
++ This message is a test for the general message code
+
+UNKNOWN     unknown message
++ Issued when the message is unknown.
+
+-- END --
+
+Points to note:
+* Leading and trailing space are trimmed from the line.  Although the above
+  exampl,e has every line starting at column 1, the lines could be indented if
+  desired.
+
+* Blank lines are ignored.
+
+* Lines starting with "#" are comments are are ignored.  Comments must be on
+  a line by themselves - inline comments will be interpreted as part of the
+  text of the line.
+
+* Lines starting $ are directives.  At present, the only directive recognised
+  is $PREFIX, which has one argument: the string used to prefix symbols.  If
+  there is no facility directive, there is no prefix to the symbols. (Prefixes
+  are explained below.)
+
+* Lines starting + indicate an explanation for the preceding message.  These
+  are intended to be processed by a separate program and used to generate an
+  error messages manual.  However they are treated like comments by the message
+  compiler.  As with comments, these must be on a line by themselves; if inline,
+  the text (including the leading "+") will be interpreted as part of the line.
+
+* Message lines.  These comprise a symbol name and a message, which may
+  include zero or more printf-style tokens.  Symbol names will be upper-cased
+  by the compiler.
+
+
+Message Compiler
+----------------
+The message compiler is a program built in the src/log/compiler directory.
+It processes the message file to produce two files:
+
+1) A C++ header file (called <message-file-name>.h) that holds lines of
+the form:
+
+   namespace {
+   isc::log::MessageID PREFIX_IDENTIFIER = "IDENTIFIER";
+      :
+   }
+
+The symbols define the keys in the global message dictionary.  At present
+they are defined as std::strings, but a future implementation could redefine
+them as numeric values.
+
+The "PREFIX_" part of the symbol name is the string defined in the $PREFIX
+the argument to the directive.  So "$PREFIX MSG_" would prefix the identifer
+ABC with "MSG_" to give the symbol MSG_ABC.  Similarly "$PREFIX E" would
+prefix it with "E" to give the symbol EABC.  If no $PREFIX is given, no
+prefix appears (so the symbol in this example would be ABC).
+
+
+2) A C++ source file (called <message-file-name>.cc) that holds the code to
+insert the symbols and messages into the map.
+
+This file declares an array of identifiers/messages in the form:
+
+    namespace {
+    const char* values[] = {
+        identifier1, text1,
+        identifier2, text2,
+        :
+        NULL
+    };
+    }
+
+(A more complex structure to group identifiers and their messages could be
+imposed, but as the array is generated by code and will be read by code,
+it is not needed.)
+
+It then declares an object that will add information to the global dictionary:
+
+    MessageInitializer <message-file-name>_<time>(values);
+
+(Declaring the object as "static" or in the anonymous namespace runs the risk
+of it being optimised away when the module is compiled with optimisation.
+But giving it a standard name would cause a clash when multiple files are
+used, hence an attempt at disambiguation.)
+
+The constructor of the MessageInitializer object retrieves the singleton
+global Dictionary object (created using standard methods to avoid the
+"static initialization fiasco") and adds each identifier and text to it.
+A check is made as each is added; if the identifier already exists, it is
+added to "overflow" vector; the vector is printed to the main logging output
+when logging is finally enabled (to indicate a programming error).
+
+
+Using the Logging
+=================
+To use the current version of the logging:
+
+1. Build message header file and source file as describe above.
+
+2. In the main module of the program, declare an instance of the
+   RootLoggerName class to define the name of the program's root logger, e.g.
+
+       #include <log/root_logger_name.h>
+
+       isc::log::RootLoggerName("b10-auth");
+
+   It should be declared outside an execution unit to allow other statically-
+   declared loggers to pick it up.
+
+2. In the code that needs to do logging, declare a logger with a given name,
+   e.g.
+
+       #include <log/logger.h>
+            :
+       isc::log::Logger logger("myname");   // "myname" can be anything
+
+   The above example assumes declaration outside a function.  If declaring
+   non-statically within a function, declare it as:
+
+       isc::log::Logger logger("myname", true);
+
+   This is due to an apparent bug in the underlying log4cxx, where the deletion
+   of a statically-declared object at program termination can cause a segment
+   fault. (The destruction of internal memory structures can sometimes happen
+   out of order.)  By default the Logger class creates the structures in its
+   constructor but does not delete them in the destruction.  The default
+   behavious works because instead of reclaiming memory at program run-down,
+   the operating system reclaims it when the process is deleted.
+
+   Setting the second argument "true" causes the Logger's destructor to delete
+   the log4cxx structures.  This does not cause a problem if the program is
+   not terminating.  So use the second form when declaring an automatic
+   instance of isc::log::Logger on the stack.
+
+3. The main program unit should include a call to isc::log::runTimeInit()
+   (defined in logger_support.h) to set the logging severity, debug log level,
+   and external message file.
+
+   a) The logging severity is one of the enum defined in logger.h, i.e.
+
+        isc::log::Logger::DEBUG
+        isc::log::Logger::INFO
+        isc::log::Logger::WARN
+        isc::log::Logger::ERROR
+        isc::log::Logger::FATAL
+        isc::log::Logger::NONE
+
+   b) The debug log level is only interpreted when the severity is DEBUG and
+      is an integer raning from 0 to 99.  0 should be used for the highest-level
+      debug messages and 99 for the lowest-level (and typically more verbose)
+      messages.
+
+   c) Name of an external message file.  This is the same as a standard message
+      file, although it should not include the $PREFIX directive. (A single
+      $PREFIX directive will be ignored; multiple directives will cause the
+      read of the file to fail with an error.)  If a message is replaced, the
+      message should include the same printf-format directives in the same order
+      as the original message.
+
+4. Issue logging calls using methods on logger, e.g.
+
+       logger.error(DPS_NSTIMEOUT, "isc.org");
+
+   (where, in the example above we might have defined the symbol in the message
+   file with something along the lines of:
+
+       $PREFIX DPS_
+           :
+       NSTIMEOUT  queries to all nameservers for %s have timed out
+
+   At present, the only logging is to the console.
+
+
+Severity Guidelines
+===================
+When using logging, the question arises, what severity should a message be
+logged at?  The following is a suggestion - as always, the decision must be
+made in the context of which the message is logged.
+
+FATAL
+-----
+The program has encountered an error that is so severe that it cannot
+continue (or there is no point in continuing).  When a fatal error has been
+logged, the program will usually exit immediately (via a call to abort()) or
+shortly afterwards, after dumping some diagnostic information.
+
+ERROR
+-----
+Something has happened such that the program can continue but the results
+for the current (or future) operations cannot be guaranteed to be correct,
+or the results will be correct but the service is impaired.  For example,
+the program started but attempts to open one or more network interfaces failed.
+
+WARN
+----
+An unusual event  happened.  Although the program will continue working
+normally, the event was sufficiently out of the ordinary to warrant drawings
+attention to it.  For example, at program start-up a zone was loaded that
+contained no resource records,
+
+INFO
+----
+A normal but significant event has occurred that should be recorded,
+e.g. the program has started or is just about to terminate, a new zone has
+been created, etc.
+
+DEBUG
+-----
+This severity is only enabled on for debugging purposes.  A debug level is
+associated with debug messages, level 0 (the default) being for high-level
+messages and level 99 (the maximum) for the lowest level.  How the messages
+are distributed between the levels is up to the developer.  So if debugging
+the NSAS (for example), a level 0 message might record the creation of a new
+zone, a level 10 recording a timeout when trying to get a nameserver address,
+but a level 50 would record every query for an address. (And we might add
+level 51 to record every update of the RTT.)
+
+Note that like severities, levels are cumulative; so if level 25 is set as the
+debug level, all debug levels from 0 to 25 will be output.  In fact, it is
+probably easier to visualise the debug levels as part of the severity system:
+
+    FATAL                High
+    ERROR
+    WARN
+    INFO
+    DEBUG level 0
+    DEBUG level 1
+       :
+    DEBUG level 99       Low
+
+When a particular severity is set, it - and all severities and/or debug
+levels above it - will be logged.
+
+Logging Sources v Logging Severities
+------------------------------------
+When logging events, make a distinction between events related to the server
+and events related to DNS messages received.  Caution needs to be exercised
+with the latter as, if the logging is enabled in the normal course of events,
+such logging could be a denoial of service vector. For example, suppose that
+the main authoritiative service logger were to log both zone loading and
+unloading as INFO and a warning message if it received an invalid packet. An
+attacker could make the INFO messages unusable by flooding the server with
+malformed packets.
+
+There are two approaches to get round this:
+
+a) Make the logging of packet-dependent events a DEBUG-severity message.
+DEBUG is not enabled by default, so these events will not be recorded unless
+DEBUG is specifically chosen.
+
+b) Record system-related and packet-related messages via different loggers
+(e.g.  in the example given, sever events could be logged using the logger
+"auth" and packet-related events at that level logged using the logger
+"pkt-auth".)
+As the loggers are independent and the severity levels independent, fine-tuning
+of what and what is not recorded can be achieved.
+
+
+Outstanding Issues
+==================
+* Ability to configure system according to configuration database.
+* Update the build procedure to create .cc and .h files from the .msg file
+  during the build process. (Requires that the message compiler is built first.)
+
+
+Notes
+=====
+The message compiler is written in C++ (instead of Python) because it
+contains a component that reads the message file.  This component is used
+in both the message compiler and the server; in the server it is used when
+the server starts up (or when triggered by a command) to read in a message
+file to overwrite the internal dictionary.  Writing it in C++ means there
+is only one piece of code that does this functionality.
+

+ 140 - 0
src/lib/log/filename.cc

@@ -0,0 +1,140 @@
+// Copyright (C) 2010  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.
+
+// $Id$
+
+#include <iostream>
+#include <algorithm>
+#include <string>
+
+#include <ctype.h>
+
+#include <log/filename.h>
+#include <log/strutil.h>
+
+using namespace std;
+
+
+namespace isc {
+namespace log {
+
+// Split string into components.  Any backslashes are assumed to have
+// been replaced by forward slashes.
+
+void
+Filename::split(const string& full_name, string& directory,
+    string& name, string& extension) const
+{
+    directory = name = extension = "";
+    bool dir_present = false;
+    if (!full_name.empty()) {
+
+        // Find the directory.
+        size_t last_slash = full_name.find_last_of('/');
+        if (last_slash != string::npos) {
+
+            // Found the last slash, so extract directory component and
+            // set where the scan for the last_dot should terminate.
+            directory = full_name.substr(0, last_slash + 1);
+            if (last_slash == full_name.size()) {
+
+                // The entire string was a directory, so exit not and don't
+                // do any more searching.
+                return;
+            }
+
+            // Found a directory so note the fact.
+            dir_present = true;
+        }
+
+        // Now search backwards for the last ".".
+        size_t last_dot = full_name.find_last_of('.');
+        if ((last_dot == string::npos) ||
+            (dir_present && (last_dot < last_slash))) {
+
+            // Last "." either not found or it occurs to the left of the last
+            // slash if a directory was present (so it is part of a directory
+            // name).  In this case, the remainder of the string after the slash
+            // is the name part.
+            name = full_name.substr(last_slash + 1);
+            return;
+        }
+
+        // Did find a valid dot, so it and everything to the right is the
+        // extension...
+        extension = full_name.substr(last_dot);
+
+        // ... and the name of the file is everything in between.
+        if ((last_dot - last_slash) > 1) {
+            name = full_name.substr(last_slash + 1, last_dot - last_slash - 1);
+        }
+    }
+
+}
+
+// Expand the stored filename with the default.
+
+string
+Filename::expandWithDefault(const string& defname) const {
+
+    string def_directory("");
+    string def_name("");
+    string def_extension("");
+
+    // Normalize the input string.
+    string copy_defname = isc::strutil::trim(defname);
+#ifdef WIN32
+    isc::strutil::normalizeSlash(copy_defname);
+#endif
+
+    // Split into the components
+    split(copy_defname, def_directory, def_name, def_extension);
+
+    // Now construct the result.
+    string retstring =
+        (directory_.empty() ? def_directory : directory_) +
+        (name_.empty() ? def_name : name_) +
+        (extension_.empty() ? def_extension : extension_);
+    return retstring;
+}
+
+// Use the stored name as default for a given name
+
+string
+Filename::useAsDefault(const string& name) const {
+
+    string name_directory("");
+    string name_name("");
+    string name_extension("");
+
+    // Normalize the input string.
+    string copy_name = isc::strutil::trim(name);
+#ifdef WIN32
+    isc::strutil::normalizeSlash(copy_name);
+#endif
+
+    // Split into the components
+    split(copy_name, name_directory, name_name, name_extension);
+
+    // Now construct the result.
+    string retstring =
+        (name_directory.empty() ? directory_ : name_directory) +
+        (name_name.empty() ? name_ : name_name) +
+        (name_extension.empty() ? extension_ : name_extension);
+    return retstring;
+}
+
+
+} // namespace log
+} // namespace isc

+ 163 - 0
src/lib/log/filename.h

@@ -0,0 +1,163 @@
+// Copyright (C) 2010  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.
+
+// $Id$
+
+#ifndef __FILENAME_H
+#define __FILENAME_H
+
+#include <string>
+
+#include <strutil.h>
+
+namespace isc {
+namespace log {
+
+/// \brief Class to Manipulate Filenames
+///
+/// This is a utility class to manipulate filenames.  It repeats some of the
+/// features found in the Boost filename class, but is self-contained so avoids
+/// the need to link in the Boost library.
+///
+/// A Unix-style filename comprises three parts:
+///
+/// Directory - everything up to and including the last "/".  If there is no
+/// "/" in the string, there is no directory component.  Note that the
+/// requirement of a trailing slash eliminates the ambiguity of whether a
+/// component is a directory or not, e.g. in /alpha/beta", "beta" could be the
+/// name of a directory or is could be a file.  The interpretation here is that
+/// "beta" is the name of a file (although that file could be a directory).
+///
+/// Note: Under Windows, the drive letter is considered to be part of the
+/// directory specification.  Unless this class becomes more widely-used on
+/// Windows, there is no point in adding redundant code.
+///
+/// Name - everthing from the character after the last "/" up to but not
+/// including the last ".".
+///
+/// Extension - everthing from the right-most "." (after the right-most "/") to
+/// the end of the string.  If there is no "." after the last "/", there is
+/// no file extension.
+///
+/// (Note that on Windows, this function will replace all "\" characters
+/// with "/" characters on input strings.)
+///
+/// This class provides functions for extracting the components and for
+/// substituting components.
+
+
+class Filename {
+public:
+
+    /// \brief Constructor
+    Filename(const std::string& name) :
+        full_name_(""), directory_(""), name_(""), extension_("")
+    {
+        setName(name);
+    }
+
+    /// \brief Sets Stored Filename
+    ///
+    /// \param name New name to replaced currently stored name
+    void setName(const std::string& name) {
+        full_name_ = isc::strutil::trim(name);
+#ifdef WIN32
+        isc::strutil::normalizeSlash(full_name_);
+#endif
+        split(full_name_, directory_, name_, extension_);
+    }
+
+    /// \return Stored Filename
+    std::string fullName() const {
+        return full_name_;
+    }
+
+    /// \return Directory of Given File Name
+    std::string directory() const {
+        return directory_;
+    }
+
+    /// \return Name of Given File Name
+    std::string name() const {
+        return name_;
+    }
+
+    /// \return Extension of Given File Name
+    std::string extension() const {
+        return extension_;
+    }
+
+    /// \brief Expand Name with Default
+    ///
+    /// A default file specified is supplied and used to fill in any missing
+    /// fields.  For example, if the name stored is "/a/b" and the supplied
+    /// name is "c.d", the result is "/a/b.d": the only field missing from the
+    /// stored name is the extension, which is supplied by the default.
+    /// Another example would be to store "a.b" and to supply a default of
+    /// "/c/d/" - the result is "/c/d/a.b".  (Note that if the supplied default
+    /// was "/c/d", the result would be "/c/a.b", even if "/c/d" were actually
+    /// a directory.)
+    ///
+    /// \param defname Default name
+    ///
+    /// \return Name expanded with defname.
+    std::string expandWithDefault(const std::string& defname) const;
+
+    /// \brief Use as Default and Substitute into String
+    ///
+    /// Does essentially the inverse of expand(); that filled in the stored
+    /// name with a default and returned the result.  This treats the stored
+    /// name as the default and uses it to fill in a given name.  In essence,
+    /// the code:
+    /// \code
+    ///       Filename f("/a/b");
+    ///       result = f.expandWithdefault("c.d");
+    /// \endcode
+    /// gives as a result "/a/b.d".  This is the same as:
+    /// \code
+    ///       Filename f("c.d");
+    ///       result = f.useAsDefault("/a/b");
+    /// \endcode
+    ///
+    /// \param name Name to expand
+    ///
+    /// \return Name expanded with stored name
+    std::string useAsDefault(const std::string&) const;
+
+private:
+    /// \brief Split Name into Components
+    ///
+    /// Splits the file name into the directory, name and extension parts.
+    /// The name is assumed to have had back slashes replaced by forward
+    /// slashes (if appropriate).
+    ///
+    /// \param full_name Name to split
+    /// \param directory Returned directory part
+    /// \param name Returned name part
+    /// \param extension Returned extension part
+    void split(const std::string& full_name, std::string& directory,
+       std::string& name, std::string& extension) const;
+
+    // Members
+
+    std::string full_name_;     ///< Given name
+    std::string directory_;     ///< Directory part
+    std::string name_;          ///< Name part
+    std::string extension_;     ///< Extension part
+};
+
+} // namespace log
+} // namespace isc
+
+#endif // __FILENAME_H

+ 307 - 0
src/lib/log/logger.cc

@@ -0,0 +1,307 @@
+// Copyright (C) 2010  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
+
+// $Id$
+
+#include <iostream>
+
+#include <stdarg.h>
+#include <stdio.h>
+
+#include <log4cxx/appender.h>
+#include <log4cxx/basicconfigurator.h>
+#include <log4cxx/patternlayout.h>
+#include <log4cxx/consoleappender.h>
+
+#include <log/root_logger_name.h>
+#include <log/logger.h>
+#include <log/message_dictionary.h>
+#include <log/message_types.h>
+#include <log/strutil.h>
+#include <log/xdebuglevel.h>
+
+using namespace std;
+
+namespace isc {
+namespace log {
+
+// Static initializations
+
+bool Logger::init_ = false;
+
+// Destructor.  Delete log4cxx stuff if "don't delete" is clear.
+
+Logger::~Logger() {
+    if (exit_delete_) {
+        delete loggerptr_;
+    }
+}
+
+// Initialize logger - create a logger as a child of the root logger.  With
+// log4cxx this is assured by naming the logger <parent>.<child>.
+
+void
+Logger::initLogger() {
+
+    // Initialize basic logging if not already done.  This is a one-off for
+    // all loggers.
+    if (!init_) {
+
+        // TEMPORARY
+        // Add a suitable console logger to the log4cxx root logger.  (This
+        // is the logger at the root of the log4cxx tree, not the BIND-10 root
+        // logger, which is one level down.)  The chosen format is:
+        //
+        // YYYY-MM-DD hh:mm:ss.sss [logger] SEVERITY: text
+        //
+        // As noted, this is a temporary hack: it is done here to ensure that
+        // a suitable output and output pattern is set.  Future versions of the
+        // software will set this based on configuration data.
+
+        log4cxx::LayoutPtr layout(
+            new log4cxx::PatternLayout(
+                "%d{yyyy-MM-DD HH:mm:ss.SSS} %-5p [%c] %m\n"));
+        log4cxx::AppenderPtr console(
+            new log4cxx::ConsoleAppender(layout));
+        log4cxx::LoggerPtr sys_root_logger = log4cxx::Logger::getRootLogger();
+        sys_root_logger->addAppender(console);
+        
+        // Set the default logging to INFO
+        sys_root_logger->setLevel(log4cxx::Level::getInfo());
+
+        // All static stuff initialized
+        init_ = true;
+    }
+
+    // Initialize this logger.  Name this as to whether the BIND-10 root logger
+    // name has been set.  (If not, this mucks up the hierarchy :-( ).
+    string root_name = RootLoggerName::getName();
+    if (root_name.empty() || (name_ == root_name)) {
+        loggerptr_ = new log4cxx::LoggerPtr(log4cxx::Logger::getLogger(name_));
+    }
+    else {
+        loggerptr_ = new log4cxx::LoggerPtr(
+            log4cxx::Logger::getLogger(root_name + "." + name_)
+        );
+    }
+}
+
+
+// Set the severity for logging.  There is a 1:1 mapping between the logging
+// severity and the log4cxx logging levels, apart from DEBUG.
+//
+// In log4cxx, each of the logging levels (DEBUG, INFO, WARN etc.) has a numeric
+// value.  The level is set to one of these and any numeric level equal to or
+// above it that is reported.  For example INFO has a value of 20000 and ERROR
+// a value of 40000. So if a message of WARN severity (= 30000) is logged, it is
+// not logged when the logger's severity level is ERROR (as 30000 !>= 40000).
+// It is reported if the logger's severity level is set to WARN (as 30000 >=
+/// 30000) or INFO (30000 >= 20000).
+//
+// This gives a simple system for handling different debug levels.  The debug
+// level is a number between 0 and 99, with 0 being least verbose and 99 the
+// most.  To implement this seamlessly, when DEBUG is set, the numeric value
+// of the logging level is actually set to (DEBUG - debug-level).  Similarly
+// messages of level "n" are logged at a logging level of (DEBUG - n).  Thus if
+// the logging level is set to DEBUG and the debug level set to 25, the actual
+// level set is 10000 - 25 = 99975.
+//
+// Attempting to log a debug message of level 26 is an attempt to log a message
+// of level 10000 - 26 = 9974.  As 9974 !>= 9975, it is not logged.  A
+// message of level 25 is, because 9975 >= 9975.
+//
+// The extended set of logging levels is implemented by the XDebugLevel class.
+
+void
+Logger::setSeverity(Severity severity, int dbglevel) {
+    switch (severity) {
+        case NONE:
+            getLogger()->setLevel(log4cxx::Level::getOff());
+            break;
+
+        case FATAL:
+            getLogger()->setLevel(log4cxx::Level::getFatal());
+            break;
+
+        case ERROR:
+            getLogger()->setLevel(log4cxx::Level::getError());
+            break;
+
+        case WARN:
+            getLogger()->setLevel(log4cxx::Level::getWarn());
+            break;
+
+        case INFO:
+            getLogger()->setLevel(log4cxx::Level::getInfo());
+            break;
+
+        case DEBUG:
+            getLogger()->setLevel(
+                log4cxx::XDebugLevel::getExtendedDebug(dbglevel));
+            break;
+
+        // Will get here for DEFAULT or any other value.  This disables the
+        // logger's own severity and it defaults to the severity of the parent
+        // logger.
+        default:
+            getLogger()->setLevel(0);
+    }
+}
+
+// Convert between numeric log4cxx logging level and BIND-10 logging severity.
+
+Logger::Severity
+Logger::convertLevel(int value) const {
+
+    // The order is optimised.  This is only likely to be called when testing
+    // for writing debug messages, so the check for DEBUG_INT is first.
+    if (value <= log4cxx::Level::DEBUG_INT) {
+        return (DEBUG);
+    } else if (value <= log4cxx::Level::INFO_INT) {
+        return (INFO);
+    } else if (value <= log4cxx::Level::WARN_INT) {
+        return (WARN);
+    } else if (value <= log4cxx::Level::ERROR_INT) {
+        return (ERROR);
+    } else if (value <= log4cxx::Level::FATAL_INT) {
+        return (FATAL);
+    } else {
+        return (NONE);
+    }
+}
+
+
+// Return the logging severity associated with this logger.
+
+Logger::Severity
+Logger::getSeverityCommon(const log4cxx::LoggerPtr& ptrlogger,
+    bool check_parent) const {
+
+    log4cxx::LevelPtr level = ptrlogger->getLevel();
+    if (level == log4cxx::LevelPtr()) {
+
+        // Null level returned, logging should be that of the parent.
+
+        if (check_parent) {
+            log4cxx::LoggerPtr parent = ptrlogger->getParent();
+            if (parent == log4cxx::LoggerPtr()) {
+
+                // No parent, so reached the end of the chain.  Return INFO
+                // severity.
+                return (INFO);
+            }
+            else {
+                return getSeverityCommon(parent, check_parent);
+            }
+        }
+        else {
+            return (DEFAULT);
+        }
+    } else {
+        return convertLevel(level->toInt());
+    }
+}
+
+
+// Get the debug level.  This returns 0 unless the severity is DEBUG.
+
+int
+Logger::getDebugLevel() {
+
+    log4cxx::LevelPtr level = getLogger()->getLevel();
+    if (level == log4cxx::LevelPtr()) {
+
+        // Null pointer returned, logging should be that of the parent.
+        return (0);
+        
+    } else {
+        int severity = level->toInt();
+        if (severity <= log4cxx::Level::DEBUG_INT) {
+            return (log4cxx::Level::DEBUG_INT - severity);
+        }
+        else {
+            return (0);
+        }
+    }
+}
+
+// Log an error message:
+// Common code.  Owing to the use of variable arguments, this must be inline
+// (hence the definition of the macro).  Also note that it expects that the
+// message buffer "message" is declared in the compilation unit.
+
+#define MESSAGE_SIZE (256)
+
+#define FORMAT_MESSAGE(message) \
+    { \
+    MessageDictionary* global = MessageDictionary::globalDictionary(); \
+    string format = global->getText(ident); \
+    va_list ap; \
+    va_start(ap, ident); \
+    vsnprintf(message, sizeof(message), format.c_str(), ap); \
+    message[sizeof(message) - 1] = '\0'; \
+    va_end(ap); \
+    }
+    
+
+// Output methods
+
+void
+Logger::debug(int dbglevel, isc::log::MessageID ident, ...) {
+    if (isDebugEnabled(dbglevel)) {
+        char message[MESSAGE_SIZE];
+        FORMAT_MESSAGE(message);
+        LOG4CXX_DEBUG(getLogger(), ident << ", " << message);
+    }
+}
+
+void
+Logger::info(isc::log::MessageID ident, ...) {
+    if (isInfoEnabled()) {
+        char message[MESSAGE_SIZE];
+        FORMAT_MESSAGE(message);
+        LOG4CXX_INFO(getLogger(), ident << ", " << message);
+    }
+}
+
+void
+Logger::warn(isc::log::MessageID ident, ...) {
+    if (isWarnEnabled()) {
+        char message[MESSAGE_SIZE];
+        FORMAT_MESSAGE(message);
+        LOG4CXX_WARN(getLogger(), ident << ", " << message);
+    }
+}
+
+void
+Logger::error(isc::log::MessageID ident, ...) {
+    if (isErrorEnabled()) {
+        char message[MESSAGE_SIZE];
+        FORMAT_MESSAGE(message);
+        LOG4CXX_ERROR(getLogger(), ident << ", " << message);
+    }
+}
+
+void
+Logger::fatal(isc::log::MessageID ident, ...) {
+    if (isFatalEnabled()) {
+        char message[MESSAGE_SIZE];
+        FORMAT_MESSAGE(message);
+        LOG4CXX_FATAL(getLogger(), ident << ", " << message);
+    }
+}
+
+
+} // namespace log
+} // namespace isc

+ 327 - 0
src/lib/log/logger.h

@@ -0,0 +1,327 @@
+// Copyright (C) 2010  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.
+
+// $Id$
+
+#ifndef __LOGGER_H
+#define __LOGGER_H
+
+#include <cstdlib>
+#include <string>
+#include <boost/lexical_cast.hpp>
+#include <log4cxx/logger.h>
+
+#include <log/dbglevels.h>
+#include <log/message_types.h>
+
+namespace isc {
+namespace log {
+
+class Logger {
+public:
+
+    /// \brief Severity Levels
+    typedef enum {
+        DEFAULT,    // Default to logging level of parent
+        DEBUG,
+        INFO,
+        WARN,
+        ERROR,
+        FATAL,
+        NONE        // Disable logging
+    } Severity;
+
+    /// \brief Constructor
+    ///
+    /// Creates/attaches to a logger of a specific name.
+    ///
+    /// \param name Name of the logger.  If the name is that of the root name,
+    /// this creates an instance of the root logger; otherwise it creates a
+    /// child of the root logger.
+    ///
+    /// \param exit_delete This argument is present to get round a bug in
+    /// log4cxx.  If a log4cxx logger is declared outside an execution unit, it
+    /// is not deleted until the program runs down.  At that point all such
+    /// objects - including internal log4cxx objects - are deleted.  However,
+    /// there seems to be a bug in log4cxx where the way that such objects are
+    /// destroyed causes a MutexException to be thrown (this is described in
+    /// https://issues.apache.org/jira/browse/LOGCXX-322).  As this only occurs
+    /// during program rundown, the issue is not serious - it just looks bad to
+    /// have the program crash instead of shut down cleanly.\n
+    /// \n
+    /// The original implementation of the isc::log::Logger had as a member a
+    /// log4cxx logger (actually a LoggerPtr).  If the isc:: Logger was declared
+    /// statically, when it was destroyed at the end of the program the internal
+    /// LoggerPtr was destroyed, which triggered the problem.  The problem did
+    /// not occur if the isc::log::Logger was created on the stack.  To get
+    /// round this, the internal LoggerPtr is now created dynamically.  The
+    /// exit_delete argument controls its destruction: if true, it is destroyed
+    /// in the ISC Logger destructor.  If false, it is not.\n
+    /// \n
+    /// When creating an isc::log::Logger on the stack, the argument should be
+    /// false (the default); when the Logger is destroyed, all the internal
+    /// log4cxx objects are destroyed.  As only the logger (and not the internal
+    /// log4cxx data structures are being destroyed), all is well.  However,
+    /// when creating the logger statically, the argument should be false.  This
+    /// means that the log4cxx objects are not destroyed at program rundown;
+    /// instead memory is reclaimed and files are closed when the process is
+    /// destroyed, something that does not trigger the bug.
+    Logger(const std::string& name, bool exit_delete = false) :
+        loggerptr_(), name_(name), exit_delete_(exit_delete)
+    {}
+
+
+    /// \brief Destructor
+    virtual ~Logger();
+
+
+    /// \brief Configure Options
+    ///
+    /// TEMPORARY: Pass in the command-line options to set the logging severity
+    /// for the root logger.  Future versions of the logger will get this
+    /// information from the configuration database.
+    ///
+    /// \param severity Severity level to log
+    /// \param dbglevel If the severity is DEBUG, this is the debug level.
+    /// This can be in the range 1 to 100 and controls the verbosity.  A value
+    /// outside these limits is silently coerced to the nearest boundary.
+    /// \param local_file If provided, the name of a message file to read in and
+    /// supersede one or more of the current messages.
+    static void runTimeInit(Severity severity = INFO, int dbglevel = 1,
+        const char* local_file = NULL);
+
+
+    /// \brief Get Name of Logger
+    ///
+    /// \return The full name of the logger (including the root name)
+    virtual std::string getName() {
+        return getLogger()->getName();
+    }
+
+
+    /// \brief Set Severity Level for Logger
+    ///
+    /// Sets the level at which this logger will log messages.  If none is set,
+    /// the level is inherited from the parent.
+    ///
+    /// \param severity Severity level to log
+    /// \param dbglevel If the severity is DEBUG, this is the debug level.
+    /// This can be in the range 1 to 100 and controls the verbosity.  A value
+    /// outside these limits is silently coerced to the nearest boundary.
+    virtual void setSeverity(Severity severity, int dbglevel = 1);
+
+
+    /// \brief Get Severity Level for Logger
+    ///
+    /// \return The current logging level of this logger.  In most cases though,
+    /// the effective logging level is what is required.
+    virtual Severity getSeverity() {
+        return getSeverityCommon(getLogger(), false);
+    }
+
+    /// \brief Get Effective Severity Level for Logger
+    ///
+    /// \return The effective severity level of the logger.  This is the same
+    /// as getSeverity() if the logger has a severity level set, but otherwise
+    /// is the severity of the parent.
+    virtual Severity getEffectiveSeverity() {
+        return getSeverityCommon(getLogger(), true);
+    }
+
+
+    /// \brief Return DEBUG Level
+    ///
+    /// \return Current setting of debug level.  This is returned regardless of
+    /// whether the 
+    virtual int getDebugLevel();
+
+
+    /// \brief Returns if Debug Message Should Be Output
+    ///
+    /// \param dbglevel Level for which debugging is checked.  Debugging is
+    /// enabled only if the logger has DEBUG enabled and if the dbglevel
+    /// checked is less than or equal to the debug level set for the logger.
+    virtual bool
+    isDebugEnabled(int dbglevel = MIN_DEBUG_LEVEL) {
+        return (getLogger()->getEffectiveLevel()->toInt() <=
+            (log4cxx::Level::DEBUG_INT - dbglevel));
+    }
+
+
+    /// \brief Is INFO Enabled?
+    virtual bool isInfoEnabled() {
+        return (getLogger()->isInfoEnabled());
+    }
+
+
+    /// \brief Is WARNING Enabled?
+    virtual bool isWarnEnabled() {
+        return (getLogger()->isWarnEnabled());
+    }
+
+
+    /// \brief Is ERROR Enabled?
+    virtual bool isErrorEnabled() {
+        return (getLogger()->isErrorEnabled());
+    }
+
+
+    /// \brief Is FATAL Enabled?
+    virtual bool isFatalEnabled() {
+        return (getLogger()->isFatalEnabled());
+    }
+
+
+    /// \brief Output Debug Message
+    ///
+    /// \param dbglevel Debug level, ranging between 0 and 99.  Higher numbers
+    /// are used for more verbose output.
+    /// \param ident Message identification.
+    /// \param ... Optional arguments for the message.
+    void debug(int dbglevel, MessageID ident, ...);
+
+
+    /// \brief Output Informational Message
+    ///
+    /// \param ident Message identification.
+    /// \param ... Optional arguments for the message.
+    void info(MessageID ident, ...);
+
+
+    /// \brief Output Warning Message
+    ///
+    /// \param ident Message identification.
+    /// \param ... Optional arguments for the message.
+    void warn(MessageID ident, ...);
+
+
+    /// \brief Output Error Message
+    ///
+    /// \param ident Message identification.
+    /// \param ... Optional arguments for the message.
+    void error(MessageID ident, ...);
+
+
+    /// \brief Output Fatal Message
+    ///
+    /// \param ident Message identification.
+    /// \param ... Optional arguments for the message.
+    void fatal(MessageID ident, ...);
+
+
+protected:
+
+    /// \brief Equality
+    ///
+    /// Check if two instances of this logger refer to the same stream.
+    /// (This method is principally for testing.)
+    ///
+    /// \return true if the logger objects are instances of the same logger.
+    bool operator==(const Logger& other) const {
+        return (*loggerptr_ == *other.loggerptr_);
+    }
+
+
+    /// \brief Logger Initialized
+    ///
+    /// Check that the logger has been properly initialized.  (This method
+    /// is principally for testing.)
+    ///
+    /// \return true if this logger object has been initialized.
+    bool isInitialized() const {
+        return (loggerptr_ != NULL);
+    }
+
+
+    /// \brief Get Severity Level for Logger
+    ///
+    /// This is common code for getSeverity() and getEffectiveSeverity() -
+    /// it returns the severity of the logger; if not set (and the check_parent)
+    /// flag is set, it searches up the parent-child tree until a severity
+    /// level is found and uses that.
+    ///
+    /// \param ptrlogger Pointer to the log4cxx logger to check.
+    /// \param check_parent true to search up the tree, false to return the
+    /// current level.
+    ///
+    /// \return The effective severity level of the logger.  This is the same
+    /// as getSeverity() if the logger has a severity level set, but otherwise
+    /// is the severity of the parent.
+    Logger::Severity getSeverityCommon(const log4cxx::LoggerPtr& ptrlogger,
+        bool check_parent) const;
+
+
+    /// \brief Convert Between BIND-10 and log4cxx Logging Levels
+    ///
+    /// Converts between the numeric value of the log4cxx logging level
+    /// and the BIND-10 severity level.
+    ///
+    /// \param value log4cxx numeric logging level
+    ///
+    /// \return BIND-10 logging severity
+    Severity convertLevel(int value) const;
+
+
+    /// \brief Initialize log4cxx Logger
+    ///
+    /// Creates the log4cxx logger used internally.  A function is provided for
+    /// this so that the creation does not take place when this Logger object
+    /// is created but when it is used.  As the latter occurs in executable
+    /// code but the former can occur during initialization, this order
+    /// guarantees that anything that is statically initialized has completed
+    /// its initialization by the time the logger is used.
+    void initLogger();
+
+
+    /// \brief Return log4cxx Logger
+    ///
+    /// Returns the log4cxx logger, initializing it if not already initialized.
+    ///
+    /// \return Loggerptr object
+    log4cxx::LoggerPtr& getLogger() {
+        if (loggerptr_ == NULL) {
+            initLogger();
+        }
+        return *loggerptr_;
+    }
+
+
+    /// \brief Read Local Message File
+    ///
+    /// Reads a local message file into the global dictionary, replacing any
+    /// definitions there.  Any messages found in the local file that do not
+    /// replace ones in the global dictionary are listed.
+    ///
+    /// \param file Local message file to be read.
+    static void readLocalMessageFile(const char* file);
+
+private:
+    // Note that loggerptr_ is a pointer to a LoggerPtr, which is itself a
+    // pointer to the underlying log4cxx logger.  This is due to the problems
+    // with memory deletion on program exit, explained in the comments for
+    // the "exit_delete" parameter in this class's constructor.
+
+    log4cxx::LoggerPtr*  loggerptr_;    ///< Pointer to the underlying logger
+    std::string          name_;         ///< Name of this logger]
+    bool                 exit_delete_;  ///< Delete loggerptr_ on exit?
+
+    // NOTE - THIS IS A PLACE HOLDER
+    static bool         init_;      ///< Set true when initialized
+};
+
+} // namespace log
+} // namespace isc
+
+
+#endif // __LOGGER_H

+ 116 - 0
src/lib/log/logger_support.cc

@@ -0,0 +1,116 @@
+// Copyright (C) 2010  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
+
+// $Id$
+
+
+
+/// \brief Temporary Logger Support
+///
+/// Performs run-time initialization of the logger system.  In particular, it
+/// is passed information from the command line and:
+///
+/// a) Sets the severity of the messages being logged (and debug level if
+/// appropriate).
+/// b) Reads in the local message file is one has been supplied.
+///
+/// These functions will be replaced once the code has bneen written to obtain
+/// the logging parameters from the configuration database.
+
+#include <vector>
+
+#include <log/logger.h>
+#include <log/logger_support.h>
+#include <log/messagedef.h>
+#include <log/message_dictionary.h>
+#include <log/message_exception.h>
+#include <log/message_reader.h>
+#include <log/message_types.h>
+#include <log/root_logger_name.h>
+
+namespace isc {
+namespace log {
+
+using namespace std;
+
+// Declare a logger for the logging subsystem
+Logger logger("log");
+
+
+/// \brief Reads Local Message File
+///
+/// Reads the local message file into the global dictionary, overwriting
+/// existing messages.  If the file contained any message IDs not in the
+/// dictionary, they are listed in a warning message.
+///
+/// \param file Name of the local message file
+static void
+readLocalMessageFile(const char* file) {
+    
+    MessageDictionary* dictionary = MessageDictionary::globalDictionary();
+    MessageReader reader(dictionary);
+    try {
+        reader.readFile(file, MessageReader::REPLACE);
+
+        // File successfully read, list the duplicates
+        MessageReader::MessageIDCollection unknown = reader.getNotAdded();
+        for (MessageReader::MessageIDCollection::const_iterator
+            i = unknown.begin(); i != unknown.end(); ++i) {
+                logger.warn(MSG_IDNOTFND, (*i).c_str());
+        }
+    }
+    catch (MessageException& e) {
+        MessageID ident = e.id();
+        vector<MessageID> args = e.arguments();
+        switch (args.size()) {
+        case 0:
+            logger.error(ident);
+            break;
+
+        case 1:
+            logger.error(ident, args[0].c_str());
+            break;
+
+        default:    // 2 or more (2 should be the maximum)
+            logger.error(ident, args[0].c_str(), args[1].c_str());
+        }
+    }
+}
+
+/// Logger Run-Time Initialization
+
+void
+runTimeInit(Logger::Severity severity, int dbglevel, const char* file) {
+
+    // Create the application root logger.  This is the logger that has the
+    // name of the application (and is one level down from the log4cxx root
+    // logger).  All other loggers created in this application will be its
+    // child.
+    //
+    // The main purpose of the application root logger is to provide the root
+    // name in output message for all other loggers.
+    Logger logger(RootLoggerName::getName());
+
+    // Set the severity associated with it.  If no other logger has a severity,
+    // this will be the default.
+    logger.setSeverity(severity, dbglevel);
+
+    // Replace any messages with local ones (if given)
+    if (file) {
+        readLocalMessageFile(file);
+    }
+}
+
+} // namespace log
+} // namespace isc

+ 43 - 0
src/lib/log/logger_support.h

@@ -0,0 +1,43 @@
+// Copyright (C) 2010  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.
+
+// $Id$
+
+#ifndef __LOGGER_SUPPORT_H
+#define __LOGGER_SUPPORT_H
+
+#include <log/logger.h>
+
+namespace isc {
+namespace log {
+
+/// \brief Run-Time Initialization
+///
+/// This code will be used until the logger is fully integrated into the BIND-10
+/// configuration database.  It performs run-time initialization of th logger,
+/// in particular supplying run-time choices to it:
+///
+/// * The severity (and if applicable, debug level) at which to log
+/// * Name of a local message file, containing localisation of message text.
+///
+/// \param severity Severity at which to log
+/// \param dbglevel Debug severiy (ignored if "severity" is not "DEBUG")
+/// \param file Name of the local message file.
+void runTimeInit(Logger::Severity severity, int dbglevel, const char* file);
+
+} // namespace log
+} // namespace isc
+
+
+#endif // __LOGGER_SUPPORT_H

+ 116 - 0
src/lib/log/message_dictionary.cc

@@ -0,0 +1,116 @@
+// 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.
+
+// $Id$
+
+#include <cstddef>
+#include <log/message_dictionary.h>
+#include <log/message_types.h>
+
+using namespace std;
+
+namespace isc {
+namespace log {
+
+// (Virtual) Destructor
+
+MessageDictionary::~MessageDictionary() {
+}
+
+// Add message and note if ID already exists
+
+bool
+MessageDictionary::add(const MessageID& ident, const std::string& text) {
+    map<MessageID, string>::iterator i = dictionary_.find(ident);
+    bool not_found = (i == dictionary_.end());
+    if (not_found) {
+
+        // Message not already in the dictionary, so add it.
+        dictionary_[ident] = text;
+    }
+    
+    return (not_found);
+}
+
+// Add message and note if ID does not already exist
+
+bool
+MessageDictionary::replace(const MessageID& ident, const std::string& text) {
+    map<MessageID, string>::iterator i = dictionary_.find(ident);
+    bool found = (i != dictionary_.end());
+    if (found) {
+
+        // Exists, so replace it.
+        dictionary_[ident] = text;
+    }
+    
+    return (found);
+}
+
+// Load a set of messages
+
+vector<MessageID>
+MessageDictionary::load(const char* messages[]) {
+    vector<MessageID> duplicates;
+    int i = 0;
+    while (messages[i]) {
+
+        // ID present, so note it and point to text.
+        MessageID ident(messages[i++]);
+        if (messages[i]) {
+
+            // Text not null, note it and point to next ident. 
+            string text(messages[i++]);
+
+            // Add ID and text to message dictionary, noting if the ID was
+            // already present.
+            bool added = add(ident, text);
+            if (!added) {
+                duplicates.push_back(ident);
+            }
+        }
+    }
+    return duplicates;
+}
+
+// Return message text or blank string
+
+string
+MessageDictionary::getText(const MessageID& ident) const {
+    map<MessageID, string>::const_iterator i = dictionary_.find(ident);
+    if (i == dictionary_.end()) {
+        return string("");
+    }
+    else {
+        return i->second;
+    }
+}
+
+// Return global dictionary
+
+MessageDictionary*
+MessageDictionary::globalDictionary() {
+    static MessageDictionary* global = NULL;
+
+    if (global == NULL) {
+        global = new MessageDictionary();
+    }
+    return global;
+}
+
+
+
+
+} // namspace log
+} // namespace isc

+ 150 - 0
src/lib/log/message_dictionary.h

@@ -0,0 +1,150 @@
+// 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.
+
+// $Id$
+
+#ifndef __MESSAGE_DICTIONARY_H
+#define __MESSAGE_DICTIONARY_H
+
+#include <cstddef>
+#include <string>
+#include <map>
+#include <vector>
+
+#include <log/message_types.h>
+
+namespace isc {
+namespace log {
+
+/// \brief Message Dictionary
+///
+/// The message dictionary is a wrapper around a std::map object, and allows
+/// message text to be retrieved given the string identification.
+///
+/// Adding text occurs in two modes:
+///
+/// Through the "Add" method, ID/text mappings are added to the dictionary
+/// unless the ID already exists.  This is designed for use during program
+/// initialization, where a local message may supplant a compiled-in message.
+///
+/// Through the "Replace" method, ID/text mappings are added to the dictionary
+/// only if the ID already exists.  This is for use when a message file is
+/// supplied to replace messages provided with the program.
+///
+/// Although the class can be used stand-alone, it does supply a static method
+/// to return a particular instance - the "global" dictionary.
+
+class MessageDictionary {
+public:
+
+    // Default constructor and assignment operator are OK for this class
+
+    /// \brief Virtual Destructor
+    virtual ~MessageDictionary();
+
+    /// \brief Add Message
+    ///
+    /// Adds a message to the dictionary.  If the ID already exists, the ID is
+    /// added to the overflow vector.
+    ///
+    /// \param ident Identification of the message to add
+    /// \param text Message text
+    ///
+    /// \return true if the message was added to the dictionary, false if the
+    /// message existed and it was not added.
+    virtual bool add(const MessageID& ident, const std::string& text);
+
+
+    /// \brief Replace Message
+    ///
+    /// Replaces a message in the dictionary.  If the ID does not exist, it is
+    /// added to the overflow vector.
+    ///
+    /// \param ident Identification of the message to replace
+    /// \param text Message text
+    ///
+    /// \return true if the message was added to the dictionary, false if the
+    /// message did not exist and it was not added.
+    virtual bool replace(const MessageID& ident, const std::string& text);
+
+
+    /// \brief Load Dictionary
+    ///
+    /// Designed to be used during the initialization of programs, this
+    /// accepts a set of (ID, text) pairs as a one-dimensional array of
+    /// const char* and adds them to the dictionary.  The messages are added
+    /// using "Add".
+    ///
+    /// \param data null-terminated array of const char* alternating ID and
+    /// message text.  This should be an odd number of elements long, the last
+    /// elemnent being NULL.  If it is an even number of elements long, the
+    /// last ID is ignored.
+    ///
+    /// \return Vector of message IDs that were not loaded because an ID of the
+    /// same name already existing in the dictionary.  This vector may be
+    /// empty.
+    virtual std::vector<MessageID> load(const char* elements[]);
+
+
+    /// \brief Get Message Text
+    ///
+    /// Given an ID, retrieve associated message text.
+    ///
+    /// \param ident Message identification
+    ///
+    /// \return Text associated with message or empty string if the ID is not
+    /// recognised.  (Note: this precludes an ID being associated with an empty
+    /// string.)
+    virtual std::string getText(const MessageID& ident) const;
+
+
+    /// \brief Number of Items in Dictionary
+    ///
+    /// \return Number of items in the dictionary
+    virtual size_t size() const {
+        return dictionary_.size();
+    }
+
+
+    // Allow access to the internal map structure, but don't allow alteration.
+    typedef std::map<MessageID, std::string>::const_iterator const_iterator;
+
+
+    /// \brief Return begin() iterator of internal map
+    const_iterator begin() const {
+        return dictionary_.begin();
+    }
+
+
+    /// \brief Return end() iterator of internal map
+    const_iterator end() const {
+        return dictionary_.end();
+    }
+
+
+    /// \brief Return Global Dictionary
+    ///
+    /// Returns a pointer to the singleton global dictionary.
+    ///
+    /// \return Pointer to global dictionary.
+    static MessageDictionary* globalDictionary();
+
+private:
+    std::map<MessageID, std::string>  dictionary_;
+};
+
+} // namespace log
+} // namespace isc
+
+#endif // __MESSAGE_DICTIONARY_H

+ 28 - 0
src/lib/log/message_exception.cc

@@ -0,0 +1,28 @@
+// 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.
+
+// $Id$
+
+/// \brief Body of Virtual Destructor
+
+#include <log/message_exception.h>
+
+namespace isc {
+namespace log {
+
+MessageException::~MessageException() throw() {
+}
+
+} // namespace log
+} // namespace isc

+ 90 - 0
src/lib/log/message_exception.h

@@ -0,0 +1,90 @@
+// 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.
+
+// $Id$
+
+#ifndef __MESSAGE_EXCEPTION_H
+#define __MESSAGE_EXCEPTION_H
+
+#include <stdexcept>
+#include <string>
+#include <vector>
+
+#include <log/message_types.h>
+
+namespace isc {
+namespace log {
+
+/// \brief Message Exception
+///
+/// Used in the message reader, this simple exception class allows a message
+/// code and its arguments to be encapsulated in an exception and thrown
+/// up the stack.
+
+class MessageException : public std::exception {
+public:
+
+    /// \brief Constructor
+    ///
+    /// \param id Message identification
+    MessageException(MessageID id) : id_(id)
+    {}
+
+    /// \brief Constructor
+    ///
+    /// \param id Message identification
+    /// \param arg1 First message argument
+    MessageException(MessageID id, const std::string& arg1) : id_(id)
+    {
+        args_.push_back(arg1);
+    }
+
+    /// \brief Constructor
+    ///
+    /// \param id Message identification
+    /// \param arg1 First message argument
+    /// \param arg2 Second message argument
+    MessageException(MessageID id, const std::string& arg1,
+        const std::string& arg2) : id_(id)
+    {
+        args_.push_back(arg1);
+        args_.push_back(arg2);
+    }
+
+    /// \brief Destructor
+    virtual ~MessageException() throw();
+
+    /// \brief Return Message ID
+    ///
+    /// \return Message identification
+    MessageID id() const {
+        return id_;
+    }
+
+    /// \brief Return Arguments
+    ///
+    /// \return Exception Arguments
+    std::vector<std::string> arguments() const {
+        return args_;
+    }
+
+private:
+    MessageID                   id_;        // Exception ID
+    std::vector<std::string>    args_;      // Exception arguments
+};
+
+} // namespace log
+} // namespace isc
+
+#endif // __MESSAGE_EXCEPTION_H

+ 32 - 0
src/lib/log/message_initializer.cc

@@ -0,0 +1,32 @@
+// 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.
+
+// $Id$
+
+#include <log/message_dictionary.h>
+#include <log/message_initializer.h>
+
+namespace isc {
+namespace log {
+
+// Constructor.  Just retrieve the global dictionary and load the IDs and
+// associated text into it.
+
+MessageInitializer::MessageInitializer(const char* values[]) {
+    MessageDictionary* global = MessageDictionary::globalDictionary();
+    global->load(values);
+}
+
+} // namespace log
+} // namespace isc

+ 63 - 0
src/lib/log/message_initializer.h

@@ -0,0 +1,63 @@
+// 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.
+
+// $Id$
+
+#ifndef __MESSAGEINITIALIZER_H
+#define __MESSAGEINITIALIZER_H
+
+#include <log/message_dictionary.h>
+
+namespace isc {
+namespace log {
+
+/// \brief Initialize Message Dictionary
+///
+/// This is a helper class to add a set of message IDs and associated text to
+/// the global dictionary.
+///
+/// It should be declared outside an execution unit and initialized with a
+/// an array of values, alternating identifier, associated text and ending with
+/// a NULL, e.g.
+///
+///     static const char* values[] = {
+///         "IDENT1", "message for ident 1",
+///         "IDENT2", "message for ident 2",
+///             :
+///         NULL
+///     };
+///     MessageDictionaryHelper xyz(values);
+///
+/// This will automatically add the message ID/text pairs to the global
+/// dictionary during initialization - all that is required is that the module
+/// containing the definition is included into the final executable.
+///
+/// Messages are added via the MessageDictionary::add() method, so any
+/// duplicates are stored in the the global dictionary's overflow vector whence
+/// they can be retrieved at run-time.
+
+class MessageInitializer {
+public:
+
+    /// \brief Constructor
+    ///
+    /// The only method in the class, this adds the array of values to the
+    /// global dictionary.
+    MessageInitializer(const char* values[]);
+};
+
+} // namespace log
+} // namespace isc
+
+#endif // __MESSAGEINITIALIZER_H

+ 184 - 0
src/lib/log/message_reader.cc

@@ -0,0 +1,184 @@
+// 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.
+
+// $Id$
+
+#include <errno.h>
+#include <string.h>
+
+#include <iostream>
+#include <fstream>
+
+#include <log/message_exception.h>
+#include <log/messagedef.h>
+#include <log/message_reader.h>
+#include <log/strutil.h>
+
+using namespace std;
+
+namespace isc {
+namespace log {
+
+// Virtual destructor.
+MessageReader::~MessageReader() {
+}
+
+
+// Read the file.
+
+void
+MessageReader::readFile(const string& file, MessageReader::Mode mode) {
+
+    // Ensure the non-added collection is empty: this object might be
+    // being reused.
+    not_added_.clear();
+
+    // Open the file
+    ifstream infile(file.c_str());
+    if (infile.fail()) {
+        throw MessageException(MSG_OPENIN, file, strerror(errno));
+    }
+
+    // Loop round reading it.
+    string line;
+    getline(infile, line);
+    while (infile.good()) {
+        processLine(line, mode);
+        getline(infile, line);
+    }
+
+    // Why did the loop terminate?
+    if (!infile.eof()) {
+        throw MessageException(MSG_READERR, file, strerror(errno));
+    }
+    infile.close();
+}
+
+// Parse a line of the file
+
+void
+MessageReader::processLine(const string& line, MessageReader::Mode mode) {
+
+    // Get rid of leading and trailing spaces
+    string text = isc::strutil::trim(line);
+
+    if (text.empty()) {
+        ;                           // Ignore blank lines
+
+    } else if ((text[0] == '#') || (text[0] == '+')) {
+        ;                           // Ignore comments or descriptions
+
+    } else if (text[0] == '$') {
+        parseDirective(text);       // Process directives
+
+    } else {
+        parseMessage(text, mode);   // Process other lines
+
+    }
+}
+
+// Process directive
+
+void
+MessageReader::parseDirective(const std::string& text) {
+
+    static string valid = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_";
+
+    // Regardless of what happens, all prefixes will be uppercase (as will
+    // all symbols).
+    string line = text;
+    isc::strutil::uppercase(line);
+    vector<string> tokens = isc::strutil::tokens(line);
+
+    // Only $PREFIX is recognised so far, so we'll handle it here.
+    if (tokens[0] != string("$PREFIX")) {
+        throw MessageException(MSG_UNRECDIR, tokens[0]);
+
+    } else if (tokens.size() < 2) {
+        throw MessageException(MSG_PRFNOARG);
+
+    } else if (tokens.size() > 2) {
+        throw MessageException(MSG_PRFEXTRARG);
+
+    }
+
+    // Token is potentially valid providing it only contains alphabetic
+    // and numeric characters (and underscores) and does not start with a
+    // digit.
+    
+    if ((tokens[1].find_first_not_of(valid) != string::npos) ||
+        (std::isdigit(tokens[1][0]))) {
+
+        // Invalid character in string or it starts with a digit.
+        throw MessageException(MSG_PRFINVARG, tokens[1]);
+    }
+
+    // All OK - unless the prefix has already been set.
+
+    if (prefix_.size() != 0) {
+        throw MessageException(MSG_DUPLPRFX);
+    }
+
+    // Prefix has not been set, so set it and return success.
+
+    prefix_ = tokens[1];
+}
+
+// Process message.  By the time this method is called, the line has been
+// stripped of leading and trailing spaces, and we believe that it is a line
+// defining a message.  The first token on the line is convered to uppercase
+// and becomes the message ID; the rest of the line is the message text.
+
+void
+MessageReader::parseMessage(const std::string& text, MessageReader::Mode mode) {
+
+    static string delimiters("\t\n ");   // Delimiters
+
+    // Look for the first delimiter.
+    size_t first_delim = text.find_first_of(delimiters);
+    if (first_delim == string::npos) {
+
+        // Just a single token in the line - this is not valid
+        throw MessageException(MSG_ONETOKEN, text);
+    }
+
+    // Extract the first token into the message ID
+    MessageID ident = text.substr(0, first_delim);
+
+    // Locate the start of the message text
+    size_t first_text = text.find_first_not_of(delimiters, first_delim);
+    if (first_text == string::npos) {
+
+        // ?? This happens if there are trailing delimiters, which should not
+        // occur as we have stripped trailing spaces off the line.  Just treat
+        // this as a single-token error for simplicity's sake.
+        throw MessageException(MSG_ONETOKEN, text);
+    }
+
+    // Add the result to the dictionary and to the non-added list if the add to
+    // the dictionary fails.
+    bool added;
+    if (mode == ADD) {
+        added = dictionary_->add(ident, text.substr(first_text));
+    }
+    else {
+        added = dictionary_->replace(ident, text.substr(first_text));
+    }
+    if (!added) {
+        not_added_.push_back(ident);
+    }
+}
+
+} // namespace log
+} // namespace isc

+ 175 - 0
src/lib/log/message_reader.h

@@ -0,0 +1,175 @@
+// 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.
+
+// $Id$
+
+#ifndef __MESSAGE_READER_H
+#define __MESSAGE_READER_H
+
+#include <iostream>
+#include <map>
+#include <string>
+#include <vector>
+
+#include <log/message_dictionary.h>
+#include <log/message_types.h>
+
+namespace isc {
+namespace log {
+
+/// \brief Read Message File
+///
+/// Reads a message file and creates a map of identifier against the text of the
+/// message.  This map can be retrieved for subsequent processing.
+
+class MessageReader {
+public:
+
+    /// \brief Read Mode
+    ///
+    /// If ADD, messages are added to the dictionary if the ID does not exist
+    /// there.  If it does, the ID is added to the dictionary's overflow
+    /// vector.
+    ///
+    /// If REPLACE, the dictionary is only modified if the message ID already
+    /// exists in it.  New message IDs are added to the overflow vector.
+    typedef enum {
+        ADD,
+        REPLACE
+    } Mode;
+
+    /// \brief Visible collection types
+    typedef std::vector<MessageID>   MessageIDCollection;
+
+    /// \brief Constructor
+    ///
+    /// Default constructor.  All work is done in the main readFile code (so
+    /// that a status return can be returned instead of needing to throw an
+    /// exception).
+    ///
+    /// \param dictionary Dictionary to which messages read read from the file
+    /// are added.  (This should be a local dictionary when the class is used in
+    /// the message compiler, and the global dictionary when used in a server.
+    /// The ownership of the dictionary object is not transferred - the caller
+    /// is responsible for managing the lifetime of the dictionary.
+    MessageReader(MessageDictionary* dictionary = NULL) :
+        dictionary_(dictionary)
+    {}
+
+
+    /// \brief Virtual Destructor
+    virtual ~MessageReader();
+
+
+    /// \brief Get Dictionary
+    ///
+    /// Returns the pointer to the dictionary object.  Note that ownership is
+    /// not transferred - the caller should not delete it.
+    ///
+    /// \return Pointer to current dictionary object
+    MessageDictionary* getDictionary() const {
+        return dictionary_;
+    }
+
+
+    /// \brief Set Dictionary
+    ///
+    /// Sets the current dictionary object.
+    ///
+    /// \param dictionary New dictionary object. The ownership of the dictionary
+    /// object is not transferred - the caller is responsible for managing the
+    /// lifetime of the dictionary.
+    void setDictionary(MessageDictionary* dictionary) {
+        dictionary_ = dictionary;
+    }
+
+
+    /// \brief Read File
+    ///
+    /// This is the main method of the class and reads in the file, parses it,
+    /// and stores the result in the message dictionary.
+    ///
+    /// \param file Name of the message file.
+    /// \param mode Addition mode.  See the description of the "Mode" enum.
+    virtual void readFile(const std::string& file, Mode mode = ADD);
+
+
+    /// \brief Process Line
+    ///
+    /// Parses a text line and adds it to the message map.  Although this is
+    /// for use in readFile, it can also be used to add individual messages
+    /// to the message map.
+    ///
+    /// \param line Line of text to process
+    /// \param mode If a message line, how to add the message to the dictionary.
+    virtual void processLine(const std::string& line, Mode mode = ADD);
+
+
+    /// \brief Get Prefix
+    ///
+    /// \return Argument to the $PREFIX directive (if present)
+    virtual std::string getPrefix() const {
+        return prefix_;
+    }
+
+
+    /// \brief Clear Prefix
+    ///
+    /// Clears the current prefix.
+    virtual void clearPrefix() {
+        prefix_ = "";
+    }
+
+
+    /// \brief Get Not-Added List
+    ///
+    /// Returns the list of IDs that were not added during the last
+    /// read of the file.
+    ///
+    /// \return Collection of messages not added
+    MessageIDCollection getNotAdded() const {
+        return not_added_;
+    }
+
+private:
+
+    /// \brief Handle a Message Definition
+    ///
+    /// Passed a line that should contain a message, this processes that line
+    /// and adds it to the dictionary according to the mode setting.
+    ///
+    /// \param line Line of text
+    /// \param ADD or REPLACE depending on how the reader is operating.  (See
+    /// the description of the Mode typedef for details.)
+    void parseMessage(const std::string& line, Mode mode);
+
+
+    /// \brief Handle Directive
+    ///
+    /// Passed a line starting with a "$", this handles the processing of
+    /// directives.
+    ///
+    /// \param line Line of text that starts with "$",
+    void parseDirective(const std::string& line);
+
+    /// Attributes
+    MessageDictionary*  dictionary_;    ///< Dictionary to add messages to
+    MessageIDCollection not_added_;     ///< List of IDs not added
+    std::string         prefix_;        ///< Input of $PREFIX statement
+};
+
+} // namespace log
+} // namespace isc
+
+#endif // __MESSAGE_READER_H

+ 32 - 0
src/lib/log/message_types.h

@@ -0,0 +1,32 @@
+// 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.
+
+// $Id$
+
+#ifndef __MESSAGE_TYPES_H
+#define __MESSAGE_TYPES_H
+
+#include <string>
+
+namespace isc {
+namespace log {
+
+typedef std::string MessageID;
+
+} // namespace log
+} // namespace isc
+
+
+
+#endif // __MESSAGE_TYPES_H

+ 27 - 0
src/lib/log/messagedef.cc

@@ -0,0 +1,27 @@
+// File created from messagedef.mes on Mon Jan 17 15:25:32 2011
+
+#include <cstddef>
+#include <log/message_initializer.h>
+
+using namespace isc::log;
+
+namespace {
+
+const char* values[] = {
+    "DUPLPRFX", "duplicate $PREFIX directive found",
+    "IDNOTFND", "could not replace message for '%s': no such message identification",
+    "ONETOKEN", "a line containing a message ID ('%s') and nothing else was found",
+    "OPENIN", "unable to open message file %s for input: %s",
+    "OPENOUT", "unable to open %s for output: %s",
+    "PRFEXTRARG", "$PREFIX directive has too many arguments",
+    "PRFINVARG", "$PREFIX directive has an invalid argument ('%s')",
+    "PRFNOARG", "no arguments were given to the $PREFIX directive",
+    "READERR", "error reading from %s: %s",
+    "UNRECDIR", "unrecognised directive '%s'",
+    "WRITERR", "error writing to %s: %s",
+    NULL
+};
+
+} // Anonymous namespace
+
+MessageInitializer messagedef_cc_Mon_Jan_17_15_25_32_2011(values);

+ 0 - 0
src/lib/log/messagedef.h


Some files were not shown because too many files changed in this diff