Browse Source

sync with trunk

git-svn-id: svn://bind10.isc.org/svn/bind10/branches/trac384@3910 e5f2f494-b856-4b98-b285-d166d9295462
Jelte Jansen 14 years ago
parent
commit
1b662b8dca
100 changed files with 9386 additions and 2664 deletions
  1. 53 0
      ChangeLog
  2. 1 1
      Makefile.am
  3. 101 4
      configure.ac
  4. 1 1
      doc/Doxyfile
  5. 1 1
      doc/guide/bind10-guide.html
  6. 1 1
      doc/guide/bind10-guide.xml
  7. 4 23
      src/bin/auth/Makefile.am
  8. 0 669
      src/bin/auth/asio_link.cc
  9. 0 452
      src/bin/auth/asio_link.h
  10. 187 113
      src/bin/auth/auth_srv.cc
  11. 43 15
      src/bin/auth/auth_srv.h
  12. 1 1
      src/bin/auth/benchmarks/Makefile.am
  13. 28 18
      src/bin/auth/benchmarks/query_bench.cc
  14. 42 21
      src/bin/auth/main.cc
  15. 3 3
      src/bin/auth/query.cc
  16. 3 6
      src/bin/auth/tests/Makefile.am
  17. 0 357
      src/bin/auth/tests/asio_link_unittest.cc
  18. 119 496
      src/bin/auth/tests/auth_srv_unittest.cc
  19. 2 2
      src/bin/auth/tests/query_unittest.cc
  20. 74 43
      src/bin/bind10/bind10.py.in
  21. 2 2
      src/bin/bind10/run_bind10.sh.in
  22. 5 5
      src/bin/bind10/tests/bind10_test.py
  23. 48 11
      src/bin/recurse/Makefile.am
  24. 0 7
      src/bin/recurse/README_FIRST.txt
  25. 145 0
      src/bin/recurse/b10-recurse.8
  26. 213 0
      src/bin/recurse/b10-recurse.xml
  27. 181 0
      src/bin/recurse/main.cc
  28. 0 177
      src/bin/recurse/recurse.py.in
  29. 84 10
      src/bin/recurse/recurse.spec.pre.in
  30. 610 0
      src/bin/recurse/recursor.cc
  31. 151 0
      src/bin/recurse/recursor.h
  32. 0 27
      src/bin/recurse/run_b10-recurse.sh.in
  33. 15 0
      src/bin/recurse/spec_config.h.pre.in
  34. 40 0
      src/bin/recurse/tests/Makefile.am
  35. 235 0
      src/bin/recurse/tests/recursor_config_unittest.cc
  36. 97 0
      src/bin/recurse/tests/recursor_unittest.cc
  37. 26 0
      src/bin/recurse/tests/run_unittests.cc
  38. 2 1
      src/lib/Makefile.am
  39. 34 0
      src/lib/asiolink/Makefile.am
  40. 103 0
      src/lib/asiolink/README
  41. 377 0
      src/lib/asiolink/asiolink.cc
  42. 575 0
      src/lib/asiolink/asiolink.h
  43. BIN
      src/lib/asiolink/doc/auth_process.jpg
  44. BIN
      src/lib/asiolink/doc/recursive_process.jpg
  45. 133 0
      src/lib/asiolink/internal/coroutine.h
  46. 225 0
      src/lib/asiolink/internal/tcpdns.h
  47. 299 0
      src/lib/asiolink/internal/udpdns.h
  48. 64 0
      src/lib/asiolink/ioaddress.cc
  49. 90 0
      src/lib/asiolink/ioaddress.h
  50. 45 0
      src/lib/asiolink/ioendpoint.cc
  51. 123 0
      src/lib/asiolink/ioendpoint.h
  52. 105 0
      src/lib/asiolink/iomessage.h
  53. 69 0
      src/lib/asiolink/iosocket.cc
  54. 129 0
      src/lib/asiolink/iosocket.h
  55. 193 0
      src/lib/asiolink/tcpdns.cc
  56. 39 0
      src/lib/asiolink/tests/Makefile.am
  57. 713 0
      src/lib/asiolink/tests/asiolink_unittest.cc
  58. 28 0
      src/lib/asiolink/tests/run_unittests.cc
  59. 145 0
      src/lib/asiolink/tests/udpdns_unittest.cc
  60. 315 0
      src/lib/asiolink/udpdns.cc
  61. 1 1
      src/lib/cc/session.cc
  62. 2 0
      src/lib/datasrc/Makefile.am
  63. 56 0
      src/lib/datasrc/memory_datasrc.cc
  64. 151 0
      src/lib/datasrc/memory_datasrc.h
  65. 687 0
      src/lib/datasrc/rbtree.h
  66. 3 1
      src/lib/datasrc/tests/Makefile.am
  67. 21 1
      src/lib/datasrc/tests/datasrc_unittest.cc
  68. 115 0
      src/lib/datasrc/tests/memory_datasrc_unittest.cc
  69. 179 0
      src/lib/datasrc/tests/rbtree_unittest.cc
  70. 46 37
      src/lib/datasrc/tests/zonetable_unittest.cc
  71. 27 19
      src/lib/datasrc/zonetable.cc
  72. 197 134
      src/lib/datasrc/zonetable.h
  73. 1 0
      src/lib/dns/Makefile.am
  74. 12 0
      src/lib/dns/buffer.h
  75. 162 0
      src/lib/dns/masterload.cc
  76. 246 0
      src/lib/dns/masterload.h
  77. 8 0
      src/lib/dns/message.h
  78. 1 0
      src/lib/dns/messagerenderer.h
  79. 1 0
      src/lib/dns/tests/Makefile.am
  80. 268 0
      src/lib/dns/tests/masterload_unittest.cc
  81. 1 0
      src/lib/dns/tests/testdata/Makefile.am
  82. 2 1
      src/lib/dns/tests/testdata/edns_toWire4.spec
  83. 5 0
      src/lib/dns/tests/testdata/masterload.txt
  84. 0 2
      src/lib/dns/tests/tsigkey_unittest.cc
  85. 19 1
      src/lib/dns/tests/unittest_util.cc
  86. 15 0
      src/lib/dns/tests/unittest_util.h
  87. 4 0
      src/lib/log/Makefile.am
  88. 37 0
      src/lib/log/dummylog.cc
  89. 60 0
      src/lib/log/dummylog.h
  90. 41 0
      src/lib/nsas/Makefile.am
  91. 7 0
      src/lib/nsas/README
  92. 40 0
      src/lib/nsas/TODO
  93. 46 0
      src/lib/nsas/address_entry.cc
  94. 104 0
      src/lib/nsas/address_entry.h
  95. 74 0
      src/lib/nsas/address_request_callback.h
  96. 60 0
      src/lib/nsas/asiolink.h
  97. 68 0
      src/lib/nsas/fetchable.h
  98. 170 0
      src/lib/nsas/hash.cc
  99. 127 0
      src/lib/nsas/hash.h
  100. 0 0
      src/lib/nsas/hash_deleter.h

+ 53 - 0
ChangeLog

@@ -1,3 +1,56 @@
+  134.  [func]      vorner
+	b10-recurse supports timeouts and retries in forwarder mode.
+	(Trac #401, svn r3660)
+
+  133.  [func]      vorner
+	New temporary logging function available in isc::log. It is used by
+	b10-recurse.
+	(Trac #393, r3602)
+
+  132.  [func]      vorner
+	The b10-recursive is configured through config manager.
+	It has "listen_on" and "forward_addresses" options.
+	(Trac #389, r3448)
+
+  131.  [func]    feng, jerry
+	src/lib/datasrc: Introduced two template classes RBTree and RBNode
+	to provide the generic map with domain name as key and anything as
+	the value. Because of some unresolved design issue, the new classes
+	are only intended to be used by memory zone and zone table.
+	(Trac #397, svn r3890)
+
+  130.	[func]		jerry
+	src/lib/datasrc: Introduced a new class MemoryDataSrc to provide
+	the general interface for memory data source.  For the initial
+	implementation, we don't make it a derived class of AbstractDataSrc
+	because the interface is so different(we'll eventually consider this
+	as part of the generalization work).
+	(Trac #422, svn r3866)
+
+  129.	[func]		jinmei
+	src/lib/dns: Added new functions masterLoad() for loading master
+	zone files.  The initial implementation can only parse a limited
+	form of master files, but BIND 9's named-compilezone can convert
+	any valid zone file into the acceptable form.
+	(Trac #423, svn r3857)
+
+  128.  [build]     vorner
+	Test for query name = '.', type = DS to authoritative nameserver
+	for root zone was added.
+	(Trac #85, svn r3836)
+
+>>>>>>> .merge-right.r3894
+  127.  [bug]       stephen
+	During normal operation process termination and resurrection messages
+	are now output regardless of the state of the verbose flag.
+	(Trac #229, svn r3828)
+
+  126.  [func]      stephen, vorner, ocean
+	The Nameserver Address Store (NSAS) component has been added. It takes
+	care of choosing an IP address of a nameserver when a zone needs to be
+	contacted.
+	(Trac #356, Trac #408, svn r3823)
+
 bind10-devel-20101201 released on December 01, 2010
 
   125.  [func]		jelte

+ 1 - 1
Makefile.am

@@ -37,7 +37,7 @@ report-coverage:
 			\*_unittest.cc \
 			\*_unittests.h \
 		--output report.info
-	$(GENHTML) -o coverage report.info 
+	$(GENHTML) --legend -o coverage report.info 
 
 coverage: clean-coverage perform-coverage report-coverage
 

+ 101 - 4
configure.ac

@@ -195,17 +195,50 @@ fi
 # specify the default warning flags in CXXFLAGS and let specific modules
 # "override" the default.
 
+# This may be used to try linker flags.
+AC_DEFUN([BIND10_CXX_TRY_FLAG], [
+  AC_MSG_CHECKING([whether $CXX supports $1])
+
+  bind10_save_CXXFLAGS="$CXXFLAGS"
+  CXXFLAGS="$CXXFLAGS $1"
+
+  AC_LINK_IFELSE([int main(void){ return 0;} ],
+                 [bind10_cxx_flag=yes], [bind10_cxx_flag=no])
+  CXXFLAGS="$bind10_save_CXXFLAGS"
+
+  if test "x$bind10_cxx_flag" = "xyes"; then
+    ifelse([$2], , :, [$2])
+  else
+    ifelse([$3], , :, [$3])
+  fi
+
+  AC_MSG_RESULT([$bind10_cxx_flag])
+])
+
 werror_ok=0
 
 # SunStudio compiler requires special compiler options for boost
 # (http://blogs.sun.com/sga/entry/boost_mini_howto)
 if test "$SUNCXX" = "yes"; then
 CXXFLAGS="$CXXFLAGS -library=stlport4 -features=tmplife -features=tmplrefstatic"
+MULTITHREADING_FLAG="-mt"
 fi
 
+BIND10_CXX_TRY_FLAG(-Wno-missing-field-initializers,
+	[WARNING_NO_MISSING_FIELD_INITIALIZERS_CFLAG="-Wno-missing-field-initializers"])
+AC_SUBST(WARNING_NO_MISSING_FIELD_INITIALIZERS_CFLAG)
+
 # gcc specific settings:
 if test "X$GXX" = "Xyes"; then
 B10_CXXFLAGS="-Wall -Wextra -Wwrite-strings -Woverloaded-virtual -Wno-sign-compare"
+case "$host" in
+*-solaris*)
+	MULTITHREADING_FLAG=-pthreads
+	;;
+*)
+	MULTITHREADING_FLAG=-pthread
+	;;
+esac
 
 # Certain versions of gcc (g++) have a bug that incorrectly warns about
 # the use of anonymous name spaces even if they're closed in a single
@@ -318,11 +351,67 @@ if test "${boost_include_path}" ; then
 	BOOST_INCLUDES="-I${boost_include_path}"
 	CPPFLAGS="$CPPFLAGS $BOOST_INCLUDES"
 fi
-AC_CHECK_HEADERS([boost/shared_ptr.hpp boost/foreach.hpp],,
+AC_CHECK_HEADERS([boost/shared_ptr.hpp boost/foreach.hpp boost/interprocess/sync/interprocess_upgradable_mutex.hpp],,
   AC_MSG_ERROR([Missing required header files.]))
 CPPFLAGS="$CPPFLAGS_SAVES"
 AC_SUBST(BOOST_INCLUDES)
 
+# Using boost::mutex can result in requiring libboost_thread with older
+# versions of Boost.  We'd like to avoid relying on a compiled Boost library
+# whenever possible, so we need to check for it step by step.
+#
+# NOTE: another fix of this problem is to simply require newer versions of
+# boost.  If we choose that solution we should simplify the following tricky
+# checks accordingly and all Makefile.am's that refer to NEED_LIBBOOST_THREAD.
+AC_MSG_CHECKING(for boost::mutex)
+CPPFLAGS_SAVES="$CPPFLAGS"
+LIBS_SAVES="$LIBS"
+CPPFLAGS="$BOOST_INCLUDES $CPPFLAGS $MULTITHREADING_FLAG"
+need_libboost_thread=0
+need_sunpro_workaround=0
+AC_TRY_LINK([
+#include <boost/thread.hpp>
+],[
+boost::mutex m;
+],
+	[ AC_MSG_RESULT(yes (without libboost_thread)) ],
+
+    # there is one specific problem with SunStudio 5.10
+    # where including boost/thread causes a compilation failure
+    # There is a workaround in boost but it checks the version not being 5.10
+    # This will probably be fixed in the future, in which case this
+    # is only a temporary workaround
+    [ AC_TRY_LINK([
+#if defined(__SUNPRO_CC) && __SUNPRO_CC == 0x5100
+#undef __SUNPRO_CC
+#define __SUNPRO_CC 0x5090
+#endif
+#include <boost/thread.hpp>
+],[
+boost::mutex m;
+],
+    [ AC_MSG_RESULT(yes (with SUNOS workaround))
+      need_sunpro_workaround=1 ],
+    	[ LIBS=" $LIBS -lboost_thread"
+	  AC_TRY_LINK([
+#include <boost/thread.hpp>
+],[
+boost::mutex m;
+],
+		  [ AC_MSG_RESULT(yes (with libboost_thread))
+		    need_libboost_thread=1 ],
+		  [ AC_MSG_RESULT(no)
+		    AC_MSG_ERROR([boost::mutex cannot be linked in this build environment.
+Perhaps you are using an older version of Boost that requires libboost_thread for the mutex support, which does not appear to be available.
+You may want to check the availability of the library or to upgrade Boost.])
+   		  ])])])
+CPPFLAGS="$CPPFLAGS_SAVES"
+LIBS="$LIBS_SAVES"
+AM_CONDITIONAL(NEED_LIBBOOST_THREAD, test $need_libboost_thread = 1)
+if test $need_sunpro_workaround = 1; then
+    AC_DEFINE([NEED_SUNPRO_WORKAROUND], [], [Need boost sunstudio workaround])
+fi
+
 #
 # Check availability of gtest, which will be used for unit tests.
 #
@@ -388,6 +477,8 @@ PTHREAD_LDFLAGS=
 AC_CHECK_LIB(pthread, pthread_create,[ PTHREAD_LDFLAGS=-lpthread ], [])
 AC_SUBST(PTHREAD_LDFLAGS)
 
+AC_SUBST(MULTITHREADING_FLAG)
+
 #
 # ASIO: we extensively use it as the C++ event management module.
 #
@@ -465,9 +556,9 @@ AC_CONFIG_FILES([Makefile
                  src/bin/msgq/tests/Makefile
                  src/bin/auth/Makefile
                  src/bin/auth/tests/Makefile
-                 src/bin/auth/tests/testdata/Makefile
                  src/bin/auth/benchmarks/Makefile
                  src/bin/recurse/Makefile
+                 src/bin/recurse/tests/Makefile
                  src/bin/xfrin/Makefile
                  src/bin/xfrin/tests/Makefile
                  src/bin/xfrout/Makefile
@@ -484,6 +575,8 @@ AC_CONFIG_FILES([Makefile
                  src/bin/usermgr/Makefile
                  src/bin/tests/Makefile
                  src/lib/Makefile
+                 src/lib/asiolink/Makefile
+                 src/lib/asiolink/tests/Makefile
                  src/lib/bench/Makefile
                  src/lib/bench/example/Makefile
                  src/lib/bench/tests/Makefile
@@ -518,6 +611,11 @@ AC_CONFIG_FILES([Makefile
                  src/lib/datasrc/Makefile
                  src/lib/datasrc/tests/Makefile
                  src/lib/xfr/Makefile
+                 src/lib/log/Makefile
+                 src/lib/testutils/Makefile
+                 src/lib/testutils/testdata/Makefile
+                 src/lib/nsas/Makefile
+                 src/lib/nsas/tests/Makefile
                ])
 AC_OUTPUT([src/bin/cfgmgr/b10-cfgmgr.py
            src/bin/cfgmgr/tests/b10-cfgmgr_test.py
@@ -532,9 +630,8 @@ AC_OUTPUT([src/bin/cfgmgr/b10-cfgmgr.py
            src/bin/xfrout/xfrout.spec.pre
            src/bin/xfrout/tests/xfrout_test
            src/bin/xfrout/run_b10-xfrout.sh
-           src/bin/recurse/recurse.py
            src/bin/recurse/recurse.spec.pre
-           src/bin/recurse/run_b10-recurse.sh
+           src/bin/recurse/spec_config.h.pre
            src/bin/zonemgr/zonemgr.py
            src/bin/zonemgr/zonemgr.spec.pre
            src/bin/zonemgr/tests/zonemgr_test

+ 1 - 1
doc/Doxyfile

@@ -568,7 +568,7 @@ WARN_LOGFILE           =
 # directories like "/usr/src/myproject". Separate the files or directories
 # with spaces.
 
-INPUT                  = ../src/lib/cc ../src/lib/config ../src/lib/dns ../src/lib/exceptions ../src/lib/datasrc ../src/bin/auth ../src/lib/bench
+INPUT                  = ../src/lib/cc ../src/lib/config ../src/lib/dns ../src/lib/exceptions ../src/lib/datasrc ../src/bin/auth ../src/lib/bench ../src/lib/log ../src/lib/asiolink/ ../src/lib/nsas
 
 # This tag can be used to specify the character encoding of the source files
 # that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is

+ 1 - 1
doc/guide/bind10-guide.html

@@ -128,7 +128,7 @@
             libraries, to build BIND 10 from source code.
           </p></div><p>
           Building from source code requires the Boost
-          build-time headers. At least Boost version 1.34 is required.
+          build-time headers. At least Boost version 1.35 is required.
   
   
         </p><p>

+ 1 - 1
doc/guide/bind10-guide.xml

@@ -274,7 +274,7 @@ var/
 
         <para>
           Building from source code requires the Boost
-          build-time headers. At least Boost version 1.34 is required.
+          build-time headers. At least Boost version 1.35 is required.
   <!-- TODO: we don't check for this version -->
   <!-- NOTE: jreed has tested with 1.34, 1.38, and 1.41. -->
         </para>

+ 4 - 23
src/bin/auth/Makefile.am

@@ -3,8 +3,9 @@ SUBDIRS = . tests benchmarks
 AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
 AM_CPPFLAGS += -I$(top_srcdir)/src/bin -I$(top_builddir)/src/bin
 AM_CPPFLAGS += -I$(top_srcdir)/src/lib/dns -I$(top_builddir)/src/lib/dns
-AM_CPPFLAGS += -I$(top_builddir)/src/lib/cc
-AM_CPPFLAGS += -I$(top_srcdir)/src/bin -I$(top_builddir)/src/bin
+AM_CPPFLAGS += -I$(top_srcdir)/src/lib/cc -I$(top_builddir)/src/lib/cc
+AM_CPPFLAGS += -I$(top_srcdir)/src/lib/asiolink
+AM_CPPFLAGS += -I$(top_builddir)/src/lib/asiolink
 AM_CPPFLAGS += $(BOOST_INCLUDES)
 
 AM_CXXFLAGS = $(B10_CXXFLAGS)
@@ -33,26 +34,6 @@ auth.spec: auth.spec.pre
 spec_config.h: spec_config.h.pre
 	$(SED) -e "s|@@LOCALSTATEDIR@@|$(localstatedir)|" spec_config.h.pre >$@
 
-# This is a wrapper library solely used for b10-auth.  The ASIO header files
-# have some code fragments that would hit gcc's unused-parameter warning,
-# which would make the build fail with -Werror (our default setting).
-# We don't want to lower the warning level for our own code just for ASIO,
-# so as a workaround we extract the ASIO related code into a separate library,
-# only for which we accept the unused-parameter warning.
-lib_LIBRARIES = libasio_link.a
-libasio_link_a_SOURCES = asio_link.cc asio_link.h
-# Note: the ordering matters: -Wno-... must follow -Wextra (defined in
-# B10_CXXFLAGS)
-libasio_link_a_CXXFLAGS = $(AM_CXXFLAGS)
-if USE_GXX
-libasio_link_a_CXXFLAGS += -Wno-unused-parameter
-endif
-if USE_CLANGPP
-# Same for clang++, but we need to turn off -Werror completely.
-libasio_link_a_CXXFLAGS += -Wno-error
-endif
-libasio_link_a_CPPFLAGS = $(AM_CPPFLAGS)
-
 BUILT_SOURCES = spec_config.h 
 pkglibexec_PROGRAMS = b10-auth
 b10_auth_SOURCES = auth_srv.cc auth_srv.h
@@ -65,7 +46,7 @@ b10_auth_LDADD += $(top_builddir)/src/lib/dns/libdns++.la
 b10_auth_LDADD += $(top_builddir)/src/lib/config/libcfgclient.la
 b10_auth_LDADD += $(top_builddir)/src/lib/cc/libcc.la
 b10_auth_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
-b10_auth_LDADD += libasio_link.a
+b10_auth_LDADD += $(top_builddir)/src/lib/asiolink/libasiolink.la
 b10_auth_LDADD += $(top_builddir)/src/lib/xfr/libxfr.la
 b10_auth_LDADD += $(SQLITE_LIBS)
 

+ 0 - 669
src/bin/auth/asio_link.cc

@@ -1,669 +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.
-
-// $Id$
-
-#include <config.h>
-
-#include <unistd.h>             // for some IPC/network system calls
-#include <sys/socket.h>
-#include <netinet/in.h>
-
-#include <asio.hpp>
-#include <boost/lexical_cast.hpp>
-#include <boost/bind.hpp>
-
-#include <boost/shared_ptr.hpp>
-
-#include <dns/buffer.h>
-#include <dns/message.h>
-#include <dns/messagerenderer.h>
-
-#include <asio_link.h>
-
-#include <auth/auth_srv.h>
-#include <auth/common.h>
-
-using namespace asio;
-using asio::ip::udp;
-using asio::ip::tcp;
-
-using namespace std;
-using namespace isc::dns;
-
-namespace asio_link {
-IOAddress::IOAddress(const string& address_str)
-    // XXX: we cannot simply construct the address in the initialization list
-    // because we'd like to throw our own exception on failure.
-{
-    error_code err;
-    asio_address_ = ip::address::from_string(address_str, err);
-    if (err) {
-        isc_throw(IOError, "Failed to convert string to address '"
-                  << address_str << "': " << err.message());
-    }
-}
-
-IOAddress::IOAddress(const ip::address& asio_address) :
-    asio_address_(asio_address)
-{}
-
-string
-IOAddress::toText() const {
-    return (asio_address_.to_string());
-}
-
-/// \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 tcp::endpoint(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 tcp::endpoint& asio_endpoint) :
-        asio_endpoint_placeholder_(NULL), asio_endpoint_(asio_endpoint)
-    {}
-
-    /// \brief The destructor.        
-    ~TCPEndpoint() { delete asio_endpoint_placeholder_; }
-    //@}
-
-    virtual IOAddress getAddress() const {
-        return (asio_endpoint_.address());
-    }
-private:
-    const tcp::endpoint* asio_endpoint_placeholder_;
-    const tcp::endpoint& asio_endpoint_;
-};
-
-/// \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 udp::endpoint(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 udp::endpoint& asio_endpoint) :
-        asio_endpoint_placeholder_(NULL), asio_endpoint_(asio_endpoint)
-    {}
-
-    /// \brief The destructor.
-    ~UDPEndpoint() { delete asio_endpoint_placeholder_; }
-    //@}
-
-    virtual IOAddress getAddress() const {
-        return (asio_endpoint_.address());
-    }
-private:
-    const udp::endpoint* asio_endpoint_placeholder_;
-    const udp::endpoint& asio_endpoint_;
-};
-
-const IOEndpoint*
-IOEndpoint::create(const int protocol, const IOAddress& address,
-                   const unsigned short port)
-{
-    if (protocol == IPPROTO_UDP) {
-        return (new UDPEndpoint(address, port));
-    } else if (protocol == IPPROTO_TCP) {
-        return (new TCPEndpoint(address, port));
-    }
-    isc_throw(IOError,
-              "IOEndpoint creation attempt for unsupported protocol: " <<
-              protocol);
-}
-
-/// \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(tcp::socket& socket) : socket_(socket) {}
-
-    virtual int getNative() const { return (socket_.native()); }
-    virtual int getProtocol() const { return (IPPROTO_TCP); }
-private:
-    tcp::socket& socket_;
-};
-
-/// \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(udp::socket& socket) : socket_(socket) {}
-
-    virtual int getNative() const { return (socket_.native()); }
-    virtual int getProtocol() const { return (IPPROTO_UDP); }
-private:
-    udp::socket& socket_;
-};
-
-/// \brief The \c DummySocket class is a concrete derived class of
-/// \c IOSocket that is not associated with any real socket.
-///
-/// This main purpose of this class is tests, where it may be desirable to
-/// instantiate an \c IOSocket object without involving system resource
-/// allocation such as real network sockets.
-class DummySocket : public IOSocket {
-private:
-    DummySocket(const DummySocket& source);
-    DummySocket& operator=(const DummySocket& source);
-public:
-    /// \brief Constructor from the protocol number.
-    ///
-    /// The protocol must validly identify a standard network protocol.
-    /// For example, to specify TCP \c protocol must be \c IPPROTO_TCP.
-    ///
-    /// \param protocol The network protocol number for the socket.
-    DummySocket(const int protocol) : protocol_(protocol) {}
-
-    /// \brief A dummy derived method of \c IOSocket::getNative().
-    ///
-    /// This version of method always returns -1 as the object is not
-    /// associated with a real (native) socket.
-    virtual int getNative() const { return (-1); }
-
-    virtual int getProtocol() const { return (protocol_); }
-private:
-    const int protocol_;
-};
-
-IOSocket&
-IOSocket::getDummyUDPSocket() {
-    static DummySocket socket(IPPROTO_UDP);
-    return (socket);
-}
-
-IOSocket&
-IOSocket::getDummyTCPSocket() {
-    static DummySocket socket(IPPROTO_TCP);
-    return (socket);
-}
-
-IOMessage::IOMessage(const void* data, const size_t data_size,
-                     IOSocket& io_socket, const IOEndpoint& remote_endpoint) :
-    data_(data), data_size_(data_size), io_socket_(io_socket),
-    remote_endpoint_(remote_endpoint)
-{}
-
-//
-// Helper classes for asynchronous I/O using asio
-//
-class TCPClient {
-public:
-    TCPClient(AuthSrv* auth_server, io_service& io_service) :
-        auth_server_(auth_server),
-        socket_(io_service),
-        io_socket_(socket_),
-        response_buffer_(0),
-        responselen_buffer_(TCP_MESSAGE_LENGTHSIZE),
-        response_renderer_(response_buffer_),
-        dns_message_(Message::PARSE),
-        custom_callback_(NULL)
-    {}
-
-    void start() {
-        // Check for queued configuration commands
-        if (auth_server_ != NULL &&
-            auth_server_->getConfigSession()->hasQueuedMsgs()) {
-            auth_server_->getConfigSession()->checkCommand();
-        }
-        async_read(socket_, asio::buffer(data_, TCP_MESSAGE_LENGTHSIZE),
-                   boost::bind(&TCPClient::headerRead, this,
-                               placeholders::error,
-                               placeholders::bytes_transferred));
-    }
-
-    tcp::socket& getSocket() { return (socket_); }
-
-    void headerRead(const asio::error_code& error,
-                    size_t bytes_transferred)
-    {
-        if (!error) {
-            InputBuffer dnsbuffer(data_, bytes_transferred);
-
-            uint16_t msglen = dnsbuffer.readUint16();
-            async_read(socket_, asio::buffer(data_, msglen),
-                       boost::bind(&TCPClient::requestRead, this,
-                                   placeholders::error,
-                                   placeholders::bytes_transferred));
-        } else {
-            delete this;
-        }
-    }
-
-    void requestRead(const asio::error_code& error,
-                     size_t bytes_transferred)
-    {
-        if (!error) {
-            const TCPEndpoint remote_endpoint(socket_.remote_endpoint());
-            const IOMessage io_message(data_, bytes_transferred, io_socket_,
-                                       remote_endpoint);
-            // currently, for testing purpose only
-            if (custom_callback_ != NULL) {
-                (*custom_callback_)(io_message);
-                start();
-                return;
-            }
-
-            if (auth_server_->processMessage(io_message, dns_message_,
-                                             response_renderer_)) {
-                responselen_buffer_.writeUint16(
-                    response_buffer_.getLength());
-                async_write(socket_,
-                            asio::buffer(
-                                responselen_buffer_.getData(),
-                                responselen_buffer_.getLength()),
-                            boost::bind(&TCPClient::responseWrite, this,
-                                        placeholders::error));
-            } else {
-                delete this;
-            }
-        } else {
-            delete this;
-        }
-    }
-
-    void responseWrite(const asio::error_code& error) {
-        if (!error) {
-                async_write(socket_,
-                            asio::buffer(response_buffer_.getData(),
-                                         response_buffer_.getLength()),
-                            boost::bind(&TCPClient::handleWrite, this,
-                                        placeholders::error));
-        } else {
-            delete this;
-        }
-    }
-
-    void handleWrite(const asio::error_code& error) {
-        if (!error) {
-            start();            // handle next request, if any.
-      } else {
-            delete this;
-      }
-    }
-
-    // Currently this is for tests only
-    void setCallBack(const IOService::IOCallBack* callback) {
-        custom_callback_ = callback;
-    }
-
-private:
-    AuthSrv* auth_server_;
-    tcp::socket socket_;
-    TCPSocket io_socket_;
-    OutputBuffer response_buffer_;
-    OutputBuffer responselen_buffer_;
-    MessageRenderer response_renderer_;
-    Message dns_message_;
-    enum { MAX_LENGTH = 65535 };
-    static const size_t TCP_MESSAGE_LENGTHSIZE = 2;
-    char data_[MAX_LENGTH];
-
-    // currently, for testing purpose only.
-    const IOService::IOCallBack* custom_callback_;
-};
-
-class TCPServer {
-public:
-    TCPServer(AuthSrv* auth_server, io_service& io_service,
-              const ip::address& addr, const uint16_t port) :
-        auth_server_(auth_server), io_service_(io_service),
-        acceptor_(io_service_), listening_(new TCPClient(auth_server_,
-                                                         io_service_)),
-        custom_callback_(NULL)
-    {
-        tcp::endpoint endpoint(addr, port);
-        acceptor_.open(endpoint.protocol());
-        // Set v6-only (we use a different instantiation for v4,
-        // otherwise asio will bind to both v4 and v6
-        if (addr.is_v6()) {
-            acceptor_.set_option(ip::v6_only(true));
-        }
-        acceptor_.set_option(tcp::acceptor::reuse_address(true));
-        acceptor_.bind(endpoint);
-        acceptor_.listen();
-        acceptor_.async_accept(listening_->getSocket(),
-                               boost::bind(&TCPServer::handleAccept, this,
-                                           listening_, placeholders::error));
-    }
-
-    ~TCPServer() { delete listening_; }
-
-    void handleAccept(TCPClient* new_client,
-                      const asio::error_code& error)
-    {
-        if (!error) {
-            assert(new_client == listening_);
-            new_client->setCallBack(custom_callback_);
-            new_client->start();
-            listening_ = new TCPClient(auth_server_, io_service_);
-            acceptor_.async_accept(listening_->getSocket(),
-                                   boost::bind(&TCPServer::handleAccept,
-                                               this, listening_,
-                                               placeholders::error));
-        } else {
-            delete new_client;
-        }
-    }
-
-    // Currently this is for tests only
-    void setCallBack(const IOService::IOCallBack* callback) {
-        custom_callback_ = callback;
-    }
-
-private:
-    AuthSrv* auth_server_;
-    io_service& io_service_;
-    tcp::acceptor acceptor_;
-    TCPClient* listening_;
-
-    // currently, for testing purpose only.
-    const IOService::IOCallBack* custom_callback_;
-};
-
-class UDPServer {
-public:
-    UDPServer(AuthSrv* auth_server, io_service& io_service,
-              const ip::address& addr, const uint16_t port) :
-        auth_server_(auth_server),
-        io_service_(io_service),
-        socket_(io_service, addr.is_v6() ? udp::v6() : udp::v4()),
-        io_socket_(socket_),
-        response_buffer_(0),
-        response_renderer_(response_buffer_),
-        dns_message_(Message::PARSE),
-        custom_callback_(NULL)
-    {
-        socket_.set_option(socket_base::reuse_address(true));
-        // Set v6-only (we use a different instantiation for v4,
-        // otherwise asio will bind to both v4 and v6
-        if (addr.is_v6()) {
-            socket_.set_option(asio::ip::v6_only(true));
-            socket_.bind(udp::endpoint(addr, port));
-        } else {
-            socket_.bind(udp::endpoint(addr, port));
-        }
-        startReceive();
-    }
-
-    void handleRequest(const asio::error_code& error,
-                       size_t bytes_recvd)
-    {
-        // Check for queued configuration commands
-        if (auth_server_ != NULL &&
-            auth_server_->getConfigSession()->hasQueuedMsgs()) {
-            auth_server_->getConfigSession()->checkCommand();
-        }
-        if (!error && bytes_recvd > 0) {
-            const UDPEndpoint remote_endpoint(sender_endpoint_);
-            const IOMessage io_message(data_, bytes_recvd, io_socket_,
-                                       remote_endpoint);
-            // currently, for testing purpose only
-            if (custom_callback_ != NULL) {
-                (*custom_callback_)(io_message);
-                startReceive();
-                return;
-            }
-
-            dns_message_.clear(Message::PARSE);
-            response_renderer_.clear();
-            if (auth_server_->processMessage(io_message, dns_message_,
-                                             response_renderer_)) {
-                socket_.async_send_to(
-                    asio::buffer(response_buffer_.getData(),
-                                        response_buffer_.getLength()),
-                    sender_endpoint_,
-                    boost::bind(&UDPServer::sendCompleted,
-                                this,
-                                placeholders::error,
-                                placeholders::bytes_transferred));
-            } else {
-                startReceive();
-            }
-        } else {
-            startReceive();
-        }
-    }
-
-    void sendCompleted(const asio::error_code&, size_t) {
-        // Even if error occurred there's nothing to do.  Simply handle
-        // the next request.
-        startReceive();
-    }
-
-    // Currently this is for tests only
-    void setCallBack(const IOService::IOCallBack* callback) {
-        custom_callback_ = callback;
-    }
-private:
-    void startReceive() {
-        socket_.async_receive_from(
-            asio::buffer(data_, MAX_LENGTH), sender_endpoint_,
-            boost::bind(&UDPServer::handleRequest, this,
-                        placeholders::error,
-                        placeholders::bytes_transferred));
-    }
-
-private:
-    AuthSrv* auth_server_;
-    io_service& io_service_;
-    udp::socket socket_;
-    UDPSocket io_socket_;
-    OutputBuffer response_buffer_;
-    MessageRenderer response_renderer_;
-    Message dns_message_;
-    udp::endpoint sender_endpoint_;
-    enum { MAX_LENGTH = 4096 };
-    char data_[MAX_LENGTH];
-
-    // currently, for testing purpose only.
-    const IOService::IOCallBack* custom_callback_;
-};
-
-class IOServiceImpl {
-public:
-    IOServiceImpl(AuthSrv* auth_server, const char& port,
-                  const ip::address* v4addr, const ip::address* v6addr);
-    asio::io_service io_service_;
-    AuthSrv* auth_server_;
-
-    typedef boost::shared_ptr<UDPServer> UDPServerPtr;
-    typedef boost::shared_ptr<TCPServer> TCPServerPtr;
-    UDPServerPtr udp4_server_;
-    UDPServerPtr udp6_server_;
-    TCPServerPtr tcp4_server_;
-    TCPServerPtr tcp6_server_;
-
-    // This member is used only for testing at the moment.
-    IOService::IOCallBack callback_;
-};
-
-IOServiceImpl::IOServiceImpl(AuthSrv* auth_server, const char& port,
-                             const ip::address* const v4addr,
-                             const ip::address* const v6addr) :
-    auth_server_(auth_server),
-    udp4_server_(UDPServerPtr()), udp6_server_(UDPServerPtr()),
-    tcp4_server_(TCPServerPtr()), tcp6_server_(TCPServerPtr())
-{
-    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());
-    }
-
-    try {
-        if (v4addr != NULL) {
-            udp4_server_ = UDPServerPtr(new UDPServer(auth_server, io_service_,
-                                                      *v4addr, portnum));
-            tcp4_server_ = TCPServerPtr(new TCPServer(auth_server, io_service_,
-                                                      *v4addr, portnum));
-        }
-        if (v6addr != NULL) {
-            udp6_server_ = UDPServerPtr(new UDPServer(auth_server, io_service_,
-                                                      *v6addr, portnum));
-            tcp6_server_ = TCPServerPtr(new TCPServer(auth_server, io_service_,
-                                                      *v6addr, portnum));
-        }
-    } 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());
-    }
-}
-
-IOService::IOService(AuthSrv* auth_server, const char& port,
-                     const char& address) :
-    impl_(NULL)
-{
-    error_code err;
-    const ip::address addr = ip::address::from_string(&address, err);
-    if (err) {
-        isc_throw(IOError, "Invalid IP address '" << &address << "': "
-                  << err.message());
-    }
-
-    impl_ = new IOServiceImpl(auth_server, port,
-                              addr.is_v4() ? &addr : NULL,
-                              addr.is_v6() ? &addr : NULL);
-}
-
-IOService::IOService(AuthSrv* auth_server, const char& port,
-                     const bool use_ipv4, const bool use_ipv6) :
-    impl_(NULL)
-{
-    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 IOServiceImpl(auth_server, port, v4addrp, v6addrp);
-}
-
-IOService::~IOService() {
-    delete impl_;
-}
-
-void
-IOService::run() {
-    impl_->io_service_.run();
-}
-
-void
-IOService::stop() {
-    impl_->io_service_.stop();
-}
-
-asio::io_service&
-IOService::get_io_service() {
-    return (impl_->io_service_);
-}
-
-void
-IOService::setCallBack(const IOCallBack callback) {
-    impl_->callback_ = callback;
-    if (impl_->udp4_server_ != NULL) {
-        impl_->udp4_server_->setCallBack(&impl_->callback_);
-    }
-    if (impl_->udp6_server_ != NULL) {
-        impl_->udp6_server_->setCallBack(&impl_->callback_);
-    }
-    if (impl_->tcp4_server_ != NULL) {
-        impl_->tcp4_server_->setCallBack(&impl_->callback_);
-    }
-    if (impl_->tcp6_server_ != NULL) {
-        impl_->tcp6_server_->setCallBack(&impl_->callback_);
-    }
-}
-}

+ 0 - 452
src/bin/auth/asio_link.h

@@ -1,452 +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.
-
-// $Id$
-
-#ifndef __ASIO_LINK_H
-#define __ASIO_LINK_H 1
-
-// 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 <functional>
-#include <string>
-
-#include <boost/function.hpp>
-
-#include <exceptions/exceptions.h>
-
-namespace asio {
-// forward declaration for IOService::get_io_service() below
-class io_service;
-}
-
-class AuthSrv;
-
-/// \namespace asio_link
-/// \brief A wrapper interface for the ASIO library.
-///
-/// The \c asio_link namespace is used to define a set of wrapper interfaces
-/// for the ASIO library.
-///
-/// BIND 10 uses the non-Boost version of ASIO because it's header-only,
-/// i.e., does not require a separate library object to be linked, and thus
-/// lowers the bar for introduction.
-///
-/// But the advantage comes with its own costs: since the header-only version
-/// includes more definitions in public header files, it tends to trigger
-/// more compiler warnings for our own sources, and, depending on the
-/// compiler options, may make the build fail.
-///
-/// We also found it may be tricky to use ASIO and standard C++ libraries
-/// in a single translation unit, i.e., a .cc file: depending on the order
-/// of including header files, ASIO may or may not work on some platforms.
-///
-/// This wrapper interface is intended to centralize these
-/// problematic issues in a single sub module.  Other BIND 10 modules should
-/// simply include \c asio_link.h and use the wrapper API instead of
-/// including ASIO header files and using ASIO-specific classes directly.
-///
-/// This wrapper may be used for other IO libraries if and when we want to
-/// switch, but generality for that purpose is not the primary goal of
-/// this module.  The resulting interfaces are thus straightforward mapping
-/// to the ASIO counterparts.
-///
-/// Notes to developers:
-/// Currently the wrapper interface is specific to the authoritative
-/// server implementation.  But the plan is to generalize it and have
-/// other modules use it.
-///
-/// One obvious drawback of this approach is performance overhead
-/// due to the additional layer.  We should eventually evaluate the cost
-/// of the wrapper abstraction in benchmark tests. Another drawback is
-/// that the wrapper interfaces don't provide all features of ASIO
-/// (at least for the moment).  We should also re-evaluate the
-/// maintenance overhead of providing necessary wrappers as we develop
-/// more.
-///
-/// On the other hand, we may be able to exploit the wrapper approach to
-/// simplify the interfaces (by limiting the usage) and unify performance
-/// optimization points.
-///
-/// As for optimization, we may want to provide a custom allocator for
-/// the placeholder of callback handlers:
-/// http://think-async.com/Asio/asio-1.3.1/doc/asio/reference/asio_handler_allocate.html
-
-namespace asio_link {
-class IOServiceImpl;
-
-/// \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
-/// ASIO specific exceptions.
-class IOError : public isc::Exception {
-public:
-    IOError(const char* file, size_t line, const char* what) :
-        isc::Exception(file, line, what) {}
-};
-
-/// \brief The \c IOAddress class represents an IP addresses (version
-/// agnostic)
-///
-/// This class is a wrapper for the ASIO \c ip::address class.
-class IOAddress {
-public:
-    ///
-    /// \name Constructors and Destructor
-    ///
-    /// This class is copyable.  We use default versions of copy constructor
-    /// and the assignment operator.
-    /// We use the default destructor.
-    //@{
-    /// \brief Constructor from string.
-    ///
-    /// This constructor converts a textual representation of IPv4 and IPv6
-    /// addresses into an IOAddress object.
-    /// If \c address_str is not a valid representation of any type of
-    /// address, an exception of class \c IOError will be thrown.
-    /// This constructor allocates memory for the object, and if that fails
-    /// a corresponding standard exception will be thrown.
-    ///
-    /// \param address_str Textual representation of address.
-    IOAddress(const std::string& address_str);
-
-    /// \brief Constructor from an ASIO \c ip::address object.
-    ///
-    /// This constructor is intended to be used within the wrapper
-    /// implementation; user applications of the wrapper API won't use it.
-    ///
-    /// This constructor never throws an exception.
-    ///
-    /// \param asio_address The ASIO \c ip::address to be converted.
-    IOAddress(const asio::ip::address& asio_address);
-    //@}
-
-    /// \brief Convert the address to a string.
-    ///
-    /// This method is basically expected to be exception free, but
-    /// generating the string will involve resource allocation,
-    /// and if it fails the corresponding standard exception will be thrown.
-    ///
-    /// \return A string representation of the address.
-    std::string toText() const;
-private:
-    asio::ip::address asio_address_;
-};
-
-/// \brief The \c IOEndpoint class is an abstract base class to represent
-/// a communication endpoint.
-///
-/// This class is a wrapper for the ASIO endpoint classes such as
-/// \c ip::tcp::endpoint and \c ip::udp::endpoint.
-///
-/// Derived class implementations are completely hidden within the
-/// implementation.  User applications only get access to concrete
-/// \c IOEndpoint objects via the abstract interfaces.
-class IOEndpoint {
-    ///
-    /// \name Constructors and Destructor
-    ///
-    /// Note: The copy constructor and the assignment operator are
-    /// intentionally defined as private, making this class non-copyable.
-    //@{
-private:
-    IOEndpoint(const IOEndpoint& source);
-    IOEndpoint& operator=(const IOEndpoint& 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).
-    IOEndpoint() {}
-public:
-    /// The destructor.
-    virtual ~IOEndpoint() {}
-    //@}
-
-    /// \brief Returns the address of the endpoint.
-    ///
-    /// This method returns an IOAddress object corresponding to \c this
-    /// endpoint.
-    /// Note that the return value is a real object, not a reference or
-    /// a pointer.
-    /// This is aligned with the interface of the ASIO counterpart:
-    /// the \c address() method of \c ip::xxx::endpoint classes returns
-    /// an \c ip::address object.
-    /// This also means handling the address of an endpoint using this method
-    /// can be expensive.  If the address information is necessary in a
-    /// performance sensitive context and there's a more efficient interface
-    /// for that purpose, it's probably better to avoid using this method.
-    ///
-    /// This method never throws an exception.
-    ///
-    /// \return A copy of \c IOAddress object corresponding to the endpoint.
-    virtual IOAddress getAddress() const = 0;
-
-    /// \brief A polymorphic factory of endpoint from address and port.
-    ///
-    /// This method creates a new instance of (a derived class of)
-    /// \c IOEndpoint object that identifies the pair of given address
-    /// and port.
-    /// The appropriate derived class is chosen based on the specified
-    /// transport protocol.  If the \c protocol doesn't specify a protocol
-    /// supported in this implementation, an exception of class \c IOError
-    /// will be thrown.
-    ///
-    /// Memory for the created object will be dynamically allocated.  It's
-    /// the caller's responsibility to \c delete it later.
-    /// If resource allocation for the new object fails, a corresponding
-    /// standard exception will be thrown.
-    ///
-    /// \param protocol The transport protocol used for the endpoint.
-    /// Currently, only \c IPPROTO_UDP and \c IPPROTO_TCP can be specified.
-    /// \param address The (IP) address of the endpoint.
-    /// \param port The transport port number of the endpoint
-    /// \return A pointer to a newly created \c IOEndpoint object.
-    static const IOEndpoint* create(const int protocol,
-                                    const IOAddress& address,
-                                    const unsigned short port);
-};
-
-/// \brief The \c IOSocket class is an abstract base class to represent
-/// various types of network sockets.
-///
-/// This class is a wrapper for the ASIO socket classes such as
-/// \c ip::tcp::socket and \c ip::udp::socket.
-///
-/// Derived class implementations are completely hidden within the
-/// implementation.  User applications only get access to concrete
-/// \c IOSocket objects via the abstract interfaces.
-/// We may revisit this decision when we generalize the wrapper and more
-/// modules use it.  Also, at that point we may define a separate (visible)
-/// derived class for testing purposes rather than providing factory methods
-/// (i.e., getDummy variants below).
-class IOSocket {
-    ///
-    /// \name Constructors and Destructor
-    ///
-    /// Note: The copy constructor and the assignment operator are
-    /// intentionally defined as private, making this class non-copyable.
-    //@{
-private:
-    IOSocket(const IOSocket& source);
-    IOSocket& operator=(const IOSocket& 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).
-    IOSocket() {}
-public:
-    /// The destructor.
-    virtual ~IOSocket() {}
-    //@}
-
-    /// \brief Return the "native" representation of the socket.
-    ///
-    /// In practice, this is the file descriptor of the socket for
-    /// UNIX-like systems so the current implementation simply uses
-    /// \c int as the type of the return value.
-    /// We may have to need revisit this decision later.
-    ///
-    /// In general, the application should avoid using this method;
-    /// it essentially discloses an implementation specific "handle" that
-    /// can change the internal state of the socket (consider the
-    /// application closes it, for example).
-    /// But we sometimes need to perform very low-level operations that
-    /// requires the native representation.  Passing the file descriptor
-    /// to a different process is one example.
-    /// This method is provided as a necessary evil for such limited purposes.
-    ///
-    /// This method never throws an exception.
-    ///
-    /// \return The native representation of the socket.  This is the socket
-    /// file descriptor for UNIX-like systems.
-    virtual int getNative() const = 0;
-
-    /// \brief Return the transport protocol of the socket.
-    ///
-    /// Currently, it returns \c IPPROTO_UDP for UDP sockets, and
-    /// \c IPPROTO_TCP for TCP sockets.
-    ///
-    /// This method never throws an exception.
-    ///
-    /// \return IPPROTO_UDP for UDP sockets
-    /// \return IPPROTO_TCP for TCP sockets
-    virtual int getProtocol() const = 0;
-
-    /// \brief Return a non-usable "dummy" UDP socket for testing.
-    ///
-    /// This is a class method that returns a "mock" of UDP socket.
-    /// This is not associated with any actual socket, and its only
-    /// responsibility is to return \c IPPROTO_UDP from \c getProtocol().
-    /// The only feasible usage of this socket is for testing so that
-    /// the test code can prepare some "UDP data" even without opening any
-    /// actual socket.
-    ///
-    /// This method never throws an exception.
-    ///
-    /// \return A reference to an \c IOSocket object whose \c getProtocol()
-    /// returns \c IPPROTO_UDP.
-    static IOSocket& getDummyUDPSocket();
-
-    /// \brief Return a non-usable "dummy" TCP socket for testing.
-    ///
-    /// See \c getDummyUDPSocket().  This method is its TCP version.
-    ///
-    /// \return A reference to an \c IOSocket object whose \c getProtocol()
-    /// returns \c IPPROTO_TCP.
-    static IOSocket& getDummyTCPSocket();
-};
-
-/// \brief The \c IOMessage class encapsulates an incoming message received
-/// on a socket.
-///
-/// An \c IOMessage object represents a tuple of a chunk of data
-/// (a UDP packet or some segment of TCP stream), the socket over which the
-/// data is passed, the information about the other end point of the
-/// communication, and perhaps more.
-///
-/// The current design and interfaces of this class is tentative.
-/// It only provides a minimal level of support that is necessary for
-/// the current implementation of the authoritative server.
-/// A future version of this class will definitely support more.
-class IOMessage {
-    ///
-    /// \name Constructors and Destructor
-    ///
-    /// Note: The copy constructor and the assignment operator are
-    /// intentionally defined as private, making this class non-copyable.
-    //@{
-private:
-    IOMessage(const IOMessage& source);
-    IOMessage& operator=(const IOMessage& source);
-public:
-    /// \brief Constructor from message information.
-    ///
-    /// This constructor needs to handle the ASIO \c ip::address class,
-    /// and is intended to be used within this wrapper implementation.
-    /// Once the \c IOMessage object is created, the application can
-    /// get access to the information via the wrapper interface such as
-    /// \c getRemoteAddress().
-    ///
-    /// This constructor never throws an exception.
-    ///
-    /// \param data A pointer to the message data.
-    /// \param data_size The size of the message data in bytes.
-    /// \param io_socket The socket over which the data is given.
-    /// \param remote_endpoint The other endpoint of the socket, that is,
-    /// the sender of the message.
-    IOMessage(const void* data, const size_t data_size, IOSocket& io_socket,
-              const IOEndpoint& remote_endpoint);
-    //@}
-
-    /// \brief Returns a pointer to the received data.
-    const void* getData() const { return (data_); }
-
-    /// \brief Returns the size of the received data in bytes.
-    size_t getDataSize() const { return (data_size_); }
-
-    /// \brief Returns the socket on which the message arrives.
-    const IOSocket& getSocket() const { return (io_socket_); }
-
-    /// \brief Returns the endpoint that sends the message.
-    const IOEndpoint& getRemoteEndpoint() const { return (remote_endpoint_); }
-private:
-    const void* data_;
-    const size_t data_size_;
-    IOSocket& io_socket_;
-    const IOEndpoint& remote_endpoint_;
-};
-
-/// \brief The \c IOService class is a wrapper for the ASIO \c io_service
-/// class.
-///
-/// Currently, the interface of this class is very specific to the
-/// authoritative server implementation as indicated in the signature of
-/// the constructor, but the plan is to generalize it so that other BIND 10
-/// modules can use this interface, too.
-class IOService {
-    ///
-    /// \name Constructors and Destructor
-    ///
-    /// These are currently very specific to the authoritative server
-    /// implementation.
-    ///
-    /// 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 with a specific IP address and port on which
-    /// the services listen on.
-    IOService(AuthSrv* auth_server, const char& port, const char& address);
-    /// \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.
-    IOService(AuthSrv* auth_server, const char& port,
-              const bool use_ipv4, const bool use_ipv6);
-    /// \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 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();
-
-    /// \brief A functor(-like) class that specifies a custom call back
-    /// invoked from the event loop instead of the embedded authoritative
-    /// server callbacks.
-    ///
-    /// Currently, the callback is intended to be used only for testing
-    /// purposes.  But we'll need a generic callback type like this to
-    /// generalize the wrapper interface.
-    typedef boost::function<void(const IOMessage& io_message)> IOCallBack;
-
-    /// \brief Set the custom call back invoked from the event loop.
-    ///
-    /// Right now this method is only for testing, but will eventually be
-    /// generalized.
-    void setCallBack(IOCallBack callback);
-private:
-    IOServiceImpl* impl_;
-};
-}      // asio_link
-#endif // __ASIO_LINK_H
-
-// Local Variables: 
-// mode: c++
-// End: 

+ 187 - 113
src/bin/auth/auth_srv.cc

@@ -14,6 +14,8 @@
 
 // $Id$
 
+#include <config.h>
+
 #include <netinet/in.h>
 
 #include <algorithm>
@@ -21,6 +23,12 @@
 #include <iostream>
 #include <vector>
 
+#include <asiolink/asiolink.h>
+
+#include <config/ccsession.h>
+
+#include <cc/data.h>
+
 #include <exceptions/exceptions.h>
 
 #include <dns/buffer.h>
@@ -34,22 +42,16 @@
 #include <dns/rrset.h>
 #include <dns/rrttl.h>
 #include <dns/message.h>
-#include <config/ccsession.h>
-#include <cc/data.h>
-#include <exceptions/exceptions.h>
 
 #include <datasrc/query.h>
 #include <datasrc/data_source.h>
 #include <datasrc/static_datasrc.h>
 #include <datasrc/sqlite3_datasrc.h>
 
-#include <cc/data.h>
-
 #include <xfr/xfrout_client.h>
 
 #include <auth/common.h>
 #include <auth/auth_srv.h>
-#include <auth/asio_link.h>
 
 using namespace std;
 
@@ -61,7 +63,7 @@ using namespace isc::dns::rdata;
 using namespace isc::data;
 using namespace isc::config;
 using namespace isc::xfr;
-using namespace asio_link;
+using namespace asiolink;
 
 class AuthSrvImpl {
 private:
@@ -73,32 +75,34 @@ public:
     ~AuthSrvImpl();
     isc::data::ConstElementPtr setDbFile(isc::data::ConstElementPtr config);
 
-    bool processNormalQuery(const IOMessage& io_message, Message& message,
-                            MessageRenderer& response_renderer);
-    bool processAxfrQuery(const IOMessage& io_message, Message& message,
-                            MessageRenderer& response_renderer);
-    bool processNotify(const IOMessage& io_message, Message& message,
-                            MessageRenderer& response_renderer);
-    std::string db_file_;
+    bool processNormalQuery(const IOMessage& io_message, MessagePtr message,
+                            OutputBufferPtr buffer);
+    bool processAxfrQuery(const IOMessage& io_message, MessagePtr message,
+                          OutputBufferPtr buffer);
+    bool processNotify(const IOMessage& io_message, MessagePtr message,
+                       OutputBufferPtr buffer);
+
+    /// Currently non-configurable, but will be.
+    static const uint16_t DEFAULT_LOCAL_UDPSIZE = 4096;
+
+    /// These members are public because AuthSrv accesses them directly.
     ModuleCCSession* config_session_;
+    bool verbose_mode_;
+    AbstractSession* xfrin_session_;
+
+    /// Hot spot cache
+    isc::datasrc::HotCache cache_;
+private:
+    std::string db_file_;
+
     MetaDataSrc data_sources_;
     /// We keep a pointer to the currently running sqlite datasource
     /// so that we can specifically remove that one should the database
     /// file change
     ConstDataSrcPtr cur_datasrc_;
 
-    bool verbose_mode_;
-
-    AbstractSession* xfrin_session_;
-
     bool xfrout_connected_;
     AbstractXfroutClient& xfrout_client_;
-
-    /// Currently non-configurable, but will be.
-    static const uint16_t DEFAULT_LOCAL_UDPSIZE = 4096;
-
-    /// Hot spot cache
-    isc::datasrc::HotCache cache_;
 };
 
 AuthSrvImpl::AuthSrvImpl(const bool use_cache,
@@ -126,60 +130,127 @@ AuthSrvImpl::~AuthSrvImpl() {
     }
 }
 
+// This is a derived class of \c DNSLookup, to serve as a
+// callback in the asiolink module.  It calls
+// AuthSrv::processMessage() on a single DNS message.
+class MessageLookup : public DNSLookup {
+public:
+    MessageLookup(AuthSrv* srv) : server_(srv) {}
+    virtual void operator()(const IOMessage& io_message, MessagePtr message,
+                            OutputBufferPtr buffer, DNSServer* server) const
+    {
+        server_->processMessage(io_message, message, buffer, server);
+    }
+private:
+    AuthSrv* server_;
+};
+
+// This is a derived class of \c DNSAnswer, to serve as a
+// callback in the asiolink module.  It takes a completed
+// set of answer data from the DNS lookup and assembles it
+// into a wire-format response.
+class MessageAnswer : public DNSAnswer {
+public:
+    MessageAnswer(AuthSrv* srv) : server_(srv) {}
+    virtual void operator()(const IOMessage& io_message, MessagePtr message,
+                            OutputBufferPtr buffer) const
+    {
+        MessageRenderer renderer(*buffer);
+        if (io_message.getSocket().getProtocol() == IPPROTO_UDP) {
+            ConstEDNSPtr edns(message->getEDNS());
+            renderer.setLengthLimit(edns ? edns->getUDPSize() :
+                Message::DEFAULT_MAX_UDPSIZE);
+        } else {
+            renderer.setLengthLimit(65535);
+        }
+        message->toWire(renderer);
+        if (server_->getVerbose()) {
+            cerr << "[b10-auth] sending a response (" << renderer.getLength()
+                 << " bytes):\n" << message->toText() << endl;
+        }
+    }
+
+private:
+    AuthSrv* server_;
+};
+
+// This is a derived class of \c SimpleCallback, to serve
+// as a callback in the asiolink module.  It checks for queued
+// configuration messages, and executes them if found.
+class ConfigChecker : public SimpleCallback {
+public:
+    ConfigChecker(AuthSrv* srv) : server_(srv) {}
+    virtual void operator()(const IOMessage&) const {
+        if (server_->getConfigSession()->hasQueuedMsgs()) {
+            server_->getConfigSession()->checkCommand();
+        }
+    }
+private:
+    AuthSrv* server_;
+};
+
 AuthSrv::AuthSrv(const bool use_cache, AbstractXfroutClient& xfrout_client) :
-    impl_(new AuthSrvImpl(use_cache, xfrout_client))
+    impl_(new AuthSrvImpl(use_cache, xfrout_client)),
+    checkin_(new ConfigChecker(this)),
+    dns_lookup_(new MessageLookup(this)),
+    dns_answer_(new MessageAnswer(this))
 {}
 
 AuthSrv::~AuthSrv() {
     delete impl_;
+    delete checkin_;
+    delete dns_lookup_;
+    delete dns_answer_;
 }
 
 namespace {
 class QuestionInserter {
 public:
-    QuestionInserter(Message* message) : message_(message) {}
+    QuestionInserter(MessagePtr message) : message_(message) {}
     void operator()(const QuestionPtr question) {
         message_->addQuestion(question);
     }
-    Message* message_;
+    MessagePtr message_;
 };
 
 void
-makeErrorMessage(Message& message, MessageRenderer& renderer,
+makeErrorMessage(MessagePtr message, OutputBufferPtr buffer,
                  const Rcode& rcode, const bool verbose_mode)
 {
     // extract the parameters that should be kept.
     // XXX: with the current implementation, it's not easy to set EDNS0
     // depending on whether the query had it.  So we'll simply omit it.
-    const qid_t qid = message.getQid();
-    const bool rd = message.getHeaderFlag(Message::HEADERFLAG_RD);
-    const bool cd = message.getHeaderFlag(Message::HEADERFLAG_CD);
-    const Opcode& opcode = message.getOpcode();
+    const qid_t qid = message->getQid();
+    const bool rd = message->getHeaderFlag(Message::HEADERFLAG_RD);
+    const bool cd = message->getHeaderFlag(Message::HEADERFLAG_CD);
+    const Opcode& opcode = message->getOpcode();
     vector<QuestionPtr> questions;
 
     // If this is an error to a query or notify, we should also copy the
     // question section.
     if (opcode == Opcode::QUERY() || opcode == Opcode::NOTIFY()) {
-        questions.assign(message.beginQuestion(), message.endQuestion());
+        questions.assign(message->beginQuestion(), message->endQuestion());
     }
 
-    message.clear(Message::RENDER);
-    message.setQid(qid);
-    message.setOpcode(opcode);
-    message.setHeaderFlag(Message::HEADERFLAG_QR);
+    message->clear(Message::RENDER);
+    message->setQid(qid);
+    message->setOpcode(opcode);
+    message->setHeaderFlag(Message::HEADERFLAG_QR);
     if (rd) {
-        message.setHeaderFlag(Message::HEADERFLAG_RD);
+        message->setHeaderFlag(Message::HEADERFLAG_RD);
     }
     if (cd) {
-        message.setHeaderFlag(Message::HEADERFLAG_CD);
+        message->setHeaderFlag(Message::HEADERFLAG_CD);
     }
-    for_each(questions.begin(), questions.end(), QuestionInserter(&message));
-    message.setRcode(rcode);
-    message.toWire(renderer);
+    for_each(questions.begin(), questions.end(), QuestionInserter(message));
+    message->setRcode(rcode);
+
+    MessageRenderer renderer(*buffer);
+    message->toWire(renderer);
 
     if (verbose_mode) {
         cerr << "[b10-auth] sending an error response (" <<
-            renderer.getLength() << " bytes):\n" << message.toText() << endl;
+            renderer.getLength() << " bytes):\n" << message->toText() << endl;
     }
 }
 }
@@ -219,143 +290,147 @@ AuthSrv::getConfigSession() const {
     return (impl_->config_session_);
 }
 
-bool
-AuthSrv::processMessage(const IOMessage& io_message, Message& message,
-                        MessageRenderer& response_renderer)
+void
+AuthSrv::processMessage(const IOMessage& io_message, MessagePtr message,
+                        OutputBufferPtr buffer, DNSServer* server)
 {
     InputBuffer request_buffer(io_message.getData(), io_message.getDataSize());
 
     // First, check the header part.  If we fail even for the base header,
     // just drop the message.
     try {
-        message.parseHeader(request_buffer);
+        message->parseHeader(request_buffer);
 
         // Ignore all responses.
-        if (message.getHeaderFlag(Message::HEADERFLAG_QR)) {
+        if (message->getHeaderFlag(Message::HEADERFLAG_QR)) {
             if (impl_->verbose_mode_) {
                 cerr << "[b10-auth] received unexpected response, ignoring"
                      << endl;
             }
-            return (false);
+            server->resume(false);
+            return;
         }
     } catch (const Exception& ex) {
-        return (false);
+        if (impl_->verbose_mode_) {
+            cerr << "[b10-auth] DNS packet exception: " << ex.what() << endl;
+        }
+        server->resume(false);
+        return;
     }
 
-    // Parse the message.  On failure, return an appropriate error.
     try {
-        message.fromWire(request_buffer);
+        // Parse the message.
+        message->fromWire(request_buffer);
     } catch (const DNSProtocolError& error) {
         if (impl_->verbose_mode_) {
             cerr << "[b10-auth] returning " <<  error.getRcode().toText()
                  << ": " << error.what() << endl;
         }
-        makeErrorMessage(message, response_renderer, error.getRcode(),
+        makeErrorMessage(message, buffer, error.getRcode(),
                          impl_->verbose_mode_);
-        return (true);
+        server->resume(true);
+        return;
     } catch (const Exception& ex) {
         if (impl_->verbose_mode_) {
             cerr << "[b10-auth] returning SERVFAIL: " << ex.what() << endl;
         }
-        makeErrorMessage(message, response_renderer, Rcode::SERVFAIL(),
+        makeErrorMessage(message, buffer, Rcode::SERVFAIL(),
                          impl_->verbose_mode_);
-        return (true);
+        server->resume(true);
+        return;
     } // other exceptions will be handled at a higher layer.
 
     if (impl_->verbose_mode_) {
-        cerr << "[b10-auth] received a message:\n" << message.toText() << endl;
+        cerr << "[b10-auth] received a message:\n" << message->toText() << endl;
     }
 
     // Perform further protocol-level validation.
 
-    if (message.getOpcode() == Opcode::NOTIFY()) {
-        return (impl_->processNotify(io_message, message, response_renderer));
-    } else if (message.getOpcode() != Opcode::QUERY()) {
+    bool sendAnswer = true;
+    if (message->getOpcode() == Opcode::NOTIFY()) {
+        sendAnswer = impl_->processNotify(io_message, message, buffer);
+    } else if (message->getOpcode() != Opcode::QUERY()) {
         if (impl_->verbose_mode_) {
             cerr << "[b10-auth] unsupported opcode" << endl;
         }
-        makeErrorMessage(message, response_renderer, Rcode::NOTIMP(),
-                         impl_->verbose_mode_);
-        return (true);
-    }
-
-    if (message.getRRCount(Message::SECTION_QUESTION) != 1) {
-        makeErrorMessage(message, response_renderer, Rcode::FORMERR(),
+        makeErrorMessage(message, buffer, Rcode::NOTIMP(),
                          impl_->verbose_mode_);
-        return (true);
-    }
-
-    ConstQuestionPtr question = *message.beginQuestion();
-    const RRType &qtype = question->getType();
-    if (qtype == RRType::AXFR()) {
-        return (impl_->processAxfrQuery(io_message, message,
-                                        response_renderer));
-    } else if (qtype == RRType::IXFR()) {
-        makeErrorMessage(message, response_renderer, Rcode::NOTIMP(),
+    } else if (message->getRRCount(Message::SECTION_QUESTION) != 1) {
+        makeErrorMessage(message, buffer, Rcode::FORMERR(),
                          impl_->verbose_mode_);
-        return (true);
     } else {
-        return (impl_->processNormalQuery(io_message, message,
-                                          response_renderer));
+        ConstQuestionPtr question = *message->beginQuestion();
+        const RRType &qtype = question->getType();
+        if (qtype == RRType::AXFR()) {
+            sendAnswer = impl_->processAxfrQuery(io_message, message, buffer);
+        } else if (qtype == RRType::IXFR()) {
+            makeErrorMessage(message, buffer, Rcode::NOTIMP(),
+                             impl_->verbose_mode_);
+        } else {
+            sendAnswer = impl_->processNormalQuery(io_message, message, buffer);
+        }
     }
+
+    server->resume(sendAnswer);
 }
 
 bool
-AuthSrvImpl::processNormalQuery(const IOMessage& io_message, Message& message,
-                                MessageRenderer& response_renderer)
+AuthSrvImpl::processNormalQuery(const IOMessage& io_message, MessagePtr message,
+                                OutputBufferPtr buffer)
 {
-    ConstEDNSPtr remote_edns = message.getEDNS();
+    ConstEDNSPtr remote_edns = message->getEDNS();
     const bool dnssec_ok = remote_edns && remote_edns->getDNSSECAwareness();
     const uint16_t remote_bufsize = remote_edns ? remote_edns->getUDPSize() :
         Message::DEFAULT_MAX_UDPSIZE;
 
-    message.makeResponse();
-    message.setHeaderFlag(Message::HEADERFLAG_AA);
-    message.setRcode(Rcode::NOERROR());
+    message->makeResponse();
+    message->setHeaderFlag(Message::HEADERFLAG_AA);
+    message->setRcode(Rcode::NOERROR());
 
     if (remote_edns) {
         EDNSPtr local_edns = EDNSPtr(new EDNS());
         local_edns->setDNSSECAwareness(dnssec_ok);
         local_edns->setUDPSize(AuthSrvImpl::DEFAULT_LOCAL_UDPSIZE);
-        message.setEDNS(local_edns);
+        message->setEDNS(local_edns);
     }
 
     try {
-        Query query(message, cache_, dnssec_ok);
+        Query query(*message, cache_, dnssec_ok);
         data_sources_.doQuery(query);
     } catch (const Exception& ex) {
         if (verbose_mode_) {
             cerr << "[b10-auth] Internal error, returning SERVFAIL: " <<
                 ex.what() << endl;
         }
-        makeErrorMessage(message, response_renderer, Rcode::SERVFAIL(),
-                         verbose_mode_);
+        makeErrorMessage(message, buffer, Rcode::SERVFAIL(), verbose_mode_);
         return (true);
     }
 
+
+    MessageRenderer renderer(*buffer);
     const bool udp_buffer =
         (io_message.getSocket().getProtocol() == IPPROTO_UDP);
-    response_renderer.setLengthLimit(udp_buffer ? remote_bufsize : 65535);
-    message.toWire(response_renderer);
+    renderer.setLengthLimit(udp_buffer ? remote_bufsize : 65535);
+    message->toWire(renderer);
+
     if (verbose_mode_) {
         cerr << "[b10-auth] sending a response ("
-             << response_renderer.getLength()
-             << " bytes):\n" << message.toText() << endl;
+             << renderer.getLength()
+             << " bytes):\n" << message->toText() << endl;
     }
 
     return (true);
 }
 
 bool
-AuthSrvImpl::processAxfrQuery(const IOMessage& io_message, Message& message,
-                            MessageRenderer& response_renderer)
+AuthSrvImpl::processAxfrQuery(const IOMessage& io_message, MessagePtr message,
+                              OutputBufferPtr buffer)
 {
     if (io_message.getSocket().getProtocol() == IPPROTO_UDP) {
         if (verbose_mode_) {
             cerr << "[b10-auth] AXFR query over UDP isn't allowed" << endl;
         }
-        makeErrorMessage(message, response_renderer, Rcode::FORMERR(),
-                         verbose_mode_);
+        makeErrorMessage(message, buffer, Rcode::FORMERR(), verbose_mode_);
         return (true);
     }
 
@@ -382,8 +457,7 @@ AuthSrvImpl::processAxfrQuery(const IOMessage& io_message, Message& message,
             cerr << "[b10-auth] Error in handling XFR request: " << err.what()
                  << endl;
         }
-        makeErrorMessage(message, response_renderer, Rcode::SERVFAIL(),
-                         verbose_mode_);
+        makeErrorMessage(message, buffer, Rcode::SERVFAIL(), verbose_mode_);
         return (true);
     }
 
@@ -391,28 +465,26 @@ AuthSrvImpl::processAxfrQuery(const IOMessage& io_message, Message& message,
 }
 
 bool
-AuthSrvImpl::processNotify(const IOMessage& io_message, Message& message,
-                           MessageRenderer& response_renderer)
+AuthSrvImpl::processNotify(const IOMessage& io_message, MessagePtr message, 
+                           OutputBufferPtr buffer)
 {
     // The incoming notify must contain exactly one question for SOA of the
     // zone name.
-    if (message.getRRCount(Message::SECTION_QUESTION) != 1) {
+    if (message->getRRCount(Message::SECTION_QUESTION) != 1) {
         if (verbose_mode_) {
                 cerr << "[b10-auth] invalid number of questions in notify: "
-                     << message.getRRCount(Message::SECTION_QUESTION) << endl;
+                     << message->getRRCount(Message::SECTION_QUESTION) << endl;
         }
-        makeErrorMessage(message, response_renderer, Rcode::FORMERR(),
-                         verbose_mode_);
+        makeErrorMessage(message, buffer, Rcode::FORMERR(), verbose_mode_);
         return (true);
     }
-    ConstQuestionPtr question = *message.beginQuestion();
+    ConstQuestionPtr question = *message->beginQuestion();
     if (question->getType() != RRType::SOA()) {
         if (verbose_mode_) {
                 cerr << "[b10-auth] invalid question RR type in notify: "
                      << question->getType() << endl;
         }
-        makeErrorMessage(message, response_renderer, Rcode::FORMERR(),
-                         verbose_mode_);
+        makeErrorMessage(message, buffer, Rcode::FORMERR(), verbose_mode_);
         return (true);
     }
 
@@ -470,10 +542,12 @@ AuthSrvImpl::processNotify(const IOMessage& io_message, Message& message,
         return (false);
     }
 
-    message.makeResponse();
-    message.setHeaderFlag(Message::HEADERFLAG_AA);
-    message.setRcode(Rcode::NOERROR());
-    message.toWire(response_renderer);
+    message->makeResponse();
+    message->setHeaderFlag(Message::HEADERFLAG_AA);
+    message->setRcode(Rcode::NOERROR());
+
+    MessageRenderer renderer(*buffer);
+    message->toWire(renderer);
     return (true);
 }
 

+ 43 - 15
src/bin/auth/auth_srv.h

@@ -22,20 +22,12 @@
 #include <cc/data.h>
 #include <config/ccsession.h>
 
-namespace isc {
-namespace dns {
-class InputBuffer;
-class Message;
-class MessageRenderer;
-}
+#include <asiolink/asiolink.h>
 
+namespace isc {
 namespace xfr {
 class AbstractXfroutClient;
-};
 }
-
-namespace asio_link {
-class IOMessage;
 }
 
 /// \brief The implementation class for the \c AuthSrv class using the pimpl
@@ -84,11 +76,26 @@ public:
             isc::xfr::AbstractXfroutClient& xfrout_client);
     ~AuthSrv();
     //@}
-    /// \return \c true if the \a message contains a response to be returned;
-    /// otherwise \c false.
-    bool processMessage(const asio_link::IOMessage& io_message,
-                        isc::dns::Message& message,
-                        isc::dns::MessageRenderer& response_renderer);
+
+    /// \brief Process an incoming DNS message, then signal 'server' to resume 
+    ///
+    /// A DNS query (or other message) has been received by a \c DNSServer
+    /// object.  Find an answer, then post the \c DNSServer object on the
+    /// I/O service queue and return.  When the server resumes, it can
+    /// send the reply.
+    ///
+    /// \param io_message The raw message received
+    /// \param message Pointer to the \c Message object
+    /// \param buffer Pointer to an \c OutputBuffer for the resposne
+    /// \param server Pointer to the \c DNSServer
+    void processMessage(const asiolink::IOMessage& io_message,
+                        isc::dns::MessagePtr message,
+                        isc::dns::OutputBufferPtr buffer,
+                        asiolink::DNSServer* server);
+
+    /// \brief Set verbose flag
+    ///
+    /// \param on The new value of the verbose flag
 
     /// \brief Enable or disable verbose logging.
     ///
@@ -103,6 +110,8 @@ public:
     /// This method never throws an exception.
     ///
     /// \return \c true if verbose logging is enabled; otherwise \c false.
+
+    /// \brief Get the current value of the verbose flag
     bool getVerbose() const;
 
     /// \brief Updates the data source for the \c AuthSrv object.
@@ -162,6 +171,21 @@ public:
     /// control commands and configuration updates.
     void setConfigSession(isc::config::ModuleCCSession* config_session);
 
+    /// \brief Assign an ASIO IO Service queue to this Recursor object
+    void setIOService(asiolink::IOService& ios) { io_service_ = &ios; }
+
+    /// \brief Return this object's ASIO IO Service queue
+    asiolink::IOService& getIOService() const { return (*io_service_); }
+
+    /// \brief Return pointer to the DNS Lookup callback function
+    asiolink::DNSLookup* getDNSLookupProvider() const { return (dns_lookup_); }
+
+    /// \brief Return pointer to the DNS Answer callback function
+    asiolink::DNSAnswer* getDNSAnswerProvider() const { return (dns_answer_); }
+
+    /// \brief Return pointer to the Checkin callback function
+    asiolink::SimpleCallback* getCheckinProvider() const { return (checkin_); }
+
     /// \brief Set or update the size (number of slots) of hot spot cache.
     ///
     /// If the specified size is 0, it means the size will be unlimited.
@@ -202,6 +226,10 @@ public:
 
 private:
     AuthSrvImpl* impl_;
+    asiolink::IOService* io_service_;
+    asiolink::SimpleCallback* checkin_;
+    asiolink::DNSLookup* dns_lookup_;
+    asiolink::DNSAnswer* dns_answer_;
 };
 
 #endif // __AUTH_SRV_H

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

@@ -17,5 +17,5 @@ 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/bin/auth/libasio_link.a
+query_bench_LDADD += $(top_builddir)/src/lib/asiolink/libasiolink.la
 query_bench_LDADD += $(SQLITE_LIBS)

+ 28 - 18
src/bin/auth/benchmarks/query_bench.cc

@@ -26,7 +26,6 @@
 
 #include <dns/buffer.h>
 #include <dns/message.h>
-#include <dns/messagerenderer.h>
 #include <dns/name.h>
 #include <dns/question.h>
 #include <dns/rrclass.h>
@@ -34,7 +33,7 @@
 #include <xfr/xfrout_client.h>
 
 #include <auth/auth_srv.h>
-#include <auth/asio_link.h>
+#include <asiolink/asiolink.h>
 
 using namespace std;
 using namespace isc;
@@ -42,12 +41,22 @@ using namespace isc::data;
 using namespace isc::dns;
 using namespace isc::xfr;
 using namespace isc::bench;
-using namespace asio_link;
+using namespace asiolink;
 
 namespace {
 // Commonly used constant:
 XfroutClient xfrout_client("dummy_path"); // path doesn't matter
 
+// Just something to pass as the server to resume
+class DummyServer : public DNSServer {
+    public:
+        virtual void operator()(asio::error_code, size_t) { }
+        virtual void resume(const bool) { }
+        virtual DNSServer* clone() {
+            return new DummyServer(*this);
+        }
+};
+
 class QueryBenchMark {
 private:
     // Maintain dynamically generated objects via shared pointers because
@@ -56,12 +65,12 @@ private:
     typedef boost::shared_ptr<const IOEndpoint> IOEndpointPtr;
 public:
     QueryBenchMark(const int cache_slots, const char* const datasrc_file,
-                   const BenchQueries& queries, Message& query_message,
-                   MessageRenderer& renderer) :
+                   const BenchQueries& queries, MessagePtr query_message,
+                   OutputBufferPtr buffer) :
         server_(new AuthSrv(cache_slots >= 0 ? true : false, xfrout_client)),
         queries_(queries),
         query_message_(query_message),
-        renderer_(renderer),
+        buffer_(buffer),
         dummy_socket(IOSocket::getDummyUDPSocket()),
         dummy_endpoint(IOEndpointPtr(IOEndpoint::create(IPPROTO_UDP,
                                                         IOAddress("192.0.2.1"),
@@ -76,12 +85,13 @@ public:
     unsigned int run() {
         BenchQueries::const_iterator query;
         const BenchQueries::const_iterator query_end = queries_.end();
+        DummyServer server;
         for (query = queries_.begin(); query != query_end; ++query) {
             IOMessage io_message(&(*query)[0], (*query).size(), dummy_socket,
                                  *dummy_endpoint);
-            query_message_.clear(Message::PARSE);
-            renderer_.clear();
-            server_->processMessage(io_message, query_message_, renderer_);
+            query_message_->clear(Message::PARSE);
+            server_->processMessage(io_message, query_message_, buffer_,
+                &server);
         }
 
         return (queries_.size());
@@ -89,11 +99,12 @@ public:
 private:
     AuthSrvPtr server_;
     const BenchQueries& queries_;
-    Message& query_message_;
-    MessageRenderer& renderer_;
+    MessagePtr query_message_;
+    OutputBufferPtr buffer_;
     IOSocket& dummy_socket;
     IOEndpointPtr dummy_endpoint;
 };
+
 }
 
 namespace isc {
@@ -143,9 +154,8 @@ main(int argc, char* argv[]) {
 
     BenchQueries queries;
     loadQueryData(query_data_file, queries, RRClass::IN());
-    OutputBuffer buffer(4096);
-    MessageRenderer renderer(buffer);
-    Message message(Message::PARSE);
+    OutputBufferPtr buffer(new OutputBuffer(4096));
+    MessagePtr message(new Message(Message::PARSE));
 
     cout << "Parameters:" << endl;
     cout << "  Iterations: " << iteration << endl;
@@ -157,24 +167,24 @@ main(int argc, char* argv[]) {
          << endl;
     BenchMark<QueryBenchMark>(iteration,
                               QueryBenchMark(0, datasrc_file, queries, message,
-                                             renderer));
+                                             buffer));
 
     cout << "Benchmark enabling Hot Spot Cache with 10*#queries slots "
          << endl;
     BenchMark<QueryBenchMark>(iteration,
                               QueryBenchMark(10 * queries.size(), datasrc_file,
-                                             queries, message, renderer));
+                                             queries, message, buffer));
 
     cout << "Benchmark enabling Hot Spot Cache with #queries/2 slots "
          << endl;
     BenchMark<QueryBenchMark>(iteration,
                               QueryBenchMark(queries.size() / 2, datasrc_file,
-                                             queries, message, renderer));
+                                             queries, message, buffer));
 
     cout << "Benchmark disabling Hot Spot Cache" << endl;
     BenchMark<QueryBenchMark>(iteration,
                               QueryBenchMark(-1, datasrc_file, queries,
-                                             message, renderer));    
+                                             message, buffer));    
 
     return (0);
 }

+ 42 - 21
src/bin/auth/main.cc

@@ -43,7 +43,7 @@
 #include <auth/common.h>
 #include <auth/change_user.h>
 #include <auth/auth_srv.h>
-#include <auth/asio_link.h>
+#include <asiolink/asiolink.h>
 
 using namespace std;
 using namespace isc::data;
@@ -51,20 +51,22 @@ using namespace isc::cc;
 using namespace isc::config;
 using namespace isc::dns;
 using namespace isc::xfr;
+using namespace asiolink;
 
 namespace {
 
-bool verbose_mode = false;
+static bool verbose_mode = false;
 
-const string PROGRAM = "Auth";
-const char* DNSPORT = "5300";
+// Default port current 5300 for testing purposes
+static const string PROGRAM = "Auth";
+static const char* DNSPORT = "5300";
 
 /* need global var for config/command handlers.
  * todo: turn this around, and put handlers in the authserver
  * class itself? */
-AuthSrv *auth_server;
+static AuthSrv *auth_server;
 
-asio_link::IOService* io_service;
+static IOService io_service;
 
 ConstElementPtr
 my_config_handler(ConstElementPtr new_config) {
@@ -80,7 +82,7 @@ my_command_handler(const string& command, ConstElementPtr args) {
         /* let's add that message to our answer as well */
         answer = createAnswer(0, args);
     } else if (command == "shutdown") {
-        io_service->stop();
+        io_service.stop();
     }
     
     return (answer);
@@ -88,7 +90,16 @@ my_command_handler(const string& command, ConstElementPtr args) {
 
 void
 usage() {
-    cerr << "Usage: b10-auth [-a address] [-p port] [-4|-6] [-nv]" << endl;
+    cerr << "Usage:  b10-auth [-a address] [-p port] [-u user] [-4|-6] [-nv]"
+         << endl;
+    cerr << "\t-a: specify the address to listen on (default: all) " << endl;
+    cerr << "\t-p: specify the port to listen on (default: " << DNSPORT << ")"
+         << endl;
+    cerr << "\t-4: listen on all IPv4 addresses (incompatible with -a)" << endl;
+    cerr << "\t-6: listen on all IPv6 addresses (incompatible with -a)" << endl;
+    cerr << "\t-n: do not cache answers in memory" << endl;
+    cerr << "\t-u: change process UID to the specified user" << endl;
+    cerr << "\t-v: verbose output" << endl;
     exit(1);
 }
 } // end of anonymous namespace
@@ -140,12 +151,14 @@ main(int argc, char* argv[]) {
     }
 
     if (!use_ipv4 && !use_ipv6) {
-        cerr << "[b10-auth] Error: -4 and -6 can't coexist" << endl;
+        cerr << "[b10-auth] Error: Cannot specify both -4 and -6 "
+             << "at the same time" << endl;
         usage();
     }
 
     if ((!use_ipv4 || !use_ipv6) && address != NULL) {
-        cerr << "[b10-auth] Error: -4|-6 and -a can't coexist" << endl;
+        cerr << "[b10-auth] Error: Cannot specify -4 or -6 "
+             << "at the same time as -a" << endl;
         usage();
     }
 
@@ -176,6 +189,11 @@ main(int argc, char* argv[]) {
         auth_server->setVerbose(verbose_mode);
         cout << "[b10-auth] Server created." << endl;
 
+        SimpleCallback* checkin = auth_server->getCheckinProvider();
+        DNSLookup* lookup = auth_server->getDNSLookupProvider();
+        DNSAnswer* answer = auth_server->getDNSAnswerProvider();
+
+        DNSService* dns_service;
         if (address != NULL) {
             // XXX: we can only specify at most one explicit address.
             // This also means the server cannot run in the dual address
@@ -183,15 +201,17 @@ main(int argc, char* argv[]) {
             // We don't bother to fix this problem, however.  The -a option
             // is a short term workaround until we support dynamic listening
             // port allocation.
-            io_service = new asio_link::IOService(auth_server, *port,
-                                                  *address);
+            dns_service = new DNSService(io_service,  *port, *address,
+                                         checkin, lookup, answer);
         } else {
-            io_service = new asio_link::IOService(auth_server, *port,
-                                                  use_ipv4, use_ipv6);
+            dns_service = new DNSService(io_service, *port, use_ipv4,
+                                         use_ipv6, checkin, lookup,
+                                         answer);
         }
+        auth_server->setIOService(io_service);
         cout << "[b10-auth] IOService created." << endl;
 
-        cc_session = new Session(io_service->get_io_service());
+        cc_session = new Session(io_service.get_io_service());
         cout << "[b10-auth] Configuration session channel created." << endl;
 
         config_session = new ModuleCCSession(specfile, *cc_session,
@@ -203,15 +223,15 @@ main(int argc, char* argv[]) {
             changeUser(uid);
         }
 
-        xfrin_session = new Session(io_service->get_io_service());
+        xfrin_session = new Session(io_service.get_io_service());
         cout << "[b10-auth] Xfrin session channel created." << endl;
         xfrin_session->establish(NULL);
         xfrin_session_established = true;
         cout << "[b10-auth] Xfrin session channel established." << endl;
 
-        // XXX: with the current interface to asio_link we have to create
+        // XXX: with the current interface to asiolink we have to create
         // auth_server before io_service while Session needs io_service.
-        // In a next step of refactoring we should make asio_link independent
+        // In a next step of refactoring we should make asiolink independent
         // from auth_server, and create io_service, auth_server, and
         // sessions in that order.
         auth_server->setXfrinSession(xfrin_session);
@@ -219,9 +239,11 @@ main(int argc, char* argv[]) {
         auth_server->updateConfig(ElementPtr());
 
         cout << "[b10-auth] Server started." << endl;
-        io_service->run();
+        io_service.run();
+
+        delete dns_service;
     } catch (const std::exception& ex) {
-        cerr << "[b10-auth] Initialization failed: " << ex.what() << endl;
+        cerr << "[b10-auth] Server failed: " << ex.what() << endl;
         ret = 1;
     }
 
@@ -232,7 +254,6 @@ main(int argc, char* argv[]) {
     delete xfrin_session;
     delete config_session;
     delete cc_session;
-    delete io_service;
     delete auth_server;
 
     return (ret);

+ 3 - 3
src/bin/auth/query.cc

@@ -26,10 +26,10 @@ namespace isc {
 namespace auth {
 void
 Query::process() const {
-    const ZoneTable::FindResult result = zone_table_.find(qname_);
+    const ZoneTable::FindResult result = zone_table_.findZone(qname_);
 
-    if (result.code != ZoneTable::SUCCESS &&
-        result.code != ZoneTable::PARTIALMATCH) {
+    if (result.code != isc::datasrc::result::SUCCESS &&
+        result.code != isc::datasrc::result::PARTIALMATCH) {
         response_.setRcode(Rcode::SERVFAIL());
         return;
     }

+ 3 - 6
src/bin/auth/tests/Makefile.am

@@ -1,11 +1,9 @@
-SUBDIRS = testdata .
-
 AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
 AM_CPPFLAGS += -I$(top_builddir)/src/lib/dns -I$(top_srcdir)/src/bin
 AM_CPPFLAGS += -I$(top_builddir)/src/lib/cc
 AM_CPPFLAGS += $(BOOST_INCLUDES)
-AM_CPPFLAGS += -DTEST_DATA_DIR=\"$(srcdir)/testdata\"
-AM_CPPFLAGS += -DTEST_DATA_BUILDDIR=\"$(abs_top_builddir)/src/bin/auth/tests/testdata\"
+AM_CPPFLAGS += -DTEST_DATA_DIR=\"$(top_srcdir)/src/lib/testutils/testdata\"
+AM_CPPFLAGS += -DTEST_DATA_BUILDDIR=\"$(abs_top_builddir)/src/lib/testutils/testdata\"
 
 AM_CXXFLAGS = $(B10_CXXFLAGS)
 
@@ -26,7 +24,6 @@ run_unittests_SOURCES += ../change_user.h ../change_user.cc
 run_unittests_SOURCES += auth_srv_unittest.cc
 run_unittests_SOURCES += query_unittest.cc
 run_unittests_SOURCES += change_user_unittest.cc
-run_unittests_SOURCES += asio_link_unittest.cc
 run_unittests_SOURCES += run_unittests.cc
 run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
 run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
@@ -34,10 +31,10 @@ run_unittests_LDADD = $(GTEST_LDADD)
 run_unittests_LDADD += $(SQLITE_LIBS)
 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/bin/auth/libasio_link.a
 run_unittests_LDADD += $(top_builddir)/src/lib/xfr/libxfr.la
 endif
 

+ 0 - 357
src/bin/auth/tests/asio_link_unittest.cc

@@ -1,357 +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.
-
-// $Id$
-
-#include <sys/types.h>
-#include <sys/socket.h>
-#include <netdb.h>
-
-#include <stdint.h>
-
-#include <functional>
-#include <string>
-#include <vector>
-
-#include <gtest/gtest.h>
-
-#include <exceptions/exceptions.h>
-
-#include <dns/tests/unittest_util.h>
-
-#include <auth/asio_link.h>
-
-using isc::UnitTestUtil;
-using namespace std;
-using namespace asio_link;
-
-namespace {
-const char* const TEST_PORT = "53535";
-const char* const TEST_IPV6_ADDR = "::1";
-const char* const TEST_IPV4_ADDR = "127.0.0.1";
-// This data is intended to be valid as a DNS/TCP-like message: the first
-// two octets encode the length of the rest of the data.  This is crucial
-// for the tests below.
-const uint8_t test_data[] = {0, 4, 1, 2, 3, 4};
-
-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 IPv6 address-like input
-    EXPECT_THROW(IOAddress("2001:db8:::1234"), IOError);
-}
-
-TEST(IOEndpointTest, create) {
-    const IOEndpoint* ep;
-    ep = IOEndpoint::create(IPPROTO_UDP, IOAddress("192.0.2.1"), 5300);
-    EXPECT_EQ("192.0.2.1", ep->getAddress().toText());
-    delete ep;
-
-    ep = IOEndpoint::create(IPPROTO_TCP, IOAddress("192.0.2.1"), 5300);
-    EXPECT_EQ("192.0.2.1", ep->getAddress().toText());
-    delete ep;
-
-    ep = IOEndpoint::create(IPPROTO_UDP, IOAddress("2001:db8::1234"), 5300);
-    EXPECT_EQ("2001:db8::1234", ep->getAddress().toText());
-    delete ep;
-
-    ep = IOEndpoint::create(IPPROTO_TCP, IOAddress("2001:db8::1234"), 5300);
-    EXPECT_EQ("2001:db8::1234", ep->getAddress().toText());
-    delete ep;
-
-    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) {
-    EXPECT_THROW(IOService(NULL, *"65536", true, false), IOError);
-    EXPECT_THROW(IOService(NULL, *"5300.0", true, false), IOError);
-    EXPECT_THROW(IOService(NULL, *"-1", true, false), IOError);
-    EXPECT_THROW(IOService(NULL, *"domain", true, false), IOError);
-}
-
-TEST(IOServiceTest, badAddress) {
-    EXPECT_THROW(IOService(NULL, *TEST_PORT, *"192.0.2.1.1"),
-                 IOError);
-    EXPECT_THROW(IOService(NULL, *TEST_PORT, *"2001:db8:::1"),
-                 IOError);
-    EXPECT_THROW(IOService(NULL, *TEST_PORT, *"localhost"),
-                 IOError);
-}
-
-TEST(IOServiceTest, unavailableAddress) {
-    // These addresses should generally be unavailable as a valid local
-    // address, although there's no guarantee in theory.
-    EXPECT_THROW(IOService(NULL, *TEST_PORT, *"255.255.0.0"), 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(IOService(NULL, *TEST_PORT, *"::ffff:255.255.0.0"), IOError);
-}
-
-TEST(IOServiceTest, duplicateBind) {
-    // In each sub test case, second attempt should fail due to duplicate bind
-
-    // IPv6, "any" address
-    IOService* io_service = new IOService(NULL, *TEST_PORT, false, true);
-    EXPECT_THROW(IOService(NULL, *TEST_PORT, false, true), IOError);
-    delete io_service;
-
-    // IPv6, specific address
-    io_service = new IOService(NULL, *TEST_PORT, *TEST_IPV6_ADDR);
-    EXPECT_THROW(IOService(NULL, *TEST_PORT, *TEST_IPV6_ADDR), IOError);
-    delete io_service;
-
-    // IPv4, "any" address
-    io_service = new IOService(NULL, *TEST_PORT, true, false);
-    EXPECT_THROW(IOService(NULL, *TEST_PORT, true, false), IOError);
-    delete io_service;
-
-    // IPv4, specific address
-    io_service = new IOService(NULL, *TEST_PORT, *TEST_IPV4_ADDR);
-    EXPECT_THROW(IOService(NULL, *TEST_PORT, *TEST_IPV4_ADDR), IOError);
-    delete io_service;
-}
-
-struct addrinfo*
-resolveAddress(const int family, const int sock_type, const int protocol) {
-    const char* const addr = (family == AF_INET6) ?
-        TEST_IPV6_ADDR : TEST_IPV4_ADDR;
-
-    struct addrinfo hints;
-    memset(&hints, 0, sizeof(hints));
-    hints.ai_family = family;
-    hints.ai_socktype = sock_type;
-    hints.ai_protocol = protocol;
-
-    struct addrinfo* res;
-    const int error = getaddrinfo(addr, TEST_PORT, &hints, &res);
-    if (error != 0) {
-        isc_throw(IOError, "getaddrinfo failed: " << gai_strerror(error));
-    }
-
-    return (res);
-}
-
-// This fixture is a framework for various types of network operations
-// using the ASIO interfaces.  Each test case creates an IOService object,
-// opens a local "client" socket for testing, sends data via the local socket
-// to the service that would run in the IOService object.
-// A mock callback function (an ASIOCallBack object) is registered with the
-// IOService object, so the test code should be able to examine the data
-// receives on the server side.  It then checks the received data matches
-// expected parameters.
-// If initialization parameters of the IOService should be modified, the test
-// case can do it using the setIOService() method.
-// Note: the set of tests in ASIOLinkTest use actual network services and may
-// involve undesirable side effect such as blocking.
-class ASIOLinkTest : public ::testing::Test {
-protected:
-    ASIOLinkTest();
-    ~ASIOLinkTest() {
-        if (res_ != NULL) {
-            freeaddrinfo(res_);
-        }
-        if (sock_ != -1) {
-            close(sock_);
-        }
-        delete io_service_;
-    }
-    void sendUDP(const int family) {
-        res_ = resolveAddress(family, SOCK_DGRAM, IPPROTO_UDP);
-
-        sock_ = socket(res_->ai_family, res_->ai_socktype, res_->ai_protocol);
-        if (sock_ < 0) {
-            isc_throw(IOError, "failed to open test socket");
-        }
-        const int cc = sendto(sock_, test_data, sizeof(test_data), 0,
-                              res_->ai_addr, res_->ai_addrlen);
-        if (cc != sizeof(test_data)) {
-            isc_throw(IOError, "unexpected sendto result: " << cc);
-        }
-        io_service_->run();
-    }
-    void sendTCP(const int family) {
-        res_ = resolveAddress(family, SOCK_STREAM, IPPROTO_TCP);
-
-        sock_ = socket(res_->ai_family, res_->ai_socktype, res_->ai_protocol);
-        if (sock_ < 0) {
-            isc_throw(IOError, "failed to open test socket");
-        }
-        if (connect(sock_, res_->ai_addr, res_->ai_addrlen) < 0) {
-            isc_throw(IOError, "failed to connect to the test server");
-        }
-        const int cc = send(sock_, test_data, sizeof(test_data), 0);
-        if (cc != sizeof(test_data)) {
-            isc_throw(IOError, "unexpected sendto result: " << cc);
-        }
-        io_service_->run();
-    }
-    void setIOService(const char& address) {
-        delete io_service_;
-        io_service_ = NULL;
-        io_service_ = new IOService(NULL, *TEST_PORT, address);
-        io_service_->setCallBack(ASIOCallBack(this));
-    }
-    void setIOService(const bool use_ipv4, const bool use_ipv6) {
-        delete io_service_;
-        io_service_ = NULL;
-        io_service_ = new IOService(NULL, *TEST_PORT, use_ipv4, use_ipv6);
-        io_service_->setCallBack(ASIOCallBack(this));
-    }
-    void doTest(const int family, const int protocol) {
-        if (protocol == IPPROTO_UDP) {
-            sendUDP(family);
-        } else {
-            sendTCP(family);
-        }
-
-        // There doesn't seem to be an effective test for the validity of
-        // 'native'.
-        // One thing we are sure is it must be different from our local socket.
-        EXPECT_NE(sock_, callback_native_);
-        EXPECT_EQ(protocol, callback_protocol_);
-        EXPECT_EQ(family == AF_INET6 ? TEST_IPV6_ADDR : TEST_IPV4_ADDR,
-                  callback_address_);
-
-        const uint8_t* expected_data =
-            protocol == IPPROTO_UDP ? test_data : test_data + 2;
-        const size_t expected_datasize =
-            protocol == IPPROTO_UDP ? sizeof(test_data) :
-            sizeof(test_data) - 2;
-        EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, &callback_data_[0],
-                            callback_data_.size(),
-                            expected_data, expected_datasize);
-    }
-private:
-    class ASIOCallBack : public std::unary_function<IOMessage, void> {
-    public:
-        ASIOCallBack(ASIOLinkTest* test_obj) : test_obj_(test_obj) {}
-        void operator()(const IOMessage& io_message) const {
-            test_obj_->callBack(io_message);
-        }
-    private:
-        ASIOLinkTest* test_obj_;
-    };
-    void callBack(const IOMessage& io_message) {
-        callback_protocol_ = io_message.getSocket().getProtocol();
-        callback_native_ = io_message.getSocket().getNative();
-        callback_address_ =
-            io_message.getRemoteEndpoint().getAddress().toText();
-        callback_data_.assign(
-            static_cast<const uint8_t*>(io_message.getData()),
-            static_cast<const uint8_t*>(io_message.getData()) +
-            io_message.getDataSize());
-        io_service_->stop();
-    }
-protected:
-    IOService* io_service_;
-    int callback_protocol_;
-    int callback_native_;
-    string callback_address_;
-    vector<uint8_t> callback_data_;
-    int sock_;
-private:
-    struct addrinfo* res_;
-};
-
-ASIOLinkTest::ASIOLinkTest() :
-    io_service_(NULL), sock_(-1), res_(NULL)
-{
-    setIOService(true, true);
-}
-
-TEST_F(ASIOLinkTest, v6UDPSend) {
-    doTest(AF_INET6, IPPROTO_UDP);
-}
-
-TEST_F(ASIOLinkTest, v6TCPSend) {
-    doTest(AF_INET6, IPPROTO_TCP);
-}
-
-TEST_F(ASIOLinkTest, v4UDPSend) {
-    doTest(AF_INET, IPPROTO_UDP);
-}
-
-TEST_F(ASIOLinkTest, v4TCPSend) {
-    doTest(AF_INET, IPPROTO_TCP);
-}
-
-TEST_F(ASIOLinkTest, v6UDPSendSpecific) {
-    // Explicitly set a specific address to be bound to the socket.
-    // The subsequent test does not directly ensures the underlying socket
-    // is bound to the expected address, but the success of the tests should
-    // reasonably suggest it works as intended.
-    // Specifying an address also implicitly means the service runs in a
-    // single address-family mode.  In tests using TCP we can confirm that
-    // by trying to make a connection and seeing a failure.  In UDP, it'd be
-    // more complicated because we need to use a connected socket and catch
-    // an error on a subsequent read operation.  We could do it, but for
-    // simplicity we only tests the easier cases for now.
-
-    setIOService(*TEST_IPV6_ADDR);
-    doTest(AF_INET6, IPPROTO_UDP);
-}
-
-TEST_F(ASIOLinkTest, v6TCPSendSpecific) {
-    setIOService(*TEST_IPV6_ADDR);
-    doTest(AF_INET6, IPPROTO_TCP);
-
-    EXPECT_THROW(sendTCP(AF_INET), IOError);
-}
-
-TEST_F(ASIOLinkTest, v4UDPSendSpecific) {
-    setIOService(*TEST_IPV4_ADDR);
-    doTest(AF_INET, IPPROTO_UDP);
-}
-
-TEST_F(ASIOLinkTest, v4TCPSendSpecific) {
-    setIOService(*TEST_IPV4_ADDR);
-    doTest(AF_INET, IPPROTO_TCP);
-
-    EXPECT_THROW(sendTCP(AF_INET6), IOError);
-}
-
-TEST_F(ASIOLinkTest, 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.
-    setIOService(false, true);
-    EXPECT_THROW(sendTCP(AF_INET), IOError);
-}
-
-TEST_F(ASIOLinkTest, v4TCPOnly) {
-    setIOService(true, false);
-    EXPECT_THROW(sendTCP(AF_INET6), IOError);
-}
-
-}

+ 119 - 496
src/bin/auth/tests/auth_srv_unittest.cc

@@ -15,35 +15,16 @@
 // $Id$
 
 #include <config.h>
-
-#include <gtest/gtest.h>
-
-#include <dns/buffer.h>
-#include <dns/name.h>
-#include <dns/message.h>
-#include <dns/messagerenderer.h>
-#include <dns/opcode.h>
-#include <dns/rcode.h>
-#include <dns/rrclass.h>
-#include <dns/rrtype.h>
-
-#include <cc/data.h>
-#include <cc/session.h>
-
-#include <xfr/xfrout_client.h>
-
 #include <auth/auth_srv.h>
-#include <auth/asio_link.h>
+#include <testutils/srv_unittest.h>
 
-#include <dns/tests/unittest_util.h>
-
-using isc::UnitTestUtil;
 using namespace std;
 using namespace isc::cc;
 using namespace isc::dns;
 using namespace isc::data;
 using namespace isc::xfr;
-using namespace asio_link;
+using namespace asiolink;
+using isc::UnitTestUtil;
 
 namespace {
 const char* const CONFIG_TESTDB =
@@ -52,445 +33,83 @@ const char* const CONFIG_TESTDB =
 // the sqlite3 test).
 const char* const BADCONFIG_TESTDB =
     "{ \"database_file\": \"" TEST_DATA_DIR "/nodir/notexist\"}";
-const char* const DEFAULT_REMOTE_ADDRESS = "192.0.2.1";
-
-class AuthSrvTest : public ::testing::Test {
-private:
-    class MockXfroutClient : public AbstractXfroutClient {
-    public:
-        MockXfroutClient() :
-            is_connected_(false), connect_ok_(true), send_ok_(true),
-            disconnect_ok_(true)
-        {}
-        virtual void connect();
-        virtual void disconnect();
-        virtual int sendXfroutRequestInfo(int tcp_sock, const void* msg_data,
-                                          uint16_t msg_len);
-        bool isConnected() const { return (is_connected_); }
-        void disableConnect() { connect_ok_ = false; }
-        void disableDisconnect() { disconnect_ok_ = false; }
-        void enableDisconnect() { disconnect_ok_ = true; }
-        void disableSend() { send_ok_ = false; }
-    private:
-        bool is_connected_;
-        bool connect_ok_;
-        bool send_ok_;
-        bool disconnect_ok_;
-    };
-
-    class MockSession : public AbstractSession {
-    public:
-        MockSession() :
-            // by default we return a simple "success" message.
-            msg_(Element::fromJSON("{\"result\": [0, \"SUCCESS\"]}")),
-            send_ok_(true), receive_ok_(true)
-        {}
-        virtual void establish(const char* socket_file);
-        virtual void disconnect();
-        virtual int group_sendmsg(ConstElementPtr msg, string group,
-                                  string instance, string to);
-        virtual bool group_recvmsg(ConstElementPtr& envelope,
-                                   ConstElementPtr& msg,
-                                   bool nonblock, int seq);
-        virtual void subscribe(string group, string instance);
-        virtual void unsubscribe(string group, string instance);
-        virtual void startRead(boost::function<void()> read_callback);
-        virtual int reply(ConstElementPtr envelope, ConstElementPtr newmsg);
-        virtual bool hasQueuedMsgs() const;
-        virtual void setTimeout(size_t) {}
-        virtual size_t getTimeout() const { return 0; };
-
-        void setMessage(ConstElementPtr msg) { msg_ = msg; }
-        void disableSend() { send_ok_ = false; }
-        void disableReceive() { receive_ok_ = false; }
-
-        ConstElementPtr sent_msg;
-        string msg_destination;
-    private:
-        ConstElementPtr msg_;
-        bool send_ok_;
-        bool receive_ok_;
-    };
 
+class AuthSrvTest : public SrvTestBase {
 protected:
-    AuthSrvTest() : server(true, xfrout),
-                    request_message(Message::RENDER),
-                    parse_message(Message::PARSE), default_qid(0x1035),
-                    opcode(Opcode::QUERY()), qname("www.example.com"),
-                    qclass(RRClass::IN()), qtype(RRType::A()),
-                    io_message(NULL), endpoint(NULL), request_obuffer(0),
-                    request_renderer(request_obuffer),
-                    response_obuffer(0), response_renderer(response_obuffer)
-    {
+    AuthSrvTest() : server(true, xfrout) {
         server.setXfrinSession(&notify_session);
     }
-    ~AuthSrvTest() {
-        delete io_message;
-        delete endpoint;
-    }
-    MockSession notify_session;
     MockXfroutClient xfrout;
     AuthSrv server;
-    Message request_message;
-    Message parse_message;
-    const qid_t default_qid;
-    const Opcode opcode;
-    const Name qname;
-    const RRClass qclass;
-    const RRType qtype;
-    IOMessage* io_message;
-    const IOEndpoint* endpoint;
-    OutputBuffer request_obuffer;
-    MessageRenderer request_renderer;
-    OutputBuffer response_obuffer;
-    MessageRenderer response_renderer;
-    vector<uint8_t> data;
-
-    void createDataFromFile(const char* const datafile, int protocol);
-    void createRequestMessage(const Opcode& opcode, const Name& request_name,
-                              const RRClass& rrclass, const RRType& rrtype);
-    void createRequestPacket(const Opcode& opcode, const Name& request_name,
-                             const RRClass& rrclass, const RRType& rrtype,
-                             int protocol);
-    void createRequestPacket(int protocol);
 };
 
-void
-AuthSrvTest::MockSession::establish(const char*) {}
-
-void
-AuthSrvTest::MockSession::disconnect() {}
-
-void
-AuthSrvTest::MockSession::subscribe(string, string)
-{}
-
-void
-AuthSrvTest::MockSession::unsubscribe(string, string)
-{}
-
-void
-AuthSrvTest::MockSession::startRead(boost::function<void()>)
-{}
-
-int
-AuthSrvTest::MockSession::reply(ConstElementPtr, ConstElementPtr) {
-    return (-1);
-}
-
-bool
-AuthSrvTest::MockSession::hasQueuedMsgs() const {
-    return (false);
-}
-
-int
-AuthSrvTest::MockSession::group_sendmsg(ConstElementPtr msg, string group,
-                                        string, string)
-{
-    if (!send_ok_) {
-        isc_throw(XfroutError, "mock session send is disabled for test");
-    }
-
-    sent_msg = msg;
-    msg_destination = group;
-    return (0);
-}
-
-bool
-AuthSrvTest::MockSession::group_recvmsg(ConstElementPtr&,
-                                        ConstElementPtr& msg, bool, int)
-{
-    if (!receive_ok_) {
-        isc_throw(XfroutError, "mock session receive is disabled for test");
-    }
-
-    msg = msg_;
-    return (true);
-}
-
-void
-AuthSrvTest::MockXfroutClient::connect() {
-    if (!connect_ok_) {
-        isc_throw(XfroutError, "xfrout connection disabled for test");
-    }
-    is_connected_ = true;
-}
-
-void
-AuthSrvTest::MockXfroutClient::disconnect() {
-    if (!disconnect_ok_) {
-        isc_throw(XfroutError,
-                  "closing xfrout connection is disabled for test");
-    }
-    is_connected_ = false;
-}
-
-int
-AuthSrvTest::MockXfroutClient::sendXfroutRequestInfo(const int,
-                                                     const void*,
-                                                     const uint16_t)
-{
-    if (!send_ok_) {
-        isc_throw(XfroutError, "xfrout connection send is disabled for test");
-    }
-    return (0);
-}
-
-
-// These are flags to indicate whether the corresponding flag bit of the
-// DNS header is to be set in the test cases.  (Note that the flag values
-// is irrelevant to their wire-format values)
-const unsigned int QR_FLAG = 0x1;
-const unsigned int AA_FLAG = 0x2;
-const unsigned int TC_FLAG = 0x4;
-const unsigned int RD_FLAG = 0x8;
-const unsigned int RA_FLAG = 0x10;
-const unsigned int AD_FLAG = 0x20;
-const unsigned int CD_FLAG = 0x40;
-
-void
-AuthSrvTest::createDataFromFile(const char* const datafile,
-                                const int protocol = IPPROTO_UDP)
-{
-    delete io_message;
-    data.clear();
-
-    delete endpoint;
-    endpoint = IOEndpoint::create(protocol,
-                                  IOAddress(DEFAULT_REMOTE_ADDRESS), 5300);
-    UnitTestUtil::readWireData(datafile, data);
-    io_message = new IOMessage(&data[0], data.size(),
-                               protocol == IPPROTO_UDP ?
-                               IOSocket::getDummyUDPSocket() :
-                               IOSocket::getDummyTCPSocket(), *endpoint);
-}
-
-void
-AuthSrvTest::createRequestMessage(const Opcode& opcode,
-                                  const Name& request_name,
-                                  const RRClass& rrclass,
-                                  const RRType& rrtype)
-{
-    request_message.clear(Message::RENDER);
-    request_message.setOpcode(opcode);
-    request_message.setRcode(Rcode::NOERROR());
-    request_message.setQid(default_qid);
-    request_message.addQuestion(Question(request_name, rrclass, rrtype));
-}
-
-void
-AuthSrvTest::createRequestPacket(const Opcode& opcode,
-                                 const Name& request_name,
-                                 const RRClass& rrclass, const RRType& rrtype,
-                                 const int protocol = IPPROTO_UDP)
-{
-    createRequestMessage(opcode, request_name, rrclass, rrtype);
-    createRequestPacket(protocol);
-}
-
-void
-AuthSrvTest::createRequestPacket(const int protocol = IPPROTO_UDP) {
-    request_message.toWire(request_renderer);
-
-    delete io_message;
-    endpoint = IOEndpoint::create(protocol,
-                                  IOAddress(DEFAULT_REMOTE_ADDRESS), 5300);
-    io_message = new IOMessage(request_renderer.getData(),
-                               request_renderer.getLength(),
-                               protocol == IPPROTO_UDP ?
-                               IOSocket::getDummyUDPSocket() :
-                               IOSocket::getDummyTCPSocket(), *endpoint);
-}
-
-void
-headerCheck(const Message& message, const qid_t qid, const Rcode& rcode,
-            const uint16_t opcodeval, const unsigned int flags,
-            const unsigned int qdcount,
-            const unsigned int ancount, const unsigned int nscount,
-            const unsigned int arcount)
-{
-    EXPECT_EQ(qid, message.getQid());
-    EXPECT_EQ(rcode, message.getRcode());
-    EXPECT_EQ(opcodeval, message.getOpcode().getCode());
-    EXPECT_EQ((flags & QR_FLAG) != 0,
-              message.getHeaderFlag(Message::HEADERFLAG_QR));
-    EXPECT_EQ((flags & AA_FLAG) != 0,
-              message.getHeaderFlag(Message::HEADERFLAG_AA));
-    EXPECT_EQ((flags & TC_FLAG) != 0,
-              message.getHeaderFlag(Message::HEADERFLAG_TC));
-    EXPECT_EQ((flags & RA_FLAG) != 0,
-              message.getHeaderFlag(Message::HEADERFLAG_RA));
-    EXPECT_EQ((flags & RD_FLAG) != 0,
-              message.getHeaderFlag(Message::HEADERFLAG_RD));
-    EXPECT_EQ((flags & AD_FLAG) != 0,
-              message.getHeaderFlag(Message::HEADERFLAG_AD));
-    EXPECT_EQ((flags & CD_FLAG) != 0,
-              message.getHeaderFlag(Message::HEADERFLAG_CD));
-
-    EXPECT_EQ(qdcount, message.getRRCount(Message::SECTION_QUESTION));
-    EXPECT_EQ(ancount, message.getRRCount(Message::SECTION_ANSWER));
-    EXPECT_EQ(nscount, message.getRRCount(Message::SECTION_AUTHORITY));
-    EXPECT_EQ(arcount, message.getRRCount(Message::SECTION_ADDITIONAL));
-}
-
 // Unsupported requests.  Should result in NOTIMP.
 TEST_F(AuthSrvTest, unsupportedRequest) {
-    for (unsigned int i = 0; i < 16; ++i) {
-        // set Opcode to 'i', which iterators over all possible codes except
-        // the standard query and notify
-        if (i == Opcode::QUERY().getCode() ||
-            i == Opcode::NOTIFY().getCode()) {
-            continue;
-        }
-        createDataFromFile("simplequery_fromWire.wire");
-        data[2] = ((i << 3) & 0xff);
-
-        parse_message.clear(Message::PARSE);
-        EXPECT_TRUE(server.processMessage(*io_message, parse_message,
-                                          response_renderer));
-        headerCheck(parse_message, default_qid, Rcode::NOTIMP(), i, QR_FLAG,
-                    0, 0, 0, 0);
-    }
+    UNSUPPORTED_REQUEST_TEST;
 }
 
 // Simple API check
 TEST_F(AuthSrvTest, verbose) {
-    EXPECT_FALSE(server.getVerbose());
-    server.setVerbose(true);
-    EXPECT_TRUE(server.getVerbose());
-    server.setVerbose(false);
-    EXPECT_FALSE(server.getVerbose());
+    VERBOSE_TEST;
 }
 
 // Multiple questions.  Should result in FORMERR.
 TEST_F(AuthSrvTest, multiQuestion) {
-    createDataFromFile("multiquestion_fromWire.wire");
-    EXPECT_TRUE(server.processMessage(*io_message, parse_message,
-                                      response_renderer));
-    headerCheck(parse_message, default_qid, Rcode::FORMERR(), opcode.getCode(),
-                QR_FLAG, 2, 0, 0, 0);
-
-    QuestionIterator qit = parse_message.beginQuestion();
-    EXPECT_EQ(Name("example.com"), (*qit)->getName());
-    EXPECT_EQ(RRClass::IN(), (*qit)->getClass());
-    EXPECT_EQ(RRType::A(), (*qit)->getType());
-    ++qit;
-    EXPECT_EQ(Name("example.com"), (*qit)->getName());
-    EXPECT_EQ(RRClass::IN(), (*qit)->getClass());
-    EXPECT_EQ(RRType::AAAA(), (*qit)->getType());
-    ++qit;
-    EXPECT_TRUE(qit == parse_message.endQuestion());
+    MULTI_QUESTION_TEST;
 }
 
 // Incoming data doesn't even contain the complete header.  Must be silently
 // dropped.
 TEST_F(AuthSrvTest, shortMessage) {
-    createDataFromFile("shortmessage_fromWire");
-    EXPECT_FALSE(server.processMessage(*io_message, parse_message,
-                                       response_renderer));
+    SHORT_MESSAGE_TEST;
 }
 
 // Response messages.  Must be silently dropped, whether it's a valid response
 // or malformed or could otherwise cause a protocol error.
 TEST_F(AuthSrvTest, response) {
-    // A valid (although unusual) response
-    createDataFromFile("simpleresponse_fromWire.wire");
-    EXPECT_FALSE(server.processMessage(*io_message, parse_message,
-                                       response_renderer));
-
-    // A response with a broken question section.  must be dropped rather than
-    // returning FORMERR.
-    createDataFromFile("shortresponse_fromWire");
-    EXPECT_FALSE(server.processMessage(*io_message, parse_message,
-                                       response_renderer));
-
-    // A response to iquery.  must be dropped rather than returning NOTIMP.
-    createDataFromFile("iqueryresponse_fromWire.wire");
-    EXPECT_FALSE(server.processMessage(*io_message, parse_message,
-                                       response_renderer));
+    RESPONSE_TEST;
 }
 
 // Query with a broken question
 TEST_F(AuthSrvTest, shortQuestion) {
-    createDataFromFile("shortquestion_fromWire");
-    EXPECT_TRUE(server.processMessage(*io_message, parse_message,
-                                      response_renderer));
-    // Since the query's question is broken, the question section of the
-    // response should be empty.
-    headerCheck(parse_message, default_qid, Rcode::FORMERR(), opcode.getCode(),
-                QR_FLAG, 0, 0, 0, 0);
+    SHORT_QUESTION_TEST;
 }
 
 // Query with a broken answer section
 TEST_F(AuthSrvTest, shortAnswer) {
-    createDataFromFile("shortanswer_fromWire.wire");
-    EXPECT_TRUE(server.processMessage(*io_message, parse_message,
-                                      response_renderer));
-
-    // This is a bogus query, but question section is valid.  So the response
-    // should copy the question section.
-    headerCheck(parse_message, default_qid, Rcode::FORMERR(), opcode.getCode(),
-                QR_FLAG, 1, 0, 0, 0);
-
-    QuestionIterator qit = parse_message.beginQuestion();
-    EXPECT_EQ(Name("example.com"), (*qit)->getName());
-    EXPECT_EQ(RRClass::IN(), (*qit)->getClass());
-    EXPECT_EQ(RRType::A(), (*qit)->getType());
-    ++qit;
-    EXPECT_TRUE(qit == parse_message.endQuestion());
+    SHORT_ANSWER_TEST;
 }
 
 // Query with unsupported version of EDNS.
 TEST_F(AuthSrvTest, ednsBadVers) {
-    createDataFromFile("queryBadEDNS_fromWire.wire");
-    EXPECT_TRUE(server.processMessage(*io_message, parse_message,
-                                      response_renderer));
-
-    // The response must have an EDNS OPT RR in the additional section, but
-    // it will be added automatically at the render time.
-    // Note that the DNSSEC DO bit is cleared even if this bit in the query
-    // is set.  This is a limitation of the current implementation.
-    headerCheck(parse_message, default_qid, Rcode::BADVERS(), opcode.getCode(),
-                QR_FLAG, 1, 0, 0, 1);
-    EXPECT_FALSE(parse_message.getEDNS()); // EDNS isn't added at this point
-
-    parse_message.clear(Message::PARSE);
-    InputBuffer ib(response_renderer.getData(), response_renderer.getLength());
-    parse_message.fromWire(ib);
-    EXPECT_EQ(Rcode::BADVERS(), parse_message.getRcode());
-    EXPECT_TRUE(parse_message.getEDNS());
-    EXPECT_FALSE(parse_message.getEDNS()->getDNSSECAwareness());
+    EDNS_BADVERS_TEST;
 }
 
 TEST_F(AuthSrvTest, AXFROverUDP) {
-    // AXFR over UDP is invalid and should result in FORMERR.
-    createRequestPacket(opcode, Name("example.com"), RRClass::IN(),
-                        RRType::AXFR(), IPPROTO_UDP);
-    EXPECT_TRUE(server.processMessage(*io_message, parse_message,
-                                      response_renderer));
-    headerCheck(parse_message, default_qid, Rcode::FORMERR(), opcode.getCode(),
-                QR_FLAG, 1, 0, 0, 0);
+    AXFR_OVER_UDP_TEST;
 }
 
 TEST_F(AuthSrvTest, AXFRSuccess) {
     EXPECT_FALSE(xfrout.isConnected());
-    createRequestPacket(opcode, Name("example.com"), RRClass::IN(),
-                        RRType::AXFR(), IPPROTO_TCP);
+    UnitTestUtil::createRequestMessage(request_message, opcode, default_qid,
+                         Name("example.com"), RRClass::IN(), RRType::AXFR());
+    createRequestPacket(request_message, IPPROTO_TCP);
     // On success, the AXFR query has been passed to a separate process,
     // so we shouldn't have to respond.
-    EXPECT_FALSE(server.processMessage(*io_message, parse_message,
-                                       response_renderer));
+    server.processMessage(*io_message, parse_message, response_obuffer, &dnsserv);
+    EXPECT_FALSE(dnsserv.hasAnswer());
     EXPECT_TRUE(xfrout.isConnected());
 }
 
 TEST_F(AuthSrvTest, AXFRConnectFail) {
     EXPECT_FALSE(xfrout.isConnected()); // check prerequisite
     xfrout.disableConnect();
-    createRequestPacket(opcode, Name("example.com"), RRClass::IN(),
-                        RRType::AXFR(), IPPROTO_TCP);
-    EXPECT_TRUE(server.processMessage(*io_message, parse_message,
-                                      response_renderer));
-    headerCheck(parse_message, default_qid, Rcode::SERVFAIL(),
+    UnitTestUtil::createRequestMessage(request_message, opcode, default_qid,
+                         Name("example.com"), RRClass::IN(), RRType::AXFR());
+    createRequestPacket(request_message, IPPROTO_TCP);
+    server.processMessage(*io_message, parse_message, response_obuffer, &dnsserv);
+    EXPECT_TRUE(dnsserv.hasAnswer());
+    headerCheck(*parse_message, default_qid, Rcode::SERVFAIL(),
                 opcode.getCode(), QR_FLAG, 1, 0, 0, 0);
     EXPECT_FALSE(xfrout.isConnected());
 }
@@ -498,19 +117,21 @@ TEST_F(AuthSrvTest, AXFRConnectFail) {
 TEST_F(AuthSrvTest, AXFRSendFail) {
     // first send a valid query, making the connection with the xfr process
     // open.
-    createRequestPacket(opcode, Name("example.com"), RRClass::IN(),
-                        RRType::AXFR(), IPPROTO_TCP);
-    server.processMessage(*io_message, parse_message, response_renderer);
+    UnitTestUtil::createRequestMessage(request_message, opcode, default_qid,
+                         Name("example.com"), RRClass::IN(), RRType::AXFR());
+    createRequestPacket(request_message, IPPROTO_TCP);
+    server.processMessage(*io_message, parse_message, response_obuffer, &dnsserv);
     EXPECT_TRUE(xfrout.isConnected());
 
     xfrout.disableSend();
-    parse_message.clear(Message::PARSE);
-    response_renderer.clear();
-    createRequestPacket(opcode, Name("example.com"), RRClass::IN(),
-                        RRType::AXFR(), IPPROTO_TCP);
-    EXPECT_TRUE(server.processMessage(*io_message, parse_message,
-                                      response_renderer));
-    headerCheck(parse_message, default_qid, Rcode::SERVFAIL(),
+    parse_message->clear(Message::PARSE);
+    response_obuffer->clear();
+    UnitTestUtil::createRequestMessage(request_message, opcode, default_qid,
+                         Name("example.com"), RRClass::IN(), RRType::AXFR());
+    createRequestPacket(request_message, IPPROTO_TCP);
+    server.processMessage(*io_message, parse_message, response_obuffer, &dnsserv);
+    EXPECT_TRUE(dnsserv.hasAnswer());
+    headerCheck(*parse_message, default_qid, Rcode::SERVFAIL(),
                 opcode.getCode(), QR_FLAG, 1, 0, 0, 0);
 
     // The connection should have been closed due to the send failure.
@@ -522,10 +143,11 @@ TEST_F(AuthSrvTest, AXFRDisconnectFail) {
     // should it be thrown.
     xfrout.disableSend();
     xfrout.disableDisconnect();
-    createRequestPacket(opcode, Name("example.com"), RRClass::IN(),
-                        RRType::AXFR(), IPPROTO_TCP);
+    UnitTestUtil::createRequestMessage(request_message, opcode, default_qid,
+                         Name("example.com"), RRClass::IN(), RRType::AXFR());
+    createRequestPacket(request_message, IPPROTO_TCP);
     EXPECT_THROW(server.processMessage(*io_message, parse_message,
-                                       response_renderer),
+                                       response_obuffer, &dnsserv),
                  XfroutError);
     EXPECT_TRUE(xfrout.isConnected());
     // XXX: we need to re-enable disconnect.  otherwise an exception would be
@@ -534,31 +156,31 @@ TEST_F(AuthSrvTest, AXFRDisconnectFail) {
 }
 
 TEST_F(AuthSrvTest, notify) {
-    createRequestMessage(Opcode::NOTIFY(), Name("example.com"), RRClass::IN(),
-                        RRType::SOA());
+    UnitTestUtil::createRequestMessage(request_message, Opcode::NOTIFY(), default_qid,
+                         Name("example.com"), RRClass::IN(), RRType::SOA());
     request_message.setHeaderFlag(Message::HEADERFLAG_AA);
-    createRequestPacket(IPPROTO_UDP);
-    EXPECT_TRUE(server.processMessage(*io_message, parse_message,
-                                      response_renderer));
+    createRequestPacket(request_message, IPPROTO_UDP);
+    server.processMessage(*io_message, parse_message, response_obuffer, &dnsserv);
+    EXPECT_TRUE(dnsserv.hasAnswer());
 
     // An internal command message should have been created and sent to an
     // external module.  Check them.
-    EXPECT_EQ("Zonemgr", notify_session.msg_destination);
+    EXPECT_EQ("Zonemgr", notify_session.getMessageDest());
     EXPECT_EQ("notify",
-              notify_session.sent_msg->get("command")->get(0)->stringValue());
+              notify_session.getSentMessage()->get("command")->get(0)->stringValue());
     ConstElementPtr notify_args =
-        notify_session.sent_msg->get("command")->get(1);
+        notify_session.getSentMessage()->get("command")->get(1);
     EXPECT_EQ("example.com.", notify_args->get("zone_name")->stringValue());
     EXPECT_EQ(DEFAULT_REMOTE_ADDRESS,
               notify_args->get("master")->stringValue());
     EXPECT_EQ("IN", notify_args->get("zone_class")->stringValue());
 
     // On success, the server should return a response to the notify.
-    headerCheck(parse_message, default_qid, Rcode::NOERROR(),
+    headerCheck(*parse_message, default_qid, Rcode::NOERROR(),
                 Opcode::NOTIFY().getCode(), QR_FLAG | AA_FLAG, 1, 0, 0, 0);
 
     // The question must be identical to that of the received notify
-    ConstQuestionPtr question = *parse_message.beginQuestion();
+    ConstQuestionPtr question = *parse_message->beginQuestion();
     EXPECT_EQ(Name("example.com"), question->getName());
     EXPECT_EQ(RRClass::IN(), question->getClass());
     EXPECT_EQ(RRType::SOA(), question->getType());
@@ -566,17 +188,17 @@ TEST_F(AuthSrvTest, notify) {
 
 TEST_F(AuthSrvTest, notifyForCHClass) {
     // Same as the previous test, but for the CH RRClass.
-    createRequestMessage(Opcode::NOTIFY(), Name("example.com"), RRClass::CH(),
-                        RRType::SOA());
+    UnitTestUtil::createRequestMessage(request_message, Opcode::NOTIFY(), default_qid,
+                         Name("example.com"), RRClass::CH(), RRType::SOA());
     request_message.setHeaderFlag(Message::HEADERFLAG_AA);
-    createRequestPacket(IPPROTO_UDP);
-    EXPECT_TRUE(server.processMessage(*io_message, parse_message,
-                                      response_renderer));
+    createRequestPacket(request_message, IPPROTO_UDP);
+    server.processMessage(*io_message, parse_message, response_obuffer, &dnsserv);
+    EXPECT_TRUE(dnsserv.hasAnswer());
 
     // Other conditions should be the same, so simply confirm the RR class is
     // set correctly.
     ConstElementPtr notify_args =
-        notify_session.sent_msg->get("command")->get(1);
+        notify_session.getSentMessage()->get("command")->get(1);
     EXPECT_EQ("CH", notify_args->get("zone_class")->stringValue());
 }
 
@@ -587,118 +209,119 @@ TEST_F(AuthSrvTest, notifyEmptyQuestion) {
     request_message.setHeaderFlag(Message::HEADERFLAG_AA);
     request_message.setQid(default_qid);
     request_message.toWire(request_renderer);
-    createRequestPacket(IPPROTO_UDP);
-    EXPECT_TRUE(server.processMessage(*io_message, parse_message,
-                                      response_renderer));
-    headerCheck(parse_message, default_qid, Rcode::FORMERR(),
+    createRequestPacket(request_message, IPPROTO_UDP);
+    server.processMessage(*io_message, parse_message, response_obuffer, &dnsserv);
+    EXPECT_TRUE(dnsserv.hasAnswer());
+    headerCheck(*parse_message, default_qid, Rcode::FORMERR(),
                 Opcode::NOTIFY().getCode(), QR_FLAG, 0, 0, 0, 0);
 }
 
 TEST_F(AuthSrvTest, notifyMultiQuestions) {
-    createRequestMessage(Opcode::NOTIFY(), Name("example.com"), RRClass::IN(),
-                        RRType::SOA());
+    UnitTestUtil::createRequestMessage(request_message, Opcode::NOTIFY(), default_qid,
+                         Name("example.com"), RRClass::IN(), RRType::SOA());
     // add one more SOA question
     request_message.addQuestion(Question(Name("example.com"), RRClass::IN(),
                                          RRType::SOA()));
     request_message.setHeaderFlag(Message::HEADERFLAG_AA);
-    createRequestPacket(IPPROTO_UDP);
-    EXPECT_TRUE(server.processMessage(*io_message, parse_message,
-                                      response_renderer));
-    headerCheck(parse_message, default_qid, Rcode::FORMERR(),
+    createRequestPacket(request_message, IPPROTO_UDP);
+    server.processMessage(*io_message, parse_message, response_obuffer, &dnsserv);
+    EXPECT_TRUE(dnsserv.hasAnswer());
+    headerCheck(*parse_message, default_qid, Rcode::FORMERR(),
                 Opcode::NOTIFY().getCode(), QR_FLAG, 2, 0, 0, 0);
 }
 
 TEST_F(AuthSrvTest, notifyNonSOAQuestion) {
-    createRequestMessage(Opcode::NOTIFY(), Name("example.com"), RRClass::IN(),
-                        RRType::NS());
+    UnitTestUtil::createRequestMessage(request_message, Opcode::NOTIFY(), default_qid,
+                         Name("example.com"), RRClass::IN(), RRType::NS());
     request_message.setHeaderFlag(Message::HEADERFLAG_AA);
-    createRequestPacket(IPPROTO_UDP);
-    EXPECT_TRUE(server.processMessage(*io_message, parse_message,
-                                      response_renderer));
-    headerCheck(parse_message, default_qid, Rcode::FORMERR(),
+    createRequestPacket(request_message, IPPROTO_UDP);
+    server.processMessage(*io_message, parse_message, response_obuffer, &dnsserv);
+    EXPECT_TRUE(dnsserv.hasAnswer());
+    headerCheck(*parse_message, default_qid, Rcode::FORMERR(),
                 Opcode::NOTIFY().getCode(), QR_FLAG, 1, 0, 0, 0);
 }
 
 TEST_F(AuthSrvTest, notifyWithoutAA) {
     // implicitly leave the AA bit off.  our implementation will accept it.
-    createRequestPacket(Opcode::NOTIFY(), Name("example.com"), RRClass::IN(),
-                        RRType::SOA());
-    EXPECT_TRUE(server.processMessage(*io_message, parse_message,
-                                      response_renderer));
-    headerCheck(parse_message, default_qid, Rcode::NOERROR(),
+    UnitTestUtil::createRequestMessage(request_message, Opcode::NOTIFY(), default_qid,
+                         Name("example.com"), RRClass::IN(), RRType::SOA());
+    createRequestPacket(request_message, IPPROTO_UDP);
+    server.processMessage(*io_message, parse_message, response_obuffer, &dnsserv);
+    EXPECT_TRUE(dnsserv.hasAnswer());
+    headerCheck(*parse_message, default_qid, Rcode::NOERROR(),
                 Opcode::NOTIFY().getCode(), QR_FLAG | AA_FLAG, 1, 0, 0, 0);
 }
 
 TEST_F(AuthSrvTest, notifyWithErrorRcode) {
-    createRequestMessage(Opcode::NOTIFY(), Name("example.com"), RRClass::IN(),
-                        RRType::SOA());
+    UnitTestUtil::createRequestMessage(request_message, Opcode::NOTIFY(), default_qid,
+                         Name("example.com"), RRClass::IN(), RRType::SOA());
     request_message.setHeaderFlag(Message::HEADERFLAG_AA);
     request_message.setRcode(Rcode::SERVFAIL());
-    createRequestPacket(IPPROTO_UDP);
-    EXPECT_TRUE(server.processMessage(*io_message, parse_message,
-                                      response_renderer));
-    headerCheck(parse_message, default_qid, Rcode::NOERROR(),
+    createRequestPacket(request_message, IPPROTO_UDP);
+    server.processMessage(*io_message, parse_message, response_obuffer, &dnsserv);
+    EXPECT_TRUE(dnsserv.hasAnswer());
+    headerCheck(*parse_message, default_qid, Rcode::NOERROR(),
                 Opcode::NOTIFY().getCode(), QR_FLAG | AA_FLAG, 1, 0, 0, 0);
 }
 
 TEST_F(AuthSrvTest, notifyWithoutSession) {
     server.setXfrinSession(NULL);
 
-    createRequestMessage(Opcode::NOTIFY(), Name("example.com"), RRClass::IN(),
-                        RRType::SOA());
+    UnitTestUtil::createRequestMessage(request_message, Opcode::NOTIFY(), default_qid,
+                         Name("example.com"), RRClass::IN(), RRType::SOA());
     request_message.setHeaderFlag(Message::HEADERFLAG_AA);
-    createRequestPacket(IPPROTO_UDP);
+    createRequestPacket(request_message, IPPROTO_UDP);
 
     // we simply ignore the notify and let it be resent if an internal error
     // happens.
-    EXPECT_FALSE(server.processMessage(*io_message, parse_message,
-                                       response_renderer));
+    server.processMessage(*io_message, parse_message, response_obuffer, &dnsserv);
+    EXPECT_FALSE(dnsserv.hasAnswer());
 }
 
 TEST_F(AuthSrvTest, notifySendFail) {
     notify_session.disableSend();
 
-    createRequestMessage(Opcode::NOTIFY(), Name("example.com"), RRClass::IN(),
-                        RRType::SOA());
+    UnitTestUtil::createRequestMessage(request_message, Opcode::NOTIFY(), default_qid,
+                         Name("example.com"), RRClass::IN(), RRType::SOA());
     request_message.setHeaderFlag(Message::HEADERFLAG_AA);
-    createRequestPacket(IPPROTO_UDP);
+    createRequestPacket(request_message, IPPROTO_UDP);
 
-    EXPECT_FALSE(server.processMessage(*io_message, parse_message,
-                                       response_renderer));
+    server.processMessage(*io_message, parse_message, response_obuffer, &dnsserv);
+    EXPECT_FALSE(dnsserv.hasAnswer());
 }
 
 TEST_F(AuthSrvTest, notifyReceiveFail) {
     notify_session.disableReceive();
 
-    createRequestMessage(Opcode::NOTIFY(), Name("example.com"), RRClass::IN(),
-                        RRType::SOA());
+    UnitTestUtil::createRequestMessage(request_message, Opcode::NOTIFY(), default_qid,
+                         Name("example.com"), RRClass::IN(), RRType::SOA());
     request_message.setHeaderFlag(Message::HEADERFLAG_AA);
-    createRequestPacket(IPPROTO_UDP);
-    EXPECT_FALSE(server.processMessage(*io_message, parse_message,
-                                       response_renderer));
+    createRequestPacket(request_message, IPPROTO_UDP);
+    server.processMessage(*io_message, parse_message, response_obuffer, &dnsserv);
+    EXPECT_FALSE(dnsserv.hasAnswer());
 }
 
 TEST_F(AuthSrvTest, notifyWithBogusSessionMessage) {
     notify_session.setMessage(Element::fromJSON("{\"foo\": 1}"));
 
-    createRequestMessage(Opcode::NOTIFY(), Name("example.com"), RRClass::IN(),
-                        RRType::SOA());
+    UnitTestUtil::createRequestMessage(request_message, Opcode::NOTIFY(), default_qid,
+                         Name("example.com"), RRClass::IN(), RRType::SOA());
     request_message.setHeaderFlag(Message::HEADERFLAG_AA);
-    createRequestPacket(IPPROTO_UDP);
-    EXPECT_FALSE(server.processMessage(*io_message, parse_message,
-                                       response_renderer));
+    createRequestPacket(request_message, IPPROTO_UDP);
+    server.processMessage(*io_message, parse_message, response_obuffer, &dnsserv);
+    EXPECT_FALSE(dnsserv.hasAnswer());
 }
 
 TEST_F(AuthSrvTest, notifyWithSessionMessageError) {
     notify_session.setMessage(
         Element::fromJSON("{\"result\": [1, \"FAIL\"]}"));
 
-    createRequestMessage(Opcode::NOTIFY(), Name("example.com"), RRClass::IN(),
-                        RRType::SOA());
+    UnitTestUtil::createRequestMessage(request_message, Opcode::NOTIFY(), default_qid,
+                         Name("example.com"), RRClass::IN(), RRType::SOA());
     request_message.setHeaderFlag(Message::HEADERFLAG_AA);
-    createRequestPacket(IPPROTO_UDP);
-    EXPECT_FALSE(server.processMessage(*io_message, parse_message,
-                                       response_renderer));
+    createRequestPacket(request_message, IPPROTO_UDP);
+    server.processMessage(*io_message, parse_message, response_obuffer, &dnsserv);
+    EXPECT_FALSE(dnsserv.hasAnswer());
 }
 
 void
@@ -723,9 +346,9 @@ TEST_F(AuthSrvTest, updateConfig) {
     // response should have the AA flag on, and have an RR in each answer
     // and authority section.
     createDataFromFile("examplequery_fromWire.wire");
-    EXPECT_TRUE(server.processMessage(*io_message, parse_message,
-                                      response_renderer));
-    headerCheck(parse_message, default_qid, Rcode::NOERROR(), opcode.getCode(),
+    server.processMessage(*io_message, parse_message, response_obuffer, &dnsserv);
+    EXPECT_TRUE(dnsserv.hasAnswer());
+    headerCheck(*parse_message, default_qid, Rcode::NOERROR(), opcode.getCode(),
                 QR_FLAG | AA_FLAG, 1, 1, 1, 0);
 }
 
@@ -737,9 +360,9 @@ TEST_F(AuthSrvTest, datasourceFail) {
     // in a SERVFAIL response, and the answer and authority sections should
     // be empty.
     createDataFromFile("badExampleQuery_fromWire.wire");
-    EXPECT_TRUE(server.processMessage(*io_message, parse_message,
-                                      response_renderer));
-    headerCheck(parse_message, default_qid, Rcode::SERVFAIL(),
+    server.processMessage(*io_message, parse_message, response_obuffer, &dnsserv);
+    EXPECT_TRUE(dnsserv.hasAnswer());
+    headerCheck(*parse_message, default_qid, Rcode::SERVFAIL(),
                 opcode.getCode(), QR_FLAG, 1, 0, 0, 0);
 }
 
@@ -752,9 +375,9 @@ TEST_F(AuthSrvTest, updateConfigFail) {
 
     // The original data source should still exist.
     createDataFromFile("examplequery_fromWire.wire");
-    EXPECT_TRUE(server.processMessage(*io_message, parse_message,
-                                      response_renderer));
-    headerCheck(parse_message, default_qid, Rcode::NOERROR(), opcode.getCode(),
+    server.processMessage(*io_message, parse_message, response_obuffer, &dnsserv);
+    EXPECT_TRUE(dnsserv.hasAnswer());
+    headerCheck(*parse_message, default_qid, Rcode::NOERROR(), opcode.getCode(),
                 QR_FLAG | AA_FLAG, 1, 1, 1, 0);
 }
 

+ 2 - 2
src/bin/auth/tests/query_unittest.cc

@@ -55,7 +55,7 @@ TEST_F(QueryTest, noZone) {
 TEST_F(QueryTest, matchZone) {
     // add a matching zone.  since the zone is empty right now, the response
     // should have NXDOMAIN.
-    zone_table.add(ZonePtr(new Zone(qclass, Name("example.com"))));
+    zone_table.addZone(ZonePtr(new MemoryZone(qclass, Name("example.com"))));
     query.process();
     EXPECT_EQ(Rcode::NXDOMAIN(), response.getRcode());
 }
@@ -63,7 +63,7 @@ TEST_F(QueryTest, matchZone) {
 TEST_F(QueryTest, noMatchZone) {
     // there's a zone in the table but it doesn't match the qname.  should
     // result in SERVFAIL.
-    zone_table.add(ZonePtr(new Zone(qclass, Name("example.org"))));
+    zone_table.addZone(ZonePtr(new MemoryZone(qclass, Name("example.org"))));
     query.process();
     EXPECT_EQ(Rcode::SERVFAIL(), response.getRcode());
 }

+ 74 - 43
src/bin/bind10/bind10.py.in

@@ -194,8 +194,9 @@ class CChannelConnectError(Exception): pass
 class BoB:
     """Boss of BIND class."""
     
-    def __init__(self, msgq_socket_file=None, auth_port=5300, address=None,
-                 nocache=False, verbose=False, setuid=None, username=None):
+    def __init__(self, msgq_socket_file=None, dns_port=5300, address=None,
+                 forward=None, nocache=False, verbose=False, setuid=None,
+                 username=None):
         """
             Initialize the Boss of BIND. This is a singleton (only one can run).
         
@@ -204,7 +205,13 @@ class BoB:
             what it is doing.
         """
         self.address = address
-        self.auth_port = auth_port
+        self.dns_port = dns_port
+        self.forward = None
+        self.recursive = False
+        if forward:
+            self.forward = forward
+            self.recursive = True
+            self.nocache = False
         self.cc_session = None
         self.ccs = None
         self.cfg_start_auth = True
@@ -417,19 +424,26 @@ class BoB:
             Start the Authoritative server
         """
         # XXX: this must be read from the configuration manager in the future
-        authargs = ['b10-auth', '-p', str(self.auth_port)]
-        if self.address:
-            authargs += ['-a', str(self.address)]
-        if self.nocache:
-            authargs += ['-n']
+        if self.recursive:
+            dns_prog = 'b10-recurse'
+        else:
+            dns_prog = 'b10-auth'
+        dnsargs = [dns_prog]
+        if not self.recursive:
+            # The recursive uses configuration manager for these
+            dnsargs += ['-p', str(self.dns_port)]
+            if self.address:
+                dnsargs += ['-a', str(self.address)]
+            if self.nocache:
+                dnsargs += ['-n']
         if self.uid:
-            authargs += ['-u', str(self.uid)]
+            dnsargs += ['-u', str(self.uid)]
         if self.verbose:
-            authargs += ['-v']
+            dnsargs += ['-v']
 
         # ... and start
-        self.start_process("b10-auth", authargs, c_channel_env,
-            self.auth_port, self.address)
+        self.start_process("b10-auth", dnsargs, c_channel_env,
+            self.dns_port, self.address)
 
     def start_recurse(self, c_channel_env):
         """
@@ -540,6 +554,7 @@ class BoB:
     def stop_all_processes(self):
         """Stop all processes."""
         cmd = { "command": ['shutdown']}
+
         self.cc_session.group_sendmsg(cmd, 'Cmdctl', 'Cmdctl')
         self.cc_session.group_sendmsg(cmd, "ConfigManager", "ConfigManager")
         self.cc_session.group_sendmsg(cmd, "Auth", "Auth")
@@ -547,7 +562,7 @@ class BoB:
         self.cc_session.group_sendmsg(cmd, "Xfrout", "Xfrout")
         self.cc_session.group_sendmsg(cmd, "Xfrin", "Xfrin")
         self.cc_session.group_sendmsg(cmd, "Zonemgr", "Zonemgr")
-        self.cc_session.group_sendmsg(cmd, "Boss", "Stats")
+        self.cc_session.group_sendmsg(cmd, "Stats", "Stats")
 
     def stop_process(self, process):
         """Stop the given process, friendly-like."""
@@ -612,27 +627,44 @@ class BoB:
                 raise
             if pid == 0: break
             if pid in self.processes:
+
+                # One of the processes we know about.  Get information on it.
                 proc_info = self.processes.pop(pid)
                 proc_info.restart_schedule.set_run_stop_time()
                 self.dead_processes[proc_info.pid] = proc_info
-                if self.verbose:
-                    sys.stdout.write("[bind10] Process %s (PID %d) died.\n" % 
-                                     (proc_info.name, proc_info.pid))
-                if proc_info.name == "b10-msgq":
-                    if self.verbose and self.runnable:
+
+                # Write out message, but only if in the running state:
+                # During startup and shutdown, these messages are handled
+                # elsewhere.
+                if self.runnable:
+                    if exit_status is None:
+                        sys.stdout.write(
+                            "[bind10] Process %s (PID %d) died: exit status not available" % 
+                            (proc_info.name, proc_info.pid))
+                    else:
+                        sys.stdout.write(
+                            "[bind10] Process %s (PID %d) terminated, exit status = %d\n" % 
+                            (proc_info.name, proc_info.pid, exit_status))
+
+                    # Was it a special process?
+                    if proc_info.name == "b10-msgq":
                         sys.stdout.write(
                                  "[bind10] The b10-msgq process died, shutting down.\n")
-                    self.runnable = False
+                        self.runnable = False
             else:
                 sys.stdout.write("[bind10] Unknown child pid %d exited.\n" % pid)
 
     def restart_processes(self):
-        """Restart any dead processes.
-        Returns the time when the next process is ready to be restarted. 
-          If the server is shutting down, returns 0.
-          If there are no processes, returns None.
-        The values returned can be safely passed into select() as the 
-        timeout value."""
+        """
+            Restart any dead processes:
+
+            * Returns the time when the next process is ready to be restarted. 
+            * If the server is shutting down, returns 0.
+            * If there are no processes, returns None.
+
+            The values returned can be safely passed into select() as the 
+            timeout value.
+        """
         next_restart = None
         # if we're shutting down, then don't restart
         if not self.runnable:
@@ -649,13 +681,12 @@ class BoB:
             else:
                 if self.verbose:
                     sys.stdout.write("[bind10] Resurrecting dead %s process...\n" % 
-                                     proc_info.name)
+                        proc_info.name)
                 try:
                     proc_info.respawn()
                     self.processes[proc_info.pid] = proc_info
-                    if self.verbose:
-                        sys.stdout.write("[bind10] Resurrected %s (PID %d)\n" %
-                                         (proc_info.name, proc_info.pid))
+                    sys.stdout.write("[bind10] Resurrected %s (PID %d)\n" %
+                                     (proc_info.name, proc_info.pid))
                 except:
                     still_dead[proc_info.pid] = proc_info
         # remember any processes that refuse to be resurrected
@@ -697,7 +728,7 @@ def check_port(option, opt_str, value, parser):
     a valid port number. Used by OptionParser() on startup."""
     try:
         if opt_str in ['-p', '--port']:
-            parser.values.auth_port = isc.net.parse.port_parse(value)
+            parser.values.dns_port = isc.net.parse.port_parse(value)
         else:
             raise OptionValueError("Unknown option " + opt_str)
     except ValueError as e:
@@ -709,6 +740,8 @@ def check_addr(option, opt_str, value, parser):
     try:
         if opt_str in ['-a', '--address']:
             parser.values.address = isc.net.parse.addr_parse(value)
+        if opt_str in ['-f', '--forward']:
+            parser.values.forward = isc.net.parse.addr_parse(value)
         else:
             raise OptionValueError("Unknown option " + opt_str)
     except ValueError:
@@ -728,17 +761,19 @@ def main():
     parser = OptionParser(version=VERSION)
     parser.add_option("-a", "--address", dest="address", type="string",
                       action="callback", callback=check_addr, default=None,
-                      help="address the b10-auth daemon will use (default: listen on all addresses)")
+                      help="address the DNS server will use (default: listen on all addresses)")
+    parser.add_option("-f", "--forward", dest="forward", type="string",
+                      action="callback", callback=check_addr, default='',
+                      help="nameserver to which DNS queries should be forwarded")
     parser.add_option("-m", "--msgq-socket-file", dest="msgq_socket_file",
                       type="string", default=None,
                       help="UNIX domain socket file the b10-msgq daemon will use")
     parser.add_option("-n", "--no-cache", action="store_true", dest="nocache",
-                      default=False, help="disable hot-spot cache in b10-auth")
-    parser.add_option("-p", "--port", dest="auth_port", type="int",
+                      default=False, help="disable hot-spot cache in authoritative DNS server")
+    parser.add_option("-p", "--port", dest="dns_port", type="int",
                       action="callback", callback=check_port, default=5300,
-                      help="port the b10-auth daemon will use (default 5300)")
-    parser.add_option("-u", "--user", dest="user",
-                      type="string", default=None,
+                      help="port the DNS server will use (default 5300)")
+    parser.add_option("-u", "--user", dest="user", type="string", default=None,
                       help="Change user after startup (must run as root)")
     parser.add_option("-v", "--verbose", dest="verbose", action="store_true",
                       help="display more about what is going on")
@@ -783,10 +818,6 @@ def main():
     if options.verbose:
         sys.stdout.write("%s\n" % VERSION)
 
-    # TODO: set process name, perhaps by:
-    #       http://code.google.com/p/procname/
-    #       http://github.com/lericson/procname/
-
     # Create wakeup pipe for signal handlers
     wakeup_pipe = os.pipe()
     signal.set_wakeup_fd(wakeup_pipe[1])
@@ -802,9 +833,9 @@ def main():
     signal.signal(signal.SIGPIPE, signal.SIG_IGN)
 
     # Go bob!
-    boss_of_bind = BoB(options.msgq_socket_file, options.auth_port,
-                       options.address, options.nocache, options.verbose,
-                       setuid, username)
+    boss_of_bind = BoB(options.msgq_socket_file, options.dns_port,
+                       options.address, options.forward, options.nocache,
+                       options.verbose, setuid, username)
     startup_result = boss_of_bind.startup()
     if startup_result:
         sys.stderr.write("[bind10] Error on startup: %s\n" % startup_result)

+ 2 - 2
src/bin/bind10/run_bind10.sh.in

@@ -20,10 +20,10 @@ export PYTHON_EXEC
 
 BIND10_PATH=@abs_top_builddir@/src/bin/bind10
 
-PATH=@abs_top_builddir@/src/bin/msgq:@abs_top_builddir@/src/bin/auth:@abs_top_builddir@/src/bin/cfgmgr:@abs_top_builddir@/src/bin/cmdctl:@abs_top_builddir@/src/bin/stats:@abs_top_builddir@/src/bin/xfrin:@abs_top_builddir@/src/bin/xfrout:@abs_top_builddir@/src/bin/zonemgr:@abs_top_builddir@/src/bin/recurse:$PATH
+PATH=@abs_top_builddir@/src/bin/msgq:@abs_top_builddir@/src/bin/auth:@abs_top_builddir@/src/bin/recurse:@abs_top_builddir@/src/bin/cfgmgr:@abs_top_builddir@/src/bin/cmdctl:@abs_top_builddir@/src/bin/stats:@abs_top_builddir@/src/bin/xfrin:@abs_top_builddir@/src/bin/xfrout:@abs_top_builddir@/src/bin/zonemgr:$PATH
 export PATH
 
-PYTHONPATH=@abs_top_builddir@/src/lib/python:@abs_top_builddir@/src/lib/dns/python/.libs:@abs_top_builddir@/src/lib/xfr/.libs
+PYTHONPATH=@abs_top_builddir@/src/lib/python:@abs_top_builddir@/src/lib/dns/python/.libs:@abs_top_builddir@/src/lib/xfr/.libs:@abs_top_builddir@/src/lib/log/.libs
 export PYTHONPATH
 
 # If necessary (rare cases), explicitly specify paths to dynamic libraries

+ 5 - 5
src/bin/bind10/tests/bind10_test.py

@@ -78,7 +78,7 @@ class TestBoB(unittest.TestCase):
         bob = BoB()
         self.assertEqual(bob.verbose, False)
         self.assertEqual(bob.msgq_socket_file, None)
-        self.assertEqual(bob.auth_port, 5300)
+        self.assertEqual(bob.dns_port, 5300)
         self.assertEqual(bob.address, None)
         self.assertEqual(bob.cc_session, None)
         self.assertEqual(bob.ccs, None)
@@ -95,8 +95,8 @@ class TestBoB(unittest.TestCase):
         bob = BoB("alt_socket_file")
         self.assertEqual(bob.verbose, False)
         self.assertEqual(bob.msgq_socket_file, "alt_socket_file")
-        self.assertEqual(bob.auth_port, 5300)
         self.assertEqual(bob.address, None)
+        self.assertEqual(bob.dns_port, 5300)
         self.assertEqual(bob.cc_session, None)
         self.assertEqual(bob.ccs, None)
         self.assertEqual(bob.processes, {})
@@ -108,11 +108,11 @@ class TestBoB(unittest.TestCase):
         self.assertEqual(bob.cfg_start_auth, True)
         self.assertEqual(bob.cfg_start_recurse, False)
 
-    def test_init_alternate_auth_port(self):
+    def test_init_alternate_dns_port(self):
         bob = BoB(None, 9999)
         self.assertEqual(bob.verbose, False)
         self.assertEqual(bob.msgq_socket_file, None)
-        self.assertEqual(bob.auth_port, 9999)
+        self.assertEqual(bob.dns_port, 9999)
         self.assertEqual(bob.address, None)
         self.assertEqual(bob.cc_session, None)
         self.assertEqual(bob.ccs, None)
@@ -129,7 +129,7 @@ class TestBoB(unittest.TestCase):
         bob = BoB(None, 1234, IPAddr('127.127.127.127'))
         self.assertEqual(bob.verbose, False)
         self.assertEqual(bob.msgq_socket_file, None)
-        self.assertEqual(bob.auth_port, 1234)
+        self.assertEqual(bob.dns_port, 1234)
         self.assertEqual(bob.address.addr, socket.inet_aton('127.127.127.127'))
         self.assertEqual(bob.cc_session, None)
         self.assertEqual(bob.ccs, None)

+ 48 - 11
src/bin/recurse/Makefile.am

@@ -1,20 +1,57 @@
-# SUBDIRS = . tests
+SUBDIRS = . tests
+
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
+AM_CPPFLAGS += -I$(top_srcdir)/src/bin -I$(top_builddir)/src/bin
+AM_CPPFLAGS += -I$(top_srcdir)/src/lib/dns -I$(top_builddir)/src/lib/dns
+AM_CPPFLAGS += -I$(top_srcdir)/src/lib/cc -I$(top_builddir)/src/lib/cc
+AM_CPPFLAGS += -I$(top_srcdir)/src/lib/asiolink
+AM_CPPFLAGS += -I$(top_builddir)/src/lib/asiolink
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+
+AM_CXXFLAGS = $(B10_CXXFLAGS)
+
+if USE_STATIC_LINK
+AM_LDFLAGS = -static
+endif
 
 pkglibexecdir = $(libexecdir)/@PACKAGE@
 
-pkglibexec_SCRIPTS = b10-recurse
+CLEANFILES = *.gcno *.gcda recurse.spec spec_config.h
 
-b10_recursedir = $(DESTDIR)$(pkgdatadir)
-b10_recurse_DATA = recurse.spec
+man_MANS = b10-recurse.8
+EXTRA_DIST = $(man_MANS) b10-recurse.xml
+
+if ENABLE_MAN
 
-CLEANFILES=	b10-recurse recurse.pyc recurse.py recurse.spec recurse.spec.pre
+b10-recurse.8: b10-recurse.xml
+	xsltproc --novalid --xinclude --nonet -o $@ http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl $(srcdir)/b10-recurse.xml
+
+endif
 
 recurse.spec: recurse.spec.pre
 	$(SED) -e "s|@@LOCALSTATEDIR@@|$(localstatedir)|" recurse.spec.pre >$@
 
-# TODO: does this need $$(DESTDIR) also?
-# this is done here since configure.ac AC_OUTPUT doesn't expand exec_prefix
-b10-recurse: recurse.py
-	$(SED) -e "s|@@PYTHONPATH@@|@pyexecdir@|" \
-	       -e "s|@@LOCALSTATEDIR@@|$(localstatedir)|" recurse.py >$@
-	chmod a+x $@
+spec_config.h: spec_config.h.pre
+	$(SED) -e "s|@@LOCALSTATEDIR@@|$(localstatedir)|" spec_config.h.pre >$@
+
+BUILT_SOURCES = spec_config.h 
+pkglibexec_PROGRAMS = b10-recurse
+b10_recurse_SOURCES = recursor.cc recursor.h
+b10_recurse_SOURCES += $(top_builddir)/src/bin/auth/change_user.h
+b10_recurse_SOURCES += $(top_builddir)/src/bin/auth/common.h
+b10_recurse_SOURCES += main.cc
+b10_recurse_LDADD =  $(top_builddir)/src/lib/dns/libdns++.la
+b10_recurse_LDADD += $(top_builddir)/src/lib/config/libcfgclient.la
+b10_recurse_LDADD += $(top_builddir)/src/lib/cc/libcc.la
+b10_recurse_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
+b10_recurse_LDADD += $(top_builddir)/src/lib/asiolink/libasiolink.la
+b10_recurse_LDADD += $(top_builddir)/src/lib/xfr/libxfr.la
+b10_recurse_LDADD += $(top_builddir)/src/lib/log/liblog.la
+b10_recurse_LDADD += $(top_builddir)/src/bin/auth/change_user.o
+b10_recurse_LDFLAGS = -pthread
+
+# TODO: config.h.in is wrong because doesn't honor pkgdatadir
+# and can't use @datadir@ because doesn't expand default ${prefix}
+b10_recursedir = $(DESTDIR)$(pkgdatadir)
+b10_recurse_DATA = recurse.spec
+

+ 0 - 7
src/bin/recurse/README_FIRST.txt

@@ -1,7 +0,0 @@
-All the files in this directory are for testing ticket #412 only.
-
-Another ticket has created the "b10-recurse" program.  When both are merged
-into the trunk, these files should be deleted.
-
-Stephen Morris
-24 November 2010

+ 145 - 0
src/bin/recurse/b10-recurse.8

@@ -0,0 +1,145 @@
+'\" t
+.\"     Title: b10-recurse
+.\"    Author: [FIXME: author] [see http://docbook.sf.net/el/author]
+.\" Generator: DocBook XSL Stylesheets v1.75.2 <http://docbook.sf.net/>
+.\"      Date: September 16, 2010
+.\"    Manual: BIND10
+.\"    Source: BIND10
+.\"  Language: English
+.\"
+.TH "B10\-RECURSE" "8" "September 16, 2010" "BIND10" "BIND10"
+.\" -----------------------------------------------------------------
+.\" * set default formatting
+.\" -----------------------------------------------------------------
+.\" disable hyphenation
+.nh
+.\" disable justification (adjust text to left margin only)
+.ad l
+.\" -----------------------------------------------------------------
+.\" * MAIN CONTENT STARTS HERE *
+.\" -----------------------------------------------------------------
+.SH "NAME"
+b10-recurse \- Recursive DNS server
+.SH "SYNOPSIS"
+.HP \w'\fBb10\-recurse\fR\ 'u
+\fBb10\-recurse\fR [\fB\-4\fR] [\fB\-6\fR] [\fB\-a\ \fR\fB\fIaddress\fR\fR] [\fB\-n\fR] [\fB\-p\ \fR\fB\fInumber\fR\fR] [\fB\-u\ \fR\fB\fIusername\fR\fR] [\fB\-v\fR]
+.SH "DESCRIPTION"
+.PP
+The
+\fBb10\-recurse\fR
+daemon provides the BIND 10 recursive DNS server\&. Normally it is started by the
+\fBbind10\fR(8)
+boss process\&.
+.PP
+This daemon communicates with other BIND 10 components over a
+\fBb10-msgq\fR(8)
+C\-Channel connection\&. If this connection is not established,
+\fBb10\-recurse\fR
+will exit\&.
+.PP
+It also receives its configurations from
+\fBb10-cfgmgr\fR(8)\&. Currently no configuration commands are defined\&.
+.if n \{\
+.sp
+.\}
+.RS 4
+.it 1 an-trap
+.nr an-no-space-flag 1
+.nr an-break-flag 1
+.br
+.ps +1
+\fBNote\fR
+.ps -1
+.br
+.PP
+This prototype version only supports forwarding\&. Future versions will introduce full recursion, cache, lookup of local authoritative data (as in
+\fBb10\-auth\fR), and DNSSEC validation\&.
+.sp .5v
+.RE
+.SH "OPTIONS"
+.PP
+The arguments are as follows:
+.PP
+\fB\-4\fR
+.RS 4
+Enables IPv4 only mode\&. This switch may not be used with
+\fB\-6\fR
+nor
+\fB\-a\fR\&. By default, it listens on both IPv4 and IPv6 (if capable)\&.
+.RE
+.PP
+\fB\-6\fR
+.RS 4
+Enables IPv6 only mode\&. This switch may not be used with
+\fB\-4\fR
+nor
+\fB\-a\fR\&. By default, it listens on both IPv4 and IPv6 (if capable)\&.
+.RE
+.PP
+\fB\-a \fR\fB\fIaddress\fR\fR
+.RS 4
+The IPv4 or IPv6 address to listen on\&. This switch may not be used with
+\fB\-4\fR
+nor
+\fB\-6\fR\&. The default is to listen on all addresses\&. (This is a short term workaround\&. This argument may change\&.)
+.RE
+.PP
+\fB\-n\fR
+.RS 4
+Do not cache answers in memory\&. The default is to use the cache for faster responses\&. The cache keeps the most recent 30,000 answers (positive and negative) in memory for 30 seconds (instead of querying the data source, such as SQLite3 database, each time)\&.
+.RE
+.PP
+\fB\-p \fR\fB\fInumber\fR\fR
+.RS 4
+The port number it listens on\&. The default is 5300\&.
+.if n \{\
+.sp
+.\}
+.RS 4
+.it 1 an-trap
+.nr an-no-space-flag 1
+.nr an-break-flag 1
+.br
+.ps +1
+\fBNote\fR
+.ps -1
+.br
+The Y1 prototype runs on all interfaces and on this nonstandard port\&.
+.sp .5v
+.RE
+.RE
+.PP
+\fB\-u \fR\fB\fIusername\fR\fR
+.RS 4
+The user name of the
+\fBb10\-recurse\fR
+daemon\&. If specified, the daemon changes the process owner to the specified user\&. The
+\fIusername\fR
+must be either a valid numeric user ID or a valid user name\&. By default the daemon runs as the user who invokes it\&.
+.RE
+.PP
+\fB\-v\fR
+.RS 4
+Enabled verbose mode\&. This enables diagnostic messages to STDERR\&.
+.RE
+.SH "FILES"
+.PP
+None\&.
+.SH "SEE ALSO"
+.PP
+
+\fBb10-cfgmgr\fR(8),
+\fBb10-cmdctl\fR(8),
+\fBb10-loadzone\fR(8),
+\fBb10-msgq\fR(8),
+\fBbind10\fR(8),
+BIND 10 Guide\&.
+.SH "HISTORY"
+.PP
+The
+\fBb10\-recurse\fR
+daemon was first coded in September 2010\&.
+.SH "COPYRIGHT"
+.br
+Copyright \(co 2010 Internet Systems Consortium, Inc. ("ISC")
+.br

+ 213 - 0
src/bin/recurse/b10-recurse.xml

@@ -0,0 +1,213 @@
+<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+               "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd"
+	       [<!ENTITY mdash "&#8212;">]>
+<!--
+ - 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$ -->
+<refentry>
+
+  <refentryinfo>
+    <date>September 16, 2010</date>
+  </refentryinfo>
+
+  <refmeta>
+    <refentrytitle>b10-recurse</refentrytitle>
+    <manvolnum>8</manvolnum>
+    <refmiscinfo>BIND10</refmiscinfo>
+  </refmeta>
+
+  <refnamediv>
+    <refname>b10-recurse</refname>
+    <refpurpose>Recursive DNS server</refpurpose>
+  </refnamediv>
+
+  <docinfo>
+    <copyright>
+      <year>2010</year>
+      <holder>Internet Systems Consortium, Inc. ("ISC")</holder>
+    </copyright>
+  </docinfo>
+
+  <refsynopsisdiv>
+    <cmdsynopsis>
+      <command>b10-recurse</command>
+      <arg><option>-4</option></arg>
+      <arg><option>-6</option></arg>
+      <arg><option>-a <replaceable>address</replaceable></option></arg>
+      <arg><option>-n</option></arg>
+      <arg><option>-p <replaceable>number</replaceable></option></arg>
+      <arg><option>-u <replaceable>username</replaceable></option></arg>
+      <arg><option>-v</option></arg>
+    </cmdsynopsis>
+  </refsynopsisdiv>
+
+  <refsect1>
+    <title>DESCRIPTION</title>
+    <para>The <command>b10-recurse</command> daemon provides the BIND 10
+      recursive DNS server.  Normally it is started by the
+      <citerefentry><refentrytitle>bind10</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+      boss process.
+    </para>
+
+    <para>
+      This daemon communicates with other BIND 10 components over a
+      <citerefentry><refentrytitle>b10-msgq</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+      C-Channel connection.  If this connection is not established,
+      <command>b10-recurse</command> will exit.
+    </para>
+
+    <para>
+      It also receives its configurations from
+<citerefentry><refentrytitle>b10-cfgmgr</refentrytitle><manvolnum>8</manvolnum></citerefentry>.
+      Currently no configuration commands are defined.
+    </para>
+
+    <note><para>
+      This prototype version only supports forwarding.  Future versions
+      will introduce full recursion, cache, lookup of local authoritative
+      data (as in <command>b10-auth</command>), and DNSSEC validation.
+    </para></note>
+  </refsect1>
+
+  <refsect1>
+    <title>OPTIONS</title>
+
+    <para>The arguments are as follows:</para>
+
+    <variablelist>
+      <varlistentry>
+        <term><option>-4</option></term>
+        <listitem><para>
+          Enables IPv4 only mode.
+          This switch may not be used with <option>-6</option> nor
+          <option>-a</option>.
+          By default, it listens on both IPv4 and IPv6 (if capable).
+        </para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>-6</option></term>
+        <listitem><para>
+          Enables IPv6 only mode.
+          This switch may not be used with <option>-4</option> nor
+          <option>-a</option>.
+          By default, it listens on both IPv4 and IPv6 (if capable).
+        </para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>-a <replaceable>address</replaceable></option></term>
+
+        <listitem>
+          <para>The IPv4 or IPv6 address to listen on.
+            This switch may not be used with <option>-4</option> nor
+            <option>-6</option>.
+            The default is to listen on all addresses.
+            (This is a short term workaround. This argument may change.)   
+          </para>                      
+         </listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>-n</option></term>
+        <listitem><para>
+          Do not cache answers in memory.
+          The default is to use the cache for faster responses.
+	  The cache keeps the most recent 30,000 answers (positive
+	  and negative) in memory for 30 seconds (instead of querying
+	  the data source, such as SQLite3 database, each time).
+        </para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>-p <replaceable>number</replaceable></option></term>
+        <listitem><para>
+          The port number it listens on.
+          The default is 5300.</para>
+	  <note><simpara>The Y1 prototype runs on all interfaces
+	  and on this nonstandard port.</simpara></note>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>-u <replaceable>username</replaceable></option></term>
+        <listitem>
+	  <para>
+	    The user name of the <command>b10-recurse</command> daemon.
+	    If specified, the daemon changes the process owner to the
+	    specified user.
+	    The <replaceable>username</replaceable> must be either a
+	    valid numeric user ID or a valid user name.
+	    By default the daemon runs as the user who invokes it.
+	  </para>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>-v</option></term>
+        <listitem><para>
+          Enabled verbose mode. This enables diagnostic messages to
+          STDERR.
+        </para></listitem>
+      </varlistentry>
+
+    </variablelist>
+
+  </refsect1>
+
+  <refsect1>
+    <title>FILES</title>
+    <para>
+      None.
+    </para>
+<!-- TODO: this is not correct yet. -->
+  </refsect1>
+
+  <refsect1>
+    <title>SEE ALSO</title>
+    <para>
+      <citerefentry>
+        <refentrytitle>b10-cfgmgr</refentrytitle><manvolnum>8</manvolnum>
+      </citerefentry>,
+      <citerefentry>
+        <refentrytitle>b10-cmdctl</refentrytitle><manvolnum>8</manvolnum>
+      </citerefentry>,
+      <citerefentry>
+        <refentrytitle>b10-loadzone</refentrytitle><manvolnum>8</manvolnum>
+      </citerefentry>,
+      <citerefentry>
+        <refentrytitle>b10-msgq</refentrytitle><manvolnum>8</manvolnum>
+      </citerefentry>,
+      <citerefentry>
+        <refentrytitle>bind10</refentrytitle><manvolnum>8</manvolnum>
+      </citerefentry>,
+      <citetitle>BIND 10 Guide</citetitle>.
+    </para>
+  </refsect1>
+
+  <refsect1>
+    <title>HISTORY</title>
+    <para>
+      The <command>b10-recurse</command> daemon was first coded in
+      September 2010.
+    </para>
+  </refsect1>
+</refentry><!--
+ - Local variables:
+ - mode: sgml
+ - End:
+-->

+ 181 - 0
src/bin/recurse/main.cc

@@ -0,0 +1,181 @@
+// Copyright (C) 2009  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 <sys/types.h>
+#include <sys/socket.h>
+#include <sys/select.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <stdlib.h>
+#include <errno.h>
+
+#include <string>
+#include <iostream>
+
+#include <boost/foreach.hpp>
+
+#include <asiolink/asiolink.h>
+
+#include <exceptions/exceptions.h>
+
+#include <dns/buffer.h>
+#include <dns/message.h>
+#include <dns/messagerenderer.h>
+
+#include <cc/session.h>
+#include <cc/data.h>
+#include <config/ccsession.h>
+
+#include <xfr/xfrout_client.h>
+
+#include <auth/change_user.h>
+#include <auth/common.h>
+
+#include <recurse/spec_config.h>
+#include <recurse/recursor.h>
+
+#include <log/dummylog.h>
+
+using namespace std;
+using namespace isc::cc;
+using namespace isc::config;
+using namespace isc::data;
+using isc::log::dlog;
+using namespace asiolink;
+
+namespace {
+
+// Default port current 5300 for testing purposes
+static const string PROGRAM = "Recurse";
+
+IOService io_service;
+static Recursor *recursor;
+
+ConstElementPtr
+my_config_handler(ConstElementPtr new_config) {
+    return (recursor->updateConfig(new_config));
+}
+
+ConstElementPtr
+my_command_handler(const string& command, ConstElementPtr args) {
+    ConstElementPtr answer = createAnswer();
+
+    if (command == "print_message") {
+        cout << args << endl;
+        /* let's add that message to our answer as well */
+        answer = createAnswer(0, args);
+    } else if (command == "shutdown") {
+        io_service.stop();
+    }
+
+    return (answer);
+}
+
+void
+usage() {
+    cerr << "Usage:  b10-recurse [-u user] [-v]" << endl;
+    cerr << "\t-u: change process UID to the specified user" << endl;
+    cerr << "\t-v: verbose output" << endl;
+    exit(1);
+}
+} // end of anonymous namespace
+
+int
+main(int argc, char* argv[]) {
+    isc::log::dprefix = "b10-recurse";
+    int ch;
+    const char* uid = NULL;
+
+    while ((ch = getopt(argc, argv, "u:v")) != -1) {
+        switch (ch) {
+        case 'u':
+            uid = optarg;
+            break;
+        case 'v':
+            isc::log::denabled = true;
+            break;
+        case '?':
+        default:
+            usage();
+        }
+    }
+
+    if (argc - optind > 0) {
+        usage();
+    }
+
+    if (isc::log::denabled) { // Show the command line
+        string cmdline("Command line:");
+        for (int i = 0; i < argc; ++ i) {
+            cmdline = cmdline + " " + argv[i];
+        }
+        dlog(cmdline);
+    }
+
+    int ret = 0;
+
+    Session* cc_session = NULL;
+    ModuleCCSession* config_session = NULL;
+    try {
+        string specfile;
+        if (getenv("B10_FROM_BUILD")) {
+            specfile = string(getenv("B10_FROM_BUILD")) +
+                "/src/bin/recurse/recurse.spec";
+        } else {
+            specfile = string(RECURSE_SPECFILE_LOCATION);
+        }
+
+        recursor = new Recursor();
+        dlog("Server created.");
+
+        SimpleCallback* checkin = recursor->getCheckinProvider();
+        DNSLookup* lookup = recursor->getDNSLookupProvider();
+        DNSAnswer* answer = recursor->getDNSAnswerProvider();
+
+        DNSService dns_service(io_service, checkin, lookup, answer);
+
+        recursor->setDNSService(dns_service);
+        dlog("IOService created.");
+
+        cc_session = new Session(io_service.get_io_service());
+        dlog("Configuration session channel created.");
+
+        config_session = new ModuleCCSession(specfile, *cc_session,
+                                             my_config_handler,
+                                             my_command_handler);
+        dlog("Configuration channel established.");
+
+        // FIXME: This does not belong here, but inside Boss
+        if (uid != NULL) {
+            changeUser(uid);
+        }
+
+        recursor->setConfigSession(config_session);
+        dlog("Config loaded");
+
+        dlog("Server started.");
+        io_service.run();
+    } catch (const std::exception& ex) {
+        dlog(string("Server failed: ") + ex.what());
+        ret = 1;
+    }
+
+    delete config_session;
+    delete cc_session;
+    delete recursor;
+
+    return (ret);
+}

+ 0 - 177
src/bin/recurse/recurse.py.in

@@ -1,177 +0,0 @@
-#!@PYTHON@
-
-# Copyright (C) 2010  Internet Systems Consortium.
-# Copyright (C) 2010  CZ NIC
-#
-# Permission to use, copy, modify, and 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 INTERNET SYSTEMS CONSORTIUM
-# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
-# INTERNET SYSTEMS CONSORTIUM 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.
-
-"""
-   This is a dummy recursor module, purely for testing that the changes to
-   the Boss regarding the starting of recurse/auth works.  It should be deleted
-   when the real recursor module is made available.
-"""
-
-import sys; sys.path.append ('@@PYTHONPATH@@')
-import isc
-import isc.cc
-import threading
-import struct
-import signal
-from isc.datasrc import sqlite3_ds
-from socketserver import *
-import os
-from isc.config.ccsession import *
-from isc.log.log import *
-from isc.cc import SessionError, SessionTimeout
-from isc.notify import notify_out
-import isc.util.process
-import socket
-import select
-import errno
-from optparse import OptionParser, OptionValueError
-from isc.util import socketserver_mixin
-
-try:
-    from libxfr_python import *
-    from pydnspp import *
-except ImportError as e:
-    # C++ loadable module may not be installed; even so the recurse process
-    # must keep running, so we warn about it and move forward.
-    sys.stderr.write('[b10-recurse] failed to import DNS or XFR module: %s\n' % str(e))
-
-isc.util.process.rename()
-
-if "B10_FROM_BUILD" in os.environ:
-    SPECFILE_PATH = os.environ["B10_FROM_BUILD"] + "/src/bin/recurse"
-    AUTH_SPECFILE_PATH = os.environ["B10_FROM_BUILD"] + "/src/bin/auth"
-    UNIX_SOCKET_FILE= os.environ["B10_FROM_BUILD"] + "/auth_recurse_conn"
-else:
-    PREFIX = "@prefix@"
-    DATAROOTDIR = "@datarootdir@"
-    SPECFILE_PATH = "@datadir@/@PACKAGE@".replace("${datarootdir}", DATAROOTDIR).replace("${prefix}", PREFIX)
-    AUTH_SPECFILE_PATH = SPECFILE_PATH
-    UNIX_SOCKET_FILE = "@@LOCALSTATEDIR@@/auth_recurse_conn"
-
-SPECFILE_LOCATION = SPECFILE_PATH + "/recurse.spec"
-AUTH_SPECFILE_LOCATION = AUTH_SPECFILE_PATH + os.sep + "auth.spec"
-MAX_TRANSFERS_OUT = 10
-VERBOSE_MODE = False
-
-
-RESOLVER_MAX_MESSAGE_SIZE = 65535
-
-class ResolverServer:
-    def __init__(self):
-        self._unix_socket_server = None
-        self._log = None
-        self._listen_sock_file = UNIX_SOCKET_FILE
-        self._shutdown_event = threading.Event()
-        self._cc = isc.config.ModuleCCSession(SPECFILE_LOCATION, self.config_handler, self.command_handler)
-        self._config_data = self._cc.get_full_config()
-        self._cc.start()
-        self._cc.add_remote_config(AUTH_SPECFILE_LOCATION);
-
-    def config_handler(self, new_config):
-        '''Update config data. TODO. Do error check'''
-        answer = create_answer(0)
-        for key in new_config:
-            if key not in self._config_data:
-                answer = create_answer(1, "Unknown config data: " + str(key))
-                continue
-            self._config_data[key] = new_config[key]
-
-        if self._log:
-            self._log.update_config(new_config)
-
-        if self._unix_socket_server:
-            self._unix_socket_server.update_config_data(self._config_data)
-
-        return answer
-
-
-    def shutdown(self):
-        '''
-            shutdown the recurse process.
-        '''
-
-        global recurse_server
-        recurse_server = None #Avoid shutdown is called twice
-        self._shutdown_event.set()
-        if self._unix_socket_server:
-            self._unix_socket_server.shutdown()
-        sys.exit(0)
-
-    def command_handler(self, cmd, args):
-        if cmd == "shutdown":
-            self._log.log_message("info", "Received shutdown command.")
-            self.shutdown()
-            answer = create_answer(0)
-        else:
-            answer = create_answer(1, "Unknown command:" + str(cmd))
-
-        return answer
-
-    def run(self):
-        '''Get and process all commands sent from cfgmgr or other modules. '''
-        while not self._shutdown_event.is_set():
-            self._cc.check_command(False)
-
-
-recurse_server = None
-
-def signal_handler(signal, frame):
-    if recurse_server:
-        recurse_server.shutdown()
-        sys.exit(0)
-
-def set_signal_handler():
-    signal.signal(signal.SIGTERM, signal_handler)
-    signal.signal(signal.SIGINT, signal_handler)
-
-def set_cmd_options(parser):
-    parser.add_option("-a", "--address", dest="address", type="string",
-            default="127.0.0.1", help="Address on which recursor listens")
-    parser.add_option("-n", "--nocache", dest="nocache", action="store_true",
-            help="Specify to disable the cache")
-    parser.add_option("-p", "--port", dest="port", type="string",
-            default="10", help="UID under which recursor runs")
-    parser.add_option("-u", "--uid", dest="uid", type="string",
-            default="5301", help="Port on which recursor listens")
-    parser.add_option("-v", "--verbose", dest="verbose", action="store_true",
-            help="display more about what is going on")
-
-if '__main__' == __name__:
-    try:
-        parser = OptionParser()
-        set_cmd_options(parser)
-        (options, args) = parser.parse_args()
-        VERBOSE_MODE = options.verbose
-
-        set_signal_handler()
-        recurse_server = ResolverServer()
-        recurse_server.run()
-    except KeyboardInterrupt:
-        sys.stderr.write("[b10-recurse] exit recurse process\n")
-    except SessionError as e:
-        sys.stderr.write("[b10-recurse] Error creating recurse, "
-                           "is the command channel daemon running?\n")
-    except SessionTimeout as e:
-        sys.stderr.write("[b10-recurse] Error creating recurse, "
-                           "is the configuration manager running?\n")
-    except ModuleCCSessionError as e:
-        sys.stderr.write("[b10-recurse] exit recurse process:%s\n" % str(e))
-
-    if recurse_server:
-        recurse_server.shutdown()
-

+ 84 - 10
src/bin/recurse/recurse.spec.pre.in

@@ -1,15 +1,89 @@
 {
   "module_spec": {
-     "module_name": "Recurse",
-     "config_data": [
-      ],
-      "commands": [
-        {
-          "command_name": "shutdown",
-          "command_description": "Shut down Resolver",
-          "command_args": []
+    "module_name": "Recurse",
+    "module_description": "Recursive service",
+    "config_data": [
+      {
+        "item_name": "timeout",
+        "item_type": "integer",
+        "item_optional": False,
+        "item_default": 2000
+      },
+      {
+        "item_name": "retries",
+        "item_type": "integer",
+        "item_optional": False,
+        "item_default": 0
+      },
+      {
+        "item_name": "forward_addresses",
+        "item_type": "list",
+        "item_optional": True,
+        "item_default": [],
+        "list_item_spec" : {
+          "item_name": "address",
+          "item_type": "map",
+          "item_optional": False,
+          "item_default": {},
+          "map_item_spec": [
+            {
+              "item_name": "address",
+              "item_type": "string",
+              "item_optional": False,
+              "item_default": "::1"
+            },
+            {
+              "item_name": "port",
+              "item_type": "integer",
+              "item_optional": False,
+              "item_default": 53
+            }
+          ]
         }
-      ]
+      },
+      {
+        "item_name": "listen_on",
+        "item_type": "list",
+        "item_optional": False,
+        "item_default": [
+          {
+            "address": "::1",
+            "port": 5300
+          },
+          {
+            "address": "127.0.0.1",
+            "port": 5300
+          },
+        ],
+        "list_item_spec": {
+          "item_name": "address",
+          "item_type": "map",
+          "item_optional": False,
+          "item_default": {},
+          "map_item_spec": [
+            {
+              "item_name": "address",
+              "item_type": "string",
+              "item_optional": False,
+              "item_default": "::1"
+            },
+            {
+              "item_name": "port",
+              "item_type": "integer",
+              "item_optional": False,
+              "item_default": 5300
+            }
+          ]
+        }
+      }
+    ],
+    "commands": [
+      {
+        "command_name": "shutdown",
+        "command_description": "Shut down recursive DNS server",
+        "command_args": []
+      }
+    ]
   }
 }
-     
+

+ 610 - 0
src/bin/recurse/recursor.cc

@@ -0,0 +1,610 @@
+// Copyright (C) 2009  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 <config.h>
+
+#include <netinet/in.h>
+
+#include <algorithm>
+#include <vector>
+#include <cassert>
+
+#include <asiolink/asiolink.h>
+#include <asiolink/ioaddress.h>
+
+#include <boost/foreach.hpp>
+#include <boost/lexical_cast.hpp>
+
+#include <config/ccsession.h>
+
+#include <exceptions/exceptions.h>
+
+#include <dns/opcode.h>
+#include <dns/rcode.h>
+#include <dns/buffer.h>
+#include <dns/exceptions.h>
+#include <dns/name.h>
+#include <dns/question.h>
+#include <dns/rrset.h>
+#include <dns/rrttl.h>
+#include <dns/message.h>
+#include <dns/messagerenderer.h>
+
+#include <log/dummylog.h>
+
+#include <recurse/recursor.h>
+
+using namespace std;
+
+using namespace isc;
+using namespace isc::dns;
+using namespace isc::data;
+using namespace isc::config;
+using isc::log::dlog;
+using namespace asiolink;
+
+typedef pair<string, uint16_t> addr_t;
+
+class RecursorImpl {
+private:
+    // prohibit copy
+    RecursorImpl(const RecursorImpl& source);
+    RecursorImpl& operator=(const RecursorImpl& source);
+public:
+    RecursorImpl() :
+        config_session_(NULL),
+        rec_query_(NULL)
+    {}
+
+    ~RecursorImpl() {
+        queryShutdown();
+    }
+
+    void querySetup(DNSService& dnss) {
+        assert(!rec_query_); // queryShutdown must be called first
+        dlog("Query setup");
+        rec_query_ = new RecursiveQuery(dnss, upstream_);
+    }
+
+    void queryShutdown() {
+        dlog("Query shutdown");
+        delete rec_query_;
+        rec_query_ = NULL;
+    }
+
+    void setForwardAddresses(const vector<addr_t>& upstream,
+        DNSService *dnss)
+    {
+        queryShutdown();
+        upstream_ = upstream;
+        if (dnss) {
+            if (upstream_.empty()) {
+                dlog("Asked to do full recursive, but not implemented yet. "
+                    "I'll do nothing.");
+            } else {
+                dlog("Setting forward addresses:");
+                BOOST_FOREACH(const addr_t& address, upstream) {
+                    dlog(" " + address.first + ":" +
+                        boost::lexical_cast<string>(address.second));
+                }
+                querySetup(*dnss);
+            }
+        }
+    }
+
+    void processNormalQuery(const Question& question, MessagePtr message,
+                            OutputBufferPtr buffer,
+                            DNSServer* server);
+
+    /// Currently non-configurable, but will be.
+    static const uint16_t DEFAULT_LOCAL_UDPSIZE = 4096;
+
+    /// These members are public because Recursor accesses them directly.
+    ModuleCCSession* config_session_;
+    /// Addresses of the forward nameserver
+    vector<addr_t> upstream_;
+    /// Addresses we listen on
+    vector<addr_t> listen_;
+
+    /// Time in milliseconds, to timeout
+    int timeout_;
+    /// Number of retries after timeout
+    unsigned retries_;
+
+private:
+
+    /// Object to handle upstream queries
+    RecursiveQuery* rec_query_;
+};
+
+/*
+ * std::for_each has a broken interface. It makes no sense in a language
+ * without lambda functions/closures. These two classes emulate the lambda
+ * functions so for_each can be used.
+ */
+class QuestionInserter {
+public:
+    QuestionInserter(MessagePtr message) : message_(message) {}
+    void operator()(const QuestionPtr question) {
+        dlog(string("Adding question ") + question->getName().toText() +
+            " to message");
+        message_->addQuestion(question);
+    }
+    MessagePtr message_;
+};
+
+class SectionInserter {
+public:
+    SectionInserter(MessagePtr message, const Message::Section sect) :
+        message_(message), section_(sect)
+    {}
+    void operator()(const RRsetPtr rrset) {
+        //dlog("Adding RRSet to message section " +
+        //    boost::lexical_cast<string>(section_));
+        message_->addRRset(section_, rrset, true);
+    }
+    MessagePtr message_;
+    const Message::Section section_;
+};
+
+void
+makeErrorMessage(MessagePtr message, OutputBufferPtr buffer,
+                 const Rcode& rcode)
+{
+    // extract the parameters that should be kept.
+    // XXX: with the current implementation, it's not easy to set EDNS0
+    // depending on whether the query had it.  So we'll simply omit it.
+    const qid_t qid = message->getQid();
+    const bool rd = message->getHeaderFlag(Message::HEADERFLAG_RD);
+    const bool cd = message->getHeaderFlag(Message::HEADERFLAG_CD);
+    const Opcode& opcode = message->getOpcode();
+    vector<QuestionPtr> questions;
+
+    // If this is an error to a query or notify, we should also copy the
+    // question section.
+    if (opcode == Opcode::QUERY() || opcode == Opcode::NOTIFY()) {
+        questions.assign(message->beginQuestion(), message->endQuestion());
+    }
+
+    message->clear(Message::RENDER);
+    message->setQid(qid);
+    message->setOpcode(opcode);
+    message->setHeaderFlag(Message::HEADERFLAG_QR);
+    if (rd) {
+        message->setHeaderFlag(Message::HEADERFLAG_RD);
+    }
+    if (cd) {
+        message->setHeaderFlag(Message::HEADERFLAG_CD);
+    }
+    for_each(questions.begin(), questions.end(), QuestionInserter(message));
+    message->setRcode(rcode);
+    MessageRenderer renderer(*buffer);
+    message->toWire(renderer);
+
+    dlog(string("Sending an error response (") +
+        boost::lexical_cast<string>(renderer.getLength()) + " bytes):\n" +
+        message->toText());
+}
+
+// This is a derived class of \c DNSLookup, to serve as a
+// callback in the asiolink module.  It calls
+// Recursor::processMessage() on a single DNS message.
+class MessageLookup : public DNSLookup {
+public:
+    MessageLookup(Recursor* srv) : server_(srv) {}
+
+    // \brief Handle the DNS Lookup
+    virtual void operator()(const IOMessage& io_message, MessagePtr message,
+                            OutputBufferPtr buffer, DNSServer* server) const
+    {
+        server_->processMessage(io_message, message, buffer, server);
+    }
+private:
+    Recursor* server_;
+};
+
+// This is a derived class of \c DNSAnswer, to serve as a
+// callback in the asiolink module.  It takes a completed
+// set of answer data from the DNS lookup and assembles it
+// into a wire-format response.
+class MessageAnswer : public DNSAnswer {
+public:
+    virtual void operator()(const IOMessage& io_message,
+                            MessagePtr message,
+                            OutputBufferPtr buffer) const
+    {
+        const qid_t qid = message->getQid();
+        const bool rd = message->getHeaderFlag(Message::HEADERFLAG_RD);
+        const bool cd = message->getHeaderFlag(Message::HEADERFLAG_CD);
+        const Opcode& opcode = message->getOpcode();
+        const Rcode& rcode = message->getRcode();
+        vector<QuestionPtr> questions;
+        questions.assign(message->beginQuestion(), message->endQuestion());
+
+        message->clear(Message::RENDER);
+        message->setQid(qid);
+        message->setOpcode(opcode);
+        message->setRcode(rcode);
+
+        message->setHeaderFlag(Message::HEADERFLAG_QR);
+        message->setHeaderFlag(Message::HEADERFLAG_RA);
+        if (rd) {
+            message->setHeaderFlag(Message::HEADERFLAG_RD);
+        }
+        if (cd) {
+            message->setHeaderFlag(Message::HEADERFLAG_CD);
+        }
+
+
+        // Copy the question section.
+        for_each(questions.begin(), questions.end(), QuestionInserter(message));
+
+        // If the buffer already has an answer in it, copy RRsets from
+        // that into the new message, then clear the buffer and render
+        // the new message into it.
+        if (buffer->getLength() != 0) {
+            try {
+                Message incoming(Message::PARSE);
+                InputBuffer ibuf(buffer->getData(), buffer->getLength());
+                incoming.fromWire(ibuf);
+                for_each(incoming.beginSection(Message::SECTION_ANSWER),
+                         incoming.endSection(Message::SECTION_ANSWER),
+                         SectionInserter(message, Message::SECTION_ANSWER));
+                for_each(incoming.beginSection(Message::SECTION_AUTHORITY),
+                         incoming.endSection(Message::SECTION_AUTHORITY),
+                         SectionInserter(message, Message::SECTION_AUTHORITY));
+                for_each(incoming.beginSection(Message::SECTION_ADDITIONAL),
+                         incoming.endSection(Message::SECTION_ADDITIONAL),
+                         SectionInserter(message, Message::SECTION_ADDITIONAL));
+            } catch (const Exception& ex) {
+                // Incoming message couldn't be read, we just SERVFAIL
+                message->setRcode(Rcode::SERVFAIL());
+            }
+        }
+
+        // Now we can clear the buffer and render the new message into it
+        buffer->clear();
+        MessageRenderer renderer(*buffer);
+
+        if (io_message.getSocket().getProtocol() == IPPROTO_UDP) {
+            ConstEDNSPtr edns(message->getEDNS());
+            renderer.setLengthLimit(edns ? edns->getUDPSize() :
+                Message::DEFAULT_MAX_UDPSIZE);
+        } else {
+            renderer.setLengthLimit(65535);
+        }
+
+        message->toWire(renderer);
+
+        dlog(string("sending a response (") +
+            boost::lexical_cast<string>(renderer.getLength()) + "bytes): \n" +
+            message->toText());
+    }
+};
+
+// This is a derived class of \c SimpleCallback, to serve
+// as a callback in the asiolink module.  It checks for queued
+// configuration messages, and executes them if found.
+class ConfigCheck : public SimpleCallback {
+public:
+    ConfigCheck(Recursor* srv) : server_(srv) {}
+    virtual void operator()(const IOMessage&) const {
+        if (server_->getConfigSession()->hasQueuedMsgs()) {
+            server_->getConfigSession()->checkCommand();
+        }
+    }
+private:
+    Recursor* server_;
+};
+
+Recursor::Recursor() :
+    impl_(new RecursorImpl()),
+    checkin_(new ConfigCheck(this)),
+    dns_lookup_(new MessageLookup(this)),
+    dns_answer_(new MessageAnswer)
+{}
+
+Recursor::~Recursor() {
+    delete impl_;
+    delete checkin_;
+    delete dns_lookup_;
+    delete dns_answer_;
+    dlog("Deleting the Recursor");
+}
+
+void
+Recursor::setDNSService(asiolink::DNSService& dnss) {
+    impl_->queryShutdown();
+    impl_->querySetup(dnss);
+    dnss_ = &dnss;
+}
+
+void
+Recursor::setConfigSession(ModuleCCSession* config_session) {
+    impl_->config_session_ = config_session;
+}
+
+ModuleCCSession*
+Recursor::getConfigSession() const {
+    return (impl_->config_session_);
+}
+
+void
+Recursor::processMessage(const IOMessage& io_message, MessagePtr message,
+                        OutputBufferPtr buffer, DNSServer* server)
+{
+    dlog("Got a DNS message");
+    InputBuffer request_buffer(io_message.getData(), io_message.getDataSize());
+    // First, check the header part.  If we fail even for the base header,
+    // just drop the message.
+    try {
+        message->parseHeader(request_buffer);
+
+        // Ignore all responses.
+        if (message->getHeaderFlag(Message::HEADERFLAG_QR)) {
+            dlog("Received unexpected response, ignoring");
+            server->resume(false);
+            return;
+        }
+    } catch (const Exception& ex) {
+        dlog(string("DNS packet exception: ") + ex.what());
+        server->resume(false);
+        return;
+    }
+
+    // Parse the message.  On failure, return an appropriate error.
+    try {
+        message->fromWire(request_buffer);
+    } catch (const DNSProtocolError& error) {
+        dlog(string("returning ") + error.getRcode().toText() + ": " + 
+            error.what());
+        makeErrorMessage(message, buffer, error.getRcode());
+        server->resume(true);
+        return;
+    } catch (const Exception& ex) {
+        dlog(string("returning SERVFAIL: ") + ex.what());
+        makeErrorMessage(message, buffer, Rcode::SERVFAIL());
+        server->resume(true);
+        return;
+    } // other exceptions will be handled at a higher layer.
+
+    dlog("received a message:\n" + message->toText());
+
+    // Perform further protocol-level validation.
+    bool sendAnswer = true;
+    if (message->getOpcode() == Opcode::NOTIFY()) {
+        makeErrorMessage(message, buffer, Rcode::NOTAUTH());
+        dlog("Notify arrived, but we are not authoritative");
+    } else if (message->getOpcode() != Opcode::QUERY()) {
+        dlog("Unsupported opcode (got: " + message->getOpcode().toText() +
+            ", expected: " + Opcode::QUERY().toText());
+        makeErrorMessage(message, buffer, Rcode::NOTIMP());
+    } else if (message->getRRCount(Message::SECTION_QUESTION) != 1) {
+        dlog("The query contained " +
+            boost::lexical_cast<string>(message->getRRCount(
+            Message::SECTION_QUESTION) + " questions, exactly one expected"));
+        makeErrorMessage(message, buffer, Rcode::FORMERR());
+    } else {
+        ConstQuestionPtr question = *message->beginQuestion();
+        const RRType &qtype = question->getType();
+        if (qtype == RRType::AXFR()) {
+            if (io_message.getSocket().getProtocol() == IPPROTO_UDP) {
+                makeErrorMessage(message, buffer, Rcode::FORMERR());
+            } else {
+                makeErrorMessage(message, buffer, Rcode::NOTIMP());
+            }
+        } else if (qtype == RRType::IXFR()) {
+            makeErrorMessage(message, buffer, Rcode::NOTIMP());
+        } else {
+            // The RecursiveQuery object will post the "resume" event to the
+            // DNSServer when an answer arrives, so we don't have to do it now.
+            sendAnswer = false;
+            impl_->processNormalQuery(*question, message, buffer, server);
+        }
+    }
+
+    if (sendAnswer) {
+        server->resume(true);
+    }
+}
+
+void
+RecursorImpl::processNormalQuery(const Question& question, MessagePtr message,
+                                 OutputBufferPtr buffer, DNSServer* server)
+{
+    dlog("Processing normal query");
+    ConstEDNSPtr edns(message->getEDNS());
+    const bool dnssec_ok = edns && edns->getDNSSECAwareness();
+
+    message->makeResponse();
+    message->setHeaderFlag(Message::HEADERFLAG_RA);
+    message->setRcode(Rcode::NOERROR());
+    if (edns) {
+        EDNSPtr edns_response(new EDNS());
+        edns_response->setDNSSECAwareness(dnssec_ok);
+        edns_response->setUDPSize(RecursorImpl::DEFAULT_LOCAL_UDPSIZE);
+        message->setEDNS(edns_response);
+    }
+    rec_query_->sendQuery(question, buffer, server);
+}
+
+namespace {
+
+vector<addr_t>
+parseAddresses(ConstElementPtr addresses) {
+    vector<addr_t> result;
+    if (addresses) {
+        if (addresses->getType() == Element::list) {
+            for (size_t i(0); i < addresses->size(); ++ i) {
+                ConstElementPtr addrPair(addresses->get(i));
+                ConstElementPtr addr(addrPair->get("address"));
+                ConstElementPtr port(addrPair->get("port"));
+                if (!addr || ! port) {
+                    isc_throw(BadValue, "Address must contain both the IP"
+                        "address and port");
+                }
+                try {
+                    IOAddress(addr->stringValue());
+                    if (port->intValue() < 0 ||
+                        port->intValue() > 0xffff) {
+                        isc_throw(BadValue, "Bad port value (" <<
+                            port->intValue() << ")");
+                    }
+                    result.push_back(addr_t(addr->stringValue(),
+                        port->intValue()));
+                }
+                catch (const TypeError &e) { // Better error message
+                    isc_throw(TypeError,
+                        "Address must be a string and port an integer");
+                }
+            }
+        } else if (addresses->getType() != Element::null) {
+            isc_throw(TypeError,
+                "forward_addresses config element must be a list");
+        }
+    }
+    return (result);
+}
+
+}
+
+ConstElementPtr
+Recursor::updateConfig(ConstElementPtr config) {
+    dlog("New config comes: " + config->toWire());
+
+    try {
+        // Parse forward_addresses
+        ConstElementPtr forwardAddressesE(config->get("forward_addresses"));
+        vector<addr_t> forwardAddresses(parseAddresses(forwardAddressesE));
+        ConstElementPtr listenAddressesE(config->get("listen_on"));
+        vector<addr_t> listenAddresses(parseAddresses(listenAddressesE));
+        bool set_timeouts(false);
+        int timeout = impl_->timeout_;
+        unsigned retries = impl_->retries_;
+        ConstElementPtr timeoutE(config->get("timeout")),
+            retriesE(config->get("retries"));
+        if (timeoutE) {
+            // It should be safe to just get it, the config manager should
+            // check for us
+            timeout = timeoutE->intValue();
+            if (timeout < -1) {
+                isc_throw(BadValue, "Timeout too small");
+            }
+            set_timeouts = true;
+        }
+        if (retriesE) {
+            if (retriesE->intValue() < 0) {
+                isc_throw(BadValue, "Negative number of retries");
+            }
+            retries = retriesE->intValue();
+            set_timeouts = true;
+        }
+        // Everything OK, so commit the changes
+        // listenAddresses can fail to bind, so try them first
+        if (listenAddressesE) {
+            setListenAddresses(listenAddresses);
+        }
+        if (forwardAddressesE) {
+            setForwardAddresses(forwardAddresses);
+        }
+        if (set_timeouts) {
+            setTimeouts(timeout, retries);
+        }
+        return (isc::config::createAnswer());
+    } catch (const isc::Exception& error) {
+        dlog(string("error in config: ") + error.what());
+        return (isc::config::createAnswer(1, error.what()));
+    }
+}
+
+void
+Recursor::setForwardAddresses(const vector<addr_t>& addresses)
+{
+    impl_->setForwardAddresses(addresses, dnss_);
+}
+
+bool
+Recursor::isForwarding() const {
+    return (!impl_->upstream_.empty());
+}
+
+vector<addr_t>
+Recursor::getForwardAddresses() const {
+    return (impl_->upstream_);
+}
+
+namespace {
+
+void
+setAddresses(DNSService *service, const vector<addr_t>& addresses) {
+    service->clearServers();
+    BOOST_FOREACH(const addr_t &address, addresses) {
+        service->addServer(address.second, address.first);
+    }
+}
+
+}
+
+void
+Recursor::setListenAddresses(const vector<addr_t>& addresses) {
+    try {
+        dlog("Setting listen addresses:");
+        BOOST_FOREACH(const addr_t& addr, addresses) {
+            dlog(" " + addr.first + ":" +
+                        boost::lexical_cast<string>(addr.second));
+        }
+        setAddresses(dnss_, addresses);
+        impl_->listen_ = addresses;
+    }
+    catch (const exception& e) {
+        /*
+         * We couldn't set it. So return it back. If that fails as well,
+         * we have a problem.
+         *
+         * If that fails, bad luck, but we are useless anyway, so just die
+         * and let boss start us again.
+         */
+        dlog(string("Unable to set new address: ") + e.what());
+        try {
+            setAddresses(dnss_, impl_->listen_);
+        }
+        catch (const exception& e2) {
+            dlog(string("Unable to recover from error;"));
+            dlog(string("Rollback failed with: ") + e2.what());
+            abort();
+        }
+        throw e; // Let it fly a little bit further
+    }
+}
+
+void
+Recursor::setTimeouts(int timeout, unsigned retries) {
+    dlog("Setting timeout to " + boost::lexical_cast<string>(timeout) +
+        " and retry count to " + boost::lexical_cast<string>(retries));
+    impl_->timeout_ = timeout;
+    impl_->retries_ = retries;
+    impl_->queryShutdown();
+    impl_->querySetup(*dnss_);
+}
+pair<int, unsigned>
+Recursor::getTimeouts() const {
+    return (pair<int, unsigned>(impl_->timeout_, impl_->retries_));
+}
+
+vector<addr_t>
+Recursor::getListenAddresses() const {
+    return (impl_->listen_);
+}

+ 151 - 0
src/bin/recurse/recursor.h

@@ -0,0 +1,151 @@
+// Copyright (C) 2009  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 __RECURSOR_H
+#define __RECURSOR_H 1
+
+#include <string>
+#include <vector>
+#include <utility>
+
+#include <cc/data.h>
+#include <config/ccsession.h>
+
+#include <asiolink/asiolink.h>
+
+class RecursorImpl;
+
+/**
+ * \short The recursive nameserver.
+ *
+ * It is a concreate class implementing recursive DNS server protocol
+ * processing. It is responsible for handling incoming DNS requests. It parses
+ * them, passes them deeper into the resolving machinery and then creates the
+ * answer. It doesn't really know about chasing referrals and similar, it
+ * simply plugs the parts that know into the network handling code.
+ */
+class Recursor {
+    ///
+    /// \name Constructors, Assignment Operator and Destructor.
+    ///
+    /// Note: The copy constructor and the assignment operator are
+    /// intentionally defined as private.
+    //@{
+private:
+    Recursor(const Recursor& source);
+    Recursor& operator=(const Recursor& source);
+public:
+    /// The constructor.
+    Recursor();
+    ~Recursor();
+    //@}
+
+    /// \brief Process an incoming DNS message, then signal 'server' to resume 
+    ///
+    /// A DNS query (or other message) has been received by a \c DNSServer
+    /// object.  Find an answer, then post the \c DNSServer object on the
+    /// I/O service queue and return.  When the server resumes, it can
+    /// send the reply.
+    ///
+    /// \param io_message The raw message received
+    /// \param message Pointer to the \c Message object
+    /// \param buffer Pointer to an \c OutputBuffer for the resposne
+    /// \param server Pointer to the \c DNSServer
+    void processMessage(const asiolink::IOMessage& io_message,
+                        isc::dns::MessagePtr message,
+                        isc::dns::OutputBufferPtr buffer,
+                        asiolink::DNSServer* server);
+
+    /// \brief Set and get the config session
+    isc::config::ModuleCCSession* getConfigSession() const;
+    void setConfigSession(isc::config::ModuleCCSession* config_session);
+
+    /// \brief Handle commands from the config session
+    isc::data::ConstElementPtr updateConfig(isc::data::ConstElementPtr config);
+
+    /// \brief Assign an ASIO IO Service queue to this Recursor object
+    void setDNSService(asiolink::DNSService& dnss);
+
+    /// \brief Return this object's ASIO IO Service queue
+    asiolink::DNSService& getDNSService() const { return (*dnss_); }
+
+    /// \brief Return pointer to the DNS Lookup callback function
+    asiolink::DNSLookup* getDNSLookupProvider() { return (dns_lookup_); }
+
+    /// \brief Return pointer to the DNS Answer callback function
+    asiolink::DNSAnswer* getDNSAnswerProvider() { return (dns_answer_); }
+
+    /// \brief Return pointer to the Checkin callback function
+    asiolink::SimpleCallback* getCheckinProvider() { return (checkin_); }
+
+    /**
+     * \brief Specify the list of upstream servers.
+     *
+     * Specify the list off addresses of upstream servers to forward queries
+     * to. If the list is empty, this server is set to full recursive mode.
+     * If it is non-empty, it switches to forwarder.
+     *
+     * @param addresses The list of addresses to use (each one is the address
+     * and port pair).
+     */
+    void setForwardAddresses(const std::vector<std::pair<std::string,
+        uint16_t> >& addresses);
+    /**
+     * \short Get list of upstream addresses.
+     *
+     * \see setForwardAddresses.
+     */
+    std::vector<std::pair<std::string, uint16_t> > getForwardAddresses() const;
+    /// Return if we are in forwarding mode (if not, we are in fully recursive)
+    bool isForwarding() const;
+
+    /**
+     * Set and get the addresses we listen on.
+     */
+    void setListenAddresses(const std::vector<std::pair<std::string,
+        uint16_t> >& addresses);
+    std::vector<std::pair<std::string, uint16_t> > getListenAddresses() const;
+
+    /**
+     * \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 retries The number of retries (0 means try the first time only,
+     *     do not retry).
+     */
+    void setTimeouts(int timeout = -1, unsigned retries = 0);
+
+    /**
+     * \short Get info about timeouts.
+     *
+     * \returns Timeout and retries (as described in setTimeouts).
+     */
+    std::pair<int, unsigned> getTimeouts() const;
+
+private:
+    RecursorImpl* impl_;
+    asiolink::DNSService* dnss_;
+    asiolink::SimpleCallback* checkin_;
+    asiolink::DNSLookup* dns_lookup_;
+    asiolink::DNSAnswer* dns_answer_;
+};
+
+#endif // __RECURSOR_H
+
+// Local Variables: 
+// mode: c++
+// End: 

+ 0 - 27
src/bin/recurse/run_b10-recurse.sh.in

@@ -1,27 +0,0 @@
-#! /bin/sh
-
-# Copyright (C) 2010  Internet Systems Consortium.
-#
-# Permission to use, copy, modify, and 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 INTERNET SYSTEMS CONSORTIUM
-# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
-# INTERNET SYSTEMS CONSORTIUM 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.
-
-PYTHON_EXEC=${PYTHON_EXEC:-@PYTHON@}
-export PYTHON_EXEC
-
-MYPATH_PATH=@abs_top_builddir@/src/bin/recurse
-PYTHONPATH=@abs_top_srcdir@/src/lib/python:@abs_top_builddir@/src/lib/xfr/.libs:@abs_top_builddir@/src/lib/dns/python/.libs
-export PYTHONPATH
-
-cd ${MYPATH_PATH}
-${PYTHON_EXEC} b10-recurse
-

+ 15 - 0
src/bin/recurse/spec_config.h.pre.in

@@ -0,0 +1,15 @@
+// Copyright (C) 2009  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.
+
+#define RECURSE_SPECFILE_LOCATION "@prefix@/share/@PACKAGE@/recurse.spec"

+ 40 - 0
src/bin/recurse/tests/Makefile.am

@@ -0,0 +1,40 @@
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
+AM_CPPFLAGS += -I$(top_builddir)/src/lib/dns -I$(top_srcdir)/src/bin
+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)
+
+if USE_STATIC_LINK
+AM_LDFLAGS = -static
+endif
+
+CLEANFILES = *.gcno *.gcda
+
+TESTS =
+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 += ../recursor.h ../recursor.cc
+run_unittests_SOURCES += recursor_unittest.cc
+run_unittests_SOURCES += recursor_config_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/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
+endif
+
+noinst_PROGRAMS = $(TESTS)

+ 235 - 0
src/bin/recurse/tests/recursor_config_unittest.cc

@@ -0,0 +1,235 @@
+// 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 <recurse/recursor.h>
+#include <testutils/srv_unittest.h>
+
+namespace {
+class RecursorConfig : public ::testing::Test {
+    public:
+        IOService ios;
+        DNSService dnss;
+        Recursor server;
+        RecursorConfig() :
+            dnss(ios, NULL, NULL, NULL)
+        {
+            server.setDNSService(dnss);
+        }
+        void invalidTest(const string &JOSN);
+};
+
+TEST_F(RecursorConfig, forwardAddresses) {
+    // Default value should be fully recursive
+    EXPECT_TRUE(server.getForwardAddresses().empty());
+    EXPECT_FALSE(server.isForwarding());
+
+    // Try putting there some addresses
+    vector<pair<string, uint16_t> > addresses;
+    addresses.push_back(pair<string, uint16_t>(DEFAULT_REMOTE_ADDRESS, 53));
+    addresses.push_back(pair<string, uint16_t>("::1", 53));
+    server.setForwardAddresses(addresses);
+    EXPECT_EQ(2, server.getForwardAddresses().size());
+    EXPECT_EQ("::1", server.getForwardAddresses()[1].first);
+    EXPECT_TRUE(server.isForwarding());
+
+    // Is it independent from what we do with the vector later?
+    addresses.clear();
+    EXPECT_EQ(2, server.getForwardAddresses().size());
+
+    // Did it return to fully recursive?
+    server.setForwardAddresses(addresses);
+    EXPECT_TRUE(server.getForwardAddresses().empty());
+    EXPECT_FALSE(server.isForwarding());
+}
+
+TEST_F(RecursorConfig, forwardAddressConfig) {
+    // Try putting there some address
+    ElementPtr config(Element::fromJSON("{"
+        "\"forward_addresses\": ["
+        "   {"
+        "       \"address\": \"192.0.2.1\","
+        "       \"port\": 53"
+        "   }"
+        "]"
+        "}"));
+    ConstElementPtr result(server.updateConfig(config));
+    EXPECT_EQ(result->toWire(), isc::config::createAnswer()->toWire());
+    EXPECT_TRUE(server.isForwarding());
+    ASSERT_EQ(1, server.getForwardAddresses().size());
+    EXPECT_EQ("192.0.2.1", server.getForwardAddresses()[0].first);
+    EXPECT_EQ(53, server.getForwardAddresses()[0].second);
+
+    // And then remove all addresses
+    config = Element::fromJSON("{"
+        "\"forward_addresses\": null"
+        "}");
+    result = server.updateConfig(config);
+    EXPECT_EQ(result->toWire(), isc::config::createAnswer()->toWire());
+    EXPECT_FALSE(server.isForwarding());
+    EXPECT_EQ(0, server.getForwardAddresses().size());
+}
+
+void
+RecursorConfig::invalidTest(const string &JOSN) {
+    ElementPtr config(Element::fromJSON(JOSN));
+    EXPECT_FALSE(server.updateConfig(config)->equals(
+        *isc::config::createAnswer())) << "Accepted config " << JOSN << endl;
+}
+
+TEST_F(RecursorConfig, invalidForwardAddresses) {
+    // Try torturing it with some invalid inputs
+    invalidTest("{"
+        "\"forward_addresses\": \"error\""
+        "}");
+    invalidTest("{"
+        "\"forward_addresses\": [{}]"
+        "}");
+    invalidTest("{"
+        "\"forward_addresses\": [{"
+        "   \"port\": 1.5,"
+        "   \"address\": \"192.0.2.1\""
+        "}]}");
+    invalidTest("{"
+        "\"forward_addresses\": [{"
+        "   \"port\": -5,"
+        "   \"address\": \"192.0.2.1\""
+        "}]}");
+    invalidTest("{"
+        "\"forward_addresses\": [{"
+        "   \"port\": 53,"
+        "   \"address\": \"bad_address\""
+        "}]}");
+}
+
+TEST_F(RecursorConfig, listenAddresses) {
+    // Default value should be fully recursive
+    EXPECT_TRUE(server.getListenAddresses().empty());
+
+    // Try putting there some addresses
+    vector<pair<string, uint16_t> > addresses;
+    addresses.push_back(pair<string, uint16_t>("127.0.0.1", 5300));
+    addresses.push_back(pair<string, uint16_t>("::1", 5300));
+    server.setListenAddresses(addresses);
+    EXPECT_EQ(2, server.getListenAddresses().size());
+    EXPECT_EQ("::1", server.getListenAddresses()[1].first);
+
+    // Is it independent from what we do with the vector later?
+    addresses.clear();
+    EXPECT_EQ(2, server.getListenAddresses().size());
+
+    // Did it return to fully recursive?
+    server.setListenAddresses(addresses);
+    EXPECT_TRUE(server.getListenAddresses().empty());
+}
+
+TEST_F(RecursorConfig, DISABLED_listenAddressConfig) {
+    // Try putting there some address
+    ElementPtr config(Element::fromJSON("{"
+        "\"listen_on\": ["
+        "   {"
+        "       \"address\": \"127.0.0.1\","
+        "       \"port\": 5300"
+        "   }"
+        "]"
+        "}"));
+    ConstElementPtr result(server.updateConfig(config));
+    EXPECT_EQ(result->toWire(), isc::config::createAnswer()->toWire());
+    ASSERT_EQ(1, server.getListenAddresses().size());
+    EXPECT_EQ("127.0.0.1", server.getListenAddresses()[0].first);
+    EXPECT_EQ(5300, server.getListenAddresses()[0].second);
+
+    // As this is example address, the machine should not have it on
+    // any interface
+    // FIXME: This test aborts, because it tries to rollback and
+    //     it is impossible, since the sockets are not closed.
+    //     Once #388 is solved, enable this test.
+    config = Element::fromJSON("{"
+        "\"listen_on\": ["
+        "   {"
+        "       \"address\": \"192.0.2.0\","
+        "       \"port\": 5300"
+        "   }"
+        "]"
+        "}");
+    result = server.updateConfig(config);
+    EXPECT_FALSE(result->equals(*isc::config::createAnswer()));
+    ASSERT_EQ(1, server.getListenAddresses().size());
+    EXPECT_EQ("127.0.0.1", server.getListenAddresses()[0].first);
+    EXPECT_EQ(5300, server.getListenAddresses()[0].second);
+}
+
+TEST_F(RecursorConfig, invalidListenAddresses) {
+    // Try torturing it with some invalid inputs
+    invalidTest("{"
+        "\"listen_on\": \"error\""
+        "}");
+    invalidTest("{"
+        "\"listen_on\": [{}]"
+        "}");
+    invalidTest("{"
+        "\"listen_on\": [{"
+        "   \"port\": 1.5,"
+        "   \"address\": \"192.0.2.1\""
+        "}]}");
+    invalidTest("{"
+        "\"listen_on\": [{"
+        "   \"port\": -5,"
+        "   \"address\": \"192.0.2.1\""
+        "}]}");
+    invalidTest("{"
+        "\"listen_on\": [{"
+        "   \"port\": 53,"
+        "   \"address\": \"bad_address\""
+        "}]}");
+}
+
+// Just test it sets and gets the values correctly
+TEST_F(RecursorConfig, timeouts) {
+    server.setTimeouts(0, 1);
+    EXPECT_EQ(0, server.getTimeouts().first);
+    EXPECT_EQ(1, server.getTimeouts().second);
+    server.setTimeouts();
+    EXPECT_EQ(-1, server.getTimeouts().first);
+    EXPECT_EQ(0, server.getTimeouts().second);
+}
+
+TEST_F(RecursorConfig, timeoutsConfig) {
+    ElementPtr config = Element::fromJSON("{"
+            "\"timeout\": 1000,"
+            "\"retries\": 3"
+            "}");
+    ConstElementPtr result(server.updateConfig(config));
+    EXPECT_EQ(result->toWire(), isc::config::createAnswer()->toWire());
+    EXPECT_EQ(1000, server.getTimeouts().first);
+    EXPECT_EQ(3, server.getTimeouts().second);
+}
+
+TEST_F(RecursorConfig, invalidTimeoutsConfig) {
+    invalidTest("{"
+        "\"timeout\": \"error\""
+        "}");
+    invalidTest("{"
+        "\"timeout\": -2"
+        "}");
+    invalidTest("{"
+        "\"retries\": \"error\""
+        "}");
+    invalidTest("{"
+        "\"retries\": -1"
+        "}");
+}
+
+}

+ 97 - 0
src/bin/recurse/tests/recursor_unittest.cc

@@ -0,0 +1,97 @@
+// 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 <recurse/recursor.h>
+#include <testutils/srv_unittest.h>
+
+namespace {
+const char* const TEST_PORT = "53535";
+
+class RecursorTest : public SrvTestBase{
+protected:
+    RecursorTest() : server(){}
+    Recursor server;
+};
+
+// Unsupported requests.  Should result in NOTIMP.
+TEST_F(RecursorTest, unsupportedRequest) {
+    UNSUPPORTED_REQUEST_TEST;
+}
+
+// Multiple questions.  Should result in FORMERR.
+TEST_F(RecursorTest, multiQuestion) {
+    MULTI_QUESTION_TEST; 
+}
+
+// Incoming data doesn't even contain the complete header.  Must be silently
+// dropped.
+TEST_F(RecursorTest, shortMessage) {
+    SHORT_MESSAGE_TEST;
+}
+
+// Response messages.  Must be silently dropped, whether it's a valid response
+// or malformed or could otherwise cause a protocol error.
+TEST_F(RecursorTest, response) {
+    RESPONSE_TEST;
+}
+
+// Query with a broken question
+TEST_F(RecursorTest, shortQuestion) {
+    SHORT_QUESTION_TEST;
+}
+
+// Query with a broken answer section
+TEST_F(RecursorTest, shortAnswer) {
+    SHORT_ANSWER_TEST;
+}
+
+// Query with unsupported version of EDNS.
+TEST_F(RecursorTest, ednsBadVers) {
+    EDNS_BADVERS_TEST;
+}
+
+TEST_F(RecursorTest, AXFROverUDP) {
+    AXFR_OVER_UDP_TEST;
+}
+
+TEST_F(RecursorTest, AXFRFail) {
+    UnitTestUtil::createRequestMessage(request_message, opcode, default_qid,
+                                       Name("example.com"), RRClass::IN(),
+                                       RRType::AXFR());
+    createRequestPacket(request_message, IPPROTO_TCP);
+    // AXFR is not implemented and should always send NOTIMP.
+    server.processMessage(*io_message, parse_message, response_obuffer, &dnsserv);
+    EXPECT_TRUE(dnsserv.hasAnswer());
+    headerCheck(*parse_message, default_qid, Rcode::NOTIMP(), opcode.getCode(),
+                QR_FLAG, 1, 0, 0, 0);
+}
+
+TEST_F(RecursorTest, notifyFail) {
+    // Notify should always return NOTAUTH
+    request_message.clear(Message::RENDER);
+    request_message.setOpcode(Opcode::NOTIFY());
+    request_message.setRcode(Rcode::NOERROR());
+    request_message.setHeaderFlag(Message::HEADERFLAG_AA);
+    request_message.setQid(default_qid);
+    request_message.setHeaderFlag(Message::HEADERFLAG_AA);
+    createRequestPacket(request_message, IPPROTO_UDP);
+    server.processMessage(*io_message, parse_message, response_obuffer, &dnsserv);
+    EXPECT_TRUE(dnsserv.hasAnswer());
+    headerCheck(*parse_message, default_qid, Rcode::NOTAUTH(),
+                Opcode::NOTIFY().getCode(), QR_FLAG, 0, 0, 0, 0);
+}
+
+}

+ 26 - 0
src/bin/recurse/tests/run_unittests.cc

@@ -0,0 +1,26 @@
+// Copyright (C) 2009  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 <gtest/gtest.h>
+
+#include <dns/tests/unittest_util.h>
+
+int
+main(int argc, char* argv[]) {
+    ::testing::InitGoogleTest(&argc, argv);
+    isc::UnitTestUtil::addDataPath(TEST_DATA_DIR);
+    isc::UnitTestUtil::addDataPath(TEST_DATA_BUILDDIR);
+
+    return (RUN_ALL_TESTS());
+}

+ 2 - 1
src/lib/Makefile.am

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

+ 34 - 0
src/lib/asiolink/Makefile.am

@@ -0,0 +1,34 @@
+SUBDIRS = . tests
+
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+AM_CPPFLAGS += -I$(top_srcdir)/src/lib/dns -I$(top_builddir)/src/lib/dns
+
+AM_CXXFLAGS = $(B10_CXXFLAGS)
+
+CLEANFILES = *.gcno *.gcda
+
+# This is a wrapper library solely used for b10-auth.  The ASIO header files
+# 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 += internal/coroutine.h
+# Note: the ordering matters: -Wno-... must follow -Wextra (defined in
+# B10_CXXFLAGS)
+libasiolink_la_CXXFLAGS = $(AM_CXXFLAGS)
+if USE_GXX
+libasiolink_la_CXXFLAGS += -Wno-unused-parameter
+endif
+if USE_CLANGPP
+# Same for clang++, but we need to turn off -Werror completely.
+libasiolink_la_CXXFLAGS += -Wno-error
+endif
+libasiolink_la_CPPFLAGS = $(AM_CPPFLAGS)
+libasiolink_la_LIBADD = $(top_builddir)/src/lib/log/liblog.la

+ 103 - 0
src/lib/asiolink/README

@@ -0,0 +1,103 @@
+The asiolink library is intended to provide an abstraction layer between
+BIND10 modules and the socket I/O subsystem we are using (currently, the
+headers-only version of ASIO, release 1.43).  This has several benefits,
+including:
+
+  - Simple interface
+
+  - Back-end flexibility:  It would be easy to switch from using
+    ASIO to boost::asio, and even relatively straightforward to switch
+    to any other asynchronous I/O system.
+
+  - Cleaner compilation:  The ASIO headers include code which can
+    generate warnings in some compilers due to unused parameters and
+    such.  Including ASIO header files throughout the BIND 10 tree would
+    require us to relax the strictness of our error checking.  Including
+    them in only one place allows us to relax strictness here, while
+    leaving it in place elsewhere.
+
+Currently, the asiolink library only supports DNS servers (i.e., b10-auth
+and b10-recurse).  The plan is to make it more generic and allow it to
+support other modules as well.
+
+Some of the classes defined here--for example, IOSocket, IOEndpoint,
+and IOAddress--are to be used by BIND 10 modules as wrappers around
+ASIO-specific classes.
+
+Other classes implement the DNS protocol on behalf of BIND 10 modules.
+
+These DNS server and client routines are written using the "stackless
+coroutine" pattern invented by Chris Kohlhoff and described at
+http://blog.think-async.com/2010/03/potted-guide-to-stackless-coroutines.html.
+This is intended to simplify development a bit, since it allows the
+routines to be written in a straightfowrard step-step-step fashion rather
+than as a complex chain of separate handler functions.
+
+Coroutine objects (i.e., UDPServer, TCPServer and UDPQuery) are objects
+with reenterable operator() members.  When an instance of one of these
+classes is called as a function, it resumes at the position where it left
+off.  Thus, a UDPServer can issue an asynchronous I/O call and specify
+itself as the handler object; when the call completes, the UDPServer
+carries on at the same position.  As a result, the code can look as
+if it were using synchronous, not asynchronous, I/O, providing some of
+the benefit of threading but with minimal switching overhead.
+
+So, in simplified form, the behavior of a DNS Server is:
+
+  REENTER:
+    while true:
+      YIELD packet = read_packet
+      FORK
+      if not parent:
+        break
+
+    # This callback informs the caller that a packet has arrived, and
+    # gives it a chance to update configuration, etc
+    SimpleCallback(packet)
+    YIELD answer = DNSLookup(packet, this)
+    response = DNSAnswer(answer)
+    YIELD send(response)
+
+At each "YIELD" point, the coroutine initiates an asynchronous operation,
+then pauses and turns over control to some other task on the ASIO service
+queue.  When the operation completes, the coroutine resumes.
+
+DNSLookup, DNSAnswer and SimpleCallback define callback methods
+used by a DNS Server to communicate with the module that called it.
+They are abstract-only classes whose concrete implementations
+are supplied by the calling module.
+
+The DNSLookup callback always runs asynchronously.  Concrete
+implementations must be sure to call the server's "resume" method when
+it is finished.
+
+In an authoritative server, the DNSLookup implementation would examine
+the query, look up the answer, then call "resume".  (See the diagram
+in doc/auth_process.jpg.)
+
+In a recursive server, the DNSLookup impelemtation would initiate a
+DNSQuery, which in turn would be responsible for calling the server's
+"resume" method.  (See the diagram in doc/recursive_process.jpg.)
+
+A DNSQuery object is intended to handle resolution of a query over
+the network when the local authoritative data sources or cache are not
+sufficient.  The plan is that it will make use of subsidiary DNSFetch
+calls to get data from particular authoritative servers, and when it has
+gotten a complete answer, it calls "resume".
+
+In current form, however, DNSQuery is much simpler; it forwards queries
+to a single upstream resolver and passes the answers back to the client.
+It is constructed with the address of the forward server.  Queries are
+initiated with the question to ask the forward server, a buffer into
+which to write the answer, and a pointer to the coroutine to be resumed
+when the answer has arrived.  In simplified form, the DNSQuery routine is:
+
+  REENTER:
+    render the question into a wire-format query packet
+    YIELD send(query)
+    YIELD response = read_packet
+    server->resume
+
+Currently, DNSQuery is only implemented for UDP queries.  In future work
+it will be necessary to write code to fall back to TCP when circumstances
+require it.

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

@@ -0,0 +1,377 @@
+// 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 <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/shared_ptr.hpp>
+
+#include <dns/buffer.h>
+#include <dns/message.h>
+
+#include <asiolink/asiolink.h>
+#include <asiolink/internal/tcpdns.h>
+#include <asiolink/internal/udpdns.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;
+
+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());
+}
+
+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;
+
+}
+
+RecursiveQuery::RecursiveQuery(DNSService& dns_service,
+    const AddressVector& upstream, int timeout, unsigned retries) :
+    dns_service_(dns_service), upstream_(new AddressVector(upstream)),
+    timeout_(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 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_;
+    shared_ptr<AddressVector> upstream_;
+
+    // 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 timeout_;
+    unsigned retries_;
+
+    // (re)send the query to the server.
+    void send() {
+        const int uc = upstream_->size();
+        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,
+                timeout_);
+            io_.post(query);
+        } else {
+            dlog("Error, no upstream servers to send to.");
+        }
+    }
+public:
+    RunningQuery(asio::io_service& io, const Question &question,
+        shared_ptr<AddressVector> upstream,
+        OutputBufferPtr buffer, DNSServer* server, int timeout,
+        unsigned retries) :
+        io_(io),
+        question_(question),
+        upstream_(upstream),
+        buffer_(buffer),
+        server_(server->clone()),
+        timeout_(timeout),
+        retries_(retries)
+    {
+        send();
+    }
+
+    // This function is used as callback from DNSQuery.
+    virtual void operator()(UDPQuery::Result result) {
+        if (result == UDPQuery::TIME_OUT && retries_ --) {
+            dlog("Resending query");
+            // We timed out, but we have some retries, so send again
+            send();
+        } else {
+            server_->resume(result == UDPQuery::SUCCESS);
+            delete this;
+        }
+    }
+};
+
+}
+
+void
+RecursiveQuery::sendQuery(const Question& question, 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, upstream_, buffer, server,
+         timeout_, retries_);
+}
+
+}

+ 575 - 0
src/lib/asiolink/asiolink.h

@@ -0,0 +1,575 @@
+// 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 __ASIOLINK_H
+#define __ASIOLINK_H 1
+
+// 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 <functional>
+#include <string>
+#include <vector>
+#include <utility>
+
+#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;
+}
+
+/// \namespace asiolink
+/// \brief A wrapper interface for the ASIO library.
+///
+/// The \c asiolink namespace is used to define a set of wrapper interfaces
+/// for the ASIO library.
+///
+/// BIND 10 uses the non-Boost version of ASIO because it's header-only,
+/// i.e., does not require a separate library object to be linked, and thus
+/// lowers the bar for introduction.
+///
+/// But the advantage comes with its own costs: since the header-only version
+/// includes more definitions in public header files, it tends to trigger
+/// more compiler warnings for our own sources, and, depending on the
+/// compiler options, may make the build fail.
+///
+/// We also found it may be tricky to use ASIO and standard C++ libraries
+/// in a single translation unit, i.e., a .cc file: depending on the order
+/// of including header files, ASIO may or may not work on some platforms.
+///
+/// This wrapper interface is intended to centralize these
+/// problematic issues in a single sub module.  Other BIND 10 modules should
+/// simply include \c asiolink.h and use the wrapper API instead of
+/// including ASIO header files and using ASIO-specific classes directly.
+///
+/// This wrapper may be used for other IO libraries if and when we want to
+/// switch, but generality for that purpose is not the primary goal of
+/// this module.  The resulting interfaces are thus straightforward mapping
+/// to the ASIO counterparts.
+///
+/// Notes to developers:
+/// Currently the wrapper interface is fairly specific to use by a
+/// DNS server, i.e., b10-auth or b10-recurse.  But the plan is to
+/// generalize it and have other modules use it as well.
+///
+/// One obvious drawback of this approach is performance overhead
+/// due to the additional layer.  We should eventually evaluate the cost
+/// of the wrapper abstraction in benchmark tests. Another drawback is
+/// that the wrapper interfaces don't provide all features of ASIO
+/// (at least for the moment).  We should also re-evaluate the
+/// maintenance overhead of providing necessary wrappers as we develop
+/// more.
+///
+/// On the other hand, we may be able to exploit the wrapper approach to
+/// simplify the interfaces (by limiting the usage) and unify performance
+/// optimization points.
+///
+/// As for optimization, we may want to provide a custom allocator for
+/// the placeholder of callback handlers:
+/// http://think-async.com/Asio/asio-1.3.1/doc/asio/reference/asio_handler_allocate.html
+
+namespace asiolink {
+class DNSServiceImpl;
+struct IOServiceImpl;
+
+/// \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
+/// ASIO specific exceptions.
+class IOError : public isc::Exception {
+public:
+    IOError(const char* file, size_t line, const char* what) :
+        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 inline 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 inline 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 inline 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 inline 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) {}
+        inline 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 inline 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::OutputBufferPtr buffer,
+                            DNSServer* server) const
+    {
+        (*self_)(io_message, 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::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 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, int timeout = -1, unsigned retries = 0);
+    //@}
+
+    /// \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::OutputBufferPtr buffer,
+                   DNSServer* server);
+private:
+    DNSService& dns_service_;
+    boost::shared_ptr<std::vector<std::pair<std::string, uint16_t> > >
+        upstream_;
+    int timeout_;
+    unsigned retries_;
+};
+
+}      // asiolink
+#endif // __ASIOLINK_H
+
+// Local Variables: 
+// mode: c++
+// End: 

BIN
src/lib/asiolink/doc/auth_process.jpg


BIN
src/lib/asiolink/doc/recursive_process.jpg


+ 133 - 0
src/lib/asiolink/internal/coroutine.h

@@ -0,0 +1,133 @@
+//
+// coroutine.h
+// ~~~~~~~~~~~
+//
+// Copyright (c) 2003-2010 Christopher M. Kohlhoff (chris at kohlhoff dot com)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+#ifndef COROUTINE_HPP
+#define COROUTINE_HPP
+
+
+// \brief Coroutine object
+//
+// A coroutine object maintains the state of a re-enterable routine.  It
+// is assignable and copy-constructable, and can be used as a base class
+// for a class that uses it, or as a data member.  The copy overhead is
+// a single int.
+//
+// A reenterable function contains a CORO_REENTER (coroutine)  { ... }
+// block.  Whenever an asychrnonous operation is initiated within the
+// routine, the function is provided as the handler object.  (The simplest
+// way to do this is to have the reenterable function be the operator()
+// member for the coroutine object itself.)   For example:
+// 
+//     CORO_YIELD socket->async_read_some(buffer, *this);
+//
+// The CORO_YIELD keyword updates the current status of the coroutine to
+// indicate the line number currently being executed.  The
+// async_read_some() call is initiated, with a copy of the updated
+// corotutine as its handler object, and the current coroutine exits.  When
+// the async_read_some() call finishes, the copied coroutine will be
+// called, and will resume processing exactly where the original one left
+// off--right after asynchronous call.  This allows asynchronous I/O
+// routines to be written with a logical flow, step following step, rather
+// than as a linked chain of separate handler functions.
+//
+// When necessary, a coroutine can fork itself using the CORO_FORK keyword.
+// This updates the status of the coroutine and makes a copy.  The copy can
+// then be called directly or posted to the ASIO service queue so that both
+// coroutines will continue forward, one "parent" and one "child".  The
+// is_parent() and is_child() methods indicate which is which.
+//
+// The CORO_REENTER, CORO_YIELD and CORO_FORK keywords are implemented
+// via preprocessor macros.  The CORO_REENTER block is actually a large,
+// complex switch statement.  Because of this, inline variable declaration
+// is impossible within CORO_REENTER unless it is done in a subsidiary
+// scope--and if it is, that scope cannot contain CORO_YIELD or CORO_FORK
+// keywords.
+//
+// Because coroutines are frequently copied, it is best to minimize copy
+// overhead by limiting the size of data members in derived classes.
+//
+// It should be noted that when a coroutine falls out of scope its memory
+// is reclaimed, even though it may be scheduled to resume when an
+// asynchronous operation completes.  Any shared_ptr<> objects declared in
+// the coroutine may be destroyed if their reference count drops to zero,
+// in which case the coroutine will have serious problems once it resumes.
+// One solution so this is to have the space that will be used by a
+// coroutine pre-allocated and stored on a free list; a new coroutine can
+// fetch the block of space off a free list, place a shared pointer to it
+// on an "in use" list, and carry on.  The reference in the "in use" list
+// would prevent the data from being destroyed.
+class coroutine
+{
+public:
+  coroutine() : value_(0) {}
+  virtual ~coroutine() {}
+  bool is_child() const { return value_ < 0; }
+  bool is_parent() const { return !is_child(); }
+  bool is_complete() const { return value_ == -1; }
+  int get_value() const { return value_; }
+private:
+  friend class coroutine_ref;
+  int value_;
+};
+
+class coroutine_ref
+{
+public:
+  coroutine_ref(coroutine& c) : value_(c.value_), modified_(false) {}
+  coroutine_ref(coroutine* c) : value_(c->value_), modified_(false) {}
+  ~coroutine_ref() { if (!modified_) value_ = -1; }
+  operator int() const { return value_; }
+  int& operator=(int v) { modified_ = true; return value_ = v; }
+private:
+  void operator=(const coroutine_ref&);
+  int& value_;
+  bool modified_;
+};
+
+#define CORO_REENTER(c) \
+  switch (coroutine_ref _coro_value = c) \
+    case -1: if (_coro_value) \
+    { \
+      goto terminate_coroutine; \
+      terminate_coroutine: \
+      _coro_value = -1; \
+      goto bail_out_of_coroutine; \
+      bail_out_of_coroutine: \
+      break; \
+    } \
+    else case 0:
+
+#define CORO_YIELD \
+  for (_coro_value = __LINE__;;) \
+    if (_coro_value == 0) \
+    { \
+      case __LINE__: ; \
+      break; \
+    } \
+    else \
+      switch (_coro_value ? 0 : 1) \
+        for (;;) \
+          case -1: if (_coro_value) \
+            goto terminate_coroutine; \
+          else for (;;) \
+            case 1: if (_coro_value) \
+              goto bail_out_of_coroutine; \
+            else case 0:
+
+#define CORO_FORK \
+  for (_coro_value = -__LINE__;; _coro_value = __LINE__) \
+    if (_coro_value == __LINE__) \
+    { \
+      case -__LINE__: ; \
+      break; \
+    } \
+    else
+#endif // COROUTINE_HPP
+

+ 225 - 0
src/lib/asiolink/internal/tcpdns.h

@@ -0,0 +1,225 @@
+// 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 __TCPDNS_H
+#define __TCPDNS_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 <asiolink/asiolink.h>
+#include <asiolink/internal/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_; }
+    //@}
+
+    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::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) {}
+
+    inline int getNative() const { return (socket_.native()); }
+    inline int getProtocol() const { return (IPPROTO_TCP); }
+
+private:
+    asio::ip::tcp::socket& socket_;
+};
+
+/// \brief A TCP-specific \c DNSServer object.
+///
+/// This class inherits from both \c DNSServer and from \c coroutine,
+/// defined in coroutine.h. 
+class TCPServer : public virtual DNSServer, public virtual coroutine {
+public:
+    explicit TCPServer(asio::io_service& io_service,
+                       const asio::ip::address& addr, const uint16_t port, 
+                       const SimpleCallback* checkin = NULL,
+                       const DNSLookup* lookup = NULL,
+                       const DNSAnswer* answer = NULL);
+
+    void operator()(asio::error_code ec = asio::error_code(),
+                    size_t length = 0);
+    void asyncLookup();
+    void resume(const bool done);
+    bool hasAnswer() { return (done_); }
+    int value() { return (get_value()); }
+
+    DNSServer* clone() {
+        TCPServer* s = new TCPServer(*this);
+        return (s);
+    }
+
+private:
+    enum { MAX_LENGTH = 65535 };
+    static const size_t TCP_MESSAGE_LENGTHSIZE = 2;
+
+    // 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.
+    //
+    // An ASIO acceptor object to handle new connections.  Created in
+    // the constructor.
+    boost::shared_ptr<asio::ip::tcp::acceptor> acceptor_;
+
+    // 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::tcp::socket> socket_;
+
+    // The buffer into which the response is written
+    boost::shared_ptr<isc::dns::OutputBuffer> respbuf_;
+
+    // \c IOMessage and \c Message objects to be passed to the
+    // DNS lookup and answer providers
+    boost::shared_ptr<asiolink::IOMessage> io_message_;
+    isc::dns::MessagePtr message_;
+
+    // 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 // __TCPDNS_H
+
+// Local Variables: 
+// mode: c++
+// End: 

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

@@ -0,0 +1,299 @@
+// 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 __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_;
+    isc::dns::MessagePtr 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_;
+};
+
+//
+// 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_;
+};
+}
+
+
+#endif // __UDPDNS_H
+
+// Local Variables: 
+// mode: c++
+// End: 

+ 64 - 0
src/lib/asiolink/ioaddress.cc

@@ -0,0 +1,64 @@
+// 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 <config.h>
+
+#include <unistd.h>             // for some IPC/network system calls
+#include <sys/socket.h>
+#include <netinet/in.h>
+
+#include <asio.hpp>
+
+#include <asiolink/asiolink.h>
+
+using namespace asio;
+using asio::ip::udp;
+using asio::ip::tcp;
+
+using namespace std;
+
+namespace asiolink {
+
+// XXX: we cannot simply construct the address in the initialization list,
+// because we'd like to throw our own exception on failure.
+IOAddress::IOAddress(const string& address_str) {
+    error_code err;
+    asio_address_ = ip::address::from_string(address_str, err);
+    if (err) {
+        isc_throw(IOError, "Failed to convert string to address '"
+                  << address_str << "': " << err.message());
+    }
+}
+
+IOAddress::IOAddress(const ip::address& asio_address) :
+    asio_address_(asio_address)
+{}
+
+string
+IOAddress::toText() const {
+    return (asio_address_.to_string());
+}
+
+short
+IOAddress::getFamily() const {
+    if (asio_address_.is_v4()) {
+        return (AF_INET);
+    } else {
+        return (AF_INET6);
+    }
+}
+
+}

+ 90 - 0
src/lib/asiolink/ioaddress.h

@@ -0,0 +1,90 @@
+// 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 __IOADDRESS_H
+#define __IOADDRESS_H 1
+
+// 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 <functional>
+#include <string>
+
+#include <exceptions/exceptions.h>
+
+namespace asiolink {
+
+/// \brief The \c IOAddress class represents an IP addresses (version
+/// agnostic)
+///
+/// This class is a wrapper for the ASIO \c ip::address class.
+class IOAddress {
+public:
+    ///
+    /// \name Constructors and Destructor
+    ///
+    /// This class is copyable.  We use default versions of copy constructor
+    /// and the assignment operator.
+    /// We use the default destructor.
+    //@{
+    /// \brief Constructor from string.
+    ///
+    /// This constructor converts a textual representation of IPv4 and IPv6
+    /// addresses into an IOAddress object.
+    /// If \c address_str is not a valid representation of any type of
+    /// address, an exception of class \c IOError will be thrown.
+    /// This constructor allocates memory for the object, and if that fails
+    /// a corresponding standard exception will be thrown.
+    ///
+    /// \param address_str Textual representation of address.
+    IOAddress(const std::string& address_str);
+
+    /// \brief Constructor from an ASIO \c ip::address object.
+    ///
+    /// This constructor is intended to be used within the wrapper
+    /// implementation; user applications of the wrapper API won't use it.
+    ///
+    /// This constructor never throws an exception.
+    ///
+    /// \param asio_address The ASIO \c ip::address to be converted.
+    IOAddress(const asio::ip::address& asio_adress);
+    //@}
+
+    /// \brief Convert the address to a string.
+    ///
+    /// This method is basically expected to be exception free, but
+    /// generating the string will involve resource allocation,
+    /// and if it fails the corresponding standard exception will be thrown.
+    ///
+    /// \return A string representation of the address.
+    std::string toText() const;
+
+    /// \brief Returns the address family.
+    short getFamily() const;
+
+private:
+    asio::ip::address asio_address_;
+};
+
+}      // asiolink
+#endif // __IOADDRESS_H
+
+// Local Variables: 
+// mode: c++
+// End: 

+ 45 - 0
src/lib/asiolink/ioendpoint.cc

@@ -0,0 +1,45 @@
+// 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 <config.h>
+
+#include <unistd.h>             // for some IPC/network system calls
+#include <sys/socket.h>
+#include <netinet/in.h>
+
+#include <asiolink/asiolink.h>
+#include <internal/tcpdns.h>
+#include <internal/udpdns.h>
+
+using namespace std;
+
+namespace asiolink {
+
+const IOEndpoint*
+IOEndpoint::create(const int protocol, const IOAddress& address,
+                   const unsigned short port)
+{
+    if (protocol == IPPROTO_UDP) {
+        return (new UDPEndpoint(address, port));
+    } else if (protocol == IPPROTO_TCP) {
+        return (new TCPEndpoint(address, port));
+    }
+    isc_throw(IOError,
+              "IOEndpoint creation attempt for unsupported protocol: " <<
+              protocol);
+}
+
+}

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

@@ -0,0 +1,123 @@
+// 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 __IOENDPOINT_H
+#define __IOENDPOINT_H 1
+
+// 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 <functional>
+#include <string>
+
+#include <exceptions/exceptions.h>
+
+namespace asiolink {
+
+/// \brief The \c IOEndpoint class is an abstract base class to represent
+/// a communication endpoint.
+///
+/// This class is a wrapper for the ASIO endpoint classes such as
+/// \c ip::tcp::endpoint and \c ip::udp::endpoint.
+///
+/// Derived class implementations are completely hidden within the
+/// implementation.  User applications only get access to concrete
+/// \c IOEndpoint objects via the abstract interfaces.
+class IOEndpoint {
+    ///
+    /// \name Constructors and Destructor
+    ///
+    /// Note: The copy constructor and the assignment operator are
+    /// intentionally defined as private, making this class non-copyable.
+    //@{
+private:
+    IOEndpoint(const IOEndpoint& source);
+    IOEndpoint& operator=(const IOEndpoint& 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).
+    IOEndpoint() {}
+public:
+    /// The destructor.
+    virtual ~IOEndpoint() {}
+    //@}
+
+    /// \brief Returns the address of the endpoint.
+    ///
+    /// This method returns an IOAddress object corresponding to \c this
+    /// endpoint.
+    ///
+    /// Note that the return value is a real object, not a reference or
+    /// a pointer.
+    ///
+    /// This is aligned with the interface of the ASIO counterpart:
+    /// the \c address() method of \c ip::xxx::endpoint classes returns
+    /// an \c ip::address object.
+    ///
+    /// This also means handling the address of an endpoint using this method
+    /// can be expensive.  If the address information is necessary in a
+    /// performance sensitive context and there's a more efficient interface
+    /// for that purpose, it's probably better to avoid using this method.
+    ///
+    /// This method never throws an exception.
+    ///
+    /// \return A copy of \c IOAddress object corresponding to the endpoint.
+    virtual IOAddress getAddress() const = 0;
+
+    /// \brief Returns the port of the endpoint.
+    virtual uint16_t getPort() const = 0;
+
+    /// \brief Returns the protocol number of the endpoint (TCP, UDP...)
+    virtual short getProtocol() const = 0;
+
+    /// \brief Returns the address family of the endpoint.
+    virtual short getFamily() const = 0;
+
+    /// \brief A polymorphic factory of endpoint from address and port.
+    ///
+    /// This method creates a new instance of (a derived class of)
+    /// \c IOEndpoint object that identifies the pair of given address
+    /// and port.
+    /// The appropriate derived class is chosen based on the specified
+    /// transport protocol.  If the \c protocol doesn't specify a protocol
+    /// supported in this implementation, an exception of class \c IOError
+    /// will be thrown.
+    ///
+    /// Memory for the created object will be dynamically allocated.  It's
+    /// the caller's responsibility to \c delete it later.
+    /// If resource allocation for the new object fails, a corresponding
+    /// standard exception will be thrown.
+    ///
+    /// \param protocol The transport protocol used for the endpoint.
+    /// Currently, only \c IPPROTO_UDP and \c IPPROTO_TCP can be specified.
+    /// \param address The (IP) address of the endpoint.
+    /// \param port The transport port number of the endpoint
+    /// \return A pointer to a newly created \c IOEndpoint object.
+    static const IOEndpoint* create(const int protocol,
+                                    const IOAddress& address,
+                                    const unsigned short port);
+};
+
+}      // asiolink
+#endif // __IOENDPOINT_H
+
+// Local Variables: 
+// mode: c++
+// End: 

+ 105 - 0
src/lib/asiolink/iomessage.h

@@ -0,0 +1,105 @@
+// 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 __IOMESSAGE_H
+#define __IOMESSAGE_H 1
+
+// 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 <functional>
+#include <string>
+
+#include <exceptions/exceptions.h>
+
+#include <asiolink/ioendpoint.h>
+#include <asiolink/iosocket.h>
+
+namespace asiolink {
+
+/// \brief The \c IOMessage class encapsulates an incoming message received
+/// on a socket.
+///
+/// An \c IOMessage object represents a tuple of a chunk of data
+/// (a UDP packet or some segment of TCP stream), the socket over which the
+/// data is passed, the information about the other end point of the
+/// communication, and perhaps more.
+///
+/// The current design and interfaces of this class is tentative.
+/// It only provides a minimal level of support that is necessary for
+/// the current implementation of the authoritative server.
+/// A future version of this class will definitely support more.
+class IOMessage {
+    ///
+    /// \name Constructors and Destructor
+    ///
+    /// Note: The copy constructor and the assignment operator are
+    /// intentionally defined as private, making this class non-copyable.
+    //@{
+private:
+    IOMessage(const IOMessage& source);
+    IOMessage& operator=(const IOMessage& source);
+public:
+    /// \brief Constructor from message data
+    ///
+    /// This constructor needs to handle the ASIO \c ip::address class,
+    /// and is intended to be used within this wrapper implementation.
+    /// Once the \c IOMessage object is created, the application can
+    /// get access to the information via the wrapper interface such as
+    /// \c getRemoteAddress().
+    ///
+    /// This constructor never throws an exception.
+    ///
+    /// \param data A pointer to the message data.
+    /// \param data_size The size of the message data in bytes.
+    /// \param io_socket The socket over which the data is given.
+    /// \param remote_endpoint The other endpoint of the socket, that is,
+    /// the sender of the message.
+    IOMessage(const void* data, const size_t data_size,
+              const IOSocket& io_socket, const IOEndpoint& remote_endpoint) :
+        data_(data), data_size_(data_size), io_socket_(io_socket),
+        remote_endpoint_(remote_endpoint)
+    {}
+    //@}
+
+    /// \brief Returns a pointer to the received data.
+    const void* getData() const { return (data_); }
+
+    /// \brief Returns the size of the received data in bytes.
+    size_t getDataSize() const { return (data_size_); }
+
+    /// \brief Returns the socket on which the message arrives.
+    const IOSocket& getSocket() const { return (io_socket_); }
+
+    /// \brief Returns the endpoint that sends the message.
+    const IOEndpoint& getRemoteEndpoint() const { return (remote_endpoint_); }
+
+private:
+    const void* data_;
+    const size_t data_size_;
+    const IOSocket& io_socket_;
+    const IOEndpoint& remote_endpoint_;
+};
+
+
+}      // asiolink
+#endif // __IOMESSAGE_H
+
+// Local Variables: 
+// mode: c++
+// End: 

+ 69 - 0
src/lib/asiolink/iosocket.cc

@@ -0,0 +1,69 @@
+// Copyright (C) 2010  CZ NIC
+// Copyed from other version of auth/asiolink.cc which is:
+// 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 "iosocket.h"
+
+#include <asio.hpp>
+
+using namespace asio;
+
+namespace asiolink {
+
+/// \brief The \c DummySocket class is a concrete derived class of
+/// \c IOSocket that is not associated with any real socket.
+///
+/// This main purpose of this class is tests, where it may be desirable to
+/// instantiate an \c IOSocket object without involving system resource
+/// allocation such as real network sockets.
+class DummySocket : public IOSocket {
+private:
+    DummySocket(const DummySocket& source);
+    DummySocket& operator=(const DummySocket& source);
+public:
+    /// \brief Constructor from the protocol number.
+    ///
+    /// The protocol must validly identify a standard network protocol.
+    /// For example, to specify TCP \c protocol must be \c IPPROTO_TCP.
+    ///
+    /// \param protocol The network protocol number for the socket.
+    DummySocket(const int protocol) : protocol_(protocol) {}
+
+    /// \brief A dummy derived method of \c IOSocket::getNative().
+    ///
+    /// This version of method always returns -1 as the object is not
+    /// associated with a real (native) socket.
+    virtual int getNative() const { return (-1); }
+
+    virtual int getProtocol() const { return (protocol_); }
+private:
+    const int protocol_;
+};
+
+IOSocket&
+IOSocket::getDummyUDPSocket() {
+    static DummySocket socket(IPPROTO_UDP);
+    return (socket);
+}
+
+IOSocket&
+IOSocket::getDummyTCPSocket() {
+    static DummySocket socket(IPPROTO_TCP);
+    return (socket);
+}
+
+}

+ 129 - 0
src/lib/asiolink/iosocket.h

@@ -0,0 +1,129 @@
+// 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 __IOSOCKET_H
+#define __IOSOCKET_H 1
+
+// 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 <functional>
+#include <string>
+
+#include <exceptions/exceptions.h>
+
+namespace asiolink {
+
+/// \brief The \c IOSocket class is an abstract base class to represent
+/// various types of network sockets.
+///
+/// This class is a wrapper for the ASIO socket classes such as
+/// \c ip::tcp::socket and \c ip::udp::socket.
+///
+/// Derived class implementations are completely hidden within the
+/// implementation.  User applications only get access to concrete
+/// \c IOSocket objects via the abstract interfaces.
+///
+/// We may revisit this decision when we generalize the wrapper and more
+/// modules use it.  Also, at that point we may define a separate (visible)
+/// derived class for testing purposes rather than providing factory methods
+/// (i.e., getDummy variants below).
+class IOSocket {
+    ///
+    /// \name Constructors and Destructor
+    ///
+    /// Note: The copy constructor and the assignment operator are
+    /// intentionally defined as private, making this class non-copyable.
+    //@{
+private:
+    IOSocket(const IOSocket& source);
+    IOSocket& operator=(const IOSocket& 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).
+    IOSocket() {}
+public:
+    /// The destructor.
+    virtual ~IOSocket() {}
+    //@}
+
+    /// \brief Return the "native" representation of the socket.
+    ///
+    /// In practice, this is the file descriptor of the socket for
+    /// UNIX-like systems so the current implementation simply uses
+    /// \c int as the type of the return value.
+    /// We may have to need revisit this decision later.
+    ///
+    /// In general, the application should avoid using this method;
+    /// it essentially discloses an implementation specific "handle" that
+    /// can change the internal state of the socket (consider the
+    /// application closes it, for example).
+    /// But we sometimes need to perform very low-level operations that
+    /// requires the native representation.  Passing the file descriptor
+    /// to a different process is one example.
+    /// This method is provided as a necessary evil for such limited purposes.
+    ///
+    /// This method never throws an exception.
+    ///
+    /// \return The native representation of the socket.  This is the socket
+    /// file descriptor for UNIX-like systems.
+    virtual int getNative() const = 0;
+
+    /// \brief Return the transport protocol of the socket.
+    ///
+    /// Currently, it returns \c IPPROTO_UDP for UDP sockets, and
+    /// \c IPPROTO_TCP for TCP sockets.
+    ///
+    /// This method never throws an exception.
+    ///
+    /// \return IPPROTO_UDP for UDP sockets
+    /// \return IPPROTO_TCP for TCP sockets
+    virtual int getProtocol() const = 0;
+
+    /// \brief Return a non-usable "dummy" UDP socket for testing.
+    ///
+    /// This is a class method that returns a "mock" of UDP socket.
+    /// This is not associated with any actual socket, and its only
+    /// responsibility is to return \c IPPROTO_UDP from \c getProtocol().
+    /// The only feasible usage of this socket is for testing so that
+    /// the test code can prepare some "UDP data" even without opening any
+    /// actual socket.
+    ///
+    /// This method never throws an exception.
+    ///
+    /// \return A reference to an \c IOSocket object whose \c getProtocol()
+    /// returns \c IPPROTO_UDP.
+    static IOSocket& getDummyUDPSocket();
+
+    /// \brief Return a non-usable "dummy" TCP socket for testing.
+    ///
+    /// See \c getDummyUDPSocket().  This method is its TCP version.
+    ///
+    /// \return A reference to an \c IOSocket object whose \c getProtocol()
+    /// returns \c IPPROTO_TCP.
+    static IOSocket& getDummyTCPSocket();
+};
+
+}      // asiolink
+#endif // __IOSOCKET_H
+
+// Local Variables: 
+// mode: c++
+// End: 

+ 193 - 0
src/lib/asiolink/tcpdns.cc

@@ -0,0 +1,193 @@
+// 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 <config.h>
+
+#include <unistd.h>             // for some IPC/network system calls
+#include <sys/socket.h>
+#include <netinet/in.h>
+
+#include <asio.hpp>
+#include <asio/ip/address.hpp>
+
+#include <boost/array.hpp>
+#include <boost/shared_ptr.hpp>
+
+#include <dns/buffer.h>
+#include <dns/message.h>
+
+#include <asiolink.h>
+#include <internal/coroutine.h>
+#include <internal/tcpdns.h>
+
+using namespace asio;
+using asio::ip::udp;
+using asio::ip::tcp;
+
+using namespace std;
+using namespace isc::dns;
+
+namespace asiolink {
+/// The following functions implement the \c UDPServer class.
+///
+/// The constructor
+TCPServer::TCPServer(io_service& io_service,
+                     const ip::address& addr, const uint16_t port, 
+                     const SimpleCallback* checkin,
+                     const DNSLookup* lookup,
+                     const DNSAnswer* answer) :
+    io_(io_service), done_(false),
+    checkin_callback_(checkin), lookup_callback_(lookup),
+    answer_callback_(answer)
+{
+    tcp::endpoint endpoint(addr, port);
+    acceptor_.reset(new tcp::acceptor(io_service));
+    acceptor_->open(endpoint.protocol());
+    // Set v6-only (we use a separate instantiation for v4,
+    // otherwise asio will bind to both v4 and v6
+    if (addr.is_v6()) {
+        acceptor_->set_option(ip::v6_only(true));
+    }
+    acceptor_->set_option(tcp::acceptor::reuse_address(true));
+    acceptor_->bind(endpoint);
+    acceptor_->listen();
+}
+
+void
+TCPServer::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.
+    boost::array<const_buffer,2> bufs;
+    OutputBuffer lenbuf(TCP_MESSAGE_LENGTHSIZE);
+
+    CORO_REENTER (this) {
+        do {
+            /// Create a socket to listen for connections
+            socket_.reset(new tcp::socket(acceptor_->get_io_service()));
+
+            /// Wait for new connections. In the event of error,
+            /// try again
+            do {
+                CORO_YIELD acceptor_->async_accept(*socket_, *this);
+            } while (!ec);
+
+            /// 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 connections while the
+            /// handles the one that has just arrived.
+            CORO_FORK io_.post(TCPServer(*this));
+        } while (is_parent());
+
+        /// Instantiate the data buffer that will be used by the
+        /// asynchronous read call.
+        data_.reset(new char[MAX_LENGTH]);
+
+        /// Read the message, in two parts.  First, the message length:
+        CORO_YIELD async_read(*socket_, asio::buffer(data_.get(),
+                              TCP_MESSAGE_LENGTHSIZE), *this);
+        if (ec) {
+            CORO_YIELD return;
+        }
+
+        /// Now read the message itself. (This is done in a different scope
+        /// to allow inline variable declarations.)
+        CORO_YIELD {
+            InputBuffer dnsbuffer((const void *) data_.get(), length);
+            uint16_t msglen = dnsbuffer.readUint16();
+            async_read(*socket_, asio::buffer(data_.get(), msglen), *this);
+        }
+
+        if (ec) {
+            CORO_YIELD return;
+        }
+
+        // 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 TCPEndpoint(socket_->remote_endpoint()));
+        iosock_.reset(new TCPSocket(*socket_));
+        io_message_.reset(new IOMessage(data_.get(), length, *iosock_, *peer_));
+        bytes_ = length;
+
+        // Perform any necessary operations prior to processing the incoming
+        // packet (e.g., checking for queued configuration messages).
+        //
+        // (XXX: it may be a performance issue to have this called for
+        // every single incoming packet; we may wish to throttle it somehow
+        // 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;
+        }
+
+        // Reset or instantiate objects that will be needed by the
+        // DNS lookup and the write call.
+        respbuf_.reset(new OutputBuffer(0));
+        message_.reset(new Message(Message::PARSE));
+
+        // Schedule a DNS lookup, and yield.  When the lookup is
+        // finished, the coroutine will resume immediately after
+        // this point.
+        CORO_YIELD io_.post(AsyncLookup<TCPServer>(*this));
+
+        // 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_, message_, respbuf_);
+
+        // Set up the response, beginning with two length bytes.
+        lenbuf.writeUint16(respbuf_->getLength());
+        bufs[0] = buffer(lenbuf.getData(), lenbuf.getLength());
+        bufs[1] = buffer(respbuf_->getData(), respbuf_->getLength());
+
+        // 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 async_write(*socket_, bufs, *this);
+    }
+}
+
+/// Call the DNS lookup provider.  (Expected to be called by the
+/// AsyncLookup<TCPServer> handler.)
+void
+TCPServer::asyncLookup() {
+    (*lookup_callback_)(*io_message_, 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
+TCPServer::resume(const bool done) {
+    done_ = done;
+    io_.post(*this);
+}
+
+}

+ 39 - 0
src/lib/asiolink/tests/Makefile.am

@@ -0,0 +1,39 @@
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+AM_CPPFLAGS += -I$(top_builddir)/src/lib/dns -I$(top_srcdir)/src/bin
+AM_CPPFLAGS += -I$(top_builddir)/src/lib/cc
+AM_CPPFLAGS += -DTEST_DATA_DIR=\"$(srcdir)/testdata\"
+
+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 = $(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 += 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 += $(SQLITE_LIBS)
+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/asiolink/libasiolink.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)

+ 713 - 0
src/lib/asiolink/tests/asiolink_unittest.cc

@@ -0,0 +1,713 @@
+// 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 <config.h>
+
+#include <string.h>
+
+#include <boost/lexical_cast.hpp>
+#include <boost/bind.hpp>
+
+#include <gtest/gtest.h>
+
+#include <exceptions/exceptions.h>
+
+#include <dns/tests/unittest_util.h>
+
+#include <dns/buffer.h>
+#include <dns/message.h>
+
+#include <asiolink/asiolink.h>
+#include <asiolink/iosocket.h>
+#include <asiolink/internal/tcpdns.h>
+#include <asiolink/internal/udpdns.h>
+
+#include <asio.hpp>
+
+using isc::UnitTestUtil;
+using namespace std;
+using namespace asiolink;
+using namespace isc::dns;
+using namespace asio;
+using asio::ip::udp;
+
+namespace {
+const char* const TEST_SERVER_PORT = "53535";
+const char* const TEST_CLIENT_PORT = "53536";
+const char* const TEST_IPV6_ADDR = "::1";
+const char* const TEST_IPV4_ADDR = "127.0.0.1";
+// This data is intended to be valid as a DNS/TCP-like message: the first
+// two octets encode the length of the rest of the data.  This is crucial
+// for the tests below.
+const uint8_t test_data[] = {0, 4, 1, 2, 3, 4};
+
+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, *"255.255.0.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:255.255.0.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
+// IPv4 or v6, TCP or UDP, and client or server operation.
+struct addrinfo*
+resolveAddress(const int family, const int protocol, const bool client) {
+    const char* const addr = (family == AF_INET6) ?
+        TEST_IPV6_ADDR : TEST_IPV4_ADDR;
+    const char* const port = client ? TEST_CLIENT_PORT : TEST_SERVER_PORT;
+
+    struct addrinfo hints;
+    memset(&hints, 0, sizeof(hints));
+    hints.ai_family = family;
+    hints.ai_socktype = (protocol == IPPROTO_UDP) ? SOCK_DGRAM : SOCK_STREAM;
+    hints.ai_protocol = protocol;
+    hints.ai_flags = AI_NUMERICSERV;
+
+    struct addrinfo* res;
+    const int error = getaddrinfo(addr, port, &hints, &res);
+    if (error != 0) {
+        isc_throw(IOError, "getaddrinfo failed: " << gai_strerror(error));
+    }
+
+    return (res);
+}
+
+// This fixture is a framework for various types of network operations
+// using the ASIO interfaces.  Each test case creates an IOService object,
+// opens a local "client" socket for testing, sends data via the local socket
+// to the service that would run in the IOService object.
+// A mock callback function (an ASIOCallBack object) is registered with the
+// IOService object, so the test code should be able to examine the data
+// received on the server side.  It then checks the received data matches
+// expected parameters.
+// If initialization parameters of the IOService should be modified, the test
+// case can do it using the setDNSService() method.
+// Note: the set of tests in ASIOLinkTest use actual network services and may
+// involve undesirable side effects such as blocking.
+class ASIOLinkTest : public ::testing::Test {
+protected:
+    ASIOLinkTest();
+    ~ASIOLinkTest() {
+        if (res_ != NULL) {
+            freeaddrinfo(res_);
+        }
+        if (sock_ != -1) {
+            close(sock_);
+        }
+        delete dns_service_;
+        delete callback_;
+        delete io_service_;
+    }
+
+    // Send a test UDP packet to a mock server
+    void sendUDP(const int family) {
+        res_ = resolveAddress(family, IPPROTO_UDP, false);
+
+        sock_ = socket(res_->ai_family, res_->ai_socktype, res_->ai_protocol);
+        if (sock_ < 0) {
+            isc_throw(IOError, "failed to open test socket");
+        }
+        const int cc = sendto(sock_, test_data, sizeof(test_data), 0,
+                              res_->ai_addr, res_->ai_addrlen);
+        if (cc != sizeof(test_data)) {
+            isc_throw(IOError, "unexpected sendto result: " << cc);
+        }
+        io_service_->run();
+    }
+
+    // Send a test TCP packet to a mock server
+    void sendTCP(const int family) {
+        res_ = resolveAddress(family, IPPROTO_TCP, false);
+
+        sock_ = socket(res_->ai_family, res_->ai_socktype, res_->ai_protocol);
+        if (sock_ < 0) {
+            isc_throw(IOError, "failed to open test socket");
+        }
+        if (connect(sock_, res_->ai_addr, res_->ai_addrlen) < 0) {
+            isc_throw(IOError, "failed to connect to the test server");
+        }
+        const int cc = send(sock_, test_data, sizeof(test_data), 0);
+        if (cc != sizeof(test_data)) {
+            isc_throw(IOError, "unexpected send result: " << cc);
+        }
+        io_service_->run();
+    }
+
+    // Receive a UDP packet from a mock server; used for testing
+    // recursive lookup.  The caller must place a RecursiveQuery 
+    // on the IO Service queue before running this routine.
+    void recvUDP(const int family, void* buffer, size_t& size) {
+        res_ = resolveAddress(family, IPPROTO_UDP, true);
+
+        sock_ = socket(res_->ai_family, res_->ai_socktype, res_->ai_protocol);
+        if (sock_ < 0) {
+            isc_throw(IOError, "failed to open test socket");
+        }
+
+        if (bind(sock_, res_->ai_addr, res_->ai_addrlen) < 0) {
+            isc_throw(IOError, "bind failed: " << strerror(errno));
+        }
+
+        // The IO service queue should have a RecursiveQuery object scheduled
+        // to run at this point.  This call will cause it to begin an
+        // async send, then return.
+        io_service_->run_one();
+
+        // ... and this one will block until the send has completed
+        io_service_->run_one();
+
+        // Now we attempt to recv() whatever was sent
+        const int ret = recv(sock_, buffer, size, MSG_DONTWAIT);
+        if (ret < 0) {
+            isc_throw(IOError, "recvfrom failed");
+        }
+        
+        // Pass the message size back via the size parameter
+        size = ret;
+    }
+
+
+    // Set up an IO Service queue using the specified address
+    void setDNSService(const char& address) {
+        delete dns_service_;
+        dns_service_ = NULL;
+        delete io_service_;
+        io_service_ = new IOService();
+        callback_ = new ASIOCallBack(this);
+        dns_service_ = new DNSService(*io_service_, *TEST_SERVER_PORT, address, callback_, NULL, NULL);
+    }
+
+    // Set up an IO Service queue using the "any" address, on IPv4 if
+    // 'use_ipv4' is true and on IPv6 if 'use_ipv6' is true.
+    void setDNSService(const bool use_ipv4, const bool use_ipv6) {
+        delete dns_service_;
+        dns_service_ = NULL;
+        delete io_service_;
+        io_service_ = new IOService();
+        callback_ = new ASIOCallBack(this);
+        dns_service_ = new DNSService(*io_service_, *TEST_SERVER_PORT, use_ipv4, use_ipv6, callback_,
+                                      NULL, NULL);
+    }
+
+    // Set up empty DNS Service
+    // Set up an IO Service queue without any addresses
+    void setDNSService() {
+        delete dns_service_;
+        dns_service_ = NULL;
+        delete io_service_;
+        io_service_ = new IOService();
+        callback_ = new ASIOCallBack(this);
+        dns_service_ = new DNSService(*io_service_, callback_, NULL, NULL);
+    }
+
+    // Run a simple server test, on either IPv4 or IPv6, and over either
+    // UDP or TCP.  Calls the sendUDP() or sendTCP() methods, which will
+    // start the IO Service queue.  The UDPServer or TCPServer that was
+    // created by setIOService() will receive the test packet and issue a
+    // callback, which enables us to check that the data it received
+    // matches what we sent.
+    void doTest(const int family, const int protocol) {
+        if (protocol == IPPROTO_UDP) {
+            sendUDP(family);
+        } else {
+            sendTCP(family);
+        }
+
+        // There doesn't seem to be an effective test for the validity of
+        // 'native'.
+        // One thing we are sure is it must be different from our local socket.
+        EXPECT_NE(sock_, callback_native_);
+        EXPECT_EQ(protocol, callback_protocol_);
+        EXPECT_EQ(family == AF_INET6 ? TEST_IPV6_ADDR : TEST_IPV4_ADDR,
+                  callback_address_);
+
+        const uint8_t* expected_data =
+            protocol == IPPROTO_UDP ? test_data : test_data + 2;
+        const size_t expected_datasize =
+            protocol == IPPROTO_UDP ? sizeof(test_data) :
+            sizeof(test_data) - 2;
+        EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, &callback_data_[0],
+                            callback_data_.size(),
+                            expected_data, expected_datasize);
+    }
+
+protected:
+    // This is a nonfunctional mockup of a DNSServer object.  Its purpose
+    // is to resume after a recursive query or other asynchronous call
+    // has completed.
+    class MockServer : public DNSServer {
+    public:
+        explicit MockServer(asio::io_service& io_service,
+                            const asio::ip::address& addr, const uint16_t port,
+                            SimpleCallback* checkin = NULL,
+                            DNSLookup* lookup = NULL,
+                            DNSAnswer* answer = NULL) :
+            io_(io_service),
+            message_(new Message(Message::PARSE)),
+            respbuf_(new OutputBuffer(0)),
+            checkin_(checkin), lookup_(lookup), answer_(answer)
+        {}
+
+        void operator()(asio::error_code ec = asio::error_code(),
+                        size_t length = 0)
+        {}
+
+        void resume(const bool done) {
+            done_ = done;
+            io_.post(*this);
+        }
+
+        DNSServer* clone() {
+            MockServer* s = new MockServer(*this);
+            return (s);
+        }
+
+        inline void asyncLookup() {
+            if (lookup_) {
+                (*lookup_)(*io_message_, message_, respbuf_, this);
+            }
+        }
+
+    protected:
+        asio::io_service& io_;
+        bool done_;
+
+    private:
+        // Currently unused; these will be used for testing
+        // asynchronous lookup calls via the asyncLookup() method
+        boost::shared_ptr<asiolink::IOMessage> io_message_;
+        isc::dns::MessagePtr message_;
+        isc::dns::OutputBufferPtr respbuf_;
+
+        // Callback functions provided by the caller
+        const SimpleCallback* checkin_;
+        const DNSLookup* lookup_;
+        const DNSAnswer* answer_;
+    };
+
+    // This version of mock server just stops the io_service when it is resumed
+    class MockServerStop : public MockServer {
+        public:
+            explicit MockServerStop(asio::io_service& io_service, bool* done) :
+                MockServer(io_service, asio::ip::address(), 0),
+                done_(done)
+            {}
+
+            void resume(const bool done) {
+                *done_ = done;
+                io_.stop();
+            }
+
+            DNSServer* clone() {
+                return (new MockServerStop(*this));
+            }
+        private:
+            bool* done_;
+    };
+
+private:
+    class ASIOCallBack : public SimpleCallback {
+    public:
+        ASIOCallBack(ASIOLinkTest* test_obj) : test_obj_(test_obj) {}
+        void operator()(const IOMessage& io_message) const {
+            test_obj_->callBack(io_message);
+        }
+    private:
+        ASIOLinkTest* test_obj_;
+    };
+    void callBack(const IOMessage& io_message) {
+        callback_protocol_ = io_message.getSocket().getProtocol();
+        callback_native_ = io_message.getSocket().getNative();
+        callback_address_ =
+            io_message.getRemoteEndpoint().getAddress().toText();
+        callback_data_.assign(
+            static_cast<const uint8_t*>(io_message.getData()),
+            static_cast<const uint8_t*>(io_message.getData()) +
+            io_message.getDataSize());
+        io_service_->stop();
+    }
+protected:
+    // We use a pointer for io_service_, because for some tests we
+    // need to recreate a new one within one onstance of this class
+    IOService* io_service_;
+    DNSService* dns_service_;
+    ASIOCallBack* callback_;
+    int callback_protocol_;
+    int callback_native_;
+    string callback_address_;
+    vector<uint8_t> callback_data_;
+    int sock_;
+private:
+    struct addrinfo* res_;
+};
+
+ASIOLinkTest::ASIOLinkTest() :
+    dns_service_(NULL), callback_(NULL), sock_(-1), res_(NULL)
+{
+    io_service_ = new IOService();
+    setDNSService(true, true);
+}
+
+TEST_F(ASIOLinkTest, v6UDPSend) {
+    doTest(AF_INET6, IPPROTO_UDP);
+}
+
+TEST_F(ASIOLinkTest, v6TCPSend) {
+    doTest(AF_INET6, IPPROTO_TCP);
+}
+
+TEST_F(ASIOLinkTest, v4UDPSend) {
+    doTest(AF_INET, IPPROTO_UDP);
+}
+
+TEST_F(ASIOLinkTest, v4TCPSend) {
+    doTest(AF_INET, IPPROTO_TCP);
+}
+
+TEST_F(ASIOLinkTest, v6UDPSendSpecific) {
+    // Explicitly set a specific address to be bound to the socket.
+    // The subsequent test does not directly ensures the underlying socket
+    // is bound to the expected address, but the success of the tests should
+    // reasonably suggest it works as intended.
+    // Specifying an address also implicitly means the service runs in a
+    // single address-family mode.  In tests using TCP we can confirm that
+    // by trying to make a connection and seeing a failure.  In UDP, it'd be
+    // more complicated because we need to use a connected socket and catch
+    // an error on a subsequent read operation.  We could do it, but for
+    // simplicity we only tests the easier cases for now.
+
+    setDNSService(*TEST_IPV6_ADDR);
+    doTest(AF_INET6, IPPROTO_UDP);
+}
+
+TEST_F(ASIOLinkTest, v6TCPSendSpecific) {
+    setDNSService(*TEST_IPV6_ADDR);
+    doTest(AF_INET6, IPPROTO_TCP);
+
+    EXPECT_THROW(sendTCP(AF_INET), IOError);
+}
+
+TEST_F(ASIOLinkTest, v4UDPSendSpecific) {
+    setDNSService(*TEST_IPV4_ADDR);
+    doTest(AF_INET, IPPROTO_UDP);
+}
+
+TEST_F(ASIOLinkTest, v4TCPSendSpecific) {
+    setDNSService(*TEST_IPV4_ADDR);
+    doTest(AF_INET, IPPROTO_TCP);
+
+    EXPECT_THROW(sendTCP(AF_INET6), IOError);
+}
+
+TEST_F(ASIOLinkTest, v6AddServer) {
+    setDNSService();
+    dns_service_->addServer(*TEST_SERVER_PORT, TEST_IPV6_ADDR);
+    doTest(AF_INET6, IPPROTO_TCP);
+
+    EXPECT_THROW(sendTCP(AF_INET), IOError);
+}
+
+TEST_F(ASIOLinkTest, v4AddServer) {
+    setDNSService();
+    dns_service_->addServer(*TEST_SERVER_PORT, TEST_IPV4_ADDR);
+    doTest(AF_INET, IPPROTO_TCP);
+
+    EXPECT_THROW(sendTCP(AF_INET6), IOError);
+}
+
+TEST_F(ASIOLinkTest, DISABLED_clearServers) {
+    // FIXME: Enable when clearServers actually close the sockets
+    //    See #388
+    setDNSService();
+    dns_service_->clearServers();
+
+    EXPECT_THROW(sendTCP(AF_INET), IOError);
+    EXPECT_THROW(sendTCP(AF_INET6), IOError);
+}
+
+TEST_F(ASIOLinkTest, v6TCPOnly) {
+    // Open only IPv6 TCP socket.  A subsequent attempt of establishing an
+    // IPv4/TCP connection should fail.  See above for why we only test this
+    // for TCP.
+    setDNSService(false, true);
+    EXPECT_THROW(sendTCP(AF_INET), IOError);
+}
+
+TEST_F(ASIOLinkTest, v4TCPOnly) {
+    setDNSService(true, false);
+    EXPECT_THROW(sendTCP(AF_INET6), IOError);
+}
+
+vector<pair<string, uint16_t> >
+singleAddress(const string &address, uint16_t port) {
+    vector<pair<string, uint16_t> > result;
+    result.push_back(pair<string, uint16_t>(address, port));
+    return (result);
+}
+
+TEST_F(ASIOLinkTest, recursiveSetupV4) {
+    setDNSService(true, false);
+    uint16_t port = boost::lexical_cast<uint16_t>(TEST_CLIENT_PORT);
+    EXPECT_NO_THROW(RecursiveQuery(*dns_service_, singleAddress(TEST_IPV6_ADDR,
+        port)));
+}
+
+TEST_F(ASIOLinkTest, recursiveSetupV6) {
+    setDNSService(false, true);
+    uint16_t port = boost::lexical_cast<uint16_t>(TEST_CLIENT_PORT);
+    EXPECT_NO_THROW(RecursiveQuery(*dns_service_, singleAddress(TEST_IPV6_ADDR,
+        port)));
+}
+
+// XXX:
+// This is very inadequate unit testing.  It should be generalized into
+// a routine that can do this with variable address family, address, and
+// port, and with the various callbacks defined in such a way as to ensure
+// full code coverage including error cases.
+TEST_F(ASIOLinkTest, recursiveSend) {
+    setDNSService(true, false);
+    asio::io_service& io = io_service_->get_io_service();
+
+    // Note: We use the test prot plus one to ensure we aren't binding
+    // to the same port as the actual server
+    uint16_t port = boost::lexical_cast<uint16_t>(TEST_CLIENT_PORT);
+    asio::ip::address addr = asio::ip::address::from_string(TEST_IPV4_ADDR);
+
+    MockServer server(io, addr, port, NULL, NULL, NULL);
+    RecursiveQuery rq(*dns_service_, singleAddress(TEST_IPV4_ADDR, port));
+
+    Question q(Name("example.com"), RRClass::IN(), RRType::TXT());
+    OutputBufferPtr buffer(new OutputBuffer(0));
+    rq.sendQuery(q, buffer, &server);
+
+    char data[4096];
+    size_t size = sizeof(data);
+    EXPECT_NO_THROW(recvUDP(AF_INET, data, size));
+
+    Message m(Message::PARSE);
+    InputBuffer ibuf(data, size);
+
+    // Make sure we can parse the message that was sent
+    EXPECT_NO_THROW(m.parseHeader(ibuf));
+    EXPECT_NO_THROW(m.fromWire(ibuf));
+
+    // Check that the question sent matches the one we wanted
+    QuestionPtr q2 = *m.beginQuestion();
+    EXPECT_EQ(q.getName(), q2->getName());
+    EXPECT_EQ(q.getType(), q2->getType());
+    EXPECT_EQ(q.getClass(), q2->getClass());
+}
+
+void
+receive_and_inc(udp::socket* socket, int* num) {
+    (*num) ++;
+    static char inbuff[512];
+    socket->async_receive(asio::buffer(inbuff, 512),
+        boost::bind(receive_and_inc, socket, num));
+}
+
+// Test it tries the correct amount of times before giving up
+TEST_F(ASIOLinkTest, recursiveTimeout) {
+    // Prepare the service (we do not use the common setup, we do not answer
+    setDNSService();
+    asio::io_service& service = io_service_->get_io_service();
+
+    // Prepare the socket
+    uint16_t port = boost::lexical_cast<uint16_t>(TEST_CLIENT_PORT);
+    udp::socket socket(service, udp::v4());
+    socket.set_option(socket_base::reuse_address(true));
+    socket.bind(udp::endpoint(ip::address::from_string(TEST_IPV4_ADDR), port));
+    // And count the answers
+    int num = -1; // One is counted before the receipt of the first one
+    receive_and_inc(&socket, &num);
+
+    // Prepare the server
+    bool done(true);
+    MockServerStop server(service, &done);
+
+    // Do the answer
+    RecursiveQuery query(*dns_service_, singleAddress(TEST_IPV4_ADDR, port),
+        10, 2);
+    Question question(Name("example.net"), RRClass::IN(), RRType::A());
+    OutputBufferPtr buffer(new OutputBuffer(0));
+    query.sendQuery(question, buffer, &server);
+
+    // Run the test
+    service.run();
+
+    // The query should fail
+    EXPECT_FALSE(done);
+    EXPECT_EQ(3, num);
+}
+
+}

+ 28 - 0
src/lib/asiolink/tests/run_unittests.cc

@@ -0,0 +1,28 @@
+// Copyright (C) 2009  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 <gtest/gtest.h>
+
+#include <dns/tests/unittest_util.h>
+
+int
+main(int argc, char* argv[])
+{
+    ::testing::InitGoogleTest(&argc, argv);
+    isc::UnitTestUtil::addDataPath(TEST_DATA_DIR);
+
+    return (RUN_ALL_TESTS());
+}

+ 145 - 0
src/lib/asiolink/tests/udpdns_unittest.cc

@@ -0,0 +1,145 @@
+// Copyright (C) 2010  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
+// 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 <gtest/gtest.h>
+#include <asio.hpp>
+#include <boost/bind.hpp>
+#include <cstdlib>
+
+#include <dns/question.h>
+
+#include <asiolink/internal/udpdns.h>
+
+using namespace asio;
+using namespace isc::dns;
+using asio::ip::udp;
+
+namespace {
+
+const asio::ip::address TEST_HOST(asio::ip::address::from_string("127.0.0.1"));
+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::UDPQuery.
+class UDPQueryTest : public ::testing::Test,
+    public asiolink::UDPQuery::Callback
+{
+    public:
+        // Expected result of the callback
+        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 UDPQuery will store response
+        OutputBufferPtr buffer_;
+        // The query we are testing
+        asiolink::UDPQuery query_;
+
+        UDPQueryTest() :
+            run_(false),
+            question_(Name("example.net"), RRClass::IN(), RRType::A()),
+            buffer_(new OutputBuffer(512)),
+            query_(service_, question_, asiolink::IOAddress(TEST_HOST),
+                TEST_PORT, buffer_, this, 100)
+        { }
+
+        // This is the callback's (), so it can be called.
+        void operator()(asiolink::UDPQuery::Result result) {
+            // We check the query returns the correct result
+            EXPECT_EQ(expected_, result);
+            // Check it is called only once
+            EXPECT_FALSE(run_);
+            // And mark the callback was called
+            run_ = true;
+        }
+        // A response handler, pretending to be remote DNS server
+        void respond(udp::endpoint* remote, udp::socket* socket) {
+            // Some data came, just send something back.
+            socket->send_to(asio::buffer(TEST_DATA, sizeof TEST_DATA),
+                *remote);
+            socket->close();
+        }
+};
+
+/*
+ * Test that when we run the query and stop it after it was run,
+ * it returns "stopped" correctly.
+ *
+ * That is why stop() is posted to the service_ as well instead
+ * of calling it.
+ */
+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::UDPQuery::stop, query_,
+        asiolink::UDPQuery::STOPPED));
+    // Run both of them
+    service_.run();
+    EXPECT_TRUE(run_);
+}
+
+/*
+ * Test that when we queue the query to service_ and call stop()
+ * before it gets executed, it acts sanely as well (eg. has the
+ * same result as running stop() after - calls the callback).
+ */
+TEST_F(UDPQueryTest, prematureStop) {
+    expected_ = asiolink::UDPQuery::STOPPED;
+    // Stop before it is started
+    query_.stop();
+    service_.post(query_);
+    service_.run();
+    EXPECT_TRUE(run_);
+}
+
+/*
+ * Test that it will timeout when no answer will arrive.
+ */
+TEST_F(UDPQueryTest, timeout) {
+    expected_ = asiolink::UDPQuery::TIME_OUT;
+    service_.post(query_);
+    service_.run();
+    EXPECT_TRUE(run_);
+}
+
+/*
+ * Test that it will succeed when we fake an answer and
+ * stores the same data we send.
+ *
+ * This is done through a real socket on loopback address.
+ */
+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(
+        &UDPQueryTest::respond, this, &remote, &socket));
+    service_.post(query_);
+    service_.run();
+    EXPECT_TRUE(run_);
+    ASSERT_EQ(sizeof TEST_DATA, buffer_->getLength());
+    EXPECT_EQ(0, memcmp(TEST_DATA, buffer_->getData(), sizeof TEST_DATA));
+}
+
+}

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

@@ -0,0 +1,315 @@
+// 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 <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(proto, 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));
+        message_.reset(new Message(Message::PARSE));
+
+        // 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));
+
+        // 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_, 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_, 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);
+}
+
+// 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);
+        }
+    }
+}
+
+}

+ 1 - 1
src/lib/cc/session.cc

@@ -171,7 +171,7 @@ SessionImpl::readData(void* data, size_t datalen) {
         asio::async_read(socket_, asio::buffer(data, datalen),
                          boost::bind(&setResult, &read_result, _1));
         asio::deadline_timer timer(socket_.io_service());
-    
+
         if (getTimeout() != 0) {
             timer.expires_from_now(boost::posix_time::milliseconds(getTimeout()));
             timer.async_wait(boost::bind(&setResult, &timer_result, _1));

+ 2 - 0
src/lib/datasrc/Makefile.am

@@ -15,4 +15,6 @@ libdatasrc_la_SOURCES += static_datasrc.h static_datasrc.cc
 libdatasrc_la_SOURCES += sqlite3_datasrc.h sqlite3_datasrc.cc
 libdatasrc_la_SOURCES += query.h query.cc
 libdatasrc_la_SOURCES += cache.h cache.cc
+libdatasrc_la_SOURCES += rbtree.h 
 libdatasrc_la_SOURCES += zonetable.h zonetable.cc
+libdatasrc_la_SOURCES += memory_datasrc.h memory_datasrc.cc

+ 56 - 0
src/lib/datasrc/memory_datasrc.cc

@@ -0,0 +1,56 @@
+// 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 <dns/name.h>
+#include <datasrc/memory_datasrc.h>
+
+using namespace std;
+using namespace isc::dns;
+
+namespace isc {
+namespace datasrc {
+
+/// Implementation details for \c MemoryDataSrc hidden from the public
+/// interface.
+///
+/// For now, \c MemoryDataSrc only contains a \c ZoneTable object, which
+/// consists of (pointers to) \c MemoryZone objects, we may add more
+/// member variables later for new features.
+struct MemoryDataSrc::MemoryDataSrcImpl {
+    ZoneTable zone_table;
+};
+
+MemoryDataSrc::MemoryDataSrc() : impl_(new MemoryDataSrcImpl)
+{}
+
+MemoryDataSrc::~MemoryDataSrc() {
+    delete impl_;
+}
+
+result::Result
+MemoryDataSrc::addZone(ZonePtr zone) {
+    if (!zone) {
+        isc_throw(InvalidParameter,
+                  "Null pointer is passed to MemoryDataSrc::addZone()");
+    }
+    return (impl_->zone_table.addZone(zone));
+}
+
+MemoryDataSrc::FindResult
+MemoryDataSrc::findZone(const isc::dns::Name& name) const {
+    return (FindResult(impl_->zone_table.findZone(name).code,
+                       impl_->zone_table.findZone(name).zone));
+}
+} // end of namespace datasrc
+} // end of namespace dns

+ 151 - 0
src/lib/datasrc/memory_datasrc.h

@@ -0,0 +1,151 @@
+// 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 __MEMORY_DATA_SOURCE_H
+#define __MEMORY_DATA_SOURCE_H 1
+
+#include <datasrc/zonetable.h>
+
+namespace isc {
+namespace dns {
+class Name;
+};
+
+namespace datasrc {
+
+/// \brief A data source that uses in memory dedicated backend.
+///
+/// The \c MemoryDataSrc class represents a data source and provides a
+/// basic interface to help DNS lookup processing. For a given domain
+/// name, its \c findZone() method searches the in memory dedicated backend
+/// for the zone that gives a longest match against that name.
+///
+/// The in memory dedicated backend are assumed to be of the same RR class,
+/// but the \c MemoryDataSrc class does not enforce the assumption through
+/// its interface.
+/// For example, the \c addZone() method does not check if the new zone is of
+/// the same RR class as that of the others already in the dedicated backend.
+/// It is caller's responsibility to ensure this assumption.
+///
+/// <b>Notes to developer:</b>
+///
+/// For now, we don't make it a derived class of AbstractDataSrc because the
+/// interface is so different (we'll eventually consider this as part of the
+/// generalization work).
+///
+/// The addZone() method takes a (Boost) shared pointer because it would be
+/// inconvenient to require the caller to maintain the ownership of zones,
+/// while it wouldn't be safe to delete unnecessary zones inside the dedicated
+/// backend.
+///
+/// The findZone() method takes a domain name and returns the best matching \c
+/// MemoryZone in the form of (Boost) shared pointer, so that it can provide
+/// the general interface for all data sources.
+///
+/// Currently, \c FindResult::zone is immutable for safety.
+/// In future versions we may want to make it changeable.  For example,
+/// we may want to allow configuration update on an existing zone.
+class MemoryDataSrc {
+public:
+    /// \brief A helper structure to represent the search result of
+    /// <code>MemoryDataSrc::find()</code>.
+    ///
+    /// This is a straightforward pair of the result code and a share pointer
+    /// to the found zone to represent the result of \c find().
+    /// We use this in order to avoid overloading the return value for both
+    /// the result code ("success" or "not found") and the found object,
+    /// i.e., avoid using \c NULL to mean "not found", etc.
+    ///
+    /// This is a simple value class with no internal state, so for
+    /// convenience we allow the applications to refer to the members
+    /// directly.
+    ///
+    /// See the description of \c find() for the semantics of the member
+    /// variables.
+    struct FindResult {
+        FindResult(result::Result param_code, const ConstZonePtr param_zone) :
+            code(param_code), zone(param_zone)
+        {}
+        const result::Result code;
+        const ConstZonePtr zone;
+    };
+
+    ///
+    /// \name Constructors and Destructor.
+    ///
+    /// \b Note:
+    /// The copy constructor and the assignment operator are intentionally
+    /// defined as private, making this class non copyable.
+    //@{
+private:
+    MemoryDataSrc(const MemoryDataSrc& source);
+    MemoryDataSrc& operator=(const MemoryDataSrc& source);
+
+public:
+    /// Default constructor.
+    ///
+    /// This constructor internally involves resource allocation, and if
+    /// it fails, a corresponding standard exception will be thrown.
+    /// It never throws an exception otherwise.
+    MemoryDataSrc();
+
+    /// The destructor.
+    ~MemoryDataSrc();
+    //@}
+
+    /// Add a \c Zone to the \c MemoryDataSrc.
+    ///
+    /// \c Zone must not be associated with a NULL pointer; otherwise
+    /// an exception of class \c InvalidParameter will be thrown.
+    /// If internal resource allocation fails, a corresponding standard
+    /// exception will be thrown.
+    /// This method never throws an exception otherwise.
+    ///
+    /// \param zone A \c Zone object to be added.
+    /// \return \c result::SUCCESS If the zone is successfully
+    /// added to the memory data source.
+    /// \return \c result::EXIST The memory data source already
+    /// stores a zone that has the same origin.
+    result::Result addZone(ZonePtr zone);
+
+    /// Find a \c Zone that best matches the given name in the \c MemoryDataSrc.
+    ///
+    /// It searches the internal storage for a \c Zone that gives the
+    /// longest match against \c name, and returns the result in the
+    /// form of a \c FindResult object as follows:
+    /// - \c code: The result code of the operation.
+    ///   - \c result::SUCCESS: A zone that gives an exact match
+    //    is found
+    ///   - \c result::PARTIALMATCH: A zone whose origin is a
+    //    super domain of \c name is found (but there is no exact match)
+    ///   - \c result::NOTFOUND: For all other cases.
+    /// - \c zone: A <Boost> shared pointer to the found \c Zone object if one
+    //  is found; otherwise \c NULL.
+    ///
+    /// This method never throws an exception.
+    ///
+    /// \param name A domain name for which the search is performed.
+    /// \return A \c FindResult object enclosing the search result (see above).
+    FindResult findZone(const isc::dns::Name& name) const;
+
+private:
+    struct MemoryDataSrcImpl;
+    MemoryDataSrcImpl* impl_;
+};
+}
+}
+#endif  // __DATA_SOURCE_MEMORY_H
+// Local Variables:
+// mode: c++
+// End:

+ 687 - 0
src/lib/datasrc/rbtree.h

@@ -0,0 +1,687 @@
+// 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 _RBTREE_H
+#define _RBTREE_H 1
+
+//! \file datasrc/rbtree.h
+///
+/// \note The purpose of the RBTree is to provide a generic map with 
+/// domain names as the key that can be used by various BIND 10 modules or
+/// even by other applications.  However, because of some unresolved design
+/// issue, the design and interface are not fixed, and RBTree isn't ready to
+/// be used as a base data structure by other modules.
+
+#include <dns/name.h>
+#include <boost/utility.hpp>
+#include <boost/shared_ptr.hpp>
+#include <exception>
+#include <ostream>
+#include <algorithm>
+
+namespace isc {
+namespace datasrc {
+
+namespace helper {
+
+/// Helper function to remove the base domain from super domain
+///
+/// the precondition of this function is the super_name contains the
+/// sub_name so \code Name a("a.b.c"); Name b("b.c");
+/// Name c = a - b; \\c will be "a" \endcode
+///
+/// \note function in this namespace is not intended to be used outside.
+inline isc::dns::Name
+operator-(const isc::dns::Name& super_name, const isc::dns::Name& sub_name) {
+    return (super_name.split(0, super_name.getLabelCount() -
+                             sub_name.getLabelCount()));
+}
+}
+
+template <typename T>
+class RBTree;
+/// \brief \c RBNode use by RBTree to store any data related to one domain name
+
+/// It has two roles, the first one is as one node in the \c RBTree,
+/// the second one is to store the data related to one domain name and maintain
+/// the domain name hierarchy struct in one domain name space.
+/// As for the first role, it has left, right, parent and color members
+/// which is used to keep the balance of the \c RBTree.
+/// As for the second role, \c RBNode use down pointer to refer to all its sub
+/// domains, so the name of current node always relative to the up node. since
+/// we only has down pointer without up pointer, so we can only walk down from
+/// top domain to sub domain.
+/// One special kind of node is non-terminal node
+/// which has subdomains with RRset but itself doesn't have any RRsets.
+///
+/// \note \c RBNode basically used internally by RBTree, it is meaningless to
+/// inherited from it or create it without \c RBTree.
+template <typename T>
+class RBNode : public boost::noncopyable {
+public:
+    /// only \c RBTree can create and destroy \c RBNode
+    friend class RBTree<T>;
+    typedef boost::shared_ptr<T> NodeDataPtr;
+
+    /// \name Deonstructor
+    /// \note it's seems a little strange that constructor is private
+    /// but deconstructor left public, the reason is for some smart pointer
+    /// like std::auto_ptr, they needs to delete RBNode in sometimes, but
+    /// \code delete *pointer_to_node \codeend shouldn't be called directly
+    //@{
+    ~RBNode();
+    //@}
+
+    /// \name Test functions
+    //@{
+    /// \brief return the name of current node, it's relative to its top node
+    ///
+    /// To get the absolute name of one node, the node path from the top node
+    /// to current node has to be recorded
+    const isc::dns::Name& getName() const { return (name_); }
+
+    /// \brief return the data store in this node
+    /// \note, since the data is managed by RBNode, developer should not
+    /// free the pointer
+    NodeDataPtr& getData() { return (data_); }
+    /// \brief return the data stored in this node, read-only version
+    const NodeDataPtr& getData() const { return (data_); }
+
+    /// \brief return whether the node has related data
+    /// \note it's meaningless has empty \c RBNode in one RBTree, the only
+    /// exception is for non-terminal node which has sub domain nodes who
+    /// has data(rrset)
+    bool isEmpty() const { return (data_.get() == NULL); }
+    //@}
+
+    /// \name Modify functions
+    //@{
+    /// \breif set the data stored in the node
+    void setData(const NodeDataPtr& data) { data_ = data; }
+    //@}
+
+
+private:
+    /// \brief Define rbnode color
+    enum RBNodeColor {BLACK, RED};
+
+    /// \name Constructors
+    /// \note \c Single RBNode is meaningless without living inside one \c RBTree
+    /// the creation and destroy of one \c RBNode is handle by host \c RBTree, so
+    /// the constructors and destructor of \c RBNode is left private
+    //@{
+    /// \brief Default constructor.
+    ///
+    /// This constructor is provided specifically for generating a special
+    /// "null" node, and is intended be used only internally.
+    RBNode();
+
+    /// \brief Constructor from the node name.
+    ///
+    /// \param name The domain name corresponding to the node.
+    RBNode(const isc::dns::Name& name);
+    //@}
+
+    /// This is a factory class method of a special singleton null node.
+    static RBNode<T>* NULL_NODE() {
+        static RBNode<T> null_node;
+        return (&null_node);
+    }
+
+    /// data to maintain the rbtree balance
+    RBNode<T>*  parent_;
+    RBNode<T>*  left_;
+    RBNode<T>*  right_;
+    RBNodeColor color_;
+
+    isc::dns::Name     name_;
+    NodeDataPtr       data_;
+    /// the down pointer points to the root node of sub domains of current
+    /// domain
+    /// \par Adding down pointer to \c RBNode is for two purpose:
+    /// \li Accelerate the search process, with sub domain tree, it split the
+    /// big flat tree into several hierarchy trees
+    /// \li It save memory useage, so same label won't be saved several times
+    RBNode<T>*  down_;
+};
+
+
+// typically each node should has a name associate with it
+// this construction is only used to create \c NULLNODE
+template <typename T>
+RBNode<T>::RBNode() :
+    parent_(this),
+    left_(this),
+    right_(this),
+    color_(BLACK),
+    // dummy name, the value doesn't matter:
+    name_(isc::dns::Name::ROOT_NAME()),
+    down_(this)
+{
+}
+
+template <typename T>
+RBNode<T>::RBNode(const isc::dns::Name& name) :
+    parent_(NULL_NODE()),
+    left_(NULL_NODE()),
+    right_(NULL_NODE()),
+    color_(RED),
+    name_(name),
+    down_(NULL_NODE())
+{
+}
+
+
+template <typename T>
+RBNode<T>::~RBNode() {
+}
+/// \brief \c RBTree class represents all the domains with the same suffix,
+/// so it can be used to store the domains in one zone.
+///
+/// \c RBTree is a generic red black tree, and contains all the nodes with
+/// the same suffix, since each name may have sub domain names
+/// so \c RBTree is a recursive data structure namely tree in tree.
+/// So for one zone, several RBTrees may be involved. But from outside, the sub
+/// tree is opaque for end users.
+///
+/// \c RBTree split the domain space into hierarchy red black trees, nodes in one
+/// tree has the same base name. The benefit of this struct is that:
+/// - enhance the query performace compared with one big flat red black tree
+/// - decrase the memory footprint to save common labels only once.
+
+/*
+/// \verbatim
+/// with the following names:
+///     a       x.d.e.f     o.w.y.d.e.f
+///     b       z.d.e.f     p.w.y.d.e.f
+///     c       g.h         q.w.y.d.e.f
+///     the tree will looks like:
+///                               b
+///                             /   \
+///                            a    d.e.f
+///                                   /|\
+///                                  c | g.h
+///                                    |
+///                                   w.y
+///                                   /|\
+///                                  x | z
+///                                    |
+///                                    p
+///                                   / \
+///                                  o   q
+/// \endverbatim
+/// \note open problems:
+/// - current find funciton only return non-empty nodes, so there is no difference
+///   between find one not exist name with empty non-terminal nodes, but in DNS query
+///   logic, they are different
+/// \todo
+/// - add remove interface
+/// - add iterator to iterate the whole rbtree while may needed by axfr
+/// - since \c RBNode only has down pointer without up pointer, the node path during finding
+///   should be recorded for later use
+*/
+template <typename T>
+class RBTree : public boost::noncopyable {
+    friend class RBNode<T>;
+public:
+    /// \brief The return value for the \c find() insert() and erase() method
+    enum Result {
+        SUCCEED, //insert or erase succeed
+        EXACTMATCH, //find the target name
+        PARTIALMATCH, //find part of target name
+        NOTFOUND,  // for find function means no related name found
+                   // for erase function means erase not exist name
+        ALREADYEXIST, //for insert operation, the name to insert already exist
+    };
+
+    /// \name Constructor and Destructor
+    //@{
+    RBTree();
+
+    /// \b Note: RBTree is not intended to be inherited so the destructor
+    /// is not virtual
+    ~RBTree();
+    //@}
+
+    /// \name Inquery methods
+    //@{
+    /// \brief Find the node with the name
+    /// \param name Target to be found
+    /// \param node Point to the node when the return vaule is \c not
+    /// NOTFOUND, if the return value is NOTFOUND, the value of node is
+    /// \c unknown
+    Result find(const isc::dns::Name& name, RBNode<T>** node) const;
+    Result find(const isc::dns::Name& name, const RBNode<T>** node) const;
+
+    /// \brief Get the total node count in the tree
+    /// the node count including the node created common suffix node,
+    /// this function will only be used for debuging
+    int getNodeCount() const { return (node_count_);}
+    //@}
+
+    /// \name Debug function
+    //@{
+    /// \brief print the nodes in the trees
+    void dumpTree(std::ostream& os, unsigned int depth = 0) const;
+    //@}
+
+    /// \name Modify function
+    //@{
+    /// \brief Insert the domain name into the tree
+    /// \param name The name to be inserted into the tree
+    /// \param inserted_node If no node with the name in the tree,
+    /// new \c RBNode will be created, otherwise nothing will be done.
+    /// Anyway the pointer point to the node with the name will be assigned to
+    /// inserted_node
+    /// \return
+    //  - SUCCEED means no node exists in the tree with the name before insert
+    /// - ALREADYEXIST means already has the node with the given name
+    //
+    /// \node To modify the data related with one name but not sure the name has
+    /// inserted or not, it is better to call \code insert \endcode,instead of
+    /// \code find() \endcode, in case the name isn't exist and needs to insert again
+    Result insert(const isc::dns::Name& name, RBNode<T>** inserted_node);
+    //@}
+
+private:
+    /// \name RBTree balance functions
+    //@{
+    void insertRebalance(RBNode<T>** root, RBNode<T>* node);
+    RBNode<T>* rightRotate(RBNode<T>** root, RBNode<T>* node);
+    RBNode<T>* leftRotate(RBNode<T>** root, RBNode<T>* node);
+    //@}
+
+    /// \name Helper functions
+    //@{
+    /// \brief delete tree whose root is equal to node
+    void deleteHelper(RBNode<T> *node);
+    /// \brief find the node with name
+    /// \param name is the target, up will points to the base domain of
+    /// the tree which name resides, node will point to the target node
+    /// if we has exact same name or partical name in current tree.
+    /// so for example, in zone a, we has
+    /// b.a, c.b.a and d.b.a search c.b.a, up will points to b.a.
+    /// and node will points to c.b.a
+    /// \note parameter up now is not used by any funciton, but we are gonna
+    /// need it soon to implement function like remove
+    Result findHelper(const isc::dns::Name& name, const RBNode<T>** up,
+                      RBNode<T>** node) const;
+    void dumpTreeHelper(std::ostream& os, const RBNode<T>* node,
+                        unsigned int depth) const;
+    /// for indent purpose, add certian mount empty charachter to output stream
+    /// according to the depth. This is a helper function which is only used when
+    /// dump tree
+    static void indent(std::ostream& os, unsigned int depth);
+
+    /// Split one node into two nodes, keep the old node and create one new
+    /// node, old node will hold the base name, new node will be the down node
+    /// of old node, new node will hold the sub_name, the data
+    /// of old node will be move into new node, and old node became non-terminal
+    void nodeFission(RBNode<T>& node, const isc::dns::Name& sub_name);
+    //@}
+
+    RBNode<T>*  root_;
+    RBNode<T>*  NULLNODE;
+    /// the node count of current tree
+    unsigned int node_count_;
+};
+
+template <typename T>
+RBTree<T>::RBTree() {
+    NULLNODE = RBNode<T>::NULL_NODE();
+    root_ = NULLNODE;
+    node_count_ = 0;
+}
+
+template <typename T>
+RBTree<T>::~RBTree() {
+    deleteHelper(root_);
+    assert(node_count_ == 0);
+}
+
+template <typename T>
+void RBTree<T> ::deleteHelper(RBNode<T> *root) {
+    if (root == NULLNODE) {
+        return;
+    }
+
+    RBNode<T> *node = root;
+    while (root->left_ != NULLNODE || root->right_ != NULLNODE) {
+        while (node->left_ != NULLNODE || node->right_ != NULLNODE) {
+            node = (node->left_ != NULLNODE) ? node->left_ : node->right_;
+        }
+
+        RBNode<T> *parent = node->parent_;
+        if (parent->left_ == node) {
+            parent->left_ = NULLNODE;
+        } else {
+            parent->right_ = NULLNODE;
+        }
+
+        deleteHelper(node->down_);
+        delete node;
+        --node_count_;
+        node = parent;
+    }
+
+    deleteHelper(root->down_);
+    delete root;
+    --node_count_;
+}
+
+template <typename T>
+typename RBTree<T>::Result
+RBTree<T>::find(const isc::dns::Name& name, RBNode<T>** node) const {
+    const RBNode<T>* up_node = NULLNODE;
+    return (findHelper(name, &up_node, node));
+}
+
+template <typename T>
+typename RBTree<T>::Result
+RBTree<T>::find(const isc::dns::Name& name, const RBNode<T>** node) const {
+    const RBNode<T>* up_node;
+    RBNode<T>* target_node;
+    const typename RBTree<T>::Result ret =
+        findHelper(name, &up_node, &target_node);
+    if (ret != NOTFOUND) {
+        *node = target_node;
+    }
+    return (ret);
+}
+
+template <typename T>
+typename RBTree<T>::Result
+RBTree<T>::findHelper(const isc::dns::Name& target_name, const RBNode<T>** up_node,
+                      RBNode<T>** target) const
+{
+    using namespace helper;
+
+    RBNode<T>* node = root_;
+    typename RBTree<T>::Result ret = NOTFOUND;
+    *up_node = NULLNODE;
+    isc::dns::Name name = target_name;
+
+    while (node != NULLNODE) {
+        const isc::dns::NameComparisonResult compare_result =
+            name.compare(node->name_);
+        const isc::dns::NameComparisonResult::NameRelation relation =
+            compare_result.getRelation();
+        if (relation == isc::dns::NameComparisonResult::EQUAL) {
+            if (!node->isEmpty()) {
+                *target = node;
+                ret = EXACTMATCH;
+            }
+            break;
+        } else {
+            const int common_label_count = compare_result.getCommonLabels();
+            // If the common label count is 1, there is no common label between
+            // the two names, except the trailing "dot".
+            if (common_label_count == 1) {
+                node = (compare_result.getOrder() < 0) ?
+                    node->left_ : node->right_;
+            } else if (relation == isc::dns::NameComparisonResult::SUBDOMAIN) {
+                *up_node = node;
+                name = name - node->name_;
+                if (!node->isEmpty()) {
+                    ret = RBTree<T>::PARTIALMATCH;
+                    *target = node;
+                }
+                node = node->down_;
+            } else {
+                break;
+            }
+        }
+    }
+
+    return (ret);
+}
+
+template <typename T>
+typename RBTree<T>::Result
+RBTree<T>::insert(const isc::dns::Name& target_name, RBNode<T>** new_node) {
+    using namespace helper;
+    RBNode<T>* parent = NULLNODE;
+    RBNode<T>* current = root_;
+    RBNode<T>* up_node = NULLNODE;
+    isc::dns::Name name = target_name;
+
+    int order = -1;
+    while (current != NULLNODE) {
+        const isc::dns::NameComparisonResult compare_result =
+            name.compare(current->name_);
+        const isc::dns::NameComparisonResult::NameRelation relation =
+            compare_result.getRelation();
+        if (relation == isc::dns::NameComparisonResult::EQUAL) {
+            if (new_node != NULL) {
+                *new_node = current;
+            }
+            return (ALREADYEXIST);
+        } else {
+            const int common_label_count = compare_result.getCommonLabels();
+            if (common_label_count == 1) {
+                parent = current;
+                order = compare_result.getOrder();
+                current = order < 0 ? current->left_ : current->right_;
+            } else {
+                // insert sub domain to sub tree
+                if (relation == isc::dns::NameComparisonResult::SUBDOMAIN) {
+                    parent = NULLNODE;
+                    up_node = current;
+                    name = name - current->name_;
+                    current = current->down_;
+                } else {
+                    // The number of labels in common is fewer
+                    // than the number of labels at the current
+                    // node, so the current node must be adjusted
+                    // to have just the common suffix, and a down
+                    // pointer made to a new tree.
+                    const isc::dns::Name common_ancestor = name.split(
+                        name.getLabelCount() - common_label_count,
+                        common_label_count);
+                    nodeFission(*current, common_ancestor);
+                }
+            }
+        }
+    }
+
+    RBNode<T>** current_root = (up_node != NULLNODE) ?
+        &(up_node->down_) : &root_;
+    // using auto_ptr here is avoid memory leak in case of exceptoin raised
+    // after the RBNode creation, if we can make sure no exception will be
+    // raised until the end of the function, we can remove it for optimization
+    std::auto_ptr<RBNode<T> > node(new RBNode<T>(name));
+    node->parent_ = parent;
+    if (parent == NULLNODE) {
+        *current_root = node.get();
+        //node is the new root of sub tree, so its init color
+        // is BLACK
+        node->color_ = RBNode<T>::BLACK;
+    } else if (order < 0) {
+        parent->left_ = node.get();
+    } else {
+        parent->right_ = node.get();
+    }
+    insertRebalance(current_root, node.get());
+    if (new_node != NULL) {
+        *new_node = node.get();
+    }
+
+    ++node_count_;
+    node.release();
+    return (SUCCEED);
+}
+
+template <typename T>
+void
+RBTree<T>::nodeFission(RBNode<T>& node, const isc::dns::Name& base_name) {
+    using namespace helper;
+    const isc::dns::Name sub_name = node.name_ - base_name;
+    // using auto_ptr here is to avoid memory leak in case of exceptoin raised
+    // after the RBNode creation
+    std::auto_ptr<RBNode<T> > down_node(new RBNode<T>(sub_name));
+    std::swap(node.data_, down_node->data_);
+    down_node->down_ = node.down_;
+    node.name_ = base_name;
+    node.down_ = down_node.get();
+    //root node of sub tree, the initial color is BLACK
+    down_node->color_ = RBNode<T>::BLACK;
+    ++node_count_;
+    down_node.release();
+}
+
+template <typename T>
+void
+RBTree<T>::insertRebalance(RBNode<T>** root, RBNode<T>* node) {
+
+    RBNode<T>* uncle;
+    while (node != *root && node->parent_->color_ == RBNode<T>::RED) {
+        if (node->parent_ == node->parent_->parent_->left_) {
+            uncle = node->parent_->parent_->right_;
+
+            if (uncle->color_ == RBNode<T>::RED) {
+                node->parent_->color_ = RBNode<T>::BLACK;
+                uncle->color_ = RBNode<T>::BLACK;
+                node->parent_->parent_->color_ = RBNode<T>::RED;
+                node = node->parent_->parent_;
+            } else {
+                if (node == node->parent_->right_) {
+                    node = node->parent_;
+                    leftRotate(root, node);
+                }
+                node->parent_->color_ = RBNode<T>::BLACK;
+                node->parent_->parent_->color_ = RBNode<T>::RED;
+                rightRotate(root, node->parent_->parent_);
+            }
+        } else {
+            uncle = node->parent_->parent_->left_;
+            if (uncle->color_ == RBNode<T>::RED) {
+                node->parent_->color_ = RBNode<T>::BLACK;
+                uncle->color_ = RBNode<T>::BLACK;
+                node->parent_->parent_->color_ = RBNode<T>::RED;
+                node = node->parent_->parent_;
+            } else {
+                if (node == node->parent_->left_) {
+                    node = node->parent_;
+                    rightRotate(root, node);
+                }
+                node->parent_->color_ = RBNode<T>::BLACK;
+                node->parent_->parent_->color_ = RBNode<T>::RED;
+                leftRotate(root, node->parent_->parent_);
+            }
+        }
+    }
+
+    (*root)->color_ = RBNode<T>::BLACK;
+}
+
+
+template <typename T>
+RBNode<T>*
+RBTree<T>::leftRotate(RBNode<T>** root, RBNode<T>* node) {
+    RBNode<T>* right = node->right_;
+    node->right_ = right->left_;
+    if (right->left_ != NULLNODE)
+        right->left_->parent_ = node;
+
+    right->parent_ = node->parent_;
+
+    if (node->parent_ != NULLNODE) {
+        if (node == node->parent_->left_) {
+            node->parent_->left_ = right;
+        } else  {
+            node->parent_->right_ = right;
+        }
+    } else {
+        *root = right;
+    }
+
+    right->left_ = node;
+    node->parent_ = right;
+    return (node);
+}
+
+template <typename T>
+RBNode<T>*
+RBTree<T>::rightRotate(RBNode<T>** root, RBNode<T>* node) {
+    RBNode<T>* left = node->left_;
+    node->left_ = left->right_;
+    if (left->right_ != NULLNODE)
+        left->right_->parent_ = node;
+
+    left->parent_ = node->parent_;
+
+    if (node->parent_ != NULLNODE) {
+        if (node == node->parent_->right_) {
+            node->parent_->right_ = left;
+        } else  {
+            node->parent_->left_ = left;
+        }
+    } else {
+        *root = left;
+    }
+    left->right_ = node;
+    node->parent_ = left;
+    return (node);
+}
+
+template <typename T>
+void
+RBTree<T>::dumpTree(std::ostream& os, unsigned int depth) const {
+    indent(os, depth);
+    os << "tree has " << node_count_ << " node(s)\n";
+    dumpTreeHelper(os, root_, depth);
+}
+
+template <typename T>
+void
+RBTree<T>::dumpTreeHelper(std::ostream& os, const RBNode<T>* node,
+                          unsigned int depth) const
+{
+    if (node == NULLNODE) {
+        indent(os, depth);
+        os << "NULL\n";
+        return;
+    }
+
+    indent(os, depth);
+    os << node->name_.toText() << " ("
+              << ((node->color_ == RBNode<T>::BLACK) ? "black" : "red") << ")";
+    os << ((node->isEmpty()) ? "[invisible] \n" : "\n");
+
+    if (node->down_ != NULLNODE) {
+        indent(os, depth + 1);
+        os << "begin down from " << node->name_.toText() << "\n";
+        dumpTreeHelper(os, node->down_, depth + 1);
+        indent(os, depth + 1);
+        os << "end down from " << node->name_.toText() << "\n";
+    }
+    dumpTreeHelper(os, node->left_, depth + 1);
+    dumpTreeHelper(os, node->right_, depth + 1);
+}
+
+template <typename T>
+void
+RBTree<T>::indent(std::ostream& os, unsigned int depth) {
+    static const unsigned int INDENT_FOR_EACH_DEPTH = 5;
+    os << std::string(depth * INDENT_FOR_EACH_DEPTH, ' ');
+}
+
+}
+}
+
+#endif  // _RBTREE_H
+
+// Local Variables:
+// mode: c++
+// End:

+ 3 - 1
src/lib/datasrc/tests/Makefile.am

@@ -24,12 +24,14 @@ run_unittests_SOURCES += static_unittest.cc
 run_unittests_SOURCES += query_unittest.cc
 run_unittests_SOURCES += cache_unittest.cc
 run_unittests_SOURCES += test_datasrc.h test_datasrc.cc
+run_unittests_SOURCES += rbtree_unittest.cc
 run_unittests_SOURCES += zonetable_unittest.cc
+run_unittests_SOURCES += memory_datasrc_unittest.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/datasrc/libdatasrc.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/cc/libcc.la
 run_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la

+ 21 - 1
src/lib/datasrc/tests/datasrc_unittest.cc

@@ -877,12 +877,32 @@ TEST_F(DataSrcTest, NSECZonecutOfNonsecureZone) {
     EXPECT_TRUE(it->isLast());
 }
 
-TEST_F(DataSrcTest, RootDSQuery) {
+// Test sending a DS query to root (nonsense, but it should survive)
+TEST_F(DataSrcTest, RootDSQuery1) {
     EXPECT_NO_THROW(createAndProcessQuery(Name("."), RRClass::IN(),
                                           RRType::DS()));
     headerCheck(msg, Rcode::REFUSED(), true, false, true, 0, 0, 0);
 }
 
+// The same, but when we have the root zone
+// (which triggers rfc4035 section 3.1.4.1)
+TEST_F(DataSrcTest, RootDSQuery2) {
+    // The message
+    msg.makeResponse();
+    msg.setOpcode(Opcode::QUERY());
+    msg.addQuestion(Question(Name("."), RRClass::IN(), RRType::DS()));
+    msg.setHeaderFlag(Message::HEADERFLAG_RD);
+    // Prepare the source
+    DataSrcPtr sql3_source = DataSrcPtr(new Sqlite3DataSrc);
+    ConstElementPtr sqlite_root = Element::fromJSON(
+        "{ \"database_file\": \"" TEST_DATA_DIR "/test-root.sqlite3\"}");
+    EXPECT_NO_THROW(sql3_source->init(sqlite_root));
+    // Make the query
+    EXPECT_NO_THROW(performQuery(*sql3_source, cache, msg));
+
+    headerCheck(msg, Rcode::NOERROR(), true, true, true, 0, 1, 0);
+}
+
 TEST_F(DataSrcTest, DSQueryFromCache) {
     // explicitly enable hot spot cache
     cache.setEnabled(true);

+ 115 - 0
src/lib/datasrc/tests/memory_datasrc_unittest.cc

@@ -0,0 +1,115 @@
+// 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 <exceptions/exceptions.h>
+
+#include <dns/name.h>
+#include <dns/rrclass.h>
+
+#include <datasrc/zonetable.h>
+#include <datasrc/memory_datasrc.h>
+
+#include <gtest/gtest.h>
+
+using namespace isc::dns;
+using namespace isc::datasrc;
+
+namespace {
+
+class MemoryDataSrcTest : public ::testing::Test {
+protected:
+    MemoryDataSrcTest()
+    {}
+    MemoryDataSrc memory_datasrc;
+};
+
+TEST_F(MemoryDataSrcTest, add_find_Zone) {
+    // test add zone
+    // Bogus zone (NULL)
+    EXPECT_THROW(memory_datasrc.addZone(ZonePtr()), isc::InvalidParameter);
+
+    // add zones with different names one by one
+    EXPECT_EQ(result::SUCCESS, memory_datasrc.addZone(
+                  ZonePtr(new MemoryZone(RRClass::IN(), Name("a")))));
+    EXPECT_EQ(result::SUCCESS, memory_datasrc.addZone(
+                  ZonePtr(new MemoryZone(RRClass::CH(), Name("b")))));
+    EXPECT_EQ(result::SUCCESS, memory_datasrc.addZone(
+                  ZonePtr(new MemoryZone(RRClass::IN(), Name("c")))));
+    // add zones with the same name suffix
+    EXPECT_EQ(result::SUCCESS, memory_datasrc.addZone(
+                  ZonePtr(new MemoryZone(RRClass::CH(),
+                                         Name("x.d.e.f")))));
+    EXPECT_EQ(result::SUCCESS, memory_datasrc.addZone(
+                  ZonePtr(new MemoryZone(RRClass::CH(),
+                                         Name("o.w.y.d.e.f")))));
+    EXPECT_EQ(result::SUCCESS, memory_datasrc.addZone(
+                  ZonePtr(new MemoryZone(RRClass::CH(),
+                                         Name("p.w.y.d.e.f")))));
+    EXPECT_EQ(result::SUCCESS, memory_datasrc.addZone(
+                  ZonePtr(new MemoryZone(RRClass::IN(),
+                                         Name("q.w.y.d.e.f")))));
+    // add super zone and its subzone
+    EXPECT_EQ(result::SUCCESS, memory_datasrc.addZone(
+                  ZonePtr(new MemoryZone(RRClass::CH(), Name("g.h")))));
+    EXPECT_EQ(result::SUCCESS, memory_datasrc.addZone(
+                  ZonePtr(new MemoryZone(RRClass::IN(), Name("i.g.h")))));
+    EXPECT_EQ(result::SUCCESS, memory_datasrc.addZone(
+                  ZonePtr(new MemoryZone(RRClass::IN(),
+                                         Name("z.d.e.f")))));
+    EXPECT_EQ(result::SUCCESS, memory_datasrc.addZone(
+                  ZonePtr(new MemoryZone(RRClass::IN(),
+                                         Name("j.z.d.e.f")))));
+
+    // different zone class isn't allowed.
+    EXPECT_EQ(result::EXIST, memory_datasrc.addZone(
+                  ZonePtr(new MemoryZone(RRClass::CH(),
+                                         Name("q.w.y.d.e.f")))));
+
+    // names are compared in a case insensitive manner.
+    EXPECT_EQ(result::EXIST, memory_datasrc.addZone(
+                  ZonePtr(new MemoryZone(RRClass::IN(),
+                                         Name("Q.W.Y.d.E.f")))));
+
+    // test find zone
+    EXPECT_EQ(result::SUCCESS, memory_datasrc.findZone(Name("a")).code);
+    EXPECT_EQ(Name("a"),
+              memory_datasrc.findZone(Name("a")).zone->getOrigin());
+
+    EXPECT_EQ(result::SUCCESS,
+              memory_datasrc.findZone(Name("j.z.d.e.f")).code);
+    EXPECT_EQ(Name("j.z.d.e.f"),
+              memory_datasrc.findZone(Name("j.z.d.e.f")).zone->getOrigin());
+
+    // NOTFOUND
+    EXPECT_EQ(result::NOTFOUND, memory_datasrc.findZone(Name("d.e.f")).code);
+    EXPECT_EQ(ConstZonePtr(), memory_datasrc.findZone(Name("d.e.f")).zone);
+
+    EXPECT_EQ(result::NOTFOUND,
+              memory_datasrc.findZone(Name("w.y.d.e.f")).code);
+    EXPECT_EQ(ConstZonePtr(),
+              memory_datasrc.findZone(Name("w.y.d.e.f")).zone);
+
+    // there's no exact match.  the result should be the longest match,
+    // and the code should be PARTIALMATCH.
+    EXPECT_EQ(result::PARTIALMATCH,
+              memory_datasrc.findZone(Name("j.g.h")).code);
+    EXPECT_EQ(Name("g.h"),
+              memory_datasrc.findZone(Name("g.h")).zone->getOrigin());
+
+    EXPECT_EQ(result::PARTIALMATCH,
+              memory_datasrc.findZone(Name("z.i.g.h")).code);
+    EXPECT_EQ(Name("i.g.h"),
+              memory_datasrc.findZone(Name("z.i.g.h")).zone->getOrigin());
+}
+}

File diff suppressed because it is too large
+ 179 - 0
src/lib/datasrc/tests/rbtree_unittest.cc


+ 46 - 37
src/lib/datasrc/tests/zonetable_unittest.cc

@@ -26,79 +26,88 @@ using namespace isc::datasrc;
 
 namespace {
 TEST(ZoneTest, init) {
-    Zone zone(RRClass::IN(), Name("example.com"));
+    MemoryZone zone(RRClass::IN(), Name("example.com"));
     EXPECT_EQ(Name("example.com"), zone.getOrigin());
     EXPECT_EQ(RRClass::IN(), zone.getClass());
 
-    Zone ch_zone(RRClass::CH(), Name("example"));
+    MemoryZone ch_zone(RRClass::CH(), Name("example"));
     EXPECT_EQ(Name("example"), ch_zone.getOrigin());
     EXPECT_EQ(RRClass::CH(), ch_zone.getClass());
 }
 
+TEST(ZoneTest, find) {
+    MemoryZone zone(RRClass::IN(), Name("example.com"));
+    EXPECT_EQ(Zone::NXDOMAIN,
+              zone.find(Name("www.example.com"), RRType::A()).code);
+}
+
 class ZoneTableTest : public ::testing::Test {
 protected:
-    ZoneTableTest() : zone1(new Zone(RRClass::IN(), Name("example.com"))),
-                      zone2(new Zone(RRClass::IN(), Name("example.net"))),
-                      zone3(new Zone(RRClass::IN(), Name("example")))
+    ZoneTableTest() : zone1(new MemoryZone(RRClass::IN(),
+                                           Name("example.com"))),
+                      zone2(new MemoryZone(RRClass::IN(),
+                                           Name("example.net"))),
+                      zone3(new MemoryZone(RRClass::IN(), Name("example")))
     {}
     ZoneTable zone_table;
     ZonePtr zone1, zone2, zone3;
 };
 
-TEST_F(ZoneTableTest, add) {
-    EXPECT_EQ(ZoneTable::SUCCESS, zone_table.add(zone1));
-    EXPECT_EQ(ZoneTable::EXIST, zone_table.add(zone1));
+TEST_F(ZoneTableTest, addZone) {
+    EXPECT_EQ(result::SUCCESS, zone_table.addZone(zone1));
+    EXPECT_EQ(result::EXIST, zone_table.addZone(zone1));
     // names are compared in a case insensitive manner.
-    EXPECT_EQ(ZoneTable::EXIST, zone_table.add(
-                  ZonePtr(new Zone(RRClass::IN(), Name("EXAMPLE.COM")))));
+    EXPECT_EQ(result::EXIST, zone_table.addZone(
+                  ZonePtr(new MemoryZone(RRClass::IN(), Name("EXAMPLE.COM")))));
 
-    EXPECT_EQ(ZoneTable::SUCCESS, zone_table.add(zone2));
-    EXPECT_EQ(ZoneTable::SUCCESS, zone_table.add(zone3));
+    EXPECT_EQ(result::SUCCESS, zone_table.addZone(zone2));
+    EXPECT_EQ(result::SUCCESS, zone_table.addZone(zone3));
 
     // Zone table is indexed only by name.  Duplicate origin name with
     // different zone class isn't allowed.
-    EXPECT_EQ(ZoneTable::EXIST, zone_table.add(
-                  ZonePtr(new Zone(RRClass::CH(), Name("example.com")))));
+    EXPECT_EQ(result::EXIST, zone_table.addZone(
+                  ZonePtr(new MemoryZone(RRClass::CH(),
+                                         Name("example.com")))));
 
     /// Bogus zone (NULL)
-    EXPECT_THROW(zone_table.add(ZonePtr()), isc::InvalidParameter);
+    EXPECT_THROW(zone_table.addZone(ZonePtr()), isc::InvalidParameter);
 }
 
-TEST_F(ZoneTableTest, remove) {
-    EXPECT_EQ(ZoneTable::SUCCESS, zone_table.add(zone1));
-    EXPECT_EQ(ZoneTable::SUCCESS, zone_table.add(zone2));
-    EXPECT_EQ(ZoneTable::SUCCESS, zone_table.add(zone3));
+TEST_F(ZoneTableTest, removeZone) {
+    EXPECT_EQ(result::SUCCESS, zone_table.addZone(zone1));
+    EXPECT_EQ(result::SUCCESS, zone_table.addZone(zone2));
+    EXPECT_EQ(result::SUCCESS, zone_table.addZone(zone3));
 
-    EXPECT_EQ(ZoneTable::SUCCESS, zone_table.remove(Name("example.net")));
-    EXPECT_EQ(ZoneTable::NOTFOUND, zone_table.remove(Name("example.net")));
+    EXPECT_EQ(result::SUCCESS, zone_table.removeZone(Name("example.net")));
+    EXPECT_EQ(result::NOTFOUND, zone_table.removeZone(Name("example.net")));
 }
 
-TEST_F(ZoneTableTest, find) {
-    EXPECT_EQ(ZoneTable::SUCCESS, zone_table.add(zone1));
-    EXPECT_EQ(ZoneTable::SUCCESS, zone_table.add(zone2));
-    EXPECT_EQ(ZoneTable::SUCCESS, zone_table.add(zone3));
+TEST_F(ZoneTableTest, findZone) {
+    EXPECT_EQ(result::SUCCESS, zone_table.addZone(zone1));
+    EXPECT_EQ(result::SUCCESS, zone_table.addZone(zone2));
+    EXPECT_EQ(result::SUCCESS, zone_table.addZone(zone3));
 
-    EXPECT_EQ(ZoneTable::SUCCESS, zone_table.find(Name("example.com")).code);
+    EXPECT_EQ(result::SUCCESS, zone_table.findZone(Name("example.com")).code);
     EXPECT_EQ(Name("example.com"),
-              zone_table.find(Name("example.com")).zone->getOrigin());
+              zone_table.findZone(Name("example.com")).zone->getOrigin());
 
-    EXPECT_EQ(ZoneTable::NOTFOUND,
-              zone_table.find(Name("example.org")).code);
-    EXPECT_EQ(static_cast<const Zone*>(NULL),
-              zone_table.find(Name("example.org")).zone);
+    EXPECT_EQ(result::NOTFOUND,
+              zone_table.findZone(Name("example.org")).code);
+    EXPECT_EQ(ConstZonePtr(),
+              zone_table.findZone(Name("example.org")).zone);
 
     // there's no exact match.  the result should be the longest match,
     // and the code should be PARTIALMATCH.
-    EXPECT_EQ(ZoneTable::PARTIALMATCH,
-              zone_table.find(Name("www.example.com")).code);
+    EXPECT_EQ(result::PARTIALMATCH,
+              zone_table.findZone(Name("www.example.com")).code);
     EXPECT_EQ(Name("example.com"),
-              zone_table.find(Name("www.example.com")).zone->getOrigin());
+              zone_table.findZone(Name("www.example.com")).zone->getOrigin());
 
     // make sure the partial match is indeed the longest match by adding
     // a zone with a shorter origin and query again.
-    ZonePtr zone_com(new Zone(RRClass::IN(), Name("com")));
-    EXPECT_EQ(ZoneTable::SUCCESS, zone_table.add(zone_com));
+    ZonePtr zone_com(new MemoryZone(RRClass::IN(), Name("com")));
+    EXPECT_EQ(result::SUCCESS, zone_table.addZone(zone_com));
     EXPECT_EQ(Name("example.com"),
-              zone_table.find(Name("www.example.com")).zone->getOrigin());
+              zone_table.findZone(Name("www.example.com")).zone->getOrigin());
 }
 }

+ 27 - 19
src/lib/datasrc/zonetable.cc

@@ -28,32 +28,39 @@ using namespace isc::dns;
 namespace isc {
 namespace datasrc {
 
-struct Zone::ZoneImpl {
-    ZoneImpl(const RRClass& zone_class, const Name& origin) :
+struct MemoryZone::MemoryZoneImpl {
+    MemoryZoneImpl(const RRClass& zone_class, const Name& origin) :
         zone_class_(zone_class), origin_(origin)
     {}
     RRClass zone_class_;
     Name origin_;
 };
 
-Zone::Zone(const RRClass& zone_class, const Name& origin) : impl_(NULL) {
-    impl_ = new ZoneImpl(zone_class, origin);
+MemoryZone::MemoryZone(const RRClass& zone_class, const Name& origin) :
+    impl_(new MemoryZoneImpl(zone_class, origin))
+{
 }
 
-Zone::~Zone() {
+MemoryZone::~MemoryZone() {
     delete impl_;
 }
 
 const Name&
-Zone::getOrigin() const {
+MemoryZone::getOrigin() const {
     return (impl_->origin_);
 }
 
 const RRClass&
-Zone::getClass() const {
+MemoryZone::getClass() const {
     return (impl_->zone_class_);
 }
 
+Zone::FindResult
+MemoryZone::find(const Name&, const RRType&) const {
+    // This is a tentative implementation that always returns NXDOMAIN.
+    return (FindResult(NXDOMAIN, RRsetPtr()));
+}
+
 // This is a temporary, inefficient implementation using std::map and handmade
 // iteration to realize longest match.
 
@@ -70,29 +77,30 @@ ZoneTable::~ZoneTable() {
     delete impl_;
 }
 
-ZoneTable::Result
-ZoneTable::add(ZonePtr zone) {
+result::Result
+ZoneTable::addZone(ZonePtr zone) {
     if (!zone) {
         isc_throw(InvalidParameter,
-                  "Null pointer is passed to ZoneTable::add()");
+                  "Null pointer is passed to ZoneTable::addZone()");
     }
 
     if (impl_->zones.insert(
             ZoneTableImpl::NameAndZone(zone->getOrigin(), zone)).second
         == true) {
-        return (SUCCESS);
+        return (result::SUCCESS);
     } else {
-        return (EXIST);
+        return (result::EXIST);
     }
 }
 
-ZoneTable::Result
-ZoneTable::remove(const Name& origin) {
-    return (impl_->zones.erase(origin) == 1 ? SUCCESS : NOTFOUND);
+result::Result
+ZoneTable::removeZone(const Name& origin) {
+    return (impl_->zones.erase(origin) == 1 ? result::SUCCESS :
+                                              result::NOTFOUND);
 }
 
 ZoneTable::FindResult
-ZoneTable::find(const Name& name) const {
+ZoneTable::findZone(const Name& name) const {
     // Inefficient internal loop to find a longest match.
     // This will be replaced with a single call to more intelligent backend.
     for (int i = 0; i < name.getLabelCount(); ++i) {
@@ -100,11 +108,11 @@ ZoneTable::find(const Name& name) const {
         ZoneTableImpl::ZoneMap::const_iterator found =
             impl_->zones.find(matchname);
         if (found != impl_->zones.end()) {
-            return (FindResult(i == 0 ? SUCCESS : PARTIALMATCH,
-                               (*found).second.get()));
+            return (FindResult(i == 0 ? result::SUCCESS :
+                               result::PARTIALMATCH, (*found).second));
         }
     }
-    return (FindResult(NOTFOUND, NULL));
+    return (FindResult(result::NOTFOUND, ConstZonePtr()));
 }
 } // end of namespace datasrc
 } // end of namespace isc

+ 197 - 134
src/lib/datasrc/zonetable.h

@@ -17,6 +17,8 @@
 
 #include <boost/shared_ptr.hpp>
 
+#include <dns/rrset.h>
+
 namespace isc {
 namespace dns {
 class Name;
@@ -24,20 +26,181 @@ class RRClass;
 };
 
 namespace datasrc {
+namespace result {
+/// Result codes of various public methods of in memory data source
+///
+/// The detailed semantics may differ in different methods.
+/// See the description of specific methods for more details.
+///
+/// Note: this is intended to be used from other data sources eventually,
+/// but for now it's specific to in memory data source and its backend.
+enum Result {
+    SUCCESS,  ///< The operation is successful.
+    EXIST,    ///< The search key is already stored.
+    NOTFOUND, ///< The specified object is not found.
+    PARTIALMATCH ///< \c Only a partial match is found.
+};
+}
 
-/// \brief A single authoritative zone
+/// \brief The base class for a single authoritative zone
 ///
-/// The \c Zone class represents a DNS zone as part of %data source.
+/// The \c Zone class is an abstract base class for representing
+/// a DNS zone as part of data source.
 ///
 /// At the moment this is provided mainly for making the \c ZoneTable class
-/// testable, and only provides a minimal set of features.
+/// and the authoritative query logic  testable, and only provides a minimal
+/// set of features.
 /// This is why this class is defined in the same header file, but it may
 /// have to move to a separate header file when we understand what is
 /// necessary for this class for actual operation.
-/// Likewise, it will have more features.  For example, it will maintain
+///
+/// The idea is to provide a specific derived zone class for each data
+/// source, beginning with in memory one.  At that point the derived classes
+/// will have more specific features.  For example, they will maintain
 /// information about the location of a zone file, whether it's loaded in
 /// memory, etc.
+///
+/// It's not yet clear how the derived zone classes work with various other
+/// data sources when we integrate these components, but one possibility is
+/// something like this:
+/// - If the underlying database such as some variant of SQL doesn't have an
+///   explicit representation of zones (as part of public interface), we can
+///   probably use a "default" zone class that simply encapsulates the
+///   corresponding data source and calls a common "find" like method.
+/// - Some data source may want to specialize it by inheritance as an
+///   optimization.  For example, in the current schema design of the sqlite3
+///   data source, its (derived) zone class would contain the information of
+///   the "zone ID".
+///
+/// <b>Note:</b> Unlike some other abstract base classes we don't name the
+/// class beginning with "Abstract".  This is because we want to have
+/// commonly used definitions such as \c Result and \c ZonePtr, and we want
+/// to make them look more intuitive.
 class Zone {
+public:
+    /// Result codes of the \c find() method.
+    ///
+    /// Note: the codes are tentative.  We may need more, or we may find
+    /// some of them unnecessary as we implement more details.
+    enum Result {
+        SUCCESS,                ///< An exact match is found.
+        DELEGATION,             ///< The search encounters a zone cut.
+        NXDOMAIN, ///< There is no domain name that matches the search name
+        NXRRSET,  ///< There is a matching name but no RRset of the search type
+        CNAME,    ///< The search encounters and returns a CNAME RR
+        DNAME     ///< The search encounters and returns a DNAME RR
+    };
+
+    /// A helper structure to represent the search result of \c find().
+    ///
+    /// This is a straightforward tuple of the result code and a pointer
+    /// to the found RRset to represent the result of \c find()
+    /// (there will be more members in the future - see the class
+    /// description).
+    /// We use this in order to avoid overloading the return value for both
+    /// the result code ("success" or "not found") and the found object,
+    /// i.e., avoid using \c NULL to mean "not found", etc.
+    ///
+    /// This is a simple value class whose internal state never changes,
+    /// so for convenience we allow the applications to refer to the members
+    /// directly.
+    ///
+    /// Note: we should eventually include a notion of "zone node", which
+    /// corresponds to a particular domain name of the zone, so that we can
+    /// find RRsets of a different RR type for that name (e.g. for type ANY
+    /// query or to include DS RRs with delegation).
+    ///
+    /// Note: we may also want to include the closest enclosure "node" to
+    /// optimize including the NSEC for no-wildcard proof (FWIW NSD does that).
+    struct FindResult {
+        FindResult(Result param_code,
+                   const isc::dns::ConstRRsetPtr param_rrset) :
+            code(param_code), rrset(param_rrset)
+        {}
+        const Result code;
+        const isc::dns::ConstRRsetPtr rrset;
+    };
+
+    ///
+    /// \name Constructors and Destructor.
+    ///
+    //@{
+protected:
+    /// 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).
+    Zone() {}
+public:
+    /// The destructor.
+    virtual ~Zone() {}
+    //@}
+
+    ///
+    /// \name Getter Methods
+    ///
+    /// These methods should never throw an exception.
+    //@{
+    /// Return the origin name of the zone.
+    virtual const isc::dns::Name& getOrigin() const = 0;
+
+    /// Return the RR class of the zone.
+    virtual const isc::dns::RRClass& getClass() const = 0;
+    //@}
+
+    ///
+    /// \name Search Method
+    ///
+    //@{
+    /// Search the zone for a given pair of domain name and RR type.
+    ///
+    /// Each derived version of this method searches the underlying backend
+    /// for the data that best matches the given name and type.
+    /// This method is expected to be "intelligent", and identifies the
+    /// best possible answer for the search key.  Specifically,
+    /// - If the search name belongs under a zone cut, it returns the code
+    ///   of \c DELEGATION and the NS RRset at the zone cut.
+    /// - If there is no matching name, it returns the code of \c NXDOMAIN,
+    ///   and, if DNSSEC is requested, the NSEC RRset that proves the
+    ///   non-existence.
+    /// - If there is a matching name but no RRset of the search type, it
+    ///   returns the code of \c NXRRSET, and, if DNSSEC is required,
+    ///   the NSEC RRset for that name.
+    /// - If there is a matching name with CNAME, it returns the code of
+    ///   \c CNAME and that CNAME RR.
+    /// - If the search name matches a delegation point of DNAME, it returns
+    ///   the code of \c DNAME and that DNAME RR.
+    ///
+    /// A derived version of this method may involve internal resource
+    /// allocation, especially for constructing the resulting RRset, and may
+    /// throw an exception if it fails.
+    /// It should not throw other types of exceptions.
+    ///
+    /// Note: It's quite likely that we'll need to specify search options.
+    /// For example, we should be able to specify whether to allow returning
+    /// glue records at or under a zone cut.  We leave this interface open
+    /// at this moment.
+    ///
+    /// \param name The domain name to be searched for.
+    /// \param type The RR type to be searched for.
+    /// \return A \c FindResult object enclosing the search result (see above).
+    virtual FindResult find(const isc::dns::Name& name,
+                            const isc::dns::RRType& type) const = 0;
+    //@}
+};
+
+/// \brief A pointer-like type pointing to a \c Zone object.
+typedef boost::shared_ptr<Zone> ZonePtr;
+
+/// \brief A pointer-like type pointing to a \c Zone object.
+typedef boost::shared_ptr<const Zone> ConstZonePtr;
+
+/// A derived zone class intended to be used with the memory data source.
+///
+/// Currently this is almost empty and is only used for testing the
+/// \c ZoneTable class.  It will be substantially expanded, and will probably
+/// moved to a separate header file.
+class MemoryZone : public Zone {
     ///
     /// \name Constructors and Destructor.
     ///
@@ -46,8 +209,8 @@ class Zone {
     /// defined as private, making this class non copyable.
     //@{
 private:
-    Zone(const Zone& source);
-    Zone& operator=(const Zone& source);
+    MemoryZone(const MemoryZone& source);
+    MemoryZone& operator=(const MemoryZone& source);
 public:
     /// \brief Constructor from zone parameters.
     ///
@@ -57,112 +220,40 @@ public:
     ///
     /// \param rrclass The RR class of the zone.
     /// \param origin The origin name of the zone.
-    Zone(const isc::dns::RRClass& rrclass, const isc::dns::Name& origin);
+    MemoryZone(const isc::dns::RRClass& rrclass, const isc::dns::Name& origin);
 
     /// The destructor.
-    ~Zone();
+    virtual ~MemoryZone();
     //@}
 
-    ///
-    /// \name Getter Methods
-    ///
-    /// These methods never throw an exception.
-    //@{
-    /// \brief Return the origin name of the zone.
-    const isc::dns::Name& getOrigin() const;
-
-    /// \brief Return the RR class of the zone.
-    const isc::dns::RRClass& getClass() const;
-    //@}
+    virtual const isc::dns::Name& getOrigin() const;
+    virtual const isc::dns::RRClass& getClass() const;
+    virtual FindResult find(const isc::dns::Name& name,
+                            const isc::dns::RRType& type) const;
 
 private:
-    struct ZoneImpl;
-    ZoneImpl* impl_;
+    struct MemoryZoneImpl;
+    MemoryZoneImpl* impl_;
 };
 
-/// \brief A pointer-like type pointing to a \c Zone object.
-typedef boost::shared_ptr<Zone> ZonePtr;
-
-/// \brief A pointer-like type pointing to a \c Zone object.
-typedef boost::shared_ptr<const Zone> ConstZonePtr;
-
 /// \brief A set of authoritative zones.
 ///
-/// The \c ZoneTable class represents a set of zones of the same RR class
-/// and provides a basic interface to help DNS lookup processing.
-/// For a given domain name, its \c find() method searches the set for a zone
-/// that gives a longest match against that name.
-///
-/// The set of zones are assumed to be of the same RR class, but the
-/// \c ZoneTable class does not enforce the assumption through its interface.
-/// For example, the \c add() method does not check if the new zone
-/// is of the same RR class as that of the others already in the table.
-/// It is caller's responsibility to ensure this assumption.
+/// \c ZoneTable class is primarily intended to be used as a backend for the
+/// \c MemoryDataSrc class, but is exposed as a separate class in case some
+/// application wants to use it directly (e.g. for a customized data source
+/// implementation).
 ///
-/// <b>Notes to developer:</b>
-///
-/// The add() method takes a (Boost) shared pointer because it would be
-/// inconvenient to require the caller to maintain the ownership of zones,
-/// while it wouldn't be safe to delete unnecessary zones inside the zone
-/// table.
-///
-/// On the other hand, the find() method returns a bare pointer, rather than
-/// the shared pointer, in order to minimize the dependency on Boost
-/// definitions in our public interfaces.  This means the caller can only
-/// refer to the returned object (via the pointer) for a short period.
-///  It should be okay for simple lookup purposes, but if we see the need
-/// for keeping a \c Zone object for a longer period of context, we may
-/// have to revisit this decision.
-///
-/// Currently, \c FindResult::zone is immutable for safety.
-/// In future versions we may want to make it changeable.  For example,
-/// we may want to allow configuration update on an existing zone.
-///
-/// In BIND 9's "zt" module, the equivalent of \c find() has an "option"
-/// parameter.  The only defined option is the one to specify the "no exact"
-/// mode, and the only purpose of that mode is to prefer a second longest match
-/// even if there is an exact match in order to deal with type DS query.
-/// This trick may help enhance performance, but it also seems to make the
-/// implementation complicated for a very limited, minor case.  So, for now,
-/// we don't introduce the special mode, and, since it was the only reason to
-/// have search options in BIND 9, our initial implementation doesn't provide
-/// a switch for options.
+/// For more descriptions about its struct and interfaces, please refer to the
+/// corresponding struct and interfaces of \c MemoryDataSrc.
 class ZoneTable {
 public:
-    /// Result codes of various public methods of \c ZoneTable.
-    ///
-    /// The detailed semantics may differ in different methods.
-    /// See the description of specific methods for more details.
-    enum Result {
-        SUCCESS,  ///< The operation is successful.
-        EXIST,    ///< A zone is already stored in \c ZoneTable.
-        NOTFOUND, ///< The specified zone is not found in \c ZoneTable.
-        PARTIALMATCH ///< \c Only a partial match is found in \c find(). 
-    };
-
-    /// \brief A helper structure to represent the search result of
-    /// <code>ZoneTable::find()</code>.
-    ///
-    /// This is a straightforward pair of the result code and a pointer
-    /// to the found zone to represent the result of \c find().
-    /// We use this in order to avoid overloading the return value for both
-    /// the result code ("success" or "not found") and the found object,
-    /// i.e., avoid using \c NULL to mean "not found", etc.
-    ///
-    /// This is a simple value class with no internal state, so for
-    /// convenience we allow the applications to refer to the members
-    /// directly.
-    ///
-    /// See the description of \c find() for the semantics of the member
-    /// variables.
     struct FindResult {
-        FindResult(Result param_code, const Zone* param_zone) :
+        FindResult(result::Result param_code, const ConstZonePtr param_zone) :
             code(param_code), zone(param_zone)
         {}
-        const Result code;
-        const Zone* const zone;
+        const result::Result code;
+        const ConstZonePtr zone;
     };
-
     ///
     /// \name Constructors and Destructor.
     ///
@@ -187,53 +278,25 @@ public:
     //@}
 
     /// Add a \c Zone to the \c ZoneTable.
-    ///
-    /// \c zone must not be associated with a NULL pointer; otherwise
-    /// an exception of class \c InvalidParameter will be thrown.
-    /// If internal resource allocation fails, a corresponding standard
-    /// exception will be thrown.
-    /// This method never throws an exception otherwise.
-    ///
-    /// \param zone A \c Zone object to be added.
-    /// \return \c SUCCESS If the zone is successfully added to the zone table.
-    /// \return \c EXIST The zone table already stores a zone that has the
-    /// same origin.
-    Result add(ZonePtr zone);
+    /// See the description of <code>MemoryDataSrc::addZone()</code> for more
+    /// details.
+    result::Result addZone(ZonePtr zone);
 
     /// Remove a \c Zone of the given origin name from the \c ZoneTable.
     ///
     /// This method never throws an exception.
     ///
     /// \param origin The origin name of the zone to be removed.
-    /// \return \c SUCCESS If the zone is successfully removed from the
-    /// zone table.
-    /// \return \c NOTFOUND The zone table does not store the zone that matches
-    /// \c origin.
-    Result remove(const isc::dns::Name& origin);
+    /// \return \c result::SUCCESS If the zone is successfully
+    /// removed from the zone table.
+    /// \return \c result::NOTFOUND The zone table does not
+    /// store the zone that matches \c origin.
+    result::Result removeZone(const isc::dns::Name& origin);
 
     /// Find a \c Zone that best matches the given name in the \c ZoneTable.
-    ///
-    /// It searches the internal storage for a \c Zone that gives the
-    /// longest match against \c name, and returns the result in the
-    /// form of a \c FindResult object as follows:
-    /// - \c code: The result code of the operation.
-    ///   - \c SUCCESS: A zone that gives an exact match is found
-    ///   - \c PARTIALMATCH: A zone whose origin is a super domain of
-    ///     \c name is found (but there is no exact match)
-    ///   - \c NOTFOUND: For all other cases.
-    /// - \c zone: A pointer to the found \c Zone object if one is found;
-    /// otherwise \c NULL.
-    ///
-    /// The pointer returned in the \c FindResult object is only valid until
-    /// the corresponding zone is removed from the zone table.
-    /// The caller must ensure that the zone is held in the zone table while
-    /// it needs to refer to it.
-    ///
-    /// This method never throws an exception.
-    ///
-    /// \param name A domain name for which the search is performed.
-    /// \return A \c FindResult object enclosing the search result (see above).
-    FindResult find(const isc::dns::Name& name) const;
+    /// See the description of <code>MemoryDataSrc::findZone()</code> for more
+    /// details.
+    FindResult findZone(const isc::dns::Name& name) const;
 
 private:
     struct ZoneTableImpl;

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

@@ -69,6 +69,7 @@ libdns___la_SOURCES += dnssectime.h dnssectime.cc
 libdns___la_SOURCES += edns.h edns.cc
 libdns___la_SOURCES += exceptions.h exceptions.cc
 libdns___la_SOURCES += util/hex.h
+libdns___la_SOURCES += masterload.h masterload.cc
 libdns___la_SOURCES += message.h message.cc
 libdns___la_SOURCES += messagerenderer.h messagerenderer.cc
 libdns___la_SOURCES += name.h name.cc

+ 12 - 0
src/lib/dns/buffer.h

@@ -25,6 +25,8 @@
 
 #include <exceptions/exceptions.h>
 
+#include <boost/shared_ptr.hpp>
+
 namespace isc {
 namespace dns {
 
@@ -412,6 +414,16 @@ public:
 private:
     std::vector<uint8_t> data_;
 };
+
+/// \brief Pointer-like types pointing to \c InputBuffer or \c OutputBuffer
+///
+/// These types are expected to be used as an argument in asynchronous
+/// callback functions.  The internal reference-counting will ensure that
+/// that ongoing state information will not be lost if the object
+/// that originated the asynchronous call falls out of scope.
+typedef boost::shared_ptr<InputBuffer> InputBufferPtr;
+typedef boost::shared_ptr<OutputBuffer> OutputBufferPtr;
+
 }
 }
 #endif  // __BUFFER_H

+ 162 - 0
src/lib/dns/masterload.cc

@@ -0,0 +1,162 @@
+// 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 <istream>
+#include <fstream>
+#include <sstream>
+#include <string>
+#include <cctype>
+
+#include <boost/scoped_ptr.hpp>
+
+#include <exceptions/exceptions.h>
+
+#include <dns/masterload.h>
+#include <dns/name.h>
+#include <dns/rdata.h>
+#include <dns/rrclass.h>
+#include <dns/rrset.h>
+#include <dns/rrttl.h>
+#include <dns/rrtype.h>
+
+using namespace std;
+using namespace boost;
+using namespace isc::dns::rdata;
+
+namespace isc {
+namespace dns {
+void
+masterLoad(const char* const filename, const Name& origin,
+           const RRClass& zone_class, MasterLoadCallback callback)
+{
+    ifstream ifs;
+
+    ifs.open(filename, ios_base::in);
+    if (ifs.fail()) {
+        isc_throw(MasterLoadError, "Failed to open master file: " << filename);
+    }
+    masterLoad(ifs, origin, zone_class, callback);
+    ifs.close();
+}
+
+void
+masterLoad(istream& input, const Name& origin, const RRClass& zone_class,
+           MasterLoadCallback callback)
+{
+    RRsetPtr rrset;
+    string line;
+    unsigned int line_count = 1;
+
+    do {
+        getline(input, line);
+        if (input.bad() || (input.fail() && !input.eof())) {
+            isc_throw(MasterLoadError, "Unexpectedly failed to read a line");
+        }
+
+        // blank/comment lines should be simply skipped.
+        if (line.empty() || line[0] == ';') {
+            continue;
+        }
+
+        // The line shouldn't have leading space (which means omitting the
+        // owner name).
+        if (isspace(line[0])) {
+            isc_throw(MasterLoadError, "Leading space at line " << line_count);
+        }
+
+        // Parse a single RR
+        istringstream iss(line);
+        string owner_txt, ttl_txt, rrclass_txt, rrtype_txt;
+        stringbuf rdatabuf;
+        iss >> owner_txt >> ttl_txt >> rrclass_txt >> rrtype_txt >> &rdatabuf;
+        if (iss.bad() || iss.fail()) {
+            isc_throw(MasterLoadError, "Parse failure for a valid RR at line "
+                      << line_count);
+        }
+
+        // This simple version doesn't support relative owner names with a
+        // separate origin.
+        if (owner_txt.empty() || *(owner_txt.end() - 1) != '.') {
+            isc_throw(MasterLoadError, "Owner name is not absolute at line "
+                      << line_count);
+        }
+
+        // XXX: this part is a bit tricky (and less efficient).  We are going
+        // to validate the text for the RR parameters, and throw an exception
+        // if any of them is invalid by converting an underlying exception
+        // to MasterLoadError.  To do that, we need to define the corresponding
+        // variables used for RRset construction outside the try-catch block,
+        // but we don't like to use a temporary variable with a meaningless
+        // initial value.  So we define pointers outside the try block
+        // and allocate/initialize the actual objects within the block.
+        // To make it exception safe we use Boost.scoped_ptr.
+        scoped_ptr<const Name> owner;
+        scoped_ptr<const RRTTL> ttl;
+        scoped_ptr<const RRClass> rrclass;
+        scoped_ptr<const RRType> rrtype;
+        ConstRdataPtr rdata;
+        try {
+            owner.reset(new Name(owner_txt));
+            ttl.reset(new RRTTL(ttl_txt));
+            rrclass.reset(new RRClass(rrclass_txt));
+            rrtype.reset(new RRType(rrtype_txt));
+            rdata = createRdata(*rrtype, *rrclass, rdatabuf.str());
+        } catch (const Exception& ex) {
+            isc_throw(MasterLoadError, "Invalid RR text at line " << line_count
+                      << ": " << ex.what());
+        }
+
+        // Origin related validation:
+        //  - reject out-of-zone data
+        //  - reject SOA whose owner is not at the top of zone
+        const NameComparisonResult cmp_result = owner->compare(origin);
+        if (cmp_result.getRelation() != NameComparisonResult::EQUAL &&
+            cmp_result.getRelation() != NameComparisonResult::SUBDOMAIN) {
+            isc_throw(MasterLoadError, "Out-of-zone data at line "
+                      << line_count);
+        }
+        if (*rrtype == RRType::SOA() &&
+            cmp_result.getRelation() != NameComparisonResult::EQUAL) {
+            isc_throw(MasterLoadError, "SOA not at top of zone at line "
+                      << line_count);
+        }
+
+        // Reject RR class mismatching
+        if (*rrclass != zone_class) {
+            isc_throw(MasterLoadError, "RR class (" << rrclass_txt
+                      << ") does not match the zone class (" << zone_class
+                      << ") at line " << line_count);
+        }
+
+        // Everything is okay.  Now create/update RRset with the new RR.
+        // If this is the first RR or the RR type/name is new, we are seeing
+        // a new RRset.
+        if (!rrset || rrset->getType() != *rrtype ||
+            rrset->getName() != *owner) {
+            // Commit the previous RRset, if any.
+            if (rrset) {
+                callback(rrset);
+            }
+            rrset = RRsetPtr(new RRset(*owner, *rrclass, *rrtype, *ttl));
+        }
+        rrset->addRdata(rdata);
+    } while (++line_count, !input.eof());
+
+    // Commit the last RRset, if any.
+    if (rrset) {
+        callback(rrset);
+    }
+}
+} // namespace dns
+} // namespace isc

+ 246 - 0
src/lib/dns/masterload.h

@@ -0,0 +1,246 @@
+// 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 __MASTERLOAD_H
+#define __MASTERLOAD_H 1
+
+#include <iosfwd>
+
+#include <boost/function.hpp>
+
+#include <exceptions/exceptions.h>
+
+#include <dns/rrset.h>
+
+namespace isc {
+namespace dns {
+class Name;
+class RRClass;
+
+/// \brief An exception that is thrown if an error occurs while loading a
+/// master zone data.
+class MasterLoadError : public isc::Exception {
+public:
+    MasterLoadError(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) {}
+};
+
+/// The type of the \c callback parameter of \c masterLoad().
+///
+/// This represents a functor object or a function that takes one parameter
+/// of type \c RRsetPtr and returns nothing.
+typedef boost::function<void(RRsetPtr)> MasterLoadCallback;
+
+///
+/// \name Master zone file loader functions.
+///
+//@{
+/// Master zone file loader from a file.
+///
+/// This function parses a given file as a master DNS zone file for
+/// the given origin name and RR class, constructs a sequence of \c RRset
+/// from the RRs containing in the file, and calls the given \c callback
+/// functor object or function with each \c RRset.
+///
+/// The \c callback parameter is a functor object or a function that
+/// takes one parameter of type \c RRsetPtr and returns nothing,
+/// i.e. \c void (see below for specific examples).
+/// More precisely, it can be anything that this form of boost::function
+/// can represent, but the caller normally doesn't have to care about
+/// that level of details.
+///
+/// The ownership of constructed RRsets is transferred to the callback
+/// and this function never uses it once it is called.
+/// The callback can freely modify the passed \c RRset.
+///
+/// This function performs minimum level of validation on the input:
+/// - Each RR is a valid textual representation per the DNS protocol.
+/// - The class of each RR must be identical to the specified RR class.
+/// - The owner name of each RR must be a subdomain of the origin name
+///   (that can be equal to the origin).
+/// - If an SOA RR is included, its owner name must be the origin name.
+/// If any of these validation checks fails, this function throws an
+/// exception of class \c MasterLoadError.
+///
+/// It does not perform other semantical checks, however.  For example,
+/// it doesn't check if an NS RR of the origin name is included or if
+/// there is more than one SOA RR.  Such further checks are the caller's
+/// (or the callback's) responsibility.
+///
+/// <b>Acceptable Format</b>
+///
+/// The current implementation only supports a restricted form of master files
+/// for simplicity.  One easy way to ensure that a handwritten zone file is
+/// acceptable to this implementation is to preprocess it with BIND 9's
+/// named-compilezone tool with both the input and output formats being
+/// "text".
+/// Here is an example:
+/// \code % named-compilezone -f text -F text -o example.com.norm
+///      example.com example.com.zone
+/// \endcode
+/// where example.com.zone is the original zone file for the "example.com"
+/// zone.  The output file is example.com.norm, which should be acceptable
+/// by this implementation.
+///
+/// Below are specific restrictions that this implementation assumes.
+/// Basically, each RR must consist of exactly one line
+/// (so there shouldn't be a multi-line RR) in the following format:
+/// \code  <owner name> <TTL> <RRCLASS> <RRTYPE> <RDATA (single line)>
+/// \endcode
+/// Here are some more details about the restrictions:
+/// - No special directives such as $TTL are supported.
+/// - The owner name must be absolute, that is, it must end with a period.
+/// - "@" is not recognized as a valid owner name.
+/// - Owner names, TTL and RRCLASS cannot be omitted.
+/// - As a corollary, a non blank line must not begin with a space character.
+/// - The order of the RR parameters is fixed, for example, this is acceptable:
+/// \code example.com. 3600 IN A 192.0.2.1
+/// \endcode
+///  but this is not even though it's valid per RFC1035:
+/// \code example.com. IN 3600 A 192.0.2.1
+/// \endcode
+/// - <TTL>, <RRCLASS>, and <RRTYPE> must be recognizable by the \c RRTTL,
+///   RRClass and RRType class implementations of this library.  In particular,
+///   as of this writing TTL must be a decimal number (a convenient extension
+///   such as "1H" instead of 3600 cannot be used).  Not all standard RR
+///   classes and RR types are supported yet, so the mnemonics of them will
+///   be rejected, too.
+/// - RR TTLs of the same RRset must be the same; even if they are different,
+///   this implementation simply uses the TTL of the first RR.
+///
+/// Blank lines and lines beginning with a semi-colon are allowed, and will
+/// be simply ignored.  Comments cannot coexist with an RR line, however.
+/// For example, this will be rejected:
+/// \code example.com. 3600 IN A 192.0.2.1 ; this is a comment
+/// \endcode
+///
+/// This implementation assumes that RRs of a single RRset are not
+/// interleaved with RRs of a different RRset.
+/// That is, the following sequence shouldn't happen:
+/// \code example.com. 3600 IN A 192.0.2.1
+/// example.com. 3600 IN AAAA 2001:db8::1
+/// example.com. 3600 IN A 192.0.2.2
+/// \endcode
+/// But it does not consider this an error; it will simply regard each RR
+/// as a separate RRset and call the callback with them separately.
+/// It is up to the callback to merge multiple RRsets into one if possible
+/// and necessary.
+///
+/// <b>Exceptions</b>
+///
+/// This function throws an exception of class \c MasterLoadError in the
+/// following cases:
+/// - Any of the validation checks fails (see the class description).
+/// - The input data is not in the acceptable format (see the details of
+///   the format above).
+/// - The specified file cannot be opened for loading.
+/// - An I/O error occurs during the loading.
+///
+/// In addition, this function requires resource allocation for parsing and
+/// constructing RRsets.  If it fails, the corresponding standard exception
+/// will be thrown.
+///
+/// The callback may throw its own function.  This function doesn't catch it
+/// and will simply propagate it towards the caller.
+///
+/// <b>Usage Examples</b>
+///
+/// A simplest example usage of this function would be to parse a zone
+/// file and (after validation) dump the content to the standard output.
+/// This is an example functor object and a call to \c masterLoad
+/// that implements this scenario:
+/// \code struct ZoneDumper {
+///     void operator()(ConstRRsetPtr rrset) const {
+///        std::cout << *rrset;
+///     }
+/// };
+/// ...
+///    masterLoad(zone_file, Name("example.com"), RRClass::IN(), ZoneDumper());
+/// \endcode
+/// Alternatively, you can use a normal function instead of a functor:
+/// \code void zoneDumper(ConstRRsetPtr rrset) {
+///    std::cout << *rrset;
+/// }
+/// ...
+///    masterLoad(zone_file, Name("example.com"), RRClass::IN(), zoneDumper);
+/// \endcode
+/// Or, if you want to use it with a member function of some other class,
+/// wrapping things with \c boost::bind would be handy:
+/// \code class ZoneDumper {
+/// public:
+///    void dump(ConstRRsetPtr rrset) const {
+///        std::cout << *rrset;
+///    }
+/// };
+/// ...
+///    ZoneDumper dumper;
+///    masterLoad(rr_stream, Name("example.com"), RRClass::IN(),
+///               boost::bind(&ZoneDumper::dump, &dumper, _1));
+/// \endcode
+/// You can find a bit more complicated examples in the unit tests code for
+/// this function.
+///
+/// <b>Implementation Notes</b>
+///
+/// The current implementation is in a preliminary level and needs further
+/// extensions.  Some design decisions may also have to be reconsidered as
+/// we gain experiences.  Those include:
+/// - We should be more flexible about the input format.
+/// - We may want to allow optional conditions.  For example, we may want to
+///   be generous about some validation failures and be able to continue
+///   parsing.
+/// - Especially if we allow to be generous, we may also want to support
+///   returning an error code instead of throwing an exception when we
+///   encounter validation failure.
+/// - We may want to support incremental loading.
+/// - If we add these optional features we may want to introduce a class
+///   that encapsulates loading status and options.
+/// - RRSIGs are currently identified as their owner name and RR type (RRSIG).
+///   In practice it should be sufficient, but technically we should also
+///   consider the Type Covered field.
+///
+/// \param filename A path to a master zone file to be loaded.
+/// \param origin The origin name of the zone.
+/// \param zone_class The RR class of the zone.
+/// \param callbck A callback functor or function that is to be called
+/// for each RRset.
+void masterLoad(const char* const filename, const Name& origin,
+                const RRClass& zone_class, MasterLoadCallback callback);
+
+/// Master zone file loader from input stream.
+///
+/// This function is same as the other version
+/// (\c masterLoad(const char* const, const Name&, const RRClass&, MasterLoadCallback))
+/// except that it takes a \c std::istream instead of a file.
+/// It extracts lines from the stream and handles each line just as a line
+/// of a file for the other version of function.
+/// All descriptions of the other version apply to this version except those
+/// specific to file I/O.
+///
+/// \param input An input stream object that is to emit zone's RRs.
+/// \param origin The origin name of the zone.
+/// \param zone_class The RR class of the zone.
+/// \param callbck A callback functor or function that is to be called for
+/// each RRset.
+void masterLoad(std::istream& input, const Name& origin,
+                const RRClass& zone_class, MasterLoadCallback callback);
+}
+//@}
+}
+
+#endif  // __MASTERLOAD_H
+
+// Local Variables:
+// mode: c++
+// End:

+ 8 - 0
src/lib/dns/message.h

@@ -517,6 +517,14 @@ private:
     MessageImpl* impl_;
 };
 
+/// \brief Pointer-like type pointing to a \c Message
+///
+/// This type is expected to be used as an argument in asynchronous
+/// callback functions.  The internal reference-counting will ensure that
+/// that ongoing state information will not be lost if the object
+/// that originated the asynchronous call falls out of scope.
+typedef boost::shared_ptr<Message> MessagePtr;
+
 std::ostream& operator<<(std::ostream& os, const Message& message);
 }
 }

+ 1 - 0
src/lib/dns/messagerenderer.h

@@ -258,6 +258,7 @@ public:
     /// \param name A \c Name object to be written.
     /// \param compress A boolean indicating whether to enable name compression.
     void writeName(const Name& name, bool compress = true);
+    //@}
 private:
     struct MessageRendererImpl;
     MessageRendererImpl* impl_;

+ 1 - 0
src/lib/dns/tests/Makefile.am

@@ -42,6 +42,7 @@ run_unittests_SOURCES += rdata_tsig_unittest.cc
 run_unittests_SOURCES += rrset_unittest.cc rrsetlist_unittest.cc
 run_unittests_SOURCES += question_unittest.cc
 run_unittests_SOURCES += rrparamregistry_unittest.cc
+run_unittests_SOURCES += masterload_unittest.cc
 run_unittests_SOURCES += message_unittest.cc
 run_unittests_SOURCES += base32hex_unittest.cc
 run_unittests_SOURCES += base64_unittest.cc

+ 268 - 0
src/lib/dns/tests/masterload_unittest.cc

@@ -0,0 +1,268 @@
+// 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 <functional>
+#include <ios>
+#include <fstream>
+#include <sstream>
+#include <string>
+#include <vector>
+
+#include <boost/bind.hpp>
+
+#include <gtest/gtest.h>
+
+#include <dns/masterload.h>
+#include <dns/name.h>
+#include <dns/rrclass.h>
+#include <dns/rrset.h>
+
+using namespace std;
+using namespace isc::dns;
+
+namespace {
+// A callback functor for masterLoad() commonly used for the following tests.
+class TestCallback : public unary_function<ConstRRsetPtr, void> {
+public:
+    TestCallback(vector<ConstRRsetPtr>& rrsets) : rrsets_(rrsets) {}
+    void operator()(ConstRRsetPtr rrset) {
+        rrsets_.push_back(rrset);
+    }
+private:
+    vector<ConstRRsetPtr>& rrsets_;
+};
+
+// A function version of TestCallback.
+void
+testCallback(ConstRRsetPtr rrset, vector<ConstRRsetPtr>* rrsets) {
+    rrsets->push_back(rrset);
+}
+
+class MasterLoadTest : public ::testing::Test {
+protected:
+    MasterLoadTest() : origin("example.com"), zclass(RRClass::IN()),
+                   callback(results) {}
+public:
+    void rrsetCallback(ConstRRsetPtr rrset) {
+        results.push_back(rrset);
+    }
+protected:
+    Name origin;
+    RRClass zclass;
+    stringstream rr_stream;
+    vector<ConstRRsetPtr> results;
+    TestCallback callback;
+};
+
+// Commonly used test RRs
+const char* const txt_rr = "example.com. 3600 IN TXT \"test data\"\n";
+const char* const a_rr1 = "www.example.com. 60 IN A 192.0.2.1\n";
+const char* const a_rr2 = "www.example.com. 60 IN A 192.0.2.2\n";
+const char* const a_rr3 = "ftp.example.com. 60 IN A 192.0.2.3\n";
+// multi-field RR case
+const char* const soa_rr = "example.com. 7200 IN SOA . . 0 0 0 0 0\n";
+
+TEST_F(MasterLoadTest, loadRRs) {
+    // a simple case: loading 3 RRs, each consists of a single RRset.
+    rr_stream << txt_rr << a_rr1 << soa_rr;
+    masterLoad(rr_stream, origin, zclass, callback);
+    ASSERT_EQ(3, results.size());
+    EXPECT_EQ(txt_rr, results[0]->toText());
+    EXPECT_EQ(a_rr1, results[1]->toText());
+    EXPECT_EQ(soa_rr, results[2]->toText());
+}
+
+TEST_F(MasterLoadTest, loadWithFunctionCallback) {
+    // The same test as loadRRs but using a normal function (not a functor
+    // object)
+    rr_stream << txt_rr << a_rr1 << soa_rr;
+    masterLoad(rr_stream, origin, zclass,
+               bind2nd(ptr_fun(testCallback), &results));
+    ASSERT_EQ(3, results.size());
+    EXPECT_EQ(txt_rr, results[0]->toText());
+    EXPECT_EQ(a_rr1, results[1]->toText());
+    EXPECT_EQ(soa_rr, results[2]->toText());
+}
+
+TEST_F(MasterLoadTest, loadWithMemFunctionCallback) {
+    // The same test as loadRRs but using a class member function (with a
+    // help of Boost.bind)
+    rr_stream << txt_rr << a_rr1 << soa_rr;
+    masterLoad(rr_stream, origin, zclass,
+               boost::bind(&MasterLoadTest::rrsetCallback, this, _1));
+    ASSERT_EQ(3, results.size());
+    EXPECT_EQ(txt_rr, results[0]->toText());
+    EXPECT_EQ(a_rr1, results[1]->toText());
+    EXPECT_EQ(soa_rr, results[2]->toText());
+}
+
+TEST_F(MasterLoadTest, loadComments) {
+    rr_stream << ";; comment line, should be skipped\n"
+              << "\n"           // blank line (should be skipped)
+              << txt_rr;
+    masterLoad(rr_stream, origin, zclass, callback);
+    ASSERT_EQ(1, results.size());
+    EXPECT_EQ(txt_rr, results[0]->toText());
+}
+
+TEST_F(MasterLoadTest, loadRRset) {
+    // load an RRset containing two RRs
+    rr_stream << a_rr1 << a_rr2;
+    masterLoad(rr_stream, origin, zclass, callback);
+    ASSERT_EQ(1, results.size());
+    EXPECT_EQ(string(a_rr1) + string(a_rr2), results[0]->toText());
+}
+
+TEST_F(MasterLoadTest, loadRRsetsOfSameType) {
+    // load two RRsets with the same RR type and different owner names.
+    // the loader must distinguish them as separate RRsets.
+    rr_stream << a_rr1 << a_rr3;
+    masterLoad(rr_stream, origin, zclass, callback);
+    ASSERT_EQ(2, results.size());
+    EXPECT_EQ(a_rr1, results[0]->toText());
+    EXPECT_EQ(a_rr3, results[1]->toText());
+}
+
+TEST_F(MasterLoadTest, loadRRsetsInterleaved) {
+    // two RRs that belongs to the same RRset (rr1 and rr2) are interleaved
+    // by another.  This is an unexpected case for this loader, but it's
+    // not considered an error.  The loader will simply treat them separate
+    // RRsets.
+    rr_stream << a_rr1 << a_rr3 << a_rr2;
+    masterLoad(rr_stream, origin, zclass, callback);
+    ASSERT_EQ(3, results.size());
+    EXPECT_EQ(a_rr1, results[0]->toText());
+    EXPECT_EQ(a_rr3, results[1]->toText());
+    EXPECT_EQ(a_rr2, results[2]->toText());
+}
+
+TEST_F(MasterLoadTest, loadWithNoEOF) {
+    // the input stream doesn't end with a new line (and the following blank
+    // line).  It should be accepted.
+    string rr_string(a_rr1);
+    rr_string.erase(rr_string.end() - 1);
+    rr_stream << rr_string;
+    masterLoad(rr_stream, origin, zclass, callback);
+    ASSERT_EQ(1, results.size());
+    EXPECT_EQ(a_rr1, results[0]->toText());
+}
+
+TEST_F(MasterLoadTest, loadEmpty) {
+    // an unusual case: empty input.  load must succeed with an empty result.
+    masterLoad(rr_stream, origin, zclass, callback);
+    EXPECT_EQ(0, results.size());   
+}
+
+TEST_F(MasterLoadTest, loadWithBeginningSpace) {
+    rr_stream << " " << a_rr1;
+    EXPECT_THROW(masterLoad(rr_stream, origin, zclass, callback),
+                 MasterLoadError);
+}
+
+TEST_F(MasterLoadTest, loadWithBeginningTab) {
+    rr_stream << "\t" << a_rr1;
+    EXPECT_THROW(masterLoad(rr_stream, origin, zclass, callback),
+                 MasterLoadError);
+}
+
+TEST_F(MasterLoadTest, loadInvalidRRClass) {
+    rr_stream << "example.com. 3600 CH TXT \"test text\"";
+    EXPECT_THROW(masterLoad(rr_stream, origin, zclass, callback),
+                 MasterLoadError);
+}
+
+TEST_F(MasterLoadTest, loadOutOfZoneData) {
+    rr_stream << "example.org. 3600 IN A 192.0.2.255";
+    EXPECT_THROW(masterLoad(rr_stream, origin, zclass, callback),
+                 MasterLoadError);
+}
+
+TEST_F(MasterLoadTest, loadNonAtopSOA) {
+    // SOA's owner name must be zone's origin.
+    rr_stream << "soa.example.com. 3600 IN SOA . . 0 0 0 0 0";
+    EXPECT_THROW(masterLoad(rr_stream, origin, zclass, callback),
+                 MasterLoadError);
+}
+
+TEST_F(MasterLoadTest, loadBadRRText) {
+    rr_stream << "example..com. 3600 IN A 192.0.2.1"; // bad owner name
+    EXPECT_THROW(masterLoad(rr_stream, origin, zclass, callback),
+                 MasterLoadError);
+
+    // currently we only support numeric TTLs
+    stringstream rr_stream2("example.com. 1D IN A 192.0.2.1");
+    EXPECT_THROW(masterLoad(rr_stream2, origin, zclass, callback),
+                 MasterLoadError);
+
+    // bad RR class text
+    stringstream rr_stream3("example.com. 3600 BAD A 192.0.2.1");
+    EXPECT_THROW(masterLoad(rr_stream3, origin, zclass, callback),
+                 MasterLoadError);
+
+    // bad RR type text
+    stringstream rr_stream4("example.com. 3600 IN BAD 192.0.2.1");
+    EXPECT_THROW(masterLoad(rr_stream4, origin, zclass, callback),
+                 MasterLoadError);
+
+    // bad RDATA text
+    stringstream rr_stream5("example.com. 3600 IN A 2001:db8::1");
+    EXPECT_THROW(masterLoad(rr_stream5, origin, zclass, callback),
+                 MasterLoadError);
+
+    // incomplete RR text
+    stringstream rr_stream6("example.com. 3600 IN A");
+    EXPECT_THROW(masterLoad(rr_stream6, origin, zclass, callback),
+                 MasterLoadError);
+
+    // owner name is not absolute
+    stringstream rr_stream7("example.com 3600 IN A 192.0.2.1");
+    EXPECT_THROW(masterLoad(rr_stream7, origin, zclass, callback),
+                 MasterLoadError);
+}
+
+// This is a helper callback to test the case the input stream becomes bad
+// in the middle of processing.
+class StreamInvalidator : public unary_function<ConstRRsetPtr, void> {
+public:
+    StreamInvalidator(stringstream& ss) : ss_(ss) {}
+    void operator()(ConstRRsetPtr) {
+        ss_.setstate(ios::badbit);
+    }
+private:
+    stringstream& ss_;
+};
+
+TEST_F(MasterLoadTest, loadBadStream) {
+    rr_stream << txt_rr << a_rr1;
+    StreamInvalidator invalidator(rr_stream);
+    EXPECT_THROW(masterLoad(rr_stream, origin, zclass, invalidator),
+                 MasterLoadError);
+}
+
+TEST_F(MasterLoadTest, loadFromFile) {
+    // The main parser is shared with the stream version, so we simply test
+    // file I/O specific parts.
+    masterLoad(TEST_DATA_SRCDIR "/masterload.txt", origin, zclass, callback);
+    ASSERT_EQ(2, results.size());
+    EXPECT_EQ(txt_rr, results[0]->toText());
+    EXPECT_EQ(string(a_rr1) + string(a_rr2), results[1]->toText());
+
+    // NULL file name.  Should result in exception.
+    EXPECT_THROW(masterLoad(NULL, origin, zclass, callback), MasterLoadError);
+
+    // Non existent file name.  Ditto.
+    EXPECT_THROW(masterLoad(TEST_DATA_BUILDDIR "/notexistent.txt", origin,
+                            zclass, callback), MasterLoadError);
+}
+} // end namespace

+ 1 - 0
src/lib/dns/tests/testdata/Makefile.am

@@ -26,6 +26,7 @@ BUILT_SOURCES += rdata_tsig_toWire5.wire
 EXTRA_DIST = gen-wiredata.py.in
 EXTRA_DIST += edns_toWire1.spec edns_toWire2.spec
 EXTRA_DIST += edns_toWire3.spec edns_toWire4.spec
+EXTRA_DIST += masterload.txt
 EXTRA_DIST += message_fromWire1 message_fromWire2
 EXTRA_DIST += message_fromWire3 message_fromWire4
 EXTRA_DIST += message_fromWire5 message_fromWire6

+ 2 - 1
src/lib/dns/tests/testdata/edns_toWire4.spec

@@ -1,5 +1,6 @@
 #
-# Same as edns_toWire1 but setting the DO bit
+# Same as edns_toWire1 but setting the DO bit, and using an unusual
+# UDP payload size
 #
 [edns]
 do: 1

+ 5 - 0
src/lib/dns/tests/testdata/masterload.txt

@@ -0,0 +1,5 @@
+;; a simple (incomplete) zone file
+
+example.com. 3600 IN TXT "test data"
+www.example.com. 60 IN A 192.0.2.1
+www.example.com. 60 IN A 192.0.2.2

+ 0 - 2
src/lib/dns/tests/tsigkey_unittest.cc

@@ -12,8 +12,6 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 
-// $Id: rrtype_unittest.cc 476 2010-01-19 00:29:28Z jinmei $
-
 #include <string>
 
 #include <gtest/gtest.h>

+ 19 - 1
src/lib/dns/tests/unittest_util.cc

@@ -25,13 +25,15 @@
 
 #include <gtest/gtest.h>
 
+#include <dns/rcode.h>
 #include <dns/name.h>
+#include <dns/message.h>
 #include <dns/tests/unittest_util.h>
 
 using namespace std;
+using namespace isc::dns;
 
 using isc::UnitTestUtil;
-using isc::dns::NameComparisonResult;
 
 namespace {
 class UnitTestUtilConfig {
@@ -175,3 +177,19 @@ UnitTestUtil::matchName(const char*, const char*,
     }
     return (::testing::AssertionSuccess());
 }
+
+void
+UnitTestUtil::createRequestMessage(Message& message,
+                                   const Opcode& opcode,
+                                   const uint16_t qid,
+                                   const Name& name,
+                                   const RRClass& rrclass,
+                                   const RRType& rrtype)
+{
+    message.clear(Message::RENDER);
+    message.setOpcode(opcode);
+    message.setRcode(Rcode::NOERROR());
+    message.setQid(qid);
+    message.addQuestion(Question(name, rrclass, rrtype));
+}
+

+ 15 - 0
src/lib/dns/tests/unittest_util.h

@@ -21,6 +21,7 @@
 #include <string>
 
 #include <dns/name.h>
+#include <dns/message.h>
 
 #include <gtest/gtest.h>
 
@@ -80,6 +81,20 @@ public:
     static ::testing::AssertionResult
     matchName(const char* nameexp1, const char* nameexp2,
               const isc::dns::Name& name1, const isc::dns::Name& name2);
+
+    ///
+    /// Populate a request message
+    ///
+    /// Create a request message in 'request_message' using the 
+    /// opcode 'opcode' and the name/class/type query tuple specified in
+    /// 'name', 'rrclass' and 'rrtype.
+    static void
+    createRequestMessage(isc::dns::Message& request_message,
+                         const isc::dns::Opcode& opcode,
+                         const uint16_t qid,
+                         const isc::dns::Name& name,
+                         const isc::dns::RRClass& rrclass,
+                         const isc::dns::RRType& rrtype);
 };
 }
 #endif // __UNITTEST_UTIL_H

+ 4 - 0
src/lib/log/Makefile.am

@@ -0,0 +1,4 @@
+AM_CXXFLAGS = $(B10_CXXFLAGS)
+
+lib_LTLIBRARIES = liblog.la
+liblog_la_SOURCES = dummylog.cc dummylog.h

+ 37 - 0
src/lib/log/dummylog.cc

@@ -0,0 +1,37 @@
+// Copyright (C) 2010  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
+// 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 "dummylog.h"
+
+#include <iostream>
+
+using namespace std;
+
+namespace isc {
+namespace log {
+
+bool denabled = false;
+string dprefix;
+
+void dlog(const string& message) {
+    if (denabled) {
+        if (!dprefix.empty()) {
+            cerr << "[" << dprefix << "] ";
+        }
+        cerr << message << endl;
+    }
+}
+
+}
+}

+ 60 - 0
src/lib/log/dummylog.h

@@ -0,0 +1,60 @@
+// Copyright (C) 2010  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
+// 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 _ISC_DUMMYLOG_H
+#define _ISC_DUMMYLOG_H 1
+
+#include <string>
+
+namespace isc {
+namespace log {
+
+/// Are we doing logging?
+extern bool denabled;
+/**
+ * \short Prefix into logs.
+ *
+ * The prefix is printed in front of every log message in square brackets.
+ * The usual convention is to put the name of program here.
+ */
+extern std::string dprefix;
+
+/**
+ * \short Temporary interface to logging.
+ *
+ * This is a temporary function to do logging. It has wrong interface currently
+ * and should be replaced by something else. It's main purpose now is to mark
+ * places where logging should happen. When it is removed, compiler will do
+ * our work of finding the places.
+ *
+ * The only thing it does is printing the dprogram prefix, message and
+ * a newline if denabled is true.
+ *
+ * There are no tests for this function, since it is only temporary and
+ * trivial. Tests will be written for the real logging framework when it is
+ * created.
+ *
+ * It has the d in front of the name so it is unlikely anyone will create
+ * a real logging function with the same name and the place wouldn't be found
+ * as a compilation error.
+ *
+ * @param message The message to log. The real interface will probably have
+ *     more parameters.
+ */
+void dlog(const std::string& message);
+
+}
+}
+
+#endif // _ISC_DUMMYLOG_H

+ 41 - 0
src/lib/nsas/Makefile.am

@@ -0,0 +1,41 @@
+SUBDIRS = . tests
+
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES) $(MULTITHREADING_FLAG)
+AM_CPPFLAGS += -I$(top_srcdir)/src/lib/dns -I$(top_builddir)/src/lib/dns
+AM_CPPFLAGS += -I$(top_srcdir)/src/lib/nsas -I$(top_builddir)/src/lib/nsas
+AM_CPPFLAGS += $(SQLITE_CFLAGS)
+AM_CXXFLAGS = $(B10_CXXFLAGS)
+
+# Some versions of GCC warn about some versions of Boost regarding
+# missing initializer for members in its posix_time.
+# https://svn.boost.org/trac/boost/ticket/3477
+# But older GCC compilers don't have the flag.
+AM_CXXFLAGS += $(WARNING_NO_MISSING_FIELD_INITIALIZERS_CFLAG)
+
+if USE_CLANGPP
+# clang++ complains about unused function parameters in some boost header
+# files.
+AM_CXXFLAGS += -Wno-unused-parameter
+endif
+
+lib_LTLIBRARIES = libnsas.la
+libnsas_la_SOURCES  = address_entry.h address_entry.cc
+libnsas_la_SOURCES += asiolink.h
+libnsas_la_SOURCES += hash.cc hash.h
+libnsas_la_SOURCES += hash_deleter.h
+libnsas_la_SOURCES += hash_key.cc hash_key.h
+libnsas_la_SOURCES += hash_table.h
+libnsas_la_SOURCES += lru_list.h
+libnsas_la_SOURCES += nameserver_address_store.cc nameserver_address_store.h
+libnsas_la_SOURCES += nameserver_address.h nameserver_address.cc
+libnsas_la_SOURCES += nameserver_entry.cc nameserver_entry.h
+libnsas_la_SOURCES += nsas_entry_compare.h
+libnsas_la_SOURCES += nsas_entry.h nsas_types.h
+libnsas_la_SOURCES += zone_entry.cc zone_entry.h
+libnsas_la_SOURCES += fetchable.h
+libnsas_la_SOURCES += address_request_callback.h
+libnsas_la_SOURCES += resolver_interface.h
+libnsas_la_SOURCES += random_number_generator.h
+
+CLEANFILES = *.gcno *.gcda

+ 7 - 0
src/lib/nsas/README

@@ -0,0 +1,7 @@
+For an overview of the Nameserver Address Store, see the requirements and design
+documents at http://bind10.isc.org/wiki/Resolver.
+
+At the time of writing (19 October 2010), the file asiolink.h is present in this
+directory only for the purposes of development.  When the recursor's
+asynchronous I/O code has been finished, this will be removed and the NSAS will
+use the "real" code.

+ 40 - 0
src/lib/nsas/TODO

@@ -0,0 +1,40 @@
+Long term:
+* Make a mechanism the cache (which does not exist at the time of writing this
+  note) will be able to notify the NSAS that something has changed (address,
+  new nameserver, etc). Because the cache will have access to the data and
+  knows when it changes (it updates its structures), it is the best place. It
+  will be caching even data like authority and additional sections. It will
+  notify us somehow (we will need to tell it when).
+
+  The changes we need to know about is when set of nameservers or set of
+  addresses for a nameserver change and when a NS record or nameserver's A or
+  AAAA record is explicitly removed from the cache.
+* Optimisation to pass max two outstanding queries on the network (but fetch
+  everything from cache right away). The first can be done by having number of
+  packets on the network, with max of 4 (each query are 2 of them, A and AAAA),
+  if it drops to 2, another one can be send.
+* Add the cache cookies/contexts.
+* Logging.
+* Remove LRU from the nameserver entries, drop them when they are not
+  referenced by any zone entry. This will remove duplicates, keep the RTTs
+  longer and will provide access to everything that exists. This is
+  tricky, though, because we need to be thread safe. There seems to be
+  solution to use weak_ptr inside the hash_table instead of shared_ptr and
+  catch the exception inside get() (and getOrAdd) and delete the dead pointer.
+* Better way to dispatch all calbacks in a list is needed. We take them out of
+  the list and dispatch them one by one.  This is wrong because when an
+  exception happens inside the callback, we lose the ones not dispatched yet.
+
+  What should be done in this situation anyway? Putting them back? Will anybody
+  still call them? Taking them one by one?
+
+  Or recommend that if the result is really needed, that destruction of it
+  should be considered failure if it wasn't called yet? Make it the default
+  (eg. signal failure by destruction or call that function from destructor)?
+* Make a zone entry hash table have multiple LRU lists, each one for part of the
+  slots. This will prevent locking contention while still keeping close to
+  the theoretical LRU behaviour (statistically, accesses to each of the part
+  should be as common as to others).
+
+  It might be a good idea to encapsulate the LRUs into the hash table directly
+  (or create a class holding both the hash table and the LRU lists).

+ 46 - 0
src/lib/nsas/address_entry.cc

@@ -0,0 +1,46 @@
+// 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$
+
+/// \file address_entry.cc
+///
+/// This file exists to define the single constant \c AddressEntry::UNREACHABLE,
+/// equal to the value \c UINT32_MAX.
+///
+/// Ideally we could use \c UINT32_MAX directly in the header file, but this
+/// constant is defined in \c stdint.h only if the macro \c __STDC_LIMIT_MACROS
+/// is defined first. (This apparently is the C89 standard.)  Defining the
+/// macro in \c address_entry.h before including \c stdint.h doesn't work as
+/// it is possible that in a source file, \c stdint.h will be included before
+/// \c address_entry.h.  In that case, the \c stdint.h include sentinel will
+/// prevent \c stdint.h being included a second time and the value won't be
+/// defined.
+///
+/// The easiest solution is the one presented here: declare the value as a
+/// static class constant, and define it in this source file.  As we can control
+/// the order of include files, this ensures that the value is defined.
+
+#define __STDC_LIMIT_MACROS
+#include <stdint.h>
+
+#include <config.h>
+
+#include "address_entry.h"
+
+namespace isc {
+namespace nsas {
+const uint32_t AddressEntry::UNREACHABLE = UINT32_MAX;
+}
+}

+ 104 - 0
src/lib/nsas/address_entry.h

@@ -0,0 +1,104 @@
+// 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 __ADDRESS_ENTRY_H
+#define __ADDRESS_ENTRY_H
+
+/// \brief Address Entry
+///
+/// Lightweight class that couples an address with a RTT and provides some
+/// convenience methods for accessing and updating the information.
+
+#include <stdint.h>
+#include "asiolink.h"
+
+namespace isc {
+namespace nsas {
+
+class AddressEntry {
+public:
+    /// Creates an address entry given IOAddress entry and RTT
+    /// This is the only constructor; the default copy constructor and
+    /// assignment operator are valid for this object.
+    ///
+    /// \param address Address object representing this address
+    /// \param rtt Initial round-trip time
+    AddressEntry(const asiolink::IOAddress& address, uint32_t rtt = 0) :
+        address_(address), rtt_(rtt), dead_until_(0)
+    {}
+
+    /// \return Address object
+    asiolink::IOAddress getAddress() const {
+        return address_;
+    }
+
+    /// \return Current round-trip time
+    uint32_t getRTT() {
+        if(dead_until_ != 0 && time(NULL) >= dead_until_){
+            dead_until_ = 0;
+            rtt_ = 1; //reset the rtt to a small value so it has an opportunity to be updated
+        }
+
+        return rtt_;
+    }
+
+    /// Set current RTT
+    ///
+    /// \param rtt New RTT to be associated with this address
+    void setRTT(uint32_t rtt) {
+        if(rtt == UNREACHABLE){
+            dead_until_ = time(NULL) + 5*60;//Cache the unreachable server for 5 minutes (RFC2308 sec7.2)
+        }
+
+        rtt_ = rtt;
+    }
+
+    /// Mark address as unreachable.
+    void setUnreachable() {
+        setRTT(UNREACHABLE);   // Largest long number is code for unreachable
+    }
+
+    /// Check if address is unreachable
+    ///
+    /// \return true if the address is unreachable, false if not
+    bool isUnreachable() {
+        return (getRTT() == UNREACHABLE); // The getRTT() will check the cache time for unreachable server
+    }
+
+    /// \return true if the object is a V4 address
+    bool isV4() const {
+        return (address_.getFamily() == AF_INET);
+    }
+
+    /// \return true if the object is a V6 address
+    bool isV6() const {
+        return (address_.getFamily() == AF_INET6);
+    }
+
+    // Next element is defined public for testing
+    static const uint32_t UNREACHABLE;  ///< RTT indicating unreachable address
+
+private:
+    asiolink::IOAddress address_;       ///< Address
+    uint32_t        rtt_;               ///< Round-trip time
+    time_t  dead_until_;                ///< Dead time for unreachable server
+};
+
+}   // namespace dns
+}   // namespace isc
+
+
+#endif // __ADDRESS_ENTRY_H

+ 74 - 0
src/lib/nsas/address_request_callback.h

@@ -0,0 +1,74 @@
+// 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 __ADDRESS_REQUEST_CALLBACK_H
+#define __ADDRESS_REQUEST_CALLBACK_H
+
+#include "asiolink.h"
+#include "nameserver_address.h"
+
+namespace isc {
+namespace nsas {
+
+/// \brief Callback When Address Obtained
+///
+/// This is the callback object used to return an address of a nameserver to a
+/// caller.  It (or a subclass of it) is passed to the NSAS when a request is
+/// made for the address of a nameserver.  When an address is available,
+/// methods on the passed objects are called.
+///
+/// Note that there is no guarantee as to when the methods are called; they
+/// could be called after the function call that made the address request has
+/// returned the caller.  Equally, the call could complete before that function
+/// call returns.  It is up to the caller to handle all cases.
+///
+/// In terms of use, a shared pointer to this object is passed to the NSAS.
+/// The NSAS will store the object via a shared pointer and after the callback
+/// will delete the pointer.  Whether this results in the deletion of the
+/// callback object is up to the caller - if the caller wants to retain it
+/// they should keep the shared pointer.
+
+class AddressRequestCallback {
+public:
+
+    /// Default constructor, copy contructor and assignment operator
+    /// are implicitly present and are OK.
+
+    /// \brief Virtual Destructor
+    virtual ~AddressRequestCallback()
+    {}
+
+    /// \brief Success Callback
+    ///
+    /// This method is used when an address has been retrieved for the request.
+    ///
+    /// \param address Address to be used to access the nameserver.
+    virtual void success(const NameserverAddress& address) = 0;
+
+    /// \brief Unreachable
+    ///
+    /// This method is called when a request is made for an address, but all
+    /// the addresses for the zone are marked as unreachable.  This may be
+    /// due to the NS records being unobtainable, or the A records for known
+    /// nameservers being unobtainable.
+    virtual void unreachable() = 0;
+
+};
+
+} // namespace nsas
+} // namespace isc
+
+#endif // __ADDRESS_REQUEST_CALLBACK_H

+ 60 - 0
src/lib/nsas/asiolink.h

@@ -0,0 +1,60 @@
+// 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 __ASIOLINK_H
+#define __ASIOLINK_H
+
+#include <string>
+#include <sys/socket.h>
+
+namespace asiolink {
+
+/// \brief IO Address Dummy Class
+///
+/// As part of ther recursor, Evan has written the asiolink.h file, which
+/// encapsulates some of the boost::asio classes.  Until these are checked
+/// into trunk and merged with this branch, these dummy classes should fulfill
+/// their function.
+
+class IOAddress {
+public:
+    /// \param address_str String representing the address
+    IOAddress(const std::string& address_str) : address_(address_str)
+    {}
+
+    /// \param Just a virtual destructor
+    virtual ~ IOAddress() { }
+
+    /// \return Textual representation of the address
+    std::string toText() const
+    {return address_;}
+
+    /// \return Address family of the address
+    virtual short getFamily() const {
+        return ((address_.find(".") != std::string::npos) ? AF_INET : AF_INET6);
+    }
+
+    /// \return true if two addresses are equal
+    bool equal(const IOAddress& address)
+    {return (toText() == address.toText());}
+
+private:
+    std::string     address_;       ///< Address represented
+};
+
+}   // namespace asiolink
+
+#endif // __ASIOLINK_H

+ 68 - 0
src/lib/nsas/fetchable.h

@@ -0,0 +1,68 @@
+// Copyright (C) 2010  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
+// 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 __FETCHABLE_H
+#define __FETCHABLE_H
+
+/**
+ * \file fetchable.h
+ * \short Interface of information that can be fetched.
+ */
+
+namespace isc {
+namespace nsas {
+
+/**
+ * \short Interface of information that can be fetched.
+ *
+ * This just holds a state of information that can be fetched from somewhere.
+ * No locking is performed, if it is desirable, it should be locked manually.
+ */
+class Fetchable {
+    public:
+        /// \short States the Fetchable object can be in.
+        enum State {
+            /// \short No one yet asked for the information.
+            NOT_ASKED,
+            /// \short The information is too old and should not be used.
+            EXPIRED,
+            /// \short The information is asked for but it did not arrive.
+            IN_PROGRESS,
+            /// \short It is not possible to get the information.
+            UNREACHABLE,
+            /// \short The information is already present.
+            READY
+        };
+        /// \short Constructors
+        //@{
+        /// This creates the Fetchable object in the given state.
+        Fetchable(State state = NOT_ASKED) :
+            state_(state)
+        { }
+        //@}
+        /// \short Getter and setter of current state.
+        //@{
+        State getState() const { return state_; }
+        void setState(State state) { state_ = state; }
+        //@}
+    private:
+        State state_;
+};
+
+} // namespace nsas
+} // namespace isc
+
+#endif // __FETCHABLE_H

+ 170 - 0
src/lib/nsas/hash.cc

@@ -0,0 +1,170 @@
+// 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$
+
+/*! \file
+ * Some parts of this code were copied from BIND-9, which in turn was derived
+ * from universal hash function libraries of Rice University.
+
+\section license UH Universal Hashing Library
+
+Copyright ((c)) 2002, Rice University
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+    * Redistributions of source code must retain the above copyright
+    notice, this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above
+    copyright notice, this list of conditions and the following
+    disclaimer in the documentation and/or other materials provided
+    with the distribution.
+
+    * Neither the name of Rice University (RICE) nor the names of its
+    contributors may be used to endorse or promote products derived
+    from this software without specific prior written permission.
+
+
+This software is provided by RICE and the contributors on an "as is"
+basis, without any representations or warranties of any kind, express
+or implied including, but not limited to, representations or
+warranties of non-infringement, merchantability or fitness for a
+particular purpose. In no event shall RICE or contributors be liable
+for any direct, indirect, incidental, special, exemplary, or
+consequential damages (including, but not limited to, procurement of
+substitute goods or services; loss of use, data, or profits; or
+business interruption) however caused and on any theory of liability,
+whether in contract, strict liability, or tort (including negligence
+or otherwise) arising in any way out of the use of this software, even
+if advised of the possibility of such damage.
+*/
+
+#include <cassert>
+#include <stdlib.h>
+#include <algorithm>
+#include <cassert>
+#include <string>
+
+#include <config.h>
+
+#include "hash.h"
+
+using namespace std;
+
+namespace isc {
+namespace nsas {
+
+// Constructor.
+
+Hash::Hash(uint32_t tablesize, uint32_t maxkeylen, bool randomise) :
+    tablesize_(tablesize), maxkeylen_(min<uint32_t>(maxkeylen,
+        (255 - sizeof(uint16_t))))
+{
+    // (Code taken from BIND-9)
+    //
+    // Check to see that we can cope with the maximum key length which, due
+    // to the limitations, should not be more than 255 in total.  The actual
+    // number of characters in the name that are considered is reduced to
+    // ensure that the class is taken into account in the hash.  (This accounts
+    // for the "+ sizeof(uint16_t)" in the calculations below.
+    //
+    // Overflow check.  Since our implementation only does a modulo
+    // operation at the last stage of hash calculation, the accumulator
+    // must not overflow.
+    hash_accum_t overflow_limit =
+        1 << (((sizeof(hash_accum_t) - sizeof(hash_random_t))) * 8);
+    if (overflow_limit < (maxkeylen_ + sizeof(uint16_t) + 1) * 0xff) {
+        isc_throw(KeyLengthTooLong, "Hash key length too long for Hash class");
+    }
+
+    // Initialize the random number generator with the current time.
+    // TODO: Use something other than pseudo-random numbers.
+    union {
+        unsigned int    seed;
+        time_t          curtime;
+    } init_value;
+
+    if (randomise) {
+        init_value.curtime = time(NULL);
+    }
+    else {
+        init_value.seed = 0;
+    }
+    srandom(init_value.seed);
+
+    // Fill in the random vector.
+    randvec_.reserve(maxkeylen_ + sizeof(uint16_t) + 1);
+    for (uint32_t i = 0; i < (maxkeylen + sizeof(uint16_t) + 1); ++i) {
+        randvec_.push_back(static_cast<hash_random_t>(random() & 0xffff));
+    }
+    assert(sizeof(hash_random_t) == 2); // So that the "& 0xffff" is valid
+
+    // Finally, initialize the mapping table for uppercase to lowercase
+    // characters.  A table is used as indexing a table is faster than calling
+    // the tolower() function.
+    casemap_.reserve(256);
+    for (int i = 0; i < 256; ++i) {
+        casemap_.push_back(i);
+    }
+    for (int i = 'A'; i <= 'Z'; ++i) {
+        casemap_[i] += ('a' - 'A');
+    }
+}
+
+
+uint32_t Hash::operator()(const HashKey& key, bool ignorecase) const
+{
+    // Calculation as given in BIND-9.
+    hash_accum_t partial_sum = 0;
+    uint32_t i = 0;                 // Used after the end of the loop
+
+    // Perform the hashing.  If the key length if more than the maximum we set
+    // up this hash for, ignore the excess.
+    if (ignorecase) {
+        for (i = 0; i < min(key.keylen, maxkeylen_); ++i) {
+            partial_sum += mapLower(key.key[i]) * randvec_[i];
+        }
+    } else {
+        for (i = 0; i < min(key.keylen, maxkeylen_); ++i) {
+            partial_sum += key.key[i] * randvec_[i];
+        }
+    }
+
+    // Add the hash of the class code
+    union {
+        uint16_t    class_code;                 // Copy of the class code
+        char        bytes[sizeof(uint16_t)];    // Byte equivalent
+    } convert;
+
+    convert.class_code = key.class_code.getCode();
+    for (int j = 0; j < sizeof(uint16_t); ++j, ++i) {
+        partial_sum += convert.bytes[j] * randvec_[i];
+    }
+
+    // ... and finish up.
+    partial_sum += randvec_[i];
+
+    // Determine the hash value
+    uint32_t value = partial_sum % prime32_;
+
+    // ... and round it to fit the table size
+    return (value % tablesize_);
+}
+
+} // namespace nsas
+} // namespace isc

+ 127 - 0
src/lib/nsas/hash.h

@@ -0,0 +1,127 @@
+// 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 __HASH_H
+#define __HASH_H
+
+#include <stdint.h>
+#include <vector>
+
+#include <exceptions/exceptions.h>
+
+#include "hash_key.h"
+
+namespace isc {
+namespace nsas {
+
+/// \brief Too Long Key Length
+///
+/// Thrown if the expected maximum key length is too long for the data types
+/// declared in the class.
+class KeyLengthTooLong : public isc::Exception {
+public:
+    KeyLengthTooLong(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what)
+    {}
+};
+
+
+/// \brief Hash Calculation
+///
+/// Class abstracting the mechanics of the hash calculation.
+class Hash {
+public:
+
+    /// \brief Constructor
+    ///
+    /// Constructs the hash table and initialises all data structures needed
+    /// for the hashing.
+    ///
+    /// \param tablesize Size of the hash table.  For best performance, this
+    /// should be a prime number.
+    /// \param maxkeylen Maximum length (in bytes) of a key to be hashed.
+    /// calculation will return a value between 0 and N-1.  The default
+    /// value of 255 is the maximum size of a DNS name.
+    /// \param randomise If true (the default), the pseudo-random number
+    /// generator is seeded with the current time.  Otherwise it is initialised
+    /// to a known sequence.  This is principally for unit tests, where a random
+    /// sequence could lead to problems in checking results.
+    Hash(uint32_t tablesize, uint32_t maxkeylen = 255, bool randomise = true);
+
+    /// \bool Virtual Destructor
+    virtual ~Hash()
+    {}
+
+    /// \brief Return Size
+    ///
+    /// \return The hash table size with which this object was initialized
+    virtual uint32_t tableSize() const {
+        return tablesize_;
+    }
+
+    /// \brief Return Key Length
+    ///
+    /// \return Maximum length of a key that this class can cope with.
+    virtual uint32_t maxKeyLength() const {
+        return maxkeylen_;
+    }
+
+    /// \brief Hash Value
+    ///
+    /// \param key Parameters comprising the key to be hashed.
+    /// \param ignorecase true for case to be ignored when calculating the
+    /// hash value, false for it to be taken into account.
+    ///
+    /// \return Hash value, a number between 0 and N-1.
+    virtual uint32_t operator()(const HashKey& key, 
+        bool ignorecase = true) const;
+
+    /// \brief Map Lower Case to Upper Case
+    ///
+    /// Equivalent of tolower(), but using a table lookup instead of a
+    /// function call.  This should make the mapping faster.
+    ///
+    /// \param inchar Input character
+    ///
+    /// \return Mapped character
+    virtual unsigned char mapLower(unsigned char inchar) const {
+        return casemap_[inchar];
+    }
+
+private:
+
+    ///  \name Local Typedefs
+    ///
+    /// Typedefs for use in the code.  This makes it easier to compare the
+    /// hashing code with that in BIND-9.
+    //@{
+    typedef uint32_t hash_accum_t;  ///< Accumulator
+    typedef uint16_t hash_random_t; ///< Random number used in hash
+    //@}
+
+    uint32_t        tablesize_;     ///< Size of the hash table
+    uint32_t        maxkeylen_;     ///< Maximum key length
+    std::vector<unsigned char> casemap_; ///< Case mapping table
+    std::vector<hash_random_t> randvec_; ///< Vector of random numbers
+
+    static const uint32_t prime32_ = 0xfffffffb;    ///< 2^32 - 5
+                                    ///< Specifies range of hash output
+};
+
+} // namspace nsas
+} // namespace isc
+
+#endif // __HASH_H

+ 0 - 0
src/lib/nsas/hash_deleter.h


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