Browse Source

merge #502 : iterate over RRs

chenzhengzhang 14 years ago
parent
commit
a5bd8c0394
100 changed files with 3063 additions and 836 deletions
  1. 0 1
      AUTHORS
  2. 57 2
      ChangeLog
  3. 1 7
      Makefile.am
  4. 1 1
      README
  5. 8 6
      configure.ac
  6. 1 1
      doc/Doxyfile
  7. 3 0
      doc/Makefile.am
  8. 0 13
      doc/guide/Makefile
  9. 16 0
      doc/guide/Makefile.am
  10. 120 32
      doc/guide/bind10-guide.html
  11. 163 17
      doc/guide/bind10-guide.xml
  12. 1 0
      doc/version.ent.in
  13. 5 0
      src/bin/auth/auth.spec.pre.in
  14. 47 10
      src/bin/auth/auth_srv.cc
  15. 25 10
      src/bin/auth/auth_srv.h
  16. 98 25
      src/bin/auth/b10-auth.8
  17. 91 20
      src/bin/auth/b10-auth.xml
  18. 0 2
      src/bin/auth/benchmarks/query_bench.cc
  19. 0 2
      src/bin/auth/change_user.cc
  20. 0 2
      src/bin/auth/change_user.h
  21. 0 2
      src/bin/auth/common.h
  22. 28 0
      src/bin/auth/config.cc
  23. 2 33
      src/bin/auth/main.cc
  24. 0 2
      src/bin/auth/statistics.cc
  25. 0 2
      src/bin/auth/statistics.h
  26. 7 7
      src/bin/auth/tests/auth_srv_unittest.cc
  27. 0 2
      src/bin/auth/tests/change_user_unittest.cc
  28. 2 4
      src/bin/auth/tests/command_unittest.cc
  29. 46 0
      src/bin/auth/tests/config_unittest.cc
  30. 253 348
      src/bin/auth/tests/query_unittest.cc
  31. 0 2
      src/bin/auth/tests/run_unittests.cc
  32. 0 2
      src/bin/auth/tests/statistics_unittest.cc
  33. 0 1
      src/bin/bind10/bind10.xml
  34. 53 4
      src/bin/bindctl/bindctl.1
  35. 87 3
      src/bin/bindctl/bindctl.xml
  36. 0 2
      src/bin/cfgmgr/b10-cfgmgr.py.in
  37. 0 1
      src/bin/cfgmgr/b10-cfgmgr.xml
  38. 0 2
      src/bin/cfgmgr/tests/b10-cfgmgr_test.py.in
  39. 0 1
      src/bin/cmdctl/b10-cmdctl.xml
  40. 0 1
      src/bin/loadzone/b10-loadzone.xml
  41. 0 1
      src/bin/loadzone/tests/correct/mix1.db
  42. 0 1
      src/bin/loadzone/tests/correct/ttlext.db
  43. 82 9
      src/bin/msgq/msgq.py.in
  44. 0 1
      src/bin/msgq/msgq.xml
  45. 121 2
      src/bin/msgq/tests/msgq_test.py
  46. 1 0
      src/bin/resolver/Makefile.am
  47. 39 6
      src/bin/resolver/b10-resolver.8
  48. 67 6
      src/bin/resolver/b10-resolver.xml
  49. 0 2
      src/bin/resolver/main.cc
  50. 136 120
      src/bin/resolver/resolver.cc
  51. 20 3
      src/bin/resolver/resolver.h
  52. 26 0
      src/bin/resolver/resolver.spec.pre.in
  53. 259 0
      src/bin/resolver/response_classifier.cc
  54. 138 0
      src/bin/resolver/response_classifier.h
  55. 2 0
      src/bin/resolver/tests/Makefile.am
  56. 31 6
      src/bin/resolver/tests/resolver_config_unittest.cc
  57. 15 3
      src/bin/resolver/tests/resolver_unittest.cc
  58. 494 0
      src/bin/resolver/tests/response_classifier_unittest.cc
  59. 0 1
      src/bin/stats/b10-stats.xml
  60. 0 1
      src/bin/stats/stats.py.in
  61. 0 1
      src/bin/stats/stats_stub.py.in
  62. 0 1
      src/bin/stats/tests/b10-stats_stub_test.py
  63. 0 1
      src/bin/stats/tests/b10-stats_test.py
  64. 0 1
      src/bin/stats/tests/fake_time.py
  65. 0 1
      src/bin/stats/tests/isc/cc/session.py
  66. 0 1
      src/bin/stats/tests/isc/config/ccsession.py
  67. 0 2
      src/bin/stats/tests/isc/util/process.py
  68. 0 2
      src/bin/stats/tests/isc/utils/process.py
  69. 0 1
      src/bin/usermgr/b10-cmdctl-usermgr.xml
  70. 0 1
      src/bin/xfrin/b10-xfrin.xml
  71. 0 2
      src/bin/xfrin/tests/xfrin_test.py
  72. 19 5
      src/bin/xfrin/xfrin.py.in
  73. 20 0
      src/bin/xfrout/b10-xfrout.8
  74. 27 1
      src/bin/xfrout/b10-xfrout.xml
  75. 22 2
      src/bin/zonemgr/b10-zonemgr.8
  76. 31 4
      src/bin/zonemgr/b10-zonemgr.xml
  77. 3 0
      src/bin/zonemgr/tests/zonemgr_test.py
  78. 5 1
      src/bin/zonemgr/zonemgr.py.in
  79. 222 12
      src/lib/asiolink/asiolink.cc
  80. 35 4
      src/lib/asiolink/asiolink.h
  81. 2 3
      src/lib/asiolink/internal/tcpdns.h
  82. 6 3
      src/lib/asiolink/internal/udpdns.h
  83. 0 2
      src/lib/asiolink/ioaddress.cc
  84. 0 2
      src/lib/asiolink/ioaddress.h
  85. 0 2
      src/lib/asiolink/ioendpoint.cc
  86. 0 2
      src/lib/asiolink/ioendpoint.h
  87. 0 2
      src/lib/asiolink/iomessage.h
  88. 0 2
      src/lib/asiolink/iosocket.cc
  89. 0 2
      src/lib/asiolink/iosocket.h
  90. 6 5
      src/lib/asiolink/tcpdns.cc
  91. 109 16
      src/lib/asiolink/tests/asiolink_unittest.cc
  92. 0 2
      src/lib/asiolink/tests/run_unittests.cc
  93. 10 6
      src/lib/asiolink/udpdns.cc
  94. 0 2
      src/lib/bench/benchmark.h
  95. 0 2
      src/lib/bench/benchmark_util.cc
  96. 0 2
      src/lib/bench/benchmark_util.h
  97. 0 2
      src/lib/bench/example/search_bench.cc
  98. 0 2
      src/lib/bench/tests/benchmark_unittest.cc
  99. 0 2
      src/lib/bench/tests/loadquery_unittest.cc
  100. 0 0
      src/lib/bench/tests/run_unittests.cc

+ 0 - 1
AUTHORS

@@ -1 +0,0 @@
-jreed

+ 57 - 2
ChangeLog

