Parcourir la source

[trac871] Merge branch 'master' into trac871
Conflicts:
src/lib/dns/tsig.cc

JINMEI Tatuya il y a 14 ans
Parent
commit
364be72653

+ 8 - 0
ChangeLog

@@ -1,3 +1,11 @@
+226.	[func]*		jelte
+	Introduced an API for cryptographic operations. Currently it only
+	supports HMAC, intended for use with TSIG. The current
+	implementation uses Botan as the backend library.
+	This introduces a new dependency, on Botan.  Currently only Botan
+	1.8.x works; older or newer versions don't.
+	(Trac#781, git 9df42279a47eb617f586144dce8cce680598558a)
+
 225.	[func]		naokikambe
 	Added the HTTP/XML interface(b10-stats-httpd) to the statistics feature
 	in BIND 10. b10-stats-httpd is a standalone HTTP server and it requests

+ 55 - 21
configure.ac

@@ -375,36 +375,68 @@ fi
 AC_SUBST(USE_LCOV)
 
 # Check for Botan
-botan_path=""
+botan_path="yes"
 AC_ARG_WITH([botan],
   AC_HELP_STRING([--with-botan=PATH],
     [specify exact directory of Botan library]),
     [botan_path="$withval"])
-# If not specificed, try some common paths
-if test -z "$with_botan"; then
-    botandirs="/usr/local /usr/pkg /opt /opt/local /usr"
-    for d in $botandirs
-    do
-        if test -f $d/include/botan/botan.h; then
-            botan_path=$d
-            break
-        fi
-    done
+if test "${botan_path}" == "no" ; then
+    AC_MSG_ERROR([Need botan for libcryptolink])
 fi
-if test "${botan_path}" ; then
-    CPPFLAGS="$CPPFLAGS -I${botan_path}/include"
-    LDFLAGS="$LDFLAGS -L${botan_path}/lib -lbotan"
+if test "${botan_path}" != "yes" ; then
+    if test -x "${botan_path}/bin/botan-config" ; then
+        BOTAN_CONFIG="${botan_path}/bin/botan-config"
+    else
+        AC_MSG_ERROR([${botan_path}/bin/botan-config not found])
+    fi
+else
+        AC_PATH_PROG([BOTAN_CONFIG], [botan-config])
+fi
+
+if test -x "${BOTAN_CONFIG}" ; then
+    BOTAN_LDFLAGS=`${BOTAN_CONFIG} --libs`
+    # We expect botan-config --libs to contain -L<path_to_libbotan>, but
+    # this is not always the case.  As a heuristics workaround we add
+    # -L`botan-config --prefix/lib` in this case.  Same for BOTAN_INCLUDES
+    # (but using include instead of lib) below.
+    echo ${BOTAN_LDFLAGS} | grep -- -L > /dev/null || \
+	    BOTAN_LDFLAGS="-L`${BOTAN_CONFIG} --prefix`/lib ${BOTAN_LDFLAGS}"
+    BOTAN_INCLUDES=`${BOTAN_CONFIG} --cflags`
+    echo ${BOTAN_INCLUDES} | grep -- -I > /dev/null || \
+	    BOTAN_INCLUDES="-I`${BOTAN_CONFIG} --prefix`/include ${BOTAN_INCLUDES}"
+    # See python_rpath for some info on why we do this
+    if test $rpath_available = yes; then
+        BOTAN_RPATH=
+        for flag in ${BOTAN_LDFLAGS}; do
+                BOTAN_RPATH="${BOTAN_RPATH} `echo $flag | sed -ne 's/^\(\-L\)/-R/p'`"
+        done
+        AC_SUBST(BOTAN_RPATH)
+    fi
+
+    AC_SUBST(BOTAN_LDFLAGS)
+    AC_SUBST(BOTAN_INCLUDES)
 fi
+
+CPPFLAGS_SAVED=$CPPFLAGS
+CPPFLAGS="$BOTAN_INCLUDES $CPPFLAGS"
+LDFLAGS_SAVED=$LDFLAGS
+LDFLAGS="$BOTAN_LDFLAGS $LDFLAGS"
+
 AC_CHECK_HEADERS([botan/botan.h],,AC_MSG_ERROR([Missing required header files.]))
 AC_LINK_IFELSE(
-        [AC_LANG_PROGRAM([#include <botan/botan.h>],
+        [AC_LANG_PROGRAM([#include <botan/botan.h>
+                          #include <botan/hash.h>
+                         ],
                          [using namespace Botan;
                           LibraryInitializer::initialize();
+                          HashFunction *h = get_hash("MD5");
                          ])],
         [AC_MSG_RESULT([checking for Botan library... yes])],
         [AC_MSG_RESULT([checking for Botan library... no])
-         AC_MSG_ERROR([Missing Botan library])]
+         AC_MSG_ERROR([Needs Botan library 1.8 or higher])]
 )
+CPPFLAGS=$CPPFLAGS_SAVED
+LDFLAGS=$LDFLAGS_SAVED
 
 #
 # Configure Boost header path
@@ -717,11 +749,6 @@ AC_CONFIG_FILES([Makefile
                  src/lib/exceptions/tests/Makefile
                  src/lib/datasrc/Makefile
                  src/lib/datasrc/tests/Makefile
-                 src/lib/util/Makefile
-                 src/lib/util/io/Makefile
-                 src/lib/util/io/tests/Makefile
-                 src/lib/util/unittests/Makefile
-                 src/lib/util/tests/Makefile
                  src/lib/xfr/Makefile
                  src/lib/log/Makefile
                  src/lib/log/compiler/Makefile
@@ -736,6 +763,11 @@ AC_CONFIG_FILES([Makefile
                  src/lib/cache/tests/Makefile
                  src/lib/server_common/Makefile
                  src/lib/server_common/tests/Makefile
+                 src/lib/util/Makefile
+                 src/lib/util/io/Makefile
+                 src/lib/util/io/tests/Makefile
+                 src/lib/util/unittests/Makefile
+                 src/lib/util/tests/Makefile
                  tests/Makefile
                  tests/system/Makefile
                  tests/tools/Makefile
@@ -858,6 +890,8 @@ dnl includes too
                  ${PYTHON_LDFLAGS}
                  ${PYTHON_LIB}
   Boost:         ${BOOST_INCLUDES}
+  Botan:         ${BOTAN_INCLUDES}
+                 ${BOTAN_LDFLAGS}
   SQLite:        $SQLITE_CFLAGS
                  $SQLITE_LIBS
 

+ 6 - 5
doc/Doxyfile

@@ -568,11 +568,12 @@ 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/cryptolink \
-    ../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 ../src/lib/cache \
-    ../src/lib/server_common/ ../src/bin/sockcreator/ ../src/lib/util/
+INPUT                  = ../src/lib/cc ../src/lib/config \
+    ../src/lib/cryptolink ../src/lib/dns ../src/lib/datasrc \
+    ../src/bin/auth ../src/bin/resolver ../src/lib/bench \
+    ../src/lib/log ../src/lib/asiolink/ ../src/lib/nsas \
+    ../src/lib/testutils ../src/lib/cache ../src/lib/server_common/ \
+    ../src/bin/sockcreator/ ../src/lib/util/
 
 # 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

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

@@ -18,7 +18,6 @@ CLEANFILES = *.gcno *.gcda
 TESTS =
 if HAVE_GTEST
 
-BUILT_SOURCES = ../spec_config.h
 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
@@ -28,7 +27,6 @@ run_unittests_SOURCES += ../change_user.h ../change_user.cc
 run_unittests_SOURCES += ../auth_config.h ../auth_config.cc
 run_unittests_SOURCES += ../command.h ../command.cc
 run_unittests_SOURCES += ../common.h ../common.cc
-run_unittests_SOURCES += ../spec_config.h
 run_unittests_SOURCES += ../statistics.h ../statistics.cc
 run_unittests_SOURCES += auth_srv_unittest.cc
 run_unittests_SOURCES += config_unittest.cc

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

@@ -23,14 +23,14 @@ BIND10_PATH=@abs_top_builddir@/src/bin/bind10
 PATH=@abs_top_builddir@/src/bin/msgq:@abs_top_builddir@/src/bin/auth:@abs_top_builddir@/src/bin/resolver:@abs_top_builddir@/src/bin/cfgmgr:@abs_top_builddir@/src/bin/cmdctl:@abs_top_builddir@/src/bin/stats:@abs_top_builddir@/src/bin/xfrin:@abs_top_builddir@/src/bin/xfrout:@abs_top_builddir@/src/bin/zonemgr:$PATH
 export PATH
 
-PYTHONPATH=@abs_top_builddir@/src/lib/python:@abs_top_builddir@/src/lib/dns/python/.libs:@abs_top_builddir@/src/lib/xfr/.libs:@abs_top_builddir@/src/lib/log/.libs
+PYTHONPATH=@abs_top_builddir@/src/lib/python:@abs_top_builddir@/src/lib/dns/python/.libs:@abs_top_builddir@/src/lib/xfr/.libs:@abs_top_builddir@/src/lib/log/.libs:@abs_top_builddir@/src/lib/util/io/.libs
 export PYTHONPATH
 
 # If necessary (rare cases), explicitly specify paths to dynamic libraries
 # required by loadable python modules.
 SET_ENV_LIBRARY_PATH=@SET_ENV_LIBRARY_PATH@
 if test $SET_ENV_LIBRARY_PATH = yes; then
-	@ENV_LIBRARY_PATH@=@abs_top_builddir@/src/lib/dns/.libs:@abs_top_builddir@/src/lib/exceptions/.libs:$@ENV_LIBRARY_PATH@
+	@ENV_LIBRARY_PATH@=@abs_top_builddir@/src/lib/dns/.libs:@abs_top_builddir@/src/lib/util/.libs:@abs_top_builddir@/src/lib/util/io/.libs:@abs_top_builddir@/src/lib/exceptions/.libs:$@ENV_LIBRARY_PATH@
 	export @ENV_LIBRARY_PATH@
 fi
 

+ 1 - 1
src/bin/cfgmgr/b10-cfgmgr.py.in

@@ -41,7 +41,7 @@ if "B10_FROM_SOURCE" in os.environ:
         DATA_PATH = os.environ["B10_FROM_SOURCE_LOCALSTATEDIR"]
     else:
         DATA_PATH = os.environ["B10_FROM_SOURCE"]
-    PLUGIN_PATH = [DATA_PATH + '/src/bin/cfgmgr/plugins']
+    PLUGIN_PATHS = [DATA_PATH + '/src/bin/cfgmgr/plugins']
 else:
     PREFIX = "@prefix@"
     DATA_PATH = "@localstatedir@/@PACKAGE@".replace("${prefix}", PREFIX)

+ 1 - 1
src/bin/stats/run_b10-stats-httpd.sh.in

@@ -26,7 +26,7 @@ export BIND10_MSGQ_SOCKET_FILE
 
 STATS_PATH=@abs_top_builddir@/src/bin/stats
 
-B10_FROM_SOURCE=@abs_top_srcdir@/src/bin/stats
+B10_FROM_SOURCE=@abs_top_srcdir@
 export B10_FROM_SOURCE
 
 cd ${STATS_PATH}

+ 1 - 1
src/bin/stats/run_b10-stats.sh.in

@@ -24,7 +24,7 @@ export PYTHONPATH
 BIND10_MSGQ_SOCKET_FILE=@abs_top_builddir@/msgq_socket
 export BIND10_MSGQ_SOCKET_FILE
 
-B10_FROM_SOURCE=@abs_top_srcdir@/src/bin/stats
+B10_FROM_SOURCE=@abs_top_srcdir@
 export B10_FROM_SOURCE
 
 STATS_PATH=@abs_top_builddir@/src/bin/stats

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

@@ -39,7 +39,8 @@ isc.util.process.rename()
 # from a directory relative to that, otherwise we use the ones
 # installed on the system
 if "B10_FROM_SOURCE" in os.environ:
-    SPECFILE_LOCATION = os.environ["B10_FROM_SOURCE"] + os.sep + "stats.spec"
+    SPECFILE_LOCATION = os.environ["B10_FROM_SOURCE"] + os.sep + \
+        "src" + os.sep + "bin" + os.sep + "stats" + os.sep + "stats.spec"
 else:
     PREFIX = "@prefix@"
     DATAROOTDIR = "@datarootdir@"

+ 2 - 1
src/bin/stats/stats_httpd.py.in

@@ -38,7 +38,8 @@ import isc.util.process
 # from a directory relative to that, otherwise we use the ones
 # installed on the system
 if "B10_FROM_SOURCE" in os.environ:
-    BASE_LOCATION = os.environ["B10_FROM_SOURCE"]
+    BASE_LOCATION = os.environ["B10_FROM_SOURCE"] + os.sep + \
+        "src" + os.sep + "bin" + os.sep + "stats"
 else:
     PREFIX = "@prefix@"
     DATAROOTDIR = "@datarootdir@"

+ 1 - 1
src/bin/stats/tests/Makefile.am

@@ -14,6 +14,6 @@ endif
 	for pytest in $(PYTESTS) ; do \
 	echo Running test: $$pytest ; \
 	env PYTHONPATH=$(abs_top_srcdir)/src/lib/python:$(abs_top_builddir)/src/lib/python:$(abs_top_builddir)/src/bin/stats:$(abs_top_builddir)/src/bin/stats/tests \
-	B10_FROM_SOURCE=$(abs_top_srcdir)/src/bin/stats \
+	B10_FROM_SOURCE=$(abs_top_srcdir) \
 	$(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
 	done

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

@@ -537,7 +537,9 @@ class TestStats2(unittest.TestCase):
         """
         if "B10_FROM_SOURCE" in os.environ:
             self.assertEqual(stats.SPECFILE_LOCATION,
-                             os.environ["B10_FROM_SOURCE"] + os.sep + "stats.spec")
+                             os.environ["B10_FROM_SOURCE"] + os.sep + \
+                                 "src" + os.sep + "bin" + os.sep + "stats" + \
+                                 os.sep + "stats.spec")
         imp.reload(stats)
         # change path of SPECFILE_LOCATION
         stats.SPECFILE_LOCATION = TEST_SPECFILE_LOCATION

+ 1 - 1
src/bin/stats/tests/stats_test.in

@@ -21,7 +21,7 @@ export PYTHON_EXEC
 PYTHONPATH=@abs_top_builddir@/src/lib/python:@abs_top_srcdir@/src/bin/stats:@abs_top_srcdir@/src/bin/stats/tests
 export PYTHONPATH
 
-B10_FROM_SOURCE=@abs_top_srcdir@/src/bin/stats
+B10_FROM_SOURCE=@abs_top_srcdir@
 export B10_FROM_SOURCE
 
 TEST_PATH=@abs_top_srcdir@/src/bin/stats/tests

+ 1 - 1
src/cppcheck-suppress.lst

@@ -12,4 +12,4 @@ functionConst:src/lib/cache/rrset_cache.h
 // Intentional self assignment tests.  Suppress warning about them.
 selfAssignment:src/lib/dns/tests/name_unittest.cc:293
 selfAssignment:src/lib/dns/tests/rdata_unittest.cc:228
-selfAssignment:src/lib/dns/tests/tsigkey_unittest.cc:104
+selfAssignment:src/lib/dns/tests/tsigkey_unittest.cc:120

+ 3 - 2
src/lib/Makefile.am

@@ -1,2 +1,3 @@
-SUBDIRS = exceptions util cryptolink dns cc config python xfr bench log \
-          asiolink asiodns nsas cache resolve testutils datasrc server_common
+SUBDIRS = exceptions util cryptolink dns cc config python xfr bench \
+          log asiolink asiodns nsas cache resolve testutils datasrc \
+          server_common

+ 3 - 1
src/lib/cryptolink/Makefile.am

@@ -1,7 +1,7 @@
 SUBDIRS = . tests
 
 AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
-AM_CPPFLAGS += $(BOOST_INCLUDES)
+AM_CPPFLAGS += $(BOOST_INCLUDES) $(BOTAN_INCLUDES)
 AM_CXXFLAGS = $(B10_CXXFLAGS)
 
 CLEANFILES = *.gcno *.gcda
@@ -10,3 +10,5 @@ lib_LTLIBRARIES = libcryptolink.la
 
 libcryptolink_la_SOURCES = cryptolink.h cryptolink.cc
 libcryptolink_la_SOURCES += crypto_hmac.h crypto_hmac.cc
+
+libcryptolink_la_LIBADD = ${BOTAN_LDFLAGS} ${BOTAN_RPATH}

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

@@ -27,6 +27,7 @@
 namespace isc {
 namespace cryptolink {
 
+/// \brief Hash algorithm identifiers
 enum HashAlgorithm {
     MD5 = 0,            ///< MD5
     SHA1 = 1,           ///< SHA-1

+ 25 - 19
src/lib/cryptolink/tests/crypto_unittests.cc

@@ -21,7 +21,7 @@
 #include <util/buffer.h>
 #include <exceptions/exceptions.h>
 
-#include <boost/scoped_ptr.hpp>
+#include <boost/shared_ptr.hpp>
 
 using namespace isc::util;
 using namespace isc::cryptolink;
@@ -88,9 +88,10 @@ namespace {
         CryptoLink& crypto = CryptoLink::getCryptoLink();
 
         // Sign it
-        boost::scoped_ptr<HMAC> hmac_sign(crypto.createHMAC(secret,
+        boost::shared_ptr<HMAC> hmac_sign(crypto.createHMAC(secret,
                                                             secret_len,
-                                                            hash_algorithm));
+                                                            hash_algorithm),
+                                          deleteHMAC);
         hmac_sign->update(data_buf.getData(), data_buf.getLength());
         hmac_sign->sign(hmac_sig, hmac_len);
 
@@ -98,9 +99,10 @@ namespace {
         checkBuffer(hmac_sig, expected_hmac, hmac_len);
 
         // Check whether we can verify it ourselves
-        boost::scoped_ptr<HMAC> hmac_verify(crypto.createHMAC(secret,
+        boost::shared_ptr<HMAC> hmac_verify(crypto.createHMAC(secret,
                                                               secret_len,
-                                                              hash_algorithm));
+                                                              hash_algorithm),
+                                            deleteHMAC);
         hmac_verify->update(data_buf.getData(), data_buf.getLength());
         EXPECT_TRUE(hmac_verify->verify(hmac_sig.getData(),
                                         hmac_sig.getLength()));
@@ -119,17 +121,19 @@ namespace {
                           const uint8_t* expected_hmac,
                           size_t hmac_len) {
         CryptoLink& crypto = CryptoLink::getCryptoLink();
-        boost::scoped_ptr<HMAC> hmac_sign(crypto.createHMAC(secret,
+        boost::shared_ptr<HMAC> hmac_sign(crypto.createHMAC(secret,
                                                             secret_len,
-                                                            hash_algorithm));
+                                                            hash_algorithm),
+                                          deleteHMAC);
         hmac_sign->update(data.c_str(), data.size());
         std::vector<uint8_t> sig = hmac_sign->sign(hmac_len);
         ASSERT_EQ(hmac_len, sig.size());
         checkData(&sig[0], expected_hmac, hmac_len);
 
-        boost::scoped_ptr<HMAC> hmac_verify(crypto.createHMAC(secret,
+        boost::shared_ptr<HMAC> hmac_verify(crypto.createHMAC(secret,
                                                               secret_len,
-                                                              hash_algorithm));
+                                                              hash_algorithm),
+                                            deleteHMAC);
         hmac_verify->update(data.c_str(), data.size());
         EXPECT_TRUE(hmac_verify->verify(&sig[0], sig.size()));
 
@@ -144,9 +148,10 @@ namespace {
                          const uint8_t* expected_hmac,
                          size_t hmac_len) {
         CryptoLink& crypto = CryptoLink::getCryptoLink();
-        boost::scoped_ptr<HMAC> hmac_sign(crypto.createHMAC(secret,
+        boost::shared_ptr<HMAC> hmac_sign(crypto.createHMAC(secret,
                                                             secret_len,
-                                                            hash_algorithm));
+                                                            hash_algorithm),
+                                          deleteHMAC);
         hmac_sign->update(data.c_str(), data.size());
 
         // note: this is not exception-safe, and can leak, but
@@ -157,9 +162,10 @@ namespace {
         hmac_sign->sign(sig, hmac_len);
         checkData(sig, expected_hmac, hmac_len);
 
-        boost::scoped_ptr<HMAC> hmac_verify(crypto.createHMAC(secret,
+        boost::shared_ptr<HMAC> hmac_verify(crypto.createHMAC(secret,
                                                               secret_len,
-                                                              hash_algorithm));
+                                                              hash_algorithm),
+                                            deleteHMAC);
         hmac_verify->update(data.c_str(), data.size());
         EXPECT_TRUE(hmac_verify->verify(sig, hmac_len));
 
@@ -415,10 +421,9 @@ TEST(CryptoLinkTest, HMAC_SHA256_RFC2202_SIGN) {
 namespace {
     size_t
     sigVectorLength(HashAlgorithm alg, size_t len) {
-        std::auto_ptr<HMAC> hmac_sign(
-            CryptoLink::getCryptoLink().createHMAC("asdf", 4, alg));
-        //boost::scoped_ptr<HMAC> hmac_sign(
-        //    CryptoLink::getCryptoLink().createHMAC("asdf", 4, alg));
+        boost::shared_ptr<HMAC> hmac_sign(
+            CryptoLink::getCryptoLink().createHMAC("asdf", 4, alg),
+            deleteHMAC);
         hmac_sign->update("asdf", 4);
         const std::vector<uint8_t> sig = hmac_sign->sign(len);
         return (sig.size());
@@ -426,8 +431,9 @@ namespace {
 
     size_t
     sigBufferLength(HashAlgorithm alg, size_t len) {
-        boost::scoped_ptr<HMAC> hmac_sign(
-            CryptoLink::getCryptoLink().createHMAC("asdf", 4, alg));
+        boost::shared_ptr<HMAC> hmac_sign(
+            CryptoLink::getCryptoLink().createHMAC("asdf", 4, alg),
+            deleteHMAC);
         hmac_sign->update("asdf", 4);
         OutputBuffer sig(0);
         hmac_sign->sign(sig, len);

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

@@ -58,6 +58,7 @@ run_unittests_LDADD = $(GTEST_LDADD)
 run_unittests_LDADD += $(top_builddir)/src/lib/dns/libdns++.la
 run_unittests_LDADD += $(top_builddir)/src/lib/util/libutil.la
 run_unittests_LDADD += $(top_builddir)/src/lib/util/unittests/libutil_unittests.la
+run_unittests_LDADD += $(top_builddir)/src/lib/util/io/libutil_io.la
 run_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
 endif
 

+ 3 - 3
src/lib/dns/tests/tsigkey_unittest.cc

@@ -43,12 +43,12 @@ TEST_F(TSIGKeyTest, algorithmNames) {
 
     // Also check conversion to cryptolink definitions
     EXPECT_EQ(isc::cryptolink::MD5, TSIGKey(key_name, TSIGKey::HMACMD5_NAME(),
-                                            NULL, 0).getCryptoAlgorithm());
+                                            NULL, 0).getAlgorithm());
     EXPECT_EQ(isc::cryptolink::SHA1, TSIGKey(key_name, TSIGKey::HMACSHA1_NAME(),
-                                             NULL, 0).getCryptoAlgorithm());
+                                             NULL, 0).getAlgorithm());
     EXPECT_EQ(isc::cryptolink::SHA256, TSIGKey(key_name,
                                                TSIGKey::HMACSHA256_NAME(),
-                                               NULL, 0).getCryptoAlgorithm());
+                                               NULL, 0).getAlgorithm());
 }
 
 TEST_F(TSIGKeyTest, construct) {

+ 7 - 1
src/lib/dns/tsig.cc

@@ -85,6 +85,12 @@ TSIGContext::sign(const uint16_t qid, const void* const data,
     }
 
     TSIGError error(TSIGError::NOERROR());
+    // TSIG uses 48-bit unsigned integer to represent time signed.
+    // Since gettimeofdayWrapper() returns a 64-bit *signed* integer, we
+    // make sure it's stored in an unsigned 64-bit integer variable and
+    // represents a value in the expected range.  (In reality, however,
+    // gettimeofdayWrapper() will return a positive integer that will fit
+    // in 48 bits)
     const uint64_t now = (detail::gettimeWrapper() & 0x0000ffffffffffffULL);
 
     // For responses adjust the error code.
@@ -109,7 +115,7 @@ TSIGContext::sign(const uint16_t qid, const void* const data,
     HMACPtr hmac(CryptoLink::getCryptoLink().createHMAC(
                      impl_->key_.getSecret(),
                      impl_->key_.getSecretLength(),
-                     impl_->key_.getCryptoAlgorithm()),
+                     impl_->key_.getAlgorithm()),
                  deleteHMAC);
 
     // If the context has previous MAC (either the Request MAC or its own

+ 3 - 3
src/lib/dns/tsigkey.cc

@@ -109,7 +109,7 @@ TSIGKey::TSIGKey(const std::string& str) : impl_(NULL) {
         const HashAlgorithm algorithm = convertAlgorithmName(algo_name);
 
         vector<uint8_t> secret;
-        util::encode::decodeBase64(secret_str, secret);
+        isc::util::encode::decodeBase64(secret_str, secret);
 
         impl_ = new TSIGKeyImpl(Name(keyname_str), algo_name, algorithm,
                                 &secret[0], secret.size());
@@ -152,7 +152,7 @@ TSIGKey::getAlgorithmName() const {
 }
 
 isc::cryptolink::HashAlgorithm
-TSIGKey::getCryptoAlgorithm() const {
+TSIGKey::getAlgorithm() const {
     return (impl_->algorithm_);
 }
 
@@ -171,7 +171,7 @@ TSIGKey::toText() const {
     const vector<uint8_t> secret_v(static_cast<const uint8_t*>(getSecret()),
                                    static_cast<const uint8_t*>(getSecret()) +
                                    getSecretLength());
-    std::string secret_str = util::encode::encodeBase64(secret_v);
+    std::string secret_str = isc::util::encode::encodeBase64(secret_v);
 
     return (getKeyName().toText() + ":" + secret_str + ":" +
             getAlgorithmName().toText());

+ 1 - 1
src/lib/dns/tsigkey.h

@@ -145,7 +145,7 @@ public:
     const Name& getAlgorithmName() const;
 
     /// Return the hash algorithm name in the form of cryptolink::HashAlgorithm
-    isc::cryptolink::HashAlgorithm getCryptoAlgorithm() const;
+    isc::cryptolink::HashAlgorithm getAlgorithm() const;
 
     /// Return the length of the TSIG secret in bytes.
     size_t getSecretLength() const;

+ 0 - 1
src/lib/nsas/nameserver_address_store.h

@@ -38,7 +38,6 @@ template<class T> class LruList;
 
 namespace nsas {
 
-class ResolverInterface;
 template<class T> class HashTable;
 class ZoneEntry;
 class NameserverEntry;

+ 2 - 1
src/lib/nsas/tests/nameserver_address_unittest.cc

@@ -51,7 +51,8 @@ public:
         ns_.reset(new NameserverEntry(name_.toText(), RRClass::IN()));
         ns_->askIP(resolver_.get(), boost::shared_ptr<Callback>(new Callback), ANY_OK);
         resolver_->asksIPs(name_, 0, 1);
-        resolver_->requests[0].second->success(createResponseMessage(rrv4_));
+        resolver_->requests[0].second->success(
+            isc::util::unittests::createResponseMessage(rrv4_));
     }
 
     // Return the sample NameserverEntry

+ 2 - 1
src/lib/nsas/tests/nameserver_entry_unittest.cc

@@ -72,7 +72,8 @@ private:
         RRsetPtr set)
     {
         if (set) {
-            resolver->requests[index].second->success(createResponseMessage(set));
+            resolver->requests[index].second->success(
+                isc::util::unittests::createResponseMessage(set));
         } else {
             resolver->requests[index].second->failure();
         }

+ 2 - 146
src/lib/nsas/tests/nsas_test.h

@@ -27,6 +27,7 @@
 #include <config.h>
 
 #include <util/buffer.h>
+#include <util/unittests/resolver.h>
 #include <dns/message.h>
 #include <dns/rdata.h>
 #include <dns/rrtype.h>
@@ -35,24 +36,12 @@
 #include <dns/rcode.h>
 #include <dns/messagerenderer.h>
 #include <dns/rdataclass.h>
-#include <resolve/resolver_interface.h>
 #include "../nsas_entry.h"
 
 using namespace isc::dns::rdata;
 using namespace isc::dns;
 using namespace isc::util;
-
-namespace {
-    MessagePtr
-    createResponseMessage(RRsetPtr answer_rrset)
-    {
-        MessagePtr response(new Message(Message::RENDER));
-        response->setOpcode(Opcode::QUERY());
-        response->setRcode(Rcode::NOERROR());
-        response->addRRset(Message::SECTION_ANSWER, answer_rrset);
-        return response;
-    }
-}
+using isc::util::unittests::TestResolver;
 
 namespace isc {
 namespace dns {
@@ -223,139 +212,6 @@ private:
 
 static const uint32_t HASHTABLE_DEFAULT_SIZE = 1009; ///< First prime above 1000
 
-using namespace std;
-
-/*
- * This pretends to be a resolver. It stores the queries and
- * they can be answered.
- */
-class TestResolver : public isc::resolve::ResolverInterface {
-    private:
-        bool checkIndex(size_t index) {
-            return (requests.size() > index);
-        }
-
-        typedef std::map<isc::dns::Question, RRsetPtr >
-            PresetAnswers;
-        PresetAnswers answers_;
-    public:
-        typedef pair<QuestionPtr, CallbackPtr> Request;
-        vector<Request> requests;
-
-        /// \brief Destructor
-        ///
-        /// This is important.  All callbacks in the requests vector must be
-        /// called to remove them from internal loops.  Without this, destroying
-        /// the NSAS object will leave memory assigned.
-        ~TestResolver() {
-            for (size_t i = 0; i < requests.size(); ++i) {
-                requests[i].second->failure();
-            }
-        }
-
-        virtual void resolve(const QuestionPtr& q, const CallbackPtr& c) {
-            PresetAnswers::iterator it(answers_.find(*q));
-            if (it == answers_.end()) {
-                requests.push_back(Request(q, c));
-            } else {
-                if (it->second) {
-                    c->success(createResponseMessage(it->second));
-                } else {
-                    c->failure();
-                }
-            }
-        }
-
-        /*
-         * Add a preset answer. If shared_ptr() is passed (eg. NULL),
-         * it will generate failure. If the question is not preset,
-         * it goes to requests and you can answer later.
-         */
-        void addPresetAnswer(const isc::dns::Question& question,
-            RRsetPtr answer)
-        {
-            answers_[question] = answer;
-        }
-
-        // Thrown if the query at the given index does not exist.
-        class NoSuchRequest : public std::exception { };
-
-        // Thrown if the answer does not match the query
-        class DifferentRequest : public std::exception { };
-
-        QuestionPtr operator[](size_t index) {
-            if (index >= requests.size()) {
-                throw NoSuchRequest();
-            }
-            return (requests[index].first);
-        }
-        /*
-         * Looks if the two provided requests in resolver are A and AAAA.
-         * Sorts them so index1 is A.
-         *
-         * Returns false if there aren't enough elements
-         */
-        bool asksIPs(const Name& name, size_t index1, size_t index2) {
-            size_t max = (index1 < index2) ? index2 : index1;
-            if (!checkIndex(max)) {
-                return false;
-            }
-            EXPECT_EQ(name, (*this)[index1]->getName());
-            EXPECT_EQ(name, (*this)[index2]->getName());
-            EXPECT_EQ(RRClass::IN(), (*this)[index1]->getClass());
-            EXPECT_EQ(RRClass::IN(), (*this)[index2]->getClass());
-            // If they are the other way around, swap
-            if ((*this)[index1]->getType() == RRType::AAAA() &&
-                (*this)[index2]->getType() == RRType::A())
-            {
-                TestResolver::Request tmp((*this).requests[index1]);
-                (*this).requests[index1] =
-                    (*this).requests[index2];
-                (*this).requests[index2] = tmp;
-            }
-            // Check the correct addresses
-            EXPECT_EQ(RRType::A(), (*this)[index1]->getType());
-            EXPECT_EQ(RRType::AAAA(), (*this)[index2]->getType());
-            return (true);
-        }
-
-        /*
-         * Sends a simple answer to a query.
-         * 1) Provide index of a query and the address(es) to pass.
-         * 2) Provide index of query and components of address to pass.
-         */
-        void answer(size_t index, RRsetPtr& set) {
-            if (index >= requests.size()) {
-                throw NoSuchRequest();
-            }
-            requests[index].second->success(createResponseMessage(set));
-        }
-
-        void answer(size_t index, const Name& name, const RRType& type,
-            const rdata::Rdata& rdata, size_t TTL = 100)
-        {
-            RRsetPtr set(new RRset(name, RRClass::IN(),
-                type, RRTTL(TTL)));
-            set->addRdata(rdata);
-            answer(index, set);
-        }
-
-
-        void provideNS(size_t index,
-            RRsetPtr nameservers)
-        {
-            if (index >= requests.size()) {
-                throw NoSuchRequest();
-            }
-            if (requests[index].first->getName() != nameservers->getName() ||
-                requests[index].first->getType() != RRType::NS())
-            {
-                throw DifferentRequest();
-            }
-            requests[index].second->success(createResponseMessage(nameservers));
-        }
-};
-
 // String constants.  These should end in a dot.
 static const std::string EXAMPLE_CO_UK("example.co.uk.");
 static const std::string EXAMPLE_NET("example.net.");

+ 64 - 2
src/lib/resolve/recursive_query.cc

@@ -28,6 +28,7 @@
 #include <dns/message.h>
 #include <dns/opcode.h>
 #include <dns/exceptions.h>
+#include <dns/rdataclass.h>
 
 #include <resolve/resolve.h>
 #include <cache/resolver_cache.h>
@@ -48,6 +49,65 @@ using namespace isc::asiolink;
 namespace isc {
 namespace asiodns {
 
+namespace {
+// Function to check if the given name/class has any address in the cache
+bool
+hasAddress(const Name& name, const RRClass& rrClass,
+      const isc::cache::ResolverCache& cache)
+{
+    // FIXME: If we are single-stack and we get only the other type of
+    // address, what should we do? In that case, it will be considered
+    // unreachable, which is most probably true, because A and AAAA will
+    // usually have the same RTT, so we should have both or none from the
+    // glue.
+    return (cache.lookup(name, RRType::A(), rrClass) != RRsetPtr() ||
+            cache.lookup(name, RRType::AAAA(), rrClass) != RRsetPtr());
+}
+
+}
+
+/// \brief Find deepest usable delegation in the cache
+///
+/// This finds the deepest delegation we have in cache and is safe to use.
+/// It is not public function, therefore it's not in header. But it's not
+/// in anonymous namespace, so we can call it from unittests.
+/// \param name The name we want to delegate to.
+/// \param cache The place too look for known delegations.
+std::string
+deepestDelegation(Name name, RRClass rrclass,
+                  isc::cache::ResolverCache& cache)
+{
+    RRsetPtr cachedNS;
+    // Look for delegation point from bottom, until we find one with
+    // IP address or get to root.
+    //
+    // We need delegation with IP address so we can ask it right away.
+    // If we don't have the IP address, we would need to ask above it
+    // anyway in the best case, and the NS could be inside the zone,
+    // and we could get all loopy with the NSAS in the worst case.
+    while (name.getLabelCount() > 1 &&
+           (cachedNS = cache.lookupDeepestNS(name, rrclass)) != RRsetPtr()) {
+        // Look if we have an IP address for the NS
+        for (RdataIteratorPtr ns(cachedNS->getRdataIterator());
+             !ns->isLast(); ns->next()) {
+            // Do we have IP for this specific NS?
+            if (hasAddress(dynamic_cast<const rdata::generic::NS&>(
+                               ns->getCurrent()).getNSName(), rrclass,
+                           cache)) {
+                // Found one, stop checking and use this zone
+                // (there may be more addresses, that's only better)
+                return (cachedNS->getName().toText());
+            }
+        }
+        // We don't have anything for this one, so try something higher
+        if (name.getLabelCount() > 1) {
+            name = name.split(1);
+        }
+    }
+    // Fallback, nothing found, start at root
+    return (".");
+}
+
 typedef std::vector<std::pair<std::string, uint16_t> > AddressVector;
 
 // Here we do not use the typedef above, as the SunStudio compiler
@@ -239,7 +299,6 @@ private:
     // if we have a response for our query stored already. if
     // so, call handlerecursiveresponse(), if not, we call send()
     void doLookup() {
-        cur_zone_ = ".";
         dlog("doLookup: try cache");
         Message cached_message(Message::RENDER);
         isc::resolve::initResponseMessage(question_, cached_message);
@@ -255,9 +314,12 @@ private:
                 stop();
             }
         } else {
+            dlog("doLookup: get lowest usable delegation from cache");
+            cur_zone_ = deepestDelegation(question_.getName(),
+                                          question_.getClass(), cache_);
             send();
         }
-        
+
     }
 
     // Send the current question to the given nameserver address

+ 102 - 9
src/lib/resolve/tests/recursive_query_unittest.cc

@@ -31,7 +31,9 @@
 #include <dns/rcode.h>
 
 #include <util/buffer.h>
+#include <util/unittests/resolver.h>
 #include <dns/message.h>
+#include <dns/rdataclass.h>
 
 #include <nsas/nameserver_address_store.h>
 #include <cache/resolver_cache.h>
@@ -59,6 +61,18 @@ using namespace isc::asiolink;
 using namespace isc::dns;
 using namespace isc::util;
 
+namespace isc {
+namespace asiodns {
+
+// This is defined in recursive_query.cc, but not in header (it's not public
+// function). So bring it in to be tested.
+std::string
+deepestDelegation(Name name, RRClass rrclass,
+                  isc::cache::ResolverCache& cache);
+
+}
+}
+
 namespace {
 const char* const TEST_SERVER_PORT = "53535";
 const char* const TEST_CLIENT_PORT = "53536";
@@ -110,6 +124,9 @@ class RecursiveQueryTest : public ::testing::Test {
 protected:
     RecursiveQueryTest();
     ~RecursiveQueryTest() {
+        // It would delete itself, but after the io_service_, which could
+        // segfailt in case there were unhandled requests
+        resolver_.reset();
         if (res_ != NULL) {
             freeaddrinfo(res_);
         }
@@ -348,12 +365,6 @@ protected:
         private:
             bool* done_;
     };
-    
-    class MockResolver : public isc::resolve::ResolverInterface {
-        void resolve(const QuestionPtr& question,
-                     const ResolverInterface::CallbackPtr& callback) {
-        }
-    };
 
     // This version of mock server just stops the io_service when it is resumed
     // the second time. (Used in the clientTimeout test, where resume
@@ -423,16 +434,17 @@ protected:
     vector<uint8_t> callback_data_;
     int sock_;
     struct addrinfo* res_;
+    boost::shared_ptr<isc::util::unittests::TestResolver> resolver_;
 };
 
 RecursiveQueryTest::RecursiveQueryTest() :
     dns_service_(NULL), callback_(NULL), callback_protocol_(0),
-    callback_native_(-1), sock_(-1), res_(NULL)
+    callback_native_(-1), sock_(-1), res_(NULL),
+    resolver_(new isc::util::unittests::TestResolver())
 {
     io_service_ = new IOService();
     setDNSService(true, true);
-    boost::shared_ptr<MockResolver>mock_resolver(new MockResolver());
-    nsas_ = new isc::nsas::NameserverAddressStore(mock_resolver);
+    nsas_ = new isc::nsas::NameserverAddressStore(resolver_);
 }
 
 TEST_F(RecursiveQueryTest, v6UDPSend) {
@@ -857,7 +869,88 @@ TEST_F(RecursiveQueryTest, DISABLED_recursiveSendNXDOMAIN) {
     EXPECT_EQ(0, answer->getRRCount(Message::SECTION_ANSWER));
 }
 
+// Test that we don't start at root when we have a lower NS cached.
+TEST_F(RecursiveQueryTest, CachedNS) {
+    setDNSService(true, true);
+
+    // Check we have a reasonable fallback - if there's nothing of interest
+    // in the cache, start at root.
+    EXPECT_EQ(".", deepestDelegation(Name("www.somewhere.deep.example.org"),
+                                     RRClass::IN(), cache_));
+
+    // Prefill the cache. There's a zone with a NS and IP address for one
+    // of them (to see that one is enough) and another deeper one, with NS,
+    // but without IP.
+    RRsetPtr nsUpper(new RRset(Name("example.org"), RRClass::IN(),
+                               RRType::NS(), RRTTL(300)));
+    nsUpper->addRdata(rdata::generic::NS(Name("ns.example.org")));
+    nsUpper->addRdata(rdata::generic::NS(Name("ns2.example.org")));
+
+    RRsetPtr nsLower(new RRset(Name("somewhere.deep.example.org"),
+                               RRClass::IN(), RRType::NS(), RRTTL(300)));
+    nsLower->addRdata(rdata::generic::NS(Name("ns.somewhere.deep.example.org"))
+                      );
+
+    RRsetPtr nsIp(new RRset(Name("ns2.example.org"), RRClass::IN(),
+                            RRType::A(), RRTTL(300)));
+    nsIp->addRdata(rdata::in::A("192.0.2.1"));
+
+    // Make sure the test runs in the correct environment (we don't test
+    // the cache, but we need it to unswer this way for the test, so we
+    // just make sure)
+    ASSERT_TRUE(cache_.update(nsUpper));
+    ASSERT_TRUE(cache_.update(nsLower));
+    ASSERT_TRUE(cache_.update(nsIp));
+    RRsetPtr deepest(cache_.lookupDeepestNS(Name(
+        "www.somewhere.deep.example.org"), RRClass::IN()));
+    ASSERT_NE(RRsetPtr(), deepest);
+    ASSERT_EQ(nsLower->getName(), deepest->getName());
+
+    // Direct check of the function that chooses the delegation point
+    // It should not use nsLower, because we don't have IP address for
+    // that one. But it can choose nsUpper.
+    EXPECT_EQ("example.org.",
+              deepestDelegation(Name("www.somewhere.deep.example.org"),
+              RRClass::IN(), cache_));
+
+    // Now more complex and indirect test:
+    // We ask it to resolve the name for us. It will pick up a delegation
+    // point and ask NSAS for it. NSAS will in turn ask resolver for NS record
+    // of the delegation point. We then pick it up from the fake resolver
+    // and check it is the correct one. This checks the delegation point
+    // travels safely trough the whole path there (it would be enough to check
+    // it up to NSAS, but replacing NSAS is more complicated, so we just
+    // include in the test as well for simplicity).
+
+    // Prepare the recursive query
+    vector<pair<string, uint16_t> > roots;
+    roots.push_back(pair<string, uint16_t>("192.0.2.2", 53));
+
+    RecursiveQuery rq(*dns_service_, *nsas_, cache_,
+                      vector<pair<string, uint16_t> >(), roots);
+    // Ask a question at the bottom. It should not use the lower NS, because
+    // it would lead to a loop in NS. But it can use the nsUpper one, it has
+    // an IP address and we can avoid asking root.
+    Question q(Name("www.somewhere.deep.example.org"), RRClass::IN(),
+               RRType::A());
+    OutputBufferPtr buffer(new OutputBuffer(0));
+    MessagePtr answer(new Message(Message::RENDER));
+    // The server is here so we have something to pass there
+    MockServer server(*io_service_);
+    rq.resolve(q, answer, buffer, &server);
+    // We don't need to run the service in this test. We are interested only
+    // in the place it starts resolving at
+
+    // Look what is asked by NSAS - it should be our delegation point.
+    EXPECT_NO_THROW(EXPECT_EQ(nsUpper->getName(),
+                              (*resolver_)[0]->getName()) <<
+                    "It starts resolving at the wrong place") <<
+        "It does not ask NSAS anything, how does it know where to send?";
+}
+
 // TODO: add tests that check whether the cache is updated on succesfull
 // responses, and not updated on failures.
 
+
+
 }

+ 1 - 1
src/lib/util/unittests/Makefile.am

@@ -2,7 +2,7 @@ AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
 AM_CXXFLAGS = $(B10_CXXFLAGS)
 
 lib_LTLIBRARIES = libutil_unittests.la
-libutil_unittests_la_SOURCES = fork.h fork.cc
+libutil_unittests_la_SOURCES = fork.h fork.cc resolver.h
 libutil_unittests_la_SOURCES += newhook.h newhook.cc
 
 CLEANFILES = *.gcno *.gcda

+ 1 - 1
src/lib/util/unittests/newhook.cc

@@ -36,7 +36,7 @@ operator new(size_t size) throw(std::bad_alloc) {
 void
 operator delete(void* p) throw() {
     if (p != NULL) {
-        free (p);
+        free(p);
     }
 }
 #endif

+ 6 - 0
src/lib/util/unittests/newhook.h

@@ -61,6 +61,12 @@ extern bool force_throw_on_new;
 
 /// The allocation size that triggers an exception in the special operator new
 ///
+/// This is the exact size that causes an exception to be thrown;
+/// for example, if it is set to 100, an attempt of allocating 100 bytes
+/// will result in an exception, but allocation attempt for 101 bytes won't
+/// (unless, of course, memory is really exhausted and allocation really
+/// fails).
+///
 /// The default value is 0.  The value of this variable has no meaning
 /// unless the use of the special operator is enabled at build time and
 /// via \c force_throw_on_new.

+ 193 - 0
src/lib/util/unittests/resolver.h

@@ -0,0 +1,193 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef UTIL_UNITTEST_RESOLVER_H
+#define UTIL_UNITTEST_RESOLVER_H
+
+/// \file resolver.h
+/// \brief Fake resolver
+
+#include <map>
+#include <dns/rrset.h>
+#include <dns/question.h>
+#include <dns/message.h>
+#include <dns/opcode.h>
+#include <dns/rcode.h>
+#include <dns/rrttl.h>
+#include <resolve/resolver_interface.h>
+#include <gtest/gtest.h>
+
+namespace isc {
+namespace util {
+namespace unittests {
+
+/// \brief Put rrset into a message as an answer
+inline static isc::dns::MessagePtr
+createResponseMessage(isc::dns::RRsetPtr answer_rrset)
+{
+    isc::dns::MessagePtr response(new isc::dns::Message(
+        isc::dns::Message::RENDER));
+    response->setOpcode(isc::dns::Opcode::QUERY());
+    response->setRcode(isc::dns::Rcode::NOERROR());
+    response->addRRset(isc::dns::Message::SECTION_ANSWER, answer_rrset);
+    return response;
+}
+
+/// \brief Mock resolver
+///
+/// This class pretends to be a resolver. However, it only stores the
+/// requests and can answer them right away by prepared answers. It doesn't
+/// do any real work and is intended for testing purposes.
+class TestResolver : public isc::resolve::ResolverInterface {
+    private:
+        bool checkIndex(size_t index) {
+            return (requests.size() > index);
+        }
+
+        typedef std::map<isc::dns::Question, isc::dns::RRsetPtr>
+            PresetAnswers;
+        PresetAnswers answers_;
+    public:
+        typedef std::pair<isc::dns::QuestionPtr, CallbackPtr> Request;
+        /// \brief List of requests the tested class sent trough resolve
+        std::vector<Request> requests;
+
+        /// \brief Destructor
+        ///
+        /// This is important.  All callbacks in the requests vector must be
+        /// called to remove them from internal loops.  Without this, destroying
+        /// the NSAS object will leave memory assigned.
+        ~TestResolver() {
+            for (size_t i = 0; i < requests.size(); ++i) {
+                requests[i].second->failure();
+            }
+        }
+
+        /// \brief Testing version of resolve
+        ///
+        /// If there's a prepared answer (provided by addPresetAnswer), it
+        /// answers it right away. Otherwise it just stores the request in
+        /// the requests member so it can be examined later.
+        virtual void resolve(const isc::dns::QuestionPtr& q,
+                             const CallbackPtr& c)
+        {
+            PresetAnswers::iterator it(answers_.find(*q));
+            if (it == answers_.end()) {
+                requests.push_back(Request(q, c));
+            } else {
+                if (it->second) {
+                    c->success(createResponseMessage(it->second));
+                } else {
+                    c->failure();
+                }
+            }
+        }
+
+        /// \brief Add a preset answer.
+        ///
+        /// Add a preset answer. If shared_ptr() is passed (eg. NULL),
+        /// it will generate failure. If the question is not preset,
+        /// it goes to requests and you can answer later.
+        void addPresetAnswer(const isc::dns::Question& question,
+            isc::dns::RRsetPtr answer)
+        {
+            answers_[question] = answer;
+        }
+
+        /// \brief Thrown if the query at the given index does not exist.
+        class NoSuchRequest : public std::exception { };
+
+        /// \brief Thrown if the answer does not match the query
+        class DifferentRequest : public std::exception { };
+
+        /// \brief Provides the question of request on given answer
+        isc::dns::QuestionPtr operator[](size_t index) {
+            if (index >= requests.size()) {
+                throw NoSuchRequest();
+            }
+            return (requests[index].first);
+        }
+        /// \brief Test it asks for IP addresses
+        /// Looks if the two provided requests in resolver are A and AAAA.
+        /// Sorts them so index1 is A.
+        ///
+        /// Returns false if there aren't enough elements
+        bool asksIPs(const isc::dns::Name& name, size_t index1,
+                     size_t index2)
+        {
+            size_t max = (index1 < index2) ? index2 : index1;
+            if (!checkIndex(max)) {
+                return false;
+            }
+            EXPECT_EQ(name, (*this)[index1]->getName());
+            EXPECT_EQ(name, (*this)[index2]->getName());
+            EXPECT_EQ(isc::dns::RRClass::IN(), (*this)[index1]->getClass());
+            EXPECT_EQ(isc::dns::RRClass::IN(), (*this)[index2]->getClass());
+            // If they are the other way around, swap
+            if ((*this)[index1]->getType() == isc::dns::RRType::AAAA() &&
+                (*this)[index2]->getType() == isc::dns::RRType::A())
+            {
+                TestResolver::Request tmp((*this).requests[index1]);
+                (*this).requests[index1] =
+                    (*this).requests[index2];
+                (*this).requests[index2] = tmp;
+            }
+            // Check the correct addresses
+            EXPECT_EQ(isc::dns::RRType::A(), (*this)[index1]->getType());
+            EXPECT_EQ(isc::dns::RRType::AAAA(), (*this)[index2]->getType());
+            return (true);
+        }
+
+        /// \brief Answer a request
+        /// Sends a simple answer to a query.
+        /// 1) Provide index of a query and the address(es) to pass.
+        /// 2) Provide index of query and components of address to pass.
+        void answer(size_t index, isc::dns::RRsetPtr& set) {
+            if (index >= requests.size()) {
+                throw NoSuchRequest();
+            }
+            requests[index].second->success(createResponseMessage(set));
+        }
+
+        void answer(size_t index, const isc::dns::Name& name,
+                    const isc::dns::RRType& type,
+                    const isc::dns::rdata::Rdata& rdata, size_t TTL = 100)
+        {
+            isc::dns::RRsetPtr set(new isc::dns::RRset(name,
+                                                       isc::dns::RRClass::IN(),
+                                                       type,
+                                                       isc::dns::RRTTL(TTL)));
+            set->addRdata(rdata);
+            answer(index, set);
+        }
+        /// \Answer the query at index by list of nameservers
+        void provideNS(size_t index, isc::dns::RRsetPtr nameservers)
+        {
+            if (index >= requests.size()) {
+                throw NoSuchRequest();
+            }
+            if (requests[index].first->getName() != nameservers->getName() ||
+                requests[index].first->getType() != isc::dns::RRType::NS())
+            {
+                throw DifferentRequest();
+            }
+            requests[index].second->success(createResponseMessage(nameservers));
+        }
+};
+
+}
+}
+}
+
+#endif