Browse Source

Created b10-recurse--mostly copied from b10-auth, but with references
to datasrc, xfrin, xfrout and so on removed. This is simply the
client-facing side of a name server; it can receive and send packets
but can't process them in any way yet. It will become a simple forwarder,
and then a proper resolver, in future work.

git-svn-id: svn://bind10.isc.org/svn/bind10/branches/trac327@2958 e5f2f494-b856-4b98-b285-d166d9295462

Evan Hunt 14 years ago
parent
commit
53a82252ed

+ 4 - 0
configure.ac

@@ -423,6 +423,8 @@ AC_CONFIG_FILES([Makefile
                  src/bin/msgq/tests/Makefile
                  src/bin/auth/Makefile
                  src/bin/auth/tests/Makefile
+                 src/bin/recurse/Makefile
+                 src/bin/recurse/tests/Makefile
                  src/bin/xfrin/Makefile
                  src/bin/xfrin/tests/Makefile
                  src/bin/xfrout/Makefile
@@ -497,6 +499,8 @@ AC_OUTPUT([src/bin/cfgmgr/b10-cfgmgr.py
            src/bin/msgq/run_msgq.sh
            src/bin/auth/auth.spec.pre
            src/bin/auth/spec_config.h.pre
+           src/bin/recurse/recurse.spec.pre
+           src/bin/recurse/spec_config.h.pre
            src/lib/config/tests/data_def_unittests_config.h
            src/lib/python/isc/config/tests/config_test
            src/lib/python/isc/cc/tests/cc_test

+ 1 - 1
src/bin/Makefile.am

@@ -1 +1 @@
-SUBDIRS = bind10 bindctl cfgmgr loadzone msgq host cmdctl auth xfrin xfrout usermgr zonemgr
+SUBDIRS = bind10 bindctl cfgmgr loadzone msgq host cmdctl auth recurse xfrin xfrout usermgr zonemgr

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

@@ -17,14 +17,12 @@ pkglibexecdir = $(libexecdir)/@PACKAGE@
 
 CLEANFILES = *.gcno *.gcda auth.spec spec_config.h
 
+if ENABLE_MAN
 man_MANS = b10-auth.8
 EXTRA_DIST = $(man_MANS) b10-auth.xml
 
-if ENABLE_MAN
-
 b10-auth.8: b10-auth.xml
 	xsltproc --novalid --xinclude --nonet -o $@ http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl $(srcdir)/b10-auth.xml
-
 endif
 
 auth.spec: auth.spec.pre

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

@@ -33,7 +33,7 @@ class MessageRenderer;
 
 namespace xfr {
 class AbstractXfroutClient;
-};
+}
 }
 
 namespace asiolink {

+ 6 - 5
src/bin/auth/main.cc

@@ -51,6 +51,7 @@ using namespace isc::cc;
 using namespace isc::config;
 using namespace isc::dns;
 using namespace isc::xfr;
+using namespace asiolink;
 
 namespace {
 
@@ -64,7 +65,7 @@ const char* DNSPORT = "5300";
  * class itself? */
 AuthSrv *auth_server;
 
-asiolink::IOService* io_service;
+IOService* io_service;
 
 ConstElementPtr
 my_config_handler(ConstElementPtr new_config) {
@@ -176,8 +177,8 @@ main(int argc, char* argv[]) {
         auth_server->setVerbose(verbose_mode);
         cout << "[b10-auth] Server created." << endl;
 
-        asiolink::CheckinProvider* checkin = auth_server->getCheckinProvider();
-        asiolink::DNSProvider* process = auth_server->getDNSProvider();
+        CheckinProvider* checkin = auth_server->getCheckinProvider();
+        DNSProvider* process = auth_server->getDNSProvider();
 
         if (address != NULL) {
             // XXX: we can only specify at most one explicit address.
@@ -186,10 +187,10 @@ main(int argc, char* argv[]) {
             // We don't bother to fix this problem, however.  The -a option
             // is a short term workaround until we support dynamic listening
             // port allocation.
-            io_service = new asiolink::IOService(*port, *address,
+            io_service = new IOService(*port, *address,
                                                  checkin, process);
         } else {
-            io_service = new asiolink::IOService(*port, use_ipv4, use_ipv6,
+            io_service = new IOService(*port, use_ipv4, use_ipv6,
                                                  checkin, process);
         }
         cout << "[b10-auth] IOService created." << endl;

+ 55 - 0
src/bin/recurse/Makefile.am

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

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

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

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

@@ -0,0 +1,213 @@
+<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+               "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd"
+	       [<!ENTITY mdash "&#8212;">]>
+<!--
+ - Copyright (C) 2010  Internet Systems Consortium, Inc. ("ISC")
+ -
+ - Permission to use, copy, modify, and/or distribute this software for any
+ - purpose with or without fee is hereby granted, provided that the above
+ - copyright notice and this permission notice appear in all copies.
+ -
+ - THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+ - REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+ - AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+ - INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+ - LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+ - OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+ - PERFORMANCE OF THIS SOFTWARE.
+-->
+
+<!-- $Id$ -->
+<refentry>
+
+  <refentryinfo>
+    <date>September 16, 2010</date>
+  </refentryinfo>
+
+  <refmeta>
+    <refentrytitle>b10-recurse</refentrytitle>
+    <manvolnum>8</manvolnum>
+    <refmiscinfo>BIND10</refmiscinfo>
+  </refmeta>
+
+  <refnamediv>
+    <refname>b10-recurse</refname>
+    <refpurpose>Recursive DNS server</refpurpose>
+  </refnamediv>
+
+  <docinfo>
+    <copyright>
+      <year>2010</year>
+      <holder>Internet Systems Consortium, Inc. ("ISC")</holder>
+    </copyright>
+  </docinfo>
+
+  <refsynopsisdiv>
+    <cmdsynopsis>
+      <command>b10-recurse</command>
+      <arg><option>-4</option></arg>
+      <arg><option>-6</option></arg>
+      <arg><option>-a <replaceable>address</replaceable></option></arg>
+      <arg><option>-n</option></arg>
+      <arg><option>-p <replaceable>number</replaceable></option></arg>
+      <arg><option>-u <replaceable>username</replaceable></option></arg>
+      <arg><option>-v</option></arg>
+    </cmdsynopsis>
+  </refsynopsisdiv>
+
+  <refsect1>
+    <title>DESCRIPTION</title>
+    <para>The <command>b10-recurse</command> daemon provides the BIND 10
+      recursive DNS server.  Normally it is started by the
+      <citerefentry><refentrytitle>bind10</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+      boss process.
+    </para>
+
+    <para>
+      This daemon communicates with other BIND 10 components over a
+      <citerefentry><refentrytitle>b10-msgq</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+      C-Channel connection.  If this connection is not established,
+      <command>b10-recurse</command> will exit.
+    </para>
+
+    <para>
+      It also receives its configurations from
+<citerefentry><refentrytitle>b10-cfgmgr</refentrytitle><manvolnum>8</manvolnum></citerefentry>.
+      Currently no configuration commands are defined.
+    </para>
+
+    <note><para>
+      This prototype version only supports forwarding.  Future versions
+      will introduce full recursion, cache, lookup of local authoritative
+      data (as in <command>b10-auth</command>), and DNSSEC validation.
+    </para></note>
+  </refsect1>
+
+  <refsect1>
+    <title>OPTIONS</title>
+
+    <para>The arguments are as follows:</para>
+
+    <variablelist>
+      <varlistentry>
+        <term><option>-4</option></term>
+        <listitem><para>
+          Enables IPv4 only mode.
+          This switch may not be used with <option>-6</option> nor
+          <option>-a</option>.
+          By default, it listens on both IPv4 and IPv6 (if capable).
+        </para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>-6</option></term>
+        <listitem><para>
+          Enables IPv6 only mode.
+          This switch may not be used with <option>-4</option> nor
+          <option>-a</option>.
+          By default, it listens on both IPv4 and IPv6 (if capable).
+        </para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>-a <replaceable>address</replaceable></option></term>
+
+        <listitem>
+          <para>The IPv4 or IPv6 address to listen on.
+            This switch may not be used with <option>-4</option> nor
+            <option>-6</option>.
+            The default is to listen on all addresses.
+            (This is a short term workaround. This argument may change.)   
+          </para>                      
+         </listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>-n</option></term>
+        <listitem><para>
+          Do not cache answers in memory.
+          The default is to use the cache for faster responses.
+	  The cache keeps the most recent 30,000 answers (positive
+	  and negative) in memory for 30 seconds (instead of querying
+	  the data source, such as SQLite3 database, each time).
+        </para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>-p <replaceable>number</replaceable></option></term>
+        <listitem><para>
+          The port number it listens on.
+          The default is 5300.</para>
+	  <note><simpara>The Y1 prototype runs on all interfaces
+	  and on this nonstandard port.</simpara></note>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>-u <replaceable>username</replaceable></option></term>
+        <listitem>
+	  <para>
+	    The user name of the <command>b10-recurse</command> daemon.
+	    If specified, the daemon changes the process owner to the
+	    specified user.
+	    The <replaceable>username</replaceable> must be either a
+	    valid numeric user ID or a valid user name.
+	    By default the daemon runs as the user who invokes it.
+	  </para>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>-v</option></term>
+        <listitem><para>
+          Enabled verbose mode. This enables diagnostic messages to
+          STDERR.
+        </para></listitem>
+      </varlistentry>
+
+    </variablelist>
+
+  </refsect1>
+
+  <refsect1>
+    <title>FILES</title>
+    <para>
+      None.
+    </para>
+<!-- TODO: this is not correct yet. -->
+  </refsect1>
+
+  <refsect1>
+    <title>SEE ALSO</title>
+    <para>
+      <citerefentry>
+        <refentrytitle>b10-cfgmgr</refentrytitle><manvolnum>8</manvolnum>
+      </citerefentry>,
+      <citerefentry>
+        <refentrytitle>b10-cmdctl</refentrytitle><manvolnum>8</manvolnum>
+      </citerefentry>,
+      <citerefentry>
+        <refentrytitle>b10-loadzone</refentrytitle><manvolnum>8</manvolnum>
+      </citerefentry>,
+      <citerefentry>
+        <refentrytitle>b10-msgq</refentrytitle><manvolnum>8</manvolnum>
+      </citerefentry>,
+      <citerefentry>
+        <refentrytitle>bind10</refentrytitle><manvolnum>8</manvolnum>
+      </citerefentry>,
+      <citetitle>BIND 10 Guide</citetitle>.
+    </para>
+  </refsect1>
+
+  <refsect1>
+    <title>HISTORY</title>
+    <para>
+      The <command>b10-recurse</command> daemon was first coded in
+      September 2010.
+    </para>
+  </refsect1>
+</refentry><!--
+ - Local variables:
+ - mode: sgml
+ - End:
+-->

+ 55 - 0
src/bin/recurse/change_user.cc

@@ -0,0 +1,55 @@
+// Copyright (C) 2010  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+// $Id$
+
+#include <errno.h>
+#include <string.h>
+#include <pwd.h>
+#include <unistd.h>
+
+#include <boost/lexical_cast.hpp>
+
+#include <exceptions/exceptions.h>
+
+#include <auth/common.h>
+
+using namespace boost;
+
+void
+changeUser(const char* const username) {
+    const struct passwd *runas_pw = NULL;
+
+    runas_pw = getpwnam(username);
+    endpwent();
+    if (runas_pw == NULL) {
+        try {
+            runas_pw = getpwuid(lexical_cast<uid_t>(username));
+            endpwent();
+        } catch (const bad_lexical_cast&) {
+            ;                   // fall through to isc_throw below.
+        }
+    }
+    if (runas_pw == NULL) {
+        isc_throw(FatalError, "Unknown user name or UID:" << username);
+    }
+
+    if (setgid(runas_pw->pw_gid) < 0) {
+        isc_throw(FatalError, "setgid() failed: " << strerror(errno));
+    }
+
+    if (setuid(runas_pw->pw_uid) < 0) {
+        isc_throw(FatalError, "setuid() failed: " << strerror(errno));
+    }
+}

+ 59 - 0
src/bin/recurse/change_user.h

@@ -0,0 +1,59 @@
+// Copyright (C) 2010  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+// $Id$
+
+#ifndef __CHANGE_USER_H
+#define __CHANGE_USER_H 1
+
+/// \brief Change the run time user.
+///
+/// This function changes the user and its group of the authoritative server
+/// process.
+///
+/// On success the user ID of the process is changed to the specified user,
+/// and the group is changed to that of the new user.
+///
+/// This is considered a short term workaround until we develop clearer
+/// privilege separation, where the server won't even have to open privileged
+/// ports and can be started by a non privileged user from the beginning.
+/// This function therefore ignores some corner case problems (see below)
+/// which we would address otherwise.
+///
+/// \c username can be either a textual user name or its numeric ID.
+/// If the specified user name (or ID) doesn't specify a local user ID
+/// or the user originally starting the process doesn't have a permission
+/// of changing the user to \c username, this function throws an exception
+/// of class \c FatalError.
+///
+/// This function internally uses system libraries that do not guarantee
+/// reentrancy.  In fact, it doesn't even expect to be called more than once.
+/// The behavior is undefined if this function is called from multiple threads
+/// simultaneously or more generally called multiple times.
+///
+/// This function only offers the basic exception guarantee, that is, if
+/// an exception is thrown from this function, it's possible that an exception
+/// is thrown after changing the group ID.  This function doesn't recover
+/// from that situation.  In practice, the process is expected to consider
+/// this event a fatal error and will immediately exit, and shouldn't cause
+/// a real trouble.
+///
+/// \param username User name or ID of the new effective user.
+void changeUser(const char* const username);
+
+#endif // __CHANGE_USER_H
+
+// Local Variables:
+// mode: c++
+// End:

+ 32 - 0
src/bin/recurse/common.h

@@ -0,0 +1,32 @@
+// Copyright (C) 2009  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+// $Id$
+
+#ifndef __COMMON_H
+#define __COMMON_H 1
+
+#include <exceptions/exceptions.h>
+
+class FatalError : public isc::Exception {
+public:
+    FatalError(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) {}
+};
+
+#endif // __COMMON_H
+
+// Local Variables:
+// mode: c++
+// End:

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

@@ -0,0 +1,214 @@
+// Copyright (C) 2009  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+// $Id$
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/select.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <stdlib.h>
+#include <errno.h>
+
+#include <cassert>
+#include <iostream>
+
+#include <boost/foreach.hpp>
+
+#include <asiolink/asiolink.h>
+
+#include <exceptions/exceptions.h>
+
+#include <dns/buffer.h>
+#include <dns/message.h>
+#include <dns/messagerenderer.h>
+
+#include <cc/session.h>
+#include <cc/data.h>
+#include <config/ccsession.h>
+
+#include <xfr/xfrout_client.h>
+
+#include <recurse/spec_config.h>
+#include <recurse/common.h>
+#include <recurse/change_user.h>
+#include <recurse/recursor.h>
+
+using namespace std;
+using namespace isc::data;
+using namespace isc::cc;
+using namespace isc::config;
+using namespace isc::dns;
+using namespace isc::xfr;
+using namespace asiolink;
+
+namespace {
+
+bool verbose_mode = false;
+
+const string PROGRAM = "Recurse";
+const char* DNSPORT = "5300";
+
+Recursor *recursor;
+IOService* io_service;
+
+ConstElementPtr
+my_config_handler(ConstElementPtr new_config) {
+    return (recursor->updateConfig(new_config));
+}
+
+ConstElementPtr
+my_command_handler(const string& command, ConstElementPtr args) {
+    ConstElementPtr answer = createAnswer();
+
+    if (command == "print_message") {
+        cout << args << endl;
+        /* let's add that message to our answer as well */
+        answer = createAnswer(0, args);
+    } else if (command == "shutdown") {
+        io_service->stop();
+    }
+    
+    return (answer);
+}
+
+void
+usage() {
+    cerr << "Usage: b10-recurse [-a address] [-p port] [-4|-6] [-nv]" << endl;
+    exit(1);
+}
+} // end of anonymous namespace
+
+int
+main(int argc, char* argv[]) {
+    int ch;
+    const char* port = DNSPORT;
+    const char* address = NULL;
+    const char* uid = NULL;
+    bool use_ipv4 = true, use_ipv6 = true, cache = true;
+
+    while ((ch = getopt(argc, argv, "46a:np:u:v")) != -1) {
+        switch (ch) {
+        case '4':
+            // Note that -4 means "ipv4 only", we need to set "use_ipv6" here,
+            // not "use_ipv4".  We could use something like "ipv4_only", but
+            // we found the negatively named variable could confuse the code
+            // logic.
+            use_ipv6 = false;
+            break;
+        case '6':
+            // The same note as -4 applies.
+            use_ipv4 = false;
+            break;
+        case 'n':
+            cache = false;
+            break;
+        case 'a':
+            address = optarg;
+            break;
+        case 'p':
+            port = optarg;
+            break;
+        case 'u':
+            uid = optarg;
+            break;
+        case 'v':
+            verbose_mode = true;
+            break;
+        case '?':
+        default:
+            usage();
+        }
+    }
+
+    if (argc - optind > 0) {
+        usage();
+    }
+
+    if (!use_ipv4 && !use_ipv6) {
+        cerr << "[b10-recurse] Error: -4 and -6 can't coexist" << endl;
+        usage();
+    }
+
+    if ((!use_ipv4 || !use_ipv6) && address != NULL) {
+        cerr << "[b10-recurse] Error: -4|-6 and -a can't coexist" << endl;
+        usage();
+    }
+
+    int ret = 0;
+
+    // XXX: we should eventually pass io_service here.
+    Session* cc_session = NULL;
+    ModuleCCSession* config_session = NULL;
+    try {
+        string specfile;
+        if (getenv("B10_FROM_BUILD")) {
+            specfile = string(getenv("B10_FROM_BUILD")) +
+                "/src/bin/recurse/recurse.spec";
+        } else {
+            specfile = string(RECURSE_SPECFILE_LOCATION);
+        }
+
+        recursor = new Recursor();
+        recursor ->setVerbose(verbose_mode);
+        cout << "[b10-recurse] Server created." << endl;
+
+        CheckinProvider* checkin = recursor->getCheckinProvider();
+        DNSProvider* process = recursor->getDNSProvider();
+
+        if (address != NULL) {
+            // XXX: we can only specify at most one explicit address.
+            // This also means the server cannot run in the dual address
+            // family mode if explicit addresses need to be specified.
+            // We don't bother to fix this problem, however.  The -a option
+            // is a short term workaround until we support dynamic listening
+            // port allocation.
+            io_service = new IOService(*port, *address,
+                                                 checkin, process);
+        } else {
+            io_service = new IOService(*port, use_ipv4, use_ipv6,
+                                                 checkin, process);
+        }
+        cout << "[b10-recurse] IOService created." << endl;
+
+        cc_session = new Session(io_service->get_io_service());
+        cout << "[b10-recurse] Configuration session channel created." << endl;
+
+        config_session = new ModuleCCSession(specfile, *cc_session,
+                                             my_config_handler,
+                                             my_command_handler);
+        cout << "[b10-recurse] Configuration channel established." << endl;
+
+        if (uid != NULL) {
+            changeUser(uid);
+        }
+
+        recursor->setConfigSession(config_session);
+        recursor->updateConfig(ElementPtr());
+
+        cout << "[b10-recurse] Server started." << endl;
+        io_service->run();
+    } catch (const std::exception& ex) {
+        cerr << "[b10-recurse] Server failed: " << ex.what() << endl;
+        ret = 1;
+    }
+
+    delete config_session;
+    delete cc_session;
+    delete io_service;
+    delete recursor;
+
+    return (ret);
+}

+ 21 - 0
src/bin/recurse/recurse.spec.pre.in

@@ -0,0 +1,21 @@
+{
+  "module_spec": {
+    "module_name": "Auth",
+    "module_description": "Authoritative service",
+    "config_data": [
+      { "item_name": "database_file",
+        "item_type": "string",
+        "item_optional": true,
+        "item_default": "@@LOCALSTATEDIR@@/@PACKAGE@/zone.sqlite3"
+      }
+    ],
+    "commands": [
+      {
+        "command_name": "shutdown",
+        "command_description": "Shut down authoritative DNS server",
+        "command_args": []
+      }
+    ]
+  }
+}
+

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

@@ -0,0 +1,328 @@
+// Copyright (C) 2009  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+// $Id$
+
+#include <config.h>
+
+#include <netinet/in.h>
+
+#include <algorithm>
+#include <cassert>
+#include <iostream>
+#include <vector>
+
+#include <asiolink/asiolink.h>
+
+#include <config/ccsession.h>
+
+#include <cc/data.h>
+
+#include <exceptions/exceptions.h>
+
+#include <dns/buffer.h>
+#include <dns/exceptions.h>
+#include <dns/messagerenderer.h>
+#include <dns/name.h>
+#include <dns/question.h>
+#include <dns/rrset.h>
+#include <dns/rrttl.h>
+#include <dns/message.h>
+
+#include <xfr/xfrout_client.h>
+
+#include <recurse/recursor.h>
+
+using namespace std;
+
+using namespace isc;
+using namespace isc::cc;
+using namespace isc::dns;
+using namespace isc::dns::rdata;
+using namespace isc::data;
+using namespace isc::config;
+using namespace isc::xfr;
+using namespace asiolink;
+
+class RecursorImpl {
+private:
+    // prohibit copy
+    RecursorImpl(const RecursorImpl& source);
+    RecursorImpl& operator=(const RecursorImpl& source);
+public:
+    RecursorImpl();
+    bool processNormalQuery(const IOMessage& io_message, Message& message,
+                            MessageRenderer& response_renderer);
+    ModuleCCSession* config_session_;
+
+    bool verbose_mode_;
+
+    /// Currently non-configurable, but will be.
+    static const uint16_t DEFAULT_LOCAL_UDPSIZE = 4096;
+};
+
+RecursorImpl::RecursorImpl() : config_session_(NULL), verbose_mode_(false) {}
+
+// This is a derived class of \c DNSProvider, to serve as a
+// callback in the asiolink module.  It calls
+// Recursor::processMessage() on a single DNS message.
+class MessageProcessor : public DNSProvider {
+public:
+    MessageProcessor(Recursor* srv) : server_(srv) {}
+    virtual bool operator()(const IOMessage& io_message,
+                            isc::dns::Message& dns_message,
+                            isc::dns::MessageRenderer& renderer) const {
+        return (server_->processMessage(io_message, dns_message, renderer));
+    }
+private:
+    Recursor* server_;
+};
+
+// This is a derived class of \c CheckinProvider, to serve
+// as a callback in the asiolink module.  It checks for queued
+// configuration messages, and executes them if found.
+class ConfigChecker : public CheckinProvider {
+public:
+    ConfigChecker(Recursor* srv) : server_(srv) {}
+    virtual void operator()(void) const {
+        if (server_->configSession()->hasQueuedMsgs()) {
+            server_->configSession()->checkCommand();
+        }
+    }
+private:
+    Recursor* server_;
+};
+
+Recursor::Recursor() :
+    impl_(new RecursorImpl()),
+    checkin_provider_(new ConfigChecker(this)),
+    dns_provider_(new MessageProcessor(this))
+{}
+
+Recursor::~Recursor() {
+    delete impl_;
+    delete checkin_provider_;
+    delete dns_provider_;
+}
+
+namespace {
+class QuestionInserter {
+public:
+    QuestionInserter(Message* message) : message_(message) {}
+    void operator()(const QuestionPtr question) {
+        message_->addQuestion(question);
+    }
+    Message* message_;
+};
+
+void
+makeErrorMessage(Message& message, MessageRenderer& renderer,
+                 const Rcode& rcode, const bool verbose_mode)
+{
+    // extract the parameters that should be kept.
+    // XXX: with the current implementation, it's not easy to set EDNS0
+    // depending on whether the query had it.  So we'll simply omit it.
+    const qid_t qid = message.getQid();
+    const bool rd = message.getHeaderFlag(MessageFlag::RD());
+    const bool cd = message.getHeaderFlag(MessageFlag::CD());
+    const Opcode& opcode = message.getOpcode();
+    vector<QuestionPtr> questions;
+
+    // If this is an error to a query or notify, we should also copy the
+    // question section.
+    if (opcode == Opcode::QUERY() || opcode == Opcode::NOTIFY()) {
+        questions.assign(message.beginQuestion(), message.endQuestion());
+    }
+
+    message.clear(Message::RENDER);
+    message.setQid(qid);
+    message.setOpcode(opcode);
+    message.setHeaderFlag(MessageFlag::QR());
+    message.setUDPSize(RecursorImpl::DEFAULT_LOCAL_UDPSIZE);
+    if (rd) {
+        message.setHeaderFlag(MessageFlag::RD());
+    }
+    if (cd) {
+        message.setHeaderFlag(MessageFlag::CD());
+    }
+    for_each(questions.begin(), questions.end(), QuestionInserter(&message));
+    message.setRcode(rcode);
+    message.toWire(renderer);
+
+    if (verbose_mode) {
+        cerr << "[b10-recurse] sending an error response (" <<
+            renderer.getLength() << " bytes):\n" << message.toText() << endl;
+    }
+}
+}
+
+void
+Recursor::setVerbose(const bool on) {
+    impl_->verbose_mode_ = on;
+}
+
+bool
+Recursor::getVerbose() const {
+    return (impl_->verbose_mode_);
+}
+
+void
+Recursor::setConfigSession(ModuleCCSession* config_session) {
+    impl_->config_session_ = config_session;
+}
+
+ModuleCCSession*
+Recursor::configSession() const {
+    return (impl_->config_session_);
+}
+
+bool
+Recursor::processMessage(const IOMessage& io_message, Message& message,
+                        MessageRenderer& response_renderer)
+{
+    InputBuffer request_buffer(io_message.getData(), io_message.getDataSize());
+
+    // First, check the header part.  If we fail even for the base header,
+    // just drop the message.
+    try {
+        message.parseHeader(request_buffer);
+
+        // Ignore all responses.
+        if (message.getHeaderFlag(MessageFlag::QR())) {
+            if (impl_->verbose_mode_) {
+                cerr << "[b10-recurse] received unexpected response, ignoring"
+                     << endl;
+            }
+            return (false);
+        }
+    } catch (const Exception& ex) {
+        return (false);
+    }
+
+    // Parse the message.  On failure, return an appropriate error.
+    try {
+        message.fromWire(request_buffer);
+    } catch (const DNSProtocolError& error) {
+        if (impl_->verbose_mode_) {
+            cerr << "[b10-recurse] returning " <<  error.getRcode().toText()
+                 << ": " << error.what() << endl;
+        }
+        makeErrorMessage(message, response_renderer, error.getRcode(),
+                         impl_->verbose_mode_);
+        return (true);
+    } catch (const Exception& ex) {
+        if (impl_->verbose_mode_) {
+            cerr << "[b10-recurse] returning SERVFAIL: " << ex.what() << endl;
+        }
+        makeErrorMessage(message, response_renderer, Rcode::SERVFAIL(),
+                         impl_->verbose_mode_);
+        return (true);
+    } // other exceptions will be handled at a higher layer.
+
+    if (impl_->verbose_mode_) {
+        cerr << "[b10-recurse] received a message:\n"
+             << message.toText() << endl;
+    }
+
+    // Perform further protocol-level validation.
+    if (message.getOpcode() == Opcode::NOTIFY()) {
+        makeErrorMessage(message, response_renderer, Rcode::NOTAUTH(),
+                         impl_->verbose_mode_);
+        return (true);
+    } else if (message.getOpcode() != Opcode::QUERY()) {
+        if (impl_->verbose_mode_) {
+            cerr << "[b10-recurse] unsupported opcode" << endl;
+        }
+        makeErrorMessage(message, response_renderer, Rcode::NOTIMP(),
+                         impl_->verbose_mode_);
+        return (true);
+    }
+
+    if (message.getRRCount(Section::QUESTION()) != 1) {
+        makeErrorMessage(message, response_renderer, Rcode::FORMERR(),
+                         impl_->verbose_mode_);
+        return (true);
+    }
+
+    ConstQuestionPtr question = *message.beginQuestion();
+    const RRType &qtype = question->getType();
+    if (qtype == RRType::AXFR()) {
+        if (io_message.getSocket().getProtocol() == IPPROTO_UDP) {
+            makeErrorMessage(message, response_renderer, Rcode::FORMERR(),
+                             impl_->verbose_mode_);
+        } else {
+            makeErrorMessage(message, response_renderer, Rcode::NOTIMP(),
+                             impl_->verbose_mode_);
+        }
+        return (true);
+    } else if (qtype == RRType::IXFR()) {
+        makeErrorMessage(message, response_renderer, Rcode::NOTIMP(),
+                         impl_->verbose_mode_);
+        return (true);
+    } else {
+        return (impl_->processNormalQuery(io_message, message,
+                                          response_renderer));
+    }
+}
+
+bool
+RecursorImpl::processNormalQuery(const IOMessage& io_message, Message& message,
+                                MessageRenderer& response_renderer)
+{
+    const bool dnssec_ok = message.isDNSSECSupported();
+    const uint16_t remote_bufsize = message.getUDPSize();
+
+    message.makeResponse();
+    message.setRcode(Rcode::NOERROR());
+    message.setDNSSECSupported(dnssec_ok);
+    message.setUDPSize(RecursorImpl::DEFAULT_LOCAL_UDPSIZE);
+
+    try {
+        // HERE: initiate forward query, construct a reply
+    } catch (const Exception& ex) {
+        if (verbose_mode_) {
+            cerr << "[b10-recurse] Internal error, returning SERVFAIL: " <<
+                ex.what() << endl;
+        }
+        makeErrorMessage(message, response_renderer, Rcode::SERVFAIL(),
+                         verbose_mode_);
+        return (true);
+    }
+
+    const bool udp_buffer =
+        (io_message.getSocket().getProtocol() == IPPROTO_UDP);
+    response_renderer.setLengthLimit(udp_buffer ? remote_bufsize : 65535);
+    message.toWire(response_renderer);
+    if (verbose_mode_) {
+        cerr << "[b10-recurse] sending a response ("
+             << response_renderer.getLength()
+             << " bytes):\n" << message.toText() << endl;
+    }
+
+    return (true);
+}
+
+ConstElementPtr
+Recursor::updateConfig(ConstElementPtr new_config UNUSED_PARAM) {
+    try {
+        // We will do configuration updates here.  None are presently
+        // defined, so we just return an empty answer.
+        return (isc::config::createAnswer());
+    } catch (const isc::Exception& error) {
+        if (impl_->verbose_mode_) {
+            cerr << "[b10-recurse] error: " << error.what() << endl;
+        }
+        return (isc::config::createAnswer(1, error.what()));
+    }
+}

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

@@ -0,0 +1,90 @@
+// Copyright (C) 2009  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+// $Id$
+
+#ifndef __RECURSOR_H
+#define __RECURSOR_H 1
+
+#include <string>
+
+#include <cc/data.h>
+#include <config/ccsession.h>
+
+#include <asiolink/asiolink.h>
+
+namespace isc {
+namespace dns {
+class InputBuffer;
+class Message;
+class MessageRenderer;
+}
+}
+
+namespace asiolink {
+class IOMessage;
+}
+
+class RecursorImpl;
+
+class Recursor {
+    ///
+    /// \name Constructors, Assignment Operator and Destructor.
+    ///
+    /// Note: The copy constructor and the assignment operator are
+    /// intentionally defined as private.
+    //@{
+private:
+    Recursor(const Recursor& source);
+    Recursor& operator=(const Recursor& source);
+public:
+    /// The constructor.
+    ///
+    /// \param use_cache Whether to enable hot spot cache for lookup results.
+    /// \param xfrout_client Communication interface with a separate xfrout
+    /// process.  It's normally a reference to an xfr::XfroutClient object,
+    /// but can refer to a local mock object for testing (or other
+    /// experimental) purposes.
+    Recursor();
+    ~Recursor();
+    //@}
+    /// \return \c true if the \message contains a response to be returned;
+    /// otherwise \c false.
+    bool processMessage(const asiolink::IOMessage& io_message,
+                        isc::dns::Message& message,
+                        isc::dns::MessageRenderer& response_renderer);
+    void setVerbose(bool on);
+    bool getVerbose() const;
+    isc::data::ConstElementPtr updateConfig(isc::data::ConstElementPtr config);
+    isc::config::ModuleCCSession* configSession() const;
+    void setConfigSession(isc::config::ModuleCCSession* config_session);
+
+    asiolink::DNSProvider* getDNSProvider() {
+        return (dns_provider_);
+    }
+    asiolink::CheckinProvider* getCheckinProvider() {
+        return (checkin_provider_);
+    }
+
+private:
+    RecursorImpl* impl_;
+    asiolink::CheckinProvider* checkin_provider_;
+    asiolink::DNSProvider* dns_provider_;
+};
+
+#endif // __RECURSOR_H
+
+// Local Variables: 
+// mode: c++
+// End: 

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

@@ -0,0 +1,15 @@
+// Copyright (C) 2009  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#define RECURSE_SPECFILE_LOCATION "@prefix@/share/@PACKAGE@/auth.spec"

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

@@ -0,0 +1,57 @@
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
+AM_CPPFLAGS += -I$(top_builddir)/src/lib/dns -I$(top_srcdir)/src/bin
+AM_CPPFLAGS += -I$(top_builddir)/src/lib/cc
+AM_CPPFLAGS += -DTEST_DATA_DIR=\"$(srcdir)/testdata\"
+
+AM_CXXFLAGS = $(B10_CXXFLAGS)
+
+if USE_STATIC_LINK
+AM_LDFLAGS = -static
+endif
+
+CLEANFILES = *.gcno *.gcda
+
+TESTS =
+if HAVE_GTEST
+TESTS += run_unittests
+run_unittests_SOURCES = $(top_srcdir)/src/lib/dns/tests/unittest_util.h
+run_unittests_SOURCES += $(top_srcdir)/src/lib/dns/tests/unittest_util.cc
+run_unittests_SOURCES += ../recursor.h ../recursor.cc
+run_unittests_SOURCES += recursor_unittest.cc
+run_unittests_SOURCES += run_unittests.cc
+run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
+run_unittests_LDADD = $(GTEST_LDADD)
+run_unittests_LDADD += $(SQLITE_LIBS)
+run_unittests_LDADD +=  $(top_builddir)/src/lib/datasrc/libdatasrc.la
+run_unittests_LDADD +=  $(top_builddir)/src/lib/dns/libdns++.la
+run_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libasiolink.la
+run_unittests_LDADD += $(top_builddir)/src/lib/config/libcfgclient.la
+run_unittests_LDADD += $(top_builddir)/src/lib/cc/libcc.la
+run_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
+run_unittests_LDADD += $(top_builddir)/src/lib/xfr/libxfr.la
+endif
+
+noinst_PROGRAMS = $(TESTS)
+
+EXTRA_DIST =  testdata/badExampleQuery_fromWire
+EXTRA_DIST += testdata/badExampleQuery_fromWire.spec
+EXTRA_DIST += testdata/example.com
+EXTRA_DIST += testdata/examplequery_fromWire
+EXTRA_DIST += testdata/examplequery_fromWire.spec
+EXTRA_DIST += testdata/example.sqlite3
+EXTRA_DIST += testdata/iqueryresponse_fromWire
+EXTRA_DIST += testdata/iqueryresponse_fromWire.spec
+EXTRA_DIST += testdata/multiquestion_fromWire
+EXTRA_DIST += testdata/multiquestion_fromWire.spec
+EXTRA_DIST += testdata/queryBadEDNS_fromWire
+EXTRA_DIST += testdata/queryBadEDNS_fromWire.spec
+EXTRA_DIST += testdata/shortanswer_fromWire
+EXTRA_DIST += testdata/shortanswer_fromWire.spec
+EXTRA_DIST += testdata/shortmessage_fromWire
+EXTRA_DIST += testdata/shortquestion_fromWire
+EXTRA_DIST += testdata/shortresponse_fromWire
+EXTRA_DIST += testdata/simplequery_fromWire
+EXTRA_DIST += testdata/simplequery_fromWire.spec
+EXTRA_DIST += testdata/simpleresponse_fromWire
+EXTRA_DIST += testdata/simpleresponse_fromWire.spec

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

@@ -0,0 +1,475 @@
+// Copyright (C) 2010  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+// $Id$
+
+#include <config.h>
+
+#include <gtest/gtest.h>
+
+#include <asiolink/asiolink.h>
+
+#include <dns/buffer.h>
+#include <dns/name.h>
+#include <dns/message.h>
+#include <dns/messagerenderer.h>
+#include <dns/rrclass.h>
+#include <dns/rrtype.h>
+
+#include <cc/data.h>
+#include <cc/session.h>
+
+#include <recurse/recursor.h>
+#include <recurse/common.h>
+
+#include <dns/tests/unittest_util.h>
+
+using isc::UnitTestUtil;
+using namespace std;
+using namespace isc::cc;
+using namespace isc::dns;
+using namespace isc::data;
+using namespace asiolink;
+
+namespace {
+const char* const DEFAULT_REMOTE_ADDRESS = "192.0.2.1";
+
+class RecursorTest : public ::testing::Test {
+private:
+    class MockSession : public AbstractSession {
+    public:
+        MockSession() :
+            // by default we return a simple "success" message.
+            msg_(Element::fromJSON("{\"result\": [0, \"SUCCESS\"]}")),
+            send_ok_(true), receive_ok_(true)
+        {}
+        virtual void establish(const char* socket_file);
+        virtual void disconnect();
+        virtual int group_sendmsg(ConstElementPtr msg, string group,
+                                  string instance, string to);
+        virtual bool group_recvmsg(ConstElementPtr& envelope,
+                                   ConstElementPtr& msg,
+                                   bool nonblock, int seq);
+        virtual void subscribe(string group, string instance);
+        virtual void unsubscribe(string group, string instance);
+        virtual void startRead(boost::function<void()> read_callback);
+        virtual int reply(ConstElementPtr envelope, ConstElementPtr newmsg);
+        virtual bool hasQueuedMsgs() const;
+        virtual void setTimeout(size_t timeout UNUSED_PARAM) {};
+        virtual size_t getTimeout() const { return 0; };
+
+        void setMessage(ConstElementPtr msg) { msg_ = msg; }
+        void disableSend() { send_ok_ = false; }
+        void disableReceive() { receive_ok_ = false; }
+
+        ConstElementPtr sent_msg;
+        string msg_destination;
+    private:
+        ConstElementPtr msg_;
+        bool send_ok_;
+        bool receive_ok_;
+    };
+
+protected:
+    RecursorTest() : server(),
+                    request_message(Message::RENDER),
+                    parse_message(Message::PARSE), default_qid(0x1035),
+                    opcode(Opcode(Opcode::QUERY())), qname("www.example.com"),
+                    qclass(RRClass::IN()), qtype(RRType::A()),
+                    io_message(NULL), endpoint(NULL), request_obuffer(0),
+                    request_renderer(request_obuffer),
+                    response_obuffer(0), response_renderer(response_obuffer)
+    {}
+    ~RecursorTest() {
+        delete io_message;
+        delete endpoint;
+    }
+    MockSession notify_session;
+    Recursor server;
+    Message request_message;
+    Message parse_message;
+    const qid_t default_qid;
+    const Opcode opcode;
+    const Name qname;
+    const RRClass qclass;
+    const RRType qtype;
+    IOMessage* io_message;
+    const IOEndpoint* endpoint;
+    OutputBuffer request_obuffer;
+    MessageRenderer request_renderer;
+    OutputBuffer response_obuffer;
+    MessageRenderer response_renderer;
+    vector<uint8_t> data;
+
+    void createDataFromFile(const char* const datafile, int protocol);
+    void createRequestMessage(const Opcode& opcode, const Name& request_name,
+                              const RRClass& rrclass, const RRType& rrtype);
+    void createRequestPacket(const Opcode& opcode, const Name& request_name,
+                             const RRClass& rrclass, const RRType& rrtype,
+                             int protocol);
+    void createRequestPacket(int protocol);
+};
+
+void
+RecursorTest::MockSession::establish(const char* socket_file UNUSED_PARAM) {}
+
+void
+RecursorTest::MockSession::disconnect() {}
+
+void
+RecursorTest::MockSession::subscribe(string group UNUSED_PARAM,
+                                    string instance UNUSED_PARAM)
+{}
+
+void
+RecursorTest::MockSession::unsubscribe(string group UNUSED_PARAM,
+                                      string instance UNUSED_PARAM)
+{}
+
+void
+RecursorTest::MockSession::startRead(
+    boost::function<void()> read_callback UNUSED_PARAM)
+{}
+
+int
+RecursorTest::MockSession::reply(ConstElementPtr envelope UNUSED_PARAM,
+                                ConstElementPtr newmsg UNUSED_PARAM)
+{
+    return (-1);
+}
+
+bool
+RecursorTest::MockSession::hasQueuedMsgs() const {
+    return (false);
+}
+
+int
+RecursorTest::MockSession::group_sendmsg(ConstElementPtr msg, string group,
+                                        string instance UNUSED_PARAM,
+                                        string to UNUSED_PARAM)
+{
+    if (!send_ok_) {
+        isc_throw(FatalError, "mock session send is disabled for test");
+    }
+
+    sent_msg = msg;
+    msg_destination = group;
+    return (0);
+}
+
+bool
+RecursorTest::MockSession::group_recvmsg(ConstElementPtr& envelope UNUSED_PARAM,
+                                        ConstElementPtr& msg,
+                                        bool nonblock UNUSED_PARAM,
+                                        int seq UNUSED_PARAM)
+{
+    if (!receive_ok_) {
+        isc_throw(FatalError, "mock session receive is disabled for test");
+    }
+
+    msg = msg_;
+    return (true);
+}
+
+// These are flags to indicate whether the corresponding flag bit of the
+// DNS header is to be set in the test cases.  (Note that the flag values
+// is irrelevant to their wire-format values)
+const unsigned int QR_FLAG = 0x1;
+const unsigned int AA_FLAG = 0x2;
+const unsigned int TC_FLAG = 0x4;
+const unsigned int RD_FLAG = 0x8;
+const unsigned int RA_FLAG = 0x10;
+const unsigned int AD_FLAG = 0x20;
+const unsigned int CD_FLAG = 0x40;
+
+void
+RecursorTest::createDataFromFile(const char* const datafile,
+                                const int protocol = IPPROTO_UDP)
+{
+    delete io_message;
+    data.clear();
+
+    delete endpoint;
+    endpoint = IOEndpoint::create(protocol,
+                                  IOAddress(DEFAULT_REMOTE_ADDRESS), 5300);
+    UnitTestUtil::readWireData(datafile, data);
+    io_message = new IOMessage(&data[0], data.size(),
+                               protocol == IPPROTO_UDP ?
+                               IOSocket::getDummyUDPSocket() :
+                               IOSocket::getDummyTCPSocket(), *endpoint);
+}
+
+void
+RecursorTest::createRequestMessage(const Opcode& opcode,
+                                  const Name& request_name,
+                                  const RRClass& rrclass,
+                                  const RRType& rrtype)
+{
+    request_message.clear(Message::RENDER);
+    request_message.setOpcode(opcode);
+    request_message.setQid(default_qid);
+    request_message.addQuestion(Question(request_name, rrclass, rrtype));
+}
+
+void
+RecursorTest::createRequestPacket(const Opcode& opcode,
+                                 const Name& request_name,
+                                 const RRClass& rrclass, const RRType& rrtype,
+                                 const int protocol = IPPROTO_UDP)
+{
+    createRequestMessage(opcode, request_name, rrclass, rrtype);
+    createRequestPacket(protocol);
+}
+
+void
+RecursorTest::createRequestPacket(const int protocol = IPPROTO_UDP) {
+    request_message.toWire(request_renderer);
+
+    delete io_message;
+    endpoint = IOEndpoint::create(protocol,
+                                  IOAddress(DEFAULT_REMOTE_ADDRESS), 5300);
+    io_message = new IOMessage(request_renderer.getData(),
+                               request_renderer.getLength(),
+                               protocol == IPPROTO_UDP ?
+                               IOSocket::getDummyUDPSocket() :
+                               IOSocket::getDummyTCPSocket(), *endpoint);
+}
+
+void
+headerCheck(const Message& message, const qid_t qid, const Rcode& rcode,
+            const uint16_t opcodeval, const unsigned int flags,
+            const unsigned int qdcount,
+            const unsigned int ancount, const unsigned int nscount,
+            const unsigned int arcount)
+{
+    EXPECT_EQ(qid, message.getQid());
+    EXPECT_EQ(rcode, message.getRcode());
+    EXPECT_EQ(opcodeval, message.getOpcode().getCode());
+    EXPECT_EQ((flags & QR_FLAG) != 0, message.getHeaderFlag(MessageFlag::QR()));
+    EXPECT_EQ((flags & AA_FLAG) != 0, message.getHeaderFlag(MessageFlag::AA()));
+    EXPECT_EQ((flags & TC_FLAG) != 0, message.getHeaderFlag(MessageFlag::TC()));
+    EXPECT_EQ((flags & RA_FLAG) != 0, message.getHeaderFlag(MessageFlag::RA()));
+    EXPECT_EQ((flags & RD_FLAG) != 0, message.getHeaderFlag(MessageFlag::RD()));
+    EXPECT_EQ((flags & AD_FLAG) != 0, message.getHeaderFlag(MessageFlag::AD()));
+    EXPECT_EQ((flags & CD_FLAG) != 0, message.getHeaderFlag(MessageFlag::CD()));
+
+    EXPECT_EQ(qdcount, message.getRRCount(Section::QUESTION()));
+    EXPECT_EQ(ancount, message.getRRCount(Section::ANSWER()));
+    EXPECT_EQ(nscount, message.getRRCount(Section::AUTHORITY()));
+    EXPECT_EQ(arcount, message.getRRCount(Section::ADDITIONAL()));
+}
+
+// Unsupported requests.  Should result in NOTIMP.
+TEST_F(RecursorTest, unsupportedRequest) {
+    for (unsigned int i = 0; i < 16; ++i) {
+        // set Opcode to 'i', which iterators over all possible codes except
+        // the standard query and notify
+        if (i == Opcode::QUERY().getCode() ||
+            i == Opcode::NOTIFY().getCode()) {
+            continue;
+        }
+        createDataFromFile("simplequery_fromWire");
+        data[2] = ((i << 3) & 0xff);
+
+        parse_message.clear(Message::PARSE);
+        EXPECT_EQ(true, server.processMessage(*io_message, parse_message,
+                                              response_renderer));
+        headerCheck(parse_message, default_qid, Rcode::NOTIMP(), i, QR_FLAG,
+                    0, 0, 0, 0);
+    }
+}
+
+// Simple API check
+TEST_F(RecursorTest, verbose) {
+    EXPECT_FALSE(server.getVerbose());
+    server.setVerbose(true);
+    EXPECT_TRUE(server.getVerbose());
+    server.setVerbose(false);
+    EXPECT_FALSE(server.getVerbose());
+}
+
+// Multiple questions.  Should result in FORMERR.
+TEST_F(RecursorTest, multiQuestion) {
+    createDataFromFile("multiquestion_fromWire");
+    EXPECT_EQ(true, server.processMessage(*io_message, parse_message,
+                                          response_renderer));
+    headerCheck(parse_message, default_qid, Rcode::FORMERR(), opcode.getCode(),
+                QR_FLAG, 2, 0, 0, 0);
+
+    QuestionIterator qit = parse_message.beginQuestion();
+    EXPECT_EQ(Name("example.com"), (*qit)->getName());
+    EXPECT_EQ(RRClass::IN(), (*qit)->getClass());
+    EXPECT_EQ(RRType::A(), (*qit)->getType());
+    ++qit;
+    EXPECT_EQ(Name("example.com"), (*qit)->getName());
+    EXPECT_EQ(RRClass::IN(), (*qit)->getClass());
+    EXPECT_EQ(RRType::AAAA(), (*qit)->getType());
+    ++qit;
+    EXPECT_TRUE(qit == parse_message.endQuestion());
+}
+
+// Incoming data doesn't even contain the complete header.  Must be silently
+// dropped.
+TEST_F(RecursorTest, shortMessage) {
+    createDataFromFile("shortmessage_fromWire");
+    EXPECT_EQ(false, server.processMessage(*io_message, parse_message,
+                                           response_renderer));
+}
+
+// Response messages.  Must be silently dropped, whether it's a valid response
+// or malformed or could otherwise cause a protocol error.
+TEST_F(RecursorTest, response) {
+    // A valid (although unusual) response
+    createDataFromFile("simpleresponse_fromWire");
+    EXPECT_EQ(false, server.processMessage(*io_message, parse_message,
+                                           response_renderer));
+
+    // A response with a broken question section.  must be dropped rather than
+    // returning FORMERR.
+    createDataFromFile("shortresponse_fromWire");
+    EXPECT_EQ(false, server.processMessage(*io_message, parse_message,
+                                           response_renderer));
+
+    // A response to iquery.  must be dropped rather than returning NOTIMP.
+    createDataFromFile("iqueryresponse_fromWire");
+    EXPECT_EQ(false, server.processMessage(*io_message, parse_message,
+                                           response_renderer));
+}
+
+// Query with a broken question
+TEST_F(RecursorTest, shortQuestion) {
+    createDataFromFile("shortquestion_fromWire");
+    EXPECT_EQ(true, server.processMessage(*io_message, parse_message,
+                                          response_renderer));
+    // Since the query's question is broken, the question section of the
+    // response should be empty.
+    headerCheck(parse_message, default_qid, Rcode::FORMERR(), opcode.getCode(),
+                QR_FLAG, 0, 0, 0, 0);
+}
+
+// Query with a broken answer section
+TEST_F(RecursorTest, shortAnswer) {
+    createDataFromFile("shortanswer_fromWire");
+    EXPECT_EQ(true, server.processMessage(*io_message, parse_message,
+                                          response_renderer));
+
+    // This is a bogus query, but question section is valid.  So the response
+    // should copy the question section.
+    headerCheck(parse_message, default_qid, Rcode::FORMERR(), opcode.getCode(),
+                QR_FLAG, 1, 0, 0, 0);
+
+    QuestionIterator qit = parse_message.beginQuestion();
+    EXPECT_EQ(Name("example.com"), (*qit)->getName());
+    EXPECT_EQ(RRClass::IN(), (*qit)->getClass());
+    EXPECT_EQ(RRType::A(), (*qit)->getType());
+    ++qit;
+    EXPECT_TRUE(qit == parse_message.endQuestion());
+}
+
+// Query with unsupported version of EDNS.
+TEST_F(RecursorTest, ednsBadVers) {
+    createDataFromFile("queryBadEDNS_fromWire");
+    EXPECT_EQ(true, server.processMessage(*io_message, parse_message,
+                                          response_renderer));
+
+    // The response must have an EDNS OPT RR in the additional section.
+    // Note that the DNSSEC DO bit is cleared even if this bit in the query
+    // is set.  This is a limitation of the current implementation.
+    headerCheck(parse_message, default_qid, Rcode::BADVERS(), opcode.getCode(),
+                QR_FLAG, 1, 0, 0, 1);
+    EXPECT_EQ(4096, parse_message.getUDPSize());
+    EXPECT_FALSE(parse_message.isDNSSECSupported());
+}
+
+TEST_F(RecursorTest, AXFROverUDP) {
+    // AXFR over UDP is invalid and should result in FORMERR.
+    createRequestPacket(opcode, Name("example.com"), RRClass::IN(),
+                        RRType::AXFR(), IPPROTO_UDP);
+    EXPECT_EQ(true, server.processMessage(*io_message, parse_message,
+                                          response_renderer));
+    headerCheck(parse_message, default_qid, Rcode::FORMERR(), opcode.getCode(),
+                QR_FLAG, 1, 0, 0, 0);
+}
+
+TEST_F(RecursorTest, AXFRFail) {
+    createRequestPacket(opcode, Name("example.com"), RRClass::IN(),
+                        RRType::AXFR(), IPPROTO_TCP);
+    // AXFR is not implemented and should always send NOTIMP.
+    EXPECT_EQ(true, server.processMessage(*io_message, parse_message,
+                                           response_renderer));
+    headerCheck(parse_message, default_qid, Rcode::NOTIMP(), opcode.getCode(),
+                QR_FLAG, 1, 0, 0, 0);
+}
+
+TEST_F(RecursorTest, DISABLED_notify) {
+    createRequestMessage(Opcode::NOTIFY(), Name("example.com"), RRClass::IN(),
+                        RRType::SOA());
+    request_message.setHeaderFlag(MessageFlag::AA());
+    createRequestPacket(IPPROTO_UDP);
+    EXPECT_EQ(true, server.processMessage(*io_message, parse_message,
+                                          response_renderer));
+
+    // An internal command message should have been created and sent to an
+    // external module.  Check them.
+    EXPECT_EQ("Zonemgr", notify_session.msg_destination);
+    EXPECT_EQ("notify",
+              notify_session.sent_msg->get("command")->get(0)->stringValue());
+    ConstElementPtr notify_args =
+        notify_session.sent_msg->get("command")->get(1);
+    EXPECT_EQ("example.com.", notify_args->get("zone_name")->stringValue());
+    EXPECT_EQ(DEFAULT_REMOTE_ADDRESS,
+              notify_args->get("master")->stringValue());
+    EXPECT_EQ("IN", notify_args->get("zone_class")->stringValue());
+
+    // On success, the server should return a response to the notify.
+    headerCheck(parse_message, default_qid, Rcode::NOERROR(),
+                Opcode::NOTIFY().getCode(), QR_FLAG | AA_FLAG, 1, 0, 0, 0);
+
+    // The question must be identical to that of the received notify
+    ConstQuestionPtr question = *parse_message.beginQuestion();
+    EXPECT_EQ(Name("example.com"), question->getName());
+    EXPECT_EQ(RRClass::IN(), question->getClass());
+    EXPECT_EQ(RRType::SOA(), question->getType());
+}
+
+TEST_F(RecursorTest, notifyFail) {
+    // Notify should always return NOTAUTH
+    request_message.clear(Message::RENDER);
+    request_message.setOpcode(Opcode::NOTIFY());
+    request_message.setHeaderFlag(MessageFlag::AA());
+    request_message.setQid(default_qid);
+    request_message.toWire(request_renderer);
+    createRequestPacket(IPPROTO_UDP);
+    EXPECT_EQ(true, server.processMessage(*io_message, parse_message,
+                                          response_renderer));
+    headerCheck(parse_message, default_qid, Rcode::NOTAUTH(),
+                Opcode::NOTIFY().getCode(), QR_FLAG, 0, 0, 0, 0);
+}
+
+
+void
+updateConfig(Recursor* server, const char* const dbfile,
+             const bool expect_success)
+{
+    ConstElementPtr config_answer =
+        server->updateConfig(Element::fromJSON(dbfile));
+    EXPECT_EQ(Element::map, config_answer->getType());
+    EXPECT_TRUE(config_answer->contains("result"));
+
+    ConstElementPtr result = config_answer->get("result");
+    EXPECT_EQ(Element::list, result->getType());
+    EXPECT_EQ(expect_success ? 0 : 1, result->get(0)->intValue());
+}
+
+}

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

@@ -0,0 +1,28 @@
+// Copyright (C) 2009  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+// $Id$
+
+#include <gtest/gtest.h>
+
+#include <dns/tests/unittest_util.h>
+
+int
+main(int argc, char* argv[])
+{
+    ::testing::InitGoogleTest(&argc, argv);
+    isc::UnitTestUtil::addDataPath(TEST_DATA_DIR);
+
+    return (RUN_ALL_TESTS());
+}

+ 13 - 0
src/bin/recurse/tests/testdata/iqueryresponse_fromWire

@@ -0,0 +1,13 @@
+###
+### This data file was auto-generated from iqueryresponse_fromWire.spec
+###
+
+# Header Section
+# ID=4149 QR=Response Opcode=IQUERY(1) Rcode=NOERROR(0)
+1035 c000
+# QDCNT=1, ANCNT=0, NSCNT=0, ARCNT=0
+0001 0000 0000 0000
+
+# Question Section
+# QNAME=example.com. QTYPE=A(1) QCLASS=IN(1)
+076578616d706c6503636f6d00 0001 0001

+ 17 - 0
src/bin/recurse/tests/testdata/multiquestion_fromWire

@@ -0,0 +1,17 @@
+###
+### This data file was auto-generated from multiquestion_fromWire.spec
+###
+
+# Header Section
+# ID=4149 QR=Query Opcode=QUERY(0) Rcode=NOERROR(0)
+1035 0000
+# QDCNT=2, ANCNT=0, NSCNT=0, ARCNT=0
+0002 0000 0000 0000
+
+# Question Section
+# QNAME=example.com. QTYPE=A(1) QCLASS=IN(1)
+076578616d706c6503636f6d00 0001 0001
+
+# Question Section
+# QNAME=example.com. QTYPE=AAAA(28) QCLASS=IN(1)
+076578616d706c6503636f6d00 001c 0001

+ 19 - 0
src/bin/recurse/tests/testdata/queryBadEDNS_fromWire

@@ -0,0 +1,19 @@
+###
+### This data file was auto-generated from queryBadEDNS_fromWire.spec
+###
+
+# Header Section
+# ID=4149 QR=Query Opcode=QUERY(0) Rcode=NOERROR(0)
+1035 0000
+# QDCNT=1, ANCNT=0, NSCNT=0, ARCNT=1
+0001 0000 0000 0001
+
+# Question Section
+# QNAME=example.com. QTYPE=A(1) QCLASS=IN(1)
+076578616d706c6503636f6d00 0001 0001
+
+# EDNS OPT RR
+# NAME=. TYPE=OPT(41) UDPSize=4096 ExtRcode=0 Version=1 DO=1
+00 0029 1000 0001 8000
+# RDLEN=0
+0000

+ 13 - 0
src/bin/recurse/tests/testdata/shortanswer_fromWire

@@ -0,0 +1,13 @@
+###
+### This data file was auto-generated from shortanswer_fromWire.spec
+###
+
+# Header Section
+# ID=4149 QR=Query Opcode=QUERY(0) Rcode=NOERROR(0)
+1035 0000
+# QDCNT=1, ANCNT=1, NSCNT=0, ARCNT=0
+0001 0001 0000 0000
+
+# Question Section
+# QNAME=example.com. QTYPE=A(1) QCLASS=IN(1)
+076578616d706c6503636f6d00 0001 0001

+ 9 - 0
src/bin/recurse/tests/testdata/shortmessage_fromWire

@@ -0,0 +1,9 @@
+###
+### DNS message-like data but doesn't contain sufficient length of data.
+###
+
+# Header Section
+# ID=4149 QR=Query Opcode=QUERY(0) Rcode=NOERROR(0)
+1035 0000
+# QDCNT=1, ANCNT=0, NSCNT=0, (ARCNT is missing)
+0001 0000 0000

+ 13 - 0
src/bin/recurse/tests/testdata/shortquestion_fromWire

@@ -0,0 +1,13 @@
+###
+### A query-like data, but missing QCLASS field in the Question section.
+###
+
+# Header Section
+# ID=4149 QR=Query Opcode=QUERY(0) Rcode=NOERROR(0)
+1035 0000
+# QDCNT=1, ANCNT=0, NSCNT=0, ARCNT=0
+0001 0000 0000 0000
+
+# Question Section
+# QNAME=example.com. QTYPE=A(1) (QCLASS missing)
+076578616d706c6503636f6d00 0001

+ 13 - 0
src/bin/recurse/tests/testdata/shortresponse_fromWire

@@ -0,0 +1,13 @@
+###
+### A response-like data, but missing QCLASS field in the Question section.
+###
+
+# Header Section
+# ID=4149 QR=Response Opcode=QUERY(0) Rcode=NOERROR(0)
+1035 8000
+# QDCNT=1, ANCNT=0, NSCNT=0, ARCNT=0
+0001 0000 0000 0000
+
+# Question Section
+# QNAME=example.com. QTYPE=A(1) (QCLASS is missing)
+076578616d706c6503636f6d00 0001

+ 13 - 0
src/bin/recurse/tests/testdata/simplequery_fromWire

@@ -0,0 +1,13 @@
+###
+### This data file was auto-generated from simplequery_fromWire.spec
+###
+
+# Header Section
+# ID=4149 QR=Query Opcode=QUERY(0) Rcode=NOERROR(0)
+1035 0000
+# QDCNT=1, ANCNT=0, NSCNT=0, ARCNT=0
+0001 0000 0000 0000
+
+# Question Section
+# QNAME=example.com. QTYPE=A(1) QCLASS=IN(1)
+076578616d706c6503636f6d00 0001 0001

+ 13 - 0
src/bin/recurse/tests/testdata/simpleresponse_fromWire

@@ -0,0 +1,13 @@
+###
+### This data file was auto-generated from simpleresponse_fromWire.spec
+###
+
+# Header Section
+# ID=4149 QR=Response Opcode=QUERY(0) Rcode=NOERROR(0)
+1035 8000
+# QDCNT=1, ANCNT=0, NSCNT=0, ARCNT=0
+0001 0000 0000 0000
+
+# Question Section
+# QNAME=example.com. QTYPE=A(1) QCLASS=IN(1)
+076578616d706c6503636f6d00 0001 0001

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

@@ -430,7 +430,6 @@ protected:
     /// This is intentionally defined as \c protected as this base class
     /// should never be instantiated (except as part of a derived class).
     CheckinProvider() {}
-    //@}
 public:
     /// \brief The destructor
     virtual ~CheckinProvider() {}