@@ -1,12 +1,67 @@
+  159.	[func]		smann
+	The resolver now has a configurable set of root servers to start
+	resolving at (called root_addresses). By default these are not
+	(yet) filled in. If empty, a hardcoded address for f-root will be
+	used right now.
+	(Trac #483, git a07e078b4feeb01949133fc88c9939254c38aa7c)
+
+  158.	[func]		jelte
+	The Resolver module will now do (very limited) resolving, if not
+	set to forwarding mode (i.e. if the configuration option
+	forward_addresses is left empty). It only supports referrals that
+	contain glue addresses at this point, and does no other processing
+	of authoritative answers.
+	(Trac #484, git 7b84de4c0e11f4a070e038ca4f093486e55622af)
+
+  157.  [bug]       vorner
+	One frozen process no longer freezes the whole b10-msgq. It caused the
+	whole system to stop working.
+	(Trac #420, git 93697f58e4d912fa87bc7f9a591c1febc9e0d139)
+
+  156.	[func]		stephen
+	Added ResponseClassifier class to examine response from
+	a server and classify it into one of several categories.
+	(Trac #487, git 18491370576e7438c7893f8551bbb8647001be9c)
+
+bind10-devel-20110120 released on January 20, 2011
+
+  155.	[doc]		jreed
+	Miscellaneous documentation improvements for man pages and
+	the guide, including auth, resolver, stats, xfrout, and
+	zonemgr.  (git c14c4741b754a1eb226d3bdc3a7abbc4c5d727c0)
+
+  154.	[bug]		jinmei
+	b10-xfrin/b10-zonemgr: Fixed a bug where these programs didn't
+	receive command responses from CC sessions.  Eventually the
+	receive buffer became full, and many other components that rely
+	on CC channels would stall (as noted in #420 and #513).  This is
+	an urgent care fix due to the severity of the problem; we'll need
+	to revisit it for cleaner fix later. (Trac #516, git 62c72fc)
+
+  153.	[bug]		jelte
+	b10-cfgmgr: Fixed a bug where configuration updates sometimes
+	lost previous settings in the configuration manager.
+	(Trac #427, git 2df894155657754151e0860e2ca9cdbed7317c70)
+
+  152.	[func]*		jinmei
+	b10-auth: Added new configuration variable "statistics-interval"
+	to allow the user to change the timer interval for periodic
+	statistics updates.  The update can also be disabled by setting
+	the value to 0.  Disabling statistics updates will also work as
+	a temporary workaround of a known issue that b10-auth can block in
+	sending statistics and stop responding to queries as a result.
+	(Trac #513, git 285c5ee)
+
   151.  [bug]		smann
 	lib/log/dummylog.h: 
 	lib/log/dummylog.cc: Modify dlog so that it takes an optional 2nd
         argument of type bool (true or false). This flag, if set, will cause
         the message to be printed whether or not -v is chosen.
         (trac #432, git 880220478c3e8702d56d761b1e0b21b77d08ee5a)
+
   150.  [bug]		jelte
 	b10-cfgmgr: No longer save the configuration on exit. Configuration
-	is already saved if it is changed succesfully, so writing it on
+	is already saved if it is changed successfully, so writing it on
 	exit (and hence, when nothing has changed too) is unnecessary and
 	may even cause problems.
 	(Trac #435, git fd7baa38c08d54d5b5f84930c1684c436d2776dc)
@@ -256,7 +311,7 @@ bind10-devel-20101201 released on December 01, 2010
 
   112.	[func]		zhang likun
 	Add one mixin class to override the naive serve_forever() provided
-	in python library socketserver. Instead of polling for shutdwon
+	in python library socketserver. Instead of polling for shutdown
 	every poll_interval seconds, one socketpair is used to wake up
 	the waiting server. (Trac #352, svn r3366)
 

+ 1 - 7
Makefile.am

@@ -1,4 +1,4 @@
-SUBDIRS = src
+SUBDIRS = doc src
 USE_LCOV=@USE_LCOV@
 LCOV=@LCOV@
 GENHTML=@GENHTML@
@@ -282,9 +282,3 @@ EXTRA_DIST += ext/asio/asio/is_write_buffered.hpp
 EXTRA_DIST += ext/asio/asio/buffered_read_stream_fwd.hpp
 EXTRA_DIST += ext/asio/asio/socket_acceptor_service.hpp
 EXTRA_DIST += ext/asio/asio.hpp
-
-## include the guide in tarball, later will include the other parts there
-## but they cleanup.
-EXTRA_DIST += doc/guide/bind10-guide.css
-EXTRA_DIST += doc/guide/bind10-guide.html
-EXTRA_DIST += doc/guide/bind10-guide.xml

+ 1 - 1
README

@@ -48,7 +48,7 @@ Simple build instructions:
   ./configure
   make
 
-If building from Subversion repository, run:
+If building from Git repository, run:
 
   autoreconf --install
 

+ 8 - 6
configure.ac

@@ -2,7 +2,7 @@
 # Process this file with autoconf to produce a configure script.
 
 AC_PREREQ([2.59])
-AC_INIT(bind10-devel, 20101201, bind10-dev@isc.org)
+AC_INIT(bind10-devel, 20110120, bind10-dev@isc.org)
 AC_CONFIG_SRCDIR(README)
 AM_INIT_AUTOMAKE
 AC_CONFIG_HEADERS([config.h])
@@ -83,7 +83,7 @@ case "$host" in
 	CPPFLAGS="$CPPFLAGS -D_XPG4_2 -D__EXTENSIONS__"
 	;;
 *-apple-darwin*)
-	# libtool doesn't work pefectly with Darwin: libtool embeds the
+	# libtool doesn't work perfectly with Darwin: libtool embeds the
 	# final install path in dynamic libraries and our loadable python
 	# modules always refer to that path even if it's loaded within the
 	# source tree.  This prevents pre-install tests from working.
@@ -202,7 +202,7 @@ fi
 
 # Compiler dependent settings: define some mandatory CXXFLAGS here.
 # We also use a separate variable B10_CXXFLAGS.  This will (and should) be
-# used as the default value for each specifc AM_CXXFLAGS:
+# used as the default value for each specific AM_CXXFLAGS:
 # AM_CXXFLAGS = $(B10_CXXFLAGS)
 # AM_CXXFLAGS += ... # add module specific flags
 # We need this so that we can disable some specific compiler warnings per
@@ -555,7 +555,7 @@ fi
 # So, for the moment, we simply disable the use of /dev/poll.  Unless we
 # implement recursive DNS server with randomized ports, we don't need the
 # scalability that /dev/poll can provide, so this decision wouldn't affect
-# run time performance.  Hpefully we can find a better solution or the ASIO
+# run time performance.  Hopefully we can find a better solution or the ASIO
 # code will be updated by the time we really need it.
 AC_CHECK_HEADERS(sys/devpoll.h, ac_cv_have_devpoll=yes, ac_cv_have_devpoll=no)
 if test "X$ac_cv_have_devpoll" = "Xyes" -a "X$GXX" = "Xyes"; then
@@ -574,6 +574,8 @@ AC_ARG_ENABLE(install-configurations,
 AM_CONDITIONAL(INSTALL_CONFIGURATIONS, test x$install_configurations = xyes || test x$install_configurations = xtrue)
 
 AC_CONFIG_FILES([Makefile
+                 doc/Makefile
+                 doc/guide/Makefile
                  src/Makefile
                  src/bin/Makefile
                  src/bin/bind10/Makefile
@@ -655,7 +657,8 @@ AC_CONFIG_FILES([Makefile
                  src/lib/nsas/Makefile
                  src/lib/nsas/tests/Makefile
                ])
-AC_OUTPUT([src/bin/cfgmgr/b10-cfgmgr.py
+AC_OUTPUT([doc/version.ent
+           src/bin/cfgmgr/b10-cfgmgr.py
            src/bin/cfgmgr/tests/b10-cfgmgr_test.py
            src/bin/cmdctl/cmdctl.py
            src/bin/cmdctl/run_b10-cmdctl.sh
@@ -712,7 +715,6 @@ AC_OUTPUT([src/bin/cfgmgr/b10-cfgmgr.py
            chmod +x src/bin/cmdctl/run_b10-cmdctl.sh
            chmod +x src/bin/xfrin/run_b10-xfrin.sh
            chmod +x src/bin/xfrout/run_b10-xfrout.sh
-           chmod +x src/bin/resolver/run_b10-resolver.sh
            chmod +x src/bin/zonemgr/run_b10-zonemgr.sh
            chmod +x src/bin/stats/tests/stats_test
            chmod +x src/bin/stats/run_b10-stats.sh

+ 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 ../src/lib/log ../src/lib/asiolink/ ../src/lib/nsas
+INPUT                  = ../src/lib/cc ../src/lib/config ../src/lib/dns ../src/lib/exceptions ../src/lib/datasrc ../src/bin/auth ../src/bin/resolver ../src/lib/bench ../src/lib/log ../src/lib/asiolink/ ../src/lib/nsas ../src/lib/testutils
 
 # 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

+ 3 - 0
doc/Makefile.am

@@ -0,0 +1,3 @@
+SUBDIRS = guide
+
+EXTRA_DIST = version.ent.in

+ 0 - 13
doc/guide/Makefile

@@ -1,13 +0,0 @@
-#
-# Quick and dirty makefile
-#
-
-bind10-guide.html: bind10-guide.xml
-	xsltproc --novalid --xinclude --nonet \
-		-o bind10-guide.html \
-		--stringparam html.stylesheet bind10-guide.css \
-		http://docbook.sourceforge.net/release/xsl/current/html/docbook.xsl \
-		bind10-guide.xml
-
-clean:
-	rm -f bind10-guide.html

+ 16 - 0
doc/guide/Makefile.am

@@ -0,0 +1,16 @@
+EXTRA_DIST = bind10-guide.css
+EXTRA_DIST += bind10-guide.html
+EXTRA_DIST += bind10-guide.xml
+
+# This is not a "man" manual, but reuse this for now for docbook.
+if ENABLE_MAN
+
+bind10-guide.html: bind10-guide.xml
+	xsltproc --novalid --xinclude --nonet \
+		--path $(top_builddir)/doc \
+		-o $@ \
+		--stringparam html.stylesheet $(srcdir)/bind10-guide.css \
+		http://docbook.sourceforge.net/release/xsl/current/html/docbook.xsl \
+		$(srcdir)/bind10-guide.xml
+
+endif

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


+ 163 - 17
doc/guide/bind10-guide.xml

@@ -2,6 +2,8 @@
 <!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
 "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd" [
 <!ENTITY mdash  "&#x2014;" >
+<!ENTITY % version SYSTEM "version.ent">
+%version;
 ]>
 <book>
   <?xml-stylesheet href="bind10-guide.css" type="text/css"?>
@@ -15,13 +17,20 @@
     </copyright>
 
     <abstract>
-      <para>This is the reference guide for BIND 10.</para>
-      <para>
-        The most up-to-date version of this document, along with other documents
-        for BIND 10, can be found at
-        <ulink url="http://bind10.isc.org/docs"/>.
+      <para>BIND 10 is a Domain Name System (DNS) suite managed by
+	Internet Systems Consortium (ISC). It includes DNS libraries
+	and modular components for controlling authoritative and
+	recursive DNS servers.
       </para>
-    </abstract>
+      <para>
+        This is the reference guide for BIND 10 version &__VERSION__;.
+	The most up-to-date version of this document, along with
+	other documents for BIND 10, can be found at <ulink
+	url="http://bind10.isc.org/docs"/>.  </para> </abstract>
+
+      <releaseinfo>This is the reference guide for BIND 10 version
+        &__VERSION__;.</releaseinfo>
+
   </bookinfo>
 
   <chapter id="intro">
@@ -35,8 +44,8 @@
 
     <note>
       <para>
-        This guide covers the experimental prototype version of
-        BIND 10.
+        This guide covers the experimental prototype of
+        BIND 10 version &__VERSION__;.
       </para>
     </note>
 
@@ -51,7 +60,7 @@
       <title>Supported Platforms</title>
       <para>
   BIND 10 builds have been tested on Debian GNU/Linux 5,
-  Ubuntu 9.10, NetBSD 5, Solaris 10, FreeBSD 7, and CentOS
+  Ubuntu 9.10, NetBSD 5, Solaris 10, FreeBSD 7 and 8, and CentOS
   Linux 5.3.
 
   It has been tested on Sparc, i386, and amd64 hardware
@@ -115,6 +124,7 @@
       <para>
 
         <itemizedlist>
+
           <listitem>
             <simpara>
               <command>b10-msgq</command> &mdash;
@@ -123,6 +133,7 @@
               BIND 10 processes.
             </simpara>
           </listitem>
+
           <listitem>
             <simpara>
               <command>b10-auth</command> &mdash;
@@ -130,6 +141,7 @@
               This process serves DNS requests.
             </simpara>
           </listitem>
+
           <listitem>
             <simpara>
               <command>b10-cfgmgr</command> &mdash;
@@ -137,6 +149,7 @@
               This process maintains all of the configuration for BIND 10.
             </simpara>
           </listitem>
+
           <listitem>
             <simpara>
               <command>b10-cmdctl</command> &mdash;
@@ -147,6 +160,23 @@
 
           <listitem>
             <simpara>
+              <command>b10-resolver</command> &mdash;
+              Recursive name server.
+              This process handles incoming queries.
+<!-- TODO: -->
+            </simpara>
+          </listitem>
+
+          <listitem>
+            <simpara>
+              <command>b10-stats</command> &mdash;
+              Statistics collection daemon.
+              This process collects and reports statistics data.
+            </simpara>
+          </listitem>
+
+          <listitem>
+            <simpara>
               <command>b10-xfrin</command> &mdash;
               Incoming zone transfer service.
               This process is used to transfer a new copy
@@ -393,7 +423,7 @@ var/
       <para>
         BIND 10 is open source software written in C++ and Python.
         It is freely available in source code form from ISC via
-        the Subversion code revision control system or as a downloadable
+        the Git code revision control system or as a downloadable
         tar file. It may also be available in pre-compiled ready-to-use
         packages from operating system vendors.
       </para>
@@ -414,7 +444,7 @@ var/
       </section>
 
       <section>
-        <title>Retrieve from Subversion</title>
+        <title>Retrieve from Git</title>
         <para>
           Downloading this "bleeding edge" code is recommended only for
           developers or advanced users.  Using development code in a production
@@ -423,7 +453,7 @@ var/
 
         <note>
           <para>
-            When using source code retrieved via Subversion additional
+            When using source code retrieved via Git additional
             software will be required:  automake (v1.11 or newer),
             libtoolize, and autoconf (2.59 or newer).
             These may need to be installed.
@@ -433,14 +463,16 @@ var/
         <para>
           The latest development code, including temporary experiments
           and un-reviewed code, is available via the BIND 10 code revision
-          control system. This is powered by Subversion and all the BIND 10
+          control system. This is powered by Git and all the BIND 10
           development is public.
-          The leading development is done in the <quote>trunk</quote>.
+          The leading development is done in the <quote>master</quote>.
         </para>
         <para>
-          The code can be checked out from <filename>svn://bind10.isc.org/svn/bind10</filename>; for example to check out the trunk:
+          The code can be checked out from
+          <filename>git://bind10.isc.org/bind10</filename>;
+          for example:
 
-        <screen>$ <userinput>svn co svn://bind10.isc.org/svn/bind10/trunk</userinput></screen>
+        <screen>$ <userinput>git clone git://bind10.isc.org/bind10</userinput></screen>
         </para>
 
         <para>
@@ -657,7 +689,9 @@ var/
       about other modules.
       The <command>bind10</command> master process will also start up
       <command>b10-cmdctl</command> for admins to communicate with the
-      system, <command>b10-auth</command> for Authoritative DNS service,
+      system, <command>b10-auth</command> for authoritative DNS service or
+      <command>b10-resolver</command> for recursive name service,
+      <command>b10-stats</command> for statistics collection,
       <command>b10-xfrin</command> for inbound DNS zone transfers,
       <command>b10-xfrout</command> for outbound DNS zone transfers,
       and <command>b10-zonemgr</command> for secondary service.
@@ -1261,6 +1295,118 @@ what is XfroutClient xfr_client??
 
   </chapter>
 
+  <chapter id="resolverserver">
+    <title>Recursive Name Server</title>
+
+    <para>
+      The <command>b10-resolver</command> process is started by
+      <command>bind10</command>.
+<!-- TODO
+      It provides a resolver so DNS clients can ask it to do recursion
+      and it will return answers.
+-->
+    </para>
+
+    <note><simpara>
+      The current version only provides a forwarding DNS server.
+      It does not cache and does not iterate to find answers.
+      It simply forwards the query on to another full resolver.
+    </simpara></note>
+
+    <para>
+      The main <command>bind10</command> process can be configured
+      to select to run either the authoritative or resolver.
+      By default, it starts the authoritative service.
+<!-- TODO: later both -->
+
+      You may change this using <command>bindctl</command>, for example:
+
+      <screen>
+&gt; <userinput>config set Boss/start_auth false</userinput>
+&gt; <userinput>config set Boss/start_resolver true</userinput>
+&gt; <userinput>config commit</userinput>
+</screen>
+
+    </para>
+
+<!-- TODO: -->
+    <note><simpara>
+       In the current version, the master <command>bind10</command>
+       process must be stopped and restarted to start up the resolver.
+    </simpara></note>
+
+    <para>
+      Then the upstream address and port must be configured to
+      forward queries to, such as:
+
+      <screen>
+&gt; <userinput>config set Resolver/forward_addresses [{ "address": "<replaceable>192.168.1.1</replaceable>", "port": 53 }]</userinput>
+&gt; <userinput>config commit</userinput>
+</screen>
+
+      (Replace <replaceable>192.168.1.1</replaceable> to point to your
+      full resolver.)
+    </para>
+
+    <para>
+      The resolver also needs to be configured to listen on an address
+      and port:
+
+      <screen>
+&gt; <userinput>config set Resolver/listen_on [{ "address": "127.0.0.1", "port": 53 }]</userinput>
+&gt; <userinput>config commit</userinput>
+</screen>
+    </para>
+
+<!-- TODO: later the above will have some defaults -->
+
+<!-- TODO: later try this
+
+> config set Resolver/forward_addresses[0]/address "192.168.8.8"
+> config set Resolver/forward_addresses[0]/port 53
+then change those defaults with config set Resolver/forward_addresses[0]/address "1.2.3.4"
+> config set Resolver/forward_addresses[0]/address "1.2.3.4"
+-->
+
+  </chapter>
+
+  <chapter id="statistics">
+    <title>Statistics</title>
+
+    <para>
+      The <command>b10-stats</command> process is started by
+      <command>bind10</command>.
+      It periodically collects statistics data from various modules
+      and aggregates it.
+<!-- TODO -->
+    </para>
+
+    <para>
+
+       This stats daemon provides commands to identify if it is running,
+       show specified or all statistics data, set values, remove data,
+       and reset data.
+
+       For example, using <command>bindctl</command>:
+
+       <screen>
+&gt; <userinput>Stats show</userinput>
+{
+    "auth.queries.tcp": 1749,
+    "auth.queries.udp": 867868,
+    "bind10.boot_time": "2011-01-20T16:59:03Z",
+    "report_time": "2011-01-20T17:04:06Z",
+    "stats.boot_time": "2011-01-20T16:59:05Z",
+    "stats.last_update_time": "2011-01-20T17:04:05Z",
+    "stats.lname": "4d3869d9_a@jreed.example.net",
+    "stats.start_time": "2011-01-20T16:59:05Z",
+    "stats.timestamp": 1295543046.823504
+}
+       </screen>
+    </para>
+
+  </chapter>
+
 <!-- TODO: how to help: run unit tests, join lists, review trac tickets -->
 
   <!-- <index>    <title>Index</title> </index> -->

+ 1 - 0
doc/version.ent.in

@@ -0,0 +1 @@
+<!ENTITY __VERSION__ "@PACKAGE_VERSION@">

+ 5 - 0
src/bin/auth/auth.spec.pre.in

@@ -52,6 +52,11 @@
 	    }
 	  ]
         }
+      },
+      { "item_name": "statistics-interval",
+        "item_type": "integer",
+        "item_optional": true,
+        "item_default": 60
       }
     ],
     "commands": [

+ 47 - 10
src/bin/auth/auth_srv.cc

@@ -12,8 +12,6 @@
 // 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>
@@ -23,6 +21,8 @@
 #include <iostream>
 #include <vector>
 
+#include <boost/bind.hpp>
+
 #include <asiolink/asiolink.h>
 
 #include <config/ccsession.h>
@@ -87,6 +87,8 @@ public:
     bool processNotify(const IOMessage& io_message, MessagePtr message,
                        OutputBufferPtr buffer);
 
+    IOService io_service_;
+
     /// Currently non-configurable, but will be.
     static const uint16_t DEFAULT_LOCAL_UDPSIZE = 4096;
 
@@ -102,6 +104,9 @@ public:
     /// Hot spot cache
     isc::datasrc::HotCache cache_;
 
+    /// Interval timer for periodic submission of statistics counters.
+    IntervalTimer statistics_timer_;
+
     /// Query counters for statistics
     AuthCounters counters_;
 private:
@@ -125,6 +130,7 @@ AuthSrvImpl::AuthSrvImpl(const bool use_cache,
     config_session_(NULL), verbose_mode_(false),
     xfrin_session_(NULL),
     memory_datasrc_class_(RRClass::IN()),
+    statistics_timer_(io_service_),
     counters_(verbose_mode_),
     xfrout_connected_(false),
     xfrout_client_(xfrout_client)
@@ -153,9 +159,13 @@ AuthSrvImpl::~AuthSrvImpl() {
 class MessageLookup : public DNSLookup {
 public:
     MessageLookup(AuthSrv* srv) : server_(srv) {}
-    virtual void operator()(const IOMessage& io_message, MessagePtr message,
-                            OutputBufferPtr buffer, DNSServer* server) const
+    virtual void operator()(const IOMessage& io_message,
+                            MessagePtr message,
+                            MessagePtr answer_message,
+                            OutputBufferPtr buffer,
+                            DNSServer* server) const
     {
+        (void) answer_message;
         server_->processMessage(io_message, message, buffer, server);
     }
 private:
@@ -174,7 +184,7 @@ class MessageAnswer : public DNSAnswer {
 public:
     MessageAnswer(AuthSrv*) {}
     virtual void operator()(const IOMessage&, MessagePtr,
-                            OutputBufferPtr) const
+                            MessagePtr, OutputBufferPtr) const
     {}
 };
 
@@ -195,7 +205,6 @@ private:
 
 AuthSrv::AuthSrv(const bool use_cache, AbstractXfroutClient& xfrout_client) :
     impl_(new AuthSrvImpl(use_cache, xfrout_client)),
-    io_service_(NULL),
     checkin_(new ConfigChecker(this)),
     dns_lookup_(new MessageLookup(this)),
     dns_answer_(new MessageAnswer(this))
@@ -203,10 +212,7 @@ AuthSrv::AuthSrv(const bool use_cache, AbstractXfroutClient& xfrout_client) :
 
 void
 AuthSrv::stop() {
-    if (io_service_ == NULL) {
-        throw FatalError("Assumption failure; server is stopped before start");
-    }
-    io_service_->stop();
+    impl_->io_service_.stop();
 }
 
 AuthSrv::~AuthSrv() {
@@ -278,6 +284,11 @@ AuthSrv::getVerbose() const {
     return (impl_->verbose_mode_);
 }
 
+IOService&
+AuthSrv::getIOService() {
+    return (impl_->io_service_);
+}
+
 void
 AuthSrv::setCacheSlots(const size_t slots) {
     impl_->cache_.setSlots(slots);
@@ -341,6 +352,32 @@ AuthSrv::setMemoryDataSrc(const isc::dns::RRClass& rrclass,
     impl_->memory_datasrc_ = memory_datasrc;
 }
 
+uint32_t
+AuthSrv::getStatisticsTimerInterval() const {
+    return (impl_->statistics_timer_.getInterval());
+}
+
+void
+AuthSrv::setStatisticsTimerInterval(uint32_t interval) {
+    if (interval == impl_->statistics_timer_.getInterval()) {
+        return;
+    }
+    if (interval == 0) {
+        impl_->statistics_timer_.cancel();
+    } else {
+        impl_->statistics_timer_.setupTimer(
+            boost::bind(&AuthSrv::submitStatistics, this), interval);
+    }
+    if (impl_->verbose_mode_) {
+        if (interval == 0) {
+            cerr << "[b10-auth] Disabled statistics timer" << endl;
+        } else {
+            cerr << "[b10-auth] Set statistics timer to " << interval
+                 << " seconds" << endl;
+        }
+    }
+}
+
 void
 AuthSrv::processMessage(const IOMessage& io_message, MessagePtr message,
                         OutputBufferPtr buffer, DNSServer* server)

+ 25 - 10
src/bin/auth/auth_srv.h

@@ -12,8 +12,6 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 
-// $Id$
-
 #ifndef __AUTH_SRV_H
 #define __AUTH_SRV_H 1
 
@@ -91,9 +89,8 @@ public:
     ///
     /// It stops the internal event loop of the server and subsequently
     /// returns the control to the top level context.
-    /// The server must have been associated with an \c IOService object;
-    /// otherwise an exception of \c FatalError will be thrown.
-    /// This method never throws an exception otherwise.
+    ///
+    /// This method should never throw an exception.
     void stop();
 
     /// \brief Process an incoming DNS message, then signal 'server' to resume 
@@ -194,11 +191,8 @@ public:
     /// control commands and configuration updates.
     void setConfigSession(isc::config::ModuleCCSession* config_session);
 
-    /// \brief Assign an ASIO IO Service queue to this Resolver 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_); }
+    asiolink::IOService& getIOService();
 
     /// \brief Return pointer to the DNS Lookup callback function
     asiolink::DNSLookup* getDNSLookupProvider() const { return (dns_lookup_); }
@@ -312,6 +306,28 @@ public:
     /// is shutdown.
     void setStatisticsSession(isc::cc::AbstractSession* statistics_session);
 
+    /// Return the interval of periodic submission of statistics in seconds.
+    ///
+    /// If the statistics submission is disabled, it returns 0.
+    ///
+    /// This method never throws an exception.
+    uint32_t getStatisticsTimerInterval() const;
+
+    /// Set the interval of periodic submission of statistics.
+    ///
+    /// If the specified value is non 0, the \c AuthSrv object will submit
+    /// its statistics to the statistics module every \c interval seconds.
+    /// If it's 0, and \c AuthSrv currently submits statistics, the submission
+    /// will be disabled.
+    ///
+    /// This method should normally not throw an exception; however, its
+    /// underlying library routines may involve resource allocation, and
+    /// when it fails it would result in a corresponding standard exception.
+    ///
+    /// \param interval The submission interval in seconds if non 0;
+    /// or a value of 0 to disable the submission.
+    void setStatisticsTimerInterval(uint32_t interval);
+
     /// \brief Submit statistics counters to statistics module.
     ///
     /// This function can throw an exception from
@@ -338,7 +354,6 @@ public:
 
 private:
     AuthSrvImpl* impl_;
-    asiolink::IOService* io_service_;
     asiolink::SimpleCallback* checkin_;
     asiolink::DNSLookup* dns_lookup_;
     asiolink::DNSAnswer* dns_answer_;

+ 98 - 25
src/bin/auth/b10-auth.8

@@ -2,12 +2,12 @@
 .\"     Title: b10-auth
 .\"    Author: [FIXME: author] [see http://docbook.sf.net/el/author]
 .\" Generator: DocBook XSL Stylesheets v1.75.2 <http://docbook.sf.net/>
-.\"      Date: July 29, 2010
+.\"      Date: January 19, 2011
 .\"    Manual: BIND10
 .\"    Source: BIND10
 .\"  Language: English
 .\"
-.TH "B10\-AUTH" "8" "July 29, 2010" "BIND10" "BIND10"
+.TH "B10\-AUTH" "8" "January 19, 2011" "BIND10" "BIND10"
 .\" -----------------------------------------------------------------
 .\" * set default formatting
 .\" -----------------------------------------------------------------
@@ -36,27 +36,8 @@ This daemon communicates with other BIND 10 components over a
 C\-Channel connection\&. If this connection is not established,
 \fBb10\-auth\fR
 will exit\&.
-.PP
-It also receives its configurations from
-\fBb10-cfgmgr\fR(8)\&. It will honor the
-\fIdatabase_file\fR
-configuration to point to the SQLite3 zone file\&.
-.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 uses SQLite3 as its data source backend\&. Future versions will be configurable, supporting multiple data storage types\&.
-.sp .5v
-.RE
+It receives its configurations from
+\fBb10-cfgmgr\fR(8)\&.
 .SH "OPTIONS"
 .PP
 The arguments are as follows:
@@ -123,10 +104,102 @@ must be either a valid numeric user ID or a valid user name\&. By default the da
 .RS 4
 Enabled verbose mode\&. This enables diagnostic messages to STDERR\&.
 .RE
+.SH "CONFIGURATION AND COMMANDS"
+.PP
+The configurable settings are:
+.PP
+
+\fIdatabase_file\fR
+defines the path to the SQLite3 zone file when using the sqlite datasource\&. The default is
+/usr/local/var/bind10\-devel/zone\&.sqlite3\&.
+.PP
+
+\fIdatasources\fR
+configures data sources\&. The list items include:
+\fItype\fR
+to optionally choose the data source type (such as
+\(lqmemory\(rq);
+\fIclass\fR
+to optionally select the class (it defaults to
+\(lqIN\(rq); and
+\fIzones\fR
+to define the
+\fIfile\fR
+path name and the
+\fIorigin\fR
+(default domain)\&. By default, this is empty\&.
+.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
+.sp
+In this development version, currently this is only used for the memory data source\&. Only the IN class is supported at this time\&. By default, the memory data source is disabled\&. Also, currently the zone file must be canonical such as generated by \fBnamed\-compilezone \-D\fR\&.
+.sp .5v
+.RE
+.PP
+
+\fIstatistics\-interval\fR
+is the timer interval in seconds for
+\fBb10\-auth\fR
+to share its statistics information to
+\fBb10-stats\fR(8)\&. Statistics updates can be disabled by setting this to 0\&. The default is 60\&.
+.PP
+The configuration commands are:
+.PP
+
+\fBloadzone\fR
+tells
+\fBb10\-auth\fR
+to load or reload a zone file\&. The arguments include:
+\fIclass\fR
+which optionally defines the class (it defaults to
+\(lqIN\(rq);
+\fIorigin\fR
+is the domain name of the zone; and
+\fIdatasrc\fR
+optionally defines the type of datasource (it defaults to
+\(lqmemory\(rq)\&.
+.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
+.sp
+In this development version, currently this only supports the IN class and the memory data source\&.
+.sp .5v
+.RE
+.PP
+
+\fBsendstats\fR
+tells
+\fBb10\-auth\fR
+to send its statistics data to
+\fBb10-stats\fR(8)
+immediately\&.
+.PP
+
+\fBshutdown\fR
+exits
+\fBb10\-auth\fR\&. (Note that the BIND 10 boss process will restart this service\&.)
 .SH "FILES"
 .PP
 
-/usr/local/var/db/zone\&.sqlite3
+/usr/local/var/bind10\-devel/zone\&.sqlite3
 \(em Location for the SQLite3 zone database when
 \fIdatabase_file\fR
 configuration is not defined\&.
@@ -134,9 +207,9 @@ configuration is not defined\&.
 .PP
 
 \fBb10-cfgmgr\fR(8),
-\fBb10-cmdctl\fR(8),
 \fBb10-loadzone\fR(8),
 \fBb10-msgq\fR(8),
+\fBb10-stats\fR(8),
 \fBb10-zonemgr\fR(8),
 \fBbind10\fR(8),
 BIND 10 Guide\&.

+ 91 - 20
src/bin/auth/b10-auth.xml

@@ -2,7 +2,7 @@
                "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd"
 	       [<!ENTITY mdash "&#8212;">]>
 <!--
- - Copyright (C) 2010  Internet Systems Consortium, Inc. ("ISC")
+ - Copyright (C) 2010-2011  Internet Systems Consortium, Inc. ("ISC")
  -
  - Permission to use, copy, modify, and/or distribute this software for any
  - purpose with or without fee is hereby granted, provided that the above
@@ -17,11 +17,10 @@
  - PERFORMANCE OF THIS SOFTWARE.
 -->
 
-<!-- $Id$ -->
 <refentry>
 
   <refentryinfo>
-    <date>July 29, 2010</date>
+    <date>January 19, 2011</date>
   </refentryinfo>
 
   <refmeta>
@@ -70,22 +69,13 @@
       C-Channel connection.  If this connection is not established,
       <command>b10-auth</command> will exit.
 <!-- TODO what if msgq connection closes later, will b10-auth exit? -->
-    </para>
-
-    <para>
-      It also receives its configurations from
+      It receives its configurations from
 <citerefentry><refentrytitle>b10-cfgmgr</refentrytitle><manvolnum>8</manvolnum></citerefentry>.
-      It will honor the <emphasis>database_file</emphasis> configuration
-      to point to the SQLite3 zone file.
 
-<!-- TODO: data source -->
     </para>
 
-    <note><para>
-      This prototype version uses SQLite3 as its data source backend.
-      Future versions will be configurable, supporting multiple
-      data storage types.
-    </para></note>
+<!-- TODO: mention xfrin, xfrout, zonemgr ? -->
+
   </refsect1>
 
   <refsect1>
@@ -136,6 +126,7 @@
 	  and negative) in memory for 30 seconds (instead of querying
 	  the data source, such as SQLite3 database, each time).
         </para></listitem>
+<!-- TODO: this is SQLite3 only -->
       </varlistentry>
 
       <varlistentry>
@@ -175,14 +166,94 @@
   </refsect1>
 
   <refsect1>
+    <title>CONFIGURATION AND COMMANDS</title>
+    <para>
+      The configurable settings are:
+    </para>
+
+    <para>
+      <varname>database_file</varname> defines the path to the
+      SQLite3 zone file when using the sqlite datasource.
+      The default is
+      <filename>/usr/local/var/bind10-devel/zone.sqlite3</filename>.
+    </para>
+
+    <para>
+      <varname>datasources</varname> configures data sources.
+      The list items include:
+      <varname>type</varname> to optionally choose the data source type
+      (such as <quote>memory</quote>);
+      <varname>class</varname> to optionally select the class
+      (it defaults to <quote>IN</quote>);
+      and
+      <varname>zones</varname> to define the
+      <varname>file</varname> path name and the
+      <varname>origin</varname> (default domain).
+
+      By default, this is empty.
+
+      <note><simpara>
+        In this development version, currently this is only used for the
+        memory data source.
+        Only the IN class is supported at this time.
+        By default, the memory data source is disabled.
+        Also, currently the zone file must be canonical such as
+        generated by <command>named-compilezone -D</command>.
+      </simpara></note>
+    </para>
+
+    <para>
+      <varname>statistics-interval</varname> is the timer interval
+      in seconds for <command>b10-auth</command> to share its
+      statistics information to
+      <citerefentry><refentrytitle>b10-stats</refentrytitle><manvolnum>8</manvolnum></citerefentry>.
+      Statistics updates can be disabled by setting this to 0.
+      The default is 60.
+    </para>
+
+<!-- TODO: formating -->
+    <para>
+      The configuration commands are:
+    </para>
+
+    <para>
+      <command>loadzone</command> tells <command>b10-auth</command>
+      to load or reload a zone file. The arguments include:
+      <varname>class</varname> which optionally defines the class
+      (it defaults to <quote>IN</quote>);
+      <varname>origin</varname> is the domain name of the zone;
+      and
+      <varname>datasrc</varname> optionally defines the type of datasource
+      (it defaults to <quote>memory</quote>).
+
+      <note><simpara>
+        In this development version, currently this only supports the
+        IN class and the memory data source.
+      </simpara></note>
+    </para>
+
+    <para>
+      <command>sendstats</command> tells <command>b10-auth</command>
+      to send its statistics data to
+      <citerefentry><refentrytitle>b10-stats</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+      immediately.
+    </para>
+
+    <para>
+      <command>shutdown</command> exits <command>b10-auth</command>.
+      (Note that the BIND 10 boss process will restart this service.)
+    </para>
+
+  </refsect1>
+
+  <refsect1>
     <title>FILES</title>
     <para>
-      <filename>/usr/local/var/db/zone.sqlite3</filename>
+      <filename>/usr/local/var/bind10-devel/zone.sqlite3</filename>
       &mdash; Location for the SQLite3 zone database
       when <emphasis>database_file</emphasis> configuration is not
       defined.
     </para>
-<!-- TODO: this is not correct yet. -->
   </refsect1>
 
   <refsect1>
@@ -192,15 +263,15 @@
         <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>b10-stats</refentrytitle><manvolnum>8</manvolnum>
+      </citerefentry>,
+      <citerefentry>
         <refentrytitle>b10-zonemgr</refentrytitle><manvolnum>8</manvolnum>
       </citerefentry>,
       <citerefentry>

+ 0 - 2
src/bin/auth/benchmarks/query_bench.cc

@@ -12,8 +12,6 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 
-// $Id$
-
 #include <stdlib.h>
 
 #include <iostream>

+ 0 - 2
src/bin/auth/change_user.cc

@@ -12,8 +12,6 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 
-// $Id$
-
 #include <errno.h>
 #include <string.h>
 #include <pwd.h>

+ 0 - 2
src/bin/auth/change_user.h

@@ -12,8 +12,6 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 
-// $Id$
-
 #ifndef __CHANGE_USER_H
 #define __CHANGE_USER_H 1
 

+ 0 - 2
src/bin/auth/common.h

@@ -12,8 +12,6 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 
-// $Id$
-
 #ifndef __COMMON_H
 #define __COMMON_H 1
 

+ 28 - 0
src/bin/auth/config.cc

@@ -169,6 +169,32 @@ MemoryDatasourceConfig::build(ConstElementPtr config_value) {
     }
 }
 
+/// A derived \c AuthConfigParser class for the "statistics-internal"
+/// configuration identifier.
+class StatisticsIntervalConfig : public AuthConfigParser {
+public:
+    StatisticsIntervalConfig(AuthSrv& server) :
+        server_(server), interval_(0)
+    {}
+    virtual void build(ConstElementPtr config_value) {
+        const int32_t config_interval = config_value->intValue();
+        if (config_interval < 0) {
+            isc_throw(AuthConfigError, "negative statistics-interval value: "
+                      << config_interval);
+        }
+        interval_ = config_interval;
+    }
+    virtual void commit() {
+        // setStatisticsTimerInterval() is not 100% exception free.  But
+        // exceptions should happen only in a very rare situation, so we
+        // let them be thrown and subsequently regard them as a fatal error.
+        server_.setStatisticsTimerInterval(interval_);
+    }
+private:
+    AuthSrv& server_;
+    uint32_t interval_;
+};
+
 /// A special parser for testing: it throws from commit() despite the
 /// suggested convention of the class interface.
 class ThrowerCommitConfig : public AuthConfigParser {
@@ -191,6 +217,8 @@ createAuthConfigParser(AuthSrv& server, const std::string& config_id,
     // that it can be dynamically customized.
     if (config_id == "datasources") {
         return (new DatasourcesConfig(server));
+    } else if (config_id == "statistics-interval") {
+        return (new StatisticsIntervalConfig(server));
     } else if (internal && config_id == "datasources/memory") {
         return (new MemoryDatasourceConfig(server));
     } else if (config_id == "_commit_throw") {

+ 2 - 33
src/bin/auth/main.cc

@@ -12,8 +12,6 @@
 // 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>
@@ -25,8 +23,6 @@
 #include <cassert>
 #include <iostream>
 
-#include <boost/bind.hpp>
-
 #include <exceptions/exceptions.h>
 
 #include <dns/buffer.h>
@@ -62,17 +58,11 @@ bool verbose_mode = false;
 // Default port current 5300 for testing purposes
 const char* DNSPORT = "5300";
 
-// Note: this value must be greater than 0.
-// TODO: make it configurable via command channel.
-const uint32_t STATISTICS_SEND_INTERVAL_SEC = 60;
-
 /* need global var for config/command handlers.
  * todo: turn this around, and put handlers in the authserver
  * class itself? */
 AuthSrv *auth_server;
 
-IOService io_service;
-
 ConstElementPtr
 my_config_handler(ConstElementPtr new_config) {
     return (auth_server->updateConfig(new_config));
@@ -98,12 +88,6 @@ usage() {
     cerr << "\t-v: verbose output" << endl;
     exit(1);
 }
-
-void
-statisticsTimerCallback(AuthSrv* auth_server) {
-    assert(auth_server != NULL);
-    auth_server->submitStatistics();
-}
 } // end of anonymous namespace
 
 int
@@ -170,7 +154,6 @@ main(int argc, char* argv[]) {
     Session* cc_session = NULL;
     Session* xfrin_session = NULL;
     Session* statistics_session = NULL;
-    IntervalTimer* itimer = NULL;
     bool xfrin_session_established = false; // XXX (see Trac #287)
     bool statistics_session_established = false; // XXX (see Trac #287)
     ModuleCCSession* config_session = NULL;
@@ -195,6 +178,7 @@ main(int argc, char* argv[]) {
         cout << "[b10-auth] Server created." << endl;
 
         SimpleCallback* checkin = auth_server->getCheckinProvider();
+        IOService& io_service = auth_server->getIOService();
         DNSLookup* lookup = auth_server->getDNSLookupProvider();
         DNSAnswer* answer = auth_server->getDNSAnswerProvider();
 
@@ -213,8 +197,7 @@ main(int argc, char* argv[]) {
                                          use_ipv6, checkin, lookup,
                                          answer);
         }
-        auth_server->setIOService(io_service);
-        cout << "[b10-auth] IOService created." << endl;
+        cout << "[b10-auth] DNSServices created." << endl;
 
         cc_session = new Session(io_service.get_io_service());
         cout << "[b10-auth] Configuration session channel created." << endl;
@@ -240,11 +223,6 @@ main(int argc, char* argv[]) {
         statistics_session_established = true;
         cout << "[b10-auth] Statistics session channel established." << endl;
 
-        // 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 asiolink independent
-        // from auth_server, and create io_service, auth_server, and
-        // sessions in that order.
         auth_server->setXfrinSession(xfrin_session);
         auth_server->setStatisticsSession(statistics_session);
 
@@ -256,14 +234,6 @@ main(int argc, char* argv[]) {
         configureAuthServer(*auth_server, config_session->getFullConfig());
         auth_server->updateConfig(ElementPtr());
 
-        // create interval timer instance
-        itimer = new IntervalTimer(io_service);
-        // set up interval timer
-        // register function to send statistics with interval
-        itimer->setupTimer(boost::bind(statisticsTimerCallback, auth_server),
-                           STATISTICS_SEND_INTERVAL_SEC);
-        cout << "[b10-auth] Interval timer to send statistics set." << endl;
-
         cout << "[b10-auth] Server started." << endl;
         io_service.run();
 
@@ -281,7 +251,6 @@ main(int argc, char* argv[]) {
         xfrin_session->disconnect();
     }
 
-    delete itimer;
     delete statistics_session;
     delete xfrin_session;
     delete config_session;

+ 0 - 2
src/bin/auth/statistics.cc

@@ -12,8 +12,6 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 
-// $Id$
-
 #include <auth/statistics.h>
 
 #include <cc/data.h>

+ 0 - 2
src/bin/auth/statistics.h

@@ -12,8 +12,6 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 
-// $Id$
-
 #ifndef __STATISTICS_H
 #define __STATISTICS_H 1
 

+ 7 - 7
src/bin/auth/tests/auth_srv_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$
-
 #include <config.h>
 
 #include <vector>
@@ -34,6 +32,7 @@
 #include <auth/statistics.h>
 
 #include <dns/tests/unittest_util.h>
+#include <testutils/dnsmessage_test.h>
 #include <testutils/srv_test.h>
 
 using namespace std;
@@ -139,9 +138,10 @@ TEST_F(AuthSrvTest, builtInQueryViaDNSServer) {
     createRequestPacket(request_message, IPPROTO_UDP);
 
     (*server.getDNSLookupProvider())(*io_message, parse_message,
+                                     response_message,
                                      response_obuffer, &dnsserv);
     (*server.getDNSAnswerProvider())(*io_message, parse_message,
-                                     response_obuffer);
+                                     response_message, response_obuffer);
 
     createBuiltinVersionResponse(default_qid, response_data);
     EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
@@ -154,9 +154,10 @@ TEST_F(AuthSrvTest, builtInQueryViaDNSServer) {
 TEST_F(AuthSrvTest, iqueryViaDNSServer) {
     createDataFromFile("iquery_fromWire.wire");
     (*server.getDNSLookupProvider())(*io_message, parse_message,
+                                     response_message,
                                      response_obuffer, &dnsserv);
     (*server.getDNSAnswerProvider())(*io_message, parse_message,
-                                     response_obuffer);
+                                     response_message, response_obuffer);
 
     UnitTestUtil::readWireData("iquery_response_fromWire.wire",
                                response_data);
@@ -646,8 +647,7 @@ TEST_F(AuthSrvTest, stop) {
     // normal case is covered in command_unittest.cc.  we should primarily
     // test it here, but the current design of the stop test takes time,
     // so we consolidate the cases in the command tests.
-
-    // stop before start is prohibited.
-    EXPECT_THROW(server.stop(), FatalError);
+    // If/when the interval timer has finer granularity we'll probably add
+    // our own tests here, so we keep this empty test case.
 }
 }

+ 0 - 2
src/bin/auth/tests/change_user_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$
-
 #include <stdlib.h>
 #include <unistd.h>             // for getuid
 

+ 2 - 4
src/bin/auth/tests/command_unittest.cc

@@ -97,11 +97,9 @@ AuthConmmandTest::stopServer() {
 }
 
 TEST_F(AuthConmmandTest, shutdown) {
-    asiolink::IOService io_service;
-    asiolink::IntervalTimer itimer(io_service);
-    server.setIOService(io_service);
+    asiolink::IntervalTimer itimer(server.getIOService());
     itimer.setupTimer(boost::bind(&AuthConmmandTest::stopServer, this), 1);
-    io_service.run();
+    server.getIOService().run();
     EXPECT_EQ(0, rcode);
 }
 

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

@@ -34,6 +34,7 @@
 using namespace isc::dns;
 using namespace isc::data;
 using namespace isc::datasrc;
+using namespace asiolink;
 
 namespace {
 class AuthConfigTest : public ::testing::Test {
@@ -320,4 +321,49 @@ TEST_F(MemoryDatasrcConfigTest, badDatasrcType) {
                                                  " {\"type\": \"memory\"}]")),
                  AuthConfigError);
 }
+
+class StatisticsIntervalConfigTest : public AuthConfigTest {
+protected:
+    StatisticsIntervalConfigTest() :
+        parser(createAuthConfigParser(server, "statistics-interval"))
+    {}
+    ~StatisticsIntervalConfigTest() {
+        delete parser;
+    }
+    AuthConfigParser* parser;
+};
+
+TEST_F(StatisticsIntervalConfigTest, setInterval) {
+    // initially the timer is not configured.
+    EXPECT_EQ(0, server.getStatisticsTimerInterval());
+
+    // initialize the timer
+    parser->build(Element::fromJSON("5"));
+    parser->commit();
+    EXPECT_EQ(5, server.getStatisticsTimerInterval());
+
+    // reset the timer with a new interval
+    delete parser;
+    parser = createAuthConfigParser(server, "statistics-interval");
+    ASSERT_NE(static_cast<void*>(NULL), parser);
+    parser->build(Element::fromJSON("10"));
+    parser->commit();
+    EXPECT_EQ(10, server.getStatisticsTimerInterval());
+
+    // disable the timer again
+    delete parser;
+    parser = createAuthConfigParser(server, "statistics-interval");
+    ASSERT_NE(static_cast<void*>(NULL), parser);
+    parser->build(Element::fromJSON("0"));
+    parser->commit();
+    EXPECT_EQ(0, server.getStatisticsTimerInterval());
+}
+
+TEST_F(StatisticsIntervalConfigTest, badInterval) {
+    EXPECT_THROW(parser->build(Element::fromJSON("\"should be integer\"")),
+                 isc::data::TypeError);
+    EXPECT_THROW(parser->build(Element::fromJSON("2.5")),
+                 isc::data::TypeError);
+    EXPECT_THROW(parser->build(Element::fromJSON("-1")), AuthConfigError);
+}
 }

+ 253 - 348
src/bin/auth/tests/query_unittest.cc

@@ -12,8 +12,16 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 
+#include <sstream>
+#include <vector>
+#include <map>
+
+#include <boost/bind.hpp>
+
+#include <dns/masterload.h>
 #include <dns/message.h>
 #include <dns/name.h>
+#include <dns/opcode.h>
 #include <dns/rcode.h>
 #include <dns/rrttl.h>
 #include <dns/rrtype.h>
@@ -23,167 +31,167 @@
 
 #include <auth/query.h>
 
+#include <testutils/dnsmessage_test.h>
+
 #include <gtest/gtest.h>
 
+using namespace std;
 using namespace isc::dns;
+using namespace isc::dns::rdata;
 using namespace isc::datasrc;
 using namespace isc::auth;
+using namespace isc::testutils;
 
 namespace {
 
-RRsetPtr a_rrset = RRsetPtr(new RRset(Name("www.example.com"),
-                                      RRClass::IN(), RRType::A(),
-                                      RRTTL(3600)));
-RRsetPtr soa_rrset = RRsetPtr(new RRset(Name("example.com"),
-                                        RRClass::IN(), RRType::SOA(),
-                                        RRTTL(3600)));
-RRsetPtr ns_rrset(RRsetPtr(new RRset(Name("ns.example.com"),
-                                     RRClass::IN(), RRType::NS(),
-                                     RRTTL(3600))));
-RRsetPtr glue_a_rrset(RRsetPtr(new RRset(Name("glue.ns.example.com"),
-                                         RRClass::IN(), RRType::A(),
-                                         RRTTL(3600))));
-RRsetPtr glue_aaaa_rrset(RRsetPtr(new RRset(Name("glue.ns.example.com"),
-                                            RRClass::IN(), RRType::AAAA(),
-                                            RRTTL(3600))));
-RRsetPtr noglue_a_rrset(RRsetPtr(new RRset(Name("noglue.example.com"),
-                                           RRClass::IN(), RRType::A(),
-                                           RRTTL(3600))));
-RRsetPtr delegated_mx_a_rrset(RRsetPtr(new RRset(
-    Name("mx.delegation.example.com"), RRClass::IN(), RRType::A(),
-    RRTTL(3600))));
+// This is the content of the mock zone (see below).
+// It's a sequence of textual RRs that is supposed to be parsed by
+// dns::masterLoad().  Some of the RRs are also used as the expected
+// data in specific tests, in which case they are referenced via specific
+// local variables (such as soa_txt).
+const char* const soa_txt = "example.com. 3600 IN SOA . . 0 0 0 0 0\n";
+const char* const zone_ns_txt =
+    "example.com. 3600 IN NS glue.delegation.example.com.\n"
+    "example.com. 3600 IN NS noglue.example.com.\n"
+    "example.com. 3600 IN NS example.net.\n";
+const char* const ns_addrs_txt =
+    "glue.delegation.example.com. 3600 IN A 192.0.2.153\n"
+    "glue.delegation.example.com. 3600 IN AAAA 2001:db8::53\n"
+    "noglue.example.com. 3600 IN A 192.0.2.53\n";
+const char* const delegation_txt =
+        "delegation.example.com. 3600 IN NS glue.delegation.example.com.\n"
+        "delegation.example.com. 3600 IN NS noglue.example.com.\n"
+        "delegation.example.com. 3600 IN NS cname.example.com.\n"
+        "delegation.example.com. 3600 IN NS example.org.\n";
+const char* const mx_txt =
+    "mx.example.com. 3600 IN MX 10 www.example.com.\n"
+    "mx.example.com. 3600 IN MX 20 mailer.example.org.\n"
+    "mx.example.com. 3600 IN MX 30 mx.delegation.example.com.\n";
+const char* const www_a_txt = "www.example.com. 3600 IN A 192.0.2.80\n";
+// The rest of data won't be referenced from the test cases.
+const char* const other_zone_rrs =
+    "cname.example.com. 3600 IN CNAME www.example.com.\n"
+    "cnamemailer.example.com. 3600 IN CNAME www.example.com.\n"
+    "cnamemx.example.com. 3600 IN MX 10 cnamemailer.example.com.\n"
+    "mx.delegation.example.com. 3600 IN A 192.0.2.100\n";
 
 // This is a mock Zone class for testing.
-// It is a derived class of Zone, and simply hardcodes the results of find()
-// See the find() implementation if you want to know its content.
+// It is a derived class of Zone for the convenient of tests.
+// Its find() method emulates the common behavior of protocol compliant
+// zone classes, but simplifies some minor cases and also supports broken
+// behavior.
+// For simplicity, most names are assumed to be "in zone"; there's only
+// one zone cut at the point of name "delegation.example.com".
+// It doesn't handle empty non terminal nodes (if we need to test such cases
+// find() should have specialized code for it).
 class MockZone : public Zone {
 public:
-    MockZone(bool has_SOA = true, bool has_apex_NS = true) :
+    MockZone() :
         origin_(Name("example.com")),
-        has_SOA_(has_SOA),
-        has_apex_NS_(has_apex_NS),
-        delegation_rrset(RRsetPtr(new RRset(Name("delegation.example.com"),
-                                            RRClass::IN(), RRType::NS(),
-                                            RRTTL(3600)))),
-        cname_rrset(RRsetPtr(new RRset(Name("cname.example.com"),
-                                       RRClass::IN(), RRType::CNAME(),
-                                       RRTTL(3600)))),
-        auth_ns_rrset(RRsetPtr(new RRset(Name("example.com"),
-                                         RRClass::IN(), RRType::NS(),
-                                         RRTTL(3600)))),
-        mx_cname_rrset_(new RRset(Name("cnamemailer.example.com"),
-            RRClass::IN(), RRType::CNAME(), RRTTL(3600))),
-        mx_rrset_(new RRset(Name("mx.example.com"), RRClass::IN(),
-            RRType::MX(), RRTTL(3600)))
+        delegation_name_("delegation.example.com"),
+        has_SOA_(true),
+        has_apex_NS_(true),
+        rrclass_(RRClass::IN())
     {
-        delegation_rrset->addRdata(rdata::generic::NS(
-                          Name("glue.ns.example.com")));
-        delegation_rrset->addRdata(rdata::generic::NS(
-                          Name("noglue.example.com")));
-        delegation_rrset->addRdata(rdata::generic::NS(
-                          Name("cname.example.com")));
-        delegation_rrset->addRdata(rdata::generic::NS(
-                          Name("example.org")));
-        cname_rrset->addRdata(rdata::generic::CNAME(
-                          Name("www.example.com")));
-        auth_ns_rrset->addRdata(rdata::generic::NS(
-                          Name("glue.ns.example.com")));
-        auth_ns_rrset->addRdata(rdata::generic::NS(
-                          Name("noglue.example.com")));
-        auth_ns_rrset->addRdata(rdata::generic::NS(
-                          Name("example.net")));
-        mx_rrset_->addRdata(isc::dns::rdata::generic::MX(10,
-            Name("www.example.com")));
-        mx_rrset_->addRdata(isc::dns::rdata::generic::MX(20,
-            Name("mailer.example.org")));
-        mx_rrset_->addRdata(isc::dns::rdata::generic::MX(30,
-            Name("mx.delegation.example.com")));
-        mx_cname_rrset_->addRdata(rdata::generic::CNAME(
-            Name("mx.example.com")));
+        stringstream zone_stream;
+        zone_stream << soa_txt << zone_ns_txt << ns_addrs_txt <<
+            delegation_txt << mx_txt << www_a_txt << other_zone_rrs;
+
+        masterLoad(zone_stream, origin_, rrclass_,
+                   boost::bind(&MockZone::loadRRset, this, _1));
     }
-    virtual const isc::dns::Name& getOrigin() const;
-    virtual const isc::dns::RRClass& getClass() const;
+    virtual const isc::dns::Name& getOrigin() const { return (origin_); }
+    virtual const isc::dns::RRClass& getClass() const { return (rrclass_); }
+    virtual FindResult find(const isc::dns::Name& name,
+                            const isc::dns::RRType& type,
+                            RRsetList* target = NULL,
+                            const FindOptions options = FIND_DEFAULT) const;
 
-    FindResult find(const isc::dns::Name& name,
-                    const isc::dns::RRType& type,
-                    RRsetList* target = NULL,
-                    const FindOptions options = FIND_DEFAULT) const;
+    // If false is passed, it makes the zone broken as if it didn't have the
+    // SOA.
+    void setSOAFlag(bool on) { has_SOA_ = on; }
+
+    // If false is passed, it makes the zone broken as if it didn't have
+    // the apex NS.
+    void setApexNSFlag(bool on) { has_apex_NS_ = on; }
 
 private:
-    Name origin_;
+    typedef map<RRType, ConstRRsetPtr> RRsetStore;
+    typedef map<Name, RRsetStore> Domains;
+    Domains domains_;
+    void loadRRset(ConstRRsetPtr rrset) {
+        domains_[rrset->getName()][rrset->getType()] = rrset;
+        if (rrset->getName() == delegation_name_ &&
+            rrset->getType() == RRType::NS()) {
+            delegation_rrset_ = rrset;
+        }
+    }
+
+    const Name origin_;
+    const Name delegation_name_;
     bool has_SOA_;
     bool has_apex_NS_;
-    RRsetPtr delegation_rrset;
-    RRsetPtr cname_rrset;
-    RRsetPtr auth_ns_rrset;
-    RRsetPtr mx_cname_rrset_;
-    RRsetPtr mx_rrset_;
+    ConstRRsetPtr delegation_rrset_;
+    const RRClass rrclass_;
 };
 
-const Name&
-MockZone::getOrigin() const {
-    return (origin_);
-}
-
-const RRClass&
-MockZone::getClass() const {
-    return (RRClass::IN());
-}
-
 Zone::FindResult
 MockZone::find(const Name& name, const RRType& type,
                RRsetList* target, const FindOptions options) const
 {
-    // hardcode the find results
-    if (name == Name("www.example.com") && type == RRType::A()) {
-        return (FindResult(SUCCESS, a_rrset));
-    } else if (name == Name("www.example.com")) {
-        return (FindResult(NXRRSET, RRsetPtr()));
-    } else if (name == Name("glue.ns.example.com") && type == RRType::A() &&
-        (options & FIND_GLUE_OK) != 0) {
-        return (FindResult(SUCCESS, glue_a_rrset));
-    } else if (name == Name("noglue.example.com") && (type == RRType::A() ||
-        type == RRType::ANY())) {
-        return (FindResult(SUCCESS, noglue_a_rrset));
-    } else if (name == Name("glue.ns.example.com") && type == RRType::AAAA() &&
-        (options & FIND_GLUE_OK) != 0) {
-        return (FindResult(SUCCESS, glue_aaaa_rrset));
-    } else if (name == Name("example.com") && type == RRType::SOA() &&
-        has_SOA_)
-    {
-        return (FindResult(SUCCESS, soa_rrset));
-    } else if (name == Name("example.com") && type == RRType::NS() &&
-        has_apex_NS_)
-    {
-        return (FindResult(SUCCESS, auth_ns_rrset));
-    } else if (name == Name("example.com") && type == RRType::ANY()) {
-        target->addRRset(soa_rrset);
-        target->addRRset(auth_ns_rrset);
-        return (FindResult(SUCCESS, RRsetPtr()));
-    } else if (name == Name("mx.delegation.example.com") &&
-        type == RRType::A() && (options & FIND_GLUE_OK) != 0)
-    {
-        return (FindResult(SUCCESS, delegated_mx_a_rrset));
-    } else if (name == Name("delegation.example.com") ||
-        name.compare(Name("delegation.example.com")).getRelation() ==
-        NameComparisonResult::SUBDOMAIN)
-    {
-        return (FindResult(DELEGATION, delegation_rrset));
-    } else if (name == Name("ns.example.com")) {
-        return (FindResult(DELEGATION, ns_rrset));
-    } else if (name == Name("nxdomain.example.com")) {
+    // Emulating a broken zone: mandatory apex RRs are missing if specifically
+    // configured so (which are rare cases).
+    if (name == origin_ && type == RRType::SOA() && !has_SOA_) {
         return (FindResult(NXDOMAIN, RRsetPtr()));
-    } else if (name == Name("nxrrset.example.com")) {
+    } else if (name == origin_ && type == RRType::NS() && !has_apex_NS_) {
+        return (FindResult(NXDOMAIN, RRsetPtr()));
+    }
+
+    // Special case for names on or under a zone cut
+    if ((options & FIND_GLUE_OK) == 0 &&
+        (name == delegation_name_ ||
+         name.compare(delegation_name_).getRelation() ==
+         NameComparisonResult::SUBDOMAIN)) {
+        return (FindResult(DELEGATION, delegation_rrset_));
+    }
+
+    // normal cases.  names are searched for only per exact-match basis
+    // for simplicity.
+    const Domains::const_iterator found_domain = domains_.find(name);
+    if (found_domain != domains_.end()) {
+        // First, try exact match.
+        RRsetStore::const_iterator found_rrset =
+            found_domain->second.find(type);
+        if (found_rrset != found_domain->second.end()) {
+            return (FindResult(SUCCESS, found_rrset->second));
+        }
+
+        // If not found but the qtype is ANY, return the first RRset
+        if (!found_domain->second.empty() && type == RRType::ANY()) {
+            for (found_rrset = found_domain->second.begin();
+                 found_rrset != found_domain->second.end(); found_rrset++)
+            {
+                // Insert RRs under the domain name into target
+                if (target) {
+                    target->addRRset(
+                        boost::const_pointer_cast<RRset>(found_rrset->second));
+                }
+            }
+            return (FindResult(SUCCESS, found_domain->second.begin()->second));
+        }
+
+        // Otherwise, if this domain name has CNAME, return it.
+        found_rrset = found_domain->second.find(RRType::CNAME());
+        if (found_rrset != found_domain->second.end()) {
+            return (FindResult(CNAME, found_rrset->second));
+        }
+
+        // Otherwise it's NXRRSET case.
         return (FindResult(NXRRSET, RRsetPtr()));
-    } else if ((name == Name("cname.example.com"))) {
-        return (FindResult(CNAME, cname_rrset));
-    } else if (name == Name("cnamemailer.example.com")) {
-        return (FindResult(CNAME, mx_cname_rrset_));
-    } else if (name == Name("mx.example.com")) {
-        return (FindResult(SUCCESS, mx_rrset_));
-    } else {
-        return (FindResult(DNAME, RRsetPtr()));
     }
+
+    // query name isn't found in our domains.  returns NXDOMAIN.
+    return (FindResult(NXDOMAIN, RRsetPtr()));
 }
 
 class QueryTest : public ::testing::Test {
@@ -191,195 +199,145 @@ protected:
     QueryTest() :
         qname(Name("www.example.com")), qclass(RRClass::IN()),
         qtype(RRType::A()), response(Message::RENDER),
-        query(memory_datasrc, qname, qtype, response)
+        qid(response.getQid()), query_code(Opcode::QUERY().getCode())
     {
         response.setRcode(Rcode::NOERROR());
+        response.setOpcode(Opcode::QUERY());
+        // create and add a matching zone.
+        mock_zone = new MockZone();
+        memory_datasrc.addZone(ZonePtr(mock_zone));
     }
+    MockZone* mock_zone;
     MemoryDataSrc memory_datasrc;
     const Name qname;
     const RRClass qclass;
     const RRType qtype;
     Message response;
-    Query query;
+    const qid_t qid;
+    const uint16_t query_code;
 };
 
+// A wrapper to check resulting response message commonly used in
+// tests below.
+// check_origin needs to be specified only when the authority section has
+// an SOA RR.  The interface is not generic enough but should be okay
+// for our test cases in practice.
+void
+responseCheck(Message& response, const isc::dns::Rcode& rcode,
+              unsigned int flags, const unsigned int ancount,
+              const unsigned int nscount, const unsigned int arcount,
+              const char* const expected_answer,
+              const char* const expected_authority,
+              const char* const expected_additional,
+              const Name& check_origin = Name::ROOT_NAME())
+{
+    // In our test cases QID, Opcode, and QDCOUNT should be constant, so
+    // we don't bother the test cases specifying these values.
+    headerCheck(response, response.getQid(), rcode, Opcode::QUERY().getCode(),
+                flags, 0, ancount, nscount, arcount);
+    if (expected_answer != NULL) {
+        rrsetsCheck(expected_answer,
+                    response.beginSection(Message::SECTION_ANSWER),
+                    response.endSection(Message::SECTION_ANSWER));
+    }
+    if (expected_authority != NULL) {
+        rrsetsCheck(expected_authority,
+                    response.beginSection(Message::SECTION_AUTHORITY),
+                    response.endSection(Message::SECTION_AUTHORITY),
+                    check_origin);
+    }
+    if (expected_additional != NULL) {
+        rrsetsCheck(expected_additional,
+                    response.beginSection(Message::SECTION_ADDITIONAL),
+                    response.endSection(Message::SECTION_ADDITIONAL));
+    }
+}
+
 TEST_F(QueryTest, noZone) {
     // There's no zone in the memory datasource.  So the response should have
     // REFUSED.
-    EXPECT_NO_THROW(query.process());
+    MemoryDataSrc empty_memory_datasrc;
+    Query nozone_query(empty_memory_datasrc, qname, qtype, response);
+    EXPECT_NO_THROW(nozone_query.process());
     EXPECT_EQ(Rcode::REFUSED(), response.getRcode());
 }
 
 TEST_F(QueryTest, exactMatch) {
-    // add a matching zone.
-    memory_datasrc.addZone(ZonePtr(new MockZone()));
+    Query query(memory_datasrc, qname, qtype, response);
     EXPECT_NO_THROW(query.process());
     // find match rrset
-    EXPECT_TRUE(response.getHeaderFlag(Message::HEADERFLAG_AA));
-    EXPECT_EQ(Rcode::NOERROR(), response.getRcode());
-    EXPECT_TRUE(response.hasRRset(Message::SECTION_ANSWER,
-                                  Name("www.example.com"), RRClass::IN(),
-                                  RRType::A()));
-    EXPECT_TRUE(response.hasRRset(Message::SECTION_AUTHORITY,
-                                  Name("example.com"), RRClass::IN(),
-                                  RRType::NS()));
-    EXPECT_TRUE(response.hasRRset(Message::SECTION_ADDITIONAL,
-                                  Name("glue.ns.example.com"),
-                                  RRClass::IN(), RRType::A()));
-    EXPECT_TRUE(response.hasRRset(Message::SECTION_ADDITIONAL,
-                                  Name("glue.ns.example.com"),
-                                  RRClass::IN(), RRType::AAAA()));
-    EXPECT_TRUE(response.hasRRset(Message::SECTION_ADDITIONAL,
-                                  Name("noglue.example.com"),
-                                  RRClass::IN(), RRType::A()));
+    responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
+                  www_a_txt, zone_ns_txt, ns_addrs_txt);
 }
 
 TEST_F(QueryTest, exactAddrMatch) {
     // find match rrset, omit additional data which has already been provided
     // in the answer section from the additional.
-    memory_datasrc.addZone(ZonePtr(new MockZone()));
-    const Name noglue_name(Name("noglue.example.com"));
-    Query noglue_query(memory_datasrc, noglue_name, qtype, response);
-    EXPECT_NO_THROW(noglue_query.process());
-    EXPECT_TRUE(response.getHeaderFlag(Message::HEADERFLAG_AA));
-    EXPECT_EQ(Rcode::NOERROR(), response.getRcode());
-    EXPECT_TRUE(response.hasRRset(Message::SECTION_ANSWER,
-                                  Name("noglue.example.com"), RRClass::IN(),
-                                  RRType::A()));
-    EXPECT_TRUE(response.hasRRset(Message::SECTION_AUTHORITY,
-                                  Name("example.com"), RRClass::IN(),
-                                  RRType::NS()));
-    EXPECT_TRUE(response.hasRRset(Message::SECTION_ADDITIONAL,
-                                  Name("glue.ns.example.com"),
-                                  RRClass::IN(), RRType::A()));
-    EXPECT_TRUE(response.hasRRset(Message::SECTION_ADDITIONAL,
-                                  Name("glue.ns.example.com"),
-                                  RRClass::IN(), RRType::AAAA()));
-    EXPECT_FALSE(response.hasRRset(Message::SECTION_ADDITIONAL,
-                                  Name("noglue.example.com"),
-                                  RRClass::IN(), RRType::A()));
+    EXPECT_NO_THROW(Query(memory_datasrc, Name("noglue.example.com"), qtype,
+                          response).process());
+
+    responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 2,
+                  "noglue.example.com. 3600 IN A 192.0.2.53\n", zone_ns_txt,
+                  "glue.delegation.example.com. 3600 IN A 192.0.2.153\n"
+                  "glue.delegation.example.com. 3600 IN AAAA 2001:db8::53\n");
 }
 
 TEST_F(QueryTest, apexNSMatch) {
     // find match rrset, omit authority data which has already been provided
     // in the answer section from the authority section.
-    memory_datasrc.addZone(ZonePtr(new MockZone()));
-    const Name apex_name(Name("example.com"));
-    Query apex_ns_query(memory_datasrc, apex_name, RRType::NS(), response);
-    EXPECT_NO_THROW(apex_ns_query.process());
-    EXPECT_TRUE(response.getHeaderFlag(Message::HEADERFLAG_AA));
-    EXPECT_EQ(Rcode::NOERROR(), response.getRcode());
-    EXPECT_TRUE(response.hasRRset(Message::SECTION_ANSWER,
-                                  Name("example.com"), RRClass::IN(),
-                                  RRType::NS()));
-    EXPECT_FALSE(response.hasRRset(Message::SECTION_AUTHORITY,
-                                  Name("example.com"), RRClass::IN(),
-                                  RRType::NS()));
-    EXPECT_TRUE(response.hasRRset(Message::SECTION_ADDITIONAL,
-                                  Name("glue.ns.example.com"),
-                                  RRClass::IN(), RRType::A()));
-    EXPECT_TRUE(response.hasRRset(Message::SECTION_ADDITIONAL,
-                                  Name("glue.ns.example.com"),
-                                  RRClass::IN(), RRType::AAAA()));
-    EXPECT_TRUE(response.hasRRset(Message::SECTION_ADDITIONAL,
-                                  Name("noglue.example.com"),
-                                  RRClass::IN(), RRType::A()));
+    EXPECT_NO_THROW(Query(memory_datasrc, Name("example.com"), RRType::NS(),
+                          response).process());
+
+    responseCheck(response, Rcode::NOERROR(), AA_FLAG, 3, 0, 3,
+                  zone_ns_txt, NULL, ns_addrs_txt);
 }
 
 TEST_F(QueryTest, exactAnyMatch) {
     // find match rrset, omit additional data which has already been provided
     // in the answer section from the additional.
-    memory_datasrc.addZone(ZonePtr(new MockZone()));
-    const Name noglue_name(Name("noglue.example.com"));
-    Query noglue_query(memory_datasrc, noglue_name, RRType::ANY(), response);
-    EXPECT_NO_THROW(noglue_query.process());
-    EXPECT_TRUE(response.getHeaderFlag(Message::HEADERFLAG_AA));
-    EXPECT_EQ(Rcode::NOERROR(), response.getRcode());
-    EXPECT_TRUE(response.hasRRset(Message::SECTION_ANSWER,
-                                  Name("noglue.example.com"), RRClass::IN(),
-                                  RRType::A()));
-    EXPECT_TRUE(response.hasRRset(Message::SECTION_AUTHORITY,
-                                  Name("example.com"), RRClass::IN(),
-                                  RRType::NS()));
-    EXPECT_TRUE(response.hasRRset(Message::SECTION_ADDITIONAL,
-                                  Name("glue.ns.example.com"),
-                                  RRClass::IN(), RRType::A()));
-    EXPECT_TRUE(response.hasRRset(Message::SECTION_ADDITIONAL,
-                                  Name("glue.ns.example.com"),
-                                  RRClass::IN(), RRType::AAAA()));
-    EXPECT_FALSE(response.hasRRset(Message::SECTION_ADDITIONAL,
-                                  Name("noglue.example.com"),
-                                  RRClass::IN(), RRType::A()));
+    EXPECT_NO_THROW(Query(memory_datasrc, Name("noglue.example.com"),
+                          RRType::ANY(), response).process());
+
+    responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 2,
+                  "noglue.example.com. 3600 IN A 192.0.2.53\n",
+                  zone_ns_txt,
+                  "glue.delegation.example.com. 3600 IN A 192.0.2.153\n"
+                  "glue.delegation.example.com. 3600 IN AAAA 2001:db8::53\n");
 }
 
 // This tests that when we need to look up Zone's apex NS records for
 // authoritative answer, and there is no apex NS records. It should
 // throw in that case.
 TEST_F(QueryTest, noApexNS) {
-    // Add a zone without apex NS records
-    memory_datasrc.addZone(ZonePtr(new MockZone(true, false)));
-    const Name noglue_name(Name("noglue.example.com"));
-    Query noglue_query(memory_datasrc, noglue_name, qtype, response);
-    EXPECT_THROW(noglue_query.process(), Query::NoApexNS);
-    // We don't look into the response, as it throwed
+    // Disable apex NS record
+    mock_zone->setApexNSFlag(false);
+
+    EXPECT_THROW(Query(memory_datasrc, Name("noglue.example.com"), qtype,
+                       response).process(), Query::NoApexNS);
+    // We don't look into the response, as it threw
 }
 
 TEST_F(QueryTest, delegation) {
-    // add a matching zone.
-    memory_datasrc.addZone(ZonePtr(new MockZone()));
-    const Name delegation_name(Name("delegation.example.com"));
-    Query delegation_query(memory_datasrc, delegation_name, qtype, response);
-    EXPECT_NO_THROW(delegation_query.process());
-    EXPECT_FALSE(response.getHeaderFlag(Message::HEADERFLAG_AA));
-    EXPECT_EQ(Rcode::NOERROR(), response.getRcode());
-    EXPECT_TRUE(response.hasRRset(Message::SECTION_AUTHORITY,
-                                  Name("delegation.example.com"),
-                                  RRClass::IN(), RRType::NS()));
-    // glue address records
-    EXPECT_TRUE(response.hasRRset(Message::SECTION_ADDITIONAL,
-                                  Name("glue.ns.example.com"),
-                                  RRClass::IN(), RRType::A()));
-    EXPECT_TRUE(response.hasRRset(Message::SECTION_ADDITIONAL,
-                                  Name("glue.ns.example.com"),
-                                  RRClass::IN(), RRType::AAAA()));
-    // noglue address records
-    EXPECT_TRUE(response.hasRRset(Message::SECTION_ADDITIONAL,
-                                  Name("noglue.example.com"),
-                                  RRClass::IN(), RRType::A()));
-    // NS name has a CNAME
-    EXPECT_FALSE(response.hasRRset(Message::SECTION_ADDITIONAL,
-                                  Name("www.example.com"),
-                                  RRClass::IN(), RRType::A()));
-    // NS name is out of zone
-    EXPECT_FALSE(response.hasRRset(Message::SECTION_ADDITIONAL,
-                                  Name("example.org"),
-                                  RRClass::IN(), RRType::A()));
+    EXPECT_NO_THROW(Query(memory_datasrc, Name("delegation.example.com"),
+                          qtype, response).process());
+
+    responseCheck(response, Rcode::NOERROR(), 0, 0, 4, 3,
+                  NULL, delegation_txt, ns_addrs_txt);
 }
 
 TEST_F(QueryTest, nxdomain) {
-    // add a matching zone.
-    memory_datasrc.addZone(ZonePtr(new MockZone()));
-    const Name nxdomain_name(Name("nxdomain.example.com"));
-    Query nxdomain_query(memory_datasrc, nxdomain_name, qtype, response);
-    EXPECT_NO_THROW(nxdomain_query.process());
-    EXPECT_EQ(Rcode::NXDOMAIN(), response.getRcode());
-    EXPECT_EQ(0, response.getRRCount(Message::SECTION_ANSWER));
-    EXPECT_EQ(0, response.getRRCount(Message::SECTION_ADDITIONAL));
-    EXPECT_TRUE(response.hasRRset(Message::SECTION_AUTHORITY,
-        Name("example.com"), RRClass::IN(), RRType::SOA()));
+    EXPECT_NO_THROW(Query(memory_datasrc, Name("nxdomain.example.com"), qtype,
+                          response).process());
+    responseCheck(response, Rcode::NXDOMAIN(), AA_FLAG, 0, 1, 0,
+                  NULL, soa_txt, NULL, mock_zone->getOrigin());
 }
 
 TEST_F(QueryTest, nxrrset) {
-    // add a matching zone.
-    memory_datasrc.addZone(ZonePtr(new MockZone()));
-    const Name nxrrset_name(Name("nxrrset.example.com"));
-    Query nxrrset_query(memory_datasrc, nxrrset_name, qtype, response);
-    EXPECT_NO_THROW(nxrrset_query.process());
-    EXPECT_EQ(Rcode::NOERROR(), response.getRcode());
-    EXPECT_EQ(0, response.getRRCount(Message::SECTION_ANSWER));
-    EXPECT_EQ(0, response.getRRCount(Message::SECTION_ADDITIONAL));
-    EXPECT_TRUE(response.hasRRset(Message::SECTION_AUTHORITY,
-        Name("example.com"), RRClass::IN(), RRType::SOA()));
+    EXPECT_NO_THROW(Query(memory_datasrc, Name("www.example.com"),
+                          RRType::TXT(), response).process());
+
+    responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 1, 0,
+                  NULL, soa_txt, NULL, mock_zone->getOrigin());
 }
 
 /*
@@ -387,27 +345,23 @@ TEST_F(QueryTest, nxrrset) {
  * throw in that case.
  */
 TEST_F(QueryTest, noSOA) {
-    memory_datasrc.addZone(ZonePtr(new MockZone(false)));
+    // disable zone's SOA RR.
+    mock_zone->setSOAFlag(false);
 
     // The NX Domain
-    const Name nxdomain_name(Name("nxdomain.example.com"));
-    Query nxdomain_query(memory_datasrc, nxdomain_name, qtype, response);
-    EXPECT_THROW(nxdomain_query.process(), Query::NoSOA);
+    EXPECT_THROW(Query(memory_datasrc, Name("nxdomain.example.com"),
+                       qtype, response).process(), Query::NoSOA);
     // Of course, we don't look into the response, as it throwed
 
     // NXRRSET
-    const Name nxrrset_name(Name("nxrrset.example.com"));
-    Query nxrrset_query(memory_datasrc, nxrrset_name, qtype, response);
-    EXPECT_THROW(nxrrset_query.process(), Query::NoSOA);
+    EXPECT_THROW(Query(memory_datasrc, Name("nxrrset.example.com"),
+                       qtype, response).process(), Query::NoSOA);
 }
 
 TEST_F(QueryTest, noMatchZone) {
     // there's a zone in the memory datasource but it doesn't match the qname.
     // should result in REFUSED.
-    memory_datasrc.addZone(ZonePtr(new MockZone()));
-    const Name nomatch_name(Name("example.org"));
-    Query nomatch_query(memory_datasrc, nomatch_name, qtype, response);
-    nomatch_query.process();
+    Query(memory_datasrc, Name("example.org"), qtype, response).process();
     EXPECT_EQ(Rcode::REFUSED(), response.getRcode());
 }
 
@@ -418,76 +372,27 @@ TEST_F(QueryTest, noMatchZone) {
  * A record, other to unknown out of zone one.
  */
 TEST_F(QueryTest, MX) {
-    memory_datasrc.addZone(ZonePtr(new MockZone()));
-    Name qname("mx.example.com");
-    Query mx_query(memory_datasrc, qname, RRType::MX(), response);
-    EXPECT_NO_THROW(mx_query.process());
-    EXPECT_EQ(Rcode::NOERROR(), response.getRcode());
-    EXPECT_TRUE(response.hasRRset(Message::SECTION_ANSWER,
-        Name("mx.example.com"), RRClass::IN(), RRType::MX()));
-    EXPECT_TRUE(response.hasRRset(Message::SECTION_ADDITIONAL,
-        Name("www.example.com"), RRClass::IN(), RRType::A()));
-    // We want to skip the additional ones related to authoritative
-    RRsetPtr ns;
-    for (SectionIterator<RRsetPtr> ai(response.beginSection(
-        Message::SECTION_AUTHORITY)); ai != response.endSection(
-        Message::SECTION_AUTHORITY); ++ai)
-    {
-        if ((*ai)->getName() == Name("example.com") && (*ai)->getType() ==
-            RRType::NS())
-        {
-            ns = *ai;
-            break;
-        }
-    }
-    /*
-     * In fact, the MX RRset mentions three names, but we don't know anything
-     * about one of them and one is under a zone cut, so we should have just
-     * one RRset (A for www.example.com)
-     */
-    // We can't use getRRCount, as it counts RRs, not RRsets
-    unsigned additional_count(0);
-    for (SectionIterator<RRsetPtr> ai(response.beginSection(
-        Message::SECTION_ADDITIONAL)); ai != response.endSection(
-        Message::SECTION_ADDITIONAL); ++ai)
-    {
-        // Skip the ones for the NS record
-        if (ns) {
-            for (RdataIteratorPtr nsi(ns->getRdataIterator()); !nsi->isLast();
-                nsi->next())
-            {
-                if ((*ai)->getName() ==
-                    dynamic_cast<const isc::dns::rdata::generic::NS&>(
-                    nsi->getCurrent()).getNSName())
-                {
-                    goto NS_ADDITIONAL_DATA;
-                }
-            }
-        }
-        // It is not related to the NS, then it must be related to the MX
-        ++additional_count;
-        EXPECT_EQ(Name("www.example.com"), (*ai)->getName());
-        EXPECT_EQ(RRType::A(), (*ai)->getType());
-        NS_ADDITIONAL_DATA:;
-    }
-    EXPECT_EQ(1, additional_count);
+    Query(memory_datasrc, Name("mx.example.com"), RRType::MX(),
+          response).process();
+
+    responseCheck(response, Rcode::NOERROR(), AA_FLAG, 3, 3, 4,
+                  mx_txt, NULL,
+                  (string(ns_addrs_txt) + string(www_a_txt)).c_str());
 }
 
 /*
- * Test when we ask for MX and encounter an alias (CNAME in this case).
+ * Test when we ask for MX whose exchange is an alias (CNAME in this case).
  *
- * This should not trigger the additional processing.
+ * This should not trigger the additional processing for the exchange.
  */
 TEST_F(QueryTest, MXAlias) {
-    memory_datasrc.addZone(ZonePtr(new MockZone()));
-    Name qname("cnamemailer.example.com");
-    Query mx_query(memory_datasrc, qname, RRType::MX(), response);
-    EXPECT_NO_THROW(mx_query.process());
-    EXPECT_EQ(Rcode::NOERROR(), response.getRcode());
-    // We should not have the IP address in additional section
-    // Currently, the section should be completely empty
-    EXPECT_TRUE(response.beginSection(Message::SECTION_ADDITIONAL) ==
-        response.endSection(Message::SECTION_ADDITIONAL));
-}
+    Query(memory_datasrc, Name("cnamemx.example.com"), RRType::MX(),
+          response).process();
 
+    // there shouldn't be no additional RRs for the exchanges (we have 3
+    // RRs for the NS).  The normal MX case is tested separately so we don't
+    // bother to examine the answer (and authority) sections.
+    responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
+                  NULL, NULL, ns_addrs_txt);
+}
 }

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

@@ -12,8 +12,6 @@
 // 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>

+ 0 - 2
src/bin/auth/tests/statistics_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$
-
 #include <config.h>
 
 #include <gtest/gtest.h>

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

@@ -17,7 +17,6 @@
  - PERFORMANCE OF THIS SOFTWARE.
 -->
 
-<!-- $Id$ -->
 <refentry>
 
   <refentryinfo>

+ 53 - 4
src/bin/bindctl/bindctl.1

@@ -2,12 +2,12 @@
 .\"     Title: bindctl
 .\"    Author: [see the "AUTHORS" section]
 .\" Generator: DocBook XSL Stylesheets v1.75.2 <http://docbook.sf.net/>
-.\"      Date: March 18, 2010
+.\"      Date: December 23, 2010
 .\"    Manual: BIND10
 .\"    Source: BIND10
 .\"  Language: English
 .\"
-.TH "BINDCTL" "1" "March 18, 2010" "BIND10" "BIND10"
+.TH "BINDCTL" "1" "December 23, 2010" "BIND10" "BIND10"
 .\" -----------------------------------------------------------------
 .\" * set default formatting
 .\" -----------------------------------------------------------------
@@ -22,7 +22,7 @@
 bindctl \- control and configure BIND 10
 .SH "SYNOPSIS"
 .HP \w'\fBbindctl\fR\ 'u
-\fBbindctl\fR
+\fBbindctl\fR [\fB\-a\ \fR\fB\fIaddress\fR\fR] [\fB\-h\fR] [\fB\-c\ \fR\fB\fIfile\fR\fR] [\fB\-p\ \fR\fB\fInumber\fR\fR] [\fB\-\-address\ \fR\fB\fIaddress\fR\fR] [\fB\-\-help\fR] [\fB\-\-certificate\-chain\ \fR\fB\fIfile\fR\fR] [\fB\-\-port\ \fR\fB\fInumber\fR\fR] [\fB\-\-version\fR]
 .SH "DESCRIPTION"
 .PP
 The
@@ -32,15 +32,64 @@ via its interactive command interpreter\&.
 .PP
 
 \fBbindctl\fR
-communicates over the REST\-ful interface provided by
+communicates over a HTTPS REST\-ful interface provided by
 \fBb10-cmdctl\fR(8)\&. The
 \fBb10-cfgmgr\fR(8)
 daemon stores the configurations and defines the commands\&.
+.SH "ARGUMENTS"
+.PP
+The arguments are as follows:
+.PP
+\fB\-a\fR \fIaddress\fR, \fB\-\-address\fR \fIaddress\fR
+.RS 4
+The IPv4 or IPv6 address to use to connect to the running
+\fBb10-cmdctl\fR(8)
+daemon\&. The default is 127\&.0\&.0\&.1\&.
+.RE
+.PP
+\fB\-c\fR \fIfile\fR, \fB\-\-certificate\-chain\fR \fIfile\fR
+.RS 4
+The PEM formatted server certificate validation chain file\&.
+.RE
+.PP
+\fB\-h\fR, \fB\-\-help\fR
+.RS 4
+Display command usage\&.
+.RE
+.PP
+\fB\-p\fR \fInumber\fR, \fB\-\-port\fR \fInumber\fR
+.RS 4
+The port number to use to connect to the running
+\fBb10-cmdctl\fR(8)
+daemon\&. The default is 8080\&.
+.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
+This default port number may change\&.
+.sp .5v
+.RE
+.RE
+.PP
+\fB\-\-version\fR
+.RS 4
+Display the version number and exit\&.
+.RE
+.SH "AUTHENTICATION"
 .PP
 The tool will authenticate using a username and password\&. On the first successful login, it will save the details to
 ~/\&.bind10/default_user\&.csv
 which will be used for later uses of
 \fBbindctl\fR\&.
+.SH "USAGE"
 .PP
 The
 \fBbindctl\fR

+ 87 - 3
src/bin/bindctl/bindctl.xml

@@ -17,11 +17,10 @@
  - PERFORMANCE OF THIS SOFTWARE.
 -->
 
-<!-- $Id$ -->
 <refentry>
 
   <refentryinfo>
-    <date>March 18, 2010</date>
+    <date>December 23, 2010</date>
   </refentryinfo>
 
   <refmeta>
@@ -45,6 +44,15 @@
   <refsynopsisdiv>
     <cmdsynopsis>
       <command>bindctl</command>
+      <arg><option>-a <replaceable>address</replaceable></option></arg>
+      <arg><option>-h</option></arg>
+      <arg><option>-c <replaceable>file</replaceable></option></arg>
+      <arg><option>-p <replaceable>number</replaceable></option></arg>
+      <arg><option>--address <replaceable>address</replaceable></option></arg>
+      <arg><option>--help</option></arg>
+      <arg><option>--certificate-chain <replaceable>file</replaceable></option></arg>
+      <arg><option>--port <replaceable>number</replaceable></option></arg>
+      <arg><option>--version</option></arg>
     </cmdsynopsis>
   </refsynopsisdiv>
 
@@ -60,7 +68,7 @@
     </para>
 
     <para>
-      <command>bindctl</command> communicates over the REST-ful
+      <command>bindctl</command> communicates over a HTTPS REST-ful
       interface provided by
       <citerefentry><refentrytitle>b10-cmdctl</refentrytitle><manvolnum>8</manvolnum></citerefentry>.
       The
@@ -68,6 +76,75 @@
       daemon stores the configurations and defines the commands.
     </para>
 
+  </refsect1>
+
+  <refsect1>
+    <title>ARGUMENTS</title>
+
+    <para>The arguments are as follows:</para>
+
+    <variablelist>
+
+      <varlistentry>
+        <term><option>-a</option> <replaceable>address</replaceable>, <option>--address</option> <replaceable>address</replaceable></term>
+
+        <listitem>
+          <para>The IPv4 or IPv6 address to use to connect to the running
+            <citerefentry><refentrytitle>b10-cmdctl</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+            daemon.
+            The default is 127.0.0.1.
+          </para>
+         </listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>-c</option> <replaceable>file</replaceable>,
+        <option>--certificate-chain</option> <replaceable>file</replaceable></term>
+
+        <listitem>
+          <para>The PEM formatted server certificate validation chain file.
+          </para>
+<!-- TODO: any default? -->
+<!-- TODO: any way to choose this for cmdctl? -->
+         </listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>-h</option>,
+          <option>--help</option></term>
+        <listitem><para>
+          Display command usage.</para>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>-p</option> <replaceable>number</replaceable>, <option>--port</option> <replaceable>number</replaceable></term>
+
+        <listitem>
+          <para>The port number to use to connect to the running
+            <citerefentry><refentrytitle>b10-cmdctl</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+            daemon.
+            The default is 8080.</para>
+<!-- TODO: -->
+            <note><simpara>This default port number may change.</simpara></note>
+         </listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term>
+          <option>--version</option></term>
+        <listitem><para>
+          Display the version number and exit.</para>
+        </listitem>
+      </varlistentry>
+
+    </variablelist>
+
+  </refsect1>
+
+  <refsect1>
+    <title>AUTHENTICATION</title>
+
     <para>
       The tool will authenticate using a username and password.
       On the first successful login, it will save the details to
@@ -75,6 +152,13 @@
       which will be used for later uses of <command>bindctl</command>.
     </para>
 
+<!-- TODO: mention HTTPS? -->
+
+  </refsect1>
+
+  <refsect1>
+    <title>USAGE</title>
+
     <para>
       The <command>bindctl</command> prompt shows
       <quote>&gt; </quote>.

+ 0 - 2
src/bin/cfgmgr/b10-cfgmgr.py.in

@@ -15,8 +15,6 @@
 # NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
 # WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 
-# $Id$
-
 import sys; sys.path.append ('@@PYTHONPATH@@')
 
 from isc.config.cfgmgr import ConfigManager, ConfigManagerDataReadError

+ 0 - 1
src/bin/cfgmgr/b10-cfgmgr.xml

@@ -17,7 +17,6 @@
  - PERFORMANCE OF THIS SOFTWARE.
 -->
 
-<!-- $Id$ -->
 <refentry>
 
   <refentryinfo>

+ 0 - 2
src/bin/cfgmgr/tests/b10-cfgmgr_test.py.in

@@ -13,8 +13,6 @@
 # NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
 # WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 
-# $Id: cfgmgr_test.py 2126 2010-06-16 14:40:22Z jelte $
-
 #
 # Tests for the configuration manager run script
 #

+ 0 - 1
src/bin/cmdctl/b10-cmdctl.xml

@@ -17,7 +17,6 @@
  - PERFORMANCE OF THIS SOFTWARE.
 -->
 
-<!-- $Id$ -->
 <refentry>
 
   <refentryinfo>

+ 0 - 1
src/bin/loadzone/b10-loadzone.xml

@@ -17,7 +17,6 @@
  - PERFORMANCE OF THIS SOFTWARE.
 -->
 
-<!-- $Id$ -->
 <refentry>
 
   <refentryinfo>

+ 0 - 1
src/bin/loadzone/tests/correct/mix1.db

@@ -1,4 +1,3 @@
-; $Id: ttl1.db,v 1.6 2007/06/19 23:47:04 tbox Exp $
 $ORIGIN mix1.
 @			IN SOA	ns hostmaster (
 				1        ; serial

+ 0 - 1
src/bin/loadzone/tests/correct/ttlext.db

@@ -1,4 +1,3 @@
-; $Id: ttl1.db,v 1.6 2007/06/19 23:47:04 tbox Exp $
 $ORIGIN ttlext.
 @			IN SOA	ns hostmaster (
 				1        ; serial

+ 82 - 9
src/bin/msgq/msgq.py.in

@@ -127,6 +127,7 @@ class MsgQ:
         self.hostname = socket.gethostname()
         self.subs = SubscriptionManager()
         self.lnames = {}
+        self.sendbuffs = {}
 
     def setup_poller(self):
         """Set up the poll thing.  Internal function."""
@@ -135,9 +136,11 @@ class MsgQ:
         except AttributeError:
             self.kqueue = select.kqueue()
     
-    def add_kqueue_socket(self, socket):
-        event = select.kevent(socket.fileno(),
-                              select.KQ_FILTER_READ,
+    def add_kqueue_socket(self, socket, enable_write = False):
+        filters = select.KQ_FILTER_READ
+        if enable_write:
+            filters |= select.KQ_FILTER_WRITE
+        event = select.kevent(socket.fileno(), filters,
                               select.KQ_EV_ADD | select.KQ_EV_ENABLE)
         self.kqueue.control([event], 0)
 
@@ -187,6 +190,12 @@ class MsgQ:
         # TODO: When we have logging, we might want
         # to add a debug message here that a new connection
         # was made
+        self.register_socket(self, newsocket)
+
+    def register_socket(self, newsocket):
+        """
+        Internal function to insert a socket. Used by process_accept and some tests.
+        """
         self.sockets[newsocket.fileno()] = newsocket
         lname = self.newlname()
         self.lnames[lname] = newsocket
@@ -198,10 +207,10 @@ class MsgQ:
 
     def process_socket(self, fd):
         """Process a read on a socket."""
-        sock = self.sockets[fd]
-        if sock == None:
+        if not fd in self.sockets:
             sys.stderr.write("[b10-msgq] Got read on Strange Socket fd %d\n" % fd)
             return
+        sock = self.sockets[fd]
 #        sys.stderr.write("[b10-msgq] Got read on fd %d\n" %fd)
         self.process_packet(fd, sock)
 
@@ -213,7 +222,9 @@ class MsgQ:
         lname = [ k for k, v in self.lnames.items() if v == sock ][0]
         del self.lnames[lname]
         sock.close()
-        self.sockets[fd] = None
+        del self.sockets[fd]
+        if fd in self.sendbuffs:
+            del self.sendbuffs[fd]
         sys.stderr.write("[b10-msgq] Closing socket fd %d\n" % fd)
 
     def getbytes(self, fd, sock, length):
@@ -287,6 +298,9 @@ class MsgQ:
             self.process_command_unsubscribe(sock, routing, data)
         elif cmd == 'getlname':
             self.process_command_getlname(sock, routing, data)
+        elif cmd == 'ping':
+            # Command for testing purposes
+            self.process_command_ping(sock, routing, data)
         else:
             sys.stderr.write("[b10-msgq] Invalid command: %s\n" % cmd)
 
@@ -305,10 +319,61 @@ class MsgQ:
         return ret
 
     def sendmsg(self, sock, env, msg = None):
-        sock.send(self.preparemsg(env, msg))
+        self.send_prepared_msg(sock, self.preparemsg(env, msg))
+
+    def __send_data(self, sock, data):
+        try:
+            return sock.send(data, socket.MSG_DONTWAIT)
+        except socket.error as e:
+            if e.errno == errno.EAGAIN or e.errno == errno.EWOULDBLOCK:
+                return 0
+            else:
+                raise e
 
     def send_prepared_msg(self, sock, msg):
-        sock.send(msg)
+        # Try to send the data, but only if there's nothing waiting
+        fileno = sock.fileno()
+        if fileno in self.sendbuffs:
+            amount_sent = 0
+        else:
+            amount_sent = self.__send_data(sock, msg)
+
+        # Still something to send
+        if amount_sent < len(msg):
+            now = time.clock()
+            # Append it to buffer (but check the data go away)
+            if fileno in self.sendbuffs:
+                (last_sent, buff) = self.sendbuffs[fileno]
+                if now - last_sent > 0.1:
+                    self.kill_socket(fileno, sock)
+                    return
+                buff += msg
+            else:
+                buff = msg[amount_sent:]
+                last_sent = now
+                if self.poller:
+                    self.poller.register(fileno, select.POLLIN |
+                        select.POLLOUT)
+                else:
+                    self.add_kqueue_socket(fileno, True)
+            self.sendbuffs[fileno] = (last_sent, buff)
+
+    def __process_write(self, fileno):
+        # Try to send some data from the buffer
+        (_, msg) = self.sendbuffs[fileno]
+        sock = self.sockets[fileno]
+        amount_sent = self.__send_data(sock, msg)
+        # Keep the rest
+        msg = msg[amount_sent:]
+        if len(msg) == 0:
+            # If there's no more, stop requesting for write availability
+            if self.poller:
+                self.poller.register(fileno, select.POLLIN)
+            else:
+                self.add_kqueue_socket(fileno)
+            del self.sendbuffs[fileno]
+        else:
+            self.sendbuffs[fileno] = (time.clock(), msg)
 
     def newlname(self):
         """Generate a unique connection identifier for this socket.
@@ -317,6 +382,9 @@ class MsgQ:
         self.connection_counter += 1
         return "%x_%x@%s" % (time.time(), self.connection_counter, self.hostname)
 
+    def process_command_ping(self, sock, routing, data):
+        self.sendmsg(sock, { "type" : "pong" }, data)
+
     def process_command_getlname(self, sock, routing, data):
         lname = [ k for k, v in self.lnames.items() if v == sock ][0]
         self.sendmsg(sock, { "type" : "getlname" }, { "lname" : lname })
@@ -379,7 +447,10 @@ class MsgQ:
                 if fd == self.listen_socket.fileno():
                     self.process_accept()
                 else:
-                    self.process_socket(fd)
+                    if event & select.POLLOUT:
+                        self.__process_write(fd)
+                    if event & select.POLLIN:
+                        self.process_socket(fd)
 
     def run_kqueue(self):
         while True:
@@ -391,6 +462,8 @@ class MsgQ:
                 if event.ident == self.listen_socket.fileno():
                     self.process_accept()
                 else:
+                    if event.flags & select.KQ_FILTER_WRITE:
+                        self.process_socket(event.ident)
                     if event.flags & select.KQ_FILTER_READ and event.data > 0:
                         self.process_socket(event.ident)
                     elif event.flags & select.KQ_EV_EOF:

+ 0 - 1
src/bin/msgq/msgq.xml

@@ -17,7 +17,6 @@
  - PERFORMANCE OF THIS SOFTWARE.
 -->
 
-<!-- $Id$ -->
 <refentry>
 
   <refentryinfo>

+ 121 - 2
src/bin/msgq/tests/msgq_test.py

@@ -3,10 +3,14 @@ from msgq import SubscriptionManager, MsgQ
 import unittest
 import os
 import socket
+import signal
+import sys
+import time
+import isc.cc
 
 #
-# Currently only the subscription part is implemented...  I'd have to mock
-# out a socket, which, while not impossible, is not trivial.
+# Currently only the subscription part and some sending is implemented...
+# I'd have to mock out a socket, which, while not impossible, is not trivial.
 #
 
 class TestSubscriptionManager(unittest.TestCase):
@@ -108,5 +112,120 @@ class TestSubscriptionManager(unittest.TestCase):
         msgq = MsgQ("/does/not/exist")
         self.assertRaises(socket.error, msgq.setup)
 
+class SendNonblock(unittest.TestCase):
+    """
+    Tests that the whole thing will not get blocked if someone does not read.
+    """
+
+    def terminate_check(self, task, timeout = 1):
+        """
+        Runs task in separate process (task is a function) and checks
+        it terminates sooner than timeout.
+        """
+        task_pid = os.fork()
+        if task_pid == 0:
+            # Kill the forked process after timeout by SIGALRM
+            signal.alarm(timeout)
+            # Run the task
+            # If an exception happens or we run out of time, we terminate
+            # with non-zero
+            task()
+            # If we got here, then everything worked well and in time
+            # In that case, we terminate successfully
+            sys.exit()
+        else:
+            (pid, status) = os.waitpid(task_pid, 0)
+            self.assertEqual(0, status,
+                "The task did not complete successfully in time")
+
+    def infinite_sender(self, sender):
+        """
+        Sends data until an exception happens. socket.error is caught,
+        as it means the socket got closed. Sender is called to actually
+        send the data.
+        """
+        msgq = MsgQ()
+        # We do only partial setup, so we don't create the listening socket
+        msgq.setup_poller()
+        (read, write) = socket.socketpair(socket.AF_UNIX, socket.SOCK_STREAM)
+        msgq.register_socket(write)
+        # Keep sending while it is not closed by the msgq
+        try:
+            while True:
+                sender(msgq, write)
+        except socket.error:
+            pass
+
+    def test_infinite_sendmsg(self):
+        """
+        Tries sending messages (and not reading them) until it either times
+        out (in blocking call, wrong) or closes it (correct).
+        """
+        self.terminate_check(lambda: self.infinite_sender(
+            lambda msgq, socket: msgq.sendmsg(socket, {}, {"message" : "x"})))
+
+    def test_infinite_sendprepared(self):
+        """
+        Tries sending data (and not reading them) until it either times
+        out (in blocking call, wrong) or closes it (correct).
+        """
+        self.terminate_check(lambda: self.infinite_sender(
+            lambda msgq, socket: msgq.send_prepared_msg(socket, b"data")))
+
+    def send_many(self, data):
+        """
+        Tries that sending a command many times and getting an answer works.
+        """
+        msgq = MsgQ()
+        msgq.setup_poller()
+        # msgq.run needs to compare with the listen_socket, so we provide
+        # a replacement
+        class DummySocket:
+            def fileno():
+                return -1
+        msgq.listen_socket = DummySocket
+        (queue, out) = socket.socketpair(socket.AF_UNIX, socket.SOCK_STREAM)
+        def run():
+            length = len(data)
+            queue_pid = os.fork()
+            if queue_pid == 0:
+                signal.alarm(10)
+                msgq.register_socket(queue)
+                msgq.run()
+            else:
+                try:
+                    def killall(signum, frame):
+                        os.kill(queue_pid, signal.SIGTERM)
+                        sys.exit(1)
+                    signal.signal(signal.SIGALRM, killall)
+                    msg = msgq.preparemsg({"type" : "ping"}, data)
+                    now = time.clock()
+                    while time.clock() - now < 0.2:
+                        out.sendall(msg)
+                        # Check the answer
+                        (routing, received) = msgq.read_packet(out.fileno(),
+                            out)
+                        self.assertEqual({"type" : "pong"},
+                            isc.cc.message.from_wire(routing))
+                        self.assertEqual(data, received)
+                finally:
+                    os.kill(queue_pid, signal.SIGTERM)
+        self.terminate_check(run)
+
+    def test_small_sends(self):
+        """
+        Tests sending small data many times.
+        """
+        self.send_many(b"data")
+
+    def test_large_sends(self):
+        """
+        Tests sending large data many times.
+        """
+        data = b"data"
+        for i in range(1, 20):
+            data = data + data
+        self.send_many(data)
+
 if __name__ == '__main__':
     unittest.main()

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

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

+ 39 - 6
src/bin/resolver/b10-resolver.8

@@ -2,12 +2,12 @@
 .\"     Title: b10-resolver
 .\"    Author: [FIXME: author] [see http://docbook.sf.net/el/author]
 .\" Generator: DocBook XSL Stylesheets v1.75.2 <http://docbook.sf.net/>
-.\"      Date: January 3, 2011
+.\"      Date: January 19, 2011
 .\"    Manual: BIND10
 .\"    Source: BIND10
 .\"  Language: English
 .\"
-.TH "B10\-RESOLVER" "8" "January 3, 2011" "BIND10" "BIND10"
+.TH "B10\-RESOLVER" "8" "January 19, 2011" "BIND10" "BIND10"
 .\" -----------------------------------------------------------------
 .\" * set default formatting
 .\" -----------------------------------------------------------------
@@ -38,7 +38,7 @@ C\-Channel connection\&. If this connection is not established,
 will exit\&.
 .PP
 It also receives its configurations from
-\fBb10-cfgmgr\fR(8)\&. Currently no configuration commands are defined\&.
+\fBb10-cfgmgr\fR(8)\&.
 .if n \{\
 .sp
 .\}
@@ -73,9 +73,40 @@ must be either a valid numeric user ID or a valid user name\&. By default the da
 .RS 4
 Enabled verbose mode\&. This enables diagnostic messages to STDERR\&.
 .RE
-.SH "FILES"
+.SH "CONFIGURATION AND COMMANDS"
 .PP
-None\&.
+The configurable settings are:
+.PP
+
+\fIforward_addresses\fR
+defines the list of addresses and ports that
+\fBb10\-resolver\fR
+should forward queries to\&. Defining this enables forwarding\&.
+.PP
+
+\fIlisten_on\fR
+is a list of addresses and ports for
+\fBb10\-resolver\fR
+to listen on\&. The list items are the
+\fIaddress\fR
+string and
+\fIport\fR
+number\&. The defaults are address ::1 port 5300 and address 127\&.0\&.0\&.1 port 5300\&.
+.PP
+
+\fIretries\fR
+is the number of times to retry (resend query) after a timeout\&. The default is 0 (do not retry)\&.
+.PP
+
+\fItimeout\fR
+is the number of milliseconds to wait for answer\&. If set to \-1, the timeout is disabled\&. The default is 2000\&.
+.PP
+The configuration command is:
+.PP
+
+\fBshutdown\fR
+exits
+\fBb10\-resolver\fR\&. (Note that the BIND 10 boss process will restart this service\&.)
 .SH "SEE ALSO"
 .PP
 
@@ -88,7 +119,9 @@ BIND 10 Guide\&.
 .PP
 The
 \fBb10\-resolver\fR
-daemon was first coded in September 2010\&.
+daemon was first coded in September 2010\&. The initial implementation only provided forwarding\&.
+
+
 .SH "COPYRIGHT"
 .br
 Copyright \(co 2010 Internet Systems Consortium, Inc. ("ISC")

+ 67 - 6
src/bin/resolver/b10-resolver.xml

@@ -2,7 +2,7 @@
                "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd"
 	       [<!ENTITY mdash "&#8212;">]>
 <!--
- - Copyright (C) 2010  Internet Systems Consortium, Inc. ("ISC")
+ - Copyright (C) 2010-2011  Internet Systems Consortium, Inc. ("ISC")
  -
  - Permission to use, copy, modify, and/or distribute this software for any
  - purpose with or without fee is hereby granted, provided that the above
@@ -17,11 +17,10 @@
  - PERFORMANCE OF THIS SOFTWARE.
 -->
 
-<!-- $Id$ -->
 <refentry>
 
   <refentryinfo>
-    <date>January 3, 2011</date>
+    <date>January 19, 2011</date>
   </refentryinfo>
 
   <refmeta>
@@ -68,7 +67,6 @@
     <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>
@@ -112,12 +110,71 @@
   </refsect1>
 
   <refsect1>
+    <title>CONFIGURATION AND COMMANDS</title>
+    <para>
+      The configurable settings are:
+    </para>
+
+    <para>
+      <varname>forward_addresses</varname> defines the list of addresses
+      and ports that <command>b10-resolver</command> should forward
+      queries to.
+      Defining this enables forwarding.
+<!-- TODO: list
+address
+	::1
+port
+	53
+-->
+    </para>
+
+<!-- trac386:
+
+once that is merged you can for instance do 'config add Resolver/forward_addresses { "port": 123 } and it will fill in the rest (in this case ::1 for the address)
+
+-->
+
+    <para>
+      <varname>listen_on</varname> is a list of addresses and ports for
+      <command>b10-resolver</command> to listen on.
+      The list items are the <varname>address</varname> string
+      and <varname>port</varname> number.
+      The defaults are address ::1 port 5300 and
+      address 127.0.0.1 port 5300.
+    </para>
+
+    <para>
+      <varname>retries</varname> is the number of times to retry
+      (resend query) after a timeout.
+      The default is 0 (do not retry).
+    </para>
+
+    <para>
+      <varname>timeout</varname> is the number of milliseconds to
+      wait for answer. If set to -1, the timeout is disabled.
+      The default is 2000.
+    </para>
+
+<!-- TODO: formating -->
+    <para>
+      The configuration command is:
+    </para>
+
+    <para>
+      <command>shutdown</command> exits <command>b10-resolver</command>.
+      (Note that the BIND 10 boss process will restart this service.)
+    </para>
+
+  </refsect1>
+
+<!--
+  <refsect1>
     <title>FILES</title>
     <para>
       None.
     </para>
-<!-- TODO: this is not correct yet. -->
   </refsect1>
+-->
 
   <refsect1>
     <title>SEE ALSO</title>
@@ -142,7 +199,11 @@
     <title>HISTORY</title>
     <para>
       The <command>b10-resolver</command> daemon was first coded in
-      September 2010.
+      September 2010. The initial implementation only provided
+      forwarding.
+<!-- TODO: document when caching was added -->
+<!-- TODO: document when iteration was added -->
+<!-- TODO: document when validation was added -->
     </para>
   </refsect1>
 </refentry><!--

+ 0 - 2
src/bin/resolver/main.cc

@@ -12,8 +12,6 @@
 // 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>

+ 136 - 120
src/bin/resolver/resolver.cc

@@ -12,8 +12,6 @@
 // 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>
@@ -66,6 +64,8 @@ private:
 public:
     ResolverImpl() :
         config_session_(NULL),
+        timeout_(2000),
+        retries_(3),
         rec_query_(NULL)
     {}
 
@@ -76,36 +76,58 @@ public:
     void querySetup(DNSService& dnss) {
         assert(!rec_query_); // queryShutdown must be called first
         dlog("Query setup");
-        rec_query_ = new RecursiveQuery(dnss, upstream_);
+        rec_query_ = new RecursiveQuery(dnss, upstream_, upstream_root_, timeout_, retries_);
     }
 
     void queryShutdown() {
-        dlog("Query shutdown");
-        delete rec_query_;
-        rec_query_ = NULL;
+        // only shut down if we have actually called querySetup before
+        // (this is not a safety check, just to prevent logging of
+        // actions that are not performed
+        if (rec_query_) {
+            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.",true);
-            } else {
+            if (!upstream_.empty()) {
                 dlog("Setting forward addresses:");
                 BOOST_FOREACH(const addr_t& address, upstream) {
                     dlog(" " + address.first + ":" +
                         boost::lexical_cast<string>(address.second));
                 }
-                querySetup(*dnss);
+            } else {
+                dlog("No forward addresses, running in recursive mode");
             }
         }
     }
 
-    void processNormalQuery(const Question& question, MessagePtr message,
+    void setRootAddresses(const vector<addr_t>& upstream_root,
+                          DNSService *dnss)
+    {
+        queryShutdown();
+        upstream_root_ = upstream_root;
+        if (dnss) {
+            if (!upstream_root_.empty()) {
+                dlog("Setting root addresses:");
+                BOOST_FOREACH(const addr_t& address, upstream_root) {
+                    dlog(" " + address.first + ":" +
+                        boost::lexical_cast<string>(address.second));
+                }
+            } else {
+                dlog("No root addresses");
+            }
+            querySetup(*dnss);
+        }
+    }
+
+    void processNormalQuery(const Question& question,
+                            MessagePtr answer_message,
                             OutputBufferPtr buffer,
                             DNSServer* server);
 
@@ -114,6 +136,8 @@ public:
 
     /// These members are public because Resolver accesses them directly.
     ModuleCCSession* config_session_;
+    /// Addresses of the root nameserver(s)
+    vector<addr_t> upstream_root_;
     /// Addresses of the forward nameserver
     vector<addr_t> upstream_;
     /// Addresses we listen on
@@ -146,20 +170,6 @@ public:
     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)
@@ -207,10 +217,14 @@ public:
     MessageLookup(Resolver* srv) : server_(srv) {}
 
     // \brief Handle the DNS Lookup
-    virtual void operator()(const IOMessage& io_message, MessagePtr message,
-                            OutputBufferPtr buffer, DNSServer* server) const
+    virtual void operator()(const IOMessage& io_message,
+                            MessagePtr query_message,
+                            MessagePtr answer_message,
+                            OutputBufferPtr buffer,
+                            DNSServer* server) const
     {
-        server_->processMessage(io_message, message, buffer, server);
+        server_->processMessage(io_message, query_message,
+                                answer_message, buffer, server);
     }
 private:
     Resolver* server_;
@@ -223,76 +237,62 @@ private:
 class MessageAnswer : public DNSAnswer {
 public:
     virtual void operator()(const IOMessage& io_message,
-                            MessagePtr message,
+                            MessagePtr query_message,
+                            MessagePtr answer_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());
+        const qid_t qid = query_message->getQid();
+        const bool rd = query_message->getHeaderFlag(Message::HEADERFLAG_RD);
+        const bool cd = query_message->getHeaderFlag(Message::HEADERFLAG_CD);
+        const Opcode& opcode = query_message->getOpcode();
 
-        message->clear(Message::RENDER);
-        message->setQid(qid);
-        message->setOpcode(opcode);
-        message->setRcode(rcode);
+        // Fill in the final details of the answer message
+        answer_message->setQid(qid);
+        answer_message->setOpcode(opcode);
 
-        message->setHeaderFlag(Message::HEADERFLAG_QR);
-        message->setHeaderFlag(Message::HEADERFLAG_RA);
+        answer_message->setHeaderFlag(Message::HEADERFLAG_QR);
+        answer_message->setHeaderFlag(Message::HEADERFLAG_RA);
         if (rd) {
-            message->setHeaderFlag(Message::HEADERFLAG_RD);
+            answer_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);
-                message->setRcode(incoming.getRcode());
-                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());
-            }
+            answer_message->setHeaderFlag(Message::HEADERFLAG_CD);
         }
 
+        vector<QuestionPtr> questions;
+        questions.assign(query_message->beginQuestion(), query_message->endQuestion());
+        for_each(questions.begin(), questions.end(), QuestionInserter(answer_message));
+        
         // Now we can clear the buffer and render the new message into it
         buffer->clear();
         MessageRenderer renderer(*buffer);
 
+        ConstEDNSPtr edns(query_message->getEDNS());
+        const bool dnssec_ok = edns && edns->getDNSSECAwareness();
+        if (edns) {
+            EDNSPtr edns_response(new EDNS());
+            edns_response->setDNSSECAwareness(dnssec_ok);
+
+            // TODO: We should make our own edns bufsize length configurable
+            edns_response->setUDPSize(Message::DEFAULT_MAX_EDNS0_UDPSIZE);
+            answer_message->setEDNS(edns_response);
+        }
+        
         if (io_message.getSocket().getProtocol() == IPPROTO_UDP) {
-            ConstEDNSPtr edns(message->getEDNS());
-            renderer.setLengthLimit(edns ? edns->getUDPSize() :
-                Message::DEFAULT_MAX_UDPSIZE);
+            if (edns) {
+                renderer.setLengthLimit(edns->getUDPSize());
+            } else {
+                renderer.setLengthLimit(Message::DEFAULT_MAX_UDPSIZE);
+            }
         } else {
             renderer.setLengthLimit(65535);
         }
 
-        message->toWire(renderer);
+        answer_message->toWire(renderer);
 
         dlog(string("sending a response (") +
             boost::lexical_cast<string>(renderer.getLength()) + "bytes): \n" +
-            message->toText());
+            answer_message->toText());
     }
 };
 
@@ -328,8 +328,6 @@ Resolver::~Resolver() {
 
 void
 Resolver::setDNSService(asiolink::DNSService& dnss) {
-    impl_->queryShutdown();
-    impl_->querySetup(dnss);
     dnss_ = &dnss;
 }
 
@@ -344,18 +342,21 @@ Resolver::getConfigSession() const {
 }
 
 void
-Resolver::processMessage(const IOMessage& io_message, MessagePtr message,
-                        OutputBufferPtr buffer, DNSServer* server)
+Resolver::processMessage(const IOMessage& io_message,
+                         MessagePtr query_message,
+                         MessagePtr answer_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);
+        query_message->parseHeader(request_buffer);
 
         // Ignore all responses.
-        if (message->getHeaderFlag(Message::HEADERFLAG_QR)) {
+        if (query_message->getHeaderFlag(Message::HEADERFLAG_QR)) {
             dlog("Received unexpected response, ignoring");
             server->resume(false);
             return;
@@ -368,52 +369,53 @@ Resolver::processMessage(const IOMessage& io_message, MessagePtr message,
 
     // Parse the message.  On failure, return an appropriate error.
     try {
-        message->fromWire(request_buffer);
+        query_message->fromWire(request_buffer);
     } catch (const DNSProtocolError& error) {
         dlog(string("returning ") + error.getRcode().toText() + ": " + 
             error.what());
-        makeErrorMessage(message, buffer, error.getRcode());
+        makeErrorMessage(query_message, buffer, error.getRcode());
         server->resume(true);
         return;
     } catch (const Exception& ex) {
         dlog(string("returning SERVFAIL: ") + ex.what());
-        makeErrorMessage(message, buffer, Rcode::SERVFAIL());
+        makeErrorMessage(query_message, buffer, Rcode::SERVFAIL());
         server->resume(true);
         return;
     } // other exceptions will be handled at a higher layer.
 
-    dlog("received a message:\n" + message->toText());
+    dlog("received a message:\n" + query_message->toText());
 
     // Perform further protocol-level validation.
     bool sendAnswer = true;
-    if (message->getOpcode() == Opcode::NOTIFY()) {
-        makeErrorMessage(message, buffer, Rcode::NOTAUTH());
+    if (query_message->getOpcode() == Opcode::NOTIFY()) {
+        makeErrorMessage(query_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() +
+    } else if (query_message->getOpcode() != Opcode::QUERY()) {
+        dlog("Unsupported opcode (got: " + query_message->getOpcode().toText() +
             ", expected: " + Opcode::QUERY().toText());
-        makeErrorMessage(message, buffer, Rcode::NOTIMP());
-    } else if (message->getRRCount(Message::SECTION_QUESTION) != 1) {
+        makeErrorMessage(query_message, buffer, Rcode::NOTIMP());
+    } else if (query_message->getRRCount(Message::SECTION_QUESTION) != 1) {
         dlog("The query contained " +
-            boost::lexical_cast<string>(message->getRRCount(
+            boost::lexical_cast<string>(query_message->getRRCount(
             Message::SECTION_QUESTION) + " questions, exactly one expected"));
-        makeErrorMessage(message, buffer, Rcode::FORMERR());
+        makeErrorMessage(query_message, buffer, Rcode::FORMERR());
     } else {
-        ConstQuestionPtr question = *message->beginQuestion();
+        ConstQuestionPtr question = *query_message->beginQuestion();
         const RRType &qtype = question->getType();
         if (qtype == RRType::AXFR()) {
             if (io_message.getSocket().getProtocol() == IPPROTO_UDP) {
-                makeErrorMessage(message, buffer, Rcode::FORMERR());
+                makeErrorMessage(query_message, buffer, Rcode::FORMERR());
             } else {
-                makeErrorMessage(message, buffer, Rcode::NOTIMP());
+                makeErrorMessage(query_message, buffer, Rcode::NOTIMP());
             }
         } else if (qtype == RRType::IXFR()) {
-            makeErrorMessage(message, buffer, Rcode::NOTIMP());
+            makeErrorMessage(query_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);
+            impl_->processNormalQuery(*question, answer_message,
+                                      buffer, server);
         }
     }
 
@@ -423,23 +425,13 @@ Resolver::processMessage(const IOMessage& io_message, MessagePtr message,
 }
 
 void
-ResolverImpl::processNormalQuery(const Question& question, MessagePtr message,
-                                 OutputBufferPtr buffer, DNSServer* server)
+ResolverImpl::processNormalQuery(const Question& question,
+                                 MessagePtr answer_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(ResolverImpl::DEFAULT_LOCAL_UDPSIZE);
-        message->setEDNS(edns_response);
-    }
-    rec_query_->sendQuery(question, buffer, server);
+    rec_query_->sendQuery(question, answer_message, buffer, server);
 }
 
 namespace {
@@ -474,7 +466,7 @@ parseAddresses(ConstElementPtr addresses) {
             }
         } else if (addresses->getType() != Element::null) {
             isc_throw(TypeError,
-                "forward_addresses config element must be a list");
+                "root_addresses, forward_addresses, and listen_on config element must be a list");
         }
     }
     return (result);
@@ -488,6 +480,8 @@ Resolver::updateConfig(ConstElementPtr config) {
 
     try {
         // Parse forward_addresses
+        ConstElementPtr rootAddressesE(config->get("root_addresses"));
+        vector<addr_t> rootAddresses(parseAddresses(rootAddressesE));
         ConstElementPtr forwardAddressesE(config->get("forward_addresses"));
         vector<addr_t> forwardAddresses(parseAddresses(forwardAddressesE));
         ConstElementPtr listenAddressesE(config->get("listen_on"));
@@ -515,14 +509,27 @@ Resolver::updateConfig(ConstElementPtr config) {
         }
         // Everything OK, so commit the changes
         // listenAddresses can fail to bind, so try them first
+        bool need_query_restart = false;
+        
         if (listenAddressesE) {
             setListenAddresses(listenAddresses);
+            need_query_restart = true;
         }
         if (forwardAddressesE) {
             setForwardAddresses(forwardAddresses);
+            need_query_restart = true;
+        }
+        if (rootAddressesE) {
+            setRootAddresses(rootAddresses);
         }
         if (set_timeouts) {
             setTimeouts(timeout, retries);
+            need_query_restart = true;
+        }
+
+        if (need_query_restart) {
+            impl_->queryShutdown();
+            impl_->querySetup(*dnss_);
         }
         return (isc::config::createAnswer());
     } catch (const isc::Exception& error) {
@@ -537,6 +544,12 @@ Resolver::setForwardAddresses(const vector<addr_t>& addresses)
     impl_->setForwardAddresses(addresses, dnss_);
 }
 
+void
+Resolver::setRootAddresses(const vector<addr_t>& addresses)
+{
+    impl_->setRootAddresses(addresses, dnss_);
+}
+
 bool
 Resolver::isForwarding() const {
     return (!impl_->upstream_.empty());
@@ -547,6 +560,11 @@ Resolver::getForwardAddresses() const {
     return (impl_->upstream_);
 }
 
+vector<addr_t>
+Resolver::getRootAddresses() const {
+    return (impl_->upstream_root_);
+}
+
 namespace {
 
 void
@@ -597,8 +615,6 @@ Resolver::setTimeouts(int timeout, unsigned retries) {
         " and retry count to " + boost::lexical_cast<string>(retries));
     impl_->timeout_ = timeout;
     impl_->retries_ = retries;
-    impl_->queryShutdown();
-    impl_->querySetup(*dnss_);
 }
 pair<int, unsigned>
 Resolver::getTimeouts() const {

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

@@ -12,8 +12,6 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 
-// $Id$
-
 #ifndef __RESOLVER_H
 #define __RESOLVER_H 1
 
@@ -65,7 +63,8 @@ public:
     /// \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::MessagePtr query_message,
+                        isc::dns::MessagePtr answer_message,
                         isc::dns::OutputBufferPtr buffer,
                         asiolink::DNSServer* server);
 
@@ -113,6 +112,24 @@ public:
     bool isForwarding() const;
 
     /**
+     * \brief Specify the list of root nameservers.
+     *
+     * Specify the list of addresses of root nameservers
+     *
+     * @param addresses The list of addresses to use (each one is the address
+     * and port pair).
+     */
+    void setRootAddresses(const std::vector<std::pair<std::string,
+                          uint16_t> >& addresses);
+
+    /**
+     * \short Get list of root addresses.
+     *
+     * \see setRootAddresses.
+     */
+    std::vector<std::pair<std::string, uint16_t> > getRootAddresses() const;
+
+    /**
      * Set and get the addresses we listen on.
      */
     void setListenAddresses(const std::vector<std::pair<std::string,

+ 26 - 0
src/bin/resolver/resolver.spec.pre.in

@@ -42,6 +42,32 @@
         }
       },
       {
+        "item_name": "root_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,

+ 259 - 0
src/bin/resolver/response_classifier.cc

@@ -0,0 +1,259 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+// $Id$
+
+#include <cstddef>
+#include <vector>
+
+#include <resolver/response_classifier.h>
+#include <dns/name.h>
+#include <dns/opcode.h>
+#include <dns/rcode.h>
+#include <dns/rrset.h>
+
+using namespace isc::dns;
+using namespace std;
+
+// Classify the response in the "message" object.
+
+ResponseClassifier::Category ResponseClassifier::classify(
+    const Question& question, const MessagePtr& message, bool tcignore)
+{
+    // Check header bits
+    if (!message->getHeaderFlag(Message::HEADERFLAG_QR)) {
+        return (NOTRESPONSE);   // Query-response bit not set in the response
+    }
+
+    // We only recognise responses to queries here
+    if (message->getOpcode() != Opcode::QUERY()) {
+        return (OPCODE);
+    }
+
+    // Apparently have a response.  There must be a single question in it...
+    const vector<QuestionPtr> msgquestion(message->beginQuestion(),
+            message->endQuestion());
+    if (msgquestion.size() != 1) {
+        return (NOTONEQUEST); // Not one question in response question section
+    }
+
+    // ... and the question should be equal to the question given.
+    // XXX: This means that "question" may not be the question sent by the
+    // client.  In the case of a CNAME response, the qname of subsequent
+    // questions needs to be altered.
+    if (question != *(msgquestion[0])) {
+        return (MISMATQUEST);
+    }
+
+    // Check for Rcode-related errors.
+    const Rcode& rcode = message->getRcode();
+    if (rcode != Rcode::NOERROR()) {
+        if (rcode == Rcode::NXDOMAIN()) {
+
+            // No such domain.  According to RFC2308, the domain referred to by
+            // the QNAME does not exist, although there may be a CNAME in the
+            // answer section and there may be an SOA and/or NS RRs in the
+            // authority section (ignoring any DNSSEC RRs for now).
+            //
+            // Note the "may".  There may not be anything.  Also, note that if
+            // there is a CNAME in the answer section, the authoritative server
+            // has verified that the name given in the CNAME's RDATA field does
+            // not exist. And that if a CNAME is returned in the answer, then
+            // the QNAME of the RRs in the authority section will refer to the
+            // authority for the CNAME's RDATA and not to the original question.
+            //
+            // Without doing further classification, it is sufficient to say
+            // that if an NXDOMAIN is received, there was no translation of the
+            // QNAME available.
+            return (NXDOMAIN);  // Received NXDOMAIN from parent.
+
+        } else {
+
+            // Not NXDOMAIN but not NOERROR either.  Must be an RCODE-related
+            // error.
+            return (RCODE);
+        }
+    }
+
+    // All seems OK and we can start looking at the content.  However, one
+    // more header check remains - was the response truncated?  If so, we'll
+    // probably want to re-query over TCP.  However, in some circumstances we
+    // might want to go with what we have.  So give the caller the option of
+    // ignoring the TC bit.
+    if (message->getHeaderFlag(Message::HEADERFLAG_TC) && (!tcignore)) {
+        return (TRUNCATED);
+    }
+
+    // By the time we get here, we're assured that the packet format is correct.
+    // We now need to decide as to whether it is an answer, a CNAME, or a
+    // referral.  For this, we need to inspect the contents of the answer
+    // and authority sections.
+    const vector<RRsetPtr> answer(
+            message->beginSection(Message::SECTION_ANSWER),
+            message->endSection(Message::SECTION_ANSWER)
+            );
+    const vector<RRsetPtr> authority(
+            message->beginSection(Message::SECTION_AUTHORITY),
+            message->endSection(Message::SECTION_AUTHORITY)
+            );
+
+    // If there is nothing in the answer section, it is a referral - unless
+    // there is nothing in the authority section
+    if (answer.empty()) {
+        if (authority.empty()) {
+            return (EMPTY);
+        } else {
+            return (REFERRAL);
+        }
+    }
+
+    // Look at two cases - one RRset in the answer and multiple RRsets in
+    // the answer.
+    if (answer.size() == 1) {
+
+        // Does the name and class of the answer match that of the question?
+        if ((answer[0]->getName() == question.getName()) &&
+            (answer[0]->getClass() == question.getClass())) {
+
+            // It does.  How about the type of the response?  The response
+            // is an answer if the type matches that of the question, or if the
+            // question was for type ANY.  It is a CNAME reply if the answer
+            // type is CNAME.  And it is an error for anything else.
+            if ((answer[0]->getType() == question.getType()) ||
+                (question.getType() == RRType::ANY())) {
+                return (ANSWER);
+            } else if (answer[0]->getType() == RRType::CNAME()) {
+                return (CNAME);
+            } else {
+                return (INVTYPE);
+            }
+        }
+        else {
+
+            // Either the name and/or class of the reply don't match that of
+            // the question.
+            return (INVNAMCLASS);
+        }
+    }
+
+    // There are multiple RRsets in the answer. They should all have the same
+    // QCLASS, else there is some error in the response.
+    for (int i = 1; i < answer.size(); ++i) {
+        if (answer[0]->getClass() != answer[i]->getClass()) {
+            return (MULTICLASS);
+        }
+    }
+
+    // If the request type was ANY and they all have the same QNAME, we have
+    // an answer.  But if they don't have the same QNAME, we must have an error;
+    // the only way we could get different QNAMES in an answer is if one were a
+    // CNAME - in which case there should no other record types at that QNAME.
+    if (question.getType() == RRType::ANY()) {
+        bool all_same = true;
+        for (int i = 1; (i < answer.size()) && all_same; ++i) {
+            all_same = (answer[0]->getName() == answer[i]->getName());
+        }
+        if (all_same) {
+            return (ANSWER);
+        } else {
+            return (EXTRADATA);
+        }
+    }
+
+    // Multiple RRs in the answer, and not all the same QNAME.  This
+    // is either an answer, a CNAME (in either case, there could be multiple
+    // CNAMEs in the chain) or an error.
+    //
+    // So we need to follow the CNAME chain to resolve this.  For this to work:
+    //
+    // a) There must be one RR that matches the name, class and type of
+    //    the question, and this is a CNAME.
+    // b) The CNAME chain is followed until the end of the chain does not
+    //    exist (answer is a CNAME) or it is not of type CNAME (ANSWER).
+    //
+    // In the latter case, if there are additional RRs, it must be an error.
+
+    vector<RRsetPtr> ansrrset(answer);
+    vector<int> present(ansrrset.size(), 1);
+    return cnameChase(question.getName(), question.getType(), ansrrset, present,
+        ansrrset.size());
+}
+
+// Search the CNAME chain.
+ResponseClassifier::Category ResponseClassifier::cnameChase(
+    const Name& qname, const RRType& qtype, vector<RRsetPtr>& ansrrset,
+    vector<int>& present, size_t size)
+{
+    // Search through the vector of RRset pointers until we find one with the
+    // right QNAME.
+    for (int i = 0; i < ansrrset.size(); ++i) {
+        if (present[i]) {
+
+            // This entry has not been logically removed, so look at it.
+            if (ansrrset[i]->getName() == qname) {
+
+                // QNAME match.  If this RRset is a CNAME, remove it from
+                // further consideration.  If nothing is left, the end of the
+                // chain is a CNAME so this is a CNAME.  Otherwise replace
+                // the name with the RDATA of the CNAME and call ourself
+                // recursively.
+                if (ansrrset[i]->getType() == RRType::CNAME()) {
+
+                    // Don't consider it in the next iteration (although we
+                    // can still access it for now).
+                    present[i] = 0;
+                    --size;
+                    if (size == 0) {
+                        return (CNAME);
+                    }
+                    else {
+                        if (ansrrset[i]->getRdataCount() != 1) {
+
+                            // Multiple RDATA for a CNAME?  This is invalid.
+
+                            return (NOTSINGLE);
+                        }
+                        RdataIteratorPtr it = ansrrset[i]->getRdataIterator();
+                        Name newname(it->getCurrent().toText());
+
+                        return cnameChase(newname, qtype, ansrrset, present,
+                            size);
+                    }
+
+                } else {
+
+                    // We've got here because the element is not a CNAME.  If
+                    // this is the last element and the type is the one we are
+                    // after, we've found the answer, or it is an error.  If
+                    // there is more than one RRset left in the list we are
+                    // searching, we have extra data in the answer.
+                    if (ansrrset[i]->getType() == qtype) {
+                        if (size == 1) {
+                            return (ANSWERCNAME);
+                        } else {
+                            return (EXTRADATA);
+                        }
+                    }
+                    return (INVTYPE);
+                }
+            }
+        }
+    }
+
+    // We get here if we've dropped off the end of the list without finding the
+    // QNAME we are looking for.  This means that the CNAME chain has ended
+    // but there are additional RRsets in the data.
+
+    return (EXTRADATA);
+}

+ 138 - 0
src/bin/resolver/response_classifier.h

@@ -0,0 +1,138 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+// $Id$
+
+#ifndef __RESPONSE_CLASSIFIER_H
+#define __RESPONSE_CLASSIFIER_H
+
+#include <cstddef>
+
+#include <dns/question.h>
+#include <dns/message.h>
+#include <dns/question.h>
+
+/// \brief Classify Server Response
+///
+/// This class is used in the recursive server.  It is passed an answer received
+/// from an upstream server and categorises it.
+///
+/// TODO: It is unlikely that the code can be used in this form.  Some adaption
+/// of it will be required to put it in the server.
+///
+/// TODO: The code here does not take into account any EDNS0 fields.
+
+class ResponseClassifier {
+public:
+
+    /// \brief Category of Answer
+    ///
+    /// In the valid answers, not the distinction between REFERRAL and CNAME.
+    /// A REFERRAL answer means that the answer section of the message is
+    /// empty, but there is something in the authority section.  A CNAME means
+    /// that the answer section contains one or more CNAMES in a chain that
+    /// do not end with a non-CNAME RRset.
+    enum Category {
+
+        // Codes indicating that a message is valid.
+
+        ANSWER,             ///< Response contains the answer
+        ANSWERCNAME,        ///< Response was a CNAME chain ending in an answer
+        CNAME,              ///< Response was a CNAME
+        NXDOMAIN,           ///< Response was an NXDOMAIN
+        REFERRAL,           ///< Response contains a referral
+
+        // Codes indicating that a message is invalid.  Note that the error()
+        // method relies on these appearing after the "message valid" codes.
+
+        EMPTY,              ///< No answer or authority sections
+        EXTRADATA,          ///< Answer section contains more RRsets than needed
+        INVNAMCLASS,        ///< Invalid name or class in answer
+        INVTYPE,            ///< Name/class of answer correct, type is wrong
+        MISMATQUEST,        ///< Response question section != question
+        MULTICLASS,         ///< Multiple classes in multi-RR answer
+        NOTONEQUEST,        ///< Not one question in response question section
+        NOTRESPONSE,        ///< Response has the Query/Response bit clear
+        NOTSINGLE,          ///< CNAME has multiple RDATA elements.
+        OPCODE,             ///< Opcode field does not indicate a query
+        RCODE,              ///< RCODE indicated an error
+        TRUNCATED           ///< Response was truncated
+    };
+
+    /// \brief Check Error
+    ///
+    /// An inline routine to quickly classify whether the return category is
+    /// an error or not.  This makes use of internal knowledge of the order of
+    /// codes in the Category enum.
+    ///
+    /// \param code Return category from classify()
+    ///
+    /// \return true if the category is an error, false if not.
+    static bool error(Category code) {
+        return (code > REFERRAL);
+    }
+
+    /// \brief Classify
+    ///
+    /// Classify the response in the "message" object.
+    ///
+    /// \param question Question that was sent to the server
+    /// \param message Pointer to the associated response from the server.
+    /// \param tcignore If set, the TC bit in a response packet is
+    /// ignored.  Otherwise the error code TRUNCATED will be returned.  The
+    /// only time this is likely to be used is in development where we are not
+    /// going to fail over to TCP and will want to use what is returned, even
+    /// if some of the response was lost.
+    static Category classify(const isc::dns::Question& question,
+            const isc::dns::MessagePtr& message, bool tcignore = false);
+
+private:
+    /// \brief Follow CNAMEs
+    ///
+    /// Given a QNAME and an answer section that contains CNAMEs, assume that
+    /// they form a CNAME chain and search through them.  Possible outcomes
+    /// are:
+    ///
+    /// a) All CNAMES and they form a chain.  The result is a referral.
+    /// b) All but one are CNAMES and they form a chain.  The other is pointed
+    ///    to by the last element of the chain and is the correct QTYPE.  The
+    ///    result is an answer.
+    /// c) Having followed the CNAME chain as far as we can, there is one
+    ///    remaining RRset that is of the wrong type, or there are multiple
+    ///    RRsets remaining.  return the EXTRADATA code.
+    ///
+    /// \param qname Question name we are searching for
+    /// \param qtype Question type we are search for.  (This is assumed not
+    /// to be "ANY".)
+    /// \param ansrrset Vector of RRsetPtr pointing to the RRsets we are
+    /// considering.
+    /// \param present Array of "int" the same size of ansrrset, with each
+    /// element set to "1" to allow the corresponding element of ansrrset to
+    /// be checked, and "0" to skip it.  This might be premature optimisation,
+    /// but the algorithm would otherwise involve duplicating the RRset
+    /// vector then removing elements from random positions one by one.  As
+    /// each removal involves the destruction of an "xxxPtr" element (which
+    /// presently is implemented by boost::shared_ptr), the overhad of memory
+    /// management seemed high.  This solution imposes some additional loop
+    /// cycles, but that should be minimal compared with the overhead of the
+    /// memory management.
+    /// \param size Number of elements to check.  See description of \c present
+    /// for details.
+    static Category cnameChase(const isc::dns::Name& qname,
+        const isc::dns::RRType& qtype,
+        std::vector<isc::dns::RRsetPtr>& ansrrset, std::vector<int>& present,
+        size_t size);
+};
+
+#endif // __RESPONSE_CLASSIFIER_H

+ 2 - 0
src/bin/resolver/tests/Makefile.am

@@ -20,8 +20,10 @@ TESTS += run_unittests
 run_unittests_SOURCES = $(top_srcdir)/src/lib/dns/tests/unittest_util.h
 run_unittests_SOURCES += $(top_srcdir)/src/lib/dns/tests/unittest_util.cc
 run_unittests_SOURCES += ../resolver.h ../resolver.cc
+run_unittests_SOURCES += ../response_classifier.h ../response_classifier.cc
 run_unittests_SOURCES += resolver_unittest.cc
 run_unittests_SOURCES += resolver_config_unittest.cc
+run_unittests_SOURCES += response_classifier_unittest.cc
 run_unittests_SOURCES += run_unittests.cc
 run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
 run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)

+ 31 - 6
src/bin/resolver/tests/resolver_config_unittest.cc

@@ -96,6 +96,31 @@ TEST_F(ResolverConfig, forwardAddressConfig) {
     EXPECT_EQ(0, server.getForwardAddresses().size());
 }
 
+TEST_F(ResolverConfig, rootAddressConfig) {
+    // Try putting there some address
+    ElementPtr config(Element::fromJSON("{"
+        "\"root_addresses\": ["
+        "   {"
+        "       \"address\": \"192.0.2.1\","
+        "       \"port\": 53"
+        "   }"
+        "]"
+        "}"));
+    ConstElementPtr result(server.updateConfig(config));
+    EXPECT_EQ(result->toWire(), isc::config::createAnswer()->toWire());
+    ASSERT_EQ(1, server.getRootAddresses().size());
+    EXPECT_EQ("192.0.2.1", server.getRootAddresses()[0].first);
+    EXPECT_EQ(53, server.getRootAddresses()[0].second);
+
+    // And then remove all addresses
+    config = Element::fromJSON("{"
+        "\"root_addresses\": null"
+        "}");
+    result = server.updateConfig(config);
+    EXPECT_EQ(result->toWire(), isc::config::createAnswer()->toWire());
+    EXPECT_EQ(0, server.getRootAddresses().size());
+}
+
 void
 ResolverConfig::invalidTest(const string &JOSN) {
     ElementPtr config(Element::fromJSON(JOSN));
@@ -134,8 +159,8 @@ TEST_F(ResolverConfig, listenAddresses) {
 
     // 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));
+    addresses.push_back(pair<string, uint16_t>("127.0.0.1", 5321));
+    addresses.push_back(pair<string, uint16_t>("::1", 5321));
     server.setListenAddresses(addresses);
     EXPECT_EQ(2, server.getListenAddresses().size());
     EXPECT_EQ("::1", server.getListenAddresses()[1].first);
@@ -155,7 +180,7 @@ TEST_F(ResolverConfig, DISABLED_listenAddressConfig) {
         "\"listen_on\": ["
         "   {"
         "       \"address\": \"127.0.0.1\","
-        "       \"port\": 5300"
+        "       \"port\": 5321"
         "   }"
         "]"
         "}"));
@@ -163,7 +188,7 @@ TEST_F(ResolverConfig, DISABLED_listenAddressConfig) {
     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);
+    EXPECT_EQ(5321, server.getListenAddresses()[0].second);
 
     // As this is example address, the machine should not have it on
     // any interface
@@ -174,7 +199,7 @@ TEST_F(ResolverConfig, DISABLED_listenAddressConfig) {
         "\"listen_on\": ["
         "   {"
         "       \"address\": \"192.0.2.0\","
-        "       \"port\": 5300"
+        "       \"port\": 5321"
         "   }"
         "]"
         "}");
@@ -182,7 +207,7 @@ TEST_F(ResolverConfig, DISABLED_listenAddressConfig) {
     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);
+    EXPECT_EQ(5321, server.getListenAddresses()[0].second);
 }
 
 TEST_F(ResolverConfig, invalidListenAddresses) {

+ 15 - 3
src/bin/resolver/tests/resolver_unittest.cc

@@ -16,6 +16,7 @@
 
 #include <resolver/resolver.h>
 #include <dns/tests/unittest_util.h>
+#include <testutils/dnsmessage_test.h>
 #include <testutils/srv_test.h>
 
 using namespace isc::dns;
@@ -29,7 +30,10 @@ class ResolverTest : public SrvTestBase{
 protected:
     ResolverTest() : server(){}
     virtual void processMessage() {
-        server.processMessage(*io_message, parse_message, response_obuffer,
+        server.processMessage(*io_message,
+                              parse_message,
+                              response_message,
+                              response_obuffer,
                               &dnsserv);
     }
     Resolver server;
@@ -82,7 +86,11 @@ TEST_F(ResolverTest, AXFRFail) {
                                        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);
+    server.processMessage(*io_message,
+                          parse_message,
+                          response_message,
+                          response_obuffer,
+                          &dnsserv);
     EXPECT_TRUE(dnsserv.hasAnswer());
     headerCheck(*parse_message, default_qid, Rcode::NOTIMP(), opcode.getCode(),
                 QR_FLAG, 1, 0, 0, 0);
@@ -97,7 +105,11 @@ TEST_F(ResolverTest, notifyFail) {
     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);
+    server.processMessage(*io_message,
+                          parse_message,
+                          response_message,
+                          response_obuffer,
+                          &dnsserv);
     EXPECT_TRUE(dnsserv.hasAnswer());
     headerCheck(*parse_message, default_qid, Rcode::NOTAUTH(),
                 Opcode::NOTIFY().getCode(), QR_FLAG, 0, 0, 0, 0);

+ 494 - 0
src/bin/resolver/tests/response_classifier_unittest.cc

@@ -0,0 +1,494 @@
+// 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 <iostream>
+#include <gtest/gtest.h>
+
+#include <dns/tests/unittest_util.h>
+
+#include <resolver/response_classifier.h>
+
+#include <dns/name.h>
+#include <dns/opcode.h>
+#include <dns/question.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+#include <dns/rcode.h>
+#include <dns/rrclass.h>
+#include <dns/rrset.h>
+#include <dns/rrtype.h>
+#include <dns/rrttl.h>
+
+using namespace std;
+using namespace isc::dns;
+using namespace rdata;
+using namespace isc::dns::rdata::generic;
+using namespace isc::dns::rdata::in;
+
+namespace {
+class ResponseClassifierTest : public ::testing::Test {
+public:
+    /// \brief Constructor
+    ///
+    /// The naming convention is:
+    ///
+    /// <category>_<class>_<type>_<name>
+    ///
+    /// <category> is "qu" (question), "rrs" (rrset),
+    /// <qclass> is self-explanatory
+    /// <qtype> is self-explanatory
+    /// <name> is the first part of the domain name (all expected to be in
+    /// example.com)
+    ///
+    /// Message variables
+    ///
+    /// msg_<qtype>  Where <qtype> is the type of query.  These are only used
+    /// in the early tests where simple messages are required.
+
+    ResponseClassifierTest() :
+        msg_a(new Message(Message::RENDER)),
+        msg_any(new Message(Message::RENDER)),
+        qu_ch_a_www(Name("www.example.com"), RRClass::CH(), RRType::A()),
+        qu_in_any_www(Name("www.example.com"), RRClass::IN(), RRType::ANY()),
+        qu_in_a_www2(Name("www2.example.com"), RRClass::IN(), RRType::A()),
+        qu_in_a_www(Name("www.example.com"), RRClass::IN(), RRType::A()),
+        qu_in_cname_www1(Name("www1.example.com"), RRClass::IN(), RRType::A()),
+        qu_in_ns_(Name("example.com"), RRClass::IN(), RRType::NS()),
+        qu_in_txt_www(Name("www.example.com"), RRClass::IN(), RRType::TXT()),
+        rrs_hs_txt_www(new RRset(Name("www.example.com"), RRClass::HS(),
+            RRType::TXT(), RRTTL(300))),
+        rrs_in_a_mail(new RRset(Name("mail.example.com"), RRClass::IN(),
+            RRType::A(), RRTTL(300))),
+        rrs_in_a_www(new RRset(Name("www.example.com"), RRClass::IN(),
+            RRType::A(), RRTTL(300))),
+        rrs_in_cname_www1(new RRset(Name("www1.example.com"), RRClass::IN(),
+            RRType::CNAME(), RRTTL(300))),
+        rrs_in_cname_www2(new RRset(Name("www2.example.com"), RRClass::IN(),
+            RRType::CNAME(), RRTTL(300))),
+        rrs_in_ns_(new RRset(Name("example.com"), RRClass::IN(),
+            RRType::NS(), RRTTL(300))),
+        rrs_in_txt_www(new RRset(Name("www.example.com"), RRClass::IN(),
+            RRType::TXT(), RRTTL(300)))
+    {
+        // Set up the message to indicate a successful response to the question
+        // "www.example.com A", but don't add in any response sections.
+        msg_a->setHeaderFlag(Message::HEADERFLAG_QR);
+        msg_a->setOpcode(Opcode::QUERY());
+        msg_a->setRcode(Rcode::NOERROR());
+        msg_a->addQuestion(qu_in_a_www);
+
+        // ditto for the query "www.example.com ANY"
+        msg_any->setHeaderFlag(Message::HEADERFLAG_QR);
+        msg_any->setOpcode(Opcode::QUERY());
+        msg_any->setRcode(Rcode::NOERROR());
+        msg_any->addQuestion(qu_in_any_www);
+
+        // The next set of assignments set up the following zone records
+        //
+        // example.com           NS     ns0.isc.org
+        //                       NS     ns0.example.org
+        //
+        // www.example.com       A      1.2.3.4
+        //                       TXT    "An example text string"
+        //
+        // mail.example.com      A      4.5.6.7
+        //
+        // www1.example.com      CNAME  www.example.com
+        //
+        // www2.example.com      CNAME  www1.example.com
+
+        // Set up an imaginary NS RRset for an authority section
+        rrs_in_ns_->addRdata(ConstRdataPtr(new NS(Name("ns0.isc.org"))));
+        rrs_in_ns_->addRdata(ConstRdataPtr(new NS(Name("ns0.example.org"))));
+
+        // Set up the records for the www host
+        rrs_in_a_www->addRdata(ConstRdataPtr(new A("1.2.3.4")));
+        rrs_in_txt_www->addRdata(ConstRdataPtr(
+            new TXT("An example text string")));
+
+        // ... for the mail host
+        rrs_in_a_mail->addRdata(ConstRdataPtr(new A("5.6.7.8")));
+
+        // ... the CNAME records
+        rrs_in_cname_www1->addRdata(ConstRdataPtr(
+            new CNAME("www.example.com")));
+        rrs_in_cname_www2->addRdata(ConstRdataPtr(
+            new CNAME("www1.example.com")));
+    }
+
+    MessagePtr  msg_a;              // Pointer to message in RENDER state
+    MessagePtr  msg_any;            // Pointer to message in RENDER state
+    Question    qu_ch_a_www;        // www.example.com CH A
+    Question    qu_in_any_www;      // www.example.com IN ANY
+    Question    qu_in_a_www2;       // www.example.com IN ANY
+    Question    qu_in_a_www;        // www.example.com IN A
+    Question    qu_in_cname_www1;   // www1.example.com IN CNAME
+    Question    qu_in_ns_;          // example.com IN NS
+    Question    qu_in_txt_www;      // www.example.com IN TXT
+    RRsetPtr    rrs_hs_txt_www;     // www.example.com HS TXT
+    RRsetPtr    rrs_in_a_mail;      // mail.example.com IN A
+    RRsetPtr    rrs_in_a_www;       // www.example.com IN A
+    RRsetPtr    rrs_in_cname_www1;  // www1.example.com IN CNAME
+    RRsetPtr    rrs_in_cname_www2;  // www2.example.com IN CNAME
+    RRsetPtr    rrs_in_ns_;         // example.com IN NS
+    RRsetPtr    rrs_in_txt_www;     // www.example.com IN TXT
+};
+
+// Test that the error() function categorises the codes correctly.
+
+TEST_F(ResponseClassifierTest, StatusCodes) {
+    EXPECT_FALSE(ResponseClassifier::error(ResponseClassifier::ANSWER));
+    EXPECT_FALSE(ResponseClassifier::error(ResponseClassifier::ANSWERCNAME));
+    EXPECT_FALSE(ResponseClassifier::error(ResponseClassifier::CNAME));
+    EXPECT_FALSE(ResponseClassifier::error(ResponseClassifier::NXDOMAIN));
+    EXPECT_FALSE(ResponseClassifier::error(ResponseClassifier::REFERRAL));
+
+    EXPECT_TRUE(ResponseClassifier::error(ResponseClassifier::EMPTY));
+    EXPECT_TRUE(ResponseClassifier::error(ResponseClassifier::EXTRADATA));
+    EXPECT_TRUE(ResponseClassifier::error(ResponseClassifier::INVNAMCLASS));
+    EXPECT_TRUE(ResponseClassifier::error(ResponseClassifier::INVTYPE));
+    EXPECT_TRUE(ResponseClassifier::error(ResponseClassifier::MISMATQUEST));
+    EXPECT_TRUE(ResponseClassifier::error(ResponseClassifier::MULTICLASS));
+    EXPECT_TRUE(ResponseClassifier::error(ResponseClassifier::NOTONEQUEST));
+    EXPECT_TRUE(ResponseClassifier::error(ResponseClassifier::NOTRESPONSE));
+    EXPECT_TRUE(ResponseClassifier::error(ResponseClassifier::NOTSINGLE));
+    EXPECT_TRUE(ResponseClassifier::error(ResponseClassifier::OPCODE));
+    EXPECT_TRUE(ResponseClassifier::error(ResponseClassifier::RCODE));
+    EXPECT_TRUE(ResponseClassifier::error(ResponseClassifier::TRUNCATED));
+}
+
+// Test that the system will reject a message which is a query.
+
+TEST_F(ResponseClassifierTest, Query) {
+
+    // Set up message to indicate a query (QR flag = 0, one question).  By
+    // default the opcode will be 0 (query)
+    msg_a->setHeaderFlag(Message::HEADERFLAG_QR, false);
+
+    // Should be rejected as it is a query, not a response
+    EXPECT_EQ(ResponseClassifier::NOTRESPONSE,
+        ResponseClassifier::classify(qu_in_a_www, msg_a));
+}
+
+// Check that we get an OPCODE error on all but QUERY opcodes.
+
+TEST_F(ResponseClassifierTest, Opcode) {
+
+    uint8_t query = static_cast<uint8_t>(Opcode::QUERY().getCode());
+
+    for (uint8_t i = 0; i < (1 << 4); ++i) {
+        msg_a->setOpcode(Opcode(i));
+        if (i == query) {
+            EXPECT_NE(ResponseClassifier::OPCODE,
+                ResponseClassifier::classify(qu_in_a_www, msg_a));
+        } else {
+            EXPECT_EQ(ResponseClassifier::OPCODE,
+                ResponseClassifier::classify(qu_in_a_www, msg_a));
+        }
+    }
+}
+
+// Test that the system will reject a response with anything other than one
+// question.
+
+TEST_F(ResponseClassifierTest, MultipleQuestions) {
+
+    // Create a message object for this test that has no question section.
+    MessagePtr message(new Message(Message::RENDER));
+    message->setHeaderFlag(Message::HEADERFLAG_QR);
+    message->setOpcode(Opcode::QUERY());
+    message->setRcode(Rcode::NOERROR());
+
+    // Zero questions
+    EXPECT_EQ(ResponseClassifier::NOTONEQUEST,
+        ResponseClassifier::classify(qu_in_a_www, message));
+
+    // One question
+    message->addQuestion(qu_in_a_www);
+    EXPECT_NE(ResponseClassifier::NOTONEQUEST,
+        ResponseClassifier::classify(qu_in_a_www, message));
+
+    // Two questions
+    message->addQuestion(qu_in_ns_);
+    EXPECT_EQ(ResponseClassifier::NOTONEQUEST,
+        ResponseClassifier::classify(qu_in_a_www, message));
+
+    // And finish the check with three questions
+    message->addQuestion(qu_in_txt_www);
+    EXPECT_EQ(ResponseClassifier::NOTONEQUEST,
+        ResponseClassifier::classify(qu_in_a_www, message));
+}
+
+// Test that the question in the question section in the message response
+// is equal to the question supplied.
+
+TEST_F(ResponseClassifierTest, SameQuestion) {
+
+    EXPECT_EQ(ResponseClassifier::MISMATQUEST,
+        ResponseClassifier::classify(qu_in_ns_, msg_a));
+    EXPECT_NE(ResponseClassifier::MISMATQUEST,
+        ResponseClassifier::classify(qu_in_a_www, msg_a));
+}
+
+// Should get an NXDOMAIN response only on an NXDOMAIN RCODE.
+
+TEST_F(ResponseClassifierTest, NXDOMAIN) {
+
+    uint16_t nxdomain = static_cast<uint16_t>(Rcode::NXDOMAIN().getCode());
+
+    for (uint8_t i = 0; i < (1 << 4); ++i) {
+        msg_a->setRcode(Rcode(i));
+        if (i == nxdomain) {
+            EXPECT_EQ(ResponseClassifier::NXDOMAIN,
+                ResponseClassifier::classify(qu_in_a_www, msg_a));
+        } else {
+            EXPECT_NE(ResponseClassifier::NXDOMAIN,
+                ResponseClassifier::classify(qu_in_a_www, msg_a));
+        }
+    }
+}
+
+// Check that we get an RCODE error on all but NXDOMAIN and NOERROR responses.
+
+TEST_F(ResponseClassifierTest, RCODE) {
+
+    uint16_t nxdomain = static_cast<uint16_t>(Rcode::NXDOMAIN().getCode());
+    uint16_t noerror = static_cast<uint16_t>(Rcode::NOERROR().getCode());
+
+    for (uint8_t i = 0; i < (1 << 4); ++i) {
+        msg_a->setRcode(Rcode(i));
+        if ((i == nxdomain) || (i == noerror)) {
+            EXPECT_NE(ResponseClassifier::RCODE,
+                ResponseClassifier::classify(qu_in_a_www, msg_a));
+        } else {
+            EXPECT_EQ(ResponseClassifier::RCODE,
+                ResponseClassifier::classify(qu_in_a_www, msg_a));
+        }
+    }
+}
+
+// Test that the code will detect a truncated message.  Even if nothing else
+// is wrong, we'll want to retry the query if we receive a truncated code.
+// However, we give the option to the user of the code aws to whether they
+// want to take into account the truncated bit.
+
+TEST_F(ResponseClassifierTest, Truncated) {
+
+    // Don't expect the truncated code whatever option we ask for if the TC
+    // bit is not set.
+    msg_a->setHeaderFlag(Message::HEADERFLAG_TC, false);
+    EXPECT_NE(ResponseClassifier::TRUNCATED,
+        ResponseClassifier::classify(qu_in_a_www, msg_a, true));
+    EXPECT_NE(ResponseClassifier::TRUNCATED,
+        ResponseClassifier::classify(qu_in_a_www, msg_a, false));
+
+    // Expect the truncated code if the TC bit is set, only if we don't ignore
+    // it.
+    msg_a->setHeaderFlag(Message::HEADERFLAG_TC, true);
+    EXPECT_NE(ResponseClassifier::TRUNCATED,
+        ResponseClassifier::classify(qu_in_a_www, msg_a, true));
+    EXPECT_EQ(ResponseClassifier::TRUNCATED,
+        ResponseClassifier::classify(qu_in_a_www, msg_a, false));
+}
+
+// Check for an empty packet (i.e. no error, but with the answer and additional
+// sections empty).
+
+TEST_F(ResponseClassifierTest, Empty) {
+
+    EXPECT_EQ(ResponseClassifier::EMPTY,
+        ResponseClassifier::classify(qu_in_a_www, msg_a));
+}
+
+// Anything where we have an empty answer section but something in the
+// authority section is a referral (if the status is NOERROR).
+
+TEST_F(ResponseClassifierTest, EmptyAnswerReferral) {
+
+    msg_a->addRRset(Message::SECTION_AUTHORITY, rrs_in_ns_);
+    EXPECT_EQ(ResponseClassifier::REFERRAL,
+        ResponseClassifier::classify(qu_in_a_www, msg_a));
+
+}
+
+// Check the case where we have a simple answer in the answer section.  This
+// occurs when the QNAME/QTYPE/QCLASS matches one of the RRsets in the
+// answer section - expect when the QTYPE is ANY, in which case the match
+// must be on the QNAME/QCLASS alone.
+
+TEST_F(ResponseClassifierTest, SingleAnswer) {
+
+    // Check a question that matches the answer
+    msg_a->addRRset(Message::SECTION_ANSWER, rrs_in_a_www);
+    EXPECT_EQ(ResponseClassifier::ANSWER,
+        ResponseClassifier::classify(qu_in_a_www, msg_a));
+
+    // Check an ANY question that matches the answer
+    msg_any->addRRset(Message::SECTION_ANSWER, rrs_in_a_www);
+    EXPECT_EQ(ResponseClassifier::ANSWER,
+        ResponseClassifier::classify(qu_in_any_www, msg_any));
+
+    // Check a CNAME response that matches the QNAME.
+    MessagePtr message_a(new Message(Message::RENDER));
+    message_a->setHeaderFlag(Message::HEADERFLAG_QR);
+    message_a->setOpcode(Opcode::QUERY());
+    message_a->setRcode(Rcode::NOERROR());
+    message_a->addQuestion(qu_in_cname_www1);
+    message_a->addRRset(Message::SECTION_ANSWER, rrs_in_cname_www1);
+    EXPECT_EQ(ResponseClassifier::CNAME,
+        ResponseClassifier::classify(qu_in_cname_www1, message_a));
+
+    // Check if the answer QNAME does not match the question
+    // Q: www.example.com  IN A
+    // A: mail.example.com IN A
+    MessagePtr message_b(new Message(Message::RENDER));
+    message_b->setHeaderFlag(Message::HEADERFLAG_QR);
+    message_b->setOpcode(Opcode::QUERY());
+    message_b->setRcode(Rcode::NOERROR());
+    message_b->addQuestion(qu_in_a_www);
+    message_b->addRRset(Message::SECTION_ANSWER, rrs_in_a_mail);
+    EXPECT_EQ(ResponseClassifier::INVNAMCLASS,
+        ResponseClassifier::classify(qu_in_a_www, message_b));
+
+    // Check if the answer class does not match the question
+    // Q: www.example.com CH A
+    // A: www.example.com IN A
+    MessagePtr message_c(new Message(Message::RENDER));
+    message_c->setHeaderFlag(Message::HEADERFLAG_QR);
+    message_c->setOpcode(Opcode::QUERY());
+    message_c->setRcode(Rcode::NOERROR());
+    message_c->addQuestion(qu_ch_a_www);
+    message_c->addRRset(Message::SECTION_ANSWER, rrs_in_a_www);
+    EXPECT_EQ(ResponseClassifier::INVNAMCLASS,
+        ResponseClassifier::classify(qu_ch_a_www, message_c));
+
+    // Check if the answer type does not match the question
+    // Q: www.example.com IN A
+    // A: www.example.com IN TXT
+    MessagePtr message_d(new Message(Message::RENDER));
+    message_d->setHeaderFlag(Message::HEADERFLAG_QR);
+    message_d->setOpcode(Opcode::QUERY());
+    message_d->setRcode(Rcode::NOERROR());
+    message_d->addQuestion(qu_in_a_www);
+    message_d->addRRset(Message::SECTION_ANSWER, rrs_in_txt_www);
+    EXPECT_EQ(ResponseClassifier::INVTYPE,
+        ResponseClassifier::classify(qu_in_a_www, message_d));
+}
+
+// Check what happens if we have multiple RRsets in the answer.
+
+TEST_F(ResponseClassifierTest, MultipleAnswerRRsets) {
+
+    // All the same QNAME but different types is only valid on an ANY query.
+    MessagePtr message_a(new Message(Message::RENDER));
+    message_a->setHeaderFlag(Message::HEADERFLAG_QR);
+    message_a->setOpcode(Opcode::QUERY());
+    message_a->setRcode(Rcode::NOERROR());
+    message_a->addQuestion(qu_in_any_www);
+    message_a->addRRset(Message::SECTION_ANSWER, rrs_in_a_www);
+    message_a->addRRset(Message::SECTION_ANSWER, rrs_in_txt_www);
+    EXPECT_EQ(ResponseClassifier::ANSWER,
+        ResponseClassifier::classify(qu_in_any_www, message_a));
+
+    // On another type of query, it results in an EXTRADATA error
+    MessagePtr message_b(new Message(Message::RENDER));
+    message_b->setHeaderFlag(Message::HEADERFLAG_QR);
+    message_b->setOpcode(Opcode::QUERY());
+    message_b->setRcode(Rcode::NOERROR());
+    message_b->addQuestion(qu_in_a_www);
+    message_b->addRRset(Message::SECTION_ANSWER, rrs_in_a_www);
+    message_b->addRRset(Message::SECTION_ANSWER, rrs_in_txt_www);
+    EXPECT_EQ(ResponseClassifier::EXTRADATA,
+        ResponseClassifier::classify(qu_in_a_www, message_b));
+
+    // Same QNAME on an ANY query is not valid with mixed classes
+    MessagePtr message_c(new Message(Message::RENDER));
+    message_c->setHeaderFlag(Message::HEADERFLAG_QR);
+    message_c->setOpcode(Opcode::QUERY());
+    message_c->setRcode(Rcode::NOERROR());
+    message_c->addQuestion(qu_in_any_www);
+    message_c->addRRset(Message::SECTION_ANSWER, rrs_in_a_www);
+    message_c->addRRset(Message::SECTION_ANSWER, rrs_hs_txt_www);
+    EXPECT_EQ(ResponseClassifier::MULTICLASS,
+        ResponseClassifier::classify(qu_in_any_www, message_c));
+
+    // Mixed QNAME is not valid unless QNAME requested is a CNAME.
+    MessagePtr message_d(new Message(Message::RENDER));
+    message_d->setHeaderFlag(Message::HEADERFLAG_QR);
+    message_d->setOpcode(Opcode::QUERY());
+    message_d->setRcode(Rcode::NOERROR());
+    message_d->addQuestion(qu_in_a_www);
+    message_d->addRRset(Message::SECTION_ANSWER, rrs_in_a_www);
+    message_d->addRRset(Message::SECTION_ANSWER, rrs_in_a_mail);
+    EXPECT_EQ(ResponseClassifier::EXTRADATA,
+        ResponseClassifier::classify(qu_in_a_www, message_d));
+
+    // Mixed QNAME is not valid when the query is an ANY.
+    MessagePtr message_e(new Message(Message::RENDER));
+    message_e->setHeaderFlag(Message::HEADERFLAG_QR);
+    message_e->setOpcode(Opcode::QUERY());
+    message_e->setRcode(Rcode::NOERROR());
+    message_e->addQuestion(qu_in_any_www);
+    message_e->addRRset(Message::SECTION_ANSWER, rrs_in_a_www);
+    message_e->addRRset(Message::SECTION_ANSWER, rrs_in_txt_www);
+    message_e->addRRset(Message::SECTION_ANSWER, rrs_in_a_mail);
+    EXPECT_EQ(ResponseClassifier::EXTRADATA,
+        ResponseClassifier::classify(qu_in_any_www, message_e));
+}
+
+// CNAME chain is CNAME if it terminates in a CNAME, answer if it
+// does not, and error if there are RRs left over.
+TEST_F(ResponseClassifierTest, CNAMEChain) {
+
+    // Answer contains a single CNAME
+    MessagePtr message_a(new Message(Message::RENDER));
+    message_a->setHeaderFlag(Message::HEADERFLAG_QR);
+    message_a->setOpcode(Opcode::QUERY());
+    message_a->setRcode(Rcode::NOERROR());
+    message_a->addQuestion(qu_in_a_www2);
+    message_a->addRRset(Message::SECTION_ANSWER, rrs_in_cname_www2);
+    EXPECT_EQ(ResponseClassifier::CNAME,
+        ResponseClassifier::classify(qu_in_a_www2, message_a));
+
+    // Add a CNAME for www1, and it should still return a CNAME
+    message_a->addRRset(Message::SECTION_ANSWER, rrs_in_cname_www1);
+    EXPECT_EQ(ResponseClassifier::CNAME,
+        ResponseClassifier::classify(qu_in_a_www2, message_a));
+
+    // Add the A record for www and it should be an answer
+    message_a->addRRset(Message::SECTION_ANSWER, rrs_in_a_www);
+    EXPECT_EQ(ResponseClassifier::ANSWERCNAME,
+        ResponseClassifier::classify(qu_in_a_www2, message_a));
+
+    // Adding an unrelated TXT record should result in EXTRADATA
+    message_a->addRRset(Message::SECTION_ANSWER, rrs_in_txt_www);
+    EXPECT_EQ(ResponseClassifier::EXTRADATA,
+        ResponseClassifier::classify(qu_in_a_www2, message_a));
+
+    // Recreate the chain, but this time end with a TXT RR and not the A
+    // record.  This should return INVTYPE.
+    MessagePtr message_b(new Message(Message::RENDER));
+    message_b->setHeaderFlag(Message::HEADERFLAG_QR);
+    message_b->setOpcode(Opcode::QUERY());
+    message_b->setRcode(Rcode::NOERROR());
+    message_b->addQuestion(qu_in_a_www2);
+    message_b->addRRset(Message::SECTION_ANSWER, rrs_in_cname_www2);
+    message_b->addRRset(Message::SECTION_ANSWER, rrs_in_cname_www1);
+    message_b->addRRset(Message::SECTION_ANSWER, rrs_in_txt_www);
+
+    EXPECT_EQ(ResponseClassifier::INVTYPE,
+        ResponseClassifier::classify(qu_in_a_www2, message_b));
+}
+
+} // Anonymous namespace

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

@@ -17,7 +17,6 @@
  - PERFORMANCE OF THIS SOFTWARE.
 -->
 
-<!-- $Id$ -->
 <refentry>
 
   <refentryinfo>

+ 0 - 1
src/bin/stats/stats.py.in

@@ -15,7 +15,6 @@
 # NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
 # WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 
-# $Id$
 __version__ = "$Revision$"
 
 import sys; sys.path.append ('@@PYTHONPATH@@')

+ 0 - 1
src/bin/stats/stats_stub.py.in

@@ -15,7 +15,6 @@
 # NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
 # WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 
-# $Id$
 __version__ = "$Revision$"
 
 import sys; sys.path.append ('@@PYTHONPATH@@')

+ 0 - 1
src/bin/stats/tests/b10-stats_stub_test.py

@@ -13,7 +13,6 @@
 # NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
 # WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 
-# $Id$
 __version__ = "$Revision$"
 
 #

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

@@ -13,7 +13,6 @@
 # NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
 # WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 
-# $Id$
 __version__ = "$Revision$"
 
 #

+ 0 - 1
src/bin/stats/tests/fake_time.py

@@ -13,7 +13,6 @@
 # NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
 # WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 
-# $Id$
 __version__ = "$Revision$"
 
 # This is a dummy time class against a Python standard time class.

+ 0 - 1
src/bin/stats/tests/isc/cc/session.py

@@ -13,7 +13,6 @@
 # NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
 # WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 
-# $Id$
 # This module is a mock-up class of isc.cc.session
 
 __version__ = "$Revision$"

+ 0 - 1
src/bin/stats/tests/isc/config/ccsession.py

@@ -13,7 +13,6 @@
 # NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
 # WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 
-# $Id$
 # This module is a mock-up class of isc.cc.session
 
 __version__ = "$Revision$"

+ 0 - 2
src/bin/stats/tests/isc/util/process.py

@@ -13,8 +13,6 @@
 # NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
 # WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 
-# $Id$
-
 # A dummy function of isc.util.process.rename()
 def rename(name=None):
     pass

+ 0 - 2
src/bin/stats/tests/isc/utils/process.py

@@ -13,8 +13,6 @@
 # NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
 # WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 
-# $Id$
-
 # A dummy function of isc.utils.process.rename()
 def rename(name=None):
     pass

+ 0 - 1
src/bin/usermgr/b10-cmdctl-usermgr.xml

@@ -17,7 +17,6 @@
  - PERFORMANCE OF THIS SOFTWARE.
 -->
 
-<!-- $Id$ -->
 <refentry>
 
   <refentryinfo>

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

@@ -17,7 +17,6 @@
  - PERFORMANCE OF THIS SOFTWARE.
 -->
 
-<!-- $Id$ -->
 <refentry>
 
   <refentryinfo>

+ 0 - 2
src/bin/xfrin/tests/xfrin_test.py

@@ -13,8 +13,6 @@
 # NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
 # WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 
-# $Id$
-
 import unittest
 import socket
 from xfrin import *

+ 19 - 5
src/bin/xfrin/xfrin.py.in

@@ -16,8 +16,6 @@
 # NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
 # WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 
-# $Id$
-
 import sys; sys.path.append ('@@PYTHONPATH@@')
 import os
 import signal
@@ -521,8 +519,19 @@ class Xfrin:
             msg = create_command(notify_out.ZONE_NEW_DATA_READY_CMD, param)
             # catch the exception, in case msgq has been killed.
             try:
-                self._send_cc_session.group_sendmsg(msg, XFROUT_MODULE_NAME)
-                self._send_cc_session.group_sendmsg(msg, ZONE_MANAGER_MODULE_NAME)
+                seq = self._send_cc_session.group_sendmsg(msg,
+                                                          XFROUT_MODULE_NAME)
+                try:
+                    answer, env = self._send_cc_session.group_recvmsg(False,
+                                                                      seq)
+                except isc.cc.session.SessionTimeout:
+                    pass        # for now we just ignore the failure
+                seq = self._send_cc_session.group_sendmsg(msg, ZONE_MANAGER_MODULE_NAME)
+                try:
+                    answer, env = self._send_cc_session.group_recvmsg(False,
+                                                                      seq)
+                except isc.cc.session.SessionTimeout:
+                    pass        # for now we just ignore the failure
             except socket.error as err: 
                 log_error("Fail to send message to %s and %s, msgq may has been killed" 
                           % (XFROUT_MODULE_NAME, ZONE_MANAGER_MODULE_NAME))
@@ -530,7 +539,12 @@ class Xfrin:
             msg = create_command(ZONE_XFRIN_FAILED, param)
             # catch the exception, in case msgq has been killed.
             try:
-                self._send_cc_session.group_sendmsg(msg, ZONE_MANAGER_MODULE_NAME)
+                seq = self._send_cc_session.group_sendmsg(msg, ZONE_MANAGER_MODULE_NAME)
+                try:
+                    answer, env = self._send_cc_session.group_recvmsg(False,
+                                                                      seq)
+                except isc.cc.session.SessionTimeout:
+                    pass        # for now we just ignore the failure
             except socket.error as err:
                 log_error("Fail to send message to %s, msgq may has been killed" 
                           % ZONE_MANAGER_MODULE_NAME)

+ 20 - 0
src/bin/xfrout/b10-xfrout.8

@@ -69,6 +69,26 @@ The configurable settings are:
 
 \fItransfers_out\fR
 defines the maximum number of outgoing zone transfers that can run concurrently\&. The default is 10\&.
+.PP
+
+\fIlog_name\fR
+.PP
+
+\fIlog_file\fR
+The location of the log file if using a file channel\&. If undefined, then the file channel is closed\&. The default is
+/usr/local/var/bind10\-devel/log/Xfrout\&.log\&.
+.PP
+
+\fIlog_severity\fR
+The default is "debug"\&.
+.PP
+
+\fIlog_versions\fR
+The default is 5\&.
+.PP
+
+\fIlog_max_bytes\fR
+The default is 1048576\&.
 .if n \{\
 .sp
 .\}

+ 27 - 1
src/bin/xfrout/b10-xfrout.xml

@@ -17,7 +17,6 @@
  - PERFORMANCE OF THIS SOFTWARE.
 -->
 
-<!-- $Id$ -->
 <refentry>
 
   <refentryinfo>
@@ -98,6 +97,33 @@
       defines the maximum number of outgoing zone transfers
       that can run concurrently. The default is 10.
     </para>
+    <para>
+      <varname>log_name</varname>
+<!-- TODO -->
+    </para>
+    <para>
+      <varname>log_file</varname>
+<!-- TODO -->
+      The location of the log file if using a file channel.
+      If undefined, then the file channel is closed.
+      The default is
+      <filename>/usr/local/var/bind10-devel/log/Xfrout.log</filename>.
+    </para>
+    <para>
+      <varname>log_severity</varname>
+<!-- TODO -->
+      The default is "debug".
+    </para>
+    <para>
+      <varname>log_versions</varname>
+<!-- TODO -->
+      The default is 5.
+    </para>
+    <para>
+      <varname>log_max_bytes</varname>
+<!-- TODO -->
+      The default is 1048576.
+    </para>
 
 <!-- TODO: log configurations not documented yet in here. jreed
      has some but waiting on decisions ... -->

+ 22 - 2
src/bin/zonemgr/b10-zonemgr.8

@@ -2,12 +2,12 @@
 .\"     Title: b10-zonemgr
 .\"    Author: [FIXME: author] [see http://docbook.sf.net/el/author]
 .\" Generator: DocBook XSL Stylesheets v1.75.2 <http://docbook.sf.net/>
-.\"      Date: September 8, 2010
+.\"      Date: October 18, 2010
 .\"    Manual: BIND10
 .\"    Source: BIND10
 .\"  Language: English
 .\"
-.TH "B10\-ZONEMGR" "8" "September 8, 2010" "BIND10" "BIND10"
+.TH "B10\-ZONEMGR" "8" "October 18, 2010" "BIND10" "BIND10"
 .\" -----------------------------------------------------------------
 .\" * set default formatting
 .\" -----------------------------------------------------------------
@@ -43,6 +43,26 @@ receives its configurations from
 \fBb10-cfgmgr\fR(8)\&.
 .SH "CONFIGURATION AND COMMANDS"
 .PP
+The configurable settings are:
+.PP
+
+\fIjitter_scope\fR
+defines the random jitter range subtracted from the refresh and retry timers to avoid many zones from refreshing at the same time\&. The refresh or retry time actually used is a random time between the defined refresh or retry time and it multiplied by the
+\fIjitter_scope\fR\&. This is re\-evaluated after each refresh or retry\&. This value is a real number and the maximum is 0\&.5 (half of the refresh or retry time)\&. The default is 0\&.25\&. Set to 0 to disable the jitter\&.
+.PP
+
+\fIlowerbound_refresh\fR
+defines the minimum SOA REFRESH time in seconds\&. The default is 10\&.
+.PP
+
+\fIlowerbound_retry\fR
+defines the minimum SOA RETRY time in seconds\&. The default is 5\&.
+.PP
+
+\fImax_transfer_timeout\fR
+defines the maximum amount of time in seconds for a transfer\&.
+The default is 14400 (4 hours)\&.
+.PP
 The configuration commands are:
 .PP
 

+ 31 - 4
src/bin/zonemgr/b10-zonemgr.xml

@@ -17,11 +17,10 @@
  - PERFORMANCE OF THIS SOFTWARE.
 -->
 
-<!-- $Id$ -->
 <refentry>
 
   <refentryinfo>
-    <date>September 8, 2010</date>
+    <date>October 18, 2010</date>
   </refentryinfo>
 
   <refmeta>
@@ -90,11 +89,39 @@
 
   <refsect1>
     <title>CONFIGURATION AND COMMANDS</title>
-<!--
     <para>
       The configurable settings are:
     </para>
--->
+    <para>
+      <varname>jitter_scope</varname>
+      defines the random jitter range subtracted from the refresh
+      and retry timers to avoid many zones from refreshing at the
+      same time.
+      The refresh or retry time actually used is a random time
+      between the defined refresh or retry time and it multiplied
+      by the <varname>jitter_scope</varname>.
+      This is re-evaluated after each refresh or retry.
+      This value is a real number and the maximum is 0.5 (half of the
+      refresh or retry time).
+      The default is 0.25.
+      Set to 0 to disable the jitter.
+    </para>
+    <para>
+      <varname>lowerbound_refresh</varname>
+      defines the minimum SOA REFRESH time in seconds.
+      The default is 10.
+    </para>
+    <para>
+      <varname>lowerbound_retry</varname>
+      defines the minimum SOA RETRY time in seconds.
+      The default is 5.
+    </para>
+    <para>
+      <varname>max_transfer_timeout</varname>
+      defines the maximum amount of time in seconds for a transfer.
+<!-- TODO: what is the purpose of this? -->
+      The default is 14400 (4 hours).
+    </para>
 
 <!-- TODO: formating -->
     <para>

+ 3 - 0
src/bin/zonemgr/tests/zonemgr_test.py

@@ -43,6 +43,9 @@ class MySession():
         if module_name not in ("Auth", "Xfrin"):
             raise ZonemgrTestException("module name not exist")
 
+    def group_recvmsg(self, nonblock, seq):
+        return None, None
+
 class MyZonemgrRefresh(ZonemgrRefresh):
     def __init__(self):
         class FakeConfig:

+ 5 - 1
src/bin/zonemgr/zonemgr.py.in

@@ -266,7 +266,11 @@ class ZonemgrRefresh:
         """Send command between modules."""
         msg = create_command(command_name, params)
         try:
-            self._cc.group_sendmsg(msg, module_name)
+            seq = self._cc.group_sendmsg(msg, module_name)
+            try:
+                answer, env = self._cc.group_recvmsg(False, seq)
+            except isc.cc.session.SessionTimeout:
+                pass        # for now we just ignore the failure
         except socket.error:
             sys.stderr.write("[b10-zonemgr] Failed to send to module %s, the session has been closed." % module_name)
 

+ 222 - 12
src/lib/asiolink/asiolink.cc

@@ -12,8 +12,6 @@
 // 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
@@ -32,6 +30,7 @@
 
 #include <dns/buffer.h>
 #include <dns/message.h>
+#include <dns/rcode.h>
 
 #include <asiolink/asiolink.h>
 #include <asiolink/internal/tcpdns.h>
@@ -39,6 +38,7 @@
 
 #include <log/dummylog.h>
 
+
 using namespace asio;
 using asio::ip::udp;
 using asio::ip::tcp;
@@ -48,8 +48,46 @@ using namespace isc::dns;
 using isc::log::dlog;
 using namespace boost;
 
+// Is this something we can use in libdns++?
+namespace {
+    class SectionInserter {
+    public:
+        SectionInserter(MessagePtr message, const Message::Section sect) :
+            message_(message), section_(sect)
+        {}
+        void operator()(const RRsetPtr rrset) {
+            message_->addRRset(section_, rrset, true);
+        }
+        MessagePtr message_;
+        const Message::Section section_;
+    };
+
+
+    /// \brief Copies the parts relevant for a DNS answer to the
+    /// target message
+    ///
+    /// This adds all the RRsets in the answer, authority and
+    /// additional sections to the target, as well as the response
+    /// code
+    void copyAnswerMessage(const Message& source, MessagePtr target) {
+        target->setRcode(source.getRcode());
+
+        for_each(source.beginSection(Message::SECTION_ANSWER),
+                 source.endSection(Message::SECTION_ANSWER),
+                 SectionInserter(target, Message::SECTION_ANSWER));
+        for_each(source.beginSection(Message::SECTION_AUTHORITY),
+                 source.endSection(Message::SECTION_AUTHORITY),
+                 SectionInserter(target, Message::SECTION_AUTHORITY));
+        for_each(source.beginSection(Message::SECTION_ADDITIONAL),
+                 source.endSection(Message::SECTION_ADDITIONAL),
+                 SectionInserter(target, Message::SECTION_ADDITIONAL));
+    }
+}
+
 namespace asiolink {
 
+typedef pair<string, uint16_t> addr_t;
+
 class IOServiceImpl {
 private:
     IOServiceImpl(const IOService& source);
@@ -245,8 +283,11 @@ typedef std::vector<std::pair<std::string, uint16_t> > AddressVector;
 }
 
 RecursiveQuery::RecursiveQuery(DNSService& dns_service,
-    const AddressVector& upstream, int timeout, unsigned retries) :
+    const AddressVector& upstream,
+    const AddressVector& upstream_root,
+    int timeout, unsigned retries) :
     dns_service_(dns_service), upstream_(new AddressVector(upstream)),
+    upstream_root_(new AddressVector(upstream_root)),
     timeout_(timeout), retries_(retries)
 {}
 
@@ -298,8 +339,17 @@ private:
 
     // Info for (re)sending the query (the question and destination)
     Question question_;
+
+    // This is where we build and store our final answer
+    MessagePtr answer_message_;
+
+    // currently we use upstream as the current list of NS records
+    // we should differentiate between forwarding and resolving
     shared_ptr<AddressVector> upstream_;
 
+    // root servers...just copied over to the zone_servers_
+    shared_ptr<AddressVector> upstream_root_;
+
     // Buffer to store the result.
     OutputBufferPtr buffer_;
 
@@ -314,9 +364,26 @@ private:
     int timeout_;
     unsigned retries_;
 
+    // normal query state
+
+    // if we change this to running and add a sent, we can do
+    // decoupled timeouts i think
+    bool done;
+
+    // Not using NSAS at this moment, so we keep a list
+    // of 'current' zone servers
+    vector<addr_t> zone_servers_;
+
+    // Update the question that will be sent to the server
+    void setQuestion(const Question& new_question) {
+        question_ = new_question;
+    }
+
     // (re)send the query to the server.
     void send() {
         const int uc = upstream_->size();
+        const int zs = zone_servers_.size();
+        buffer_->clear();
         if (uc > 0) {
             int serverIndex = rand() % uc;
             dlog("Sending upstream query (" + question_.toText() +
@@ -326,34 +393,156 @@ private:
                 upstream_->at(serverIndex).second, buffer_, this,
                 timeout_);
             io_.post(query);
+        } else if (zs > 0) {
+            int serverIndex = rand() % zs;
+            dlog("Sending query to zone server (" + question_.toText() +
+                ") to " + zone_servers_.at(serverIndex).first);
+            UDPQuery query(io_, question_,
+                zone_servers_.at(serverIndex).first,
+                zone_servers_.at(serverIndex).second, buffer_, this,
+                timeout_);
+            io_.post(query);
         } else {
             dlog("Error, no upstream servers to send to.");
         }
     }
+    
+    // This function is called by operator() if there is an actual
+    // answer from a server and we are in recursive mode
+    // depending on the contents, we go on recursing or return
+    //
+    // Note that the footprint may change as this function may
+    // need to append data to the answer we are building later.
+    //
+    // returns true if we are done
+    // returns false if we are not done
+    bool handleRecursiveAnswer(const Message& incoming) {
+        if (incoming.getRRCount(Message::SECTION_ANSWER) > 0) {
+            dlog("Got final result, copying answer.");
+            copyAnswerMessage(incoming, answer_message_);
+            return true;
+        } else {
+            dlog("Got delegation, continuing");
+            // ok we need to do some more processing.
+            // the ns list should contain all nameservers
+            // while the additional may contain addresses for
+            // them.
+            // this needs to tie into NSAS of course
+            // for this very first mockup, hope there is an
+            // address in additional and just use that
+
+            // send query to the addresses in the delegation
+            bool found_ns_address = false;
+            zone_servers_.clear();
+
+            for (RRsetIterator rrsi = incoming.beginSection(Message::SECTION_ADDITIONAL);
+                 rrsi != incoming.endSection(Message::SECTION_ADDITIONAL) && !found_ns_address;
+                 rrsi++) {
+                ConstRRsetPtr rrs = *rrsi;
+                if (rrs->getType() == RRType::A()) {
+                    // found address
+                    RdataIteratorPtr rdi = rrs->getRdataIterator();
+                    // just use the first for now
+                    if (!rdi->isLast()) {
+                        std::string addr_str = rdi->getCurrent().toText();
+                        dlog("[XX] first address found: " + addr_str);
+                        // now we have one address, simply
+                        // resend that exact same query
+                        // to that address and yield, when it
+                        // returns, loop again.
+                        
+                        // should use NSAS
+                        zone_servers_.push_back(addr_t(addr_str, 53));
+                        found_ns_address = true;
+                    }
+                }
+            }
+            if (found_ns_address) {
+                // next resolver round
+                send();
+                return false;
+            } else {
+                dlog("[XX] no ready-made addresses in additional. need nsas.");
+                // this will result in answering with the delegation. oh well
+                copyAnswerMessage(incoming, answer_message_);
+                return true;
+            }
+        }
+    }
+    
+
 public:
     RunningQuery(asio::io_service& io, const Question &question,
-        shared_ptr<AddressVector> upstream,
+        MessagePtr answer_message, shared_ptr<AddressVector> upstream,
+        shared_ptr<AddressVector> upstream_root,
         OutputBufferPtr buffer, DNSServer* server, int timeout,
         unsigned retries) :
         io_(io),
         question_(question),
+        answer_message_(answer_message),
         upstream_(upstream),
+        upstream_root_(upstream_root),
         buffer_(buffer),
         server_(server->clone()),
         timeout_(timeout),
-        retries_(retries)
+        retries_(retries),
+        zone_servers_()
     {
+        dlog("Started a new RunningQuery");
+        done = false;
+
+        // should use NSAS for root servers
+        // Adding root servers if not a forwarder
+        if (upstream_->empty()) {
+            if (upstream_root_->empty()) { //if no root ips given, use this
+                zone_servers_.push_back(addr_t("192.5.5.241", 53));
+            }
+            else
+            {
+              //copy the list
+              dlog("Size is " + 
+                    boost::lexical_cast<string>(upstream_root_->size()) + 
+                    "\n");
+              //Use BOOST_FOREACH here? Is it faster?
+              for(AddressVector::iterator it = upstream_root_->begin();
+                   it < upstream_root_->end(); it++) {
+                zone_servers_.push_back(addr_t(it->first,it->second));
+                dlog("Put " + zone_servers_.back().first + "into root list\n");
+              }
+            }
+        }
         send();
     }
 
+
     // This function is used as callback from DNSQuery.
     virtual void operator()(UDPQuery::Result result) {
-        if (result == UDPQuery::TIME_OUT && retries_ --) {
-            dlog("Resending query");
+        // XXX is this the place for TCP retry?
+        if (result != UDPQuery::TIME_OUT) {
+            // we got an answer
+            Message incoming(Message::PARSE);
+            InputBuffer ibuf(buffer_->getData(), buffer_->getLength());
+            incoming.fromWire(ibuf);
+
+            if (upstream_->size() == 0 &&
+                incoming.getRcode() == Rcode::NOERROR()) {
+                done = handleRecursiveAnswer(incoming);
+            } else {
+                copyAnswerMessage(incoming, answer_message_);
+                done = true;
+            }
+            
+            if (done) {
+                server_->resume(result == UDPQuery::SUCCESS);
+                delete this;
+            }
+        } else if (retries_--) {
             // We timed out, but we have some retries, so send again
+            dlog("Timeout, resending query");
             send();
         } else {
-            server_->resume(result == UDPQuery::SUCCESS);
+            // out of retries, give up for now
+            server_->resume(false);
             delete this;
         }
     }
@@ -362,7 +551,9 @@ public:
 }
 
 void
-RecursiveQuery::sendQuery(const Question& question, OutputBufferPtr buffer,
+RecursiveQuery::sendQuery(const Question& question,
+                          MessagePtr answer_message,
+                          OutputBufferPtr buffer,
                           DNSServer* server)
 {
     // XXX: eventually we will need to be able to determine whether
@@ -371,8 +562,8 @@ RecursiveQuery::sendQuery(const Question& question, OutputBufferPtr buffer,
     // 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_);
+    new RunningQuery(io, question, answer_message, upstream_, upstream_root_,
+                         buffer, server, timeout_, retries_);
 }
 
 class IntervalTimerImpl {
@@ -386,6 +577,11 @@ public:
     void setupTimer(const IntervalTimer::Callback& cbfunc,
                     const uint32_t interval);
     void callback(const asio::error_code& error);
+    void cancel() {
+        timer_.cancel();
+        interval_ = 0;
+    }
+    uint32_t getInterval() const { return (interval_); }
 private:
     // a function to update timer_ when it expires
     void updateTimer();
@@ -398,7 +594,7 @@ private:
 };
 
 IntervalTimerImpl::IntervalTimerImpl(IOService& io_service) :
-    timer_(io_service.get_io_service())
+    interval_(0), timer_(io_service.get_io_service())
 {}
 
 IntervalTimerImpl::~IntervalTimerImpl()
@@ -427,6 +623,10 @@ IntervalTimerImpl::setupTimer(const IntervalTimer::Callback& cbfunc,
 
 void
 IntervalTimerImpl::updateTimer() {
+    if (interval_ == 0) {
+        // timer has been canceled.  Do nothing.
+        return;
+    }
     try {
         // Update expire time to (current time + interval_).
         timer_.expires_from_now(boost::posix_time::seconds(interval_));
@@ -461,4 +661,14 @@ IntervalTimer::setupTimer(const Callback& cbfunc, const uint32_t interval) {
     return (impl_->setupTimer(cbfunc, interval));
 }
 
+void
+IntervalTimer::cancel() {
+    impl_->cancel();
+}
+
+uint32_t
+IntervalTimer::getInterval() const {
+    return (impl_->getInterval());
+}
+
 }

+ 35 - 4
src/lib/asiolink/asiolink.h

@@ -12,8 +12,6 @@
 // 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
 
@@ -414,10 +412,11 @@ public:
     /// \param DNSServer DNSServer object to use
     virtual void operator()(const IOMessage& io_message,
                             isc::dns::MessagePtr message,
+                            isc::dns::MessagePtr answer_message,
                             isc::dns::OutputBufferPtr buffer,
                             DNSServer* server) const
     {
-        (*self_)(io_message, message, buffer, server);
+        (*self_)(io_message, message, answer_message, buffer, server);
     }
 private:
     DNSLookup* self_;
@@ -467,6 +466,7 @@ public:
     /// \param buffer The result is put here
     virtual void operator()(const IOMessage& io_message,
                             isc::dns::MessagePtr message,
+                            isc::dns::MessagePtr answer_message,
                             isc::dns::OutputBufferPtr buffer) const = 0;
 };
 
@@ -538,6 +538,8 @@ public:
     ///        query on.
     /// \param upstream Addresses and ports of the upstream servers
     ///        to forward queries to.
+    /// \param upstream_root Addresses and ports of the root servers
+    ///        to use when resolving.
     /// \param timeout How long to timeout the query, in ms
     ///     -1 means never timeout (but do not use that).
     ///     TODO: This should be computed somehow dynamically in future
@@ -545,7 +547,10 @@ public:
     ///     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);
+                   upstream, 
+                   const std::vector<std::pair<std::string, uint16_t> >&
+                   upstream_root, 
+                   int timeout = -1, unsigned retries = 0);
     //@}
 
     /// \brief Initiates an upstream query in the \c RecursiveQuery object.
@@ -559,12 +564,15 @@ public:
     /// \param buffer An output buffer into which the response can be copied
     /// \param server A pointer to the \c DNSServer object handling the client
     void sendQuery(const isc::dns::Question& question,
+                   isc::dns::MessagePtr answer_message,
                    isc::dns::OutputBufferPtr buffer,
                    DNSServer* server);
 private:
     DNSService& dns_service_;
     boost::shared_ptr<std::vector<std::pair<std::string, uint16_t> > >
         upstream_;
+    boost::shared_ptr<std::vector<std::pair<std::string, uint16_t> > >
+        upstream_root_;
     int timeout_;
     unsigned retries_;
 };
@@ -657,6 +665,29 @@ public:
     /// \throw isc::Unexpected ASIO library error
     ///
     void setupTimer(const Callback& cbfunc, const uint32_t interval);
+
+    /// Cancel the timer.
+    ///
+    /// If the timer has been set up, this method cancels any asynchronous
+    /// events waiting on the timer and stops the timer itself.
+    /// If the timer has already been canceled, this method effectively does
+    /// nothing.
+    ///
+    /// This method never throws an exception.
+    void cancel();
+
+    /// Return the timer interval.
+    ///
+    /// This method returns the timer interval in seconds if it's running;
+    /// if the timer has been canceled it returns 0.
+    ///
+    /// This method never throws an exception.
+    ///
+    /// Note: We may want to change the granularity of the timer to
+    /// milliseconds or even finer.  If and when this happens the semantics
+    /// of the return value of this method will be changed accordingly.
+    uint32_t getInterval() const;
+
 private:
     IntervalTimerImpl* impl_;
 };

+ 2 - 3
src/lib/asiolink/internal/tcpdns.h

@@ -12,8 +12,6 @@
 // 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
 
@@ -197,7 +195,8 @@ private:
     // \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_;
+    isc::dns::MessagePtr query_message_;
+    isc::dns::MessagePtr answer_message_;
 
     // The buffer into which the query packet is written
     boost::shared_array<char>data_;

+ 6 - 3
src/lib/asiolink/internal/udpdns.h

@@ -12,8 +12,6 @@
 // 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
 
@@ -210,7 +208,12 @@ private:
     // \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 original query as sent by the client
+    isc::dns::MessagePtr query_message_;
+
+    // The response message we are building
+    isc::dns::MessagePtr answer_message_;
 
     // The buffer into which the response is written
     isc::dns::OutputBufferPtr respbuf_;

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

@@ -12,8 +12,6 @@
 // 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

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

@@ -12,8 +12,6 @@
 // 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
 

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

@@ -12,8 +12,6 @@
 // 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

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

@@ -12,8 +12,6 @@
 // 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
 

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

@@ -12,8 +12,6 @@
 // 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
 

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

@@ -14,8 +14,6 @@
 // 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>

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

@@ -12,8 +12,6 @@
 // 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
 

+ 6 - 5
src/lib/asiolink/tcpdns.cc

@@ -12,8 +12,6 @@
 // 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
@@ -144,7 +142,8 @@ TCPServer::operator()(error_code ec, size_t length) {
         // 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));
+        query_message_.reset(new Message(Message::PARSE));
+        answer_message_.reset(new Message(Message::RENDER));
 
         // Schedule a DNS lookup, and yield.  When the lookup is
         // finished, the coroutine will resume immediately after
@@ -159,7 +158,8 @@ TCPServer::operator()(error_code ec, size_t length) {
 
         // Call the DNS answer provider to render the answer into
         // wire format
-        (*answer_callback_)(*io_message_, message_, respbuf_);
+        (*answer_callback_)(*io_message_, query_message_,
+                            answer_message_, respbuf_);
 
         // Set up the response, beginning with two length bytes.
         lenbuf.writeUint16(respbuf_->getLength());
@@ -178,7 +178,8 @@ TCPServer::operator()(error_code ec, size_t length) {
 /// AsyncLookup<TCPServer> handler.)
 void
 TCPServer::asyncLookup() {
-    (*lookup_callback_)(*io_message_, message_, respbuf_, this);
+    (*lookup_callback_)(*io_message_, query_message_,
+                        answer_message_, respbuf_, this);
 }
 
 /// Post this coroutine on the ASIO service queue so that it will

+ 109 - 16
src/lib/asiolink/tests/asiolink_unittest.cc

@@ -12,9 +12,6 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 
-// $Id$
-
-
 #include <config.h>
 
 #include <sys/socket.h>
@@ -31,6 +28,7 @@
 #include <exceptions/exceptions.h>
 
 #include <dns/tests/unittest_util.h>
+#include <dns/rcode.h>
 
 #include <dns/buffer.h>
 #include <dns/message.h>
@@ -442,6 +440,7 @@ protected:
                             DNSAnswer* answer = NULL) :
             io_(io_service),
             message_(new Message(Message::PARSE)),
+            answer_message_(new Message(Message::RENDER)),
             respbuf_(new OutputBuffer(0)),
             checkin_(checkin), lookup_(lookup), answer_(answer)
         {}
@@ -450,7 +449,8 @@ protected:
                         size_t length = 0)
         {}
 
-        void resume(const bool) { // in our test this shouldn't be called
+        void resume(const bool) {
+          // should never be called in our tests
         }
 
         DNSServer* clone() {
@@ -460,7 +460,8 @@ protected:
 
         inline void asyncLookup() {
             if (lookup_) {
-                (*lookup_)(*io_message_, message_, respbuf_, this);
+                (*lookup_)(*io_message_, message_, answer_message_,
+                           respbuf_, this);
             }
         }
 
@@ -473,6 +474,7 @@ protected:
         // asynchronous lookup calls via the asyncLookup() method
         boost::shared_ptr<asiolink::IOMessage> io_message_;
         isc::dns::MessagePtr message_;
+        isc::dns::MessagePtr answer_message_;
         isc::dns::OutputBufferPtr respbuf_;
 
         // Callback functions provided by the caller
@@ -643,15 +645,17 @@ singleAddress(const string &address, uint16_t port) {
 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)));
+    EXPECT_NO_THROW(RecursiveQuery(*dns_service_,
+                                   singleAddress(TEST_IPV4_ADDR, port),
+                                   singleAddress(TEST_IPV4_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)));
+    EXPECT_NO_THROW(RecursiveQuery(*dns_service_,
+                                   singleAddress(TEST_IPV6_ADDR, port),
+                                   singleAddress(TEST_IPV6_ADDR,port)));
 }
 
 // XXX:
@@ -659,7 +663,7 @@ TEST_F(ASIOLinkTest, recursiveSetupV6) {
 // a routine that can do this with variable address family, address, and
 // port, and with the various callbacks defined in such a way as to ensure
 // full code coverage including error cases.
-TEST_F(ASIOLinkTest, recursiveSend) {
+TEST_F(ASIOLinkTest, forwarderSend) {
     setDNSService(true, false);
 
     // Note: We use the test prot plus one to ensure we aren't binding
@@ -667,11 +671,14 @@ TEST_F(ASIOLinkTest, recursiveSend) {
     uint16_t port = boost::lexical_cast<uint16_t>(TEST_CLIENT_PORT);
 
     MockServer server(*io_service_);
-    RecursiveQuery rq(*dns_service_, singleAddress(TEST_IPV4_ADDR, port));
+    RecursiveQuery rq(*dns_service_,
+                      singleAddress(TEST_IPV4_ADDR, port),
+                      singleAddress(TEST_IPV4_ADDR, port));
 
     Question q(Name("example.com"), RRClass::IN(), RRType::TXT());
     OutputBufferPtr buffer(new OutputBuffer(0));
-    rq.sendQuery(q, buffer, &server);
+    MessagePtr answer(new Message(Message::RENDER));
+    rq.sendQuery(q, answer, buffer, &server);
 
     char data[4096];
     size_t size = sizeof(data);
@@ -712,11 +719,14 @@ TEST_F(ASIOLinkTest, recursiveTimeout) {
 
     // Do the answer
     const uint16_t port = boost::lexical_cast<uint16_t>(TEST_CLIENT_PORT);
-    RecursiveQuery query(*dns_service_, singleAddress(TEST_IPV4_ADDR, port),
-        10, 2);
+    RecursiveQuery query(*dns_service_,
+                         singleAddress(TEST_IPV4_ADDR, port),
+                         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);
+    MessagePtr answer(new Message(Message::RENDER));
+    query.sendQuery(question, answer, buffer, &server);
 
     // Run the test
     io_service_->run();
@@ -746,12 +756,64 @@ TEST_F(ASIOLinkTest, recursiveTimeout) {
     EXPECT_EQ(3, num);
 }
 
+// as mentioned above, we need a more better framework for this,
+// in addition to that, this sends out queries into the world
+// (which we should catch somehow and fake replies for)
+// for the skeleton code, it shouldn't be too much of a problem
+// Ok so even we don't all have access to the DNS world right now,
+// so disabling these tests too.
+TEST_F(ASIOLinkTest, DISABLED_recursiveSendOk) {
+    setDNSService(true, false);
+    bool done;
+    
+    MockServerStop server(*io_service_, &done);
+    vector<pair<string, uint16_t> > empty_vector;
+    RecursiveQuery rq(*dns_service_, empty_vector, empty_vector, 10000, 0);
+
+    Question q(Name("www.isc.org"), RRClass::IN(), RRType::A());
+    OutputBufferPtr buffer(new OutputBuffer(0));
+    MessagePtr answer(new Message(Message::RENDER));
+    rq.sendQuery(q, answer, buffer, &server);
+    io_service_->run();
+
+    // Check that the answer we got matches the one we wanted
+    EXPECT_EQ(Rcode::NOERROR(), answer->getRcode());
+    ASSERT_EQ(1, answer->getRRCount(Message::SECTION_ANSWER));
+    RRsetPtr a = *answer->beginSection(Message::SECTION_ANSWER);
+    EXPECT_EQ(q.getName(), a->getName());
+    EXPECT_EQ(q.getType(), a->getType());
+    EXPECT_EQ(q.getClass(), a->getClass());
+    EXPECT_EQ(1, a->getRdataCount());
+}
+
+// see comments at previous test
+TEST_F(ASIOLinkTest, DISABLED_recursiveSendNXDOMAIN) {
+    setDNSService(true, false);
+    bool done;
+    
+    MockServerStop server(*io_service_, &done);
+    vector<pair<string, uint16_t> > empty_vector;
+    RecursiveQuery rq(*dns_service_, empty_vector, empty_vector, 10000, 0);
+
+    Question q(Name("wwwdoesnotexist.isc.org"), RRClass::IN(), RRType::A());
+    OutputBufferPtr buffer(new OutputBuffer(0));
+    MessagePtr answer(new Message(Message::RENDER));
+    rq.sendQuery(q, answer, buffer, &server);
+    io_service_->run();
+
+    // Check that the answer we got matches the one we wanted
+    EXPECT_EQ(Rcode::NXDOMAIN(), answer->getRcode());
+    EXPECT_EQ(0, answer->getRRCount(Message::SECTION_ANSWER));
+}
+
+
+
 // This fixture is for testing IntervalTimer. Some callback functors are 
 // registered as callback function of the timer to test if they are called
 // or not.
 class IntervalTimerTest : public ::testing::Test {
 protected:
-    IntervalTimerTest() : io_service_() {};
+    IntervalTimerTest() : io_service_() {}
     ~IntervalTimerTest() {}
     class TimerCallBack : public std::unary_function<void, void> {
     public:
@@ -811,6 +873,19 @@ protected:
         int count_;
         int prev_counter_;
     };
+    class TimerCallBackCanceller {
+    public:
+        TimerCallBackCanceller(unsigned int& counter, IntervalTimer& itimer) :
+            counter_(counter), itimer_(itimer)
+        {}
+        void operator()() {
+            ++counter_;
+            itimer_.cancel();
+        }
+    private:
+        unsigned int& counter_;
+        IntervalTimer& itimer_;
+    };
     class TimerCallBackOverwriter : public std::unary_function<void, void> {
     public:
         TimerCallBackOverwriter(IntervalTimerTest* test_obj,
@@ -864,6 +939,7 @@ TEST_F(IntervalTimerTest, startIntervalTimer) {
     start = boost::posix_time::microsec_clock::universal_time();
     // setup timer
     itimer.setupTimer(TimerCallBack(this), 1);
+    EXPECT_EQ(1, itimer.getInterval());
     io_service_.run();
     // reaches here after timer expired
     // delta: difference between elapsed time and 1 second
@@ -926,6 +1002,23 @@ TEST_F(IntervalTimerTest, destructIntervalTimer) {
     EXPECT_TRUE(timer_cancel_success_);
 }
 
+TEST_F(IntervalTimerTest, cancel) {
+    // Similar to destructIntervalTimer test, but the first timer explicitly
+    // cancels itself on first callback.
+    IntervalTimer itimer_counter(io_service_);
+    IntervalTimer itimer_watcher(io_service_);
+    unsigned int counter = 0;
+    itimer_counter.setupTimer(TimerCallBackCanceller(counter, itimer_counter),
+                              1);
+    itimer_watcher.setupTimer(TimerCallBack(this), 3);
+    io_service_.run();
+    EXPECT_EQ(1, counter);
+    EXPECT_EQ(0, itimer_counter.getInterval());
+
+    // canceling an already canceled timer shouldn't cause any surprise.
+    EXPECT_NO_THROW(itimer_counter.cancel());
+}
+
 TEST_F(IntervalTimerTest, overwriteIntervalTimer) {
     // Note: This test currently takes 4 seconds. The timer should have
     // finer granularity and timer periods in this test should be shorter

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

@@ -12,8 +12,6 @@
 // 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>

+ 10 - 6
src/lib/asiolink/udpdns.cc

@@ -12,8 +12,6 @@
 // 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
@@ -69,7 +67,7 @@ UDPServer::UDPServer(io_service& io_service,
     if (addr.is_v6()) {
         socket_->set_option(asio::ip::v6_only(true));
     }
-    socket_->bind(udp::endpoint(proto, port));
+    socket_->bind(udp::endpoint(addr, port));
 }
 
 /// The function operator is implemented with the "stackless coroutine"
@@ -132,13 +130,16 @@ UDPServer::operator()(error_code ec, size_t length) {
         // 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));
+        query_message_.reset(new Message(Message::PARSE));
+        answer_message_.reset(new Message(Message::RENDER));
 
         // Schedule a DNS lookup, and yield.  When the lookup is
         // finished, the coroutine will resume immediately after
         // this point.
         CORO_YIELD io_.post(AsyncLookup<UDPServer>(*this));
 
+        dlog("[XX] got an answer");
+
         // The 'done_' flag indicates whether we have an answer
         // to send back.  If not, exit the coroutine permanently.
         if (!done_) {
@@ -147,7 +148,8 @@ UDPServer::operator()(error_code ec, size_t length) {
 
         // Call the DNS answer provider to render the answer into
         // wire format
-        (*answer_callback_)(*io_message_, message_, respbuf_);
+        (*answer_callback_)(*io_message_, query_message_,
+                            answer_message_, respbuf_);
 
         // Begin an asynchronous send, and then yield.  When the
         // send completes, we will resume immediately after this point
@@ -163,7 +165,8 @@ UDPServer::operator()(error_code ec, size_t length) {
 /// AsyncLookup<UDPServer> handler.)
 void
 UDPServer::asyncLookup() {
-    (*lookup_callback_)(*io_message_, message_, respbuf_, this);
+    (*lookup_callback_)(*io_message_, query_message_, answer_message_,
+                        respbuf_, this);
 }
 
 /// Post this coroutine on the ASIO service queue so that it will
@@ -253,6 +256,7 @@ UDPQuery::operator()(error_code ec, size_t length) {
                 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) {

+ 0 - 2
src/lib/bench/benchmark.h

@@ -12,8 +12,6 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 
-// $Id$
-
 #ifndef __BENCHMARK_H
 #define __BENCHMARK_H 1
 

+ 0 - 2
src/lib/bench/benchmark_util.cc

@@ -12,8 +12,6 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 
-// $Id$
-
 #include <fstream>
 #include <iostream>
 #include <string>

+ 0 - 2
src/lib/bench/benchmark_util.h

@@ -12,8 +12,6 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 
-// $Id$
-
 #ifndef __BENCHMARK_UTIL_H
 #define __BENCHMARK_UTIL_H 1
 

+ 0 - 2
src/lib/bench/example/search_bench.cc

@@ -12,8 +12,6 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 
-// $Id$
-
 #include <unistd.h>             // for getpid
 
 #include <cassert>

+ 0 - 2
src/lib/bench/tests/benchmark_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$
-
 #include <unistd.h>             // for usleep
 
 #include <bench/benchmark.h>

+ 0 - 2
src/lib/bench/tests/loadquery_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$
-
 #include <algorithm>
 #include <utility>
 #include <vector>

+ 0 - 0
src/lib/bench/tests/run_unittests.cc


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