import unbound 1.7.3, testing from benno@ and Brad.
authorsthen <sthen@openbsd.org>
Mon, 17 Sep 2018 09:43:42 +0000 (09:43 +0000)
committersthen <sthen@openbsd.org>
Mon, 17 Sep 2018 09:43:42 +0000 (09:43 +0000)
41 files changed:
usr.sbin/unbound/cachedb/redis.c [new file with mode: 0644]
usr.sbin/unbound/cachedb/redis.h [new file with mode: 0644]
usr.sbin/unbound/daemon/cachedump.h
usr.sbin/unbound/testcode/asynclook.c [new file with mode: 0644]
usr.sbin/unbound/testcode/checklocks.c [new file with mode: 0644]
usr.sbin/unbound/testcode/checklocks.h [new file with mode: 0644]
usr.sbin/unbound/testcode/delayer.c [new file with mode: 0644]
usr.sbin/unbound/testcode/do-tests.sh [new file with mode: 0755]
usr.sbin/unbound/testcode/fake_event.c [new file with mode: 0644]
usr.sbin/unbound/testcode/fake_event.h [new file with mode: 0644]
usr.sbin/unbound/testcode/lock_verify.c [new file with mode: 0644]
usr.sbin/unbound/testcode/memstats.c [new file with mode: 0644]
usr.sbin/unbound/testcode/mini_tdir.sh [new file with mode: 0755]
usr.sbin/unbound/testcode/mini_tpkg.sh [new file with mode: 0755]
usr.sbin/unbound/testcode/perf.c [new file with mode: 0644]
usr.sbin/unbound/testcode/petal.c [new file with mode: 0644]
usr.sbin/unbound/testcode/pktview.c [new file with mode: 0644]
usr.sbin/unbound/testcode/readhex.c [new file with mode: 0644]
usr.sbin/unbound/testcode/readhex.h [new file with mode: 0644]
usr.sbin/unbound/testcode/replay.c [new file with mode: 0644]
usr.sbin/unbound/testcode/replay.h [new file with mode: 0644]
usr.sbin/unbound/testcode/run_vm.sh [new file with mode: 0644]
usr.sbin/unbound/testcode/signit.c [new file with mode: 0644]
usr.sbin/unbound/testcode/streamtcp.1 [new file with mode: 0644]
usr.sbin/unbound/testcode/streamtcp.c [new file with mode: 0644]
usr.sbin/unbound/testcode/testbound.c [new file with mode: 0644]
usr.sbin/unbound/testcode/testpkts.c [new file with mode: 0644]
usr.sbin/unbound/testcode/testpkts.h [new file with mode: 0644]
usr.sbin/unbound/testcode/unitanchor.c [new file with mode: 0644]
usr.sbin/unbound/testcode/unitauth.c [new file with mode: 0644]
usr.sbin/unbound/testcode/unitdname.c [new file with mode: 0644]
usr.sbin/unbound/testcode/unitecs.c [new file with mode: 0644]
usr.sbin/unbound/testcode/unitldns.c [new file with mode: 0644]
usr.sbin/unbound/testcode/unitlruhash.c [new file with mode: 0644]
usr.sbin/unbound/testcode/unitmain.c [new file with mode: 0644]
usr.sbin/unbound/testcode/unitmain.h [new file with mode: 0644]
usr.sbin/unbound/testcode/unitmsgparse.c [new file with mode: 0644]
usr.sbin/unbound/testcode/unitneg.c [new file with mode: 0644]
usr.sbin/unbound/testcode/unitregional.c [new file with mode: 0644]
usr.sbin/unbound/testcode/unitslabhash.c [new file with mode: 0644]
usr.sbin/unbound/testcode/unitverify.c [new file with mode: 0644]

diff --git a/usr.sbin/unbound/cachedb/redis.c b/usr.sbin/unbound/cachedb/redis.c
new file mode 100644 (file)
index 0000000..3dfbf8f
--- /dev/null
@@ -0,0 +1,283 @@
+/*
+ * cachedb/redis.c - cachedb redis module
+ *
+ * Copyright (c) 2018, NLnet Labs. All rights reserved.
+ *
+ * This software is open source.
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 
+ * Neither the name of the NLNET LABS nor the names of its contributors may
+ * be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+ * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * \file
+ *
+ * This file contains a module that uses the redis database to cache
+ * dns responses.
+ */
+
+#include "config.h"
+#ifdef USE_CACHEDB
+#include "cachedb/redis.h"
+#include "cachedb/cachedb.h"
+#include "util/alloc.h"
+#include "util/config_file.h"
+#include "sldns/sbuffer.h"
+
+#ifdef USE_REDIS
+#include "hiredis/hiredis.h"
+
+struct redis_moddata {
+       redisContext** ctxs;    /* thread-specific redis contexts */
+       int numctxs;            /* number of ctx entries */
+       const char* server_host; /* server's IP address or host name */
+       int server_port;         /* server's TCP port */
+       struct timeval timeout;  /* timeout for connection setup and commands */
+};
+
+static redisContext*
+redis_connect(const struct redis_moddata* moddata)
+{
+       redisContext* ctx;
+
+       ctx = redisConnectWithTimeout(moddata->server_host,
+               moddata->server_port, moddata->timeout);
+       if(!ctx || ctx->err) {
+               const char *errstr = "out of memory";
+               if(ctx)
+                       errstr = ctx->errstr;
+               log_err("failed to connect to redis server: %s", errstr);
+               goto fail;
+       }
+       if(redisSetTimeout(ctx, moddata->timeout) != REDIS_OK) {
+               log_err("failed to set redis timeout");
+               goto fail;
+       }
+       return ctx;
+
+  fail:
+       if(ctx)
+               redisFree(ctx);
+       return NULL;
+}
+
+static int
+redis_init(struct module_env* env, struct cachedb_env* cachedb_env)
+{
+       int i;
+       struct redis_moddata* moddata = NULL;
+
+       verbose(VERB_ALGO, "redis_init");
+
+       moddata = calloc(1, sizeof(struct redis_moddata));
+       if(!moddata) {
+               log_err("out of memory");
+               return 0;
+       }
+       moddata->numctxs = env->cfg->num_threads;
+       moddata->ctxs = calloc(env->cfg->num_threads, sizeof(redisContext*));
+       if(!moddata->ctxs) {
+               log_err("out of memory");
+               free(moddata);
+               return 0;
+       }
+       /* note: server_host is a shallow reference to configured string.
+        * we don't have to free it in this module. */
+       moddata->server_host = env->cfg->redis_server_host;
+       moddata->server_port = env->cfg->redis_server_port;
+       moddata->timeout.tv_sec = env->cfg->redis_timeout / 1000;
+       moddata->timeout.tv_usec = (env->cfg->redis_timeout % 1000) * 1000;
+       for(i = 0; i < moddata->numctxs; i++)
+               moddata->ctxs[i] = redis_connect(moddata);
+       cachedb_env->backend_data = moddata;
+       return 1;
+}
+
+static void
+redis_deinit(struct module_env* env, struct cachedb_env* cachedb_env)
+{
+       struct redis_moddata* moddata = (struct redis_moddata*)
+               cachedb_env->backend_data;
+       (void)env;
+
+       verbose(VERB_ALGO, "redis_deinit");
+
+       if(!moddata)
+               return;
+       if(moddata->ctxs) {
+               int i;
+               for(i = 0; i < moddata->numctxs; i++) {
+                       if(moddata->ctxs[i])
+                               redisFree(moddata->ctxs[i]);
+               }
+               free(moddata->ctxs);
+       }
+       free(moddata);
+}
+
+/*
+ * Send a redis command and get a reply.  Unified so that it can be used for
+ * both SET and GET.  If 'data' is non-NULL the command is supposed to be
+ * SET and GET otherwise, but the implementation of this function is agnostic
+ * about the semantics (except for logging): 'command', 'data', and 'data_len'
+ * are opaquely passed to redisCommand().
+ * This function first checks whether a connection with a redis server has
+ * been established; if not it tries to set up a new one.
+ * It returns redisReply returned from redisCommand() or NULL if some low
+ * level error happens.  The caller is responsible to check the return value,
+ * if it's non-NULL, it has to free it with freeReplyObject().
+ */
+static redisReply*
+redis_command(struct module_env* env, struct cachedb_env* cachedb_env,
+       const char* command, const uint8_t* data, size_t data_len)
+{
+       redisContext* ctx;
+       redisReply* rep;
+       struct redis_moddata* d = (struct redis_moddata*)
+               cachedb_env->backend_data;
+
+       /* We assume env->alloc->thread_num is a unique ID for each thread
+        * in [0, num-of-threads).  We could treat it as an error condition
+        * if the assumption didn't hold, but it seems to be a fundamental
+        * assumption throughout the unbound architecture, so we simply assert
+        * it. */
+       log_assert(env->alloc->thread_num < d->numctxs);
+       ctx = d->ctxs[env->alloc->thread_num];
+
+       /* If we've not established a connection to the server or we've closed
+        * it on a failure, try to re-establish a new one.   Failures will be
+        * logged in redis_connect(). */
+       if(!ctx) {
+               ctx = redis_connect(d);
+               d->ctxs[env->alloc->thread_num] = ctx;
+       }
+       if(!ctx)
+               return NULL;
+
+       /* Send the command and get a reply, synchronously. */
+       rep = (redisReply*)redisCommand(ctx, command, data, data_len);
+       if(!rep) {
+               /* Once an error as a NULL-reply is returned the context cannot
+                * be reused and we'll need to set up a new connection. */
+               log_err("redis_command: failed to receive a reply, "
+                       "closing connection: %s", ctx->errstr);
+               redisFree(ctx);
+               d->ctxs[env->alloc->thread_num] = NULL;
+               return NULL;
+       }
+
+       /* Check error in reply to unify logging in that case.
+        * The caller may perform context-dependent checks and logging. */
+       if(rep->type == REDIS_REPLY_ERROR)
+               log_err("redis: %s resulted in an error: %s",
+                       data ? "set" : "get", rep->str);
+
+       return rep;
+}
+
+static int
+redis_lookup(struct module_env* env, struct cachedb_env* cachedb_env,
+       char* key, struct sldns_buffer* result_buffer)
+{
+       redisReply* rep;
+       char cmdbuf[4+(CACHEDB_HASHSIZE/8)*2+1]; /* "GET " + key */
+       int n;
+       int ret = 0;
+
+       verbose(VERB_ALGO, "redis_lookup of %s", key);
+
+       n = snprintf(cmdbuf, sizeof(cmdbuf), "GET %s", key);
+       if(n < 0 || n >= (int)sizeof(cmdbuf)) {
+               log_err("redis_lookup: unexpected failure to build command");
+               return 0;
+       }
+
+       rep = redis_command(env, cachedb_env, cmdbuf, NULL, 0);
+       if(!rep)
+               return 0;
+       switch (rep->type) {
+       case REDIS_REPLY_NIL:
+               verbose(VERB_ALGO, "redis_lookup: no data cached");
+               break;
+       case REDIS_REPLY_STRING:
+               verbose(VERB_ALGO, "redis_lookup found %d bytes",
+                       (int)rep->len);
+               if((size_t)rep->len > sldns_buffer_capacity(result_buffer)) {
+                       log_err("redis_lookup: replied data too long: %lu",
+                               (size_t)rep->len);
+                       break;
+               }
+               sldns_buffer_clear(result_buffer);
+               sldns_buffer_write(result_buffer, rep->str, rep->len);
+               sldns_buffer_flip(result_buffer);
+               ret = 1;
+               break;
+       case REDIS_REPLY_ERROR:
+               break;          /* already logged */
+       default:
+               log_err("redis_lookup: unexpected type of reply for (%d)",
+                       rep->type);
+               break;
+       }
+       freeReplyObject(rep);
+       return ret;
+}
+
+static void
+redis_store(struct module_env* env, struct cachedb_env* cachedb_env,
+       char* key, uint8_t* data, size_t data_len)
+{
+       redisReply* rep;
+       char cmdbuf[4+(CACHEDB_HASHSIZE/8)*2+3+1]; /* "SET " + key + " %b" */
+       int n;
+
+       verbose(VERB_ALGO, "redis_store %s (%d bytes)", key, (int)data_len);
+
+       /* build command to set to a binary safe string */
+       n = snprintf(cmdbuf, sizeof(cmdbuf), "SET %s %%b", key);
+       if(n < 0 || n >= (int)sizeof(cmdbuf)) {
+               log_err("redis_store: unexpected failure to build command");
+               return;
+       }
+
+       rep = redis_command(env, cachedb_env, cmdbuf, data, data_len);
+       if(rep) {
+               verbose(VERB_ALGO, "redis_store set completed");
+               if(rep->type != REDIS_REPLY_STATUS &&
+                       rep->type != REDIS_REPLY_ERROR) {
+                       log_err("redis_store: unexpected type of reply (%d)",
+                               rep->type);
+               }
+               freeReplyObject(rep);
+       }
+}
+
+struct cachedb_backend redis_backend = { "redis",
+       redis_init, redis_deinit, redis_lookup, redis_store
+};
+#endif /* USE_REDIS */
+#endif /* USE_CACHEDB */
diff --git a/usr.sbin/unbound/cachedb/redis.h b/usr.sbin/unbound/cachedb/redis.h
new file mode 100644 (file)
index 0000000..2da2a64
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+ * cachedb/redis.h - cachedb redis module
+ *
+ * Copyright (c) 2018, NLnet Labs. All rights reserved.
+ *
+ * This software is open source.
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 
+ * Neither the name of the NLNET LABS nor the names of its contributors may
+ * be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+ * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * \file
+ *
+ * This file contains a module that uses the redis database to cache
+ * dns responses.
+ */
+
+/** the redis backend definition, contains callable functions
+ * and name string */
+extern struct cachedb_backend redis_backend;
index 0f2feab..72c172d 100644 (file)
@@ -72,6 +72,7 @@
 #ifndef DAEMON_DUMPCACHE_H
 #define DAEMON_DUMPCACHE_H
 struct worker;
+#include "daemon/remote.h"
 
 /**
  * Dump cache(s) to text
@@ -80,7 +81,7 @@ struct worker;
  *     ptrs to the caches.
  * @return false on ssl print error.
  */
-int dump_cache(SSL* ssl, struct worker* worker);
+int dump_cache(RES* ssl, struct worker* worker);
 
 /**
  * Load cache(s) from text 
@@ -89,7 +90,7 @@ int dump_cache(SSL* ssl, struct worker* worker);
  *     ptrs to the caches.
  * @return false on ssl error.
  */
-int load_cache(SSL* ssl, struct worker* worker);
+int load_cache(RES* ssl, struct worker* worker);
 
 /**
  * Print the delegation used to lookup for this name.
@@ -101,7 +102,7 @@ int load_cache(SSL* ssl, struct worker* worker);
  * @param nmlabs: labels in name.
  * @return false on ssl error.
  */
-int print_deleg_lookup(SSL* ssl, struct worker* worker, uint8_t* nm,
+int print_deleg_lookup(RES* ssl, struct worker* worker, uint8_t* nm,
        size_t nmlen, int nmlabs);
 
 #endif /* DAEMON_DUMPCACHE_H */
diff --git a/usr.sbin/unbound/testcode/asynclook.c b/usr.sbin/unbound/testcode/asynclook.c
new file mode 100644 (file)
index 0000000..06bcf5a
--- /dev/null
@@ -0,0 +1,558 @@
+/*
+ * testcode/asynclook.c - debug program perform async libunbound queries.
+ *
+ * Copyright (c) 2008, NLnet Labs. All rights reserved.
+ *
+ * This software is open source.
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 
+ * Neither the name of the NLNET LABS nor the names of its contributors may
+ * be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+ * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * \file
+ *
+ * This program shows the results from several background lookups,
+ * while printing time in the foreground.
+ */
+
+#include "config.h"
+#ifdef HAVE_GETOPT_H
+#include <getopt.h>
+#endif
+#include "libunbound/unbound.h"
+#include "libunbound/context.h"
+#include "util/locks.h"
+#include "util/log.h"
+#include "sldns/rrdef.h"
+#ifdef UNBOUND_ALLOC_LITE
+#undef malloc
+#undef calloc
+#undef realloc
+#undef free
+#undef strdup
+#endif
+#ifdef HAVE_SSL
+#ifdef HAVE_OPENSSL_SSL_H
+#include <openssl/ssl.h>
+#endif
+#ifdef HAVE_OPENSSL_ERR_H
+#include <openssl/err.h>
+#endif
+#endif /* HAVE_SSL */
+
+
+/** keeping track of the async ids */
+struct track_id {
+       /** the id to pass to libunbound to cancel */
+       int id;
+       /** true if cancelled */
+       int cancel;
+       /** a lock on this structure for thread safety */
+       lock_basic_type lock;
+};
+
+/**
+ * result list for the lookups
+ */
+struct lookinfo {
+       /** name to look up */
+       char* name;
+       /** tracking number that can be used to cancel the query */
+       int async_id;
+       /** error code from libunbound */
+       int err;
+       /** result from lookup */
+       struct ub_result* result;
+};
+
+/** global variable to see how many queries we have left */
+static int num_wait = 0;
+
+/** usage information for asynclook */
+static void usage(char* argv[])
+{
+       printf("usage: %s [options] name ...\n", argv[0]);
+       printf("names are looked up at the same time, asynchronously.\n");
+       printf("        -b : use blocking requests\n");
+       printf("        -c : cancel the requests\n");
+       printf("        -d : enable debug output\n");
+       printf("        -f addr : use addr, forward to that server\n");
+       printf("        -h : this help message\n");
+       printf("        -H fname : read hosts from fname\n");
+       printf("        -r fname : read resolv.conf from fname\n");
+       printf("        -t : use a resolver thread instead of forking a process\n");
+       printf("        -x : perform extended threaded test\n");
+       exit(1);
+}
+
+/** print result from lookup nicely */
+static void
+print_result(struct lookinfo* info)
+{
+       char buf[100];
+       if(info->err) /* error (from libunbound) */
+               printf("%s: error %s\n", info->name,
+                       ub_strerror(info->err));
+       else if(!info->result)
+               printf("%s: cancelled\n", info->name);
+       else if(info->result->havedata)
+               printf("%s: %s\n", info->name,
+                       inet_ntop(AF_INET, info->result->data[0],
+                       buf, (socklen_t)sizeof(buf)));
+       else {
+               /* there is no data, why that? */
+               if(info->result->rcode == 0 /*noerror*/ ||
+                       info->result->nxdomain)
+                       printf("%s: no data %s\n", info->name,
+                       info->result->nxdomain?"(no such host)":
+                       "(no IP4 address)");
+               else    /* some error (from the server) */
+                       printf("%s: DNS error %d\n", info->name,
+                               info->result->rcode);
+       }
+}
+
+/** this is a function of type ub_callback_t */
+static void 
+lookup_is_done(void* mydata, int err, struct ub_result* result)
+{
+       /* cast mydata back to the correct type */
+       struct lookinfo* info = (struct lookinfo*)mydata;
+       fprintf(stderr, "name %s resolved\n", info->name);
+       info->err = err;
+       info->result = result;
+       /* one less to wait for */
+       num_wait--;
+}
+
+/** check error, if bad, exit with error message */
+static void 
+checkerr(const char* desc, int err)
+{
+       if(err != 0) {
+               printf("%s error: %s\n", desc, ub_strerror(err));
+               exit(1);
+       }
+}
+
+#ifdef THREADS_DISABLED
+/** only one process can communicate with async worker */
+#define NUMTHR 1
+#else /* have threads */
+/** number of threads to make in extended test */
+#define NUMTHR 10
+#endif
+
+/** struct for extended thread info */
+struct ext_thr_info {
+       /** thread num for debug */
+       int thread_num;
+       /** thread id */
+       ub_thread_type tid;
+       /** context */
+       struct ub_ctx* ctx;
+       /** size of array to query */
+       int argc;
+       /** array of names to query */
+       char** argv;
+       /** number of queries to do */
+       int numq;
+};
+
+/** if true, we are testing against 'localhost' and extra checking is done */
+static int q_is_localhost = 0;
+
+/** check result structure for the 'correct' answer */
+static void
+ext_check_result(const char* desc, int err, struct ub_result* result)
+{
+       checkerr(desc, err);
+       if(result == NULL) {
+               printf("%s: error result is NULL.\n", desc);
+               exit(1);
+       }
+       if(q_is_localhost) {
+               if(strcmp(result->qname, "localhost") != 0) {
+                       printf("%s: error result has wrong qname.\n", desc);
+                       exit(1);
+               }
+               if(result->qtype != LDNS_RR_TYPE_A) {
+                       printf("%s: error result has wrong qtype.\n", desc);
+                       exit(1);
+               }
+               if(result->qclass != LDNS_RR_CLASS_IN) {
+                       printf("%s: error result has wrong qclass.\n", desc);
+                       exit(1);
+               }
+               if(result->data == NULL) {
+                       printf("%s: error result->data is NULL.\n", desc);
+                       exit(1);
+               }
+               if(result->len == NULL) {
+                       printf("%s: error result->len is NULL.\n", desc);
+                       exit(1);
+               }
+               if(result->rcode != 0) {
+                       printf("%s: error result->rcode is set.\n", desc);
+                       exit(1);
+               }
+               if(result->havedata == 0) {
+                       printf("%s: error result->havedata is unset.\n", desc);
+                       exit(1);
+               }
+               if(result->nxdomain != 0) {
+                       printf("%s: error result->nxdomain is set.\n", desc);
+                       exit(1);
+               }
+               if(result->secure || result->bogus) {
+                       printf("%s: error result->secure or bogus is set.\n", 
+                               desc);
+                       exit(1);
+               }
+               if(result->data[0] == NULL) {
+                       printf("%s: error result->data[0] is NULL.\n", desc);
+                       exit(1);
+               }
+               if(result->len[0] != 4) {
+                       printf("%s: error result->len[0] is wrong.\n", desc);
+                       exit(1);
+               }
+               if(result->len[1] != 0 || result->data[1] != NULL) {
+                       printf("%s: error result->data[1] or len[1] is "
+                               "wrong.\n", desc);
+                       exit(1);
+               }
+               if(result->answer_packet == NULL) {
+                       printf("%s: error result->answer_packet is NULL.\n", 
+                               desc);
+                       exit(1);
+               }
+               if(result->answer_len != 54) {
+                       printf("%s: error result->answer_len is wrong.\n", 
+                               desc);
+                       exit(1);
+               }
+       }
+}
+
+/** extended bg result callback, this function is ub_callback_t */
+static void 
+ext_callback(void* mydata, int err, struct ub_result* result)
+{
+       struct track_id* my_id = (struct track_id*)mydata;
+       int doprint = 0;
+       if(my_id) {
+               /* I have an id, make sure we are not cancelled */
+               lock_basic_lock(&my_id->lock);
+               if(doprint) 
+                       printf("cb %d: ", my_id->id);
+               if(my_id->cancel) {
+                       printf("error: query id=%d returned, but was cancelled\n",
+                               my_id->id);
+                       abort();
+                       exit(1);
+               }
+               lock_basic_unlock(&my_id->lock);
+       }
+       ext_check_result("ext_callback", err, result);
+       log_assert(result);
+       if(doprint) {
+               struct lookinfo pi;
+               pi.name = result?result->qname:"noname";
+               pi.result = result;
+               pi.err = 0;
+               print_result(&pi);
+       }
+       ub_resolve_free(result);
+}
+
+/** extended thread worker */
+static void*
+ext_thread(void* arg)
+{
+       struct ext_thr_info* inf = (struct ext_thr_info*)arg;
+       int i, r;
+       struct ub_result* result;
+       struct track_id* async_ids = NULL;
+       log_thread_set(&inf->thread_num);
+       if(inf->thread_num > NUMTHR*2/3) {
+               async_ids = (struct track_id*)calloc((size_t)inf->numq, sizeof(struct track_id));
+               if(!async_ids) {
+                       printf("out of memory\n");
+                       exit(1);
+               }
+               for(i=0; i<inf->numq; i++) {
+                       lock_basic_init(&async_ids[i].lock);
+               }
+       }
+       for(i=0; i<inf->numq; i++) {
+               if(async_ids) {
+                       r = ub_resolve_async(inf->ctx, 
+                               inf->argv[i%inf->argc], LDNS_RR_TYPE_A, 
+                               LDNS_RR_CLASS_IN, &async_ids[i], ext_callback, 
+                               &async_ids[i].id);
+                       checkerr("ub_resolve_async", r);
+                       if(i > 100) {
+                               lock_basic_lock(&async_ids[i-100].lock);
+                               r = ub_cancel(inf->ctx, async_ids[i-100].id);
+                               if(r != UB_NOID)
+                                       async_ids[i-100].cancel=1;
+                               lock_basic_unlock(&async_ids[i-100].lock);
+                               if(r != UB_NOID) 
+                                       checkerr("ub_cancel", r);
+                       }
+               } else if(inf->thread_num > NUMTHR/2) {
+                       /* async */
+                       r = ub_resolve_async(inf->ctx, 
+                               inf->argv[i%inf->argc], LDNS_RR_TYPE_A, 
+                               LDNS_RR_CLASS_IN, NULL, ext_callback, NULL);
+                       checkerr("ub_resolve_async", r);
+               } else  {
+                       /* blocking */
+                       r = ub_resolve(inf->ctx, inf->argv[i%inf->argc], 
+                               LDNS_RR_TYPE_A, LDNS_RR_CLASS_IN, &result);
+                       ext_check_result("ub_resolve", r, result);
+                       ub_resolve_free(result);
+               }
+       }
+       if(inf->thread_num > NUMTHR/2) {
+               r = ub_wait(inf->ctx);
+               checkerr("ub_ctx_wait", r);
+       }
+       /* if these locks are destroyed, or if the async_ids is freed, then
+          a use-after-free happens in another thread.
+          The allocation is only part of this test, though. */
+       /*
+       if(async_ids) {
+               for(i=0; i<inf->numq; i++) {
+                       lock_basic_destroy(&async_ids[i].lock);
+               }
+       }
+       free(async_ids);
+       */
+       
+       return NULL;
+}
+
+/** perform extended threaded test */
+static int
+ext_test(struct ub_ctx* ctx, int argc, char** argv)
+{
+       struct ext_thr_info inf[NUMTHR];
+       int i;
+       if(argc == 1 && strcmp(argv[0], "localhost") == 0)
+               q_is_localhost = 1;
+       printf("extended test start (%d threads)\n", NUMTHR);
+       for(i=0; i<NUMTHR; i++) {
+               /* 0 = this, 1 = library bg worker */
+               inf[i].thread_num = i+2;
+               inf[i].ctx = ctx;
+               inf[i].argc = argc;
+               inf[i].argv = argv;
+               inf[i].numq = 100;
+               ub_thread_create(&inf[i].tid, ext_thread, &inf[i]);
+       }
+       /* the work happens here */
+       for(i=0; i<NUMTHR; i++) {
+               ub_thread_join(inf[i].tid);
+       }
+       printf("extended test end\n");
+       ub_ctx_delete(ctx);
+       checklock_stop();
+       return 0;
+}
+
+/** getopt global, in case header files fail to declare it. */
+extern int optind;
+/** getopt global, in case header files fail to declare it. */
+extern char* optarg;
+
+/** main program for asynclook */
+int main(int argc, char** argv) 
+{
+       int c;
+       struct ub_ctx* ctx;
+       struct lookinfo* lookups;
+       int i, r, cancel=0, blocking=0, ext=0;
+
+       /* init log now because solaris thr_key_create() is not threadsafe */
+       log_init(0,0,0);
+       /* lock debug start (if any) */
+       checklock_start();
+
+       /* create context */
+       ctx = ub_ctx_create();
+       if(!ctx) {
+               printf("could not create context, %s\n", strerror(errno));
+               return 1;
+       }
+
+       /* command line options */
+       if(argc == 1) {
+               usage(argv);
+       }
+       while( (c=getopt(argc, argv, "bcdf:hH:r:tx")) != -1) {
+               switch(c) {
+                       case 'd':
+                               r = ub_ctx_debuglevel(ctx, 3);
+                               checkerr("ub_ctx_debuglevel", r);
+                               break;
+                       case 't':
+                               r = ub_ctx_async(ctx, 1);
+                               checkerr("ub_ctx_async", r);
+                               break;
+                       case 'c':
+                               cancel = 1;
+                               break;
+                       case 'b':
+                               blocking = 1;
+                               break;
+                       case 'r':
+                               r = ub_ctx_resolvconf(ctx, optarg);
+                               if(r != 0) {
+                                       printf("ub_ctx_resolvconf "
+                                               "error: %s : %s\n",
+                                               ub_strerror(r), 
+                                               strerror(errno));
+                                       return 1;
+                               }
+                               break;
+                       case 'H':
+                               r = ub_ctx_hosts(ctx, optarg);
+                               if(r != 0) {
+                                       printf("ub_ctx_hosts "
+                                               "error: %s : %s\n",
+                                               ub_strerror(r), 
+                                               strerror(errno));
+                                       return 1;
+                               }
+                               break;
+                       case 'f':
+                               r = ub_ctx_set_fwd(ctx, optarg);
+                               checkerr("ub_ctx_set_fwd", r);
+                               break;
+                       case 'x':
+                               ext = 1;
+                               break;
+                       case 'h':
+                       case '?':
+                       default:
+                               usage(argv);
+               }
+       }
+       argc -= optind;
+       argv += optind;
+
+#ifdef HAVE_SSL
+#ifdef HAVE_ERR_LOAD_CRYPTO_STRINGS
+       ERR_load_crypto_strings();
+#endif
+#if OPENSSL_VERSION_NUMBER < 0x10100000 || !defined(HAVE_OPENSSL_INIT_SSL)
+       ERR_load_SSL_strings();
+#endif
+#if OPENSSL_VERSION_NUMBER < 0x10100000 || !defined(HAVE_OPENSSL_INIT_CRYPTO)
+       OpenSSL_add_all_algorithms();
+#else
+       OPENSSL_init_crypto(OPENSSL_INIT_ADD_ALL_CIPHERS
+               | OPENSSL_INIT_ADD_ALL_DIGESTS
+               | OPENSSL_INIT_LOAD_CRYPTO_STRINGS, NULL);
+#endif
+#if OPENSSL_VERSION_NUMBER < 0x10100000 || !defined(HAVE_OPENSSL_INIT_SSL)
+       (void)SSL_library_init();
+#else
+       (void)OPENSSL_init_ssl(OPENSSL_INIT_LOAD_SSL_STRINGS, NULL);
+#endif
+#endif /* HAVE_SSL */
+
+       if(ext)
+               return ext_test(ctx, argc, argv);
+
+       /* allocate array for results. */
+       lookups = (struct lookinfo*)calloc((size_t)argc, 
+               sizeof(struct lookinfo));
+       if(!lookups) {
+               printf("out of memory\n");
+               return 1;
+       }
+
+       /* perform asynchronous calls */
+       num_wait = argc;
+       for(i=0; i<argc; i++) {
+               lookups[i].name = argv[i];
+               if(blocking) {
+                       fprintf(stderr, "lookup %s\n", argv[i]);
+                       r = ub_resolve(ctx, argv[i], LDNS_RR_TYPE_A,
+                               LDNS_RR_CLASS_IN, &lookups[i].result);
+                       checkerr("ub_resolve", r);
+               } else {
+                       fprintf(stderr, "start async lookup %s\n", argv[i]);
+                       r = ub_resolve_async(ctx, argv[i], LDNS_RR_TYPE_A,
+                               LDNS_RR_CLASS_IN, &lookups[i], &lookup_is_done, 
+                               &lookups[i].async_id);
+                       checkerr("ub_resolve_async", r);
+               }
+       }
+       if(blocking)
+               num_wait = 0;
+       else if(cancel) {
+               for(i=0; i<argc; i++) {
+                       fprintf(stderr, "cancel %s\n", argv[i]);
+                       r = ub_cancel(ctx, lookups[i].async_id);
+                       if(r != UB_NOID) 
+                               checkerr("ub_cancel", r);
+               }
+               num_wait = 0;
+       }
+
+       /* wait while the hostnames are looked up. Do something useful here */
+       if(num_wait > 0)
+           for(i=0; i<1000; i++) {
+               usleep(100000);
+               fprintf(stderr, "%g seconds passed\n", 0.1*(double)i);
+               r = ub_process(ctx);
+               checkerr("ub_process", r);
+               if(num_wait == 0)
+                       break;
+       }
+       if(i>=999) {
+               printf("timed out\n");
+               return 0;
+       }
+       printf("lookup complete\n");
+
+       /* print lookup results */
+       for(i=0; i<argc; i++) {
+               print_result(&lookups[i]);
+               ub_resolve_free(lookups[i].result);
+       }
+
+       ub_ctx_delete(ctx);
+       free(lookups);
+       checklock_stop();
+       return 0;
+}
diff --git a/usr.sbin/unbound/testcode/checklocks.c b/usr.sbin/unbound/testcode/checklocks.c
new file mode 100644 (file)
index 0000000..7e6f0bb
--- /dev/null
@@ -0,0 +1,859 @@
+/**
+ * testcode/checklocks.c - wrapper on locks that checks access.
+ *
+ * Copyright (c) 2007, NLnet Labs. All rights reserved.
+ * 
+ * This software is open source.
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 
+ * Neither the name of the NLNET LABS nor the names of its contributors may
+ * be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+ * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include <signal.h>
+#include "util/locks.h"   /* include before checklocks.h */
+#include "testcode/checklocks.h"
+
+/**
+ * \file
+ * Locks that are checked.
+ *
+ * Ugly hack: uses the fact that workers start with an int thread_num, and
+ * are passed to thread_create to make the thread numbers here the same as 
+ * those used for logging which is nice.
+ *
+ * Todo: 
+ *      - debug status print, of thread lock stacks, and current waiting.
+ */
+#ifdef USE_THREAD_DEBUG
+
+/** How long to wait before lock attempt is a failure. */
+#define CHECK_LOCK_TIMEOUT 120 /* seconds */
+/** How long to wait before join attempt is a failure. */
+#define CHECK_JOIN_TIMEOUT 120 /* seconds */
+
+/** if key has been created */
+static int key_created = 0;
+/** if the key was deleted, i.e. we have quit */
+static int key_deleted = 0;
+/** we hide the thread debug info with this key. */
+static ub_thread_key_type thr_debug_key;
+/** the list of threads, so all threads can be examined. NULL if unused. */
+static struct thr_check* thread_infos[THRDEBUG_MAX_THREADS];
+/** do we check locking order */
+int check_locking_order = 1;
+/** the pid of this runset, reasonably unique. */
+static pid_t check_lock_pid;
+
+/** print all possible debug info on the state of the system */
+static void total_debug_info(void);
+
+/** print pretty lock error and exit */
+static void lock_error(struct checked_lock* lock, 
+       const char* func, const char* file, int line, const char* err)
+{
+       log_err("lock error (description follows)");
+       log_err("Created at %s %s:%d", lock->create_func, 
+               lock->create_file, lock->create_line);
+       if(lock->holder_func && lock->holder_file)
+               log_err("Previously %s %s:%d", lock->holder_func, 
+                       lock->holder_file, lock->holder_line);
+       log_err("At %s %s:%d", func, file, line);
+       log_err("Error for %s lock: %s",
+               (lock->type==check_lock_mutex)?"mutex": (
+               (lock->type==check_lock_spinlock)?"spinlock": (
+               (lock->type==check_lock_rwlock)?"rwlock": "badtype")), err);
+       log_err("complete status display:");
+       total_debug_info();
+       fatal_exit("bailing out");
+}
+
+/** 
+ * Obtain lock on debug lock structure. This could be a deadlock by the caller.
+ * The debug code itself does not deadlock. Anyway, check with timeouts. 
+ * @param lock: on what to acquire lock.
+ * @param func: user level caller identification.
+ * @param file: user level caller identification.
+ * @param line: user level caller identification.
+ */
+static void
+acquire_locklock(struct checked_lock* lock, 
+       const char* func, const char* file, int line)
+{
+       struct timespec to;
+       int err;
+       int contend = 0;
+       /* first try; inc contention counter if not immediately */
+       if((err = pthread_mutex_trylock(&lock->lock))) {
+               if(err==EBUSY)
+                       contend++;
+               else fatal_exit("error in mutex_trylock: %s", strerror(err));
+       }
+       if(!err)
+               return; /* immediate success */
+       to.tv_sec = time(NULL) + CHECK_LOCK_TIMEOUT;
+       to.tv_nsec = 0;
+       err = pthread_mutex_timedlock(&lock->lock, &to);
+       if(err) {
+               log_err("in acquiring locklock: %s", strerror(err));
+               lock_error(lock, func, file, line, "acquire locklock");
+       }
+       /* since we hold the lock, we can edit the contention_count */
+       lock->contention_count += contend;
+}
+
+/** add protected region */
+void 
+lock_protect(void *p, void* area, size_t size)
+{
+       struct checked_lock* lock = *(struct checked_lock**)p;
+       struct protected_area* e = (struct protected_area*)malloc(
+               sizeof(struct protected_area));
+       if(!e)
+               fatal_exit("lock_protect: out of memory");
+       e->region = area;
+       e->size = size;
+       e->hold = malloc(size);
+       if(!e->hold)
+               fatal_exit("lock_protect: out of memory");
+       memcpy(e->hold, e->region, e->size);
+
+       acquire_locklock(lock, __func__, __FILE__, __LINE__);
+       e->next = lock->prot;
+       lock->prot = e;
+       LOCKRET(pthread_mutex_unlock(&lock->lock));
+}
+
+/** remove protected region */
+void
+lock_unprotect(void* mangled, void* area)
+{
+       struct checked_lock* lock = *(struct checked_lock**)mangled;
+       struct protected_area* p, **prevp;
+       if(!lock) 
+               return;
+       acquire_locklock(lock, __func__, __FILE__, __LINE__);
+       p = lock->prot;
+       prevp = &lock->prot;
+       while(p) {
+               if(p->region == area) {
+                       *prevp = p->next;
+                       free(p->hold);
+                       free(p);
+                       LOCKRET(pthread_mutex_unlock(&lock->lock));
+                       return;
+               }
+               prevp = &p->next;
+               p = p->next;
+       }
+       LOCKRET(pthread_mutex_unlock(&lock->lock));
+}
+
+/** 
+ * Check protected memory region. Memory compare. Exit on error. 
+ * @param lock: which lock to check.
+ * @param func: location we are now (when failure is detected).
+ * @param file: location we are now (when failure is detected).
+ * @param line: location we are now (when failure is detected).
+ */
+static void 
+prot_check(struct checked_lock* lock,
+       const char* func, const char* file, int line)
+{
+       struct protected_area* p = lock->prot;
+       while(p) {
+               if(memcmp(p->hold, p->region, p->size) != 0) {
+                       log_hex("memory prev", p->hold, p->size);
+                       log_hex("memory here", p->region, p->size);
+                       lock_error(lock, func, file, line, 
+                               "protected area modified");
+               }
+               p = p->next;
+       }
+}
+
+/** Copy protected memory region */
+static void 
+prot_store(struct checked_lock* lock)
+{
+       struct protected_area* p = lock->prot;
+       while(p) {
+               memcpy(p->hold, p->region, p->size);
+               p = p->next;
+       }
+}
+
+/** get memory held by lock */
+size_t 
+lock_get_mem(void* pp)
+{
+       size_t s;
+       struct checked_lock* lock = *(struct checked_lock**)pp;
+       struct protected_area* p;
+       s = sizeof(struct checked_lock);
+       acquire_locklock(lock, __func__, __FILE__, __LINE__);
+       for(p = lock->prot; p; p = p->next) {
+               s += sizeof(struct protected_area);
+               s += p->size;
+       }
+       LOCKRET(pthread_mutex_unlock(&lock->lock));
+       return s;
+}
+
+/** write lock trace info to file, while you hold those locks */
+static void
+ordercheck_locklock(struct thr_check* thr, struct checked_lock* lock)
+{
+       int info[4];
+       if(!check_locking_order) return;
+       if(!thr->holding_first) return; /* no older lock, no info */
+       /* write: <lock id held> <lock id new> <file> <line> */
+       info[0] = thr->holding_first->create_thread;
+       info[1] = thr->holding_first->create_instance;
+       info[2] = lock->create_thread;
+       info[3] = lock->create_instance;
+       if(fwrite(info, 4*sizeof(int), 1, thr->order_info) != 1 ||
+               fwrite(lock->holder_file, strlen(lock->holder_file)+1, 1, 
+               thr->order_info) != 1 ||
+               fwrite(&lock->holder_line, sizeof(int), 1, 
+               thr->order_info) != 1)
+               log_err("fwrite: %s", strerror(errno));
+}
+
+/** write ordercheck lock creation details to file */
+static void 
+ordercheck_lockcreate(struct thr_check* thr, struct checked_lock* lock)
+{
+       /* write: <ffff = create> <lock id> <file> <line> */
+       int cmd = -1;
+       if(!check_locking_order) return;
+
+       if( fwrite(&cmd, sizeof(int), 1, thr->order_info) != 1 ||
+               fwrite(&lock->create_thread, sizeof(int), 1, 
+                       thr->order_info) != 1 ||
+               fwrite(&lock->create_instance, sizeof(int), 1, 
+                       thr->order_info) != 1 ||
+               fwrite(lock->create_file, strlen(lock->create_file)+1, 1, 
+                       thr->order_info) != 1 ||
+               fwrite(&lock->create_line, sizeof(int), 1, 
+               thr->order_info) != 1)
+               log_err("fwrite: %s", strerror(errno));
+}
+
+/** alloc struct, init lock empty */
+void 
+checklock_init(enum check_lock_type type, struct checked_lock** lock,
+        const char* func, const char* file, int line)
+{
+       struct checked_lock* e = (struct checked_lock*)calloc(1, 
+               sizeof(struct checked_lock));
+       struct thr_check *thr = (struct thr_check*)pthread_getspecific(
+               thr_debug_key);
+       if(!e)
+               fatal_exit("%s %s %d: out of memory", func, file, line);
+       if(!thr) {
+               /* this is called when log_init() calls lock_init()
+                * functions, and the test check code has not yet
+                * been initialised.  But luckily, the checklock_start()
+                * routine can be called multiple times without ill effect.
+                */
+               checklock_start();
+               thr = (struct thr_check*)pthread_getspecific(thr_debug_key);
+       }
+       if(!thr)
+               fatal_exit("%s %s %d: lock_init no thread info", func, file,
+                       line);
+       *lock = e;
+       e->type = type;
+       e->create_func = func;
+       e->create_file = file;
+       e->create_line = line;
+       e->create_thread = thr->num;
+       e->create_instance = thr->locks_created++;
+       ordercheck_lockcreate(thr, e);
+       LOCKRET(pthread_mutex_init(&e->lock, NULL));
+       switch(e->type) {
+               case check_lock_mutex:
+                       LOCKRET(pthread_mutex_init(&e->u.mutex, NULL));
+                       break;
+               case check_lock_spinlock:
+                       LOCKRET(pthread_spin_init(&e->u.spinlock, PTHREAD_PROCESS_PRIVATE));
+                       break;
+               case check_lock_rwlock:
+                       LOCKRET(pthread_rwlock_init(&e->u.rwlock, NULL));
+                       break;
+               default:
+                       log_assert(0);
+       }
+}
+
+/** delete prot items */
+static void 
+prot_clear(struct checked_lock* lock)
+{
+       struct protected_area* p=lock->prot, *np;
+       while(p) {
+               np = p->next;
+               free(p->hold);
+               free(p);
+               p = np;
+       }
+}
+
+/** check if type is OK for the lock given */
+static void 
+checktype(enum check_lock_type type, struct checked_lock* lock,
+        const char* func, const char* file, int line)
+{
+       if(!lock) 
+               fatal_exit("use of null/deleted lock at %s %s:%d", 
+                       func, file, line);
+       if(type != lock->type) {
+               lock_error(lock, func, file, line, "wrong lock type");
+       }
+}
+
+/** check if OK, free struct */
+void 
+checklock_destroy(enum check_lock_type type, struct checked_lock** lock,
+        const char* func, const char* file, int line)
+{
+       const size_t contention_interest = 1; /* promille contented locks */
+       struct checked_lock* e;
+       if(!lock) 
+               return;
+       e = *lock;
+       if(!e)
+               return;
+       checktype(type, e, func, file, line);
+
+       /* check if delete is OK */
+       acquire_locklock(e, func, file, line);
+       if(e->hold_count != 0)
+               lock_error(e, func, file, line, "delete while locked.");
+       if(e->wait_count != 0)
+               lock_error(e, func, file, line, "delete while waited on.");
+       prot_check(e, func, file, line);
+       *lock = NULL; /* use after free will fail */
+       LOCKRET(pthread_mutex_unlock(&e->lock));
+
+       /* contention, look at fraction in trouble. */
+       if(e->history_count > 1 &&
+          1000*e->contention_count/e->history_count > contention_interest) {
+               log_info("lock created %s %s %d has contention %u of %u (%d%%)",
+                       e->create_func, e->create_file, e->create_line,
+                       (unsigned int)e->contention_count, 
+                       (unsigned int)e->history_count,
+                       (int)(100*e->contention_count/e->history_count));
+       }
+
+       /* delete it */
+       LOCKRET(pthread_mutex_destroy(&e->lock));
+       prot_clear(e);
+       /* since nobody holds the lock - see check above, no need to unlink 
+        * from the thread-held locks list. */
+       switch(e->type) {
+               case check_lock_mutex:
+                       LOCKRET(pthread_mutex_destroy(&e->u.mutex));
+                       break;
+               case check_lock_spinlock:
+                       LOCKRET(pthread_spin_destroy(&e->u.spinlock));
+                       break;
+               case check_lock_rwlock:
+                       LOCKRET(pthread_rwlock_destroy(&e->u.rwlock));
+                       break;
+               default:
+                       log_assert(0);
+       }
+       memset(e, 0, sizeof(struct checked_lock));
+       free(e);
+}
+
+/** finish acquiring lock, shared between _(rd|wr||)lock() routines */
+static void 
+finish_acquire_lock(struct thr_check* thr, struct checked_lock* lock,
+        const char* func, const char* file, int line)
+{
+       thr->waiting = NULL;
+       lock->wait_count --;
+       lock->holder = thr;
+       lock->hold_count ++;
+       lock->holder_func = func;
+       lock->holder_file = file;
+       lock->holder_line = line;
+       ordercheck_locklock(thr, lock);
+       
+       /* insert in thread lock list, as first */
+       lock->prev_held_lock[thr->num] = NULL;
+       lock->next_held_lock[thr->num] = thr->holding_first;
+       if(thr->holding_first)
+               /* no need to lock it, since this thread already holds the
+                * lock (since it is on this list) and we only edit thr->num
+                * member in array. So it is safe.  */
+               thr->holding_first->prev_held_lock[thr->num] = lock;
+       else    thr->holding_last = lock;
+       thr->holding_first = lock;
+}
+
+/**
+ * Locking routine.
+ * @param type: as passed by user.
+ * @param lock: as passed by user.
+ * @param func: caller location.
+ * @param file: caller location.
+ * @param line: caller location.
+ * @param tryfunc: the pthread_mutex_trylock or similar function.
+ * @param timedfunc: the pthread_mutex_timedlock or similar function.
+ *     Uses absolute timeout value.
+ * @param arg: what to pass to tryfunc and timedlock.
+ * @param exclusive: if lock must be exclusive (only one allowed).
+ * @param getwr: if attempts to get writelock (or readlock) for rwlocks.
+ */
+static void 
+checklock_lockit(enum check_lock_type type, struct checked_lock* lock,
+        const char* func, const char* file, int line,
+       int (*tryfunc)(void*), int (*timedfunc)(void*, struct timespec*),
+       void* arg, int exclusive, int getwr)
+{
+       int err;
+       int contend = 0;
+       struct thr_check *thr = (struct thr_check*)pthread_getspecific(
+               thr_debug_key);
+       checktype(type, lock, func, file, line);
+       if(!thr) lock_error(lock, func, file, line, "no thread info");
+       
+       acquire_locklock(lock, func, file, line);
+       lock->wait_count ++;
+       thr->waiting = lock;
+       if(exclusive && lock->hold_count > 0 && lock->holder == thr) 
+               lock_error(lock, func, file, line, "thread already owns lock");
+       if(type==check_lock_rwlock && getwr && lock->writeholder == thr)
+               lock_error(lock, func, file, line, "thread already has wrlock");
+       LOCKRET(pthread_mutex_unlock(&lock->lock));
+
+       /* first try; if busy increase contention counter */
+       if((err=tryfunc(arg))) {
+               struct timespec to;
+               if(err != EBUSY) log_err("trylock: %s", strerror(err));
+               to.tv_sec = time(NULL) + CHECK_LOCK_TIMEOUT;
+               to.tv_nsec = 0;
+               if((err=timedfunc(arg, &to))) {
+                       if(err == ETIMEDOUT)
+                               lock_error(lock, func, file, line, 
+                                       "timeout possible deadlock");
+                       log_err("timedlock: %s", strerror(err));
+               }
+               contend ++;
+       }
+       /* got the lock */
+
+       acquire_locklock(lock, func, file, line);
+       lock->contention_count += contend;
+       lock->history_count++;
+       if(exclusive && lock->hold_count > 0)
+               lock_error(lock, func, file, line, "got nonexclusive lock");
+       if(type==check_lock_rwlock && getwr && lock->writeholder)
+               lock_error(lock, func, file, line, "got nonexclusive wrlock");
+       if(type==check_lock_rwlock && getwr)
+               lock->writeholder = thr;
+       /* check the memory areas for unauthorized changes,
+        * between last unlock time and current lock time.
+        * we check while holding the lock (threadsafe).
+        */
+       if(getwr || exclusive)
+               prot_check(lock, func, file, line);
+       finish_acquire_lock(thr, lock, func, file, line);
+       LOCKRET(pthread_mutex_unlock(&lock->lock));
+}
+
+/** helper for rdlock: try */
+static int try_rd(void* arg)
+{ return pthread_rwlock_tryrdlock((pthread_rwlock_t*)arg); }
+/** helper for rdlock: timed */
+static int timed_rd(void* arg, struct timespec* to)
+{ return pthread_rwlock_timedrdlock((pthread_rwlock_t*)arg, to); }
+
+/** check if OK, lock */
+void 
+checklock_rdlock(enum check_lock_type type, struct checked_lock* lock,
+        const char* func, const char* file, int line)
+{
+       if(key_deleted)
+               return;
+
+       log_assert(type == check_lock_rwlock);
+       checklock_lockit(type, lock, func, file, line,
+               try_rd, timed_rd, &lock->u.rwlock, 0, 0);
+}
+
+/** helper for wrlock: try */
+static int try_wr(void* arg)
+{ return pthread_rwlock_trywrlock((pthread_rwlock_t*)arg); }
+/** helper for wrlock: timed */
+static int timed_wr(void* arg, struct timespec* to)
+{ return pthread_rwlock_timedwrlock((pthread_rwlock_t*)arg, to); }
+
+/** check if OK, lock */
+void 
+checklock_wrlock(enum check_lock_type type, struct checked_lock* lock,
+        const char* func, const char* file, int line)
+{
+       if(key_deleted)
+               return;
+       log_assert(type == check_lock_rwlock);
+       checklock_lockit(type, lock, func, file, line,
+               try_wr, timed_wr, &lock->u.rwlock, 0, 1);
+}
+
+/** helper for lock mutex: try */
+static int try_mutex(void* arg)
+{ return pthread_mutex_trylock((pthread_mutex_t*)arg); }
+/** helper for lock mutex: timed */
+static int timed_mutex(void* arg, struct timespec* to)
+{ return pthread_mutex_timedlock((pthread_mutex_t*)arg, to); }
+
+/** helper for lock spinlock: try */
+static int try_spinlock(void* arg)
+{ return pthread_spin_trylock((pthread_spinlock_t*)arg); }
+/** helper for lock spinlock: timed */
+static int timed_spinlock(void* arg, struct timespec* to)
+{
+       int err;
+       /* spin for 5 seconds. (ouch for the CPU, but it beats forever) */
+       while( (err=try_spinlock(arg)) == EBUSY) {
+#ifndef S_SPLINT_S
+               if(time(NULL) >= to->tv_sec)
+                       return ETIMEDOUT;
+               usleep(1000); /* in 1/1000000s of a second */
+#endif
+       }
+       return err;
+}
+
+/** check if OK, lock */
+void 
+checklock_lock(enum check_lock_type type, struct checked_lock* lock,
+        const char* func, const char* file, int line)
+{
+       if(key_deleted)
+               return;
+       log_assert(type != check_lock_rwlock);
+       switch(type) {
+               case check_lock_mutex:
+                       checklock_lockit(type, lock, func, file, line,
+                               try_mutex, timed_mutex, &lock->u.mutex, 1, 0);
+                       break;
+               case check_lock_spinlock:
+                       /* void* cast needed because 'volatile' on some OS */
+                       checklock_lockit(type, lock, func, file, line,
+                               try_spinlock, timed_spinlock, 
+                               (void*)&lock->u.spinlock, 1, 0);
+                       break;
+               default:
+                       log_assert(0);
+       }
+}
+
+/** check if OK, unlock */
+void 
+checklock_unlock(enum check_lock_type type, struct checked_lock* lock,
+        const char* func, const char* file, int line)
+{
+       struct thr_check *thr;
+       if(key_deleted)
+               return;
+       thr = (struct thr_check*)pthread_getspecific(thr_debug_key);
+       checktype(type, lock, func, file, line);
+       if(!thr) lock_error(lock, func, file, line, "no thread info");
+
+       acquire_locklock(lock, func, file, line);
+       /* was this thread even holding this lock? */
+       if(thr->holding_first != lock &&
+               lock->prev_held_lock[thr->num] == NULL) {
+               lock_error(lock, func, file, line, "unlock nonlocked lock");
+       }
+       if(lock->hold_count <= 0)
+               lock_error(lock, func, file, line, "too many unlocks");
+
+       /* store this point as last touched by */
+       lock->holder = thr;
+       lock->hold_count --;
+       lock->holder_func = func;
+       lock->holder_file = file;
+       lock->holder_line = line;
+
+       /* delete from thread holder list */
+       /* no need to lock other lockstructs, because they are all on the
+        * held-locks list, and this thread holds their locks.
+        * we only touch the thr->num members, so it is safe.  */
+       if(thr->holding_first == lock)
+               thr->holding_first = lock->next_held_lock[thr->num];
+       if(thr->holding_last == lock)
+               thr->holding_last = lock->prev_held_lock[thr->num];
+       if(lock->next_held_lock[thr->num])
+               lock->next_held_lock[thr->num]->prev_held_lock[thr->num] =
+                       lock->prev_held_lock[thr->num];
+       if(lock->prev_held_lock[thr->num])
+               lock->prev_held_lock[thr->num]->next_held_lock[thr->num] =
+                       lock->next_held_lock[thr->num];
+       lock->next_held_lock[thr->num] = NULL;
+       lock->prev_held_lock[thr->num] = NULL;
+
+       if(type==check_lock_rwlock && lock->writeholder == thr) {
+               lock->writeholder = NULL;
+               prot_store(lock);
+       } else if(type != check_lock_rwlock) {
+               /* store memory areas that are protected, for later checks */
+               prot_store(lock);
+       }
+       LOCKRET(pthread_mutex_unlock(&lock->lock));
+
+       /* unlock it */
+       switch(type) {
+               case check_lock_mutex:
+                       LOCKRET(pthread_mutex_unlock(&lock->u.mutex));
+                       break;
+               case check_lock_spinlock:
+                       LOCKRET(pthread_spin_unlock(&lock->u.spinlock));
+                       break;
+               case check_lock_rwlock:
+                       LOCKRET(pthread_rwlock_unlock(&lock->u.rwlock));
+                       break;
+               default:
+                       log_assert(0);
+       }
+}
+
+/** open order info debug file, thr->num must be valid */
+static void 
+open_lockorder(struct thr_check* thr)
+{
+       char buf[24];
+       time_t t;
+       snprintf(buf, sizeof(buf), "ublocktrace.%d", thr->num);
+       thr->order_info = fopen(buf, "w");
+       if(!thr->order_info)
+               fatal_exit("could not open %s: %s", buf, strerror(errno));
+       thr->locks_created = 0;
+       t = time(NULL);
+       /* write: <time_stamp> <runpid> <thread_num> */
+       if(fwrite(&t, sizeof(t), 1, thr->order_info) != 1 ||
+               fwrite(&thr->num, sizeof(thr->num), 1, thr->order_info) != 1 || 
+               fwrite(&check_lock_pid, sizeof(check_lock_pid), 1, 
+               thr->order_info) != 1)
+               log_err("fwrite: %s", strerror(errno));
+}
+
+/** checklock thread main, Inits thread structure */
+static void* checklock_main(void* arg)
+{
+       struct thr_check* thr = (struct thr_check*)arg; 
+       void* ret;
+       thr->id = pthread_self();
+       /* Hack to get same numbers as in log file */
+       thr->num = *(int*)(thr->arg);
+       log_assert(thr->num < THRDEBUG_MAX_THREADS);
+       /* as an aside, due to this, won't work for libunbound bg thread */
+       if(thread_infos[thr->num] != NULL)
+               log_warn("thread warning, thr->num %d not NULL", thr->num);
+       thread_infos[thr->num] = thr;
+       LOCKRET(pthread_setspecific(thr_debug_key, thr));
+       if(check_locking_order)
+               open_lockorder(thr);
+       ret = thr->func(thr->arg);
+       thread_infos[thr->num] = NULL;
+       if(check_locking_order)
+               fclose(thr->order_info);
+       free(thr);
+       return ret;
+}
+
+/** init the main thread */
+void checklock_start(void)
+{
+       if(key_deleted)
+               return;
+       if(!key_created) {
+               struct thr_check* thisthr = (struct thr_check*)calloc(1, 
+                       sizeof(struct thr_check));
+               if(!thisthr)
+                       fatal_exit("thrcreate: out of memory");
+               key_created = 1;
+               check_lock_pid = getpid();
+               LOCKRET(pthread_key_create(&thr_debug_key, NULL));
+               LOCKRET(pthread_setspecific(thr_debug_key, thisthr));
+               thread_infos[0] = thisthr;
+               if(check_locking_order)
+                       open_lockorder(thisthr);
+       }
+}
+
+/** stop checklocks */
+void checklock_stop(void)
+{
+       if(key_created) {
+               int i;
+               key_deleted = 1;
+               if(check_locking_order)
+                       fclose(thread_infos[0]->order_info);
+               free(thread_infos[0]);
+               thread_infos[0] = NULL;
+               for(i = 0; i < THRDEBUG_MAX_THREADS; i++)
+                       log_assert(thread_infos[i] == NULL);
+                       /* should have been cleaned up. */
+               LOCKRET(pthread_key_delete(thr_debug_key));
+               key_created = 0;
+       }
+}
+
+/** allocate debug info and create thread */
+void 
+checklock_thrcreate(pthread_t* id, void* (*func)(void*), void* arg)
+{
+       struct thr_check* thr = (struct thr_check*)calloc(1, 
+               sizeof(struct thr_check));
+       if(!thr)
+               fatal_exit("thrcreate: out of memory");
+       if(!key_created) {
+               checklock_start();
+       }
+       thr->func = func;
+       thr->arg = arg;
+       LOCKRET(pthread_create(id, NULL, checklock_main, thr));
+}
+
+/** count number of thread infos */
+static int
+count_thread_infos(void)
+{
+       int cnt = 0;
+       int i;
+       for(i=0; i<THRDEBUG_MAX_THREADS; i++)
+               if(thread_infos[i])
+                       cnt++;
+       return cnt;
+}
+
+/** print lots of info on a lock */
+static void
+lock_debug_info(struct checked_lock* lock)
+{
+       if(!lock) return;
+       log_info("+++ Lock %llx, %d %d create %s %s %d",
+               (unsigned long long)(size_t)lock, 
+               lock->create_thread, lock->create_instance, 
+               lock->create_func, lock->create_file, lock->create_line);
+       log_info("lock type: %s",
+               (lock->type==check_lock_mutex)?"mutex": (
+               (lock->type==check_lock_spinlock)?"spinlock": (
+               (lock->type==check_lock_rwlock)?"rwlock": "badtype")));
+       log_info("lock contention %u, history:%u, hold:%d, wait:%d", 
+               (unsigned)lock->contention_count, (unsigned)lock->history_count,
+               lock->hold_count, lock->wait_count);
+       log_info("last touch %s %s %d", lock->holder_func, lock->holder_file,
+               lock->holder_line);
+       log_info("holder thread %d, writeholder thread %d",
+               lock->holder?lock->holder->num:-1,
+               lock->writeholder?lock->writeholder->num:-1);
+}
+
+/** print debug locks held by a thread */
+static void
+held_debug_info(struct thr_check* thr, struct checked_lock* lock)
+{
+       if(!lock) return;
+       lock_debug_info(lock);
+       held_debug_info(thr, lock->next_held_lock[thr->num]);
+}
+
+/** print debug info for a thread */
+static void
+thread_debug_info(struct thr_check* thr)
+{
+       struct checked_lock* w = NULL;
+       struct checked_lock* f = NULL;
+       struct checked_lock* l = NULL;
+       if(!thr) return;
+       log_info("pthread id is %x", (int)thr->id);
+       log_info("thread func is %llx", (unsigned long long)(size_t)thr->func);
+       log_info("thread arg is %llx (%d)",
+               (unsigned long long)(size_t)thr->arg, 
+               (thr->arg?*(int*)thr->arg:0));
+       log_info("thread num is %d", thr->num);
+       log_info("locks created %d", thr->locks_created);
+       log_info("open file for lockinfo: %s", 
+               thr->order_info?"yes, flushing":"no");
+       fflush(thr->order_info);
+       w = thr->waiting;
+       f = thr->holding_first;
+       l = thr->holding_last;
+       log_info("thread waiting for a lock: %s %llx", w?"yes":"no",
+               (unsigned long long)(size_t)w);
+       lock_debug_info(w);
+       log_info("thread holding first: %s, last: %s", f?"yes":"no", 
+               l?"yes":"no");
+       held_debug_info(thr, f);
+}
+
+static void
+total_debug_info(void)
+{
+       int i;
+       log_info("checklocks: supervising %d threads.",
+               count_thread_infos());
+       if(!key_created) {
+               log_info("No thread debug key created yet");
+       }
+       for(i=0; i<THRDEBUG_MAX_THREADS; i++) {
+               if(thread_infos[i]) {
+                       log_info("*** Thread %d information: ***", i);
+                       thread_debug_info(thread_infos[i]);
+               }
+       }
+}
+
+/** signal handler for join timeout, Exits */
+static RETSIGTYPE joinalarm(int ATTR_UNUSED(sig))
+{
+       log_err("join thread timeout. hangup or deadlock. Info follows.");
+       total_debug_info();
+       fatal_exit("join thread timeout. hangup or deadlock.");
+}
+
+/** wait for thread with a timeout */
+void 
+checklock_thrjoin(pthread_t thread)
+{
+       /* wait with a timeout */
+       if(signal(SIGALRM, joinalarm) == SIG_ERR)
+               fatal_exit("signal(): %s", strerror(errno));
+       (void)alarm(CHECK_JOIN_TIMEOUT);
+       LOCKRET(pthread_join(thread, NULL));
+       (void)alarm(0);
+}
+
+#endif /* USE_THREAD_DEBUG */
diff --git a/usr.sbin/unbound/testcode/checklocks.h b/usr.sbin/unbound/testcode/checklocks.h
new file mode 100644 (file)
index 0000000..61cc6fb
--- /dev/null
@@ -0,0 +1,343 @@
+/**
+ * testcode/checklocks.h - wrapper on locks that checks access.
+ *
+ * Copyright (c) 2007, NLnet Labs. All rights reserved.
+ * 
+ * This software is open source.
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 
+ * Neither the name of the NLNET LABS nor the names of its contributors may
+ * be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+ * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TESTCODE_CHECK_LOCKS_H
+#define TESTCODE_CHECK_LOCKS_H
+
+/**
+ * \file
+ * Locks that are checked.
+ *
+ * Holds information per lock and per thread.
+ * That information is protected by a mutex (unchecked).
+ *
+ * Checks:
+ *      o which func, file, line created the lock.
+ *      o contention count, measures amount of contention on the lock.
+ *      o the memory region(s) that the lock protects are
+ *        memcmp'ed to ascertain no race conditions.
+ *      o checks that locks are unlocked properly (before deletion).
+ *        keeps which func, file, line that locked it.
+ *     o checks deadlocks with timeout so it can print errors for them.
+ *
+ * Limitations:
+ *     o Detects unprotected memory access when the lock is locked or freed,
+ *       which detects races only if they happen, and only if in protected
+ *       memory areas.
+ *     o Detects deadlocks by timeout, so approximately, as they happen.
+ *     o Does not check order of locking.
+ *     o Uses a lot of memory.
+ *     o The checks use locks themselves, changing scheduling,
+ *       thus changing what races you see.
+ */
+
+#ifdef USE_THREAD_DEBUG
+#ifndef HAVE_PTHREAD
+/* we need the *timed*lock() routines to use for deadlock detection. */
+#error "Need pthreads for checked locks"
+#endif
+/******************* THREAD DEBUG ************************/
+#include <pthread.h>
+
+/** How many threads to allocate for */
+#define THRDEBUG_MAX_THREADS 32 /* threads */
+/** do we check locking order */
+extern int check_locking_order;
+
+/**
+ * Protection memory area.
+ * It is copied to a holding buffer to compare against later.
+ * Note that it may encompass the lock structure.
+ */
+struct protected_area {
+       /** where the memory region starts */
+       void* region;
+       /** size of the region */
+       size_t size;
+       /** backbuffer that holds a copy, of same size. */
+       void* hold;
+       /** next protected area in list */
+       struct protected_area* next;
+};
+
+/**
+ * Per thread information for locking debug wrappers. 
+ */
+struct thr_check {
+       /** thread id */
+       pthread_t id;
+       /** real thread func */
+       void* (*func)(void*);
+       /** func user arg */
+       void* arg;
+       /** number of thread in list structure */
+       int num;
+       /** instance number - how many locks have been created by thread */
+       int locks_created;
+       /** file to write locking order information to */
+       FILE* order_info;
+       /** 
+        * List of locks that this thread is holding, double
+        * linked list. The first element is the most recent lock acquired.
+        * So it represents the stack of locks acquired. (of all types).
+        */
+       struct checked_lock *holding_first, *holding_last;
+       /** if the thread is currently waiting for a lock, which one */
+       struct checked_lock* waiting;
+};
+
+/**
+ * One structure for all types of locks.
+ */
+struct checked_lock {
+       /** mutex for exclusive access to this structure */
+       pthread_mutex_t lock;
+       /** list of memory regions protected by this checked lock */
+       struct protected_area* prot;
+       /** where was this lock created */
+       const char* create_func, *create_file;
+       /** where was this lock created */
+       int create_line;
+       /** unique instance identifier */
+       int create_thread, create_instance;
+       /** contention count */
+       size_t contention_count;
+       /** number of times locked, ever */
+       size_t history_count;
+       /** hold count (how many threads are holding this lock) */
+       int hold_count;
+       /** how many threads are waiting for this lock */
+       int wait_count;
+       /** who touched it last */
+       const char* holder_func, *holder_file;
+       /** who touched it last */
+       int holder_line;
+       /** who owns the lock now */
+       struct thr_check* holder;
+       /** for rwlocks, the writelock holder */
+       struct thr_check* writeholder;
+
+       /** next lock a thread is holding (less recent) */
+       struct checked_lock* next_held_lock[THRDEBUG_MAX_THREADS];
+       /** prev lock a thread is holding (more recent) */
+       struct checked_lock* prev_held_lock[THRDEBUG_MAX_THREADS];
+
+       /** type of lock */
+       enum check_lock_type {
+               /** basic mutex */
+               check_lock_mutex,
+               /** fast spinlock */
+               check_lock_spinlock,
+               /** rwlock */
+               check_lock_rwlock
+       } type;
+       /** the lock itself, see type to disambiguate the union */
+       union {
+               /** mutex */
+               pthread_mutex_t mutex;
+               /** spinlock */
+               pthread_spinlock_t spinlock;
+               /** rwlock */
+               pthread_rwlock_t rwlock;
+       } u;
+};
+
+/**
+ * Additional call for the user to specify what areas are protected
+ * @param lock: the lock that protects the area. It can be inside the area.
+ *     The lock must be inited. Call with user lock. (any type).
+ *     It demangles the lock itself (struct checked_lock**).
+ * @param area: ptr to mem.
+ * @param size: length of area.
+ * You can call it multiple times with the same lock to give several areas.
+ * Call it when you are done initializing the area, since it will be copied
+ * at this time and protected right away against unauthorised changes until 
+ * the next lock() call is done.
+ */
+void lock_protect(void* lock, void* area, size_t size);
+
+/**
+ * Remove protected area from lock.
+ * No need to call this when deleting the lock.
+ * @param lock: the lock, any type, (struct checked_lock**).
+ * @param area: pointer to memory.
+ */
+void lock_unprotect(void* lock, void* area);
+
+/**
+ * Get memory associated with a checked lock
+ * @param lock: the checked lock, any type. (struct checked_lock**).
+ * @return: in bytes, including protected areas.
+ */
+size_t lock_get_mem(void* lock);
+
+/**
+ * Initialise checklock. Sets up internal debug structures.
+ */
+void checklock_start(void);
+
+/**
+ * Cleanup internal debug state.
+ */
+void checklock_stop(void);
+
+/**
+ * Init locks.
+ * @param type: what type of lock this is.
+ * @param lock: ptr to user alloced ptr structure. This is inited.
+ *     So an alloc is done and the ptr is stored as result.
+ * @param func: caller function name.
+ * @param file: caller file name.
+ * @param line: caller line number.
+ */
+void checklock_init(enum check_lock_type type, struct checked_lock** lock,
+       const char* func, const char* file, int line);
+
+/**
+ * Destroy locks. Free the structure.
+ * @param type: what type of lock this is.
+ * @param lock: ptr to user alloced structure. This is destroyed.
+ * @param func: caller function name.
+ * @param file: caller file name.
+ * @param line: caller line number.
+ */
+void checklock_destroy(enum check_lock_type type, struct checked_lock** lock,
+       const char* func, const char* file, int line);
+
+/**
+ * Acquire readlock.
+ * @param type: what type of lock this is. Had better be a rwlock.
+ * @param lock: ptr to lock.
+ * @param func: caller function name.
+ * @param file: caller file name.
+ * @param line: caller line number.
+ */
+void checklock_rdlock(enum check_lock_type type, struct checked_lock* lock,
+       const char* func, const char* file, int line);
+
+/**
+ * Acquire writelock.
+ * @param type: what type of lock this is. Had better be a rwlock.
+ * @param lock: ptr to lock.
+ * @param func: caller function name.
+ * @param file: caller file name.
+ * @param line: caller line number.
+ */
+void checklock_wrlock(enum check_lock_type type, struct checked_lock* lock,
+       const char* func, const char* file, int line);
+
+/**
+ * Locks.
+ * @param type: what type of lock this is. Had better be mutex or spinlock.
+ * @param lock: the lock.
+ * @param func: caller function name.
+ * @param file: caller file name.
+ * @param line: caller line number.
+ */
+void checklock_lock(enum check_lock_type type, struct checked_lock* lock,
+       const char* func, const char* file, int line);
+
+/**
+ * Unlocks.
+ * @param type: what type of lock this is.
+ * @param lock: the lock.
+ * @param func: caller function name.
+ * @param file: caller file name.
+ * @param line: caller line number.
+ */
+void checklock_unlock(enum check_lock_type type, struct checked_lock* lock,
+       const char* func, const char* file, int line);
+
+/**
+ * Create thread.
+ * @param thr: Thread id, where to store result.
+ * @param func: thread start function.
+ * @param arg: user argument.
+ */
+void checklock_thrcreate(pthread_t* thr, void* (*func)(void*), void* arg);
+
+/**
+ * Wait for thread to exit. Returns thread return value.
+ * @param thread: thread to wait for.
+ */
+void checklock_thrjoin(pthread_t thread);
+
+/** structures to enable compiler type checking on the locks. 
+ * Also the pointer makes it so that the lock can be part of the protected
+ * region without any possible problem (since the ptr will stay the same.)
+ * i.e. there can be contention and readlocks stored in checked_lock, while
+ * the protected area stays the same, even though it contains (ptr to) lock.
+ */
+struct checked_lock_rw { struct checked_lock* c_rw; };
+/** structures to enable compiler type checking on the locks. */
+struct checked_lock_mutex { struct checked_lock* c_m; };
+/** structures to enable compiler type checking on the locks. */
+struct checked_lock_spl { struct checked_lock* c_spl; };
+
+/** debugging rwlock */
+typedef struct checked_lock_rw lock_rw_type;
+#define lock_rw_init(lock) checklock_init(check_lock_rwlock, &((lock)->c_rw), __func__, __FILE__, __LINE__)
+#define lock_rw_destroy(lock) checklock_destroy(check_lock_rwlock, &((lock)->c_rw), __func__, __FILE__, __LINE__)
+#define lock_rw_rdlock(lock) checklock_rdlock(check_lock_rwlock, (lock)->c_rw, __func__, __FILE__, __LINE__)
+#define lock_rw_wrlock(lock) checklock_wrlock(check_lock_rwlock, (lock)->c_rw, __func__, __FILE__, __LINE__)
+#define lock_rw_unlock(lock) checklock_unlock(check_lock_rwlock, (lock)->c_rw, __func__, __FILE__, __LINE__)
+
+/** debugging mutex */
+typedef struct checked_lock_mutex lock_basic_type;
+#define lock_basic_init(lock) checklock_init(check_lock_mutex, &((lock)->c_m), __func__, __FILE__, __LINE__)
+#define lock_basic_destroy(lock) checklock_destroy(check_lock_mutex, &((lock)->c_m), __func__, __FILE__, __LINE__)
+#define lock_basic_lock(lock) checklock_lock(check_lock_mutex, (lock)->c_m, __func__, __FILE__, __LINE__)
+#define lock_basic_unlock(lock) checklock_unlock(check_lock_mutex, (lock)->c_m, __func__, __FILE__, __LINE__)
+
+/** debugging spinlock */
+typedef struct checked_lock_spl lock_quick_type;
+#define lock_quick_init(lock) checklock_init(check_lock_spinlock, &((lock)->c_spl), __func__, __FILE__, __LINE__)
+#define lock_quick_destroy(lock) checklock_destroy(check_lock_spinlock, &((lock)->c_spl), __func__, __FILE__, __LINE__)
+#define lock_quick_lock(lock) checklock_lock(check_lock_spinlock, (lock)->c_spl, __func__, __FILE__, __LINE__)
+#define lock_quick_unlock(lock) checklock_unlock(check_lock_spinlock, (lock)->c_spl, __func__, __FILE__, __LINE__)
+
+/** we use the pthread id, our thr_check structure is kept behind the scenes */
+typedef pthread_t ub_thread_type;
+#define ub_thread_create(thr, func, arg) checklock_thrcreate(thr, func, arg)
+#define ub_thread_self() pthread_self()
+#define ub_thread_join(thread) checklock_thrjoin(thread)
+
+typedef pthread_key_t ub_thread_key_type;
+#define ub_thread_key_create(key, f) LOCKRET(pthread_key_create(key, f))
+#define ub_thread_key_set(key, v) LOCKRET(pthread_setspecific(key, v))
+#define ub_thread_key_get(key) pthread_getspecific(key)
+
+#endif /* USE_THREAD_DEBUG */
+#endif /* TESTCODE_CHECK_LOCKS_H */
diff --git a/usr.sbin/unbound/testcode/delayer.c b/usr.sbin/unbound/testcode/delayer.c
new file mode 100644 (file)
index 0000000..5489b59
--- /dev/null
@@ -0,0 +1,1185 @@
+/*
+ * testcode/delayer.c - debug program that delays queries to a server.
+ *
+ * Copyright (c) 2008, NLnet Labs. All rights reserved.
+ *
+ * This software is open source.
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 
+ * Neither the name of the NLNET LABS nor the names of its contributors may
+ * be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+ * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * \file
+ *
+ * This program delays queries made. It performs as a proxy to another
+ * server and delays queries to it.
+ */
+
+#include "config.h"
+#ifdef HAVE_GETOPT_H
+#include <getopt.h>
+#endif
+#ifdef HAVE_TIME_H
+#include <time.h>
+#endif
+#include <sys/time.h>
+#include "util/net_help.h"
+#include "util/config_file.h"
+#include "sldns/sbuffer.h"
+#include <signal.h>
+
+/** number of reads per select for delayer */
+#define TRIES_PER_SELECT 100
+
+/**
+ * The ring buffer
+ */
+struct ringbuf {
+       /** base of buffer */
+       uint8_t* buf;
+       /** size of buffer */
+       size_t size;
+       /** low mark, items start here */
+       size_t low;
+       /** high mark, items end here */
+       size_t high;
+};
+
+/**
+ * List of proxy fds that return replies from the server to our clients.
+ */
+struct proxy {
+       /** the fd to listen for replies from server */
+       int s;
+       /** last time this was used */
+       struct timeval lastuse;
+       /** remote address */
+       struct sockaddr_storage addr;
+       /** length of addr */
+       socklen_t addr_len;
+       /** number of queries waiting (in total) */
+       size_t numwait;
+       /** number of queries sent to server (in total) */
+       size_t numsent;
+       /** numberof answers returned to client (in total) */
+       size_t numreturn;
+       /** how many times repurposed */
+       size_t numreuse;
+       /** next in proxylist */
+       struct proxy* next;
+};
+
+/**
+ * An item that has to be TCP relayed
+ */
+struct tcp_send_list {
+       /** the data item */
+       uint8_t* item;
+       /** size of item */
+       size_t len;
+       /** time when the item can be transmitted on */
+       struct timeval wait;
+       /** how much of the item has already been transmitted */
+       size_t done;
+       /** next in list */
+       struct tcp_send_list* next;
+};
+
+/**
+ * List of TCP proxy fd pairs to TCP connect client to server 
+ */
+struct tcp_proxy {
+       /** the fd to listen for client query */
+       int client_s;
+       /** the fd to listen for server answer */
+       int server_s;
+
+       /** remote client address */
+       struct sockaddr_storage addr;
+       /** length of address */
+       socklen_t addr_len;
+       /** timeout on this entry */
+       struct timeval timeout;
+
+       /** list of query items to send to server */
+       struct tcp_send_list* querylist;
+       /** last in query list */
+       struct tcp_send_list* querylast;
+       /** list of answer items to send to client */
+       struct tcp_send_list* answerlist;
+       /** last in answerlist */
+       struct tcp_send_list* answerlast;
+
+       /** next in list */
+       struct tcp_proxy* next;
+};
+
+/** usage information for delayer */
+static void usage(char* argv[])
+{
+       printf("usage: %s [options]\n", argv[0]);
+       printf("        -f addr : use addr, forward to that server, @port.\n");
+       printf("        -b addr : bind to this address to listen.\n");
+       printf("        -p port : bind to this port (use 0 for random).\n");
+       printf("        -m mem  : use this much memory for waiting queries.\n");
+       printf("        -d delay: UDP queries are delayed n milliseconds.\n");
+       printf("                  TCP is delayed twice (on send, on recv).\n");
+       printf("        -h      : this help message\n");
+       exit(1);
+}
+
+/** timeval compare, t1 < t2 */
+static int
+dl_tv_smaller(struct timeval* t1, const struct timeval* t2) 
+{
+#ifndef S_SPLINT_S
+       if(t1->tv_sec < t2->tv_sec)
+               return 1;
+       if(t1->tv_sec == t2->tv_sec &&
+               t1->tv_usec < t2->tv_usec)
+               return 1;
+#endif
+       return 0;
+}
+
+/** timeval add, t1 += t2 */
+static void
+dl_tv_add(struct timeval* t1, const struct timeval* t2) 
+{
+#ifndef S_SPLINT_S
+       t1->tv_sec += t2->tv_sec;
+       t1->tv_usec += t2->tv_usec;
+       while(t1->tv_usec > 1000000) {
+               t1->tv_usec -= 1000000;
+               t1->tv_sec++;
+       }
+#endif
+}
+
+/** timeval subtract, t1 -= t2 */
+static void
+dl_tv_subtract(struct timeval* t1, const struct timeval* t2) 
+{
+#ifndef S_SPLINT_S
+       t1->tv_sec -= t2->tv_sec;
+       if(t1->tv_usec >= t2->tv_usec) {
+               t1->tv_usec -= t2->tv_usec;
+       } else {
+               t1->tv_sec--;
+               t1->tv_usec = 1000000-(t2->tv_usec-t1->tv_usec);
+       }
+#endif
+}
+
+
+/** create new ring buffer */
+static struct ringbuf*
+ring_create(size_t sz)
+{
+       struct ringbuf* r = (struct ringbuf*)calloc(1, sizeof(*r));
+       if(!r) fatal_exit("out of memory");
+       r->buf = (uint8_t*)malloc(sz);
+       if(!r->buf) fatal_exit("out of memory");
+       r->size = sz;
+       r->low = 0;
+       r->high = 0;
+       return r;
+}
+
+/** delete ring buffer */
+static void
+ring_delete(struct ringbuf* r)
+{
+       if(!r) return;
+       free(r->buf);
+       free(r);
+}
+
+/** add entry to ringbuffer */
+static void
+ring_add(struct ringbuf* r, sldns_buffer* pkt, struct timeval* now, 
+       struct timeval* delay, struct proxy* p)
+{
+       /* time -- proxy* -- 16bitlen -- message */
+       uint16_t len = (uint16_t)sldns_buffer_limit(pkt);
+       struct timeval when;
+       size_t needed;
+       uint8_t* where = NULL;
+       log_assert(sldns_buffer_limit(pkt) <= 65535);
+       needed = sizeof(when) + sizeof(p) + sizeof(len) + len;
+       /* put item into ringbuffer */
+       if(r->low < r->high) {
+               /* used part is in the middle */
+               if(r->size - r->high >= needed) {
+                       where = r->buf + r->high;
+                       r->high += needed;
+               } else if(r->low > needed) {
+                       /* wrap around ringbuffer */
+                       /* make sure r->low == r->high means empty */
+                       /* so r->low == r->high cannot be used to signify
+                        * a completely full ringbuf */
+                       if(r->size - r->high > sizeof(when)+sizeof(p)) {
+                               /* zero entry at end of buffer */
+                               memset(r->buf+r->high, 0, 
+                                       sizeof(when)+sizeof(p));
+                       }
+                       where = r->buf;
+                       r->high = needed;
+               } else {
+                       /* drop message */
+                       log_warn("warning: mem full, dropped message");
+                       return;
+               }
+       } else {
+               /* empty */
+               if(r->high == r->low) {
+                       where = r->buf;
+                       r->low = 0;
+                       r->high = needed;
+               /* unused part is in the middle */
+               /* so ringbuffer has wrapped around */
+               } else if(r->low - r->high > needed) {
+                       where = r->buf + r->high;
+                       r->high += needed;
+               } else {
+                       log_warn("warning: mem full, dropped message");
+                       return;
+               }
+       }
+       when = *now;
+       dl_tv_add(&when, delay);
+       /* copy it at where part */
+       log_assert(where != NULL);
+       memmove(where, &when, sizeof(when));
+       memmove(where+sizeof(when), &p, sizeof(p));
+       memmove(where+sizeof(when)+sizeof(p), &len, sizeof(len));
+       memmove(where+sizeof(when)+sizeof(p)+sizeof(len), 
+               sldns_buffer_begin(pkt), len);
+}
+
+/** see if the ringbuffer is empty */
+static int
+ring_empty(struct ringbuf* r)
+{
+       return (r->low == r->high);
+}
+
+/** peek at timevalue for next item in ring */
+static struct timeval*
+ring_peek_time(struct ringbuf* r)
+{
+       if(ring_empty(r))
+               return NULL;
+       return (struct timeval*)&r->buf[r->low];
+}
+
+/** get entry from ringbuffer */
+static int
+ring_pop(struct ringbuf* r, sldns_buffer* pkt, struct timeval* tv, 
+       struct proxy** p)
+{
+       /* time -- proxy* -- 16bitlen -- message */
+       uint16_t len;
+       uint8_t* where = NULL;
+       size_t done;
+       if(r->low == r->high)
+               return 0;
+       where = r->buf + r->low;
+       memmove(tv, where, sizeof(*tv));
+       memmove(p, where+sizeof(*tv), sizeof(*p));
+       memmove(&len, where+sizeof(*tv)+sizeof(*p), sizeof(len));
+       memmove(sldns_buffer_begin(pkt), 
+               where+sizeof(*tv)+sizeof(*p)+sizeof(len), len);
+       sldns_buffer_set_limit(pkt, (size_t)len);
+       done = sizeof(*tv)+sizeof(*p)+sizeof(len)+len;
+       /* move lowmark */
+       if(r->low < r->high) {
+               /* used part in middle */
+               log_assert(r->high - r->low >= done);
+               r->low += done;
+       } else {
+               /* unused part in middle */
+               log_assert(r->size - r->low >= done);
+               r->low += done;
+               if(r->size - r->low > sizeof(*tv)+sizeof(*p)) {
+                       /* see if it is zeroed; means end of buffer */
+                       struct proxy* pz;
+                       memmove(&pz, r->buf+r->low+sizeof(*tv), sizeof(pz));
+                       if(pz == NULL)
+                               r->low = 0;
+               } else r->low = 0;
+       }
+       if(r->low == r->high) {
+               r->low = 0; /* reset if empty */
+               r->high = 0;
+       }
+       return 1;
+}
+       
+/** signal handler global info */
+static volatile int do_quit = 0;
+
+/** signal handler for user quit */
+static RETSIGTYPE delayer_sigh(int sig)
+{
+       printf("exit on signal %d\n", sig);
+       do_quit = 1;
+}
+
+/** send out waiting packets */
+static void
+service_send(struct ringbuf* ring, struct timeval* now, sldns_buffer* pkt,
+       struct sockaddr_storage* srv_addr, socklen_t srv_len)
+{
+       struct proxy* p;
+       struct timeval tv;
+       ssize_t sent;
+       while(!ring_empty(ring) && 
+               dl_tv_smaller(ring_peek_time(ring), now)) {
+               /* this items needs to be sent out */
+               if(!ring_pop(ring, pkt, &tv, &p))
+                       fatal_exit("ringbuf error: pop failed");
+               verbose(1, "send out query %d.%6.6d", 
+                       (unsigned)tv.tv_sec, (unsigned)tv.tv_usec);
+               log_addr(1, "from client", &p->addr, p->addr_len);
+               /* send it */
+               sent = sendto(p->s, (void*)sldns_buffer_begin(pkt), 
+                       sldns_buffer_limit(pkt), 0, 
+                       (struct sockaddr*)srv_addr, srv_len);
+               if(sent == -1) {
+#ifndef USE_WINSOCK
+                       log_err("sendto: %s", strerror(errno));
+#else
+                       log_err("sendto: %s", wsa_strerror(WSAGetLastError()));
+#endif
+               } else if(sent != (ssize_t)sldns_buffer_limit(pkt)) {
+                       log_err("sendto: partial send");
+               }
+               p->lastuse = *now;
+               p->numsent++;
+       }
+}
+
+/** do proxy for one readable client */
+static void
+do_proxy(struct proxy* p, int retsock, sldns_buffer* pkt)
+{
+       int i;
+       ssize_t r;
+       for(i=0; i<TRIES_PER_SELECT; i++) {
+               r = recv(p->s, (void*)sldns_buffer_begin(pkt), 
+                       sldns_buffer_capacity(pkt), 0);
+               if(r == -1) {
+#ifndef USE_WINSOCK
+                       if(errno == EAGAIN || errno == EINTR)
+                               return;
+                       log_err("recv: %s", strerror(errno));
+#else
+                       if(WSAGetLastError() == WSAEINPROGRESS ||
+                               WSAGetLastError() == WSAEWOULDBLOCK)
+                               return;
+                       log_err("recv: %s", wsa_strerror(WSAGetLastError()));
+#endif
+                       return;
+               }
+               sldns_buffer_set_limit(pkt, (size_t)r);
+               log_addr(1, "return reply to client", &p->addr, p->addr_len);
+               /* send reply back to the real client */
+               p->numreturn++;
+               r = sendto(retsock, (void*)sldns_buffer_begin(pkt), (size_t)r, 
+                       0, (struct sockaddr*)&p->addr, p->addr_len);
+               if(r == -1) {
+#ifndef USE_WINSOCK
+                       log_err("sendto: %s", strerror(errno));
+#else
+                       log_err("sendto: %s", wsa_strerror(WSAGetLastError()));
+#endif
+               }
+       }
+}
+
+/** proxy return replies to clients */
+static void
+service_proxy(fd_set* rset, int retsock, struct proxy* proxies, 
+       sldns_buffer* pkt, struct timeval* now)
+{
+       struct proxy* p;
+       for(p = proxies; p; p = p->next) {
+               if(FD_ISSET(p->s, rset)) {
+                       p->lastuse = *now;
+                       do_proxy(p, retsock, pkt);
+               }
+       }
+}
+
+/** find or else create proxy for this remote client */
+static struct proxy*
+find_create_proxy(struct sockaddr_storage* from, socklen_t from_len,
+       fd_set* rorig, int* max, struct proxy** proxies, int serv_ip6,
+       struct timeval* now, struct timeval* reuse_timeout)
+{
+       struct proxy* p;
+       struct timeval t;
+       for(p = *proxies; p; p = p->next) {
+               if(sockaddr_cmp(from, from_len, &p->addr, p->addr_len)==0)
+                       return p;
+       }
+       /* possibly: reuse lapsed entries */
+       for(p = *proxies; p; p = p->next) {
+               if(p->numwait > p->numsent || p->numsent > p->numreturn)
+                       continue;
+               t = *now;
+               dl_tv_subtract(&t, &p->lastuse);
+               if(dl_tv_smaller(&t, reuse_timeout))
+                       continue;
+               /* yes! */
+               verbose(1, "reuse existing entry");
+               memmove(&p->addr, from, from_len);
+               p->addr_len = from_len;
+               p->numreuse++;
+               return p;
+       }
+       /* create new */
+       p = (struct proxy*)calloc(1, sizeof(*p));
+       if(!p) fatal_exit("out of memory");
+       p->s = socket(serv_ip6?AF_INET6:AF_INET, SOCK_DGRAM, 0);
+       if(p->s == -1) {
+#ifndef USE_WINSOCK
+               fatal_exit("socket: %s", strerror(errno));
+#else
+               fatal_exit("socket: %s", wsa_strerror(WSAGetLastError()));
+#endif
+       }
+       fd_set_nonblock(p->s);
+       memmove(&p->addr, from, from_len);
+       p->addr_len = from_len;
+       p->next = *proxies;
+       *proxies = p;
+       FD_SET(FD_SET_T p->s, rorig);
+       if(p->s+1 > *max)
+               *max = p->s+1;
+       return p;
+}
+
+/** recv new waiting packets */
+static void
+service_recv(int s, struct ringbuf* ring, sldns_buffer* pkt, 
+       fd_set* rorig, int* max, struct proxy** proxies,
+       struct sockaddr_storage* srv_addr, socklen_t srv_len, 
+       struct timeval* now, struct timeval* delay, struct timeval* reuse)
+{
+       int i;
+       struct sockaddr_storage from;
+       socklen_t from_len;
+       ssize_t len;
+       struct proxy* p;
+       for(i=0; i<TRIES_PER_SELECT; i++) {
+               from_len = (socklen_t)sizeof(from);
+               len = recvfrom(s, (void*)sldns_buffer_begin(pkt),
+                       sldns_buffer_capacity(pkt), 0,
+                       (struct sockaddr*)&from, &from_len);
+               if(len < 0) {
+#ifndef USE_WINSOCK
+                       if(errno == EAGAIN || errno == EINTR)
+                               return;
+                       fatal_exit("recvfrom: %s", strerror(errno));
+#else
+                       if(WSAGetLastError() == WSAEWOULDBLOCK || 
+                               WSAGetLastError() == WSAEINPROGRESS)
+                               return;
+                       fatal_exit("recvfrom: %s", 
+                               wsa_strerror(WSAGetLastError()));
+#endif
+               }
+               sldns_buffer_set_limit(pkt, (size_t)len);
+               /* find its proxy element */
+               p = find_create_proxy(&from, from_len, rorig, max, proxies,
+                       addr_is_ip6(srv_addr, srv_len), now, reuse);
+               if(!p) fatal_exit("error: cannot find or create proxy");
+               p->lastuse = *now;
+               ring_add(ring, pkt, now, delay, p);
+               p->numwait++;
+               log_addr(1, "recv from client", &p->addr, p->addr_len);
+       }
+}
+
+/** delete tcp proxy */
+static void
+tcp_proxy_delete(struct tcp_proxy* p)
+{
+       struct tcp_send_list* s, *sn;
+       if(!p)
+               return;
+       log_addr(1, "delete tcp proxy", &p->addr, p->addr_len);
+       s = p->querylist;
+       while(s) {
+               sn = s->next;
+               free(s->item);
+               free(s);
+               s = sn;
+       }
+       s = p->answerlist;
+       while(s) {
+               sn = s->next;
+               free(s->item);
+               free(s);
+               s = sn;
+       }
+#ifndef USE_WINSOCK
+       close(p->client_s);
+       if(p->server_s != -1)
+               close(p->server_s);
+#else
+       closesocket(p->client_s);
+       if(p->server_s != -1)
+               closesocket(p->server_s);
+#endif
+       free(p);
+}
+
+/** accept new TCP connections, and set them up */
+static void
+service_tcp_listen(int s, fd_set* rorig, int* max, struct tcp_proxy** proxies,
+       struct sockaddr_storage* srv_addr, socklen_t srv_len, 
+       struct timeval* now, struct timeval* tcp_timeout)
+{
+       int newfd;
+       struct sockaddr_storage addr;
+       struct tcp_proxy* p;
+       socklen_t addr_len;
+       newfd = accept(s, (struct sockaddr*)&addr, &addr_len);
+       if(newfd == -1) {
+#ifndef USE_WINSOCK
+               if(errno == EAGAIN || errno == EINTR)
+                       return;
+               fatal_exit("accept: %s", strerror(errno));
+#else
+               if(WSAGetLastError() == WSAEWOULDBLOCK || 
+                       WSAGetLastError() == WSAEINPROGRESS ||
+                       WSAGetLastError() == WSAECONNRESET)
+                       return;
+               fatal_exit("accept: %s", wsa_strerror(WSAGetLastError()));
+#endif
+       }
+       p = (struct tcp_proxy*)calloc(1, sizeof(*p));
+       if(!p) fatal_exit("out of memory");
+       memmove(&p->addr, &addr, addr_len);
+       p->addr_len = addr_len;
+       log_addr(1, "new tcp proxy", &p->addr, p->addr_len);
+       p->client_s = newfd;
+       p->server_s = socket(addr_is_ip6(srv_addr, srv_len)?AF_INET6:AF_INET,
+               SOCK_STREAM, 0);
+       if(p->server_s == -1) {
+#ifndef USE_WINSOCK
+               fatal_exit("tcp socket: %s", strerror(errno));
+#else
+               fatal_exit("tcp socket: %s", wsa_strerror(WSAGetLastError()));
+#endif
+       }
+       fd_set_nonblock(p->client_s);
+       fd_set_nonblock(p->server_s);
+       if(connect(p->server_s, (struct sockaddr*)srv_addr, srv_len) == -1) {
+#ifndef USE_WINSOCK
+               if(errno != EINPROGRESS) {
+                       log_err("tcp connect: %s", strerror(errno));
+                       close(p->server_s);
+                       close(p->client_s);
+#else
+               if(WSAGetLastError() != WSAEWOULDBLOCK &&
+                       WSAGetLastError() != WSAEINPROGRESS) {
+                       log_err("tcp connect: %s", 
+                               wsa_strerror(WSAGetLastError()));
+                       closesocket(p->server_s);
+                       closesocket(p->client_s);
+#endif
+                       free(p);
+                       return;
+               }
+       }
+       p->timeout = *now;
+       dl_tv_add(&p->timeout, tcp_timeout);
+
+       /* listen to client and server */
+       FD_SET(FD_SET_T p->client_s, rorig);
+       FD_SET(FD_SET_T p->server_s, rorig);
+       if(p->client_s+1 > *max)
+               *max = p->client_s+1;
+       if(p->server_s+1 > *max)
+               *max = p->server_s+1;
+
+       /* add into proxy list */
+       p->next = *proxies;
+       *proxies = p;
+}
+
+/** relay TCP, read a part */
+static int
+tcp_relay_read(int s, struct tcp_send_list** first, 
+       struct tcp_send_list** last, struct timeval* now, 
+       struct timeval* delay, sldns_buffer* pkt)
+{
+       struct tcp_send_list* item;
+       ssize_t r = recv(s, (void*)sldns_buffer_begin(pkt), 
+               sldns_buffer_capacity(pkt), 0);
+       if(r == -1) {
+#ifndef USE_WINSOCK
+               if(errno == EINTR || errno == EAGAIN)
+                       return 1;
+               log_err("tcp read: %s", strerror(errno));
+#else
+               if(WSAGetLastError() == WSAEINPROGRESS || 
+                       WSAGetLastError() == WSAEWOULDBLOCK)
+                       return 1;
+               log_err("tcp read: %s", wsa_strerror(WSAGetLastError()));
+#endif
+               return 0;
+       } else if(r == 0) {
+               /* connection closed */
+               return 0;
+       }
+       item = (struct tcp_send_list*)malloc(sizeof(*item));
+       if(!item) {
+               log_err("out of memory");
+               return 0;
+       }
+       verbose(1, "read item len %d", (int)r);
+       item->len = (size_t)r;
+       item->item = memdup(sldns_buffer_begin(pkt), item->len);
+       if(!item->item) {
+               free(item);
+               log_err("out of memory");
+               return 0;
+       }
+       item->done = 0;
+       item->wait = *now;
+       dl_tv_add(&item->wait, delay);
+       item->next = NULL;
+       
+       /* link in */
+       if(*first) {
+               (*last)->next = item;
+       } else {
+               *first = item;
+       }
+       *last = item;
+       return 1;
+}
+
+/** relay TCP, write a part */
+static int
+tcp_relay_write(int s, struct tcp_send_list** first, 
+       struct tcp_send_list** last, struct timeval* now)
+{
+       ssize_t r;
+       struct tcp_send_list* p;
+       while(*first) {
+               p = *first;
+               /* is the item ready? */
+               if(!dl_tv_smaller(&p->wait, now))
+                       return 1;
+               /* write it */
+               r = send(s, (void*)(p->item + p->done), p->len - p->done, 0);
+               if(r == -1) {
+#ifndef USE_WINSOCK
+                       if(errno == EAGAIN || errno == EINTR)
+                               return 1;
+                       log_err("tcp write: %s", strerror(errno));
+#else
+                       if(WSAGetLastError() == WSAEWOULDBLOCK || 
+                               WSAGetLastError() == WSAEINPROGRESS)
+                               return 1;
+                       log_err("tcp write: %s", 
+                               wsa_strerror(WSAGetLastError()));
+#endif
+                       return 0;
+               } else if(r == 0) {
+                       /* closed */
+                       return 0;
+               }
+               /* account it */
+               p->done += (size_t)r;
+               verbose(1, "write item %d of %d", (int)p->done, (int)p->len);
+               if(p->done >= p->len) {
+                       free(p->item);
+                       *first = p->next;
+                       if(!*first)
+                               *last = NULL;
+                       free(p);
+               } else {
+                       /* partial write */
+                       return 1;
+               }
+       }
+       return 1;
+}
+
+/** perform TCP relaying */
+static void
+service_tcp_relay(struct tcp_proxy** tcp_proxies, struct timeval* now,
+       struct timeval* delay, struct timeval* tcp_timeout, sldns_buffer* pkt,
+       fd_set* rset, fd_set* rorig, fd_set* worig)
+{
+       struct tcp_proxy* p, **prev;
+       struct timeval tout;
+       int delete_it;
+       p = *tcp_proxies;
+       prev = tcp_proxies;
+       tout = *now;
+       dl_tv_add(&tout, tcp_timeout);
+
+       while(p) {
+               delete_it = 0;
+               /* can we receive further queries? */
+               if(!delete_it && FD_ISSET(p->client_s, rset)) {
+                       p->timeout = tout;
+                       log_addr(1, "read tcp query", &p->addr, p->addr_len);
+                       if(!tcp_relay_read(p->client_s, &p->querylist, 
+                               &p->querylast, now, delay, pkt))
+                               delete_it = 1;
+               }
+               /* can we receive further answers? */
+               if(!delete_it && p->server_s != -1 &&
+                       FD_ISSET(p->server_s, rset)) {
+                       p->timeout = tout;
+                       log_addr(1, "read tcp answer", &p->addr, p->addr_len);
+                       if(!tcp_relay_read(p->server_s, &p->answerlist, 
+                               &p->answerlast, now, delay, pkt)) {
+#ifndef USE_WINSOCK
+                               close(p->server_s);
+#else
+                               closesocket(p->server_s);
+#endif
+                               FD_CLR(FD_SET_T p->server_s, worig);
+                               FD_CLR(FD_SET_T p->server_s, rorig);
+                               p->server_s = -1;
+                       }
+               }
+               /* can we send on further queries */
+               if(!delete_it && p->querylist && p->server_s != -1) {
+                       p->timeout = tout;
+                       if(dl_tv_smaller(&p->querylist->wait, now))
+                               log_addr(1, "write tcp query", 
+                                       &p->addr, p->addr_len);
+                       if(!tcp_relay_write(p->server_s, &p->querylist, 
+                               &p->querylast, now))
+                               delete_it = 1;
+                       if(p->querylist && p->server_s != -1 &&
+                               dl_tv_smaller(&p->querylist->wait, now))
+                               FD_SET(FD_SET_T p->server_s, worig);
+                       else    FD_CLR(FD_SET_T p->server_s, worig);
+               }
+
+               /* can we send on further answers */
+               if(!delete_it && p->answerlist) {
+                       p->timeout = tout;
+                       if(dl_tv_smaller(&p->answerlist->wait, now))
+                               log_addr(1, "write tcp answer", 
+                                       &p->addr, p->addr_len);
+                       if(!tcp_relay_write(p->client_s, &p->answerlist, 
+                               &p->answerlast, now))
+                               delete_it = 1;
+                       if(p->answerlist && dl_tv_smaller(&p->answerlist->wait,
+                               now))
+                               FD_SET(FD_SET_T p->client_s, worig);
+                       else    FD_CLR(FD_SET_T p->client_s, worig);
+                       if(!p->answerlist && p->server_s == -1)
+                               delete_it = 1;
+               }
+
+               /* does this entry timeout? (unused too long) */
+               if(dl_tv_smaller(&p->timeout, now)) {
+                       delete_it = 1;
+               }
+               if(delete_it) {
+                       struct tcp_proxy* np = p->next;
+                       *prev = np;
+                       FD_CLR(FD_SET_T p->client_s, rorig);
+                       FD_CLR(FD_SET_T p->client_s, worig);
+                       if(p->server_s != -1) {
+                               FD_CLR(FD_SET_T p->server_s, rorig);
+                               FD_CLR(FD_SET_T p->server_s, worig);
+                       }
+                       tcp_proxy_delete(p);
+                       p = np;
+                       continue;
+               }
+
+               prev = &p->next;
+               p = p->next;
+       }
+}
+
+/** find waiting time */
+static int
+service_findwait(struct timeval* now, struct timeval* wait, 
+       struct ringbuf* ring, struct tcp_proxy* tcplist)
+{
+       /* first item is the time to wait */
+       struct timeval* peek = ring_peek_time(ring);
+       struct timeval tcv;
+       int have_tcpval = 0;
+       struct tcp_proxy* p;
+
+       /* also for TCP list the first in sendlists is the time to wait */
+       for(p=tcplist; p; p=p->next) {
+               if(!have_tcpval)
+                       tcv = p->timeout;
+               have_tcpval = 1;
+               if(dl_tv_smaller(&p->timeout, &tcv))
+                       tcv = p->timeout;
+               if(p->querylist && dl_tv_smaller(&p->querylist->wait, &tcv))
+                       tcv = p->querylist->wait;
+               if(p->answerlist && dl_tv_smaller(&p->answerlist->wait, &tcv))
+                       tcv = p->answerlist->wait;
+       }
+       if(peek) {
+               /* peek can be unaligned */
+               /* use wait as a temp variable */
+               memmove(wait, peek, sizeof(*wait));
+               if(!have_tcpval)
+                       tcv = *wait;
+               else if(dl_tv_smaller(wait, &tcv))
+                       tcv = *wait;
+               have_tcpval = 1;
+       }
+       if(have_tcpval) {
+               *wait = tcv;
+               dl_tv_subtract(wait, now);
+               return 1;
+       }
+       /* nothing, block */
+       return 0;
+}
+
+/** clear proxy list */
+static void
+proxy_list_clear(struct proxy* p)
+{
+       char from[109];
+       struct proxy* np;
+       int i=0, port;
+       while(p) {
+               np = p->next;
+               port = (int)ntohs(((struct sockaddr_in*)&p->addr)->sin_port);
+               if(addr_is_ip6(&p->addr, p->addr_len)) {
+                       if(inet_ntop(AF_INET6, 
+                               &((struct sockaddr_in6*)&p->addr)->sin6_addr,
+                               from, (socklen_t)sizeof(from)) == 0)
+                               (void)strlcpy(from, "err", sizeof(from));
+               } else {
+                       if(inet_ntop(AF_INET, 
+                               &((struct sockaddr_in*)&p->addr)->sin_addr,
+                               from, (socklen_t)sizeof(from)) == 0)
+                               (void)strlcpy(from, "err", sizeof(from));
+               }
+               printf("client[%d]: last %s@%d of %d : %u in, %u out, "
+                       "%u returned\n", i++, from, port, (int)p->numreuse+1,
+                       (unsigned)p->numwait, (unsigned)p->numsent, 
+                       (unsigned)p->numreturn);
+#ifndef USE_WINSOCK
+               close(p->s);
+#else
+               closesocket(p->s);
+#endif
+               free(p);
+               p = np;
+       }
+}
+
+/** clear TCP proxy list */
+static void
+tcp_proxy_list_clear(struct tcp_proxy* p)
+{
+       struct tcp_proxy* np;
+       while(p) {
+               np = p->next;
+               tcp_proxy_delete(p);
+               p = np;
+       }
+}
+
+/** delayer service loop */
+static void
+service_loop(int udp_s, int listen_s, struct ringbuf* ring, 
+       struct timeval* delay, struct timeval* reuse,
+       struct sockaddr_storage* srv_addr, socklen_t srv_len, 
+       sldns_buffer* pkt)
+{
+       fd_set rset, rorig;
+       fd_set wset, worig;
+       struct timeval now, wait;
+       int max, have_wait = 0;
+       struct proxy* proxies = NULL;
+       struct tcp_proxy* tcp_proxies = NULL;
+       struct timeval tcp_timeout;
+       tcp_timeout.tv_sec = 120;
+       tcp_timeout.tv_usec = 0;
+#ifndef S_SPLINT_S
+       FD_ZERO(&rorig);
+       FD_ZERO(&worig);
+       FD_SET(FD_SET_T udp_s, &rorig);
+       FD_SET(FD_SET_T listen_s, &rorig);
+#endif
+       max = udp_s + 1;
+       if(listen_s + 1 > max) max = listen_s + 1;
+       while(!do_quit) {
+               /* wait for events */
+               rset = rorig;
+               wset = worig;
+               if(have_wait)
+                       verbose(1, "wait for %d.%6.6d",
+                       (unsigned)wait.tv_sec, (unsigned)wait.tv_usec);
+               else    verbose(1, "wait");
+               if(select(max, &rset, &wset, NULL, have_wait?&wait:NULL) < 0) {
+                       if(errno == EAGAIN || errno == EINTR)
+                               continue;
+                       fatal_exit("select: %s", strerror(errno));
+               }
+               /* get current time */
+               if(gettimeofday(&now, NULL) < 0) {
+                       if(errno == EAGAIN || errno == EINTR)
+                               continue;
+                       fatal_exit("gettimeofday: %s", strerror(errno));
+               }
+               verbose(1, "process at %u.%6.6u\n", 
+                       (unsigned)now.tv_sec, (unsigned)now.tv_usec);
+               /* sendout delayed queries to master server (frees up buffer)*/
+               service_send(ring, &now, pkt, srv_addr, srv_len);
+               /* proxy return replies */
+               service_proxy(&rset, udp_s, proxies, pkt, &now);
+               /* see what can be received to start waiting */
+               service_recv(udp_s, ring, pkt, &rorig, &max, &proxies,
+                       srv_addr, srv_len, &now, delay, reuse);
+               /* see if there are new tcp connections */
+               service_tcp_listen(listen_s, &rorig, &max, &tcp_proxies,
+                       srv_addr, srv_len, &now, &tcp_timeout);
+               /* service tcp connections */
+               service_tcp_relay(&tcp_proxies, &now, delay, &tcp_timeout, 
+                       pkt, &rset, &rorig, &worig);
+               /* see what next timeout is (if any) */
+               have_wait = service_findwait(&now, &wait, ring, tcp_proxies);
+       }
+       proxy_list_clear(proxies);
+       tcp_proxy_list_clear(tcp_proxies);
+}
+
+/** delayer main service routine */
+static void
+service(const char* bind_str, int bindport, const char* serv_str, 
+       size_t memsize, int delay_msec)
+{
+       struct sockaddr_storage bind_addr, srv_addr;
+       socklen_t bind_len, srv_len;
+       struct ringbuf* ring = ring_create(memsize);
+       struct timeval delay, reuse;
+       sldns_buffer* pkt;
+       int i, s, listen_s;
+#ifndef S_SPLINT_S
+       delay.tv_sec = delay_msec / 1000;
+       delay.tv_usec = (delay_msec % 1000)*1000;
+#endif
+       reuse = delay; /* reuse is max(4*delay, 1 second) */
+       dl_tv_add(&reuse, &delay);
+       dl_tv_add(&reuse, &delay);
+       dl_tv_add(&reuse, &delay);
+       if(reuse.tv_sec == 0)
+               reuse.tv_sec = 1;
+       if(!extstrtoaddr(serv_str, &srv_addr, &srv_len)) {
+               printf("cannot parse forward address: %s\n", serv_str);
+               exit(1);
+       }
+       pkt = sldns_buffer_new(65535);
+       if(!pkt)
+               fatal_exit("out of memory");
+       if( signal(SIGINT, delayer_sigh) == SIG_ERR ||
+#ifdef SIGHUP
+               signal(SIGHUP, delayer_sigh) == SIG_ERR ||
+#endif
+#ifdef SIGQUIT
+               signal(SIGQUIT, delayer_sigh) == SIG_ERR ||
+#endif
+#ifdef SIGBREAK
+               signal(SIGBREAK, delayer_sigh) == SIG_ERR ||
+#endif
+#ifdef SIGALRM
+               signal(SIGALRM, delayer_sigh) == SIG_ERR ||
+#endif
+               signal(SIGTERM, delayer_sigh) == SIG_ERR)
+               fatal_exit("could not bind to signal");
+       /* bind UDP port */
+       if((s = socket(str_is_ip6(bind_str)?AF_INET6:AF_INET,
+               SOCK_DGRAM, 0)) == -1) {
+#ifndef USE_WINSOCK
+               fatal_exit("socket: %s", strerror(errno));
+#else
+               fatal_exit("socket: %s", wsa_strerror(WSAGetLastError()));
+#endif
+       }
+       i=0;
+       if(bindport == 0) {
+               bindport = 1024 + arc4random()%64000;
+               i = 100;
+       }
+       while(1) {
+               if(!ipstrtoaddr(bind_str, bindport, &bind_addr, &bind_len)) {
+                       printf("cannot parse listen address: %s\n", bind_str);
+                       exit(1);
+               }
+               if(bind(s, (struct sockaddr*)&bind_addr, bind_len) == -1) {
+#ifndef USE_WINSOCK
+                       log_err("bind: %s", strerror(errno));
+#else
+                       log_err("bind: %s", wsa_strerror(WSAGetLastError()));
+#endif
+                       if(i--==0)
+                               fatal_exit("cannot bind any port");
+                       bindport = 1024 + arc4random()%64000;
+               } else break;
+       }
+       fd_set_nonblock(s);
+       /* and TCP port */
+       if((listen_s = socket(str_is_ip6(bind_str)?AF_INET6:AF_INET,
+               SOCK_STREAM, 0)) == -1) {
+#ifndef USE_WINSOCK
+               fatal_exit("tcp socket: %s", strerror(errno));
+#else
+               fatal_exit("tcp socket: %s", wsa_strerror(WSAGetLastError()));
+#endif
+       }
+#ifdef SO_REUSEADDR
+       if(1) {
+               int on = 1;
+               if(setsockopt(listen_s, SOL_SOCKET, SO_REUSEADDR, (void*)&on,
+                       (socklen_t)sizeof(on)) < 0)
+#ifndef USE_WINSOCK
+                       fatal_exit("setsockopt(.. SO_REUSEADDR ..) failed: %s",
+                               strerror(errno));
+#else
+                       fatal_exit("setsockopt(.. SO_REUSEADDR ..) failed: %s",
+                               wsa_strerror(WSAGetLastError()));
+#endif
+       }
+#endif
+       if(bind(listen_s, (struct sockaddr*)&bind_addr, bind_len) == -1) {
+#ifndef USE_WINSOCK
+               fatal_exit("tcp bind: %s", strerror(errno));
+#else
+               fatal_exit("tcp bind: %s", wsa_strerror(WSAGetLastError()));
+#endif
+       }
+       if(listen(listen_s, 5) == -1) {
+#ifndef USE_WINSOCK
+               fatal_exit("tcp listen: %s", strerror(errno));
+#else
+               fatal_exit("tcp listen: %s", wsa_strerror(WSAGetLastError()));
+#endif
+       }
+       fd_set_nonblock(listen_s);
+       printf("listening on port: %d\n", bindport);
+
+       /* process loop */
+       do_quit = 0;
+       service_loop(s, listen_s, ring, &delay, &reuse, &srv_addr, srv_len, 
+               pkt);
+
+       /* cleanup */
+       verbose(1, "cleanup");
+#ifndef USE_WINSOCK
+       close(s);
+       close(listen_s);
+#else
+       closesocket(s);
+       closesocket(listen_s);
+#endif
+       sldns_buffer_free(pkt);
+       ring_delete(ring);
+}
+
+/** getopt global, in case header files fail to declare it. */
+extern int optind;
+/** getopt global, in case header files fail to declare it. */
+extern char* optarg;
+
+/** main program for delayer */
+int main(int argc, char** argv) 
+{
+       int c;          /* defaults */
+       const char* server = "127.0.0.1@53";
+       const char* bindto = "0.0.0.0";
+       int bindport = 0;
+       size_t memsize = 10*1024*1024;
+       int delay = 100;
+
+       verbosity = 0;
+       log_init(0, 0, 0);
+       log_ident_set("delayer");
+       if(argc == 1) usage(argv);
+       while( (c=getopt(argc, argv, "b:d:f:hm:p:")) != -1) {
+               switch(c) {
+                       case 'b':
+                               bindto = optarg;
+                               break;
+                       case 'd':
+                               if(atoi(optarg)==0 && strcmp(optarg,"0")!=0) {
+                                       printf("bad delay: %s\n", optarg);
+                                       return 1;
+                               }
+                               delay = atoi(optarg);
+                               break;
+                       case 'f':
+                               server = optarg;
+                               break;
+                       case 'm':
+                               if(!cfg_parse_memsize(optarg, &memsize)) {
+                                       printf("bad memsize: %s\n", optarg);
+                                       return 1;
+                               }
+                               break;
+                       case 'p':
+                               if(atoi(optarg)==0 && strcmp(optarg,"0")!=0) {
+                                       printf("bad port nr: %s\n", optarg);
+                                       return 1;
+                               }
+                               bindport = atoi(optarg);
+                               break;
+                       case 'h':
+                       case '?':
+                       default:
+                               usage(argv);
+               }
+       }
+       argc -= optind;
+       argv += optind;
+       if(argc != 0)
+               usage(argv);
+
+       printf("bind to %s @ %d and forward to %s after %d msec\n", 
+               bindto, bindport, server, delay);
+       service(bindto, bindport, server, memsize, delay);
+       return 0;
+}
diff --git a/usr.sbin/unbound/testcode/do-tests.sh b/usr.sbin/unbound/testcode/do-tests.sh
new file mode 100755 (executable)
index 0000000..5439f0f
--- /dev/null
@@ -0,0 +1,63 @@
+#!/usr/bin/env bash
+. testdata/common.sh
+
+NEED_SPLINT='00-lint.tdir'
+NEED_DOXYGEN='01-doc.tdir'
+NEED_XXD='fwd_compress_c00c.tdir fwd_zero.tdir'
+NEED_NC='fwd_compress_c00c.tdir fwd_zero.tdir'
+NEED_CURL='06-ianaports.tdir root_anchor.tdir'
+NEED_WHOAMI='07-confroot.tdir'
+NEED_IPV6='fwd_ancil.tdir fwd_tcp_tc6.tdir stub_udp6.tdir edns_cache.tdir'
+NEED_NOMINGW='tcp_sigpipe.tdir 07-confroot.tdir 08-host-lib.tdir fwd_ancil.tdir'
+NEED_DNSCRYPT_PROXY='dnscrypt_queries.tdir dnscrypt_queries_chacha.tdir'
+
+# test if dig and ldns-testns are available.
+test_tool_avail "dig"
+test_tool_avail "ldns-testns"
+
+# test for ipv6, uses streamtcp peculiarity.
+if ./streamtcp -f ::1 2>&1 | grep "not supported" >/dev/null 2>&1; then
+       HAVE_IPV6=no
+else
+       HAVE_IPV6=yes
+fi
+
+# test mingw. no signals and so on.
+if uname | grep MINGW >/dev/null; then
+       HAVE_MINGW=yes
+else
+       HAVE_MINGW=no
+fi
+
+cd testdata;
+sh ../testcode/mini_tdir.sh clean
+rm -f .perfstats.txt
+for test in `ls -d *.tdir`; do
+       SKIP=0
+       skip_if_in_list $test "$NEED_SPLINT" "splint"
+       skip_if_in_list $test "$NEED_DOXYGEN" "doxygen"
+       skip_if_in_list $test "$NEED_CURL" "curl"
+       skip_if_in_list $test "$NEED_XXD" "xxd"
+       skip_if_in_list $test "$NEED_NC" "nc"
+       skip_if_in_list $test "$NEED_WHOAMI" "whoami"
+       skip_if_in_list $test "$NEED_DNSCRYPT_PROXY" "dnscrypt-proxy"
+
+       if echo $NEED_IPV6 | grep $test >/dev/null; then
+               if test "$HAVE_IPV6" = no; then
+                       SKIP=1;
+               fi
+       fi
+       if echo $NEED_NOMINGW | grep $test >/dev/null; then
+               if test "$HAVE_MINGW" = yes; then
+                       SKIP=1;
+               fi
+       fi
+       if test $SKIP -eq 0; then
+               echo $test
+               sh ../testcode/mini_tdir.sh -a ../.. exe $test
+       else
+               echo "skip $test"
+       fi
+done
+sh ../testcode/mini_tdir.sh report
+cat .perfstats.txt
diff --git a/usr.sbin/unbound/testcode/fake_event.c b/usr.sbin/unbound/testcode/fake_event.c
new file mode 100644 (file)
index 0000000..80e3685
--- /dev/null
@@ -0,0 +1,1792 @@
+/*
+ * testcode/fake_event.c - fake event handling that replays existing scenario.
+ *
+ * Copyright (c) 2007, NLnet Labs. All rights reserved.
+ * 
+ * This software is open source.
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 
+ * Neither the name of the NLNET LABS nor the names of its contributors may
+ * be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+ * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * \file
+ * Event service that replays a scenario.
+ * This implements the same exported symbols as the files:
+ * util/netevent.c
+ * services/listen_dnsport.c
+ * services/outside_network.c
+ * But these do not actually access the network or events, instead
+ * the scenario is played.
+ */
+
+#include "config.h"
+#include "testcode/fake_event.h"
+#include "util/netevent.h"
+#include "util/net_help.h"
+#include "util/data/msgparse.h"
+#include "util/data/msgreply.h"
+#include "util/data/msgencode.h"
+#include "util/data/dname.h"
+#include "util/config_file.h"
+#include "services/listen_dnsport.h"
+#include "services/outside_network.h"
+#include "services/cache/infra.h"
+#include "testcode/replay.h"
+#include "testcode/testpkts.h"
+#include "util/log.h"
+#include "util/fptr_wlist.h"
+#include "sldns/sbuffer.h"
+#include "sldns/wire2str.h"
+#include "sldns/str2wire.h"
+#include <signal.h>
+struct worker;
+struct daemon_remote;
+
+/** unique code to check that fake_commpoint is that structure */
+#define FAKE_COMMPOINT_TYPECODE 97347923
+/** fake commpoint, stores information */
+struct fake_commpoint {
+       /** typecode */
+       int typecode;
+       /** if this is a udp outgoing type of commpoint */
+       int type_udp_out;
+       /** if this is a tcp outgoing type of commpoint */
+       int type_tcp_out;
+       /** if this is a http outgoing type of commpoint. */
+       int type_http_out;
+
+       /** the callback, stored for usage */
+       comm_point_callback_type* cb;
+       /** the callback userarg, stored for usage */
+       void* cb_arg;
+       /** runtime ptr */
+       struct replay_runtime* runtime;
+       /** the pending entry for this commpoint (if any) */
+       struct fake_pending* pending;
+};
+
+/** Global variable: the scenario. Saved here for when event_init is done. */
+static struct replay_scenario* saved_scenario = NULL;
+
+/** add timers and the values do not overflow or become negative */
+static void
+timeval_add(struct timeval* d, const struct timeval* add)
+{
+#ifndef S_SPLINT_S
+       d->tv_sec += add->tv_sec;
+       d->tv_usec += add->tv_usec;
+       if(d->tv_usec > 1000000) {
+               d->tv_usec -= 1000000;
+               d->tv_sec++;
+       }
+#endif
+}
+
+void 
+fake_temp_file(const char* adj, const char* id, char* buf, size_t len)
+{
+#ifdef USE_WINSOCK
+       snprintf(buf, len, "testbound_%u%s%s.tmp",
+               (unsigned)getpid(), adj, id);
+#else
+       snprintf(buf, len, "/tmp/testbound_%u%s%s.tmp",
+               (unsigned)getpid(), adj, id);
+#endif
+}
+
+void 
+fake_event_init(struct replay_scenario* scen)
+{
+       saved_scenario = scen;
+}
+
+void 
+fake_event_cleanup(void)
+{
+       replay_scenario_delete(saved_scenario);
+       saved_scenario = NULL;
+}
+
+/** helper function that logs a sldns_pkt packet to logfile */
+static void
+log_pkt(const char* desc, uint8_t* pkt, size_t len)
+{
+       char* str = sldns_wire2str_pkt(pkt, len);
+       if(!str)
+               fatal_exit("%s: (failed out of memory wire2str_pkt)", desc);
+       else {
+               log_info("%s%s", desc, str);
+               free(str);
+       }
+}
+
+/**
+ * Returns a string describing the event type.
+ */
+static const char*
+repevt_string(enum replay_event_type t)
+{
+       switch(t) {
+       case repevt_nothing:     return "NOTHING";
+       case repevt_front_query: return "QUERY";
+       case repevt_front_reply: return "CHECK_ANSWER";
+       case repevt_timeout:     return "TIMEOUT";
+       case repevt_time_passes: return "TIME_PASSES";
+       case repevt_back_reply:  return "REPLY";
+       case repevt_back_query:  return "CHECK_OUT_QUERY";
+       case repevt_autotrust_check: return "CHECK_AUTOTRUST";
+       case repevt_tempfile_check: return "CHECK_TEMPFILE";
+       case repevt_error:       return "ERROR";
+       case repevt_assign:      return "ASSIGN";
+       case repevt_traffic:     return "TRAFFIC";
+       case repevt_infra_rtt:   return "INFRA_RTT";
+       default:                 return "UNKNOWN";
+       }
+}
+
+/** delete a fake pending */
+static void 
+delete_fake_pending(struct fake_pending* pend)
+{
+       if(!pend)
+               return;
+       free(pend->zone);
+       sldns_buffer_free(pend->buffer);
+       free(pend->pkt);
+       free(pend);
+}
+
+/** delete a replay answer */
+static void
+delete_replay_answer(struct replay_answer* a)
+{
+       if(!a)
+               return;
+       if(a->repinfo.c) {
+               sldns_buffer_free(a->repinfo.c->buffer);
+               free(a->repinfo.c);
+       }
+       free(a->pkt);
+       free(a);
+}
+
+/**
+ * return: true if pending query matches the now event.
+ */
+static int 
+pending_matches_current(struct replay_runtime* runtime, 
+       struct entry** entry, struct fake_pending **pend)
+{
+       struct fake_pending* p;
+       struct entry* e;
+       if(!runtime->now || runtime->now->evt_type != repevt_back_query
+               || !runtime->pending_list)
+               return 0;
+       /* see if any of the pending queries matches */
+       for(p = runtime->pending_list; p; p = p->next) {
+               if(runtime->now->addrlen != 0 &&
+                       sockaddr_cmp(&p->addr, p->addrlen, &runtime->now->addr,
+                       runtime->now->addrlen) != 0)
+                       continue;
+               if((e=find_match(runtime->now->match, p->pkt, p->pkt_len,
+                       p->transport))) {
+                       *entry = e;
+                       *pend = p;
+                       return 1;
+               }
+       }
+       return 0;
+}
+
+/**
+ * Find the range that matches this pending message.
+ * @param runtime: runtime with current moment, and range list.
+ * @param entry: returns the pointer to entry that matches.
+ * @param pend: the pending that the entry must match.
+ * @return: true if a match is found.
+ */
+static int
+pending_find_match(struct replay_runtime* runtime, struct entry** entry, 
+       struct fake_pending* pend)
+{
+       int timenow = runtime->now->time_step;
+       struct replay_range* p = runtime->scenario->range_list;
+       while(p) {
+               if(p->start_step <= timenow && timenow <= p->end_step &&
+                 (p->addrlen == 0 || sockaddr_cmp(&p->addr, p->addrlen,
+                       &pend->addr, pend->addrlen) == 0) &&
+                 (*entry = find_match(p->match, pend->pkt, pend->pkt_len,
+                        pend->transport))) {
+                       log_info("matched query time %d in range [%d, %d] "
+                               "with entry line %d", timenow, 
+                               p->start_step, p->end_step, (*entry)->lineno);
+                       if(p->addrlen != 0)
+                               log_addr(0, "matched ip", &p->addr, p->addrlen);
+                       log_pkt("matched pkt: ",
+                               (*entry)->reply_list->reply_pkt,
+                               (*entry)->reply_list->reply_len);
+                       return 1;
+               }
+               p = p->next_range;
+       }
+       return 0;
+}
+
+/**
+ * See if outgoing pending query matches an entry.
+ * @param runtime: runtime.
+ * @param entry: if true, the entry that matches is returned.
+ * @param pend: if true, the outgoing message that matches is returned.
+ * @return: true if pending query matches the now event.
+ */
+static int 
+pending_matches_range(struct replay_runtime* runtime, 
+       struct entry** entry, struct fake_pending** pend)
+{
+       struct fake_pending* p = runtime->pending_list;
+       /* slow, O(N*N), but it works as advertised with weird matching */
+       while(p) {
+               if(p->tcp_pkt_counter != 0) {
+                       /* continue tcp transfer */
+                       *pend = p;
+                       return 1;
+               }
+               if(pending_find_match(runtime, entry, p)) {
+                       *pend = p;
+                       return 1;
+               }
+               p = p->next;
+       }
+       return 0;
+}
+
+/**
+ * Remove the item from the pending list.
+ */
+static void
+pending_list_delete(struct replay_runtime* runtime, struct fake_pending* pend)
+{
+       struct fake_pending** prev = &runtime->pending_list;
+       struct fake_pending* p = runtime->pending_list;
+
+       while(p) {
+               if(p == pend) {
+                       *prev = p->next;
+                       delete_fake_pending(pend);
+                       return;
+               }
+
+               prev = &p->next;
+               p = p->next;
+       }
+}
+
+/** number of replies in entry */
+static int
+count_reply_packets(struct entry* entry)
+{
+       int count = 0;
+       struct reply_packet* reppkt = entry->reply_list;
+       while(reppkt) {
+               count++;
+               reppkt = reppkt->next;
+       }
+       return count;
+}
+
+/**
+ * Fill buffer with reply from the entry.
+ */
+static void
+fill_buffer_with_reply(sldns_buffer* buffer, struct entry* entry, uint8_t* q,
+       size_t qlen, int tcp_pkt_counter)
+{
+       struct reply_packet* reppkt;
+       uint8_t* c;
+       size_t clen;
+       log_assert(entry && entry->reply_list);
+       sldns_buffer_clear(buffer);
+       reppkt = entry->reply_list;
+       if(tcp_pkt_counter > 0) {
+               int i = tcp_pkt_counter;
+               while(reppkt && i--)
+                       reppkt = reppkt->next;
+               if(!reppkt) fatal_exit("extra packet read from TCP stream but none is available");
+               log_pkt("extra_packet ", reppkt->reply_pkt, reppkt->reply_len);
+       }
+       if(reppkt->reply_from_hex) {
+               c = sldns_buffer_begin(reppkt->reply_from_hex);
+               clen = sldns_buffer_limit(reppkt->reply_from_hex);
+               if(!c) fatal_exit("out of memory");
+       } else {
+               c = reppkt->reply_pkt;
+               clen = reppkt->reply_len;
+       }
+       if(c) {
+               if(q) adjust_packet(entry, &c, &clen, q, qlen);
+               sldns_buffer_write(buffer, c, clen);
+               if(q) free(c);
+       }
+       sldns_buffer_flip(buffer);
+}
+
+/**
+ * Perform range entry on pending message.
+ * @param runtime: runtime buffer size preference.
+ * @param entry: entry that codes for the reply to do.
+ * @param pend: pending query that is answered, callback called.
+ */
+static void
+answer_callback_from_entry(struct replay_runtime* runtime,
+        struct entry* entry, struct fake_pending* pend)
+{
+       struct comm_point c;
+       struct comm_reply repinfo;
+       void* cb_arg = pend->cb_arg;
+       comm_point_callback_type* cb = pend->callback;
+
+       memset(&c, 0, sizeof(c));
+       c.fd = -1;
+       c.buffer = sldns_buffer_new(runtime->bufsize);
+       c.type = comm_udp;
+       if(pend->transport == transport_tcp)
+               c.type = comm_tcp;
+       fill_buffer_with_reply(c.buffer, entry, pend->pkt, pend->pkt_len,
+               pend->tcp_pkt_counter);
+       repinfo.c = &c;
+       repinfo.addrlen = pend->addrlen;
+       memcpy(&repinfo.addr, &pend->addr, pend->addrlen);
+       if(!pend->serviced) {
+               if(entry->reply_list->next &&
+                       pend->tcp_pkt_counter < count_reply_packets(entry)) {
+                       /* go to next packet next time */
+                       pend->tcp_pkt_counter++;
+               } else {
+                       pending_list_delete(runtime, pend);
+               }
+       }
+       if((*cb)(&c, cb_arg, NETEVENT_NOERROR, &repinfo)) {
+               fatal_exit("testbound: unexpected: callback returned 1");
+       }
+       sldns_buffer_free(c.buffer);
+}
+
+/** Check the now moment answer check event */
+static void
+answer_check_it(struct replay_runtime* runtime)
+{
+       struct replay_answer* ans = runtime->answer_list, 
+               *prev = NULL;
+       log_assert(runtime && runtime->now && 
+               runtime->now->evt_type == repevt_front_reply);
+       while(ans) {
+               enum transport_type tr = transport_tcp;
+               if(ans->repinfo.c->type == comm_udp)
+                       tr = transport_udp;
+               if((runtime->now->addrlen == 0 || sockaddr_cmp(
+                       &runtime->now->addr, runtime->now->addrlen,
+                       &ans->repinfo.addr, ans->repinfo.addrlen) == 0) &&
+                       find_match(runtime->now->match, ans->pkt,
+                               ans->pkt_len, tr)) {
+                       log_info("testbound matched event entry from line %d",
+                               runtime->now->match->lineno);
+                       log_info("testbound: do STEP %d %s", 
+                               runtime->now->time_step,
+                               repevt_string(runtime->now->evt_type));
+                       if(prev)
+                               prev->next = ans->next;
+                       else    runtime->answer_list = ans->next;
+                       if(!ans->next)
+                               runtime->answer_last = prev;
+                       delete_replay_answer(ans);
+                       return;
+               } else {
+                       prev = ans;
+                       ans = ans->next;
+               }
+       }
+       log_info("testbound: do STEP %d %s", runtime->now->time_step,
+               repevt_string(runtime->now->evt_type));
+       fatal_exit("testbound: not matched");
+}
+
+/**
+ * Create commpoint (as return address) for a fake incoming query.
+ */
+static void
+fake_front_query(struct replay_runtime* runtime, struct replay_moment *todo)
+{
+       struct comm_reply repinfo;
+       memset(&repinfo, 0, sizeof(repinfo));
+       repinfo.c = (struct comm_point*)calloc(1, sizeof(struct comm_point));
+       repinfo.addrlen = (socklen_t)sizeof(struct sockaddr_in);
+       if(todo->addrlen != 0) {
+               repinfo.addrlen = todo->addrlen;
+               memcpy(&repinfo.addr, &todo->addr, todo->addrlen);
+       }
+       repinfo.c->fd = -1;
+       repinfo.c->ev = (struct internal_event*)runtime;
+       repinfo.c->buffer = sldns_buffer_new(runtime->bufsize);
+       if(todo->match->match_transport == transport_tcp)
+               repinfo.c->type = comm_tcp;
+       else    repinfo.c->type = comm_udp;
+       fill_buffer_with_reply(repinfo.c->buffer, todo->match, NULL, 0, 0);
+       log_info("testbound: incoming QUERY");
+       log_pkt("query pkt", todo->match->reply_list->reply_pkt,
+               todo->match->reply_list->reply_len);
+       /* call the callback for incoming queries */
+       if((*runtime->callback_query)(repinfo.c, runtime->cb_arg, 
+               NETEVENT_NOERROR, &repinfo)) {
+               /* send immediate reply */
+               comm_point_send_reply(&repinfo);
+       }
+       /* clear it again, in case copy not done properly */
+       memset(&repinfo, 0, sizeof(repinfo));
+}
+
+/**
+ * Perform callback for fake pending message.
+ */
+static void
+fake_pending_callback(struct replay_runtime* runtime, 
+       struct replay_moment* todo, int error)
+{
+       struct fake_pending* p = runtime->pending_list;
+       struct comm_reply repinfo;
+       struct comm_point c;
+       void* cb_arg;
+       comm_point_callback_type* cb;
+
+       memset(&c, 0, sizeof(c));
+       if(!p) fatal_exit("No pending queries.");
+       cb_arg = p->cb_arg;
+       cb = p->callback;
+       c.buffer = sldns_buffer_new(runtime->bufsize);
+       c.type = comm_udp;
+       if(p->transport == transport_tcp)
+               c.type = comm_tcp;
+       if(todo->evt_type == repevt_back_reply && todo->match) {
+               fill_buffer_with_reply(c.buffer, todo->match, p->pkt,
+                       p->pkt_len, p->tcp_pkt_counter);
+       }
+       repinfo.c = &c;
+       repinfo.addrlen = p->addrlen;
+       memcpy(&repinfo.addr, &p->addr, p->addrlen);
+       if(!p->serviced) {
+               if(todo->match->reply_list->next && !error &&
+                       p->tcp_pkt_counter < count_reply_packets(todo->match)) {
+                       /* go to next packet next time */
+                       p->tcp_pkt_counter++;
+               } else {
+                       pending_list_delete(runtime, p);
+               }
+       }
+       if((*cb)(&c, cb_arg, error, &repinfo)) {
+               fatal_exit("unexpected: pending callback returned 1");
+       }
+       /* delete the pending item. */
+       sldns_buffer_free(c.buffer);
+}
+
+/** pass time */
+static void
+moment_assign(struct replay_runtime* runtime, struct replay_moment* mom)
+{
+       char* value = macro_process(runtime->vars, runtime, mom->string);
+       if(!value)
+               fatal_exit("could not process macro step %d", mom->time_step);
+       log_info("assign %s = %s", mom->variable, value);
+       if(!macro_assign(runtime->vars, mom->variable, value))
+               fatal_exit("out of memory storing macro");
+       free(value);
+       if(verbosity >= VERB_ALGO)
+               macro_print_debug(runtime->vars);
+}
+
+/** pass time */
+static void
+time_passes(struct replay_runtime* runtime, struct replay_moment* mom)
+{
+       struct fake_timer *t;
+       struct timeval tv = mom->elapse;
+       if(mom->string) {
+               char* xp = macro_process(runtime->vars, runtime, mom->string);
+               double sec;
+               if(!xp) fatal_exit("could not macro expand %s", mom->string);
+               verbose(VERB_ALGO, "EVAL %s", mom->string);
+               sec = atof(xp);
+               free(xp);
+#ifndef S_SPLINT_S
+               tv.tv_sec = sec;
+               tv.tv_usec = (int)((sec - (double)tv.tv_sec) *1000000. + 0.5);
+#endif
+       }
+       timeval_add(&runtime->now_tv, &tv);
+       runtime->now_secs = (time_t)runtime->now_tv.tv_sec;
+#ifndef S_SPLINT_S
+       log_info("elapsed %d.%6.6d  now %d.%6.6d", 
+               (int)tv.tv_sec, (int)tv.tv_usec,
+               (int)runtime->now_tv.tv_sec, (int)runtime->now_tv.tv_usec);
+#endif
+       /* see if any timers have fired; and run them */
+       while( (t=replay_get_oldest_timer(runtime)) ) {
+               t->enabled = 0;
+               log_info("fake_timer callback");
+               fptr_ok(fptr_whitelist_comm_timer(t->cb));
+               (*t->cb)(t->cb_arg);
+       }
+}
+
+/** check autotrust file contents */
+static void
+autotrust_check(struct replay_runtime* runtime, struct replay_moment* mom)
+{
+       char name[1024], line[1024];
+       FILE *in;
+       int lineno = 0, oke=1;
+       char* expanded;
+       struct config_strlist* p;
+       line[sizeof(line)-1] = 0;
+       log_assert(mom->autotrust_id);
+       fake_temp_file("_auto_", mom->autotrust_id, name, sizeof(name));
+       in = fopen(name, "r");
+       if(!in) fatal_exit("could not open %s: %s", name, strerror(errno));
+       for(p=mom->file_content; p; p=p->next) {
+               lineno++;
+               if(!fgets(line, (int)sizeof(line)-1, in)) {
+                       log_err("autotrust check failed, could not read line");
+                       log_err("file %s, line %d", name, lineno);
+                       log_err("should be: %s", p->str);
+                       fatal_exit("autotrust_check failed");
+               }
+               if(line[0]) line[strlen(line)-1] = 0; /* remove newline */
+               expanded = macro_process(runtime->vars, runtime, p->str);
+               if(!expanded) 
+                       fatal_exit("could not expand macro line %d", lineno);
+               if(verbosity >= 7 && strcmp(p->str, expanded) != 0)
+                       log_info("expanded '%s' to '%s'", p->str, expanded);
+               if(strcmp(expanded, line) != 0) {
+                       log_err("mismatch in file %s, line %d", name, lineno);
+                       log_err("file has : %s", line);
+                       log_err("should be: %s", expanded);
+                       free(expanded);
+                       oke = 0;
+                       continue;
+               }
+               free(expanded);
+               fprintf(stderr, "%s:%2d ok : %s\n", name, lineno, line);
+       }
+       if(fgets(line, (int)sizeof(line)-1, in)) {
+               log_err("autotrust check failed, extra lines in %s after %d",
+                       name, lineno);
+               do {
+                       fprintf(stderr, "file has: %s", line);
+               } while(fgets(line, (int)sizeof(line)-1, in));
+               oke = 0;
+       }
+       fclose(in);
+       if(!oke)
+               fatal_exit("autotrust_check STEP %d failed", mom->time_step);
+       log_info("autotrust %s is OK", mom->autotrust_id);
+}
+
+/** check tempfile file contents */
+static void
+tempfile_check(struct replay_runtime* runtime, struct replay_moment* mom)
+{
+       char name[1024], line[1024];
+       FILE *in;
+       int lineno = 0, oke=1;
+       char* expanded;
+       struct config_strlist* p;
+       line[sizeof(line)-1] = 0;
+       log_assert(mom->autotrust_id);
+       fake_temp_file("_temp_", mom->autotrust_id, name, sizeof(name));
+       in = fopen(name, "r");
+       if(!in) fatal_exit("could not open %s: %s", name, strerror(errno));
+       for(p=mom->file_content; p; p=p->next) {
+               lineno++;
+               if(!fgets(line, (int)sizeof(line)-1, in)) {
+                       log_err("tempfile check failed, could not read line");
+                       log_err("file %s, line %d", name, lineno);
+                       log_err("should be: %s", p->str);
+                       fatal_exit("tempfile_check failed");
+               }
+               if(line[0]) line[strlen(line)-1] = 0; /* remove newline */
+               expanded = macro_process(runtime->vars, runtime, p->str);
+               if(!expanded) 
+                       fatal_exit("could not expand macro line %d", lineno);
+               if(verbosity >= 7 && strcmp(p->str, expanded) != 0)
+                       log_info("expanded '%s' to '%s'", p->str, expanded);
+               if(strcmp(expanded, line) != 0) {
+                       log_err("mismatch in file %s, line %d", name, lineno);
+                       log_err("file has : %s", line);
+                       log_err("should be: %s", expanded);
+                       free(expanded);
+                       oke = 0;
+                       continue;
+               }
+               free(expanded);
+               fprintf(stderr, "%s:%2d ok : %s\n", name, lineno, line);
+       }
+       if(fgets(line, (int)sizeof(line)-1, in)) {
+               log_err("tempfile check failed, extra lines in %s after %d",
+                       name, lineno);
+               do {
+                       fprintf(stderr, "file has: %s", line);
+               } while(fgets(line, (int)sizeof(line)-1, in));
+               oke = 0;
+       }
+       fclose(in);
+       if(!oke)
+               fatal_exit("tempfile_check STEP %d failed", mom->time_step);
+       log_info("tempfile %s is OK", mom->autotrust_id);
+}
+
+/** Store RTT in infra cache */
+static void
+do_infra_rtt(struct replay_runtime* runtime)
+{
+       struct replay_moment* now = runtime->now;
+       int rto;
+       size_t dplen = 0;
+       uint8_t* dp = sldns_str2wire_dname(now->variable, &dplen);
+       if(!dp) fatal_exit("cannot parse %s", now->variable);
+       rto = infra_rtt_update(runtime->infra, &now->addr, now->addrlen,
+               dp, dplen, LDNS_RR_TYPE_A, atoi(now->string),
+               -1, runtime->now_secs);
+       log_addr(0, "INFRA_RTT for", &now->addr, now->addrlen);
+       log_info("INFRA_RTT(%s roundtrip %d): rto of %d", now->variable,
+               atoi(now->string), rto);
+       if(rto == 0) fatal_exit("infra_rtt_update failed");
+       free(dp);
+}
+
+/** perform exponential backoff on the timeout */
+static void
+expon_timeout_backoff(struct replay_runtime* runtime)
+{
+       struct fake_pending* p = runtime->pending_list;
+       int rtt, vs;
+       uint8_t edns_lame_known;
+       int last_rtt, rto;
+       if(!p) return; /* no pending packet to backoff */
+       if(!infra_host(runtime->infra, &p->addr, p->addrlen, p->zone,
+               p->zonelen, runtime->now_secs, &vs, &edns_lame_known, &rtt))
+               return;
+       last_rtt = rtt;
+       rto = infra_rtt_update(runtime->infra, &p->addr, p->addrlen, p->zone,
+               p->zonelen, p->qtype, -1, last_rtt, runtime->now_secs);
+       log_info("infra_rtt_update returned rto %d", rto);
+}
+
+/**
+ * Advance to the next moment.
+ */
+static void
+advance_moment(struct replay_runtime* runtime)
+{
+       if(!runtime->now)
+               runtime->now = runtime->scenario->mom_first;
+       else    runtime->now = runtime->now->mom_next;
+}
+
+/**
+ * Perform actions or checks determined by the moment.
+ * Also advances the time by one step.
+ * @param runtime: scenario runtime information.
+ */
+static void
+do_moment_and_advance(struct replay_runtime* runtime)
+{
+       struct replay_moment* mom;
+       if(!runtime->now) {
+               advance_moment(runtime);
+               return;
+       }
+       log_info("testbound: do STEP %d %s", runtime->now->time_step, 
+               repevt_string(runtime->now->evt_type));
+       switch(runtime->now->evt_type) {
+       case repevt_nothing:
+               advance_moment(runtime);
+               break;
+       case repevt_front_query:
+               /* advance moment before doing the step, so that the next
+                  moment which may check some result of the mom step
+                  can catch those results. */
+               mom = runtime->now;
+               advance_moment(runtime);
+               fake_front_query(runtime, mom);
+               break;
+       case repevt_front_reply:
+               if(runtime->answer_list) 
+                       log_err("testbound: There are unmatched answers.");
+               fatal_exit("testbound: query answer not matched");
+               break;
+       case repevt_timeout:
+               mom = runtime->now;
+               advance_moment(runtime);
+               expon_timeout_backoff(runtime);
+               fake_pending_callback(runtime, mom, NETEVENT_TIMEOUT);
+               break;
+       case repevt_back_reply:
+               mom = runtime->now;
+               advance_moment(runtime);
+               fake_pending_callback(runtime, mom, NETEVENT_NOERROR);
+               break;
+       case repevt_back_query:
+               /* Back queries are matched when they are sent out. */
+               log_err("No query matching the current moment was sent.");
+               fatal_exit("testbound: back query not matched");
+               break;
+       case repevt_error:
+               mom = runtime->now;
+               advance_moment(runtime);
+               fake_pending_callback(runtime, mom, NETEVENT_CLOSED);
+               break;
+       case repevt_time_passes:
+               time_passes(runtime, runtime->now);
+               advance_moment(runtime);
+               break;
+       case repevt_autotrust_check:
+               autotrust_check(runtime, runtime->now);
+               advance_moment(runtime);
+               break;
+       case repevt_tempfile_check:
+               tempfile_check(runtime, runtime->now);
+               advance_moment(runtime);
+               break;
+       case repevt_assign:
+               moment_assign(runtime, runtime->now);
+               advance_moment(runtime);
+               break;
+       case repevt_traffic:
+               advance_moment(runtime);
+               break;
+       case repevt_infra_rtt:
+               do_infra_rtt(runtime);
+               advance_moment(runtime);
+               break;
+       default:
+               fatal_exit("testbound: unknown event type %d", 
+                       runtime->now->evt_type);
+       }
+}
+
+/** run the scenario in event callbacks */
+static void
+run_scenario(struct replay_runtime* runtime)
+{
+       struct entry* entry = NULL;
+       struct fake_pending* pending = NULL;
+       int max_rounds = 5000;
+       int rounds = 0;
+       runtime->now = runtime->scenario->mom_first;
+       log_info("testbound: entering fake runloop");
+       do {
+               /* if moment matches pending query do it. */
+               /* else if moment matches given answer, do it */
+               /* else if precoded_range matches pending, do it */
+               /* else do the current moment */
+               if(pending_matches_current(runtime, &entry, &pending)) {
+                       log_info("testbound: do STEP %d CHECK_OUT_QUERY", 
+                               runtime->now->time_step);
+                       advance_moment(runtime);
+                       if(entry->copy_id)
+                               answer_callback_from_entry(runtime, entry, 
+                               pending);
+               } else if(runtime->answer_list && runtime->now && 
+                       runtime->now->evt_type == repevt_front_reply) {
+                       answer_check_it(runtime);                       
+                       advance_moment(runtime);
+               } else if(pending_matches_range(runtime, &entry, &pending)) {
+                       answer_callback_from_entry(runtime, entry, pending);
+               } else {
+                       do_moment_and_advance(runtime);
+               }
+               log_info("testbound: end of event stage");
+               rounds++;
+               if(rounds > max_rounds)
+                       fatal_exit("testbound: too many rounds, it loops.");
+       } while(runtime->now);
+
+       if(runtime->pending_list) {
+               struct fake_pending* p;
+               log_err("testbound: there are still messages pending.");
+               for(p = runtime->pending_list; p; p=p->next) {
+                       log_pkt("pending msg", p->pkt, p->pkt_len);
+                       log_addr(0, "pending to", &p->addr, p->addrlen);
+               }
+               fatal_exit("testbound: there are still messages pending.");
+       }
+       if(runtime->answer_list) {
+               fatal_exit("testbound: there are unmatched answers.");
+       }
+       log_info("testbound: exiting fake runloop.");
+       runtime->exit_cleanly = 1;
+}
+
+/*********** Dummy routines ***********/
+
+struct listen_dnsport* 
+listen_create(struct comm_base* base, struct listen_port* ATTR_UNUSED(ports),
+       size_t bufsize, int ATTR_UNUSED(tcp_accept_count),
+       void* ATTR_UNUSED(sslctx), struct dt_env* ATTR_UNUSED(dtenv),
+       comm_point_callback_type* cb, void* cb_arg)
+{
+       struct replay_runtime* runtime = (struct replay_runtime*)base;
+       struct listen_dnsport* l= calloc(1, sizeof(struct listen_dnsport));
+       if(!l)
+               return NULL;
+       l->base = base;
+       l->udp_buff = sldns_buffer_new(bufsize);
+       if(!l->udp_buff) {
+               free(l);
+               return NULL;
+       }
+       runtime->callback_query = cb;
+       runtime->cb_arg = cb_arg;
+       runtime->bufsize = bufsize;
+       return l;
+}
+
+void 
+listen_delete(struct listen_dnsport* listen)
+{
+       if(!listen)
+               return;
+       sldns_buffer_free(listen->udp_buff);
+       free(listen);
+}
+
+struct comm_base* 
+comm_base_create(int ATTR_UNUSED(sigs))
+{
+       /* we return the runtime structure instead. */
+       struct replay_runtime* runtime = (struct replay_runtime*)
+               calloc(1, sizeof(struct replay_runtime));
+       runtime->scenario = saved_scenario;
+       runtime->vars = macro_store_create();
+       if(!runtime->vars) fatal_exit("out of memory");
+       return (struct comm_base*)runtime;
+}
+
+void 
+comm_base_delete(struct comm_base* b)
+{
+       struct replay_runtime* runtime = (struct replay_runtime*)b;
+       struct fake_pending* p, *np;
+       struct replay_answer* a, *na;
+       struct fake_timer* t, *nt;
+       if(!runtime)
+               return;
+       runtime->scenario= NULL;
+       p = runtime->pending_list;
+       while(p) {
+               np = p->next;
+               delete_fake_pending(p);
+               p = np;
+       }
+       a = runtime->answer_list;
+       while(a) {
+               na = a->next;
+               delete_replay_answer(a);
+               a = na;
+       }
+       t = runtime->timer_list;
+       while(t) {
+               nt = t->next;
+               free(t);
+               t = nt;
+       }
+       macro_store_delete(runtime->vars);
+       free(runtime);
+}
+
+void
+comm_base_timept(struct comm_base* b, time_t** tt, struct timeval** tv)
+{
+       struct replay_runtime* runtime = (struct replay_runtime*)b;
+       *tt = &runtime->now_secs;
+       *tv = &runtime->now_tv;
+}
+
+void 
+comm_base_dispatch(struct comm_base* b)
+{
+       struct replay_runtime* runtime = (struct replay_runtime*)b;
+       run_scenario(runtime);
+       if(runtime->sig_cb)
+               (*runtime->sig_cb)(SIGTERM, runtime->sig_cb_arg);
+       else    exit(0); /* OK exit when LIBEVENT_SIGNAL_PROBLEM exists */
+}
+
+void 
+comm_base_exit(struct comm_base* b)
+{
+       struct replay_runtime* runtime = (struct replay_runtime*)b;
+       if(!runtime->exit_cleanly) {
+               /* some sort of failure */
+               fatal_exit("testbound: comm_base_exit was called.");
+       }
+}
+
+struct comm_signal* 
+comm_signal_create(struct comm_base* base,
+        void (*callback)(int, void*), void* cb_arg)
+{
+       struct replay_runtime* runtime = (struct replay_runtime*)base;
+       runtime->sig_cb = callback;
+       runtime->sig_cb_arg = cb_arg;
+       return calloc(1, sizeof(struct comm_signal));
+}
+
+int 
+comm_signal_bind(struct comm_signal* ATTR_UNUSED(comsig), int 
+       ATTR_UNUSED(sig))
+{
+       return 1;
+}
+
+void 
+comm_signal_delete(struct comm_signal* comsig)
+{
+       free(comsig);
+}
+
+void 
+comm_point_send_reply(struct comm_reply* repinfo)
+{
+       struct replay_answer* ans = (struct replay_answer*)calloc(1,
+               sizeof(struct replay_answer));
+       struct replay_runtime* runtime = (struct replay_runtime*)repinfo->c->ev;
+       log_info("testbound: comm_point_send_reply fake");
+       /* dump it into the todo list */
+       log_assert(ans);
+       memcpy(&ans->repinfo, repinfo, sizeof(struct comm_reply));
+       ans->next = NULL;
+       if(runtime->answer_last)
+               runtime->answer_last->next = ans;
+       else    runtime->answer_list = ans;
+       runtime->answer_last = ans;
+
+       /* try to parse packet */
+       ans->pkt = memdup(sldns_buffer_begin(ans->repinfo.c->buffer),
+               sldns_buffer_limit(ans->repinfo.c->buffer));
+       ans->pkt_len = sldns_buffer_limit(ans->repinfo.c->buffer);
+       if(!ans->pkt) fatal_exit("out of memory");
+       log_pkt("reply pkt: ", ans->pkt, ans->pkt_len);
+}
+
+void 
+comm_point_drop_reply(struct comm_reply* repinfo)
+{
+       log_info("comm_point_drop_reply fake");
+       if(repinfo->c) {
+               sldns_buffer_free(repinfo->c->buffer);
+               free(repinfo->c);
+       }
+}
+
+struct outside_network* 
+outside_network_create(struct comm_base* base, size_t bufsize, 
+       size_t ATTR_UNUSED(num_ports), char** ATTR_UNUSED(ifs), 
+       int ATTR_UNUSED(num_ifs), int ATTR_UNUSED(do_ip4), 
+       int ATTR_UNUSED(do_ip6), size_t ATTR_UNUSED(num_tcp), 
+       struct infra_cache* infra,
+       struct ub_randstate* ATTR_UNUSED(rnd), 
+       int ATTR_UNUSED(use_caps_for_id), int* ATTR_UNUSED(availports),
+       int ATTR_UNUSED(numavailports), size_t ATTR_UNUSED(unwanted_threshold),
+       int ATTR_UNUSED(outgoing_tcp_mss),
+       void (*unwanted_action)(void*), void* ATTR_UNUSED(unwanted_param),
+       int ATTR_UNUSED(do_udp), void* ATTR_UNUSED(sslctx),
+       int ATTR_UNUSED(delayclose), struct dt_env* ATTR_UNUSED(dtenv))
+{
+       struct replay_runtime* runtime = (struct replay_runtime*)base;
+       struct outside_network* outnet =  calloc(1, 
+               sizeof(struct outside_network));
+       (void)unwanted_action;
+       if(!outnet)
+               return NULL;
+       runtime->infra = infra;
+       outnet->base = base;
+       outnet->udp_buff = sldns_buffer_new(bufsize);
+       if(!outnet->udp_buff) {
+               free(outnet);
+               return NULL;
+       }
+       return outnet;
+}
+
+void 
+outside_network_delete(struct outside_network* outnet)
+{
+       if(!outnet)
+               return;
+       sldns_buffer_free(outnet->udp_buff);
+       free(outnet);
+}
+
+void 
+outside_network_quit_prepare(struct outside_network* ATTR_UNUSED(outnet))
+{
+}
+
+struct pending* 
+pending_udp_query(struct serviced_query* sq, sldns_buffer* packet,
+       int timeout, comm_point_callback_type* callback, void* callback_arg)
+{
+       struct replay_runtime* runtime = (struct replay_runtime*)
+               sq->outnet->base;
+       struct fake_pending* pend = (struct fake_pending*)calloc(1,
+               sizeof(struct fake_pending));
+       log_assert(pend);
+       pend->buffer = sldns_buffer_new(sldns_buffer_capacity(packet));
+       log_assert(pend->buffer);
+       sldns_buffer_write(pend->buffer, sldns_buffer_begin(packet),
+               sldns_buffer_limit(packet));
+       sldns_buffer_flip(pend->buffer);
+       memcpy(&pend->addr, &sq->addr, sq->addrlen);
+       pend->addrlen = sq->addrlen;
+       pend->callback = callback;
+       pend->cb_arg = callback_arg;
+       pend->timeout = timeout/1000;
+       pend->transport = transport_udp;
+       pend->pkt = NULL;
+       pend->zone = NULL;
+       pend->serviced = 0;
+       pend->runtime = runtime;
+       pend->pkt_len = sldns_buffer_limit(packet);
+       pend->pkt = memdup(sldns_buffer_begin(packet), pend->pkt_len);
+       if(!pend->pkt) fatal_exit("out of memory");
+       log_pkt("pending udp pkt: ", pend->pkt, pend->pkt_len);
+
+       /* see if it matches the current moment */
+       if(runtime->now && runtime->now->evt_type == repevt_back_query &&
+               (runtime->now->addrlen == 0 || sockaddr_cmp(
+                       &runtime->now->addr, runtime->now->addrlen,
+                       &pend->addr, pend->addrlen) == 0) &&
+               find_match(runtime->now->match, pend->pkt, pend->pkt_len,
+                       pend->transport)) {
+               log_info("testbound: matched pending to event. "
+                       "advance time between events.");
+               log_info("testbound: do STEP %d %s", runtime->now->time_step,
+                       repevt_string(runtime->now->evt_type));
+               advance_moment(runtime);
+               /* still create the pending, because we need it to callback */
+       } 
+       log_info("testbound: created fake pending");
+       /* add to list */
+       pend->next = runtime->pending_list;
+       runtime->pending_list = pend;
+       return (struct pending*)pend;
+}
+
+struct waiting_tcp*
+pending_tcp_query(struct serviced_query* sq, sldns_buffer* packet,
+       int timeout, comm_point_callback_type* callback, void* callback_arg)
+{
+       struct replay_runtime* runtime = (struct replay_runtime*)
+               sq->outnet->base;
+       struct fake_pending* pend = (struct fake_pending*)calloc(1,
+               sizeof(struct fake_pending));
+       log_assert(pend);
+       pend->buffer = sldns_buffer_new(sldns_buffer_capacity(packet));
+       log_assert(pend->buffer);
+       sldns_buffer_write(pend->buffer, sldns_buffer_begin(packet),
+               sldns_buffer_limit(packet));
+       sldns_buffer_flip(pend->buffer);
+       memcpy(&pend->addr, &sq->addr, sq->addrlen);
+       pend->addrlen = sq->addrlen;
+       pend->callback = callback;
+       pend->cb_arg = callback_arg;
+       pend->timeout = timeout/1000;
+       pend->transport = transport_tcp;
+       pend->pkt = NULL;
+       pend->zone = NULL;
+       pend->runtime = runtime;
+       pend->serviced = 0;
+       pend->pkt_len = sldns_buffer_limit(packet);
+       pend->pkt = memdup(sldns_buffer_begin(packet), pend->pkt_len);
+       if(!pend->pkt) fatal_exit("out of memory");
+       log_pkt("pending tcp pkt: ", pend->pkt, pend->pkt_len);
+
+       /* see if it matches the current moment */
+       if(runtime->now && runtime->now->evt_type == repevt_back_query &&
+               (runtime->now->addrlen == 0 || sockaddr_cmp(
+                       &runtime->now->addr, runtime->now->addrlen,
+                       &pend->addr, pend->addrlen) == 0) &&
+               find_match(runtime->now->match, pend->pkt, pend->pkt_len,
+                       pend->transport)) {
+               log_info("testbound: matched pending to event. "
+                       "advance time between events.");
+               log_info("testbound: do STEP %d %s", runtime->now->time_step,
+                       repevt_string(runtime->now->evt_type));
+               advance_moment(runtime);
+               /* still create the pending, because we need it to callback */
+       } 
+       log_info("testbound: created fake pending");
+       /* add to list */
+       pend->next = runtime->pending_list;
+       runtime->pending_list = pend;
+       return (struct waiting_tcp*)pend;
+}
+
+struct serviced_query* outnet_serviced_query(struct outside_network* outnet,
+       struct query_info* qinfo, uint16_t flags, int dnssec,
+       int ATTR_UNUSED(want_dnssec), int ATTR_UNUSED(nocaps),
+       int ATTR_UNUSED(tcp_upstream), int ATTR_UNUSED(ssl_upstream),
+       char* ATTR_UNUSED(tls_auth_name), struct sockaddr_storage* addr,
+       socklen_t addrlen, uint8_t* zone, size_t zonelen,
+       struct module_qstate* qstate, comm_point_callback_type* callback,
+       void* callback_arg, sldns_buffer* ATTR_UNUSED(buff),
+       struct module_env* ATTR_UNUSED(env))
+{
+       struct replay_runtime* runtime = (struct replay_runtime*)outnet->base;
+       struct fake_pending* pend = (struct fake_pending*)calloc(1,
+               sizeof(struct fake_pending));
+       char z[256];
+       log_assert(pend);
+       log_nametypeclass(VERB_OPS, "pending serviced query", 
+               qinfo->qname, qinfo->qtype, qinfo->qclass);
+       dname_str(zone, z);
+       verbose(VERB_OPS, "pending serviced query zone %s flags%s%s%s%s", 
+               z, (flags&BIT_RD)?" RD":"", (flags&BIT_CD)?" CD":"",
+               (flags&~(BIT_RD|BIT_CD))?" MORE":"", (dnssec)?" DO":"");
+
+       /* create packet with EDNS */
+       pend->buffer = sldns_buffer_new(512);
+       log_assert(pend->buffer);
+       sldns_buffer_write_u16(pend->buffer, 0); /* id */
+       sldns_buffer_write_u16(pend->buffer, flags);
+       sldns_buffer_write_u16(pend->buffer, 1); /* qdcount */
+       sldns_buffer_write_u16(pend->buffer, 0); /* ancount */
+       sldns_buffer_write_u16(pend->buffer, 0); /* nscount */
+       sldns_buffer_write_u16(pend->buffer, 0); /* arcount */
+       sldns_buffer_write(pend->buffer, qinfo->qname, qinfo->qname_len);
+       sldns_buffer_write_u16(pend->buffer, qinfo->qtype);
+       sldns_buffer_write_u16(pend->buffer, qinfo->qclass);
+       sldns_buffer_flip(pend->buffer);
+       if(1) {
+               struct edns_data edns;
+               if(!inplace_cb_query_call(env, qinfo, flags, addr, addrlen,
+                       zone, zonelen, qstate, qstate->region)) {
+                       free(pend);
+                       return NULL;
+               }
+               /* add edns */
+               edns.edns_present = 1;
+               edns.ext_rcode = 0;
+               edns.edns_version = EDNS_ADVERTISED_VERSION;
+               edns.udp_size = EDNS_ADVERTISED_SIZE;
+               edns.bits = 0;
+               edns.opt_list = qstate->edns_opts_back_out;
+               if(dnssec)
+                       edns.bits = EDNS_DO;
+               attach_edns_record(pend->buffer, &edns);
+       }
+       memcpy(&pend->addr, addr, addrlen);
+       pend->addrlen = addrlen;
+       pend->zone = memdup(zone, zonelen);
+       pend->zonelen = zonelen;
+       pend->qtype = (int)qinfo->qtype;
+       log_assert(pend->zone);
+       pend->callback = callback;
+       pend->cb_arg = callback_arg;
+       pend->timeout = UDP_AUTH_QUERY_TIMEOUT/1000;
+       pend->transport = transport_udp; /* pretend UDP */
+       pend->pkt = NULL;
+       pend->runtime = runtime;
+       pend->serviced = 1;
+       pend->pkt_len = sldns_buffer_limit(pend->buffer);
+       pend->pkt = memdup(sldns_buffer_begin(pend->buffer), pend->pkt_len);
+       if(!pend->pkt) fatal_exit("out of memory");
+       /*log_pkt("pending serviced query: ", pend->pkt, pend->pkt_len);*/
+
+       /* see if it matches the current moment */
+       if(runtime->now && runtime->now->evt_type == repevt_back_query &&
+               (runtime->now->addrlen == 0 || sockaddr_cmp(
+                       &runtime->now->addr, runtime->now->addrlen,
+                       &pend->addr, pend->addrlen) == 0) &&
+               find_match(runtime->now->match, pend->pkt, pend->pkt_len,
+                       pend->transport)) {
+               log_info("testbound: matched pending to event. "
+                       "advance time between events.");
+               log_info("testbound: do STEP %d %s", runtime->now->time_step,
+                       repevt_string(runtime->now->evt_type));
+               advance_moment(runtime);
+               /* still create the pending, because we need it to callback */
+       } 
+       log_info("testbound: created fake pending");
+       /* add to list */
+       pend->next = runtime->pending_list;
+       runtime->pending_list = pend;
+       return (struct serviced_query*)pend;
+}
+
+void outnet_serviced_query_stop(struct serviced_query* sq, void* cb_arg)
+{
+       struct fake_pending* pend = (struct fake_pending*)sq;
+       struct replay_runtime* runtime = pend->runtime;
+       /* delete from the list */
+       struct fake_pending* p = runtime->pending_list, *prev=NULL;
+       while(p) {
+               if(p == pend) {
+                       log_assert(p->cb_arg == cb_arg);
+                       (void)cb_arg;
+                       log_info("serviced pending delete");
+                       if(prev)
+                               prev->next = p->next;
+                       else    runtime->pending_list = p->next;
+                       sldns_buffer_free(p->buffer);
+                       free(p->pkt);
+                       free(p->zone);
+                       free(p);
+                       return;
+               }
+               prev = p;
+               p = p->next;
+       }
+       log_info("double delete of pending serviced query");
+}
+
+struct listen_port* listening_ports_open(struct config_file* ATTR_UNUSED(cfg),
+       int* ATTR_UNUSED(reuseport))
+{
+       return calloc(1, 1);
+}
+
+void listening_ports_free(struct listen_port* list)
+{
+       free(list);
+}
+
+struct comm_point* comm_point_create_local(struct comm_base* ATTR_UNUSED(base),
+        int ATTR_UNUSED(fd), size_t ATTR_UNUSED(bufsize),
+        comm_point_callback_type* ATTR_UNUSED(callback), 
+       void* ATTR_UNUSED(callback_arg))
+{
+       struct fake_commpoint* fc = (struct fake_commpoint*)calloc(1,
+               sizeof(*fc));
+       if(!fc) return NULL;
+       fc->typecode = FAKE_COMMPOINT_TYPECODE;
+       return (struct comm_point*)fc;
+}
+
+struct comm_point* comm_point_create_raw(struct comm_base* ATTR_UNUSED(base),
+        int ATTR_UNUSED(fd), int ATTR_UNUSED(writing),
+        comm_point_callback_type* ATTR_UNUSED(callback), 
+       void* ATTR_UNUSED(callback_arg))
+{
+       /* no pipe comm possible */
+       struct fake_commpoint* fc = (struct fake_commpoint*)calloc(1,
+               sizeof(*fc));
+       if(!fc) return NULL;
+       fc->typecode = FAKE_COMMPOINT_TYPECODE;
+       return (struct comm_point*)fc;
+}
+
+void comm_point_start_listening(struct comm_point* ATTR_UNUSED(c), 
+       int ATTR_UNUSED(newfd), int ATTR_UNUSED(sec))
+{
+       /* no bg write pipe comm possible */
+}
+
+void comm_point_stop_listening(struct comm_point* ATTR_UNUSED(c))
+{
+       /* no bg write pipe comm possible */
+}
+
+/* only cmd com _local gets deleted */
+void comm_point_delete(struct comm_point* c)
+{
+       struct fake_commpoint* fc = (struct fake_commpoint*)c;
+       if(c == NULL) return;
+       log_assert(fc->typecode == FAKE_COMMPOINT_TYPECODE);
+       if(fc->type_tcp_out) {
+               /* remove tcp pending, so no more callbacks to it */
+               pending_list_delete(fc->runtime, fc->pending);
+       }
+       free(c);
+}
+
+size_t listen_get_mem(struct listen_dnsport* ATTR_UNUSED(listen))
+{
+       return 0;
+}
+
+size_t outnet_get_mem(struct outside_network* ATTR_UNUSED(outnet))
+{
+       return 0;
+}
+
+size_t comm_point_get_mem(struct comm_point* ATTR_UNUSED(c))
+{
+       return 0;
+}
+
+size_t serviced_get_mem(struct serviced_query* ATTR_UNUSED(c))
+{
+       return 0;
+}
+
+/* fake for fptr wlist */
+int outnet_udp_cb(struct comm_point* ATTR_UNUSED(c), 
+       void* ATTR_UNUSED(arg), int ATTR_UNUSED(error),
+        struct comm_reply *ATTR_UNUSED(reply_info))
+{
+       log_assert(0);
+       return 0;
+}
+
+int outnet_tcp_cb(struct comm_point* ATTR_UNUSED(c), 
+       void* ATTR_UNUSED(arg), int ATTR_UNUSED(error),
+        struct comm_reply *ATTR_UNUSED(reply_info))
+{
+       log_assert(0);
+       return 0;
+}
+
+void pending_udp_timer_cb(void *ATTR_UNUSED(arg))
+{
+       log_assert(0);
+}
+
+void pending_udp_timer_delay_cb(void *ATTR_UNUSED(arg))
+{
+       log_assert(0);
+}
+
+void outnet_tcptimer(void* ATTR_UNUSED(arg))
+{
+       log_assert(0);
+}
+
+void comm_point_udp_callback(int ATTR_UNUSED(fd), short ATTR_UNUSED(event), 
+       void* ATTR_UNUSED(arg))
+{
+       log_assert(0);
+}
+
+void comm_point_udp_ancil_callback(int ATTR_UNUSED(fd), 
+       short ATTR_UNUSED(event), void* ATTR_UNUSED(arg))
+{
+       log_assert(0);
+}
+
+void comm_point_tcp_accept_callback(int ATTR_UNUSED(fd), 
+       short ATTR_UNUSED(event), void* ATTR_UNUSED(arg))
+{
+       log_assert(0);
+}
+
+void comm_point_tcp_handle_callback(int ATTR_UNUSED(fd), 
+       short ATTR_UNUSED(event), void* ATTR_UNUSED(arg))
+{
+       log_assert(0);
+}
+
+void comm_timer_callback(int ATTR_UNUSED(fd), 
+       short ATTR_UNUSED(event), void* ATTR_UNUSED(arg))
+{
+       log_assert(0);
+}
+
+void comm_signal_callback(int ATTR_UNUSED(fd), 
+       short ATTR_UNUSED(event), void* ATTR_UNUSED(arg))
+{
+       log_assert(0);
+}
+
+void comm_point_http_handle_callback(int ATTR_UNUSED(fd), 
+       short ATTR_UNUSED(event), void* ATTR_UNUSED(arg))
+{
+       log_assert(0);
+}
+
+void comm_point_local_handle_callback(int ATTR_UNUSED(fd), 
+       short ATTR_UNUSED(event), void* ATTR_UNUSED(arg))
+{
+       log_assert(0);
+}
+
+void comm_point_raw_handle_callback(int ATTR_UNUSED(fd), 
+       short ATTR_UNUSED(event), void* ATTR_UNUSED(arg))
+{
+       log_assert(0);
+}
+
+void comm_base_handle_slow_accept(int ATTR_UNUSED(fd), 
+       short ATTR_UNUSED(event), void* ATTR_UNUSED(arg))
+{
+       log_assert(0);
+}
+
+int serviced_udp_callback(struct comm_point* ATTR_UNUSED(c), 
+       void* ATTR_UNUSED(arg), int ATTR_UNUSED(error),
+        struct comm_reply* ATTR_UNUSED(reply_info))
+{
+       log_assert(0);
+       return 0;
+}
+
+int serviced_tcp_callback(struct comm_point* ATTR_UNUSED(c), 
+       void* ATTR_UNUSED(arg), int ATTR_UNUSED(error),
+        struct comm_reply* ATTR_UNUSED(reply_info))
+{
+       log_assert(0);
+       return 0;
+}
+
+int pending_cmp(const void* ATTR_UNUSED(a), const void* ATTR_UNUSED(b))
+{
+       log_assert(0);
+       return 0;
+}
+
+int serviced_cmp(const void* ATTR_UNUSED(a), const void* ATTR_UNUSED(b))
+{
+       log_assert(0);
+       return 0;
+}
+
+/* timers in testbound for autotrust. statistics tested in tdir. */
+struct comm_timer* comm_timer_create(struct comm_base* base, 
+       void (*cb)(void*), void* cb_arg)
+{
+       struct replay_runtime* runtime = (struct replay_runtime*)base;
+       struct fake_timer* t = (struct fake_timer*)calloc(1, sizeof(*t));
+       t->cb = cb;
+       t->cb_arg = cb_arg;
+       fptr_ok(fptr_whitelist_comm_timer(t->cb)); /* check in advance */
+       t->runtime = runtime;
+       t->next = runtime->timer_list;
+       runtime->timer_list = t;
+       return (struct comm_timer*)t;
+}
+
+void comm_timer_disable(struct comm_timer* timer)
+{
+       struct fake_timer* t = (struct fake_timer*)timer;
+       log_info("fake timer disabled");
+       t->enabled = 0;
+}
+
+void comm_timer_set(struct comm_timer* timer, struct timeval* tv)
+{
+       struct fake_timer* t = (struct fake_timer*)timer;
+       t->enabled = 1;
+       t->tv = *tv;
+       log_info("fake timer set %d.%6.6d", 
+               (int)t->tv.tv_sec, (int)t->tv.tv_usec);
+       timeval_add(&t->tv, &t->runtime->now_tv);
+}
+
+void comm_timer_delete(struct comm_timer* timer)
+{
+       struct fake_timer* t = (struct fake_timer*)timer;
+       struct fake_timer** pp, *p;
+       if(!t) return;
+
+       /* remove from linked list */
+       pp = &t->runtime->timer_list;
+       p = t->runtime->timer_list;
+       while(p) {
+               if(p == t) {
+                       /* snip from list */
+                       *pp = p->next;
+                       break;
+               }
+               pp = &p->next;
+               p = p->next;
+       }
+
+       free(timer);
+}
+
+void comm_base_set_slow_accept_handlers(struct comm_base* ATTR_UNUSED(b),
+       void (*stop_acc)(void*), void (*start_acc)(void*),
+       void* ATTR_UNUSED(arg))
+{
+       /* ignore this */
+       (void)stop_acc;
+       (void)start_acc;
+}
+
+struct ub_event_base* comm_base_internal(struct comm_base* ATTR_UNUSED(b))
+{
+       /* no pipe comm possible in testbound */
+       return NULL;
+}
+
+void daemon_remote_exec(struct worker* ATTR_UNUSED(worker))
+{
+}
+
+void listen_start_accept(struct listen_dnsport* ATTR_UNUSED(listen))
+{
+}
+
+void listen_stop_accept(struct listen_dnsport* ATTR_UNUSED(listen))
+{
+}
+
+void daemon_remote_start_accept(struct daemon_remote* ATTR_UNUSED(rc))
+{
+}
+
+void daemon_remote_stop_accept(struct daemon_remote* ATTR_UNUSED(rc))
+{
+}
+
+int create_udp_sock(int ATTR_UNUSED(family), int ATTR_UNUSED(socktype),
+       struct sockaddr* ATTR_UNUSED(addr), socklen_t ATTR_UNUSED(addrlen),
+       int ATTR_UNUSED(v6only), int* ATTR_UNUSED(inuse),
+       int* ATTR_UNUSED(noproto), int ATTR_UNUSED(rcv), int ATTR_UNUSED(snd),
+       int ATTR_UNUSED(listen), int* ATTR_UNUSED(reuseport),
+       int ATTR_UNUSED(transparent), int ATTR_UNUSED(freebind),
+       int ATTR_UNUSED(use_systemd))
+{
+       /* if you actually print to this, it'll be stdout during test */
+       return 1;
+}
+
+struct comm_point* comm_point_create_udp(struct comm_base *ATTR_UNUSED(base),
+       int ATTR_UNUSED(fd), sldns_buffer* ATTR_UNUSED(buffer),
+       comm_point_callback_type* ATTR_UNUSED(callback),
+       void* ATTR_UNUSED(callback_arg))
+{
+       log_assert(0);
+       return NULL;
+}
+
+struct comm_point* comm_point_create_tcp_out(struct comm_base*
+       ATTR_UNUSED(base), size_t ATTR_UNUSED(bufsize),
+       comm_point_callback_type* ATTR_UNUSED(callback),
+       void* ATTR_UNUSED(callback_arg))
+{
+       log_assert(0);
+       return NULL;
+}
+
+struct comm_point* outnet_comm_point_for_udp(struct outside_network* outnet,
+       comm_point_callback_type* cb, void* cb_arg,
+       struct sockaddr_storage* ATTR_UNUSED(to_addr),
+       socklen_t ATTR_UNUSED(to_addrlen))
+{
+       struct replay_runtime* runtime = (struct replay_runtime*)
+               outnet->base;
+       struct fake_commpoint* fc = (struct fake_commpoint*)calloc(1,
+               sizeof(*fc));
+       if(!fc) return NULL;
+       fc->typecode = FAKE_COMMPOINT_TYPECODE;
+       fc->type_udp_out = 1;
+       fc->cb = cb;
+       fc->cb_arg = cb_arg;
+       fc->runtime = runtime;
+       /* used by authzone transfers */
+       return (struct comm_point*)fc;
+}
+
+struct comm_point* outnet_comm_point_for_tcp(struct outside_network* outnet,
+       comm_point_callback_type* cb, void* cb_arg,
+       struct sockaddr_storage* to_addr, socklen_t to_addrlen,
+       struct sldns_buffer* query, int timeout)
+{
+       struct replay_runtime* runtime = (struct replay_runtime*)
+               outnet->base;
+       struct fake_commpoint* fc = (struct fake_commpoint*)calloc(1,
+               sizeof(*fc));
+       struct fake_pending* pend = (struct fake_pending*)calloc(1,
+               sizeof(struct fake_pending));
+       if(!fc || !pend) {
+               free(fc);
+               free(pend);
+               return NULL;
+       }
+       fc->typecode = FAKE_COMMPOINT_TYPECODE;
+       fc->type_tcp_out = 1;
+       fc->cb = cb;
+       fc->cb_arg = cb_arg;
+       fc->runtime = runtime;
+       fc->pending = pend;
+
+       /* used by authzone transfers */
+       /* create pending item */
+       pend->buffer = sldns_buffer_new(sldns_buffer_limit(query)+10);
+       if(!pend->buffer) {
+               free(fc);
+               free(pend);
+               return NULL;
+       }
+       sldns_buffer_copy(pend->buffer, query);
+       memcpy(&pend->addr, to_addr, to_addrlen);
+       pend->addrlen = to_addrlen;
+       pend->zone = NULL;
+       pend->zonelen = 0;
+       if(LDNS_QDCOUNT(sldns_buffer_begin(query)) > 0) {
+               char buf[512];
+               char addrbuf[128];
+               (void)sldns_wire2str_rrquestion_buf(sldns_buffer_at(query, LDNS_HEADER_SIZE), sldns_buffer_limit(query)-LDNS_HEADER_SIZE, buf, sizeof(buf));
+               addr_to_str((struct sockaddr_storage*)to_addr, to_addrlen,
+                       addrbuf, sizeof(addrbuf));
+               if(verbosity >= VERB_ALGO) {
+                       if(buf[0] != 0) buf[strlen(buf)-1] = 0; /* del newline*/
+                       log_info("tcp to %s: %s", addrbuf, buf);
+               }
+               log_assert(sldns_buffer_limit(query)-LDNS_HEADER_SIZE >= 2);
+               pend->qtype = (int)sldns_buffer_read_u16_at(query,
+                       LDNS_HEADER_SIZE+
+                       dname_valid(sldns_buffer_at(query, LDNS_HEADER_SIZE),
+                               sldns_buffer_limit(query)-LDNS_HEADER_SIZE));
+       }
+       pend->callback = cb;
+       pend->cb_arg = cb_arg;
+       pend->timeout = timeout;
+       pend->transport = transport_tcp;
+       pend->pkt = NULL;
+       pend->runtime = runtime;
+       pend->serviced = 0;
+       pend->pkt_len = sldns_buffer_limit(pend->buffer);
+       pend->pkt = memdup(sldns_buffer_begin(pend->buffer), pend->pkt_len);
+       if(!pend->pkt) fatal_exit("out of memory");
+
+       log_info("testbound: created fake pending for tcp_out");
+
+       /* add to list */
+       pend->next = runtime->pending_list;
+       runtime->pending_list = pend;
+
+       return (struct comm_point*)fc;
+}
+
+struct comm_point* outnet_comm_point_for_http(struct outside_network* outnet,
+       comm_point_callback_type* cb, void* cb_arg,
+       struct sockaddr_storage* to_addr, socklen_t to_addrlen, int timeout,
+       int ssl, char* host, char* path)
+{
+       struct replay_runtime* runtime = (struct replay_runtime*)
+               outnet->base;
+       struct fake_commpoint* fc = (struct fake_commpoint*)calloc(1,
+               sizeof(*fc));
+       if(!fc) {
+               return NULL;
+       }
+       fc->typecode = FAKE_COMMPOINT_TYPECODE;
+       fc->type_http_out = 1;
+       fc->cb = cb;
+       fc->cb_arg = cb_arg;
+       fc->runtime = runtime;
+
+       (void)to_addr;
+       (void)to_addrlen;
+       (void)timeout;
+
+       (void)ssl;
+       (void)host;
+       (void)path;
+
+       /* handle http comm point and return contents from test script */
+       return (struct comm_point*)fc;
+}
+
+int comm_point_send_udp_msg(struct comm_point *c, sldns_buffer* packet,
+       struct sockaddr* addr, socklen_t addrlen) 
+{
+       struct fake_commpoint* fc = (struct fake_commpoint*)c;
+       struct replay_runtime* runtime = fc->runtime;
+       struct fake_pending* pend = (struct fake_pending*)calloc(1,
+               sizeof(struct fake_pending));
+       if(!pend) {
+               log_err("malloc failure");
+               return 0;
+       }
+       fc->pending = pend;
+       /* used by authzone transfers */
+       /* create pending item */
+       pend->buffer = sldns_buffer_new(sldns_buffer_limit(packet) + 10);
+       if(!pend->buffer) {
+               free(pend);
+               return 0;
+       }
+       sldns_buffer_copy(pend->buffer, packet);
+       memcpy(&pend->addr, addr, addrlen);
+       pend->addrlen = addrlen;
+       pend->zone = NULL;
+       pend->zonelen = 0;
+       if(LDNS_QDCOUNT(sldns_buffer_begin(packet)) > 0) {
+               char buf[512];
+               char addrbuf[128];
+               (void)sldns_wire2str_rrquestion_buf(sldns_buffer_at(packet, LDNS_HEADER_SIZE), sldns_buffer_limit(packet)-LDNS_HEADER_SIZE, buf, sizeof(buf));
+               addr_to_str((struct sockaddr_storage*)addr, addrlen,
+                       addrbuf, sizeof(addrbuf));
+               if(verbosity >= VERB_ALGO) {
+                       if(buf[0] != 0) buf[strlen(buf)-1] = 0; /* del newline*/
+                       log_info("udp to %s: %s", addrbuf, buf);
+               }
+               log_assert(sldns_buffer_limit(packet)-LDNS_HEADER_SIZE >= 2);
+               pend->qtype = (int)sldns_buffer_read_u16_at(packet,
+                       LDNS_HEADER_SIZE+
+                       dname_valid(sldns_buffer_at(packet, LDNS_HEADER_SIZE),
+                               sldns_buffer_limit(packet)-LDNS_HEADER_SIZE));
+       }
+       pend->callback = fc->cb;
+       pend->cb_arg = fc->cb_arg;
+       pend->timeout = UDP_AUTH_QUERY_TIMEOUT/1000;
+       pend->transport = transport_udp;
+       pend->pkt = NULL;
+       pend->runtime = runtime;
+       pend->serviced = 0;
+       pend->pkt_len = sldns_buffer_limit(pend->buffer);
+       pend->pkt = memdup(sldns_buffer_begin(pend->buffer), pend->pkt_len);
+       if(!pend->pkt) fatal_exit("out of memory");
+
+       log_info("testbound: created fake pending for send_udp_msg");
+
+       /* add to list */
+       pend->next = runtime->pending_list;
+       runtime->pending_list = pend;
+
+       return 1;
+}
+
+int outnet_get_tcp_fd(struct sockaddr_storage* ATTR_UNUSED(addr),
+       socklen_t ATTR_UNUSED(addrlen), int ATTR_UNUSED(tcp_mss))
+{
+       log_assert(0);
+       return -1;
+}
+
+int outnet_tcp_connect(int ATTR_UNUSED(s), struct sockaddr_storage* ATTR_UNUSED(addr),
+       socklen_t ATTR_UNUSED(addrlen))
+{
+       log_assert(0);
+       return 0;
+}
+
+/*********** End of Dummy routines ***********/
diff --git a/usr.sbin/unbound/testcode/fake_event.h b/usr.sbin/unbound/testcode/fake_event.h
new file mode 100644 (file)
index 0000000..97ebb41
--- /dev/null
@@ -0,0 +1,75 @@
+/*
+ * testcode/fake_event.h - fake event handling that replays existing scenario.
+ *
+ * Copyright (c) 2007, NLnet Labs. All rights reserved.
+ * 
+ * This software is open source.
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 
+ * Neither the name of the NLNET LABS nor the names of its contributors may
+ * be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+ * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * \file
+ * Event service that replays a scenario.
+ * This implements the same exported symbols as the files:
+ * util/netevent.c
+ * services/listen_dnsport.c
+ * services/outside_network.c
+ * But these do not actually access the network or events, instead
+ * the scenario is played.
+ */
+
+#ifndef TESTCODE_FAKE_EVENT_H
+#define TESTCODE_FAKE_EVENT_H
+struct replay_scenario;
+
+/**
+ * Initialise fake event services.
+ *
+ * The fake event services will automatically start when the main program
+ * calls netevent.h functions, such as comm_base_dispatch().
+ *
+ * @param scen: Set the scenario to use for upcoming event handling.
+ */
+void fake_event_init(struct replay_scenario* scen);
+
+/**
+ * Deinit fake event services.
+ */
+void fake_event_cleanup(void);
+
+/**
+ * Get filename to store temporary config stuff. The pid is added. in /tmp.
+ * @param adj: adjective, like "_cfg_", "_auto_"
+ * @param id: identifier, like "example.com".
+ * @param buf: where to store.
+ * @param len: length of buf.
+ */
+void fake_temp_file(const char* adj, const char* id, char* buf, size_t len);
+
+#endif /* TESTCODE_FAKE_EVENT_H */
diff --git a/usr.sbin/unbound/testcode/lock_verify.c b/usr.sbin/unbound/testcode/lock_verify.c
new file mode 100644 (file)
index 0000000..666a702
--- /dev/null
@@ -0,0 +1,426 @@
+/*
+ * testcode/lock_verify.c - verifier program for lock traces, checks order.
+ *
+ * Copyright (c) 2007, NLnet Labs. All rights reserved.
+ *
+ * This software is open source.
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 
+ * Neither the name of the NLNET LABS nor the names of its contributors may
+ * be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+ * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * \file
+ *
+ * This file checks the lock traces generated by checklock.c.
+ * Checks if locks are consistently locked in the same order.
+ * If not, this can lead to deadlock if threads execute the different
+ * ordering at the same time.
+ * 
+ */
+
+#include "config.h"
+#ifdef HAVE_TIME_H
+#include <time.h>
+#endif
+#include "util/log.h"
+#include "util/rbtree.h"
+#include "util/locks.h"
+#include "util/fptr_wlist.h"
+
+/* --- data structures --- */
+struct lock_ref;
+
+/** keep track of lock id in lock-verify application 
+ * Also defined in smallapp/worker_cb.c for fptr_wlist encapsulation 
+ * breakage (the security tests break encapsulation for this test app) */
+struct order_id {
+        /** the thread id that created it */
+        int thr;
+        /** the instance number of creation */
+        int instance;
+};
+
+/** a lock */
+struct order_lock {
+       /** rbnode in all tree */
+       rbnode_type node;
+       /** lock id */
+       struct order_id id;
+       /** the creation file */
+       char* create_file;
+       /** creation line */
+       int create_line;
+       /** set of all locks that are smaller than this one (locked earlier) */
+       rbtree_type* smaller;
+       /** during depthfirstsearch, this is a linked list of the stack 
+        * of locks. points to the next lock bigger than this one. */
+       struct lock_ref* dfs_next;
+       /** if lock has been visited (all smaller locks have been compared to
+        * this lock), only need to compare this with all unvisited(bigger) 
+        * locks */
+       int visited;
+};
+
+/** reference to a lock in a rbtree set */
+struct lock_ref {
+       /** rbnode, key is an order_id ptr */
+       rbnode_type node;
+       /** the lock referenced */
+       struct order_lock* lock;
+       /** why is this ref */
+       char* file;
+       /** line number */
+       int line;
+};
+
+/** count of errors detected */
+static int errors_detected = 0;
+/** verbose? */
+static int verb = 0;
+
+/** print program usage help */
+static void
+usage(void)
+{
+       printf("lock_verify <trace files>\n");
+}
+
+/** read header entry. 
+ * @param in: file to read header of.
+ * @return: False if it does not belong to the rest. */
+static int 
+read_header(FILE* in)
+{
+       time_t t;
+       pid_t p;
+       int thrno;
+       static int have_values = 0;
+       static time_t the_time;
+       static pid_t the_pid;
+       static int threads[256];
+
+       if(fread(&t, sizeof(t), 1, in) != 1 ||  
+               fread(&thrno, sizeof(thrno), 1, in) != 1 ||
+               fread(&p, sizeof(p), 1, in) != 1) {
+               fatal_exit("fread failed");
+       }
+       /* check these values are sorta OK */
+       if(!have_values) {
+               the_time = t;
+               the_pid = p;
+               memset(threads, 0, 256*sizeof(int));
+               if(thrno >= 256) {
+                       fatal_exit("Thread number too big. %d", thrno);
+               }
+               threads[thrno] = 1;
+               have_values = 1;
+               printf(" trace %d from pid %u on %s", thrno, 
+                       (unsigned)p, ctime(&t));
+       } else {
+               if(the_pid != p) {
+                       printf(" has pid %u, not %u. Skipped.\n",
+                               (unsigned)p, (unsigned)the_pid);
+                       return 0;
+               }
+               if(threads[thrno])
+                       fatal_exit("same threadno in two files");
+               threads[thrno] = 1;
+               if( abs((int)(the_time - t)) > 3600)
+                       fatal_exit("input files from different times: %u %u",
+                               (unsigned)the_time, (unsigned)t);
+               printf(" trace of thread %u:%d\n", (unsigned)p, thrno);
+       }
+       return 1;
+}
+
+/** max length of strings: filenames and function names. */
+#define STRMAX 1024
+/** read a string from file, false on error */
+static int readup_str(char** str, FILE* in)
+{
+       char buf[STRMAX];
+       int len = 0;
+       int c;
+       /* ends in zero */
+       while( (c = fgetc(in)) != 0) {
+               if(c == EOF)
+                       fatal_exit("eof in readstr, file too short");
+               buf[len++] = c;
+               if(len == STRMAX) {
+                       fatal_exit("string too long, bad file format");
+               }
+       }
+       buf[len] = 0;
+       *str = strdup(buf);
+       return 1;
+}
+
+/** read creation entry */
+static void read_create(rbtree_type* all, FILE* in)
+{
+       struct order_lock* o = calloc(1, sizeof(struct order_lock));
+       if(!o) fatal_exit("malloc failure");
+       if(fread(&o->id.thr, sizeof(int), 1, in) != 1 ||        
+          fread(&o->id.instance, sizeof(int), 1, in) != 1 ||   
+          !readup_str(&o->create_file, in) ||
+          fread(&o->create_line, sizeof(int), 1, in) != 1)
+               fatal_exit("fread failed");
+       o->smaller = rbtree_create(order_lock_cmp);
+       o->node.key = &o->id;
+       if(!rbtree_insert(all, &o->node)) {
+               /* already inserted */
+               struct order_lock* a = (struct order_lock*)rbtree_search(all, 
+                       &o->id);
+               log_assert(a);
+               a->create_file = o->create_file;
+               a->create_line = o->create_line;
+               free(o->smaller);
+               free(o);
+               o = a;
+       }
+       if(verb) printf("read create %u %u %s %d\n", 
+               (unsigned)o->id.thr, (unsigned)o->id.instance,
+               o->create_file, o->create_line);
+}
+
+/** insert lock entry (empty) into list */
+static struct order_lock* 
+insert_lock(rbtree_type* all, struct order_id* id)
+{
+       struct order_lock* o = calloc(1, sizeof(struct order_lock));
+       if(!o) fatal_exit("malloc failure");
+       o->smaller = rbtree_create(order_lock_cmp);
+       o->id = *id;
+       o->node.key = &o->id;
+       if(!rbtree_insert(all, &o->node))
+               fatal_exit("insert fail should not happen");
+       return o;
+}
+
+/** read lock entry */
+static void read_lock(rbtree_type* all, FILE* in, int val)
+{
+       struct order_id prev_id, now_id;
+       struct lock_ref* ref;
+       struct order_lock* prev, *now;
+       ref = (struct lock_ref*)calloc(1, sizeof(struct lock_ref));
+       if(!ref) fatal_exit("malloc failure");
+       prev_id.thr = val;
+       if(fread(&prev_id.instance, sizeof(int), 1, in) != 1 || 
+          fread(&now_id.thr, sizeof(int), 1, in) != 1 ||       
+          fread(&now_id.instance, sizeof(int), 1, in) != 1 ||  
+          !readup_str(&ref->file, in) ||
+          fread(&ref->line, sizeof(int), 1, in) != 1)
+               fatal_exit("fread failed");
+       if(verb) printf("read lock %u %u %u %u %s %d\n", 
+               (unsigned)prev_id.thr, (unsigned)prev_id.instance,
+               (unsigned)now_id.thr, (unsigned)now_id.instance,
+               ref->file, ref->line);
+       /* find the two locks involved */
+       prev = (struct order_lock*)rbtree_search(all, &prev_id);
+       now = (struct order_lock*)rbtree_search(all, &now_id);
+       /* if not there - insert 'em */
+       if(!prev) prev = insert_lock(all, &prev_id);
+       if(!now) now = insert_lock(all, &now_id);
+       ref->lock = prev;
+       ref->node.key = &prev->id;
+       if(!rbtree_insert(now->smaller, &ref->node)) {
+               free(ref->file);
+               free(ref);
+       }
+}
+
+/** read input file */
+static void readinput(rbtree_type* all, char* file)
+{
+       FILE *in = fopen(file, "r");
+       int fst;
+       if(!in) {
+               perror(file);
+               exit(1);
+       }
+       printf("file %s", file);
+       if(!read_header(in)) {
+               fclose(in);
+               return;
+       }
+       while(fread(&fst, sizeof(fst), 1, in) == 1) {
+               if(fst == -1)
+                       read_create(all, in);
+               else    read_lock(all, in, fst);
+       }
+       fclose(in);
+}
+
+/** print cycle message */
+static void found_cycle(struct lock_ref* visit, int level)
+{
+       struct lock_ref* p;
+       int i = 0;
+       errors_detected++;
+       printf("Found inconsistent locking order of length %d\n", level);
+       printf("for lock %d %d created %s %d\n", 
+               visit->lock->id.thr, visit->lock->id.instance,
+               visit->lock->create_file, visit->lock->create_line);
+       printf("sequence is:\n");
+       p = visit;
+       while(p) {
+               struct order_lock* next = 
+                       p->lock->dfs_next?p->lock->dfs_next->lock:visit->lock;
+               printf("[%d] is locked at line %s %d before lock %d %d\n",
+                       i, p->file, p->line, next->id.thr, next->id.instance);
+               printf("[%d] lock %d %d is created at %s %d\n",
+                       i, next->id.thr, next->id.instance,
+                       next->create_file, next->create_line); 
+               i++;
+               p = p->lock->dfs_next;
+               if(p && p->lock == visit->lock)
+                       break;
+       }
+}
+
+/** Detect cycle by comparing visited now with all (unvisited) bigger nodes */
+static int detect_cycle(struct lock_ref* visit, struct lock_ref* from)
+{
+       struct lock_ref* p = from;
+       while(p) {
+               if(p->lock == visit->lock)
+                       return 1;
+               p = p->lock->dfs_next;
+       }
+       return 0;
+}
+
+/** recursive function to depth first search for cycles.
+ * @param visit: the lock visited at this step.
+ *     its dfs_next pointer gives the visited lock up in recursion.
+ *     same as lookfor at level 0.
+ * @param level: depth of recursion. 0 is start.
+ * @param from: search for matches from unvisited node upwards.
+ */
+static void search_cycle(struct lock_ref* visit, int level, 
+       struct lock_ref* from)
+{
+       struct lock_ref* ref;
+       /* check for cycle */
+       if(detect_cycle(visit, from) && level != 0) {
+               found_cycle(visit, level);
+               fatal_exit("found lock order cycle");
+       }
+       /* recurse */
+       if(!visit->lock->visited)
+               from = visit;
+       if(verb > 1) fprintf(stderr, "[%d] visit lock %u %u %s %d\n", level,
+                       (unsigned)visit->lock->id.thr, 
+                       (unsigned)visit->lock->id.instance,
+                       visit->lock->create_file, visit->lock->create_line);
+       RBTREE_FOR(ref, struct lock_ref*, visit->lock->smaller) {
+               ref->lock->dfs_next = visit;
+               search_cycle(ref, level+1, from);
+       }
+       visit->lock->visited = 1;
+}
+
+/** Check ordering of one lock */
+static void check_order_lock(struct order_lock* lock)
+{
+       struct lock_ref start;
+       if(lock->visited) return;
+
+       start.node.key = &lock->id;
+       start.lock = lock;
+       start.file = lock->create_file;
+       start.line = lock->create_line;
+
+       if(!lock->create_file)
+               log_err("lock %u %u does not have create info",
+                       (unsigned)lock->id.thr, (unsigned)lock->id.instance);
+
+       /* depth first search to find cycle with this lock at head */
+       lock->dfs_next = NULL;
+       search_cycle(&start, 0, &start);
+}
+
+/** Check ordering of locks */
+static void check_order(rbtree_type* all_locks)
+{
+       /* check each lock */
+       struct order_lock* lock;
+       int i=0;
+       RBTREE_FOR(lock, struct order_lock*, all_locks) {
+               if(verb)
+                   printf("[%d/%d] Checking lock %d %d %s %d\n",
+                       i, (int)all_locks->count,
+                       lock->id.thr, lock->id.instance, 
+                       lock->create_file, lock->create_line);
+               else if (i % ((all_locks->count/75)<1?1:all_locks->count/75) 
+                       == 0) 
+                   fprintf(stderr, ".");
+               i++;
+               check_order_lock(lock);
+       }
+       fprintf(stderr, "\n");
+}
+
+/** main program to verify all traces passed */
+int
+main(int argc, char* argv[])
+{
+       rbtree_type* all_locks;
+       int i;
+       time_t starttime = time(NULL);
+#ifdef USE_THREAD_DEBUG
+       /* do not overwrite the ublocktrace files with the ones generated
+        * by this program (i.e. when the log code creates a lock) */
+       check_locking_order = 0;
+#endif
+       if(argc <= 1) {
+               usage();
+               return 1;
+       }
+       log_init(NULL, 0, NULL);
+       log_ident_set("lock-verify");
+       /* init */
+       all_locks = rbtree_create(order_lock_cmp);
+       errors_detected = 0;
+
+       /* read the input files */
+       for(i=1; i<argc; i++) {
+               readinput(all_locks, argv[i]);
+       }
+
+       /* check ordering */
+       check_order(all_locks);
+
+       /* do not free a thing, OS will do it */
+       printf("checked %d locks in %d seconds with %d errors.\n", 
+               (int)all_locks->count, (int)(time(NULL)-starttime),
+               errors_detected);
+       if(errors_detected) return 1;
+       return 0;
+}
diff --git a/usr.sbin/unbound/testcode/memstats.c b/usr.sbin/unbound/testcode/memstats.c
new file mode 100644 (file)
index 0000000..dc29058
--- /dev/null
@@ -0,0 +1,249 @@
+/*
+ * testcode/memstats.c - debug tool to show memory allocation statistics.
+ *
+ * Copyright (c) 2007, NLnet Labs. All rights reserved.
+ *
+ * This software is open source.
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 
+ * Neither the name of the NLNET LABS nor the names of its contributors may
+ * be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+ * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * \file
+ *
+ * This program reads a log file and prints the memory allocation summed
+ * up.
+ */
+#include "config.h"
+#include "util/log.h"
+#include "util/rbtree.h"
+#include "util/locks.h"
+#include "util/fptr_wlist.h"
+#include <sys/stat.h>
+
+/**
+ * The allocation statistics block
+ */
+struct codeline {
+       /** rbtree node */
+       rbnode_type node;
+       /** the name of the file:linenumber */
+       char* codeline;
+       /** the name of the function */
+       char* func;
+       /** number of bytes allocated */
+       uint64_t alloc;
+       /** number of bytes freed */
+       uint64_t free;
+       /** number allocations and frees */
+       uint64_t calls;
+};
+
+/** print usage and exit */
+static void
+usage(void)
+{
+       printf("usage:  memstats <logfile>\n");
+       printf("statistics are printed on stdout.\n");
+       exit(1);
+}
+
+/** match logfile line to see if it needs accounting processing */
+static int
+match(char* line)
+{
+       /* f.e.:
+        * [1187340064] unbound[24604:0] info: ul/rb.c:81 r_create malloc(12)
+        * 0123456789 123456789 123456789 123456789
+        * But now also:
+        * Sep 16 15:18:20 unbound[1:0] info: ul/nh.c:143 memdup malloc(11)
+        */
+       if(strlen(line) < 32) /* up to 'info: ' */
+               return 0;
+       if(!strstr(line, " info: "))
+               return 0;
+       if(strstr(line, "info: stat "))
+               return 0; /* skip the hex dumps */
+       if(strstr(line+30, "malloc("))
+               return 1;
+       else if(strstr(line+30, "calloc("))
+               return 1;
+       /* skip reallocs */
+       return 0;
+}
+
+/** find or alloc codeline in tree */
+static struct codeline*
+get_codeline(rbtree_type* tree, char* key, char* func)
+{
+       struct codeline* cl = (struct codeline*)rbtree_search(tree, key);
+       if(!cl) {
+               cl = calloc(1, sizeof(*cl));
+               if(!cl) return 0;
+               cl->codeline = strdup(key);
+               if(!cl->codeline) return 0;
+               cl->func = strdup(func);
+               if(!cl->func) return 0;
+               cl->alloc = 0;
+               cl->node.key = cl->codeline;
+               (void)rbtree_insert(tree, &cl->node);
+       }
+       return cl;
+}
+
+/** read up the malloc stats */
+static void
+read_malloc_stat(char* line, rbtree_type* tree)
+{
+       char codeline[10240];
+       char name[10240];
+       int skip = 0;
+       long num = 0;
+       struct codeline* cl = 0;
+       line = strstr(line, "info: ")+6;
+       if(sscanf(line, "%s %s %n", codeline, name, &skip) != 2) {
+               printf("%s\n", line);
+               fatal_exit("unhandled malloc");
+       }
+       if(sscanf(line+skip+7, "%ld", &num) != 1) {
+               printf("%s\n%s\n", line, line+skip+7);
+               fatal_exit("unhandled malloc");
+       }
+       cl = get_codeline(tree, codeline, name);
+       if(!cl)
+               fatal_exit("alloc failure");
+       cl->alloc += num;
+       cl->calls ++;
+}
+
+/** read up the calloc stats */
+static void
+read_calloc_stat(char* line, rbtree_type* tree)
+{
+       char codeline[10240];
+       char name[10240];
+       int skip = 0;
+       long num = 0, sz = 0;
+       struct codeline* cl = 0;
+       line = strstr(line, "info: ")+6;
+       if(sscanf(line, "%s %s %n", codeline, name, &skip) != 2) {
+               printf("%s\n", line);
+               fatal_exit("unhandled calloc");
+       }
+       if(sscanf(line+skip+7, "%ld, %ld", &num, &sz) != 2) {
+               printf("%s\n%s\n", line, line+skip+7);
+               fatal_exit("unhandled calloc");
+       }
+
+       cl = get_codeline(tree, codeline, name);
+       if(!cl)
+               fatal_exit("alloc failure");
+       cl->alloc += num*sz;
+       cl->calls ++;
+}
+
+/** get size of file */
+static off_t
+get_file_size(const char* fname)
+{
+       struct stat s;
+       if(stat(fname, &s) < 0) {
+               fatal_exit("could not stat %s: %s", fname, strerror(errno));
+       }
+       return s.st_size;
+}
+
+/** read the logfile */
+static void
+readfile(rbtree_type* tree, const char* fname)
+{
+       off_t total = get_file_size(fname);
+       off_t done = (off_t)0;
+       int report = 0;
+       FILE* in = fopen(fname, "r");
+       char buf[102400];
+       if(!in)
+               fatal_exit("could not open %s: %s", fname, strerror(errno));
+       printf("Reading %s of size " ARG_LL "d\n", fname, (long long)total);
+       while(fgets(buf, 102400, in)) {
+               buf[102400-1] = 0;
+               done += (off_t)strlen(buf);
+               /* progress count */
+               if((int)(((double)done / (double)total)*100.) > report) {
+                       report = (int)(((double)done / (double)total)*100.);
+                       fprintf(stderr, " %d%%", report);
+               }
+
+               if(!match(buf))
+                       continue;
+               else if(strstr(buf+30, "malloc("))
+                       read_malloc_stat(buf, tree);
+               else if(strstr(buf+30, "calloc("))
+                       read_calloc_stat(buf, tree);
+               else {
+                       printf("%s\n", buf);
+                       fatal_exit("unhandled input");
+               }
+       }
+       fprintf(stderr, " done\n");
+       fclose(in);
+}
+
+/** print memory stats */
+static void
+printstats(rbtree_type* tree)
+{
+       struct codeline* cl;
+       uint64_t total = 0, tcalls = 0;
+       RBTREE_FOR(cl, struct codeline*, tree) {
+               printf("%12lld / %8lld in %s %s\n", (long long)cl->alloc, 
+                       (long long)cl->calls, cl->codeline, cl->func);
+               total += cl->alloc;
+               tcalls += cl->calls;
+       }
+       printf("------------\n");
+       printf("%12lld / %8lld total in %ld code lines\n", (long long)total, 
+               (long long)tcalls, (long)tree->count);
+       printf("\n");
+}
+
+/** main program */
+int main(int argc, const char* argv[])
+{
+       rbtree_type* tree = 0;
+       log_init(NULL, 0, 0);
+       if(argc != 2) {
+               usage();
+       }
+       tree = rbtree_create(codeline_cmp);
+       if(!tree)
+               fatal_exit("alloc failure");
+       readfile(tree, argv[1]);
+       printstats(tree);
+       return 0;
+}
diff --git a/usr.sbin/unbound/testcode/mini_tdir.sh b/usr.sbin/unbound/testcode/mini_tdir.sh
new file mode 100755 (executable)
index 0000000..9674551
--- /dev/null
@@ -0,0 +1,171 @@
+# tdir that only exes the files.
+args="../.."
+if test "$1" = "-a"; then
+       args=$2
+       shift
+       shift
+fi
+       
+if test "$1" = "clean"; then
+       echo "rm -f result.* .done* .tdir.var.master .tdir.var.test"
+       rm -f result.* .done* .tdir.var.master .tdir.var.test
+       exit 0
+fi
+if test "$1" = "fake"; then
+       echo "minitdir fake $2"
+       echo "fake" > .done-`basename $2 .tdir`
+       exit 0
+fi
+if test "$1" = "-f" && test "$2" = "report"; then
+       echo "Minitdir Long Report"
+       pass=0
+       fail=0
+       skip=0
+       echo "   STATUS    ELAPSED TESTNAME TESTDESCRIPTION"
+       for result in *.tdir; do
+               name=`basename $result .tdir`
+               timelen="     "
+               desc=""
+               if test -f "result.$name"; then
+                       timestart=`grep ^DateRunStart: "result.$name" | sed -e 's/DateRunStart: //'`
+                       timeend=`grep ^DateRunEnd: "result.$name" | sed -e 's/DateRunEnd: //'`
+                       timesec=`expr $timeend - $timestart`
+                       timelen=`printf %4ds $timesec`
+                       if test $? -ne 0; then
+                               timelen="$timesec""s"
+                       fi
+                       desc=`grep ^Description: "result.$name" | sed -e 's/Description: //'`
+               fi
+               if test -f ".done-$name"; then
+                       if test "$1" != "-q"; then
+                               echo "** PASSED ** $timelen $name: $desc"
+                               pass=`expr $pass + 1`
+                       fi
+               else
+                       if test -f "result.$name"; then
+                               echo "!! FAILED !! $timelen $name: $desc"
+                               fail=`expr $fail + 1`
+                       else
+                               echo ".> SKIPPED<< $timelen $name: $desc"
+                               skip=`expr $skip + 1`
+                       fi
+               fi
+       done
+       echo ""
+       if test "$skip" = "0"; then
+               echo "$pass pass, $fail fail"
+       else
+               echo "$pass pass, $fail fail, $skip skip"
+       fi
+       echo ""
+       exit 0
+fi
+if test "$1" = "report" || test "$2" = "report"; then
+       echo "Minitdir Report"
+       for result in *.tdir; do
+               name=`basename $result .tdir`
+               if test -f ".done-$name"; then
+                       if test "$1" != "-q"; then
+                               echo "** PASSED ** : $name"
+                       fi
+               else
+                       if test -f "result.$name"; then
+                               echo "!! FAILED !! : $name"
+                       else
+                               echo ">> SKIPPED<< : $name"
+                       fi
+               fi
+       done
+       exit 0
+fi
+
+if test "$1" != 'exe'; then
+       # usage
+       echo "mini tdir. Reduced functionality for old shells."
+       echo "  tdir exe <file>"
+       echo "  tdir fake <file>"
+       echo "  tdir clean"
+       echo "  tdir [-q|-f] report"
+       exit 1
+fi
+shift
+
+# do not execute if the disk is too full
+#DISKLIMIT=100000
+# This check is not portable (to Solaris 10).
+#avail=`df . | tail -1 | awk '{print $4}'`
+#if test "$avail" -lt "$DISKLIMIT"; then
+       #echo "minitdir: The disk is too full! Only $avail."
+       #exit 1
+#fi
+
+name=`basename $1 .tdir`
+dir=$name.$$
+result=result.$name
+done=.done-$name
+success="no"
+if test -x "`which bash`"; then
+       shell="bash"
+else
+       shell="sh"
+fi
+
+# check already done
+if test -f .done-$name; then
+       echo "minitdir .done-$name exists. skip test."
+       exit 0
+fi
+
+# Copy
+echo "minitdir copy $1 to $dir"
+mkdir $dir
+cp -a $name.tdir/* $dir/
+cd $dir
+
+# EXE
+echo "minitdir exe $name" > $result
+grep "Description:" $name.dsc >> $result 2>&1
+echo "DateRunStart: "`date "+%s" 2>/dev/null` >> $result
+if test -f $name.pre; then
+       echo "minitdir exe $name.pre"
+       echo "minitdir exe $name.pre" >> $result
+       $shell $name.pre $args >> $result
+       if test $? -ne 0; then
+               echo "Warning: $name.pre did not exit successfully"
+       fi
+fi
+if test -f $name.test; then
+       echo "minitdir exe $name.test"
+       echo "minitdir exe $name.test" >> $result
+       $shell $name.test $args >>$result 2>&1
+       if test $? -ne 0; then
+               echo "$name: FAILED" >> $result
+               echo "$name: FAILED"
+               success="no"
+       else
+               echo "$name: PASSED" >> $result
+               echo "$name: PASSED" > ../.done-$name
+               echo "$name: PASSED"
+               success="yes"
+       fi
+fi
+if test -f $name.post; then
+       echo "minitdir exe $name.post"
+       echo "minitdir exe $name.post" >> $result
+       $shell $name.post $args >> $result
+       if test $? -ne 0; then
+               echo "Warning: $name.post did not exit successfully"
+       fi
+fi
+echo "DateRunEnd: "`date "+%s" 2>/dev/null` >> $result
+
+mv $result ..
+cd ..
+rm -rf $dir
+# compat for windows where deletion may not succeed initially (files locked
+# by processes that still have to exit).
+if test $? -eq 1; then
+       echo "minitdir waiting for processes to terminate"
+       sleep 2 # some time to exit, and try again
+       rm -rf $dir
+fi
diff --git a/usr.sbin/unbound/testcode/mini_tpkg.sh b/usr.sbin/unbound/testcode/mini_tpkg.sh
new file mode 100755 (executable)
index 0000000..ebf27a7
--- /dev/null
@@ -0,0 +1,128 @@
+# tpkg that only exes the files.
+args="../.."
+if test "$1" = "-a"; then
+       args=$2
+       shift
+       shift
+fi
+       
+if test "$1" = "clean"; then
+       echo "rm -f result.* .done* .tpkg.var.master .tpkg.var.test"
+       rm -f result.* .done* .tpkg.var.master .tpkg.var.test
+       exit 0
+fi
+if test "$1" = "fake"; then
+       echo "minitpkg fake $2"
+       echo "fake" > .done-`basename $2 .tpkg`
+       exit 0
+fi
+if test "$1" = "report" || test "$2" = "report"; then
+       echo "Minitpkg Report"
+       for result in *.tpkg; do
+               name=`basename $result .tpkg`
+               if test -f ".done-$name"; then
+                       if test "$1" != "-q"; then
+                               echo "** PASSED ** : $name"
+                       fi
+               else
+                       if test -f "result.$name"; then
+                               echo "!! FAILED !! : $name"
+                       else
+                               echo ">> SKIPPED<< : $name"
+                       fi
+               fi
+       done
+       exit 0
+fi
+
+if test "$1" != 'exe'; then
+       # usage
+       echo "mini tpkg. Reduced functionality for old shells."
+       echo "  tpkg exe <file>"
+       echo "  tpkg fake <file>"
+       echo "  tpkg clean"
+       echo "  tpkg [-q] report"
+       exit 1
+fi
+shift
+
+# do not execute if the disk is too full
+#DISKLIMIT=100000
+# This check is not portable (to Solaris 10).
+#avail=`df . | tail -1 | awk '{print $4}'`
+#if test "$avail" -lt "$DISKLIMIT"; then
+       #echo "minitpkg: The disk is too full! Only $avail."
+       #exit 1
+#fi
+
+name=`basename $1 .tpkg`
+dir=$name.$$
+result=result.$name
+done=.done-$name
+success="no"
+if test -x "`which bash`"; then
+       shell="bash"
+else
+       shell="sh"
+fi
+
+# check already done
+if test -f .done-$name; then
+       echo "minitpkg .done-$name exists. skip test."
+       exit 0
+fi
+
+# Extract
+echo "minitpkg extract $1 to $dir"
+mkdir $dir
+gzip -cd $name.tpkg | (cd $dir; tar xf -)
+cd $dir
+mv $name.dir/* .
+
+# EXE
+echo "minitpkg exe $name" > $result
+grep "Description:" $name.dsc >> $result 2>&1
+echo "DateRunStart: "`date "+%s" 2>/dev/null` >> $result
+if test -f $name.pre; then
+       echo "minitpkg exe $name.pre"
+       echo "minitpkg exe $name.pre" >> $result
+       $shell $name.pre $args >> $result
+       if test $? -ne 0; then
+               echo "Warning: $name.pre did not exit successfully"
+       fi
+fi
+if test -f $name.test; then
+       echo "minitpkg exe $name.test"
+       echo "minitpkg exe $name.test" >> $result
+       $shell $name.test $args >>$result 2>&1
+       if test $? -ne 0; then
+               echo "$name: FAILED" >> $result
+               echo "$name: FAILED"
+               success="no"
+       else
+               echo "$name: PASSED" >> $result
+               echo "$name: PASSED" > ../.done-$name
+               echo "$name: PASSED"
+               success="yes"
+       fi
+fi
+if test -f $name.post; then
+       echo "minitpkg exe $name.post"
+       echo "minitpkg exe $name.post" >> $result
+       $shell $name.post $args >> $result
+       if test $? -ne 0; then
+               echo "Warning: $name.post did not exit successfully"
+       fi
+fi
+echo "DateRunEnd: "`date "+%s" 2>/dev/null` >> $result
+
+mv $result ..
+cd ..
+rm -rf $dir
+# compat for windows where deletion may not succeed initially (files locked
+# by processes that still have to exit).
+if test $? -eq 1; then
+       echo "minitpkg waiting for processes to terminate"
+       sleep 2 # some time to exit, and try again
+       rm -rf $dir
+fi
diff --git a/usr.sbin/unbound/testcode/perf.c b/usr.sbin/unbound/testcode/perf.c
new file mode 100644 (file)
index 0000000..d11357c
--- /dev/null
@@ -0,0 +1,654 @@
+/*
+ * testcode/perf.c - debug program to estimate name server performance.
+ *
+ * Copyright (c) 2008, NLnet Labs. All rights reserved.
+ *
+ * This software is open source.
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 
+ * Neither the name of the NLNET LABS nor the names of its contributors may
+ * be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+ * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * \file
+ *
+ * This program estimates DNS name server performance.
+ */
+
+#include "config.h"
+#ifdef HAVE_GETOPT_H
+#include <getopt.h>
+#endif
+#include <signal.h>
+#include "util/log.h"
+#include "util/locks.h"
+#include "util/net_help.h"
+#include "util/data/msgencode.h"
+#include "util/data/msgreply.h"
+#include "util/data/msgparse.h"
+#include "sldns/sbuffer.h"
+#include "sldns/wire2str.h"
+#include "sldns/str2wire.h"
+#include <sys/time.h>
+
+/** usage information for perf */
+static void usage(char* nm) 
+{
+       printf("usage: %s [options] server\n", nm);
+       printf("server: ip address of server, IP4 or IP6.\n");
+       printf("        If not on port %d add @port.\n", UNBOUND_DNS_PORT);
+       printf("-d sec  duration of test in whole seconds (0: wait for ^C)\n");
+       printf("-a str  query to ask, interpreted as a line from qfile\n");
+       printf("-f fnm  query list to read from file\n");
+       printf("        every line has format: qname qclass qtype [+-]{E}\n");
+       printf("        where + means RD set, E means EDNS enabled\n");
+       printf("-q      quiet mode, print only final qps\n");
+       exit(1);
+}
+
+struct perfinfo;
+struct perfio;
+
+/** Global info for perf */
+struct perfinfo { 
+       /** need to exit */
+       volatile int exit;
+       /** all purpose buffer (for UDP send and receive) */
+       sldns_buffer* buf;
+
+       /** destination */
+       struct sockaddr_storage dest;
+       /** length of dest socket addr */
+       socklen_t destlen;
+
+       /** when did this time slice start */
+       struct timeval since;
+       /** number of queries received in that time */
+       size_t numrecv;
+       /** number of queries sent out in that time */
+       size_t numsent;
+
+       /** duration of test in seconds */
+       int duration;
+       /** quiet mode? */
+       int quiet;
+
+       /** when did the total test start */
+       struct timeval start;
+       /** total number recvd */
+       size_t total_recv;
+       /** total number sent */
+       size_t total_sent;
+       /** numbers by rcode */
+       size_t by_rcode[32];
+       
+       /** number of I/O ports */
+       size_t io_num;
+       /** I/O ports array */
+       struct perfio* io;
+       /** max fd value in io ports */
+       int maxfd;
+       /** readset */
+       fd_set rset;
+
+       /** size of querylist */
+       size_t qlist_size;
+       /** allocated size of qlist array */
+       size_t qlist_capacity;
+       /** list of query packets (data) */
+       uint8_t** qlist_data;
+       /** list of query packets (length of a packet) */
+       size_t* qlist_len;
+       /** index into querylist, for walking the list */
+       size_t qlist_idx;
+};
+
+/** I/O port for perf */
+struct perfio {
+       /** id number */
+       size_t id;
+       /** file descriptor of socket */
+       int fd;
+       /** timeout value */
+       struct timeval timeout;
+       /** ptr back to perfinfo */
+       struct perfinfo* info;
+};
+
+/** number of msec between starting io ports */
+#define START_IO_INTERVAL 10
+/** number of msec timeout on io ports */
+#define IO_TIMEOUT 10
+
+/** signal handler global info */
+static struct perfinfo* sig_info;
+
+/** signal handler for user quit */
+static RETSIGTYPE perf_sigh(int sig)
+{
+       log_assert(sig_info);
+       if(!sig_info->quiet)
+               printf("exit on signal %d\n", sig);
+       sig_info->exit = 1;
+}
+
+/** timeval compare, t1 < t2 */
+static int
+perf_tv_smaller(struct timeval* t1, struct timeval* t2) 
+{
+#ifndef S_SPLINT_S
+       if(t1->tv_sec < t2->tv_sec)
+               return 1;
+       if(t1->tv_sec == t2->tv_sec &&
+               t1->tv_usec < t2->tv_usec)
+               return 1;
+#endif
+       return 0;
+}
+
+/** timeval add, t1 += t2 */
+static void
+perf_tv_add(struct timeval* t1, struct timeval* t2) 
+{
+#ifndef S_SPLINT_S
+       t1->tv_sec += t2->tv_sec;
+       t1->tv_usec += t2->tv_usec;
+       while(t1->tv_usec > 1000000) {
+               t1->tv_usec -= 1000000;
+               t1->tv_sec++;
+       }
+#endif
+}
+
+/** timeval subtract, t1 -= t2 */
+static void
+perf_tv_subtract(struct timeval* t1, struct timeval* t2) 
+{
+#ifndef S_SPLINT_S
+       t1->tv_sec -= t2->tv_sec;
+       if(t1->tv_usec >= t2->tv_usec) {
+               t1->tv_usec -= t2->tv_usec;
+       } else {
+               t1->tv_sec--;
+               t1->tv_usec = 1000000-(t2->tv_usec-t1->tv_usec);
+       }
+#endif
+}
+
+
+/** setup perf test environment */
+static void
+perfsetup(struct perfinfo* info)
+{
+       size_t i;
+       if(gettimeofday(&info->start, NULL) < 0)
+               fatal_exit("gettimeofday: %s", strerror(errno));
+       sig_info = info;
+       if( signal(SIGINT, perf_sigh) == SIG_ERR || 
+#ifdef SIGQUIT
+               signal(SIGQUIT, perf_sigh) == SIG_ERR ||
+#endif
+#ifdef SIGHUP
+               signal(SIGHUP, perf_sigh) == SIG_ERR ||
+#endif
+#ifdef SIGBREAK
+               signal(SIGBREAK, perf_sigh) == SIG_ERR ||
+#endif
+               signal(SIGTERM, perf_sigh) == SIG_ERR)
+               fatal_exit("could not bind to signal");
+       info->io = (struct perfio*)calloc(sizeof(struct perfio), info->io_num);
+       if(!info->io) fatal_exit("out of memory");
+#ifndef S_SPLINT_S
+       FD_ZERO(&info->rset);
+#endif
+       info->since = info->start;
+       for(i=0; i<info->io_num; i++) {
+               info->io[i].id = i;
+               info->io[i].info = info;
+               info->io[i].fd = socket(
+                       addr_is_ip6(&info->dest, info->destlen)?
+                       AF_INET6:AF_INET, SOCK_DGRAM, 0);
+               if(info->io[i].fd == -1) {
+#ifndef USE_WINSOCK
+                       fatal_exit("socket: %s", strerror(errno));
+#else
+                       fatal_exit("socket: %s", 
+                               wsa_strerror(WSAGetLastError()));
+#endif
+               }
+               if(info->io[i].fd > info->maxfd)
+                       info->maxfd = info->io[i].fd;
+#ifndef S_SPLINT_S
+               FD_SET(FD_SET_T info->io[i].fd, &info->rset);
+               info->io[i].timeout.tv_usec = ((START_IO_INTERVAL*i)%1000)
+                                               *1000;
+               info->io[i].timeout.tv_sec = (START_IO_INTERVAL*i)/1000;
+               perf_tv_add(&info->io[i].timeout, &info->since);
+#endif
+       }
+}
+
+/** cleanup perf test environment */
+static void
+perffree(struct perfinfo* info)
+{
+       size_t i;
+       if(!info) return;
+       if(info->io) {
+               for(i=0; i<info->io_num; i++) {
+#ifndef USE_WINSOCK
+                       close(info->io[i].fd);
+#else
+                       closesocket(info->io[i].fd);
+#endif
+               }
+               free(info->io);
+       }
+       for(i=0; i<info->qlist_size; i++)
+               free(info->qlist_data[i]);
+       free(info->qlist_data);
+       free(info->qlist_len);
+}
+
+/** send new query for io */
+static void
+perfsend(struct perfinfo* info, size_t n, struct timeval* now)
+{
+       ssize_t r;
+       r = sendto(info->io[n].fd, (void*)info->qlist_data[info->qlist_idx],
+               info->qlist_len[info->qlist_idx], 0,
+               (struct sockaddr*)&info->dest, info->destlen);
+       /*log_hex("send", info->qlist_data[info->qlist_idx],
+               info->qlist_len[info->qlist_idx]);*/
+       if(r == -1) {
+#ifndef USE_WINSOCK
+               log_err("sendto: %s", strerror(errno));
+#else
+               log_err("sendto: %s", wsa_strerror(WSAGetLastError()));
+#endif
+       } else if(r != (ssize_t)info->qlist_len[info->qlist_idx]) {
+               log_err("partial sendto");
+       }
+       info->qlist_idx = (info->qlist_idx+1) % info->qlist_size;
+       info->numsent++;
+
+       info->io[n].timeout.tv_sec = IO_TIMEOUT/1000;
+       info->io[n].timeout.tv_usec = (IO_TIMEOUT%1000)*1000;
+       perf_tv_add(&info->io[n].timeout, now);
+}
+
+/** got reply for io */
+static void
+perfreply(struct perfinfo* info, size_t n, struct timeval* now)
+{
+       ssize_t r;
+       r = recv(info->io[n].fd, (void*)sldns_buffer_begin(info->buf),
+               sldns_buffer_capacity(info->buf), 0);
+       if(r == -1) {
+#ifndef USE_WINSOCK
+               log_err("recv: %s", strerror(errno));
+#else
+               log_err("recv: %s", wsa_strerror(WSAGetLastError()));
+#endif
+       } else {
+               info->by_rcode[LDNS_RCODE_WIRE(sldns_buffer_begin(
+                       info->buf))]++;
+               info->numrecv++;
+       }
+       /*sldns_buffer_set_limit(info->buf, r);
+       log_buf(0, "reply", info->buf);*/
+       perfsend(info, n, now);
+}
+
+/** got timeout for io */
+static void
+perftimeout(struct perfinfo* info, size_t n, struct timeval* now)
+{
+       /* may not be a dropped packet, this is also used to start
+        * up the sending IOs */
+       perfsend(info, n, now);
+}
+
+/** print nice stats about qps */
+static void
+stat_printout(struct perfinfo* info, struct timeval* now, 
+       struct timeval* elapsed)
+{
+       /* calculate qps */
+       double dt, qps = 0;
+#ifndef S_SPLINT_S
+       dt = (double)(elapsed->tv_sec*1000000 + elapsed->tv_usec) / 1000000;
+#endif
+       if(dt > 0.001)
+               qps = (double)(info->numrecv) / dt;
+       if(!info->quiet)
+               printf("qps: %g\n", qps);
+       /* setup next slice */
+       info->since = *now;
+       info->total_sent += info->numsent;
+       info->total_recv += info->numrecv;
+       info->numrecv = 0;
+       info->numsent = 0;
+}
+
+/** wait for new events for performance test */
+static void
+perfselect(struct perfinfo* info)
+{
+       fd_set rset = info->rset;
+       struct timeval timeout, now;
+       int num;
+       size_t i;
+       if(gettimeofday(&now, NULL) < 0)
+               fatal_exit("gettimeofday: %s", strerror(errno));
+       /* time to exit? */
+       if(info->duration > 0) {
+               timeout = now;
+               perf_tv_subtract(&timeout, &info->start);
+               if((int)timeout.tv_sec >= info->duration) {
+                       info->exit = 1;
+                       return;
+               }
+       }
+       /* time for stats printout? */
+       timeout = now;
+       perf_tv_subtract(&timeout, &info->since);
+       if(timeout.tv_sec > 0) {
+               stat_printout(info, &now, &timeout);
+       }
+       /* see what is closest port to timeout; or if there is a timeout */
+       timeout = info->io[0].timeout;
+       for(i=0; i<info->io_num; i++) {
+               if(perf_tv_smaller(&info->io[i].timeout, &now)) {
+                       perftimeout(info, i, &now);
+                       return;
+               }
+               if(perf_tv_smaller(&info->io[i].timeout, &timeout)) {
+                       timeout = info->io[i].timeout;
+               }
+       }
+       perf_tv_subtract(&timeout, &now);
+       
+       num = select(info->maxfd+1, &rset, NULL, NULL, &timeout);
+       if(num == -1) {
+               if(errno == EAGAIN || errno == EINTR)
+                       return;
+               log_err("select: %s", strerror(errno));
+       }
+
+       /* handle new events */
+       for(i=0; num && i<info->io_num; i++) {
+               if(FD_ISSET(info->io[i].fd, &rset)) {
+                       perfreply(info, i, &now);
+                       num--;
+               }
+       }
+}
+
+/** show end stats */
+static void
+perfendstats(struct perfinfo* info)
+{
+       double dt, qps;
+       struct timeval timeout, now;
+       int i, lost; 
+       if(gettimeofday(&now, NULL) < 0)
+               fatal_exit("gettimeofday: %s", strerror(errno));
+       timeout = now;
+       perf_tv_subtract(&timeout, &info->since);
+       stat_printout(info, &now, &timeout);
+       
+       timeout = now;
+       perf_tv_subtract(&timeout, &info->start);
+       dt = (double)(timeout.tv_sec*1000000 + timeout.tv_usec) / 1000000.0;
+       qps = (double)(info->total_recv) / dt;
+       lost = (int)(info->total_sent - info->total_recv) - (int)info->io_num;
+       if(!info->quiet) {
+               printf("overall time:   %g sec\n", 
+                       (double)timeout.tv_sec + 
+                       (double)timeout.tv_usec/1000000.);
+               if(lost > 0) 
+                       printf("Packets lost:   %d\n", (int)lost);
+       
+               for(i=0; i<(int)(sizeof(info->by_rcode)/sizeof(size_t)); i++)
+               {
+                       if(info->by_rcode[i] > 0) {
+                               char rc[16];
+                               sldns_wire2str_rcode_buf(i, rc, sizeof(rc));
+                               printf("%d(%5s):        %u replies\n",
+                                       i, rc, (unsigned)info->by_rcode[i]);
+                       }
+               }
+       }
+       printf("average qps:    %g\n", qps);
+}
+
+/** perform the performance test */
+static void
+perfmain(struct perfinfo* info)
+{
+       perfsetup(info);
+       while(!info->exit) {
+               perfselect(info);
+       }
+       perfendstats(info);
+       perffree(info);
+}
+
+/** parse a query line to a packet into buffer */
+static int
+qlist_parse_line(sldns_buffer* buf, char* p)
+{
+       char nm[1024], cl[1024], tp[1024], fl[1024];
+       int r; 
+       int rec = 1, edns = 0;
+       struct query_info qinfo;
+       nm[0] = 0; cl[0] = 0; tp[0] = 0; fl[0] = 0;
+       r = sscanf(p, " %1023s %1023s %1023s %1023s", nm, cl, tp, fl);
+       if(r != 3 && r != 4)
+               return 0;
+       /*printf("nm='%s', cl='%s', tp='%s', fl='%s'\n", nm, cl, tp, fl);*/
+       if(strcmp(tp, "IN") == 0 || strcmp(tp, "CH") == 0) {
+               qinfo.qtype = sldns_get_rr_type_by_name(cl);
+               qinfo.qclass = sldns_get_rr_class_by_name(tp);
+       } else {
+               qinfo.qtype = sldns_get_rr_type_by_name(tp);
+               qinfo.qclass = sldns_get_rr_class_by_name(cl);
+       }
+       if(fl[0] == '+') rec = 1;
+       else if(fl[0] == '-') rec = 0;
+       else if(fl[0] == 'E') edns = 1;
+       if((fl[0] == '+' || fl[0] == '-') && fl[1] == 'E')
+               edns = 1;
+       qinfo.qname = sldns_str2wire_dname(nm, &qinfo.qname_len);
+       if(!qinfo.qname)
+               return 0;
+       qinfo.local_alias = NULL;
+       qinfo_query_encode(buf, &qinfo);
+       sldns_buffer_write_u16_at(buf, 0, 0); /* zero ID */
+       if(rec) LDNS_RD_SET(sldns_buffer_begin(buf));
+       if(edns) {
+               struct edns_data ed;
+               memset(&ed, 0, sizeof(ed));
+               ed.edns_present = 1;
+               ed.udp_size = EDNS_ADVERTISED_SIZE;
+               /* Set DO bit in all EDNS datagrams ... */
+               ed.bits = EDNS_DO;
+               attach_edns_record(buf, &ed);
+       }
+       free(qinfo.qname);
+       return 1;
+}
+
+/** grow query list capacity */
+static void
+qlist_grow_capacity(struct perfinfo* info)
+{
+       size_t newcap = (size_t)((info->qlist_capacity==0)?16:
+               info->qlist_capacity*2);
+       uint8_t** d = (uint8_t**)calloc(sizeof(uint8_t*), newcap);
+       size_t* l = (size_t*)calloc(sizeof(size_t), newcap);
+       if(!d || !l) fatal_exit("out of memory");
+       memcpy(d, info->qlist_data, sizeof(uint8_t*)*
+               info->qlist_capacity);
+       memcpy(l, info->qlist_len, sizeof(size_t)*
+               info->qlist_capacity);
+       free(info->qlist_data);
+       free(info->qlist_len);
+       info->qlist_data = d;
+       info->qlist_len = l;
+       info->qlist_capacity = newcap;
+}
+
+/** setup query list in info */
+static void
+qlist_add_line(struct perfinfo* info, char* line, int no)
+{
+       if(!qlist_parse_line(info->buf, line)) {
+               printf("error parsing query %d: %s\n", no, line);
+               exit(1);
+       }
+       sldns_buffer_write_u16_at(info->buf, 0, (uint16_t)info->qlist_size); 
+       if(info->qlist_size + 1 > info->qlist_capacity) {
+               qlist_grow_capacity(info);
+       }
+       info->qlist_len[info->qlist_size] = sldns_buffer_limit(info->buf);
+       info->qlist_data[info->qlist_size] = memdup(
+               sldns_buffer_begin(info->buf), sldns_buffer_limit(info->buf));
+       if(!info->qlist_data[info->qlist_size])
+               fatal_exit("out of memory");
+       info->qlist_size ++;
+}
+
+/** setup query list in info */
+static void
+qlist_read_file(struct perfinfo* info, char* fname)
+{
+       char buf[1024];
+       char *p;
+       FILE* in = fopen(fname, "r");
+       int lineno = 0;
+       if(!in) {
+               perror(fname);
+               exit(1);
+       }
+       while(fgets(buf, (int)sizeof(buf), in)) {
+               lineno++;
+               buf[sizeof(buf)-1] = 0;
+               p = buf;
+               while(*p == ' ' || *p == '\t')
+                       p++;
+               if(p[0] == 0 || p[0] == '\n' || p[0] == ';' || p[0] == '#')
+                       continue;
+               qlist_add_line(info, p, lineno);
+       }
+       printf("Read %s, got %u queries\n", fname, (unsigned)info->qlist_size);
+       fclose(in);
+}
+
+/** getopt global, in case header files fail to declare it. */
+extern int optind;
+/** getopt global, in case header files fail to declare it. */
+extern char* optarg;
+
+/** main program for perf */
+int main(int argc, char* argv[]) 
+{
+       char* nm = argv[0];
+       int c;
+       struct perfinfo info;
+#ifdef USE_WINSOCK
+       int r;
+       WSADATA wsa_data;
+#endif
+
+       /* defaults */
+       memset(&info, 0, sizeof(info));
+       info.io_num = 16;
+
+       log_init(NULL, 0, NULL);
+       log_ident_set("perf");
+       checklock_start();
+#ifdef USE_WINSOCK
+       if((r = WSAStartup(MAKEWORD(2,2), &wsa_data)) != 0)
+               fatal_exit("WSAStartup failed: %s", wsa_strerror(r));
+#endif
+
+       info.buf = sldns_buffer_new(65553);
+       if(!info.buf) fatal_exit("out of memory");
+
+       /* parse the options */
+       while( (c=getopt(argc, argv, "d:ha:f:q")) != -1) {
+               switch(c) {
+               case 'q':
+                       info.quiet = 1;
+                       break;
+               case 'd':
+                       if(atoi(optarg)==0 && strcmp(optarg, "0")!=0) {
+                               printf("-d not a number %s", optarg);
+                               return 1;
+                       }
+                       info.duration = atoi(optarg);
+                       break;
+               case 'a':
+                       qlist_add_line(&info, optarg, 0);
+                       break;
+               case 'f':
+                       qlist_read_file(&info, optarg);
+                       break;
+               case '?':
+               case 'h':
+               default:
+                       usage(nm);
+               }
+       }
+       argc -= optind;
+       argv += optind;
+
+       if(argc != 1) {
+               printf("error: pass server IP address on commandline.\n");
+               usage(nm);
+       }
+       if(!extstrtoaddr(argv[0], &info.dest, &info.destlen)) {
+               printf("Could not parse ip: %s\n", argv[0]);
+               return 1;
+       }
+       if(info.qlist_size == 0) {
+               printf("No queries to make, use -f or -a.\n");
+               return 1;
+       }
+       
+       /* do the performance test */
+       perfmain(&info);
+
+       sldns_buffer_free(info.buf);
+#ifdef USE_WINSOCK
+       WSACleanup();
+#endif
+       checklock_stop();
+       return 0;
+}
diff --git a/usr.sbin/unbound/testcode/petal.c b/usr.sbin/unbound/testcode/petal.c
new file mode 100644 (file)
index 0000000..1c26fa7
--- /dev/null
@@ -0,0 +1,671 @@
+/*
+ * petal.c - https daemon that is small and beautiful.
+ *
+ * Copyright (c) 2010, NLnet Labs. All rights reserved.
+ *
+ * This software is open source.
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 
+ * Neither the name of the NLNET LABS nor the names of its contributors may
+ * be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+ * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * \file
+ *
+ * HTTP1.1/SSL server.
+ */
+
+#include "config.h"
+#ifdef HAVE_GETOPT_H
+#include <getopt.h>
+#endif
+#ifdef HAVE_OPENSSL_SSL_H
+#include <openssl/ssl.h>
+#endif
+#ifdef HAVE_OPENSSL_ERR_H
+#include <openssl/err.h>
+#endif
+#ifdef HAVE_OPENSSL_RAND_H
+#include <openssl/rand.h>
+#endif
+#include <openssl/x509.h>
+#include <openssl/pem.h>
+#include <ctype.h>
+#include <signal.h>
+#if defined(UNBOUND_ALLOC_LITE) || defined(UNBOUND_ALLOC_STATS)
+#ifdef malloc
+#undef malloc
+#endif
+#ifdef free
+#undef free
+#endif
+#endif /* alloc lite or alloc stats */
+
+/** verbosity for this application */
+static int verb = 0;
+
+/** Give petal usage, and exit (1). */
+static void
+usage(void)
+{
+       printf("Usage:  petal [opts]\n");
+       printf("        https daemon serves files from ./'host'/filename\n");
+       printf("        (no hostname: from the 'default' directory)\n");
+       printf("-a addr         bind to this address, 127.0.0.1\n");
+       printf("-p port         port number, default 443\n");
+       printf("-k keyfile      SSL private key file (PEM), petal.key\n");
+       printf("-c certfile     SSL certificate file (PEM), petal.pem\n");
+       printf("-v              more verbose\n");
+       printf("-h              show this usage help\n");
+       printf("Version %s\n", PACKAGE_VERSION);
+       printf("BSD licensed, see LICENSE in source package for details.\n");
+       printf("Report bugs to %s\n", PACKAGE_BUGREPORT);
+       exit(1);
+}
+
+/** fatal exit */
+static void print_exit(const char* str) {printf("error %s\n", str); exit(1);}
+/** print errno */
+static void log_errno(const char* str)
+{printf("error %s: %s\n", str, strerror(errno));}
+
+/** parse a text IP address into a sockaddr */
+static int
+parse_ip_addr(char* str, int port, struct sockaddr_storage* ret, socklen_t* l)
+{
+       socklen_t len = 0;
+       struct sockaddr_storage* addr = NULL;
+       struct sockaddr_in6 a6;
+       struct sockaddr_in a;
+       uint16_t p = (uint16_t)port;
+       int fam = 0;
+       memset(&a6, 0, sizeof(a6));
+       memset(&a, 0, sizeof(a));
+
+       if(inet_pton(AF_INET6, str, &a6.sin6_addr) > 0) {
+               /* it is an IPv6 */
+               fam = AF_INET6;
+               a6.sin6_family = AF_INET6;
+               a6.sin6_port = (in_port_t)htons(p);
+               addr = (struct sockaddr_storage*)&a6;
+               len = (socklen_t)sizeof(struct sockaddr_in6);
+       }
+       if(inet_pton(AF_INET, str, &a.sin_addr) > 0) {
+               /* it is an IPv4 */
+               fam = AF_INET;
+               a.sin_family = AF_INET;
+               a.sin_port = (in_port_t)htons(p);
+               addr = (struct sockaddr_storage*)&a;
+               len = (socklen_t)sizeof(struct sockaddr_in);
+       }
+       if(!len) print_exit("cannot parse addr");
+       *l = len;
+       memmove(ret, addr, len);
+       return fam;
+}
+
+/** close the fd */
+static void
+fd_close(int fd)
+{
+#ifndef USE_WINSOCK
+       close(fd);
+#else
+       closesocket(fd);
+#endif
+}
+
+/** 
+ * Read one line from SSL
+ * zero terminates.
+ * skips "\r\n" (but not copied to buf).
+ * @param ssl: the SSL connection to read from (blocking).
+ * @param buf: buffer to return line in.
+ * @param len: size of the buffer.
+ * @return 0 on error, 1 on success.
+ */
+static int
+read_ssl_line(SSL* ssl, char* buf, size_t len)
+{
+       size_t n = 0;
+       int r;
+       int endnl = 0;
+       while(1) {
+               if(n >= len) {
+                       if(verb) printf("line too long\n");
+                       return 0;
+               }
+               if((r = SSL_read(ssl, buf+n, 1)) <= 0) {
+                       if(SSL_get_error(ssl, r) == SSL_ERROR_ZERO_RETURN) {
+                               /* EOF */
+                               break;
+                       }
+                       if(verb) printf("could not SSL_read\n");
+                       return 0;
+               }
+               if(endnl && buf[n] == '\n') {
+                       break;
+               } else if(endnl) {
+                       /* bad data */
+                       if(verb) printf("error: stray linefeeds\n");
+                       return 0;
+               } else if(buf[n] == '\r') {
+                       /* skip \r, and also \n on the wire */
+                       endnl = 1;
+                       continue;
+               } else if(buf[n] == '\n') {
+                       /* skip the \n, we are done */
+                       break;
+               } else n++;
+       }
+       buf[n] = 0;
+       return 1;
+}
+
+/** process one http header */
+static int
+process_one_header(char* buf, char* file, size_t flen, char* host, size_t hlen,
+       int* vs)
+{
+       if(strncasecmp(buf, "GET ", 4) == 0) {
+               char* e = strstr(buf, " HTTP/1.1");
+               if(!e) e = strstr(buf, " http/1.1");
+               if(!e) {
+                       e = strstr(buf, " HTTP/1.0");
+                       if(!e) e = strstr(buf, " http/1.0");
+                       if(!e) e = strrchr(buf, ' ');
+                       if(!e) e = strrchr(buf, '\t');
+                       if(e) *vs = 10;
+               }
+               if(e) *e = 0;
+               if(strlen(buf) < 4) return 0;
+               (void)strlcpy(file, buf+4, flen);
+       } else if(strncasecmp(buf, "Host: ", 6) == 0) {
+               (void)strlcpy(host, buf+6, hlen);
+       }
+       return 1;
+}
+
+/** read http headers and process them */
+static int
+read_http_headers(SSL* ssl, char* file, size_t flen, char* host, size_t hlen,
+       int* vs)
+{
+       char buf[1024];
+       file[0] = 0;
+       host[0] = 0;
+       while(read_ssl_line(ssl, buf, sizeof(buf))) {
+               if(verb>=2) printf("read: %s\n", buf);
+               if(buf[0] == 0)
+                       return 1;
+               if(!process_one_header(buf, file, flen, host, hlen, vs))
+                       return 0;
+       }
+       return 0;
+}
+
+/** setup SSL context */
+static SSL_CTX*
+setup_ctx(char* key, char* cert)
+{
+       SSL_CTX* ctx = SSL_CTX_new(SSLv23_server_method());
+       if(!ctx) print_exit("out of memory");
+       (void)SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2);
+       (void)SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv3);
+       if(!SSL_CTX_use_certificate_chain_file(ctx, cert))
+               print_exit("cannot read cert");
+       if(!SSL_CTX_use_PrivateKey_file(ctx, key, SSL_FILETYPE_PEM))
+               print_exit("cannot read key");
+       if(!SSL_CTX_check_private_key(ctx))
+               print_exit("private key is not correct");
+#if HAVE_DECL_SSL_CTX_SET_ECDH_AUTO
+       if (!SSL_CTX_set_ecdh_auto(ctx,1))
+               if(verb>=1) printf("failed to set_ecdh_auto, not enabling ECDHE\n");
+#elif defined(USE_ECDSA)
+       if(1) {
+               EC_KEY *ecdh = EC_KEY_new_by_curve_name (NID_X9_62_prime256v1);
+               if (!ecdh) {
+                       if(verb>=1) printf("could not find p256, not enabling ECDHE\n");
+               } else {
+                       if (1 != SSL_CTX_set_tmp_ecdh (ctx, ecdh)) {
+                               if(verb>=1) printf("Error in SSL_CTX_set_tmp_ecdh, not enabling ECDHE\n");
+                       }
+                       EC_KEY_free(ecdh);
+               }
+       }
+#endif
+       if(!SSL_CTX_load_verify_locations(ctx, cert, NULL))
+               print_exit("cannot load cert verify locations");
+       return ctx;
+}
+
+/** setup listening TCP */
+static int
+setup_fd(char* addr, int port)
+{
+       struct sockaddr_storage ad;
+       socklen_t len;
+       int fd;
+       int c = 1;
+       int fam = parse_ip_addr(addr, port, &ad, &len);
+       fd = socket(fam, SOCK_STREAM, 0);
+       if(fd == -1) {
+               log_errno("socket");
+               return -1;
+       }
+       if(setsockopt(fd, SOL_SOCKET, SO_REUSEADDR,
+               (void*)&c, (socklen_t) sizeof(int)) < 0) {
+               log_errno("setsockopt(SOL_SOCKET, SO_REUSEADDR)");
+       }
+       if(bind(fd, (struct sockaddr*)&ad, len) == -1) {
+               log_errno("bind");
+               fd_close(fd);
+               return -1;
+       }
+       if(listen(fd, 5) == -1) {
+               log_errno("listen");
+               fd_close(fd);
+               return -1;
+       }
+       return fd;
+}
+
+/** setup SSL connection to the client */
+static SSL*
+setup_ssl(int s, SSL_CTX* ctx)
+{
+       SSL* ssl = SSL_new(ctx);
+       if(!ssl) return NULL;
+       SSL_set_accept_state(ssl);
+       (void)SSL_set_mode(ssl, SSL_MODE_AUTO_RETRY);
+       if(!SSL_set_fd(ssl, s)) {
+               SSL_free(ssl);
+               return NULL;
+       }
+       return ssl;
+}
+
+/** check a file name for safety */
+static int
+file_name_is_safe(char* s)
+{
+       size_t l = strlen(s);
+       if(s[0] != '/')
+               return 0; /* must start with / */
+       if(strstr(s, "/../"))
+               return 0; /* no updirs in URL */
+       if(l>=3 && s[l-1]=='.' && s[l-2]=='.' && s[l-3]=='/')
+               return 0; /* ends with /.. */
+       return 1;
+}
+
+/** adjust host and filename */
+static void
+adjust_host_file(char* host, char* file)
+{
+       size_t i, len;
+       /* remove a port number if present */
+       if(strrchr(host, ':'))
+               *strrchr(host, ':') = 0;
+       /* lowercase */
+       len = strlen(host);
+       for(i=0; i<len; i++)
+               host[i] = tolower((unsigned char)host[i]);
+       len = strlen(file);
+       for(i=0; i<len; i++)
+               file[i] = tolower((unsigned char)file[i]);
+}
+
+/** check a host name for safety */
+static int
+host_name_is_safe(char* s)
+{
+       if(strchr(s, '/'))
+               return 0;
+       if(strcmp(s, "..") == 0)
+               return 0;
+       if(strcmp(s, ".") == 0)
+               return 0;
+       return 1;
+}
+
+/** provide file in whole transfer */
+static void
+provide_file_10(SSL* ssl, char* fname)
+{
+       char* buf, *at;
+       size_t len, avail, header_reserve=1024;
+       FILE* in = fopen(fname, 
+#ifndef USE_WINSOCK
+               "r"
+#else
+               "rb"
+#endif
+               );
+       size_t r;
+       const char* rcode = "200 OK";
+       if(!in) {
+               char hdr[1024];
+               rcode = "404 File not found";
+               snprintf(hdr, sizeof(hdr), "HTTP/1.1 %s\r\n\r\n", rcode);
+               r = strlen(hdr);
+               if(SSL_write(ssl, hdr, (int)r) <= 0) {
+                       /* write failure */
+               }
+               return;
+       }
+       fseek(in, 0, SEEK_END);
+       len = (size_t)ftell(in);
+       fseek(in, 0, SEEK_SET);
+       /* plus some space for the header */
+       buf = (char*)malloc(len+header_reserve);
+       if(!buf) {
+               fclose(in);
+               return;
+       }
+       avail = len+header_reserve;
+       at = buf;
+       snprintf(at, avail, "HTTP/1.1 %s\r\n", rcode);
+       r = strlen(at);
+       at += r;
+       avail -= r;
+       snprintf(at, avail, "Server: petal/%s\r\n", PACKAGE_VERSION);
+       r = strlen(at);
+       at += r;
+       avail -= r;
+       snprintf(at, avail, "Content-Length: %u\r\n", (unsigned)len);
+       r = strlen(at);
+       at += r;
+       avail -= r;
+       snprintf(at, avail, "\r\n");
+       r = strlen(at);
+       at += r;
+       avail -= r;
+       if(avail < len) { /* robust */
+               free(buf);
+               fclose(in);
+               return;
+       }
+       if(fread(at, 1, len, in) != len) {
+               free(buf);
+               fclose(in);
+               return;
+       }
+       fclose(in);
+       at += len;
+       avail -= len;
+       if(SSL_write(ssl, buf, at-buf) <= 0) {
+               /* write failure */
+       }
+       free(buf);
+}
+
+/** provide file over SSL, chunked encoding */
+static void
+provide_file_chunked(SSL* ssl, char* fname)
+{
+       char buf[16384];
+       char* tmpbuf = NULL;
+       char* at = buf;
+       size_t avail = sizeof(buf);
+       size_t r;
+       FILE* in = fopen(fname, 
+#ifndef USE_WINSOCK
+               "r"
+#else
+               "rb"
+#endif
+               );
+       const char* rcode = "200 OK";
+       if(!in) {
+               rcode = "404 File not found";
+       }
+
+       /* print headers */
+       snprintf(at, avail, "HTTP/1.1 %s\r\n", rcode);
+       r = strlen(at);
+       at += r;
+       avail -= r;
+       snprintf(at, avail, "Server: petal/%s\r\n", PACKAGE_VERSION);
+       r = strlen(at);
+       at += r;
+       avail -= r;
+       snprintf(at, avail, "Transfer-Encoding: chunked\r\n");
+       r = strlen(at);
+       at += r;
+       avail -= r;
+       snprintf(at, avail, "Connection: close\r\n");
+       r = strlen(at);
+       at += r;
+       avail -= r;
+       snprintf(at, avail, "\r\n");
+       r = strlen(at);
+       at += r;
+       avail -= r;
+       if(avail < 16) { /* robust */
+               if(in) fclose(in);
+               return;
+       }
+
+       do {
+               size_t red;
+               free(tmpbuf);
+               tmpbuf = malloc(avail-16);
+               if(!tmpbuf)
+                       break;
+               /* read chunk; space-16 for xxxxCRLF..CRLF0CRLFCRLF (3 spare)*/
+               red = in?fread(tmpbuf, 1, avail-16, in):0;
+               /* prepare chunk */
+               snprintf(at, avail, "%x\r\n", (unsigned)red);
+               r = strlen(at);
+               if(verb >= 3)
+               {printf("chunk len %x\n", (unsigned)red); fflush(stdout);}
+               at += r;
+               avail -= r;
+               if(red != 0) {
+                       if(red > avail) break; /* robust */
+                       memmove(at, tmpbuf, red);
+                       at += red;
+                       avail -= red;
+                       snprintf(at, avail, "\r\n");
+                       r = strlen(at);
+                       at += r;
+                       avail -= r;
+               }
+               if(in && feof(in) && red != 0) {
+                       snprintf(at, avail, "0\r\n");
+                       r = strlen(at);
+                       at += r;
+                       avail -= r;
+               }
+               if(!in || feof(in)) {
+                       snprintf(at, avail, "\r\n");
+                       r = strlen(at);
+                       at += r;
+                       avail -= r;
+               }
+               /* send chunk */
+               if(SSL_write(ssl, buf, at-buf) <= 0) {
+                       /* SSL error */
+                       break;
+               }
+
+               /* setup for next chunk */
+               at = buf;
+               avail = sizeof(buf);
+       } while(in && !feof(in) && !ferror(in));
+
+       free(tmpbuf);
+       if(in) fclose(in);
+}
+
+/** provide service to the ssl descriptor */
+static void
+service_ssl(SSL* ssl, struct sockaddr_storage* from, socklen_t falen)
+{
+       char file[1024];
+       char host[1024];
+       char combined[2048];
+       int vs = 11;
+       if(!read_http_headers(ssl, file, sizeof(file), host, sizeof(host),
+               &vs))
+               return;
+       adjust_host_file(host, file);
+       if(host[0] == 0 || !host_name_is_safe(host))
+               (void)strlcpy(host, "default", sizeof(host));
+       if(!file_name_is_safe(file)) {
+               return;
+       }
+       snprintf(combined, sizeof(combined), "%s%s", host, file);
+       if(verb) {
+               char out[100];
+               void* a = &((struct sockaddr_in*)from)->sin_addr;
+               if(falen != (socklen_t)sizeof(struct sockaddr_in))
+                       a = &((struct sockaddr_in6*)from)->sin6_addr;
+               out[0]=0;
+               (void)inet_ntop((int)((struct sockaddr_in*)from)->sin_family,
+                       a, out, (socklen_t)sizeof(out));
+               printf("%s requests %s\n", out, combined);
+               fflush(stdout);
+       }
+       if(vs == 10)
+               provide_file_10(ssl, combined);
+       else    provide_file_chunked(ssl, combined);
+}
+
+/** provide ssl service */
+static void
+do_service(char* addr, int port, char* key, char* cert)
+{
+       SSL_CTX* sslctx = setup_ctx(key, cert);
+       int fd = setup_fd(addr, port);
+       int go = 1;
+       if(fd == -1) print_exit("could not setup sockets");
+       if(verb) {printf("petal start\n"); fflush(stdout);}
+       while(go) {
+               struct sockaddr_storage from;
+               socklen_t flen = (socklen_t)sizeof(from);
+               int s = accept(fd, (struct sockaddr*)&from, &flen);
+               if(verb) fflush(stdout);
+               if(s != -1) {
+                       SSL* ssl = setup_ssl(s, sslctx);
+                       if(verb) fflush(stdout);
+                       if(ssl) {
+                               service_ssl(ssl, &from, flen);
+                               if(verb) fflush(stdout);
+                               SSL_shutdown(ssl);
+                               SSL_free(ssl);
+                       }
+                       fd_close(s);
+               } else if (verb >=2) log_errno("accept");
+               if(verb) fflush(stdout);
+       }
+       /* if we get a kill signal, the process dies and the OS reaps us */
+       if(verb) printf("petal end\n");
+       fd_close(fd);
+       SSL_CTX_free(sslctx);
+}
+
+/** getopt global, in case header files fail to declare it. */
+extern int optind;
+/** getopt global, in case header files fail to declare it. */
+extern char* optarg;
+
+/** Main routine for petal */
+int main(int argc, char* argv[])
+{
+       int c;
+       int port = 443;
+       char* addr = "127.0.0.1", *key = "petal.key", *cert = "petal.pem";
+#ifdef USE_WINSOCK
+       WSADATA wsa_data;
+       if((c=WSAStartup(MAKEWORD(2,2), &wsa_data)) != 0)
+       {       printf("WSAStartup failed\n"); exit(1); }
+       atexit((void (*)(void))WSACleanup);
+#endif
+
+       /* parse the options */
+       while( (c=getopt(argc, argv, "a:c:k:hp:v")) != -1) {
+               switch(c) {
+               case 'a':
+                       addr = optarg;
+                       break;
+               case 'c':
+                       cert = optarg;
+                       break;
+               case 'k':
+                       key = optarg;
+                       break;
+               case 'p':
+                       port = atoi(optarg);
+                       break;
+               case 'v':
+                       verb++;
+                       break;
+               case '?':
+               case 'h':
+               default:
+                       usage();
+               }
+       }
+       argc -= optind;
+       argv += optind;
+       if(argc != 0)
+               usage();
+
+#ifdef SIGPIPE
+       (void)signal(SIGPIPE, SIG_IGN);
+#endif
+#ifdef HAVE_ERR_LOAD_CRYPTO_STRINGS
+       ERR_load_crypto_strings();
+#endif
+#if OPENSSL_VERSION_NUMBER < 0x10100000 || !defined(HAVE_OPENSSL_INIT_SSL)
+       ERR_load_SSL_strings();
+#endif
+#if OPENSSL_VERSION_NUMBER < 0x10100000 || !defined(HAVE_OPENSSL_INIT_CRYPTO)
+       OpenSSL_add_all_algorithms();
+#else
+       OPENSSL_init_crypto(OPENSSL_INIT_ADD_ALL_CIPHERS
+               | OPENSSL_INIT_ADD_ALL_DIGESTS
+               | OPENSSL_INIT_LOAD_CRYPTO_STRINGS, NULL);
+#endif
+#if OPENSSL_VERSION_NUMBER < 0x10100000 || !defined(HAVE_OPENSSL_INIT_SSL)
+       (void)SSL_library_init();
+#else
+       (void)OPENSSL_init_ssl(OPENSSL_INIT_LOAD_SSL_STRINGS, NULL);
+#endif
+
+       do_service(addr, port, key, cert);
+
+#ifdef HAVE_CRYPTO_CLEANUP_ALL_EX_DATA
+       CRYPTO_cleanup_all_ex_data();
+#endif
+#ifdef HAVE_ERR_FREE_STRINGS
+       ERR_free_strings();
+#endif
+       return 0;
+}
diff --git a/usr.sbin/unbound/testcode/pktview.c b/usr.sbin/unbound/testcode/pktview.c
new file mode 100644 (file)
index 0000000..12e0d8e
--- /dev/null
@@ -0,0 +1,202 @@
+/*
+ * testcode/pktview.c - debug program to disassemble a DNS packet.
+ *
+ * Copyright (c) 2007, NLnet Labs. All rights reserved.
+ *
+ * This software is open source.
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 
+ * Neither the name of the NLNET LABS nor the names of its contributors may
+ * be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+ * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * \file
+ *
+ * This program shows a dns packet wire format.
+ */
+
+#include "config.h"
+#include "util/log.h"
+#include "util/data/dname.h"
+#include "util/data/msgparse.h"
+#include "testcode/unitmain.h"
+#include "testcode/readhex.h"
+#include "sldns/sbuffer.h"
+#include "sldns/parseutil.h"
+
+/** usage information for pktview */
+static void usage(char* argv[])
+{
+       printf("usage: %s\n", argv[0]);
+       printf("present hex packet on stdin.\n");
+       exit(1);
+}
+
+/** read hex input */
+static void read_input(sldns_buffer* pkt, FILE* in)
+{
+       char buf[102400];
+       char* np = buf;
+       while(fgets(np, (int)sizeof(buf) - (np-buf), in)) {
+               if(buf[0] == ';') /* comment */
+                       continue;
+               np = &np[strlen(np)];
+       }
+       hex_to_buf(pkt, buf);
+}
+
+/** analyze domain name in packet, possibly compressed */
+static void analyze_dname(sldns_buffer* pkt)
+{
+       size_t oldpos = sldns_buffer_position(pkt);
+       size_t len;
+       printf("[pos %d] dname: ", (int)oldpos);
+       dname_print(stdout, pkt, sldns_buffer_current(pkt));
+       len = pkt_dname_len(pkt);
+       printf(" len=%d", (int)len);
+       if(sldns_buffer_position(pkt)-oldpos != len)
+               printf(" comprlen=%d\n", 
+                       (int)(sldns_buffer_position(pkt)-oldpos));
+       else    printf("\n");
+}
+
+/** analyze rdata in packet */
+static void analyze_rdata(sldns_buffer*pkt, const sldns_rr_descriptor* desc, 
+       uint16_t rdlen)
+{
+       int rdf = 0;
+       int count = (int)desc->_dname_count;
+       size_t len, oldpos;
+       while(rdlen > 0 && count) {
+               switch(desc->_wireformat[rdf]) {
+               case LDNS_RDF_TYPE_DNAME:
+                       oldpos = sldns_buffer_position(pkt);
+                       analyze_dname(pkt);
+                       rdlen -= sldns_buffer_position(pkt)-oldpos;
+                       count --;
+                       len = 0;
+                       break;
+               case LDNS_RDF_TYPE_STR:
+                       len = sldns_buffer_current(pkt)[0] + 1;
+                       break;
+               default:
+                       len = get_rdf_size(desc->_wireformat[rdf]);
+               }
+               if(len) {
+                       printf(" wf[%d]", (int)len);
+                       sldns_buffer_skip(pkt, (ssize_t)len);
+                       rdlen -= len;
+               }
+               rdf++;
+       }
+       if(rdlen) {
+               size_t i;
+               printf(" remain[%d]\n", (int)rdlen);
+               for(i=0; i<rdlen; i++)
+                       printf(" %2.2X", (unsigned)sldns_buffer_current(pkt)[i]);
+               printf("\n");
+       }
+       else    printf("\n");
+       sldns_buffer_skip(pkt, (ssize_t)rdlen);
+}
+
+/** analyze rr in packet */
+static void analyze_rr(sldns_buffer* pkt, int q)
+{
+       uint16_t type, dclass, len;
+       uint32_t ttl;
+       analyze_dname(pkt);
+       type = sldns_buffer_read_u16(pkt);
+       dclass = sldns_buffer_read_u16(pkt);
+       printf("type %s(%d)", sldns_rr_descript(type)?  
+               sldns_rr_descript(type)->_name: "??" , (int)type);
+       printf(" class %s(%d) ", sldns_lookup_by_id(sldns_rr_classes, 
+               (int)dclass)?sldns_lookup_by_id(sldns_rr_classes, 
+               (int)dclass)->name:"??", (int)dclass);
+       if(q) {
+               printf("\n");
+       } else {
+               ttl = sldns_buffer_read_u32(pkt);
+               printf(" ttl %d (0x%x)", (int)ttl, (unsigned)ttl);
+               len = sldns_buffer_read_u16(pkt);
+               printf(" rdata len %d:\n", (int)len);
+               if(sldns_rr_descript(type))
+                       analyze_rdata(pkt, sldns_rr_descript(type), len);
+               else sldns_buffer_skip(pkt, (ssize_t)len);
+       }
+}
+
+/** analyse pkt */
+static void analyze(sldns_buffer* pkt)
+{
+       uint16_t i, f, qd, an, ns, ar;
+       int rrnum = 0;
+       printf("packet length %d\n", (int)sldns_buffer_limit(pkt));
+       if(sldns_buffer_limit(pkt) < 12) return;
+
+       i = sldns_buffer_read_u16(pkt);
+       printf("id (hostorder): %d (0x%x)\n", (int)i, (unsigned)i);
+       f = sldns_buffer_read_u16(pkt);
+       printf("flags: 0x%x\n", (unsigned)f);
+       qd = sldns_buffer_read_u16(pkt);
+       printf("qdcount: %d\n", (int)qd);
+       an = sldns_buffer_read_u16(pkt);
+       printf("ancount: %d\n", (int)an);
+       ns = sldns_buffer_read_u16(pkt);
+       printf("nscount: %d\n", (int)ns);
+       ar = sldns_buffer_read_u16(pkt);
+       printf("arcount: %d\n", (int)ar);
+       
+       printf(";-- query section\n");
+       while(sldns_buffer_remaining(pkt) > 0) {
+               if(rrnum == (int)qd) 
+                       printf(";-- answer section\n");
+               if(rrnum == (int)qd+(int)an) 
+                       printf(";-- authority section\n");
+               if(rrnum == (int)qd+(int)an+(int)ns) 
+                       printf(";-- additional section\n");
+               printf("rr %d ", rrnum);
+               analyze_rr(pkt, rrnum < (int)qd);
+               rrnum++;
+       }
+}
+
+/** main program for pktview */
+int main(int argc, char* argv[]) 
+{
+       sldns_buffer* pkt = sldns_buffer_new(65553);
+       if(argc != 1) {
+               usage(argv);
+       }
+       if(!pkt) fatal_exit("out of memory");
+
+       read_input(pkt, stdin);
+       analyze(pkt);
+
+       sldns_buffer_free(pkt);
+       return 0;
+}
diff --git a/usr.sbin/unbound/testcode/readhex.c b/usr.sbin/unbound/testcode/readhex.c
new file mode 100644 (file)
index 0000000..e871def
--- /dev/null
@@ -0,0 +1,85 @@
+/*
+ * testcode/readhex.c - read hex data.
+ *
+ * Copyright (c) 2007, NLnet Labs. All rights reserved.
+ *
+ * This software is open source.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * Neither the name of the NLNET LABS nor the names of its contributors may
+ * be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+ * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+/**
+ * \file
+ * Declarations useful for the unit tests.
+ */
+#include "config.h"
+#include <ctype.h>
+#include "testcode/readhex.h"
+#include "util/log.h"
+#include "sldns/sbuffer.h"
+#include "sldns/parseutil.h"
+
+/** skip whitespace */
+static void
+skip_whites(const char** p)
+{
+       while(1) {
+               while(isspace((unsigned char)**p))
+                       (*p)++;
+               if(**p == ';') {
+                       /* comment, skip until newline */
+                       while(**p && **p != '\n')
+                               (*p)++;
+                       if(**p == '\n')
+                               (*p)++;
+               } else return;
+       }
+}
+
+/* takes a hex string and puts into buffer */
+void hex_to_buf(sldns_buffer* pkt, const char* hex)
+{
+       const char* p = hex;
+       int val;
+       sldns_buffer_clear(pkt);
+       while(*p) {
+               skip_whites(&p);
+               if(sldns_buffer_position(pkt) == sldns_buffer_limit(pkt))
+                       fatal_exit("hex_to_buf: buffer too small");
+               if(!isalnum((unsigned char)*p))
+                       break;
+               val = sldns_hexdigit_to_int(*p++) << 4;
+               skip_whites(&p);
+               log_assert(*p && isalnum((unsigned char)*p));
+               val |= sldns_hexdigit_to_int(*p++);
+               sldns_buffer_write_u8(pkt, (uint8_t)val);
+               skip_whites(&p);
+       }
+       sldns_buffer_flip(pkt);
+}
+
diff --git a/usr.sbin/unbound/testcode/readhex.h b/usr.sbin/unbound/testcode/readhex.h
new file mode 100644 (file)
index 0000000..be64245
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+ * testcode/readhex.h - read hex data.
+ *
+ * Copyright (c) 2007, NLnet Labs. All rights reserved.
+ *
+ * This software is open source.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * Neither the name of the NLNET LABS nor the names of its contributors may
+ * be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+ * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+/**
+ * \file
+ * Declarations useful for the unit tests.
+ */
+
+#ifndef TESTCODE_READHEX_H
+#define TESTCODE_READHEX_H
+struct sldns_buffer;
+
+/** 
+ * Helper to convert hex string to packet buffer.
+ * @param pkt: buffer to put result in.
+ * @param hex: string of hex data. Spaces and ';...' comments are skipped.
+ */
+void hex_to_buf(struct sldns_buffer* pkt, const char* hex);
+
+#endif /* TESTCODE_READHEX_H */
diff --git a/usr.sbin/unbound/testcode/replay.c b/usr.sbin/unbound/testcode/replay.c
new file mode 100644 (file)
index 0000000..08d8747
--- /dev/null
@@ -0,0 +1,1044 @@
+/*
+ * testcode/replay.c - store and use a replay of events for the DNS resolver.
+ *
+ * Copyright (c) 2007, NLnet Labs. All rights reserved.
+ * 
+ * This software is open source.
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 
+ * Neither the name of the NLNET LABS nor the names of its contributors may
+ * be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+ * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * \file
+ * Store and use a replay of events for the DNS resolver.
+ * Used to test known scenarios to get known outcomes.
+ */
+
+#include "config.h"
+/* for strtod prototype */
+#include <math.h>
+#include <ctype.h>
+#include <time.h>
+#include "util/log.h"
+#include "util/net_help.h"
+#include "util/config_file.h"
+#include "testcode/replay.h"
+#include "testcode/testpkts.h"
+#include "testcode/fake_event.h"
+#include "sldns/str2wire.h"
+
+/** max length of lines in file */
+#define MAX_LINE_LEN 10240
+
+/**
+ * Expand a macro
+ * @param store: value storage
+ * @param runtime: replay runtime for other stuff.
+ * @param text: the macro text, after the ${, Updated to after the } when 
+ *     done (successfully).
+ * @return expanded text, malloced. NULL on failure.
+ */
+static char* macro_expand(rbtree_type* store, 
+       struct replay_runtime* runtime, char** text);
+
+/** compare of time values */
+static int
+timeval_smaller(const struct timeval* x, const struct timeval* y)
+{
+#ifndef S_SPLINT_S
+       if(x->tv_sec < y->tv_sec)
+               return 1;
+       else if(x->tv_sec == y->tv_sec) {
+               if(x->tv_usec <= y->tv_usec)
+                       return 1;
+               else    return 0;
+       }
+       else    return 0;
+#endif
+}
+
+/** parse keyword in string. 
+ * @param line: if found, the line is advanced to after the keyword.
+ * @param keyword: string.
+ * @return: true if found, false if not. 
+ */
+static int 
+parse_keyword(char** line, const char* keyword)
+{
+       size_t len = (size_t)strlen(keyword);
+       if(strncmp(*line, keyword, len) == 0) {
+               *line += len;
+               return 1;
+       }
+       return 0;
+}
+
+/** delete moment */
+static void
+replay_moment_delete(struct replay_moment* mom)
+{
+       if(!mom)
+               return;
+       if(mom->match) {
+               delete_entry(mom->match);
+       }
+       free(mom->autotrust_id);
+       free(mom->string);
+       free(mom->variable);
+       config_delstrlist(mom->file_content);
+       free(mom);
+}
+
+/** delete range */
+static void
+replay_range_delete(struct replay_range* rng)
+{
+       if(!rng)
+               return;
+       delete_entry(rng->match);
+       free(rng);
+}
+
+/** strip whitespace from end of string */
+static void
+strip_end_white(char* p)
+{
+       size_t i;
+       for(i = strlen(p); i > 0; i--) {
+               if(isspace((unsigned char)p[i-1]))
+                       p[i-1] = 0;
+               else return;
+       }
+}
+
+/** 
+ * Read a range from file. 
+ * @param remain: Rest of line (after RANGE keyword).
+ * @param in: file to read from.
+ * @param name: name to print in errors.
+ * @param pstate: read state structure with
+ *     with lineno : incremented as lines are read.
+ *     ttl, origin, prev for readentry.
+ * @param line: line buffer.
+ * @return: range object to add to list, or NULL on error.
+ */
+static struct replay_range*
+replay_range_read(char* remain, FILE* in, const char* name,
+       struct sldns_file_parse_state* pstate, char* line)
+{
+       struct replay_range* rng = (struct replay_range*)malloc(
+               sizeof(struct replay_range));
+       off_t pos;
+       char *parse;
+       struct entry* entry, *last = NULL;
+       if(!rng)
+               return NULL;
+       memset(rng, 0, sizeof(*rng));
+       /* read time range */
+       if(sscanf(remain, " %d %d", &rng->start_step, &rng->end_step)!=2) {
+               log_err("Could not read time range: %s", line);
+               free(rng);
+               return NULL;
+       }
+       /* read entries */
+       pos = ftello(in);
+       while(fgets(line, MAX_LINE_LEN-1, in)) {
+               pstate->lineno++;
+               parse = line;
+               while(isspace((unsigned char)*parse))
+                       parse++;
+               if(!*parse || *parse == ';') {
+                       pos = ftello(in);
+                       continue;
+               }
+               if(parse_keyword(&parse, "ADDRESS")) {
+                       while(isspace((unsigned char)*parse))
+                               parse++;
+                       strip_end_white(parse);
+                       if(!extstrtoaddr(parse, &rng->addr, &rng->addrlen)) {
+                               log_err("Line %d: could not read ADDRESS: %s", 
+                                       pstate->lineno, parse);
+                               free(rng);
+                               return NULL;
+                       }
+                       pos = ftello(in);
+                       continue;
+               }
+               if(parse_keyword(&parse, "RANGE_END")) {
+                       return rng;
+               }
+               /* set position before line; read entry */
+               pstate->lineno--;
+               fseeko(in, pos, SEEK_SET);
+               entry = read_entry(in, name, pstate, 1);
+               if(!entry)
+                       fatal_exit("%d: bad entry", pstate->lineno);
+               entry->next = NULL;
+               if(last)
+                       last->next = entry;
+               else    rng->match = entry;
+               last = entry;
+
+               pos = ftello(in);
+       }
+       replay_range_delete(rng);
+       return NULL;
+}
+
+/** Read FILE match content */
+static void
+read_file_content(FILE* in, int* lineno, struct replay_moment* mom)
+{
+       char line[MAX_LINE_LEN];
+       char* remain = line;
+       struct config_strlist** last = &mom->file_content;
+       line[MAX_LINE_LEN-1]=0;
+       if(!fgets(line, MAX_LINE_LEN-1, in))
+               fatal_exit("FILE_BEGIN expected at line %d", *lineno);
+       if(!parse_keyword(&remain, "FILE_BEGIN"))
+               fatal_exit("FILE_BEGIN expected at line %d", *lineno);
+       while(fgets(line, MAX_LINE_LEN-1, in)) {
+               (*lineno)++;
+               if(strncmp(line, "FILE_END", 8) == 0) {
+                       return;
+               }
+               if(line[0]) line[strlen(line)-1] = 0; /* remove newline */
+               if(!cfg_strlist_insert(last, strdup(line)))
+                       fatal_exit("malloc failure");
+               last = &( (*last)->next );
+       }
+       fatal_exit("no FILE_END in input file");
+}
+
+/** read assign step info */
+static void
+read_assign_step(char* remain, struct replay_moment* mom)
+{
+       char buf[1024];
+       char eq;
+       int skip;
+       buf[sizeof(buf)-1]=0;
+       if(sscanf(remain, " %1023s %c %n", buf, &eq, &skip) != 2)
+               fatal_exit("cannot parse assign: %s", remain);
+       mom->variable = strdup(buf);
+       if(eq != '=')
+               fatal_exit("no '=' in assign: %s", remain);
+       remain += skip;
+       if(remain[0]) remain[strlen(remain)-1]=0; /* remove newline */
+       mom->string = strdup(remain);
+       if(!mom->variable || !mom->string)
+               fatal_exit("out of memory");
+}
+
+/** 
+ * Read a replay moment 'STEP' from file. 
+ * @param remain: Rest of line (after STEP keyword).
+ * @param in: file to read from.
+ * @param name: name to print in errors.
+ * @param pstate: with lineno, ttl, origin, prev for parse state.
+ *     lineno is incremented.
+ * @return: range object to add to list, or NULL on error.
+ */
+static struct replay_moment*
+replay_moment_read(char* remain, FILE* in, const char* name,
+       struct sldns_file_parse_state* pstate)
+{
+       struct replay_moment* mom = (struct replay_moment*)malloc(
+               sizeof(struct replay_moment));
+       int skip = 0;
+       int readentry = 0;
+       if(!mom)
+               return NULL;
+       memset(mom, 0, sizeof(*mom));
+       if(sscanf(remain, " %d%n", &mom->time_step, &skip) != 1) {
+               log_err("%d: cannot read number: %s", pstate->lineno, remain);
+               free(mom);
+               return NULL;
+       }
+       remain += skip;
+       while(isspace((unsigned char)*remain))
+               remain++;
+       if(parse_keyword(&remain, "NOTHING")) {
+               mom->evt_type = repevt_nothing;
+       } else if(parse_keyword(&remain, "QUERY")) {
+               mom->evt_type = repevt_front_query;
+               readentry = 1;
+               if(!extstrtoaddr("127.0.0.1", &mom->addr, &mom->addrlen))
+                       fatal_exit("internal error");
+       } else if(parse_keyword(&remain, "CHECK_ANSWER")) {
+               mom->evt_type = repevt_front_reply;
+               readentry = 1;
+       } else if(parse_keyword(&remain, "CHECK_OUT_QUERY")) {
+               mom->evt_type = repevt_back_query;
+               readentry = 1;
+       } else if(parse_keyword(&remain, "REPLY")) {
+               mom->evt_type = repevt_back_reply;
+               readentry = 1;
+       } else if(parse_keyword(&remain, "TIMEOUT")) {
+               mom->evt_type = repevt_timeout;
+       } else if(parse_keyword(&remain, "TIME_PASSES")) {
+               mom->evt_type = repevt_time_passes;
+               while(isspace((unsigned char)*remain))
+                       remain++;
+               if(parse_keyword(&remain, "EVAL")) {
+                       while(isspace((unsigned char)*remain))
+                               remain++;
+                       mom->string = strdup(remain);
+                       if(!mom->string) fatal_exit("out of memory");
+                       if(strlen(mom->string)>0)
+                               mom->string[strlen(mom->string)-1]=0;
+                       remain += strlen(mom->string);
+               }
+       } else if(parse_keyword(&remain, "CHECK_AUTOTRUST")) {
+               mom->evt_type = repevt_autotrust_check;
+               while(isspace((unsigned char)*remain))
+                       remain++;
+               if(strlen(remain)>0 && remain[strlen(remain)-1]=='\n')
+                       remain[strlen(remain)-1] = 0;
+               mom->autotrust_id = strdup(remain);
+               if(!mom->autotrust_id) fatal_exit("out of memory");
+               read_file_content(in, &pstate->lineno, mom);
+       } else if(parse_keyword(&remain, "CHECK_TEMPFILE")) {
+               mom->evt_type = repevt_tempfile_check;
+               while(isspace((unsigned char)*remain))
+                       remain++;
+               if(strlen(remain)>0 && remain[strlen(remain)-1]=='\n')
+                       remain[strlen(remain)-1] = 0;
+               mom->autotrust_id = strdup(remain);
+               if(!mom->autotrust_id) fatal_exit("out of memory");
+               read_file_content(in, &pstate->lineno, mom);
+       } else if(parse_keyword(&remain, "ERROR")) {
+               mom->evt_type = repevt_error;
+       } else if(parse_keyword(&remain, "TRAFFIC")) {
+               mom->evt_type = repevt_traffic;
+       } else if(parse_keyword(&remain, "ASSIGN")) {
+               mom->evt_type = repevt_assign;
+               read_assign_step(remain, mom);
+       } else if(parse_keyword(&remain, "INFRA_RTT")) {
+               char *s, *m;
+               mom->evt_type = repevt_infra_rtt;
+               while(isspace((unsigned char)*remain))
+                       remain++;
+               s = remain;
+               remain = strchr(s, ' ');
+               if(!remain) fatal_exit("expected three args for INFRA_RTT");
+               remain[0] = 0;
+               remain++;
+               while(isspace((unsigned char)*remain))
+                       remain++;
+               m = strchr(remain, ' ');
+               if(!m) fatal_exit("expected three args for INFRA_RTT");
+               m[0] = 0;
+               m++;
+               while(isspace((unsigned char)*m))
+                       m++;
+               if(!extstrtoaddr(s, &mom->addr, &mom->addrlen))
+                       fatal_exit("bad infra_rtt address %s", s);
+               if(strlen(m)>0 && m[strlen(m)-1]=='\n')
+                       m[strlen(m)-1] = 0;
+               mom->variable = strdup(remain);
+               mom->string = strdup(m);
+               if(!mom->string) fatal_exit("out of memory");
+               if(!mom->variable) fatal_exit("out of memory");
+       } else {
+               log_err("%d: unknown event type %s", pstate->lineno, remain);
+               free(mom);
+               return NULL;
+       }
+       while(isspace((unsigned char)*remain))
+               remain++;
+       if(parse_keyword(&remain, "ADDRESS")) {
+               while(isspace((unsigned char)*remain))
+                       remain++;
+               if(strlen(remain) > 0) /* remove \n */
+                       remain[strlen(remain)-1] = 0;
+               if(!extstrtoaddr(remain, &mom->addr, &mom->addrlen)) {
+                       log_err("line %d: could not parse ADDRESS: %s", 
+                               pstate->lineno, remain);
+                       free(mom);
+                       return NULL;
+               }
+       } 
+       if(parse_keyword(&remain, "ELAPSE")) {
+               double sec;
+               errno = 0;
+               sec = strtod(remain, &remain);
+               if(sec == 0. && errno != 0) {
+                       log_err("line %d: could not parse ELAPSE: %s (%s)", 
+                               pstate->lineno, remain, strerror(errno));
+                       free(mom);
+                       return NULL;
+               }
+#ifndef S_SPLINT_S
+               mom->elapse.tv_sec = (int)sec;
+               mom->elapse.tv_usec = (int)((sec - (double)mom->elapse.tv_sec)
+                       *1000000. + 0.5);
+#endif
+       } 
+
+       if(readentry) {
+               mom->match = read_entry(in, name, pstate, 1);
+               if(!mom->match) {
+                       free(mom);
+                       return NULL;
+               }
+       }
+
+       return mom;
+}
+
+/** makes scenario with title on rest of line */
+static struct replay_scenario*
+make_scenario(char* line)
+{
+       struct replay_scenario* scen;
+       while(isspace((unsigned char)*line))
+               line++;
+       if(!*line) {
+               log_err("scenario: no title given");
+               return NULL;
+       }
+       scen = (struct replay_scenario*)malloc(sizeof(struct replay_scenario));
+       if(!scen)
+               return NULL;
+       memset(scen, 0, sizeof(*scen));
+       scen->title = strdup(line);
+       if(!scen->title) {
+               free(scen);
+               return NULL;
+       }
+       return scen;
+}
+
+struct replay_scenario* 
+replay_scenario_read(FILE* in, const char* name, int* lineno)
+{
+       char line[MAX_LINE_LEN];
+       char *parse;
+       struct replay_scenario* scen = NULL;
+       struct sldns_file_parse_state pstate;
+       line[MAX_LINE_LEN-1]=0;
+       memset(&pstate, 0, sizeof(pstate));
+       pstate.default_ttl = 3600;
+       pstate.lineno = *lineno;
+
+       while(fgets(line, MAX_LINE_LEN-1, in)) {
+               parse=line;
+               pstate.lineno++;
+               (*lineno)++;
+               while(isspace((unsigned char)*parse))
+                       parse++;
+               if(!*parse) 
+                       continue; /* empty line */
+               if(parse_keyword(&parse, ";"))
+                       continue; /* comment */
+               if(parse_keyword(&parse, "SCENARIO_BEGIN")) {
+                       scen = make_scenario(parse);
+                       if(!scen)
+                               fatal_exit("%d: could not make scen", *lineno);
+                       continue;
+               } 
+               if(!scen)
+                       fatal_exit("%d: expected SCENARIO", *lineno);
+               if(parse_keyword(&parse, "RANGE_BEGIN")) {
+                       struct replay_range* newr = replay_range_read(parse, 
+                               in, name, &pstate, line);
+                       if(!newr)
+                               fatal_exit("%d: bad range", pstate.lineno);
+                       *lineno = pstate.lineno;
+                       newr->next_range = scen->range_list;
+                       scen->range_list = newr;
+               } else if(parse_keyword(&parse, "STEP")) {
+                       struct replay_moment* mom = replay_moment_read(parse, 
+                               in, name, &pstate);
+                       if(!mom)
+                               fatal_exit("%d: bad moment", pstate.lineno);
+                       *lineno = pstate.lineno;
+                       if(scen->mom_last && 
+                               scen->mom_last->time_step >= mom->time_step)
+                               fatal_exit("%d: time goes backwards", *lineno);
+                       if(scen->mom_last)
+                               scen->mom_last->mom_next = mom;
+                       else    scen->mom_first = mom;
+                       scen->mom_last = mom;
+               } else if(parse_keyword(&parse, "SCENARIO_END")) {
+                       struct replay_moment *p = scen->mom_first;
+                       int num = 0;
+                       while(p) {
+                               num++;
+                               p = p->mom_next;
+                       }
+                       log_info("Scenario has %d steps", num);
+                       return scen;
+               }
+       }
+       log_err("scenario read failed at line %d (no SCENARIO_END?)", *lineno);
+       replay_scenario_delete(scen);
+       return NULL;
+}
+
+void 
+replay_scenario_delete(struct replay_scenario* scen)
+{
+       struct replay_moment* mom, *momn;
+       struct replay_range* rng, *rngn;
+       if(!scen)
+               return;
+       free(scen->title);
+       mom = scen->mom_first;
+       while(mom) {
+               momn = mom->mom_next;
+               replay_moment_delete(mom);
+               mom = momn;
+       }
+       rng = scen->range_list;
+       while(rng) {
+               rngn = rng->next_range;
+               replay_range_delete(rng);
+               rng = rngn;
+       }
+       free(scen);
+}
+
+/** fetch oldest timer in list that is enabled */
+static struct fake_timer*
+first_timer(struct replay_runtime* runtime)
+{
+       struct fake_timer* p, *res = NULL;
+       for(p=runtime->timer_list; p; p=p->next) {
+               if(!p->enabled)
+                       continue;
+               if(!res)
+                       res = p;
+               else if(timeval_smaller(&p->tv, &res->tv))
+                       res = p;
+       }
+       return res;
+}
+
+struct fake_timer*
+replay_get_oldest_timer(struct replay_runtime* runtime)
+{
+       struct fake_timer* t = first_timer(runtime);
+       if(t && timeval_smaller(&t->tv, &runtime->now_tv))
+               return t;
+       return NULL;
+}
+
+int
+replay_var_compare(const void* a, const void* b)
+{
+       struct replay_var* x = (struct replay_var*)a;
+       struct replay_var* y = (struct replay_var*)b;
+       return strcmp(x->name, y->name);
+}
+
+rbtree_type*
+macro_store_create(void)
+{
+       return rbtree_create(&replay_var_compare);
+}
+
+/** helper function to delete macro values */
+static void
+del_macro(rbnode_type* x, void* ATTR_UNUSED(arg))
+{
+       struct replay_var* v = (struct replay_var*)x;
+       free(v->name);
+       free(v->value);
+       free(v);
+}
+
+void
+macro_store_delete(rbtree_type* store)
+{
+       if(!store)
+               return;
+       traverse_postorder(store, del_macro, NULL);
+       free(store);
+}
+
+/** return length of macro */
+static size_t
+macro_length(char* text)
+{
+       /* we are after ${, looking for } */
+       int depth = 0;
+       size_t len = 0;
+       while(*text) {
+               len++;
+               if(*text == '}') {
+                       if(depth == 0)
+                               break;
+                       depth--;
+               } else if(text[0] == '$' && text[1] == '{') {
+                       depth++;
+               }
+               text++;
+       }
+       return len;
+}
+
+/** insert new stuff at start of buffer */
+static int
+do_buf_insert(char* buf, size_t remain, char* after, char* inserted)
+{
+       char* save = strdup(after);
+       size_t len;
+       if(!save) return 0;
+       if(strlen(inserted) > remain) {
+               free(save);
+               return 0;
+       }
+       len = strlcpy(buf, inserted, remain);
+       buf += len;
+       remain -= len;
+       (void)strlcpy(buf, save, remain);
+       free(save);
+       return 1;
+}
+
+/** do macro recursion */
+static char*
+do_macro_recursion(rbtree_type* store, struct replay_runtime* runtime,
+       char* at, size_t remain)
+{
+       char* after = at+2;
+       char* expand = macro_expand(store, runtime, &after);
+       if(!expand) 
+               return NULL; /* expansion failed */
+       if(!do_buf_insert(at, remain, after, expand)) {
+               free(expand);
+               return NULL;
+       }
+       free(expand);
+       return at; /* and parse over the expanded text to see if again */
+}
+
+/** get var from store */
+static struct replay_var*
+macro_getvar(rbtree_type* store, char* name)
+{
+       struct replay_var k;
+       k.node.key = &k;
+       k.name = name;
+       return (struct replay_var*)rbtree_search(store, &k);
+}
+
+/** do macro variable */
+static char*
+do_macro_variable(rbtree_type* store, char* buf, size_t remain)
+{
+       struct replay_var* v;
+       char* at = buf+1;
+       char* name = at;
+       char sv;
+       if(at[0]==0)
+               return NULL; /* no variable name after $ */
+       while(*at && (isalnum((unsigned char)*at) || *at=='_')) {
+               at++;
+       }
+       /* terminator, we are working in macro_expand() buffer */
+       sv = *at;
+       *at = 0; 
+       v = macro_getvar(store, name);
+       *at = sv;
+
+       if(!v) {
+               log_err("variable is not defined: $%s", name);
+               return NULL; /* variable undefined is error for now */
+       }
+
+       /* insert the variable contents */
+       if(!do_buf_insert(buf, remain, at, v->value))
+               return NULL;
+       return buf; /* and expand the variable contents */
+}
+
+/** do ctime macro on argument */
+static char*
+do_macro_ctime(char* arg)
+{
+       char buf[32];
+       time_t tt = (time_t)atoi(arg);
+       if(tt == 0 && strcmp(arg, "0") != 0) {
+               log_err("macro ctime: expected number, not: %s", arg);
+               return NULL;
+       }
+       ctime_r(&tt, buf);
+       if(buf[0]) buf[strlen(buf)-1]=0; /* remove trailing newline */
+       return strdup(buf);
+}
+
+/** perform arithmetic operator */
+static double
+perform_arith(double x, char op, double y, double* res)
+{
+       switch(op) {
+       case '+':
+               *res = x+y;
+               break;
+       case '-':
+               *res = x-y;
+               break;
+       case '/':
+               *res = x/y;
+               break;
+       case '*':
+               *res = x*y;
+               break;
+       default:
+               return 0;
+       }
+
+       return 1;
+}
+
+/** do macro arithmetic on two numbers and operand */
+static char*
+do_macro_arith(char* orig, size_t remain, char** arithstart)
+{
+       double x, y, result;
+       char operator;
+       int skip;
+       char buf[32];
+       char* at;
+       /* not yet done? we want number operand number expanded first. */
+       if(!*arithstart) {
+               /* remember start pos of expr, skip the first number */
+               at = orig;
+               *arithstart = at;
+               while(*at && (isdigit((unsigned char)*at) || *at == '.'))
+                       at++;
+               return at;
+       }
+       /* move back to start */
+       remain += (size_t)(orig - *arithstart);
+       at = *arithstart;
+
+       /* parse operands */
+       if(sscanf(at, " %lf %c %lf%n", &x, &operator, &y, &skip) != 3) {
+               *arithstart = NULL;
+               return do_macro_arith(orig, remain, arithstart);
+       }
+       if(isdigit((unsigned char)operator)) {
+               *arithstart = orig;
+               return at+skip; /* do nothing, but setup for later number */
+       }
+
+       /* calculate result */
+       if(!perform_arith(x, operator, y, &result)) {
+               log_err("unknown operator: %s", at);
+               return NULL;
+       }
+
+       /* put result back in buffer */
+       snprintf(buf, sizeof(buf), "%.12g", result);
+       if(!do_buf_insert(at, remain, at+skip, buf))
+               return NULL;
+
+       /* the result can be part of another expression, restart that */
+       *arithstart = NULL;
+       return at;
+}
+
+/** Do range macro on expanded buffer */
+static char*
+do_macro_range(char* buf)
+{
+       double x, y, z;
+       if(sscanf(buf, " %lf %lf %lf", &x, &y, &z) != 3) {
+               log_err("range func requires 3 args: %s", buf);
+               return NULL;
+       }
+       if(x <= y && y <= z) {
+               char res[1024];
+               snprintf(res, sizeof(res), "%.24g", y);
+               return strdup(res);
+       }
+       fatal_exit("value %.24g not in range [%.24g, %.24g]", y, x, z);
+       return NULL;
+}
+
+static char*
+macro_expand(rbtree_type* store, struct replay_runtime* runtime, char** text)
+{
+       char buf[10240];
+       char* at = *text;
+       size_t len = macro_length(at);
+       int dofunc = 0;
+       char* arithstart = NULL;
+       if(len >= sizeof(buf))
+               return NULL; /* too long */
+       buf[0] = 0;
+       (void)strlcpy(buf, at, len+1-1); /* do not copy last '}' character */
+       at = buf;
+
+       /* check for functions */
+       if(strcmp(buf, "time") == 0) {
+               snprintf(buf, sizeof(buf), ARG_LL "d", (long long)runtime->now_secs);
+               *text += len;
+               return strdup(buf);
+       } else if(strcmp(buf, "timeout") == 0) {
+               time_t res = 0;
+               struct fake_timer* t = first_timer(runtime);
+               if(t && (time_t)t->tv.tv_sec >= runtime->now_secs) 
+                       res = (time_t)t->tv.tv_sec - runtime->now_secs;
+               snprintf(buf, sizeof(buf), ARG_LL "d", (long long)res);
+               *text += len;
+               return strdup(buf);
+       } else if(strncmp(buf, "ctime ", 6) == 0 ||
+               strncmp(buf, "ctime\t", 6) == 0) {
+               at += 6;
+               dofunc = 1;
+       } else if(strncmp(buf, "range ", 6) == 0 ||
+               strncmp(buf, "range\t", 6) == 0) {
+               at += 6;
+               dofunc = 1;
+       }
+
+       /* actual macro text expansion */
+       while(*at) {
+               size_t remain = sizeof(buf)-strlen(buf);
+               if(strncmp(at, "${", 2) == 0) {
+                       at = do_macro_recursion(store, runtime, at, remain);
+               } else if(*at == '$') {
+                       at = do_macro_variable(store, at, remain);
+               } else if(isdigit((unsigned char)*at)) {
+                       at = do_macro_arith(at, remain, &arithstart);
+               } else {
+                       /* copy until whitespace or operator */
+                       if(*at && (isalnum((unsigned char)*at) || *at=='_')) {
+                               at++;
+                               while(*at && (isalnum((unsigned char)*at) || *at=='_'))
+                                       at++;
+                       } else at++;
+               }
+               if(!at) return NULL; /* failure */
+       }
+       *text += len;
+       if(dofunc) {
+               /* post process functions, buf has the argument(s) */
+               if(strncmp(buf, "ctime", 5) == 0) {
+                       return do_macro_ctime(buf+6);   
+               } else if(strncmp(buf, "range", 5) == 0) {
+                       return do_macro_range(buf+6);   
+               }
+       }
+       return strdup(buf);
+}
+
+char*
+macro_process(rbtree_type* store, struct replay_runtime* runtime, char* text)
+{
+       char buf[10240];
+       char* next, *expand;
+       char* at = text;
+       if(!strstr(text, "${"))
+               return strdup(text); /* no macros */
+       buf[0] = 0;
+       buf[sizeof(buf)-1]=0;
+       while( (next=strstr(at, "${")) ) {
+               /* copy text before next macro */
+               if((size_t)(next-at) >= sizeof(buf)-strlen(buf))
+                       return NULL; /* string too long */
+               (void)strlcpy(buf+strlen(buf), at, (size_t)(next-at+1));
+               /* process the macro itself */
+               next += 2;
+               expand = macro_expand(store, runtime, &next);
+               if(!expand) return NULL; /* expansion failed */
+               (void)strlcpy(buf+strlen(buf), expand, sizeof(buf)-strlen(buf));
+               free(expand);
+               at = next;
+       }
+       /* copy remainder fixed text */
+       (void)strlcpy(buf+strlen(buf), at, sizeof(buf)-strlen(buf));
+       return strdup(buf);
+}
+
+char* 
+macro_lookup(rbtree_type* store, char* name)
+{
+       struct replay_var* x = macro_getvar(store, name);
+       if(!x) return strdup("");
+       return strdup(x->value);
+}
+
+void macro_print_debug(rbtree_type* store)
+{
+       struct replay_var* x;
+       RBTREE_FOR(x, struct replay_var*, store) {
+               log_info("%s = %s", x->name, x->value);
+       }
+}
+
+int 
+macro_assign(rbtree_type* store, char* name, char* value)
+{
+       struct replay_var* x = macro_getvar(store, name);
+       if(x) {
+               free(x->value);
+       } else {
+               x = (struct replay_var*)malloc(sizeof(*x));
+               if(!x) return 0;
+               x->node.key = x;
+               x->name = strdup(name);
+               if(!x->name) {
+                       free(x);
+                       return 0;
+               }
+               (void)rbtree_insert(store, &x->node);
+       }
+       x->value = strdup(value);
+       return x->value != NULL;
+}
+
+/* testbound assert function for selftest.  counts the number of tests */
+#define tb_assert(x) \
+       do { if(!(x)) fatal_exit("%s:%d: %s: assertion %s failed", \
+               __FILE__, __LINE__, __func__, #x); \
+               num_asserts++; \
+       } while(0);
+
+void testbound_selftest(void)
+{
+       /* test the macro store */
+       rbtree_type* store = macro_store_create();
+       char* v;
+       int r;
+       int num_asserts = 0;
+       tb_assert(store);
+
+       v = macro_lookup(store, "bla");
+       tb_assert(strcmp(v, "") == 0);
+       free(v);
+
+       v = macro_lookup(store, "vlerk");
+       tb_assert(strcmp(v, "") == 0);
+       free(v);
+
+       r = macro_assign(store, "bla", "waarde1");
+       tb_assert(r);
+
+       v = macro_lookup(store, "vlerk");
+       tb_assert(strcmp(v, "") == 0);
+       free(v);
+
+       v = macro_lookup(store, "bla");
+       tb_assert(strcmp(v, "waarde1") == 0);
+       free(v);
+
+       r = macro_assign(store, "vlerk", "kanteel");
+       tb_assert(r);
+
+       v = macro_lookup(store, "bla");
+       tb_assert(strcmp(v, "waarde1") == 0);
+       free(v);
+
+       v = macro_lookup(store, "vlerk");
+       tb_assert(strcmp(v, "kanteel") == 0);
+       free(v);
+
+       r = macro_assign(store, "bla", "ww");
+       tb_assert(r);
+
+       v = macro_lookup(store, "bla");
+       tb_assert(strcmp(v, "ww") == 0);
+       free(v);
+
+       tb_assert( macro_length("}") == 1);
+       tb_assert( macro_length("blabla}") == 7);
+       tb_assert( macro_length("bla${zoink}bla}") == 7+8);
+       tb_assert( macro_length("bla${zoink}${bla}bla}") == 7+8+6);
+
+       v = macro_process(store, NULL, "");
+       tb_assert( v && strcmp(v, "") == 0);
+       free(v);
+
+       v = macro_process(store, NULL, "${}");
+       tb_assert( v && strcmp(v, "") == 0);
+       free(v);
+
+       v = macro_process(store, NULL, "blabla ${} dinges");
+       tb_assert( v && strcmp(v, "blabla  dinges") == 0);
+       free(v);
+
+       v = macro_process(store, NULL, "1${$bla}2${$bla}3");
+       tb_assert( v && strcmp(v, "1ww2ww3") == 0);
+       free(v);
+
+       v = macro_process(store, NULL, "it is ${ctime 123456}");
+       tb_assert( v && strcmp(v, "it is Fri Jan  2 10:17:36 1970") == 0);
+       free(v);
+
+       r = macro_assign(store, "t1", "123456");
+       tb_assert(r);
+       v = macro_process(store, NULL, "it is ${ctime ${$t1}}");
+       tb_assert( v && strcmp(v, "it is Fri Jan  2 10:17:36 1970") == 0);
+       free(v);
+
+       v = macro_process(store, NULL, "it is ${ctime $t1}");
+       tb_assert( v && strcmp(v, "it is Fri Jan  2 10:17:36 1970") == 0);
+       free(v);
+
+       r = macro_assign(store, "x", "1");
+       tb_assert(r);
+       r = macro_assign(store, "y", "2");
+       tb_assert(r);
+       v = macro_process(store, NULL, "${$x + $x}");
+       tb_assert( v && strcmp(v, "2") == 0);
+       free(v);
+       v = macro_process(store, NULL, "${$x - $x}");
+       tb_assert( v && strcmp(v, "0") == 0);
+       free(v);
+       v = macro_process(store, NULL, "${$y * $y}");
+       tb_assert( v && strcmp(v, "4") == 0);
+       free(v);
+       v = macro_process(store, NULL, "${32 / $y + $x + $y}");
+       tb_assert( v && strcmp(v, "19") == 0);
+       free(v);
+
+       v = macro_process(store, NULL, "${32 / ${$y+$y} + ${${100*3}/3}}");
+       tb_assert( v && strcmp(v, "108") == 0);
+       free(v);
+
+       v = macro_process(store, NULL, "${1 2 33 2 1}");
+       tb_assert( v && strcmp(v, "1 2 33 2 1") == 0);
+       free(v);
+
+       v = macro_process(store, NULL, "${123 3 + 5}");
+       tb_assert( v && strcmp(v, "123 8") == 0);
+       free(v);
+
+       v = macro_process(store, NULL, "${123 glug 3 + 5}");
+       tb_assert( v && strcmp(v, "123 glug 8") == 0);
+       free(v);
+
+       macro_store_delete(store);
+       printf("selftest successful (%d checks).\n", num_asserts);
+}
diff --git a/usr.sbin/unbound/testcode/replay.h b/usr.sbin/unbound/testcode/replay.h
new file mode 100644 (file)
index 0000000..81f0a2c
--- /dev/null
@@ -0,0 +1,471 @@
+/*
+ * testcode/replay.h - store and use a replay of events for the DNS resolver.
+ *
+ * Copyright (c) 2007, NLnet Labs. All rights reserved.
+ * 
+ * This software is open source.
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 
+ * Neither the name of the NLNET LABS nor the names of its contributors may
+ * be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+ * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * \file
+ * Store and use a replay of events for the DNS resolver.
+ * Used to test known scenarios to get known outcomes.
+ *
+ * <pre>
+ * File format for replay files.
+ *
+ * ; unbound.conf options.
+ * ; ...
+ * ; additional commandline options to pass to unbound
+ * COMMANDLINE cmdline_option
+ * ; autotrust key file contents, also adds auto-trust-anchor-file: "x" to cfg
+ * AUTOTRUST_FILE id
+ * ; contents of that file
+ * AUTOTRUST_END
+ * ; temp file names are echoed as "tmp/xxx.fname"
+ * TEMPFILE_NAME fname
+ * ; temp file contents, inline, deleted at end of run
+ * TEMPFILE_CONTENTS fname
+ * ; contents of that file
+ * ; this creates $INCLUDE /tmp/xxx.fname
+ * $INCLUDE_TEMPFILE fname
+ * TEMPFILE_END
+ * CONFIG_END
+ * ; comment line.
+ * SCENARIO_BEGIN name_of_scenario
+ * RANGE_BEGIN start_time end_time
+ *    ; give ip of the virtual server, it matches any ip if not present.
+ *    ADDRESS ip_address 
+ *    match_entries
+ * RANGE_END
+ * ; more RANGE items.
+ * ; go to the next moment
+ * STEP time_step event_type [ADDRESS ip_address]
+ * ; event_type can be:
+ *     o NOTHING - nothing
+ *     o QUERY - followed by entry
+ *     o CHECK_ANSWER - followed by entry
+ *     o CHECK_OUT_QUERY - followed by entry (if copy-id it is also reply).
+ *     o REPLY - followed by entry
+ *      o TIMEOUT
+ *      o TIME_PASSES ELAPSE [seconds] - increase 'now' time counter, can be 
+ *                             a floating point number.
+ *        TIME_PASSES EVAL [macro] - expanded for seconds to move time.
+ *      o TRAFFIC - like CHECK_ANSWER, causes traffic to flow.
+ *             actually the traffic flows before this step is taken.
+ *             the step waits for traffic to stop.
+ *      o CHECK_AUTOTRUST [id] - followed by FILE_BEGIN [to match] FILE_END.
+ *             The file contents is macro expanded before match.
+ *      o CHECK_TEMPFILE [fname] - followed by FILE_BEGIN [to match] FILE_END
+ *      o INFRA_RTT [ip] [dp] [rtt] - update infra cache entry with rtt.
+ *      o ERROR
+ * ; following entry starts on the next line, ENTRY_BEGIN.
+ * ; more STEP items
+ * SCENARIO_END
+ *
+ * Calculations, a macro-like system: ${$myvar + 3600}
+ * STEP 10 ASSIGN myvar = 3600
+ *     ; ASSIGN event. '=' is syntactic sugar here. 3600 is some expression.
+ * ${..} is macro expanded from its expression.  Text substitution.
+ *     o $var replaced with its value.  var is identifier [azAZ09_]*
+ *     o number is that number.
+ *     o ${variables and arithmetic }
+ *     o +, -, / and *.  Note, evaluated left-to-right. Use ${} for brackets.
+ *       So again, no precedence rules, so 2+3*4 == ${2+3}*4 = 20.
+ *       Do 2+${3*4} to get 24.
+ *     o ${function params}
+ *             o ${time} is the current time for the simulated unbound.
+ *             o ${ctime value} is the text ctime(value), Fri 3 Aug 2009, ...
+ *             o ${timeout} is the time until next timeout in comm_timer list.
+ *             o ${range lower value upper} checks if lower<=value<=upper
+ *                     returns value if check succeeds.
+ *
+ * ; Example file
+ * SCENARIO_BEGIN Example scenario
+ * RANGE_BEGIN 0 100
+ *   ENTRY_BEGIN
+ *   ; precoded answers to queries.
+ *   ENTRY_END
+ * END_RANGE
+ * STEP 0 QUERY
+ *   ENTRY_BEGIN
+ *   ; query
+ *   ENTRY_END
+ * ; a query is sent out to the network by resolver.
+ * ; precoded answer from range is returned.
+ * ; algorithm will do precoded answers from RANGE immediately, except if
+ * ; the next step specifically checks for that OUT_QUERY.
+ * ; or if none of the precoded answers match.
+ * STEP 1 CHECK_ANSWER
+ *   ENTRY_BEGIN
+ *   ; what the reply should look like
+ *   ENTRY_END
+ * ; successful termination. (if the answer was OK).
+ * ; also, all answers must have been checked with CHECK_ANSWER.
+ * ; and, no more pending out_queries (that have not been checked).
+ * SCENARIO_END
+ * 
+ * </pre>
+ */
+
+#ifndef TESTCODE_REPLAY_H
+#define TESTCODE_REPLAY_H
+#include "util/netevent.h"
+#include "testcode/testpkts.h"
+#include "util/rbtree.h"
+struct replay_answer;
+struct replay_moment;
+struct replay_range;
+struct fake_pending;
+struct fake_timer;
+struct replay_var;
+struct infra_cache;
+struct sldns_buffer;
+
+/**
+ * A replay scenario.
+ */
+struct replay_scenario {
+       /** name of replay scenario. malloced string. */
+       char* title;
+
+       /** The list of replay moments. Linked list. Time increases in list. */
+       struct replay_moment* mom_first;
+       /** The last element in list of replay moments. */
+       struct replay_moment* mom_last;
+
+       /** 
+        * List of matching answers. This is to ease replay scenario
+        * creation. It lists queries (to the network) and what answer
+        * should be returned. The matching answers are valid for a range
+        * of time steps. 
+        * So: timestep, parts of query, destination --> answer.
+        */
+       struct replay_range* range_list;
+};
+
+/**
+ * A replay moment.
+ * Basically, it consists of events to a fake select() call.
+ * This is a recording of an event that happens.
+ * And if output is presented, what is done with that.
+ */
+struct replay_moment {
+       /** 
+        * The replay time step number. Starts at 0, time is incremented 
+        * every time the fake select() is run. 
+        */
+       int time_step;
+       /** Next replay moment in list of replay moments. */
+       struct replay_moment* mom_next;
+
+       /** what happens this moment? */
+       enum replay_event_type {
+               /** nothing happens, as if this event is not there. */
+               repevt_nothing,
+               /** incoming query */
+               repevt_front_query,
+               /** test fails if reply to query does not match */
+               repevt_front_reply,
+               /** timeout */
+               repevt_timeout,
+               /** time passes */
+               repevt_time_passes,
+               /** reply arrives from the network */
+               repevt_back_reply,
+               /** test fails if query to the network does not match */
+               repevt_back_query,
+               /** check autotrust key file */
+               repevt_autotrust_check,
+               /** check a temp file */
+               repevt_tempfile_check,
+               /** an error happens to outbound query */
+               repevt_error,
+               /** assignment to a variable */
+               repevt_assign,
+               /** store infra rtt cache entry: addr and string (int) */
+               repevt_infra_rtt,
+               /** cause traffic to flow */
+               repevt_traffic
+       }
+               /** variable with what is to happen this moment */
+               evt_type;
+
+       /** The sent packet must match this. Incoming events, the data. */
+       struct entry* match;
+
+       /** the amount of time that passes */
+       struct timeval elapse;
+
+       /** address that must be matched, or packet remote host address. */
+       struct sockaddr_storage addr;
+       /** length of addr, if 0, then any address will do */
+       socklen_t addrlen;
+
+       /** macro name, for assign. */
+       char* variable;
+       /** string argument, for assign. */
+       char* string;
+
+       /** the autotrust file id to check */
+       char* autotrust_id;
+       /** file contents to match, one string per line */
+       struct config_strlist* file_content;
+};
+
+/**
+ * Range of timesteps, and canned replies to matching queries.
+ */
+struct replay_range {
+       /** time range when this is valid. Including start and end step. */
+       int start_step;
+       /** end step of time range. */
+       int end_step;
+       /** address of where this range is served. */
+       struct sockaddr_storage addr;
+       /** length of addr, if 0, then any address will do */
+       socklen_t addrlen;
+
+       /** Matching list */
+       struct entry* match;
+
+       /** next in list of time ranges. */
+       struct replay_range* next_range;
+};
+
+/**
+ * Replay storage of runtime information.
+ */
+struct replay_runtime {
+       /**
+        * The scenario
+        */
+       struct replay_scenario* scenario;
+       /**
+        * Current moment.
+        */
+       struct replay_moment* now;
+
+       /** 
+        * List of pending queries in order they were sent out. First
+        * one has been sent out most recently. Last one in list is oldest. 
+        */
+       struct fake_pending* pending_list;
+
+       /**
+        * List of answers to queries from clients. These need to be checked.
+        */
+       struct replay_answer* answer_list;
+       
+       /** last element in answer list. */
+       struct replay_answer* answer_last;
+
+       /** list of fake timer callbacks that are pending */
+       struct fake_timer* timer_list;
+
+       /** callback to call for incoming queries */
+       comm_point_callback_type* callback_query;
+       /** user argument for incoming query callback */
+       void *cb_arg;
+
+       /** ref the infra cache (was passed to outside_network_create) */
+       struct infra_cache* infra;
+
+       /** the current time in seconds */
+       time_t now_secs;
+       /** the current time in microseconds */
+       struct timeval now_tv;
+
+       /** signal handler callback */
+       void (*sig_cb)(int, void*);
+       /** signal handler user arg */
+       void *sig_cb_arg;
+       /** time to exit cleanly */
+       int exit_cleanly;
+
+       /** size of buffers */
+       size_t bufsize;
+
+       /**
+        * Tree of macro values. Of type replay_var
+        */
+       rbtree_type* vars;
+};
+
+/**
+ * Pending queries to network, fake replay version.
+ */
+struct fake_pending {
+       /** what is important only that we remember the query, copied here. */
+       struct sldns_buffer* buffer;
+       /** and to what address this is sent to. */
+       struct sockaddr_storage addr;
+       /** len of addr */
+       socklen_t addrlen;
+       /** zone name, uncompressed wire format (as used when sent) */
+       uint8_t* zone;
+       /** length of zone name */
+       size_t zonelen;
+       /** qtype */
+       int qtype;
+       /** The callback function to call when answer arrives (or timeout) */
+       comm_point_callback_type* callback;
+       /** callback user argument */
+       void* cb_arg;
+       /** original timeout in seconds from 'then' */
+       int timeout;
+
+       /** next in pending list */
+       struct fake_pending* next;
+       /** the buffer parsed into a sldns_pkt */
+       uint8_t* pkt;
+       size_t pkt_len;
+       /** by what transport was the query sent out */
+       enum transport_type transport;
+       /** if this is a serviced query */
+       int serviced;
+       /** if we are handling a multi pkt tcp stream, non 0 and the pkt nr*/
+       int tcp_pkt_counter;
+       /** the runtime structure this is part of */
+       struct replay_runtime* runtime;
+};
+
+/**
+ * An answer that is pending to happen.
+ */
+struct replay_answer {
+       /** Next in list */
+       struct replay_answer* next;
+       /** reply information */
+       struct comm_reply repinfo;
+       /** the answer preparsed as ldns pkt */
+       uint8_t* pkt;
+       size_t pkt_len;
+};
+
+/**
+ * Timers with callbacks, fake replay version.
+ */
+struct fake_timer {
+       /** next in list */
+       struct fake_timer* next;
+       /** the runtime structure this is part of */
+       struct replay_runtime* runtime;
+       /** the callback to call */
+       void (*cb)(void*);
+       /** the callback user argument */
+       void* cb_arg;
+       /** if timer is enabled */
+       int enabled;
+       /** when the timer expires */
+       struct timeval tv;
+};
+
+/**
+ * Replay macro variable.  And its value.
+ */
+struct replay_var {
+       /** rbtree node. Key is this structure. Sorted by name. */
+       rbnode_type node;
+       /** the variable name */
+       char* name;
+       /** the variable value */
+       char* value;
+};
+
+/**
+ * Read a replay scenario from the file.
+ * @param in: file to read from.
+ * @param name: name to print in errors.
+ * @param lineno: incremented for every line read.
+ * @return: Scenario. NULL if no scenario read.
+ */
+struct replay_scenario* replay_scenario_read(FILE* in, const char* name, 
+       int* lineno);
+
+/**
+ * Delete scenario.
+ * @param scen: to delete.
+ */
+void replay_scenario_delete(struct replay_scenario* scen);
+
+/** compare two replay_vars */
+int replay_var_compare(const void* a, const void* b);
+
+/** get oldest enabled fake timer */
+struct fake_timer* replay_get_oldest_timer(struct replay_runtime* runtime);
+
+/**
+ * Create variable storage
+ * @return new or NULL on failure.
+ */
+rbtree_type* macro_store_create(void);
+
+/**
+ * Delete variable storage
+ * @param store: the macro storage to free up.
+ */
+void macro_store_delete(rbtree_type* store);
+
+/**
+ * Apply macro substitution to string.
+ * @param store: variable store.
+ * @param runtime: the runtime to look up values as needed.
+ * @param text: string to work on.
+ * @return newly malloced string with result.
+ */
+char* macro_process(rbtree_type* store, struct replay_runtime* runtime, 
+       char* text);
+
+/**
+ * Look up a macro value. Like calling ${$name}.
+ * @param store: variable store
+ * @param name: macro name
+ * @return newly malloced string with result or strdup("") if not found.
+ *     or NULL on malloc failure.
+ */
+char* macro_lookup(rbtree_type* store, char* name);
+
+/**
+ * Set macro value.
+ * @param store: variable store
+ * @param name: macro name
+ * @param value: text to set it to.  Not expanded.
+ * @return false on failure.
+ */
+int macro_assign(rbtree_type* store, char* name, char* value);
+
+/** Print macro variables stored as debug info */
+void macro_print_debug(rbtree_type* store);
+
+/** testbounds self test */
+void testbound_selftest(void);
+
+#endif /* TESTCODE_REPLAY_H */
diff --git a/usr.sbin/unbound/testcode/run_vm.sh b/usr.sbin/unbound/testcode/run_vm.sh
new file mode 100644 (file)
index 0000000..d4c2a2e
--- /dev/null
@@ -0,0 +1,76 @@
+#!/usr/local/bin/bash
+# run tdir tests from within a VM.  Looks for loopback addr.
+# if run not from within a VM, runs the tests as usual.
+# with one argument: run that tdir, otherwise, run all tdirs.
+
+get_lo0_ip4() {
+        if test -x /sbin/ifconfig
+        then
+                LO0_IP4=`/sbin/ifconfig lo0 | grep '[^0-9]127\.' | sed -e 's/^[^1]*\(127\.[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\)[^0-9]*.*$/\1/g'`
+                if ( echo $LO0_IP4 | grep '^127\.[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*$' > /dev/null )
+                then
+                        return
+                fi
+        fi
+        LO0_IP4=127.0.0.1
+}
+get_lo0_ip4
+export LO0_IP4
+if test "x$LO0_IP4" = "x127.0.0.1"
+then
+        ALT_LOOPBACK=false
+else
+        ALT_LOOPBACK=true
+fi
+cd testdata
+TPKG=../testcode/mini_tdir.sh
+#RUNLIST=`(ls -1d *.tdir|grep -v '^0[016]')`
+RUNLIST=`(ls -1d *.tdir)`
+if test "$#" = "1"; then RUNLIST="$1"; fi
+
+# fix up tdir that was edited on keyboard interrupt.
+cleanup() {
+       echo cleanup
+       if test -f "$t.bak"; then mv "$t.bak" "$t"; fi
+       exit 0
+}
+trap cleanup SIGINT
+
+for t in $RUNLIST
+do
+       if ! $ALT_LOOPBACK
+       then
+               $TPKG exe $t
+               continue
+       fi
+       # We have alternative 127.0.0.1 number
+       if ( echo $t | grep '6\.tdir$' ) # skip IPv6 tests
+       then
+               continue
+               elif test "$t" = "edns_cache.tdir" # This one is IPv6 too!
+       then
+               continue
+       fi
+       cp -ap "$t" "$t.bak"
+       find "${t}" -type f \
+               -exec grep -q -e '127\.0\.0\.1' -e '@localhost' {} \; -print | {
+               while read f
+               do
+                       sed "s/127\.0\.0\.1/${LO0_IP4}/g" "$f" > "$f._"
+                       mv "$f._" "$f"
+                       sed "s/@localhost/@${LO0_IP4}/g" "$f" > "$f._"
+                       mv "$f._" "$f"
+               done
+       }
+       find "${t}" -type d -name "127.0.0.1" -print | {
+               while read d
+               do
+                       mv -v "$d" "${d%127.0.0.1}${LO0_IP4}"
+               done
+       }
+       $TPKG exe $t
+       rm -fr "${t}"
+       mv "$t.bak" "$t"
+done
+# get out of testdata/
+cd ..
diff --git a/usr.sbin/unbound/testcode/signit.c b/usr.sbin/unbound/testcode/signit.c
new file mode 100644 (file)
index 0000000..0eca0e0
--- /dev/null
@@ -0,0 +1,284 @@
+/*
+ * testcode/signit.c - debug tool to sign rrsets with given keys.
+ *
+ * Copyright (c) 2007, NLnet Labs. All rights reserved.
+ *
+ * This software is open source.
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 
+ * Neither the name of the NLNET LABS nor the names of its contributors may
+ * be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+ * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * \file
+ *
+ * This program signs rrsets with the given keys. It can be used to 
+ * construct input to test the validator with.
+ */
+#include "config.h"
+#include <ldns/ldns.h>
+#include <assert.h>
+
+#define DNSKEY_BIT_ZSK 0x0100
+
+/**
+ * Key settings
+ */
+struct keysets {
+       /** signature inception */
+       uint32_t incep;
+       /** signature expiration */
+       uint32_t expi;
+       /** owner name */
+       char* owner;
+       /** keytag */
+       uint16_t keytag;
+       /** DNSKEY flags */
+       uint16_t flags;
+};
+
+/** print usage and exit */
+static void
+usage(void)
+{
+       printf("usage:  signit expi ince keytag owner keyfile\n");
+       printf("present rrset data on stdin.\n");
+       printf("signed data is printed to stdout.\n");
+       printf("\n");
+       printf("Or use: signit NSEC3PARAM hash flags iter salt\n");
+       printf("present names on stdin, hashed names are printed to stdout.\n");
+       exit(1);
+}
+
+static time_t 
+convert_timeval(const char* str)
+{
+       time_t t;
+       struct tm tm;
+       memset(&tm, 0, sizeof(tm));
+       if(strlen(str) < 14)
+               return 0;
+       if(sscanf(str, "%4d%2d%2d%2d%2d%2d", &tm.tm_year, &tm.tm_mon, 
+               &tm.tm_mday, &tm.tm_hour, &tm.tm_min, &tm.tm_sec) != 6)
+               return 0;
+       tm.tm_year -= 1900;
+       tm.tm_mon--;
+       /* Check values */
+       if (tm.tm_year < 70)    return 0;
+       if (tm.tm_mon < 0 || tm.tm_mon > 11)    return 0;
+       if (tm.tm_mday < 1 || tm.tm_mday > 31)  return 0;
+       if (tm.tm_hour < 0 || tm.tm_hour > 23)  return 0;
+       if (tm.tm_min < 0 || tm.tm_min > 59)    return 0;
+       if (tm.tm_sec < 0 || tm.tm_sec > 59)    return 0;
+       /* call ldns conversion function */
+       t = ldns_mktime_from_utc(&tm);
+       return t;
+}
+
+static void fatal_exit(const char* format, ...)
+{
+       va_list args;
+       va_start(args, format);
+       printf("fatal exit: ");
+       vprintf(format, args);
+       va_end(args);
+       exit(1);
+}
+
+/** read expi ince keytag owner from cmdline */
+static void
+parse_cmdline(char *argv[], struct keysets* s)
+{
+       s->expi = convert_timeval(argv[1]);
+       s->incep = convert_timeval(argv[2]);
+       s->keytag = (uint16_t)atoi(argv[3]);
+       s->owner = argv[4];
+       s->flags = DNSKEY_BIT_ZSK; /* to enforce signing */
+}
+
+/** read all key files, exit on error */
+static ldns_key_list*
+read_keys(int num, char* names[], struct keysets* set)
+{
+       int i;
+       ldns_key_list* keys = ldns_key_list_new();
+       ldns_key* k;
+       ldns_rdf* rdf;
+       ldns_status s;
+       int b;
+       FILE* in;
+
+       if(!keys) fatal_exit("alloc failure");
+       for(i=0; i<num; i++) {
+               printf("read keyfile %s\n", names[i]);
+               in = fopen(names[i], "r");
+               if(!in) fatal_exit("could not open %s: %s", names[i],
+                               strerror(errno));
+               s = ldns_key_new_frm_fp(&k, in);
+               fclose(in);
+               if(s != LDNS_STATUS_OK)
+                       fatal_exit("bad keyfile %s: %s", names[i],
+                               ldns_get_errorstr_by_id(s));
+               ldns_key_set_expiration(k, set->expi);
+               ldns_key_set_inception(k, set->incep);
+               s = ldns_str2rdf_dname(&rdf, set->owner);
+               if(s != LDNS_STATUS_OK)
+                       fatal_exit("bad owner name %s: %s", set->owner,
+                               ldns_get_errorstr_by_id(s));
+               ldns_key_set_pubkey_owner(k, rdf);
+               ldns_key_set_flags(k, set->flags);
+               ldns_key_set_keytag(k, set->keytag);
+               b = ldns_key_list_push_key(keys, k);
+               assert(b);
+       }
+       return keys;
+}
+
+/** read list of rrs from the file */
+static ldns_rr_list*
+read_rrs(FILE* in)
+{
+       uint32_t my_ttl = 3600;
+       ldns_rdf *my_origin = NULL;
+       ldns_rdf *my_prev = NULL;
+       ldns_status s;
+       int line_nr = 1;
+       int b;
+
+       ldns_rr_list* list;
+       ldns_rr *rr;
+
+       list = ldns_rr_list_new();
+       if(!list) fatal_exit("alloc error");
+
+       while(!feof(in)) {
+               s = ldns_rr_new_frm_fp_l(&rr, in, &my_ttl, &my_origin,
+                       &my_prev, &line_nr);
+               if(s == LDNS_STATUS_SYNTAX_TTL || 
+                       s == LDNS_STATUS_SYNTAX_ORIGIN ||
+                       s == LDNS_STATUS_SYNTAX_EMPTY)
+                       continue;
+               else if(s != LDNS_STATUS_OK)
+                       fatal_exit("parse error in line %d: %s", line_nr,
+                               ldns_get_errorstr_by_id(s));
+               b = ldns_rr_list_push_rr(list, rr);
+               assert(b);
+       }
+       printf("read %d lines\n", line_nr);
+
+       return list;
+}
+
+/** sign the rrs with the keys */
+static void
+signit(ldns_rr_list* rrs, ldns_key_list* keys)
+{
+       ldns_rr_list* rrset;
+       ldns_rr_list* sigs;
+       
+       while(ldns_rr_list_rr_count(rrs) > 0) {
+               rrset = ldns_rr_list_pop_rrset(rrs);
+               if(!rrset) fatal_exit("copy alloc failure");
+               sigs = ldns_sign_public(rrset, keys);
+               if(!sigs) fatal_exit("failed to sign");
+               ldns_rr_list_print(stdout, rrset);
+               ldns_rr_list_print(stdout, sigs);
+               printf("\n");
+               ldns_rr_list_free(rrset);
+               ldns_rr_list_free(sigs);
+       }
+}
+
+/** process keys and signit */
+static void
+process_keys(int argc, char* argv[])
+{
+       ldns_rr_list* rrs;
+       ldns_key_list* keys;
+       struct keysets settings;
+       assert(argc == 6);
+
+       parse_cmdline(argv, &settings);
+       keys = read_keys(1, argv+5, &settings);
+       rrs = read_rrs(stdin);
+       signit(rrs, keys);
+
+       ldns_rr_list_deep_free(rrs);
+       ldns_key_list_free(keys);
+}
+
+/** process nsec3 params and perform hashing */
+static void
+process_nsec3(int argc, char* argv[])
+{
+       char line[10240];
+       ldns_rdf* salt;
+       ldns_rdf* in, *out;
+       ldns_status status;
+       status = ldns_str2rdf_nsec3_salt(&salt, argv[5]);
+       if(status != LDNS_STATUS_OK)
+               fatal_exit("Could not parse salt %s: %s", argv[5],
+                       ldns_get_errorstr_by_id(status));
+       assert(argc == 6);
+       while(fgets(line, (int)sizeof(line), stdin)) {
+               if(strlen(line) > 0)
+                       line[strlen(line)-1] = 0; /* remove trailing newline */
+               if(line[0]==0)
+                       continue;
+               status = ldns_str2rdf_dname(&in, line);
+               if(status != LDNS_STATUS_OK)
+                       fatal_exit("Could not parse name %s: %s", line,
+                               ldns_get_errorstr_by_id(status));
+               ldns_rdf_print(stdout, in);
+               printf(" -> ");
+               /* arg 3 is flags, unused */
+               out = ldns_nsec3_hash_name(in, (uint8_t)atoi(argv[2]), 
+                       (uint16_t)atoi(argv[4]),
+                       ldns_rdf_data(salt)[0], ldns_rdf_data(salt)+1);
+               if(!out)
+                       fatal_exit("Could not hash %s", line);
+               ldns_rdf_print(stdout, out);
+               printf("\n");
+               ldns_rdf_deep_free(in);
+               ldns_rdf_deep_free(out);
+       }
+       ldns_rdf_deep_free(salt);
+}
+
+/** main program */
+int main(int argc, char* argv[])
+{
+       if(argc != 6) {
+               usage();
+       }
+       if(strcmp(argv[1], "NSEC3PARAM") == 0) {
+               process_nsec3(argc, argv);
+               return 0;
+       }
+       process_keys(argc, argv);
+       return 0;
+}
diff --git a/usr.sbin/unbound/testcode/streamtcp.1 b/usr.sbin/unbound/testcode/streamtcp.1
new file mode 100644 (file)
index 0000000..7c738d9
--- /dev/null
@@ -0,0 +1,66 @@
+.TH "unbound\-streamtcp" "1" "Mar 21, 2013" "NLnet Labs" "unbound"
+.\"
+.\" unbound-streamtcp.1 -- unbound DNS lookup utility
+.\"
+.SH "NAME"
+.LP
+.B unbound\-streamtcp
+\- unbound DNS lookup utility
+.SH "SYNOPSIS"
+.LP
+.B unbound\-streamtcp
+.RB [ \-unsh ]
+.RB [ \-f 
+.IR ipaddr[@port] ]
+.I name
+.I type
+.I class
+.SH "DESCRIPTION"
+.LP
+.B unbound\-streamtcp
+sends a DNS Query of the given \fBtype\fR and \fBclass\fR for the given \fBname\fR
+to the DNS server over TCP and displays the response.
+.P
+If the server to query is not given using the \fB\-f\fR option then localhost
+(127.0.0.1) is used. More queries can be given on one commandline, they
+are resolved in sequence.
+.P
+The available options are:
+.TP
+.I name
+This name is resolved (looked up in the DNS).
+.TP
+.I type
+Specify the type of data to lookup.
+.TP
+.I class
+Specify the class to lookup for.
+.TP
+.B \-u
+Use UDP instead of TCP. No retries are attempted.
+.TP
+.B \-n
+Do not wait for the answer.
+.TP
+.B \-s
+Use SSL.
+.TP
+.B \-h
+Print program usage.
+.TP
+.B \-f \fIipaddr[@port]
+Specify the server to send the queries to. If not specified localhost (127.0.0.1) is used.
+.SH "EXAMPLES"
+.LP
+Some examples of use.
+.P
+$ unbound\-streamtcp www.example.com A IN
+.P
+$ unbound\-streamtcp \-f 192.168.1.1 www.example.com SOA IN
+.P
+$ unbound\-streamtcp \-f 192.168.1.1@1234 153.1.168.192.in\-addr.arpa. PTR IN
+.SH "EXIT CODE"
+The unbound\-streamtcp program exits with status code 1 on error, 
+0 on no error.
+.SH "AUTHOR"
+This manual page was written by Tomas Hozza <thozza@redhat.com>.
diff --git a/usr.sbin/unbound/testcode/streamtcp.c b/usr.sbin/unbound/testcode/streamtcp.c
new file mode 100644 (file)
index 0000000..0a63639
--- /dev/null
@@ -0,0 +1,435 @@
+/*
+ * testcode/streamtcp.c - debug program perform multiple DNS queries on tcp.
+ *
+ * Copyright (c) 2008, NLnet Labs. All rights reserved.
+ *
+ * This software is open source.
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 
+ * Neither the name of the NLNET LABS nor the names of its contributors may
+ * be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+ * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * \file
+ *
+ * This program performs multiple DNS queries on a TCP stream.
+ */
+
+#include "config.h"
+#ifdef HAVE_GETOPT_H
+#include <getopt.h>
+#endif
+#include <signal.h>
+#include "util/locks.h"
+#include "util/log.h"
+#include "util/net_help.h"
+#include "util/data/msgencode.h"
+#include "util/data/msgparse.h"
+#include "util/data/msgreply.h"
+#include "util/data/dname.h"
+#include "sldns/sbuffer.h"
+#include "sldns/str2wire.h"
+#include "sldns/wire2str.h"
+#include <openssl/ssl.h>
+#include <openssl/rand.h>
+#include <openssl/err.h>
+
+#ifndef PF_INET6
+/** define in case streamtcp is compiled on legacy systems */
+#define PF_INET6 10
+#endif
+
+/** usage information for streamtcp */
+static void usage(char* argv[])
+{
+       printf("usage: %s [options] name type class ...\n", argv[0]);
+       printf("        sends the name-type-class queries over TCP.\n");
+       printf("-f server       what ipaddr@portnr to send the queries to\n");
+       printf("-u              use UDP. No retries are attempted.\n");
+       printf("-n              do not wait for an answer.\n");
+       printf("-s              use ssl\n");
+       printf("-h              this help text\n");
+       exit(1);
+}
+
+/** open TCP socket to svr */
+static int
+open_svr(const char* svr, int udp)
+{
+       struct sockaddr_storage addr;
+       socklen_t addrlen;
+       int fd = -1;
+       /* svr can be ip@port */
+       memset(&addr, 0, sizeof(addr));
+       if(!extstrtoaddr(svr, &addr, &addrlen)) {
+               printf("fatal: bad server specs '%s'\n", svr);
+               exit(1);
+       }
+       fd = socket(addr_is_ip6(&addr, addrlen)?PF_INET6:PF_INET,
+               udp?SOCK_DGRAM:SOCK_STREAM, 0);
+       if(fd == -1) {
+#ifndef USE_WINSOCK
+               perror("socket() error");
+#else
+               printf("socket: %s\n", wsa_strerror(WSAGetLastError()));
+#endif
+               exit(1);
+       }
+       if(connect(fd, (struct sockaddr*)&addr, addrlen) < 0) {
+#ifndef USE_WINSOCK
+               perror("connect() error");
+#else
+               printf("connect: %s\n", wsa_strerror(WSAGetLastError()));
+#endif
+               exit(1);
+       }
+       return fd;
+}
+
+/** write a query over the TCP fd */
+static void
+write_q(int fd, int udp, SSL* ssl, sldns_buffer* buf, uint16_t id, 
+       const char* strname, const char* strtype, const char* strclass)
+{
+       struct query_info qinfo;
+       uint16_t len;
+       /* qname */
+       qinfo.qname = sldns_str2wire_dname(strname, &qinfo.qname_len);
+       if(!qinfo.qname) {
+               printf("cannot parse query name: '%s'\n", strname);
+               exit(1);
+       }
+
+       /* qtype and qclass */
+       qinfo.qtype = sldns_get_rr_type_by_name(strtype);
+       qinfo.qclass = sldns_get_rr_class_by_name(strclass);
+
+       /* clear local alias */
+       qinfo.local_alias = NULL;
+
+       /* make query */
+       qinfo_query_encode(buf, &qinfo);
+       sldns_buffer_write_u16_at(buf, 0, id);
+       sldns_buffer_write_u16_at(buf, 2, BIT_RD);
+
+       if(1) {
+               /* add EDNS DO */
+               struct edns_data edns;
+               memset(&edns, 0, sizeof(edns));
+               edns.edns_present = 1;
+               edns.bits = EDNS_DO;
+               edns.udp_size = 4096;
+               if(sldns_buffer_capacity(buf) >=
+                       sldns_buffer_limit(buf)+calc_edns_field_size(&edns))
+                       attach_edns_record(buf, &edns);
+       }
+
+       /* send it */
+       if(!udp) {
+               len = (uint16_t)sldns_buffer_limit(buf);
+               len = htons(len);
+               if(ssl) {
+                       if(SSL_write(ssl, (void*)&len, (int)sizeof(len)) <= 0) {
+                               log_crypto_err("cannot SSL_write");
+                               exit(1);
+                       }
+               } else {
+                       if(send(fd, (void*)&len, sizeof(len), 0) <
+                               (ssize_t)sizeof(len)){
+#ifndef USE_WINSOCK
+                               perror("send() len failed");
+#else
+                               printf("send len: %s\n", 
+                                       wsa_strerror(WSAGetLastError()));
+#endif
+                               exit(1);
+                       }
+               }
+       }
+       if(ssl) {
+               if(SSL_write(ssl, (void*)sldns_buffer_begin(buf),
+                       (int)sldns_buffer_limit(buf)) <= 0) {
+                       log_crypto_err("cannot SSL_write");
+                       exit(1);
+               }
+       } else {
+               if(send(fd, (void*)sldns_buffer_begin(buf),
+                       sldns_buffer_limit(buf), 0) < 
+                       (ssize_t)sldns_buffer_limit(buf)) {
+#ifndef USE_WINSOCK
+                       perror("send() data failed");
+#else
+                       printf("send data: %s\n", wsa_strerror(WSAGetLastError()));
+#endif
+                       exit(1);
+               }
+       }
+
+       free(qinfo.qname);
+}
+
+/** receive DNS datagram over TCP and print it */
+static void
+recv_one(int fd, int udp, SSL* ssl, sldns_buffer* buf)
+{
+       char* pktstr;
+       uint16_t len;
+       if(!udp) {
+               if(ssl) {
+                       if(SSL_read(ssl, (void*)&len, (int)sizeof(len)) <= 0) {
+                               log_crypto_err("could not SSL_read");
+                               exit(1);
+                       }
+               } else {
+                       if(recv(fd, (void*)&len, sizeof(len), 0) <
+                               (ssize_t)sizeof(len)) {
+#ifndef USE_WINSOCK
+                               perror("read() len failed");
+#else
+                               printf("read len: %s\n", 
+                                       wsa_strerror(WSAGetLastError()));
+#endif
+                               exit(1);
+                       }
+               }
+               len = ntohs(len);
+               sldns_buffer_clear(buf);
+               sldns_buffer_set_limit(buf, len);
+               if(ssl) {
+                       int r = SSL_read(ssl, (void*)sldns_buffer_begin(buf),
+                               (int)len);
+                       if(r <= 0) {
+                               log_crypto_err("could not SSL_read");
+                               exit(1);
+                       }
+                       if(r != (int)len)
+                               fatal_exit("ssl_read %d of %d", r, len);
+               } else {
+                       if(recv(fd, (void*)sldns_buffer_begin(buf), len, 0) < 
+                               (ssize_t)len) {
+#ifndef USE_WINSOCK
+                               perror("read() data failed");
+#else
+                               printf("read data: %s\n", 
+                                       wsa_strerror(WSAGetLastError()));
+#endif
+                               exit(1);
+                       }
+               }
+       } else {
+               ssize_t l;
+               sldns_buffer_clear(buf);
+               if((l=recv(fd, (void*)sldns_buffer_begin(buf), 
+                       sldns_buffer_capacity(buf), 0)) < 0) {
+#ifndef USE_WINSOCK
+                       perror("read() data failed");
+#else
+                       printf("read data: %s\n", 
+                               wsa_strerror(WSAGetLastError()));
+#endif
+                       exit(1);
+               }
+               sldns_buffer_set_limit(buf, (size_t)l);
+               len = (size_t)l;
+       }
+       printf("\nnext received packet\n");
+       log_buf(0, "data", buf);
+
+       pktstr = sldns_wire2str_pkt(sldns_buffer_begin(buf), len);
+       printf("%s", pktstr);
+       free(pktstr);
+}
+
+static int get_random(void)
+{
+       int r;
+       if (RAND_bytes((unsigned char*)&r, (int)sizeof(r)) == 1) {
+               return r;
+       }
+       return arc4random();
+}
+
+/** send the TCP queries and print answers */
+static void
+send_em(const char* svr, int udp, int usessl, int noanswer, int num, char** qs)
+{
+       sldns_buffer* buf = sldns_buffer_new(65553);
+       int fd = open_svr(svr, udp);
+       int i;
+       SSL_CTX* ctx = NULL;
+       SSL* ssl = NULL;
+       if(!buf) fatal_exit("out of memory");
+       if(usessl) {
+               ctx = connect_sslctx_create(NULL, NULL, NULL, 0);
+               if(!ctx) fatal_exit("cannot create ssl ctx");
+               ssl = outgoing_ssl_fd(ctx, fd);
+               if(!ssl) fatal_exit("cannot create ssl");
+               while(1) {
+                       int r;
+                       ERR_clear_error();
+                       if( (r=SSL_do_handshake(ssl)) == 1)
+                               break;
+                       r = SSL_get_error(ssl, r);
+                       if(r != SSL_ERROR_WANT_READ &&
+                               r != SSL_ERROR_WANT_WRITE) {
+                               log_crypto_err("could not ssl_handshake");
+                               exit(1);
+                       }
+               }
+               if(1) {
+                       X509* x = SSL_get_peer_certificate(ssl);
+                       if(!x) printf("SSL: no peer certificate\n");
+                       else {
+                               X509_print_fp(stdout, x);
+                               X509_free(x);
+                       }
+               }
+       }
+       for(i=0; i<num; i+=3) {
+               printf("\nNext query is %s %s %s\n", qs[i], qs[i+1], qs[i+2]);
+               write_q(fd, udp, ssl, buf, (uint16_t)get_random(), qs[i],
+                       qs[i+1], qs[i+2]);
+               /* print at least one result */
+               if(!noanswer)
+                       recv_one(fd, udp, ssl, buf);
+       }
+
+       if(usessl) {
+               SSL_shutdown(ssl);
+               SSL_free(ssl);
+               SSL_CTX_free(ctx);
+       }
+#ifndef USE_WINSOCK
+       close(fd);
+#else
+       closesocket(fd);
+#endif
+       sldns_buffer_free(buf);
+       printf("orderly exit\n");
+}
+
+#ifdef SIGPIPE
+/** SIGPIPE handler */
+static RETSIGTYPE sigh(int sig)
+{
+       if(sig == SIGPIPE) {
+               printf("got SIGPIPE, remote connection gone\n");
+               exit(1);
+       }
+       printf("Got unhandled signal %d\n", sig);
+       exit(1);
+}
+#endif /* SIGPIPE */
+
+/** getopt global, in case header files fail to declare it. */
+extern int optind;
+/** getopt global, in case header files fail to declare it. */
+extern char* optarg;
+
+/** main program for streamtcp */
+int main(int argc, char** argv) 
+{
+       int c;
+       const char* svr = "127.0.0.1";
+       int udp = 0;
+       int noanswer = 0;
+       int usessl = 0;
+
+#ifdef USE_WINSOCK
+       WSADATA wsa_data;
+       if(WSAStartup(MAKEWORD(2,2), &wsa_data) != 0) {
+               printf("WSAStartup failed\n");
+               return 1;
+       }
+#endif
+
+       /* lock debug start (if any) */
+       log_init(0, 0, 0);
+       checklock_start();
+
+#ifdef SIGPIPE
+       if(signal(SIGPIPE, &sigh) == SIG_ERR) {
+               perror("could not install signal handler");
+               return 1;
+       }
+#endif
+
+       /* command line options */
+       if(argc == 1) {
+               usage(argv);
+       }
+       while( (c=getopt(argc, argv, "f:hnsu")) != -1) {
+               switch(c) {
+                       case 'f':
+                               svr = optarg;
+                               break;
+                       case 'n':
+                               noanswer = 1;
+                               break;
+                       case 'u':
+                               udp = 1;
+                               break;
+                       case 's':
+                               usessl = 1;
+                               break;
+                       case 'h':
+                       case '?':
+                       default:
+                               usage(argv);
+               }
+       }
+       argc -= optind;
+       argv += optind;
+
+       if(argc % 3 != 0) {
+               printf("queries must be multiples of name,type,class\n");
+               return 1;
+       }
+       if(usessl) {
+#if OPENSSL_VERSION_NUMBER < 0x10100000 || !defined(HAVE_OPENSSL_INIT_SSL)
+               ERR_load_SSL_strings();
+#endif
+#if OPENSSL_VERSION_NUMBER < 0x10100000 || !defined(HAVE_OPENSSL_INIT_CRYPTO)
+               OpenSSL_add_all_algorithms();
+#else
+               OPENSSL_init_crypto(OPENSSL_INIT_ADD_ALL_CIPHERS
+                       | OPENSSL_INIT_ADD_ALL_DIGESTS
+                       | OPENSSL_INIT_LOAD_CRYPTO_STRINGS, NULL);
+#endif
+#if OPENSSL_VERSION_NUMBER < 0x10100000 || !defined(HAVE_OPENSSL_INIT_SSL)
+               (void)SSL_library_init();
+#else
+               (void)OPENSSL_init_ssl(OPENSSL_INIT_LOAD_SSL_STRINGS, NULL);
+#endif
+       }
+       send_em(svr, udp, usessl, noanswer, argc, argv);
+       checklock_stop();
+#ifdef USE_WINSOCK
+       WSACleanup();
+#endif
+       return 0;
+}
diff --git a/usr.sbin/unbound/testcode/testbound.c b/usr.sbin/unbound/testcode/testbound.c
new file mode 100644 (file)
index 0000000..071ac9c
--- /dev/null
@@ -0,0 +1,555 @@
+/*
+ * testcode/testbound.c - test program for unbound.
+ *
+ * Copyright (c) 2007, NLnet Labs. All rights reserved.
+ *
+ * This software is open source.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * Neither the name of the NLNET LABS nor the names of its contributors may
+ * be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+ * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+/**
+ * \file
+ * Exits with code 1 on a failure. 0 if all unit tests are successful.
+ */
+
+#include "config.h"
+#ifdef HAVE_TIME_H
+#  include <time.h>
+#endif
+#include "testcode/testpkts.h"
+#include "testcode/replay.h"
+#include "testcode/fake_event.h"
+#include "daemon/remote.h"
+#include "util/config_file.h"
+#include "sldns/keyraw.h"
+#include <ctype.h>
+
+/** signal that this is a testbound compile */
+#define unbound_testbound 1
+/** 
+ * include the main program from the unbound daemon.
+ * rename main to daemon_main to call it
+ */
+#define main daemon_main
+#include "daemon/unbound.c"
+#undef main
+
+/** maximum line length for lines in the replay file. */
+#define MAX_LINE_LEN 1024
+/** config files (removed at exit) */
+static struct config_strlist* cfgfiles = NULL;
+
+/** give commandline usage for testbound. */
+static void
+testbound_usage(void)
+{
+       printf("usage: testbound [options]\n");
+       printf("\ttest the unbound daemon.\n");
+       printf("-h      this help\n");
+       printf("-p file playback text file\n");
+       printf("-1      detect SHA1 support (exit code 0 or 1)\n");
+       printf("-2      detect SHA256 support (exit code 0 or 1)\n");
+       printf("-g      detect GOST support (exit code 0 or 1)\n");
+       printf("-e      detect ECDSA support (exit code 0 or 1)\n");
+       printf("-c      detect CLIENT_SUBNET support (exit code 0 or 1)\n");
+       printf("-i      detect IPSECMOD support (exit code 0 or 1)\n");
+       printf("-s      testbound self-test - unit test of testbound parts.\n");
+       printf("-o str  unbound commandline options separated by spaces.\n");
+       printf("Version %s\n", PACKAGE_VERSION);
+       printf("BSD licensed, see LICENSE file in source package.\n");
+       printf("Report bugs to %s.\n", PACKAGE_BUGREPORT);
+}
+
+/** Max number of arguments to pass to unbound. */
+#define MAXARG 100
+
+/** 
+ * Add options from string to passed argc. splits on whitespace.
+ * @param args: the option argument, "-v -p 12345" or so.
+ * @param pass_argc: ptr to the argc for unbound. Modified.
+ * @param pass_argv: the argv to pass to unbound. Modified.
+ */
+static void
+add_opts(const char* args, int* pass_argc, char* pass_argv[])
+{
+       const char *p = args, *np;
+       size_t len;
+       while(p && isspace((unsigned char)*p)) 
+               p++;
+       while(p && *p) {
+               /* find location of next string and length of this one */
+               if((np = strchr(p, ' ')))
+                       len = (size_t)(np-p);
+               else    len = strlen(p);
+               /* allocate and copy option */
+               if(*pass_argc >= MAXARG-1)
+                       fatal_exit("too many arguments: '%s'", p);
+               pass_argv[*pass_argc] = (char*)malloc(len+1);
+               if(!pass_argv[*pass_argc])
+                       fatal_exit("add_opts: out of memory");
+               memcpy(pass_argv[*pass_argc], p, len);
+               pass_argv[*pass_argc][len] = 0;
+               (*pass_argc)++;
+               /* go to next option */
+               p = np;
+               while(p && isspace((unsigned char)*p)) 
+                       p++;
+       }
+}
+
+/** pretty print commandline for unbound in this test */
+static void
+echo_cmdline(int argc, char* argv[])
+{
+       int i;
+       fprintf(stderr, "testbound is starting:");
+       for(i=0; i<argc; i++) {
+               fprintf(stderr, " [%s]", argv[i]);
+       }
+       fprintf(stderr, "\n");
+}
+
+/** spool temp file name */
+static void
+spool_temp_file_name(int* lineno, FILE* cfg, char* id)
+{
+       char line[MAX_LINE_LEN];
+       /* find filename for new file */
+       while(isspace((unsigned char)*id))
+               id++;
+       if(*id == '\0') 
+               fatal_exit("TEMPFILE_NAME must have id, line %d", *lineno);
+       id[strlen(id)-1]=0; /* remove newline */
+       fake_temp_file("_temp_", id, line, sizeof(line));
+       fprintf(cfg, "\"%s\"\n", line);
+}
+
+/** spool temp file */
+static void
+spool_temp_file(FILE* in, int* lineno, char* id)
+{
+       char line[MAX_LINE_LEN];
+       char* parse;
+       FILE* spool;
+       /* find filename for new file */
+       while(isspace((unsigned char)*id))
+               id++;
+       if(*id == '\0') 
+               fatal_exit("TEMPFILE_CONTENTS must have id, line %d", *lineno);
+       id[strlen(id)-1]=0; /* remove newline */
+       fake_temp_file("_temp_", id, line, sizeof(line));
+       /* open file and spool to it */
+       spool = fopen(line, "w");
+       if(!spool) fatal_exit("could not open %s: %s", line, strerror(errno));
+       fprintf(stderr, "testbound is spooling temp file: %s\n", line);
+       if(!cfg_strlist_insert(&cfgfiles, strdup(line))) 
+               fatal_exit("out of memory");
+       line[sizeof(line)-1] = 0;
+       while(fgets(line, MAX_LINE_LEN-1, in)) {
+               parse = line;
+               (*lineno)++;
+               while(isspace((unsigned char)*parse))
+                       parse++;
+               if(strncmp(parse, "$INCLUDE_TEMPFILE", 17) == 0) {
+                       char l2[MAX_LINE_LEN-30]; /* -30 makes it fit with
+                               a preceding $INCLUDE in the buf line[] */
+                       char* tid = parse+17;
+                       while(isspace((unsigned char)*tid))
+                               tid++;
+                       tid[strlen(tid)-1]=0; /* remove newline */
+                       fake_temp_file("_temp_", tid, l2, sizeof(l2));
+                       snprintf(line, sizeof(line), "$INCLUDE %s\n", l2);
+               }
+               if(strncmp(parse, "TEMPFILE_END", 12) == 0) {
+                       fclose(spool);
+                       return;
+               }
+               fputs(line, spool);
+       }
+       fatal_exit("no TEMPFILE_END in input file");
+}
+
+/** spool autotrust file */
+static void
+spool_auto_file(FILE* in, int* lineno, FILE* cfg, char* id)
+{
+       char line[MAX_LINE_LEN];
+       char* parse;
+       FILE* spool;
+       /* find filename for new file */
+       while(isspace((unsigned char)*id))
+               id++;
+       if(*id == '\0') 
+               fatal_exit("AUTROTRUST_FILE must have id, line %d", *lineno);
+       id[strlen(id)-1]=0; /* remove newline */
+       fake_temp_file("_auto_", id, line, sizeof(line));
+       /* add option for the file */
+       fprintf(cfg, "server:   auto-trust-anchor-file: \"%s\"\n", line);
+       /* open file and spool to it */
+       spool = fopen(line, "w");
+       if(!spool) fatal_exit("could not open %s: %s", line, strerror(errno));
+       fprintf(stderr, "testbound is spooling key file: %s\n", line);
+       if(!cfg_strlist_insert(&cfgfiles, strdup(line))) 
+               fatal_exit("out of memory");
+       line[sizeof(line)-1] = 0;
+       while(fgets(line, MAX_LINE_LEN-1, in)) {
+               parse = line;
+               (*lineno)++;
+               while(isspace((unsigned char)*parse))
+                       parse++;
+               if(strncmp(parse, "AUTOTRUST_END", 13) == 0) {
+                       fclose(spool);
+                       return;
+               }
+               fputs(line, spool);
+       }
+       fatal_exit("no AUTOTRUST_END in input file");
+}
+
+/** process config elements */
+static void
+setup_config(FILE* in, int* lineno, int* pass_argc, char* pass_argv[])
+{
+       char configfile[MAX_LINE_LEN];
+       char line[MAX_LINE_LEN];
+       char* parse;
+       FILE* cfg;
+       fake_temp_file("_cfg", "", configfile, sizeof(configfile));
+       add_opts("-c", pass_argc, pass_argv);
+       add_opts(configfile, pass_argc, pass_argv);
+       cfg = fopen(configfile, "w");
+       if(!cfg) fatal_exit("could not open %s: %s", 
+                       configfile, strerror(errno));
+       if(!cfg_strlist_insert(&cfgfiles, strdup(configfile))) 
+               fatal_exit("out of memory");
+       line[sizeof(line)-1] = 0;
+       /* some basic settings to not pollute the host system */
+       fprintf(cfg, "server:   use-syslog: no\n");
+       fprintf(cfg, "          directory: \"\"\n");
+       fprintf(cfg, "          chroot: \"\"\n");
+       fprintf(cfg, "          username: \"\"\n");
+       fprintf(cfg, "          pidfile: \"\"\n");
+       fprintf(cfg, "          val-log-level: 2\n");
+       fprintf(cfg, "remote-control:   control-enable: no\n");
+       while(fgets(line, MAX_LINE_LEN-1, in)) {
+               parse = line;
+               (*lineno)++;
+               while(isspace((unsigned char)*parse))
+                       parse++;
+               if(!*parse || parse[0] == ';')
+                       continue;
+               if(strncmp(parse, "COMMANDLINE", 11) == 0) {
+                       parse[strlen(parse)-1] = 0; /* strip off \n */
+                       add_opts(parse+11, pass_argc, pass_argv);
+                       continue;
+               }
+               if(strncmp(parse, "AUTOTRUST_FILE", 14) == 0) {
+                       spool_auto_file(in, lineno, cfg, parse+14);
+                       continue;
+               }
+               if(strncmp(parse, "TEMPFILE_NAME", 13) == 0) {
+                       spool_temp_file_name(lineno, cfg, parse+13);
+                       continue;
+               }
+               if(strncmp(parse, "TEMPFILE_CONTENTS", 17) == 0) {
+                       spool_temp_file(in, lineno, parse+17);
+                       continue;
+               }
+               if(strncmp(parse, "CONFIG_END", 10) == 0) {
+                       fclose(cfg);
+                       return;
+               }
+               fputs(line, cfg);
+       }
+       fatal_exit("No CONFIG_END in input file");
+
+}
+
+/** read playback file */
+static struct replay_scenario* 
+setup_playback(const char* filename, int* pass_argc, char* pass_argv[])
+{
+       struct replay_scenario* scen = NULL;
+       int lineno = 0;
+
+       if(filename) {
+               FILE *in = fopen(filename, "rb");
+               if(!in) {
+                       perror(filename);
+                       exit(1);
+               }
+               setup_config(in, &lineno, pass_argc, pass_argv);
+               scen = replay_scenario_read(in, filename, &lineno);
+               fclose(in);
+               if(!scen)
+                       fatal_exit("Could not read: %s", filename);
+       }
+       else fatal_exit("need a playback file (-p)");
+       log_info("Scenario: %s", scen->title);
+       return scen;
+}
+
+/** remove config file at exit */
+void remove_configfile(void)
+{
+       struct config_strlist* p;
+       for(p=cfgfiles; p; p=p->next)
+               unlink(p->str);
+       config_delstrlist(cfgfiles);
+       cfgfiles = NULL;
+}
+
+/**
+ * Main fake event test program. Setup, teardown and report errors.
+ * @param argc: arg count.
+ * @param argv: array of commandline arguments.
+ * @return program failure if test fails.
+ */
+int 
+main(int argc, char* argv[])
+{
+       int c, res;
+       int pass_argc = 0;
+       char* pass_argv[MAXARG];
+       char* playback_file = NULL;
+       int init_optind = optind;
+       char* init_optarg = optarg;
+       struct replay_scenario* scen = NULL;
+
+       /* we do not want the test to depend on the timezone */
+       (void)putenv("TZ=UTC");
+
+       log_init(NULL, 0, NULL);
+       /* determine commandline options for the daemon */
+       pass_argc = 1;
+       pass_argv[0] = "unbound";
+       add_opts("-d", &pass_argc, pass_argv);
+       while( (c=getopt(argc, argv, "12egciho:p:s")) != -1) {
+               switch(c) {
+               case 's':
+                       free(pass_argv[1]);
+                       testbound_selftest();
+                       checklock_stop();
+                       if(log_get_lock()) {
+                               lock_quick_destroy((lock_quick_type*)log_get_lock());
+                       }
+                       exit(0);
+               case '1':
+#ifdef USE_SHA1
+                       printf("SHA1 supported\n");
+                       exit(0);
+#else
+                       printf("SHA1 not supported\n");
+                       exit(1);
+#endif
+                       break;
+               case '2':
+#if (defined(HAVE_EVP_SHA256) || defined(HAVE_NSS) || defined(HAVE_NETTLE)) && defined(USE_SHA2)
+                       printf("SHA256 supported\n");
+                       exit(0);
+#else
+                       printf("SHA256 not supported\n");
+                       exit(1);
+#endif
+                       break;
+               case 'e':
+#if defined(USE_ECDSA)
+                       printf("ECDSA supported\n");
+                       exit(0);
+#else
+                       printf("ECDSA not supported\n");
+                       exit(1);
+#endif
+                       break;
+               case 'g':
+#ifdef USE_GOST
+                       if(sldns_key_EVP_load_gost_id()) {
+                               printf("GOST supported\n");
+                               exit(0);
+                       } else {
+                               printf("GOST not supported\n");
+                               exit(1);
+                       }
+#else
+                       printf("GOST not supported\n");
+                       exit(1);
+#endif
+                       break;
+               case 'c':
+#ifdef CLIENT_SUBNET
+                       printf("CLIENT_SUBNET supported\n");
+                       exit(0);
+#else
+                       printf("CLIENT_SUBNET not supported\n");
+                       exit(1);
+#endif
+                       break;
+               case 'i':
+#ifdef USE_IPSECMOD
+                       printf("IPSECMOD supported\n");
+                       exit(0);
+#else
+                       printf("IPSECMOD not supported\n");
+                       exit(1);
+#endif
+                       break;
+               case 'p':
+                       playback_file = optarg;
+                       break;
+               case 'o':
+                       add_opts(optarg, &pass_argc, pass_argv);
+                       break;
+               case '?':
+               case 'h':
+               default:
+                       testbound_usage();
+                       return 1;
+               }
+       }
+       argc -= optind;
+       argv += optind;
+       if(argc != 0) {
+               testbound_usage();
+               return 1;
+       }
+       log_info("Start of %s testbound program.", PACKAGE_STRING);
+       if(atexit(&remove_configfile) != 0)
+               fatal_exit("atexit() failed: %s", strerror(errno));
+
+       /* setup test environment */
+       scen = setup_playback(playback_file, &pass_argc, pass_argv);
+       /* init fake event backend */
+       fake_event_init(scen);
+
+       pass_argv[pass_argc] = NULL;
+       echo_cmdline(pass_argc, pass_argv);
+
+       /* reset getopt processing */
+       optind = init_optind;
+       optarg = init_optarg;
+
+       /* run the normal daemon */
+       res = daemon_main(pass_argc, pass_argv);
+
+       fake_event_cleanup();
+       for(c=1; c<pass_argc; c++)
+               free(pass_argv[c]);
+       if(res == 0) {
+               log_info("Testbound Exit Success\n");
+               if(log_get_lock()) {
+                       lock_quick_destroy((lock_quick_type*)log_get_lock());
+               }
+#ifdef HAVE_PTHREAD
+               /* dlopen frees its thread state (dlopen of gost engine) */
+               pthread_exit(NULL);
+#endif
+       }
+       return res;
+}
+
+/* fake remote control */
+struct listen_port* daemon_remote_open_ports(struct config_file* 
+       ATTR_UNUSED(cfg))
+{
+       return NULL;
+}
+
+struct daemon_remote* daemon_remote_create(struct config_file* ATTR_UNUSED(cfg))
+{
+       return (struct daemon_remote*)calloc(1,1);
+}
+
+void daemon_remote_delete(struct daemon_remote* rc)
+{
+       free(rc);
+}
+
+void daemon_remote_clear(struct daemon_remote* ATTR_UNUSED(rc))
+{
+       /* nothing */
+}
+
+int daemon_remote_open_accept(struct daemon_remote* ATTR_UNUSED(rc),
+        struct listen_port* ATTR_UNUSED(ports), 
+       struct worker* ATTR_UNUSED(worker))
+{
+       return 1;
+}
+
+int remote_accept_callback(struct comm_point* ATTR_UNUSED(c), 
+       void* ATTR_UNUSED(arg), int ATTR_UNUSED(error),
+        struct comm_reply* ATTR_UNUSED(repinfo))
+{
+       log_assert(0);
+       return 0;
+}
+
+int remote_control_callback(struct comm_point* ATTR_UNUSED(c), 
+       void* ATTR_UNUSED(arg), int ATTR_UNUSED(error),
+        struct comm_reply* ATTR_UNUSED(repinfo))
+{
+       log_assert(0);
+       return 0;
+}
+
+void remote_get_opt_ssl(char* ATTR_UNUSED(str), void* ATTR_UNUSED(arg))
+{
+        log_assert(0);
+}
+
+void wsvc_command_option(const char* ATTR_UNUSED(wopt), 
+       const char* ATTR_UNUSED(cfgfile), int ATTR_UNUSED(v), 
+       int ATTR_UNUSED(c))
+{
+       log_assert(0);
+}
+
+void wsvc_setup_worker(struct worker* ATTR_UNUSED(worker))
+{
+       /* do nothing */
+}
+
+void wsvc_desetup_worker(struct worker* ATTR_UNUSED(worker))
+{
+       /* do nothing */
+}
+
+#ifdef UB_ON_WINDOWS
+void worker_win_stop_cb(int ATTR_UNUSED(fd), short ATTR_UNUSED(ev),
+       void* ATTR_UNUSED(arg))
+{
+       log_assert(0);
+}
+
+void wsvc_cron_cb(void* ATTR_UNUSED(arg))
+{
+       log_assert(0);
+}
+#endif /* UB_ON_WINDOWS */
+
diff --git a/usr.sbin/unbound/testcode/testpkts.c b/usr.sbin/unbound/testcode/testpkts.c
new file mode 100644 (file)
index 0000000..ec0f7fe
--- /dev/null
@@ -0,0 +1,1705 @@
+/*
+ * testpkts. Data file parse for test packets, and query matching.
+ *
+ * Data storage for specially crafted replies for testing purposes.
+ *
+ * (c) NLnet Labs, 2005, 2006, 2007, 2008
+ * See the file LICENSE for the license
+ */
+
+/**
+ * \file
+ * This is a debugging aid. It is not efficient, especially
+ * with a long config file, but it can give any reply to any query.
+ * This can help the developer pre-script replies for queries.
+ *
+ * You can specify a packet RR by RR with header flags to return.
+ *
+ * Missing features:
+ *             - matching content different from reply content.
+ *             - find way to adjust mangled packets?
+ */
+
+#include "config.h"
+struct sockaddr_storage;
+#include <errno.h>
+#include <stdarg.h>
+#include <ctype.h>
+#include "testcode/testpkts.h"
+#include "util/net_help.h"
+#include "sldns/sbuffer.h"
+#include "sldns/rrdef.h"
+#include "sldns/pkthdr.h"
+#include "sldns/str2wire.h"
+#include "sldns/wire2str.h"
+
+/** max size of a packet */
+#define MAX_PACKETLEN 65536
+/** max line length */
+#define MAX_LINE   10240       
+/** string to show in warnings and errors */
+static const char* prog_name = "testpkts";
+
+#ifndef UTIL_LOG_H
+/** verbosity definition for compat */
+enum verbosity_value { NO_VERBOSE=0 };
+#endif
+/** logging routine, provided by caller */
+void verbose(enum verbosity_value lvl, const char* msg, ...) ATTR_FORMAT(printf, 2, 3);
+
+/** print error and exit */
+static void error(const char* msg, ...)
+{
+       va_list args;
+       va_start(args, msg);
+       fprintf(stderr, "%s error: ", prog_name);
+       vfprintf(stderr, msg, args);
+       fprintf(stderr, "\n");
+       fflush(stderr);
+       va_end(args);
+       exit(EXIT_FAILURE);
+}
+
+/** return if string is empty or comment */
+static int isendline(char c)
+{
+       if(c == ';' || c == '#' 
+               || c == '\n' || c == 0)
+               return 1;
+       return 0;
+}
+
+/** true if the string starts with the keyword given. Moves the str ahead. 
+ * @param str: before keyword, afterwards after keyword and spaces.
+ * @param keyword: the keyword to match
+ * @return: true if keyword present. False otherwise, and str unchanged.
+*/
+static int str_keyword(char** str, const char* keyword)
+{
+       size_t len = strlen(keyword);
+       assert(str && keyword);
+       if(strncmp(*str, keyword, len) != 0)
+               return 0;
+       *str += len;
+       while(isspace((unsigned char)**str))
+               (*str)++;
+       return 1;
+}
+
+/** Add reply packet to entry */
+static struct reply_packet*
+entry_add_reply(struct entry* entry) 
+{
+       struct reply_packet* pkt = (struct reply_packet*)malloc(
+               sizeof(struct reply_packet));
+       struct reply_packet ** p = &entry->reply_list;
+       if(!pkt) error("out of memory");
+       pkt->next = NULL;
+       pkt->packet_sleep = 0;
+       pkt->reply_pkt = NULL;
+       pkt->reply_from_hex = NULL;
+       pkt->raw_ednsdata = NULL;
+       /* link at end */
+       while(*p)
+               p = &((*p)->next);
+       *p = pkt;
+       return pkt;
+}
+
+/** parse MATCH line */
+static void matchline(char* line, struct entry* e)
+{
+       char* parse = line;
+       while(*parse) {
+               if(isendline(*parse)) 
+                       return;
+               if(str_keyword(&parse, "opcode")) {
+                       e->match_opcode = 1;
+               } else if(str_keyword(&parse, "qtype")) {
+                       e->match_qtype = 1;
+               } else if(str_keyword(&parse, "qname")) {
+                       e->match_qname = 1;
+               } else if(str_keyword(&parse, "rcode")) {
+                       e->match_rcode = 1;
+               } else if(str_keyword(&parse, "question")) {
+                       e->match_question = 1;
+               } else if(str_keyword(&parse, "answer")) {
+                       e->match_answer = 1;
+               } else if(str_keyword(&parse, "subdomain")) {
+                       e->match_subdomain = 1;
+               } else if(str_keyword(&parse, "all")) {
+                       e->match_all = 1;
+               } else if(str_keyword(&parse, "ttl")) {
+                       e->match_ttl = 1;
+               } else if(str_keyword(&parse, "DO")) {
+                       e->match_do = 1;
+               } else if(str_keyword(&parse, "noedns")) {
+                       e->match_noedns = 1;
+               } else if(str_keyword(&parse, "ednsdata")) {
+                       e->match_ednsdata_raw = 1;
+               } else if(str_keyword(&parse, "UDP")) {
+                       e->match_transport = transport_udp;
+               } else if(str_keyword(&parse, "TCP")) {
+                       e->match_transport = transport_tcp;
+               } else if(str_keyword(&parse, "serial")) {
+                       e->match_serial = 1;
+                       if(*parse != '=' && *parse != ':')
+                               error("expected = or : in MATCH: %s", line);
+                       parse++;
+                       e->ixfr_soa_serial = (uint32_t)strtol(parse, (char**)&parse, 10);
+                       while(isspace((unsigned char)*parse)) 
+                               parse++;
+               } else {
+                       error("could not parse MATCH: '%s'", parse);
+               }
+       }
+}
+
+/** parse REPLY line */
+static void replyline(char* line, uint8_t* reply, size_t reply_len,
+       int* do_flag)
+{
+       char* parse = line;
+       if(reply_len < LDNS_HEADER_SIZE) error("packet too short for header");
+       while(*parse) {
+               if(isendline(*parse)) 
+                       return;
+                       /* opcodes */
+               if(str_keyword(&parse, "QUERY")) {
+                       LDNS_OPCODE_SET(reply, LDNS_PACKET_QUERY);
+               } else if(str_keyword(&parse, "IQUERY")) {
+                       LDNS_OPCODE_SET(reply, LDNS_PACKET_IQUERY);
+               } else if(str_keyword(&parse, "STATUS")) {
+                       LDNS_OPCODE_SET(reply, LDNS_PACKET_STATUS);
+               } else if(str_keyword(&parse, "NOTIFY")) {
+                       LDNS_OPCODE_SET(reply, LDNS_PACKET_NOTIFY);
+               } else if(str_keyword(&parse, "UPDATE")) {
+                       LDNS_OPCODE_SET(reply, LDNS_PACKET_UPDATE);
+                       /* rcodes */
+               } else if(str_keyword(&parse, "NOERROR")) {
+                       LDNS_RCODE_SET(reply, LDNS_RCODE_NOERROR);
+               } else if(str_keyword(&parse, "FORMERR")) {
+                       LDNS_RCODE_SET(reply, LDNS_RCODE_FORMERR);
+               } else if(str_keyword(&parse, "SERVFAIL")) {
+                       LDNS_RCODE_SET(reply, LDNS_RCODE_SERVFAIL);
+               } else if(str_keyword(&parse, "NXDOMAIN")) {
+                       LDNS_RCODE_SET(reply, LDNS_RCODE_NXDOMAIN);
+               } else if(str_keyword(&parse, "NOTIMPL")) {
+                       LDNS_RCODE_SET(reply, LDNS_RCODE_NOTIMPL);
+               } else if(str_keyword(&parse, "REFUSED")) {
+                       LDNS_RCODE_SET(reply, LDNS_RCODE_REFUSED);
+               } else if(str_keyword(&parse, "YXDOMAIN")) {
+                       LDNS_RCODE_SET(reply, LDNS_RCODE_YXDOMAIN);
+               } else if(str_keyword(&parse, "YXRRSET")) {
+                       LDNS_RCODE_SET(reply, LDNS_RCODE_YXRRSET);
+               } else if(str_keyword(&parse, "NXRRSET")) {
+                       LDNS_RCODE_SET(reply, LDNS_RCODE_NXRRSET);
+               } else if(str_keyword(&parse, "NOTAUTH")) {
+                       LDNS_RCODE_SET(reply, LDNS_RCODE_NOTAUTH);
+               } else if(str_keyword(&parse, "NOTZONE")) {
+                       LDNS_RCODE_SET(reply, LDNS_RCODE_NOTZONE);
+                       /* flags */
+               } else if(str_keyword(&parse, "QR")) {
+                       LDNS_QR_SET(reply);
+               } else if(str_keyword(&parse, "AA")) {
+                       LDNS_AA_SET(reply);
+               } else if(str_keyword(&parse, "TC")) {
+                       LDNS_TC_SET(reply);
+               } else if(str_keyword(&parse, "RD")) {
+                       LDNS_RD_SET(reply);
+               } else if(str_keyword(&parse, "CD")) {
+                       LDNS_CD_SET(reply);
+               } else if(str_keyword(&parse, "RA")) {
+                       LDNS_RA_SET(reply);
+               } else if(str_keyword(&parse, "AD")) {
+                       LDNS_AD_SET(reply);
+               } else if(str_keyword(&parse, "DO")) {
+                       *do_flag = 1;
+               } else {
+                       error("could not parse REPLY: '%s'", parse);
+               }
+       }
+}
+
+/** parse ADJUST line */
+static void adjustline(char* line, struct entry* e, 
+       struct reply_packet* pkt)
+{
+       char* parse = line;
+       while(*parse) {
+               if(isendline(*parse)) 
+                       return;
+               if(str_keyword(&parse, "copy_id")) {
+                       e->copy_id = 1;
+               } else if(str_keyword(&parse, "copy_query")) {
+                       e->copy_query = 1;
+               } else if(str_keyword(&parse, "copy_ednsdata_assume_clientsubnet")) {
+                       e->copy_ednsdata_assume_clientsubnet = 1;
+               } else if(str_keyword(&parse, "sleep=")) {
+                       e->sleeptime = (unsigned int) strtol(parse, (char**)&parse, 10);
+                       while(isspace((unsigned char)*parse)) 
+                               parse++;
+               } else if(str_keyword(&parse, "packet_sleep=")) {
+                       pkt->packet_sleep = (unsigned int) strtol(parse, (char**)&parse, 10);
+                       while(isspace((unsigned char)*parse)) 
+                               parse++;
+               } else {
+                       error("could not parse ADJUST: '%s'", parse);
+               }
+       }
+}
+
+/** create new entry */
+static struct entry* new_entry(void)
+{
+       struct entry* e = (struct entry*)malloc(sizeof(struct entry));
+       if(!e) error("out of memory");
+       memset(e, 0, sizeof(*e));
+       e->match_opcode = 0;
+       e->match_qtype = 0;
+       e->match_qname = 0;
+       e->match_rcode = 0;
+       e->match_question = 0;
+       e->match_answer = 0;
+       e->match_subdomain = 0;
+       e->match_all = 0;
+       e->match_ttl = 0;
+       e->match_do = 0;
+       e->match_noedns = 0;
+       e->match_serial = 0;
+       e->ixfr_soa_serial = 0;
+       e->match_transport = transport_any;
+       e->reply_list = NULL;
+       e->copy_id = 0;
+       e->copy_query = 0;
+       e->copy_ednsdata_assume_clientsubnet = 0;
+       e->sleeptime = 0;
+       e->next = NULL;
+       return e;
+}
+
+/**
+ * Converts a hex string to binary data
+ * @param hexstr: string of hex.
+ * @param len: is the length of the string
+ * @param buf: is the buffer to store the result in
+ * @param offset: is the starting position in the result buffer
+ * @param buf_len: is the length of buf.
+ * @return This function returns the length of the result
+ */
+static size_t
+hexstr2bin(char *hexstr, int len, uint8_t *buf, size_t offset, size_t buf_len)
+{
+       char c;
+       int i; 
+       uint8_t int8 = 0;
+       int sec = 0;
+       size_t bufpos = 0;
+       
+       if (len % 2 != 0) {
+               return 0;
+       }
+
+       for (i=0; i<len; i++) {
+               c = hexstr[i];
+
+               /* case insensitive, skip spaces */
+               if (c != ' ') {
+                       if (c >= '0' && c <= '9') {
+                               int8 += c & 0x0f;  
+                       } else if (c >= 'a' && c <= 'z') {
+                               int8 += (c & 0x0f) + 9;   
+                       } else if (c >= 'A' && c <= 'Z') {
+                               int8 += (c & 0x0f) + 9;   
+                       } else {
+                               return 0;
+                       }
+                        
+                       if (sec == 0) {
+                               int8 = int8 << 4;
+                               sec = 1;
+                       } else {
+                               if (bufpos + offset + 1 <= buf_len) {
+                                       buf[bufpos+offset] = int8;
+                                       int8 = 0;
+                                       sec = 0; 
+                                       bufpos++;
+                               } else {
+                                       fprintf(stderr, "Buffer too small in hexstr2bin");
+                               }
+                       }
+               }
+        }
+        return bufpos;
+}
+
+/** convert hex buffer to binary buffer */
+static sldns_buffer *
+hex_buffer2wire(sldns_buffer *data_buffer)
+{
+       sldns_buffer *wire_buffer = NULL;
+       int c;
+       
+       /* stat hack
+        * 0 = normal
+        * 1 = comment (skip to end of line)
+        * 2 = unprintable character found, read binary data directly
+        */
+       size_t data_buf_pos = 0;
+       int state = 0;
+       uint8_t *hexbuf;
+       int hexbufpos = 0;
+       size_t wirelen;
+       uint8_t *data_wire = (uint8_t *) sldns_buffer_begin(data_buffer);
+       uint8_t *wire = (uint8_t*)malloc(MAX_PACKETLEN);
+       if(!wire) error("out of memory");
+       
+       hexbuf = (uint8_t*)malloc(MAX_PACKETLEN);
+       if(!hexbuf) error("out of memory");
+       for (data_buf_pos = 0; data_buf_pos < sldns_buffer_position(data_buffer); data_buf_pos++) {
+               c = (int) data_wire[data_buf_pos];
+               
+               if (state < 2 && !isascii((unsigned char)c)) {
+                       /*verbose("non ascii character found in file: (%d) switching to raw mode\n", c);*/
+                       state = 2;
+               }
+               switch (state) {
+                       case 0:
+                               if (    (c >= '0' && c <= '9') ||
+                                       (c >= 'a' && c <= 'f') ||
+                                       (c >= 'A' && c <= 'F') )
+                               {
+                                       if (hexbufpos >= MAX_PACKETLEN) {
+                                               error("buffer overflow");
+                                               free(hexbuf);
+                                               return 0;
+
+                                       }
+                                       hexbuf[hexbufpos] = (uint8_t) c;
+                                       hexbufpos++;
+                               } else if (c == ';') {
+                                       state = 1;
+                               } else if (c == ' ' || c == '\t' || c == '\n') {
+                                       /* skip whitespace */
+                               } 
+                               break;
+                       case 1:
+                               if (c == '\n' || c == EOF) {
+                                       state = 0;
+                               }
+                               break;
+                       case 2:
+                               if (hexbufpos >= MAX_PACKETLEN) {
+                                       error("buffer overflow");
+                                       free(hexbuf);
+                                       return 0;
+                               }
+                               hexbuf[hexbufpos] = (uint8_t) c;
+                               hexbufpos++;
+                               break;
+               }
+       }
+
+       if (hexbufpos >= MAX_PACKETLEN) {
+               /*verbose("packet size reached\n");*/
+       }
+       
+       /* lenient mode: length must be multiple of 2 */
+       if (hexbufpos % 2 != 0) {
+               if (hexbufpos >= MAX_PACKETLEN) {
+                       error("buffer overflow");
+                       free(hexbuf);
+                       return 0;
+               }
+               hexbuf[hexbufpos] = (uint8_t) '0';
+               hexbufpos++;
+       }
+
+       if (state < 2) {
+               wirelen = hexstr2bin((char *) hexbuf, hexbufpos, wire, 0, MAX_PACKETLEN);
+               wire_buffer = sldns_buffer_new(wirelen);
+               sldns_buffer_new_frm_data(wire_buffer, wire, wirelen);
+       } else {
+               error("Incomplete hex data, not at byte boundary\n");
+       }
+       free(wire);
+       free(hexbuf);
+       return wire_buffer;
+}      
+
+/** parse ORIGIN */
+static void 
+get_origin(const char* name, struct sldns_file_parse_state* pstate, char* parse)
+{
+       /* snip off rest of the text so as to make the parse work in ldns */
+       char* end;
+       char store;
+       int status;
+
+       end=parse;
+       while(!isspace((unsigned char)*end) && !isendline(*end))
+               end++;
+       store = *end;
+       *end = 0;
+       verbose(3, "parsing '%s'\n", parse);
+       status = sldns_str2wire_dname_buf(parse, pstate->origin,
+               &pstate->origin_len);
+       *end = store;
+       if(status != 0)
+               error("%s line %d:\n\t%s: %s", name, pstate->lineno,
+                       sldns_get_errorstr_parse(status), parse);
+}
+
+/** add RR to packet */
+static void add_rr(char* rrstr, uint8_t* pktbuf, size_t pktsize,
+       size_t* pktlen, struct sldns_file_parse_state* pstate,
+       sldns_pkt_section add_section, const char* fname)
+{
+       /* it must be a RR, parse and add to packet. */
+       size_t rr_len = pktsize - *pktlen;
+       size_t dname_len = 0;
+       int status;
+       uint8_t* origin = pstate->origin_len?pstate->origin:0;
+       uint8_t* prev = pstate->prev_rr_len?pstate->prev_rr:0;
+       if(*pktlen > pktsize || *pktlen < LDNS_HEADER_SIZE)
+               error("packet overflow");
+
+       /* parse RR */
+       if(add_section == LDNS_SECTION_QUESTION)
+               status = sldns_str2wire_rr_question_buf(rrstr, pktbuf+*pktlen,
+                       &rr_len, &dname_len, origin, pstate->origin_len,
+                       prev, pstate->prev_rr_len);
+       else status = sldns_str2wire_rr_buf(rrstr, pktbuf+*pktlen, &rr_len,
+                       &dname_len, pstate->default_ttl, origin,
+                       pstate->origin_len, prev, pstate->prev_rr_len);
+       if(status != 0)
+               error("%s line %d:%d %s\n\t%s", fname, pstate->lineno,
+                       LDNS_WIREPARSE_OFFSET(status),
+                       sldns_get_errorstr_parse(status), rrstr);
+       *pktlen += rr_len;
+
+       /* increase RR count */
+       if(add_section == LDNS_SECTION_QUESTION)
+               sldns_write_uint16(pktbuf+4, LDNS_QDCOUNT(pktbuf)+1);
+       else if(add_section == LDNS_SECTION_ANSWER)
+               sldns_write_uint16(pktbuf+6, LDNS_ANCOUNT(pktbuf)+1);
+       else if(add_section == LDNS_SECTION_AUTHORITY)
+               sldns_write_uint16(pktbuf+8, LDNS_NSCOUNT(pktbuf)+1);
+       else if(add_section == LDNS_SECTION_ADDITIONAL)
+               sldns_write_uint16(pktbuf+10, LDNS_ARCOUNT(pktbuf)+1);
+       else error("internal error bad section %d", (int)add_section);
+}
+
+/* add EDNS 4096 opt record */
+static void
+add_edns(uint8_t* pktbuf, size_t pktsize, int do_flag, uint8_t *ednsdata,
+       uint16_t ednslen, size_t* pktlen)
+{
+       uint8_t edns[] = {0x00, /* root label */
+               0x00, LDNS_RR_TYPE_OPT, /* type */
+               0x10, 0x00, /* class is UDPSIZE 4096 */
+               0x00, /* TTL[0] is ext rcode */
+               0x00, /* TTL[1] is edns version */
+               (uint8_t)(do_flag?0x80:0x00), 0x00, /* TTL[2-3] is edns flags, DO */
+               (uint8_t)((ednslen >> 8) & 0xff),
+               (uint8_t)(ednslen  & 0xff), /* rdatalength */
+       };
+       if(*pktlen < LDNS_HEADER_SIZE)
+               return;
+       if(*pktlen + sizeof(edns) + ednslen > pktsize)
+               error("not enough space for EDNS OPT record");
+       memmove(pktbuf+*pktlen, edns, sizeof(edns));
+       memmove(pktbuf+*pktlen+sizeof(edns), ednsdata, ednslen);
+       sldns_write_uint16(pktbuf+10, LDNS_ARCOUNT(pktbuf)+1);
+       *pktlen += (sizeof(edns) + ednslen);
+}
+
+/* Reads one entry from file. Returns entry or NULL on error. */
+struct entry*
+read_entry(FILE* in, const char* name, struct sldns_file_parse_state* pstate,
+       int skip_whitespace)
+{
+       struct entry* current = NULL;
+       char line[MAX_LINE];
+       char* parse;
+       sldns_pkt_section add_section = LDNS_SECTION_QUESTION;
+       struct reply_packet *cur_reply = NULL;
+       int reading_hex = 0;
+       int reading_hex_ednsdata = 0;
+       sldns_buffer* hex_data_buffer = NULL;
+       sldns_buffer* hex_ednsdata_buffer = NULL;
+       uint8_t pktbuf[MAX_PACKETLEN];
+       size_t pktlen = LDNS_HEADER_SIZE;
+       int do_flag = 0; /* DO flag in EDNS */
+       memset(pktbuf, 0, pktlen); /* ID = 0, FLAGS="", and rr counts 0 */
+
+       while(fgets(line, (int)sizeof(line), in) != NULL) {
+               line[MAX_LINE-1] = 0;
+               parse = line;
+               pstate->lineno++;
+
+               while(isspace((unsigned char)*parse))
+                       parse++;
+               /* test for keywords */
+               if(isendline(*parse))
+                       continue; /* skip comment and empty lines */
+               if(str_keyword(&parse, "ENTRY_BEGIN")) {
+                       if(current) {
+                               error("%s line %d: previous entry does not ENTRY_END", 
+                                       name, pstate->lineno);
+                       }
+                       current = new_entry();
+                       current->lineno = pstate->lineno;
+                       cur_reply = entry_add_reply(current);
+                       continue;
+               } else if(str_keyword(&parse, "$ORIGIN")) {
+                       get_origin(name, pstate, parse);
+                       continue;
+               } else if(str_keyword(&parse, "$TTL")) {
+                       pstate->default_ttl = (uint32_t)atoi(parse);
+                       continue;
+               }
+
+               /* working inside an entry */
+               if(!current) {
+                       error("%s line %d: expected ENTRY_BEGIN but got %s", 
+                               name, pstate->lineno, line);
+               }
+               if(str_keyword(&parse, "MATCH")) {
+                       matchline(parse, current);
+               } else if(str_keyword(&parse, "REPLY")) {
+                       replyline(parse, pktbuf, pktlen, &do_flag);
+               } else if(str_keyword(&parse, "ADJUST")) {
+                       adjustline(parse, current, cur_reply);
+               } else if(str_keyword(&parse, "EXTRA_PACKET")) {
+                       /* copy current packet into buffer */
+                       cur_reply->reply_pkt = memdup(pktbuf, pktlen);
+                       cur_reply->reply_len = pktlen;
+                       if(!cur_reply->reply_pkt)
+                               error("out of memory");
+                       cur_reply = entry_add_reply(current);
+                       /* clear for next packet */
+                       pktlen = LDNS_HEADER_SIZE;
+                       memset(pktbuf, 0, pktlen); /* ID = 0, FLAGS="", and rr counts 0 */
+               } else if(str_keyword(&parse, "SECTION")) {
+                       if(str_keyword(&parse, "QUESTION"))
+                               add_section = LDNS_SECTION_QUESTION;
+                       else if(str_keyword(&parse, "ANSWER"))
+                               add_section = LDNS_SECTION_ANSWER;
+                       else if(str_keyword(&parse, "AUTHORITY"))
+                               add_section = LDNS_SECTION_AUTHORITY;
+                       else if(str_keyword(&parse, "ADDITIONAL"))
+                               add_section = LDNS_SECTION_ADDITIONAL;
+                       else error("%s line %d: bad section %s", name, pstate->lineno, parse);
+               } else if(str_keyword(&parse, "HEX_ANSWER_BEGIN")) {
+                       hex_data_buffer = sldns_buffer_new(MAX_PACKETLEN);
+                       reading_hex = 1;
+               } else if(str_keyword(&parse, "HEX_ANSWER_END")) {
+                       if(!reading_hex) {
+                               error("%s line %d: HEX_ANSWER_END read but no HEX_ANSWER_BEGIN keyword seen", name, pstate->lineno);
+                       }
+                       reading_hex = 0;
+                       cur_reply->reply_from_hex = hex_buffer2wire(hex_data_buffer);
+                       sldns_buffer_free(hex_data_buffer);
+                       hex_data_buffer = NULL;
+               } else if(reading_hex) {
+                       sldns_buffer_printf(hex_data_buffer, "%s", line);
+               } else if(str_keyword(&parse, "HEX_EDNSDATA_BEGIN")) {
+                       hex_ednsdata_buffer = sldns_buffer_new(MAX_PACKETLEN);
+                       reading_hex_ednsdata = 1;
+               } else if(str_keyword(&parse, "HEX_EDNSDATA_END")) {
+                       if (!reading_hex_ednsdata) {
+                               error("%s line %d: HEX_EDNSDATA_END read but no"
+                                       "HEX_EDNSDATA_BEGIN keyword seen", name, pstate->lineno);
+                       }
+                       reading_hex_ednsdata = 0;
+                       cur_reply->raw_ednsdata = hex_buffer2wire(hex_ednsdata_buffer);
+                       sldns_buffer_free(hex_ednsdata_buffer);
+                       hex_ednsdata_buffer = NULL;
+               } else if(reading_hex_ednsdata) {
+                       sldns_buffer_printf(hex_ednsdata_buffer, "%s", line);
+               } else if(str_keyword(&parse, "ENTRY_END")) {
+                       if(hex_data_buffer)
+                               sldns_buffer_free(hex_data_buffer);
+                       if(hex_ednsdata_buffer)
+                               sldns_buffer_free(hex_ednsdata_buffer);
+                       if(pktlen != 0) {
+                               if(do_flag || cur_reply->raw_ednsdata) {
+                                       if(cur_reply->raw_ednsdata &&
+                                               sldns_buffer_limit(cur_reply->raw_ednsdata))
+                                               add_edns(pktbuf, sizeof(pktbuf), do_flag,
+                                                       sldns_buffer_begin(cur_reply->raw_ednsdata),
+                                                       (uint16_t)sldns_buffer_limit(cur_reply->raw_ednsdata),
+                                                       &pktlen);
+                                       else
+                                               add_edns(pktbuf, sizeof(pktbuf), do_flag,
+                                                       NULL, 0, &pktlen);
+                               }
+                               cur_reply->reply_pkt = memdup(pktbuf, pktlen);
+                               cur_reply->reply_len = pktlen;
+                               if(!cur_reply->reply_pkt)
+                                       error("out of memory");
+                       }
+                       return current;
+               } else {
+                       add_rr(skip_whitespace?parse:line, pktbuf,
+                               sizeof(pktbuf), &pktlen, pstate, add_section,
+                               name);
+               }
+
+       }
+       if(reading_hex) {
+               error("%s: End of file reached while still reading hex, "
+                       "missing HEX_ANSWER_END\n", name);
+       }
+       if(reading_hex_ednsdata) {
+               error("%s: End of file reached while still reading edns data, "
+                       "missing HEX_EDNSDATA_END\n", name);
+       }
+       if(current) {
+               error("%s: End of file reached while reading entry. "
+                       "missing ENTRY_END\n", name);
+       }
+       return 0;
+}
+
+/* reads the canned reply file and returns a list of structs */
+struct entry* 
+read_datafile(const char* name, int skip_whitespace)
+{
+       struct entry* list = NULL;
+       struct entry* last = NULL;
+       struct entry* current = NULL;
+       FILE *in;
+       struct sldns_file_parse_state pstate;
+       int entry_num = 0;
+       memset(&pstate, 0, sizeof(pstate));
+
+       if((in=fopen(name, "r")) == NULL) {
+               error("could not open file %s: %s", name, strerror(errno));
+       }
+
+       while((current = read_entry(in, name, &pstate, skip_whitespace)))
+       {
+               if(last)
+                       last->next = current;
+               else    list = current;
+               last = current;
+               entry_num ++;
+       }
+       verbose(1, "%s: Read %d entries\n", prog_name, entry_num);
+
+       fclose(in);
+       return list;
+}
+
+/** get qtype from packet */
+static sldns_rr_type get_qtype(uint8_t* pkt, size_t pktlen)
+{
+       uint8_t* d;
+       size_t dl, sl=0;
+       char* snull = NULL;
+       if(pktlen < LDNS_HEADER_SIZE)
+               return 0;
+       if(LDNS_QDCOUNT(pkt) == 0)
+               return 0;
+       /* skip over dname with dname-scan routine */
+       d = pkt+LDNS_HEADER_SIZE;
+       dl = pktlen-LDNS_HEADER_SIZE;
+       (void)sldns_wire2str_dname_scan(&d, &dl, &snull, &sl, pkt, pktlen);
+       if(dl < 2)
+               return 0;
+       return sldns_read_uint16(d);
+}
+
+/** get qtype from packet */
+static size_t get_qname_len(uint8_t* pkt, size_t pktlen)
+{
+       uint8_t* d;
+       size_t dl, sl=0;
+       char* snull = NULL;
+       if(pktlen < LDNS_HEADER_SIZE)
+               return 0;
+       if(LDNS_QDCOUNT(pkt) == 0)
+               return 0;
+       /* skip over dname with dname-scan routine */
+       d = pkt+LDNS_HEADER_SIZE;
+       dl = pktlen-LDNS_HEADER_SIZE;
+       (void)sldns_wire2str_dname_scan(&d, &dl, &snull, &sl, pkt, pktlen);
+       return pktlen-dl-LDNS_HEADER_SIZE;
+}
+
+/** returns owner from packet */
+static uint8_t* get_qname(uint8_t* pkt, size_t pktlen)
+{
+       if(pktlen < LDNS_HEADER_SIZE)
+               return NULL;
+       if(LDNS_QDCOUNT(pkt) == 0)
+               return NULL;
+       return pkt+LDNS_HEADER_SIZE;
+}
+
+/** returns opcode from packet */
+static int get_opcode(uint8_t* pkt, size_t pktlen)
+{
+       if(pktlen < LDNS_HEADER_SIZE)
+               return 0;
+       return (int)LDNS_OPCODE_WIRE(pkt);
+}
+
+/** returns rcode from packet */
+static int get_rcode(uint8_t* pkt, size_t pktlen)
+{
+       if(pktlen < LDNS_HEADER_SIZE)
+               return 0;
+       return (int)LDNS_RCODE_WIRE(pkt);
+}
+
+/** get authority section SOA serial value */
+static uint32_t get_serial(uint8_t* p, size_t plen)
+{
+       uint8_t* walk = p;
+       size_t walk_len = plen, sl=0;
+       char* snull = NULL;
+       uint16_t i;
+
+       if(walk_len < LDNS_HEADER_SIZE)
+               return 0;
+       walk += LDNS_HEADER_SIZE;
+       walk_len -= LDNS_HEADER_SIZE;
+
+       /* skip other records with wire2str_scan */
+       for(i=0; i < LDNS_QDCOUNT(p); i++)
+               (void)sldns_wire2str_rrquestion_scan(&walk, &walk_len,
+                       &snull, &sl, p, plen);
+       for(i=0; i < LDNS_ANCOUNT(p); i++)
+               (void)sldns_wire2str_rr_scan(&walk, &walk_len, &snull, &sl,
+                       p, plen);
+
+       /* walk through authority section */
+       for(i=0; i < LDNS_NSCOUNT(p); i++) {
+               /* if this is SOA then get serial, skip compressed dname */
+               uint8_t* dstart = walk;
+               size_t dlen = walk_len;
+               (void)sldns_wire2str_dname_scan(&dstart, &dlen, &snull, &sl,
+                       p, plen);
+               if(dlen >= 2 && sldns_read_uint16(dstart) == LDNS_RR_TYPE_SOA) {
+                       /* skip type, class, TTL, rdatalen */
+                       if(dlen < 10)
+                               return 0;
+                       if(dlen < 10 + (size_t)sldns_read_uint16(dstart+8))
+                               return 0;
+                       dstart += 10;
+                       dlen -= 10;
+                       /* check third rdf */
+                       (void)sldns_wire2str_dname_scan(&dstart, &dlen, &snull,
+                               &sl, p, plen);
+                       (void)sldns_wire2str_dname_scan(&dstart, &dlen, &snull,
+                               &sl, p, plen);
+                       if(dlen < 4)
+                               return 0;
+                       verbose(3, "found serial %u in msg. ",
+                               (int)sldns_read_uint32(dstart));
+                       return sldns_read_uint32(dstart);
+               }
+               /* move to next RR */
+               (void)sldns_wire2str_rr_scan(&walk, &walk_len, &snull, &sl,
+                       p, plen);
+       }
+       return 0;
+}
+
+/** get ptr to EDNS OPT record (and remaining length); behind the type u16 */
+static int
+pkt_find_edns_opt(uint8_t** p, size_t* plen)
+{
+       /* walk over the packet with scan routines */
+       uint8_t* w = *p;
+       size_t wlen = *plen, sl=0;
+       char* snull = NULL;
+       uint16_t i;
+
+       if(wlen < LDNS_HEADER_SIZE)
+               return 0;
+       w += LDNS_HEADER_SIZE;
+       wlen -= LDNS_HEADER_SIZE;
+
+       /* skip other records with wire2str_scan */
+       for(i=0; i < LDNS_QDCOUNT(*p); i++)
+               (void)sldns_wire2str_rrquestion_scan(&w, &wlen, &snull, &sl,
+                       *p, *plen);
+       for(i=0; i < LDNS_ANCOUNT(*p); i++)
+               (void)sldns_wire2str_rr_scan(&w, &wlen, &snull, &sl, *p, *plen);
+       for(i=0; i < LDNS_NSCOUNT(*p); i++)
+               (void)sldns_wire2str_rr_scan(&w, &wlen, &snull, &sl, *p, *plen);
+
+       /* walk through additional section */
+       for(i=0; i < LDNS_ARCOUNT(*p); i++) {
+               /* if this is OPT then done */
+               uint8_t* dstart = w;
+               size_t dlen = wlen;
+               (void)sldns_wire2str_dname_scan(&dstart, &dlen, &snull, &sl,
+                       *p, *plen);
+               if(dlen >= 2 && sldns_read_uint16(dstart) == LDNS_RR_TYPE_OPT) {
+                       *p = dstart+2;
+                       *plen = dlen-2;
+                       return 1;
+               }
+               /* move to next RR */
+               (void)sldns_wire2str_rr_scan(&w, &wlen, &snull, &sl, *p, *plen);
+       }
+       return 0;
+}
+
+/** return true if the packet has EDNS OPT record */
+static int
+get_has_edns(uint8_t* pkt, size_t len)
+{
+       /* use arguments as temporary variables */
+       return pkt_find_edns_opt(&pkt, &len);
+}
+
+/** return true if the DO flag is set */
+static int
+get_do_flag(uint8_t* pkt, size_t len)
+{
+       uint16_t edns_bits;
+       uint8_t* walk = pkt;
+       size_t walk_len = len;
+       if(!pkt_find_edns_opt(&walk, &walk_len)) {
+               return 0;
+       }
+       if(walk_len < 6)
+               return 0; /* malformed */
+       edns_bits = sldns_read_uint16(walk+4);
+       return (int)(edns_bits&LDNS_EDNS_MASK_DO_BIT);
+}
+
+/** zero TTLs in packet */
+static void
+zerottls(uint8_t* pkt, size_t pktlen)
+{
+       uint8_t* walk = pkt;
+       size_t walk_len = pktlen, sl=0;
+       char* snull = NULL;
+       uint16_t i;
+       uint16_t num = LDNS_ANCOUNT(pkt)+LDNS_NSCOUNT(pkt)+LDNS_ARCOUNT(pkt);
+       if(walk_len < LDNS_HEADER_SIZE)
+               return;
+       walk += LDNS_HEADER_SIZE;
+       walk_len -= LDNS_HEADER_SIZE;
+       for(i=0; i < LDNS_QDCOUNT(pkt); i++)
+               (void)sldns_wire2str_rrquestion_scan(&walk, &walk_len,
+                       &snull, &sl, pkt, pktlen);
+       for(i=0; i < num; i++) {
+               /* wipe TTL */
+               uint8_t* dstart = walk;
+               size_t dlen = walk_len;
+               (void)sldns_wire2str_dname_scan(&dstart, &dlen, &snull, &sl,
+                       pkt, pktlen);
+               if(dlen < 8)
+                       return;
+               sldns_write_uint32(dstart+4, 0);
+               /* go to next RR */
+               (void)sldns_wire2str_rr_scan(&walk, &walk_len, &snull, &sl,
+                       pkt, pktlen);
+       }
+}
+
+/** get one line (\n) from a string, move next to after the \n, zero \n */
+static int
+get_line(char** s, char** n)
+{
+       /* at end of string? end */
+       if(*n == NULL || **n == 0)
+               return 0;
+       /* result starts at next string */
+       *s = *n;
+       /* find \n after that */
+       *n = strchr(*s, '\n');
+       if(*n && **n != 0) {
+               /* terminate line */
+               (*n)[0] = 0;
+               (*n)++;
+       }
+       return 1;
+}
+
+/** match two RR sections without ordering */
+static int
+match_noloc_section(char** q, char** nq, char** p, char** np, uint16_t num)
+{
+       /* for max number of RRs in packet */
+       const uint16_t numarray = 3000;
+       char* qlines[numarray], *plines[numarray];
+       uint16_t i, j, numq=0, nump=0;
+       if(num > numarray) fatal_exit("too many RRs");
+       /* gather lines */
+       for(i=0; i<num; i++) {
+               get_line(q, nq);
+               get_line(p, np);
+               qlines[numq++] = *q;
+               plines[nump++] = *p;
+       }
+       /* see if they are all present in the other */
+       for(i=0; i<num; i++) {
+               int found = 0;
+               for(j=0; j<num; j++) {
+                       if(strcmp(qlines[i], plines[j]) == 0) {
+                               found = 1;
+                               break;
+                       }
+               }
+               if(!found) {
+                       verbose(3, "comparenoloc: failed for %s", qlines[i]);
+                       return 0;
+               }
+       }
+       return 1;
+}
+
+/** match two strings for unordered equality of RRs and everything else */
+static int
+match_noloc(char* q, char* p, uint8_t* q_pkt, size_t q_pkt_len,
+       uint8_t* p_pkt, size_t p_pkt_len)
+{
+       char* nq = q, *np = p;
+       /* if no header, compare bytes */
+       if(p_pkt_len < LDNS_HEADER_SIZE || q_pkt_len < LDNS_HEADER_SIZE) {
+               if(p_pkt_len != q_pkt_len) return 0;
+               return memcmp(p, q, p_pkt_len);
+       }
+       /* compare RR counts */
+       if(LDNS_QDCOUNT(p_pkt) != LDNS_QDCOUNT(q_pkt))
+               return 0;
+       if(LDNS_ANCOUNT(p_pkt) != LDNS_ANCOUNT(q_pkt))
+               return 0;
+       if(LDNS_NSCOUNT(p_pkt) != LDNS_NSCOUNT(q_pkt))
+               return 0;
+       if(LDNS_ARCOUNT(p_pkt) != LDNS_ARCOUNT(q_pkt))
+               return 0;
+       /* get a line from both; compare; at sections do section */
+       get_line(&q, &nq);
+       get_line(&p, &np);
+       if(strcmp(q, p) != 0) {
+               /* header line opcode, rcode, id */
+               return 0;
+       }
+       get_line(&q, &nq);
+       get_line(&p, &np);
+       if(strcmp(q, p) != 0) {
+               /* header flags, rr counts */
+               return 0;
+       }
+       /* ;; QUESTION SECTION */
+       get_line(&q, &nq);
+       get_line(&p, &np);
+       if(strcmp(q, p) != 0) return 0;
+       if(!match_noloc_section(&q, &nq, &p, &np, LDNS_QDCOUNT(p_pkt)))
+               return 0;
+
+       /* empty line and ;; ANSWER SECTION */
+       get_line(&q, &nq);
+       get_line(&p, &np);
+       if(strcmp(q, p) != 0) return 0;
+       get_line(&q, &nq);
+       get_line(&p, &np);
+       if(strcmp(q, p) != 0) return 0;
+       if(!match_noloc_section(&q, &nq, &p, &np, LDNS_ANCOUNT(p_pkt)))
+               return 0;
+
+       /* empty line and ;; AUTHORITY SECTION */
+       get_line(&q, &nq);
+       get_line(&p, &np);
+       if(strcmp(q, p) != 0) return 0;
+       get_line(&q, &nq);
+       get_line(&p, &np);
+       if(strcmp(q, p) != 0) return 0;
+       if(!match_noloc_section(&q, &nq, &p, &np, LDNS_NSCOUNT(p_pkt)))
+               return 0;
+
+       /* empty line and ;; ADDITIONAL SECTION */
+       get_line(&q, &nq);
+       get_line(&p, &np);
+       if(strcmp(q, p) != 0) return 0;
+       get_line(&q, &nq);
+       get_line(&p, &np);
+       if(strcmp(q, p) != 0) return 0;
+       if(!match_noloc_section(&q, &nq, &p, &np, LDNS_ARCOUNT(p_pkt)))
+               return 0;
+
+       return 1;
+}
+
+/** lowercase domain name - does not follow compression pointers */
+static void lowercase_dname(uint8_t** p, size_t* remain)
+{
+       unsigned i, llen;
+       if(*remain == 0) return;
+       while(**p != 0) {
+               /* compressed? */
+               if((**p & 0xc0) == 0xc0) {
+                       *p += 2;
+                       *remain -= 2;
+                       return;
+               }
+               llen = (unsigned int)**p;
+               *p += 1;
+               *remain -= 1;
+               if(*remain < llen)
+                       llen = (unsigned int)*remain;
+               for(i=0; i<llen; i++) {
+                       (*p)[i] = (uint8_t)tolower((int)(*p)[i]);
+               }
+               *p += llen;
+               *remain -= llen;
+               if(*remain == 0) return;
+       }
+       /* skip root label */
+       *p += 1;
+       *remain -= 1;
+}
+
+/** lowercase rdata of type */
+static void lowercase_rdata(uint8_t** p, size_t* remain,
+       uint16_t rdatalen, uint16_t t)
+{
+       const sldns_rr_descriptor *desc = sldns_rr_descript(t);
+       uint8_t dname_count = 0;
+       size_t i = 0;
+       size_t rdataremain = rdatalen;
+       if(!desc) {
+               /* unknown type */
+               *p += rdatalen;
+               *remain -= rdatalen;
+               return;
+       }
+       while(dname_count < desc->_dname_count) {
+               sldns_rdf_type f = sldns_rr_descriptor_field_type(desc, i++);
+               if(f == LDNS_RDF_TYPE_DNAME) {
+                       lowercase_dname(p, &rdataremain);
+                       dname_count++;
+               } else if(f == LDNS_RDF_TYPE_STR) {
+                       uint8_t len;
+                       if(rdataremain == 0) return;
+                       len = **p;
+                       *p += len+1;
+                       rdataremain -= len+1;
+               } else {
+                       int len = 0;
+                       switch(f) {
+                       case LDNS_RDF_TYPE_CLASS:
+                       case LDNS_RDF_TYPE_ALG:
+                       case LDNS_RDF_TYPE_INT8:
+                               len = 1;
+                               break;
+                       case LDNS_RDF_TYPE_INT16:
+                       case LDNS_RDF_TYPE_TYPE:
+                       case LDNS_RDF_TYPE_CERT_ALG:
+                               len = 2;
+                               break;
+                       case LDNS_RDF_TYPE_INT32:
+                       case LDNS_RDF_TYPE_TIME:
+                       case LDNS_RDF_TYPE_A:
+                       case LDNS_RDF_TYPE_PERIOD:
+                               len = 4;
+                               break;
+                       case LDNS_RDF_TYPE_TSIGTIME:
+                               len = 6;
+                               break;
+                       case LDNS_RDF_TYPE_AAAA:
+                               len = 16;
+                               break;
+                       default: error("bad rdf type in lowercase %d", (int)f);
+                       }
+                       *p += len;
+                       rdataremain -= len;
+               }
+       }
+       /* skip remainder of rdata */
+       *p += rdataremain;
+       *remain -= rdatalen;
+}
+
+/** lowercase all names in the message */
+static void lowercase_pkt(uint8_t* pkt, size_t pktlen)
+{
+       uint16_t i;
+       uint8_t* p = pkt;
+       size_t remain = pktlen;
+       uint16_t t, rdatalen;
+       if(pktlen < LDNS_HEADER_SIZE)
+               return;
+       p += LDNS_HEADER_SIZE;
+       remain -= LDNS_HEADER_SIZE;
+       for(i=0; i<LDNS_QDCOUNT(pkt); i++) {
+               lowercase_dname(&p, &remain);
+               if(remain < 4) return;
+               p += 4;
+               remain -= 4;
+       }
+       for(i=0; i<LDNS_ANCOUNT(pkt)+LDNS_NSCOUNT(pkt)+LDNS_ARCOUNT(pkt); i++) {
+               lowercase_dname(&p, &remain);
+               if(remain < 10) return;
+               t = sldns_read_uint16(p);
+               rdatalen = sldns_read_uint16(p+8);
+               p += 10;
+               remain -= 10;
+               if(remain < rdatalen) return;
+               lowercase_rdata(&p, &remain, rdatalen, t);
+       }
+}
+
+/** match question section of packet */
+static int
+match_question(uint8_t* q, size_t qlen, uint8_t* p, size_t plen, int mttl)
+{
+       char* qstr, *pstr, *s, *qcmpstr, *pcmpstr;
+       uint8_t* qb = q, *pb = p;
+       int r;
+       /* zero TTLs */
+       qb = memdup(q, qlen);
+       pb = memdup(p, plen);
+       if(!qb || !pb) error("out of memory");
+       if(!mttl) {
+               zerottls(qb, qlen);
+               zerottls(pb, plen);
+       }
+       lowercase_pkt(qb, qlen);
+       lowercase_pkt(pb, plen);
+       qstr = sldns_wire2str_pkt(qb, qlen);
+       pstr = sldns_wire2str_pkt(pb, plen);
+       if(!qstr || !pstr) error("cannot pkt2string");
+
+       /* remove before ;; QUESTION */
+       s = strstr(qstr, ";; QUESTION SECTION");
+       qcmpstr = s;
+       s = strstr(pstr, ";; QUESTION SECTION");
+       pcmpstr = s;
+       if(!qcmpstr && !pcmpstr) {
+               free(qstr);
+               free(pstr);
+               free(qb);
+               free(pb);
+               return 1;
+       }
+       if(!qcmpstr || !pcmpstr) {
+               free(qstr);
+               free(pstr);
+               free(qb);
+               free(pb);
+               return 0;
+       }
+       
+       /* remove after answer section, (;; AUTH, ;; ADD, ;; MSG size ..) */
+       s = strstr(qcmpstr, ";; ANSWER SECTION");
+       if(!s) s = strstr(qcmpstr, ";; AUTHORITY SECTION");
+       if(!s) s = strstr(qcmpstr, ";; ADDITIONAL SECTION");
+       if(!s) s = strstr(qcmpstr, ";; MSG SIZE");
+       if(s) *s = 0;
+       s = strstr(pcmpstr, ";; ANSWER SECTION");
+       if(!s) s = strstr(pcmpstr, ";; AUTHORITY SECTION");
+       if(!s) s = strstr(pcmpstr, ";; ADDITIONAL SECTION");
+       if(!s) s = strstr(pcmpstr, ";; MSG SIZE");
+       if(s) *s = 0;
+
+       r = (strcmp(qcmpstr, pcmpstr) == 0);
+
+       if(!r) {
+               verbose(3, "mismatch question section '%s' and '%s'",
+                       qcmpstr, pcmpstr);
+       }
+
+       free(qstr);
+       free(pstr);
+       free(qb);
+       free(pb);
+       return r;
+}
+
+/** match answer section of packet */
+static int
+match_answer(uint8_t* q, size_t qlen, uint8_t* p, size_t plen, int mttl)
+{
+       char* qstr, *pstr, *s, *qcmpstr, *pcmpstr;
+       uint8_t* qb = q, *pb = p;
+       int r;
+       /* zero TTLs */
+       qb = memdup(q, qlen);
+       pb = memdup(p, plen);
+       if(!qb || !pb) error("out of memory");
+       if(!mttl) {
+               zerottls(qb, qlen);
+               zerottls(pb, plen);
+       }
+       lowercase_pkt(qb, qlen);
+       lowercase_pkt(pb, plen);
+       qstr = sldns_wire2str_pkt(qb, qlen);
+       pstr = sldns_wire2str_pkt(pb, plen);
+       if(!qstr || !pstr) error("cannot pkt2string");
+
+       /* remove before ;; ANSWER */
+       s = strstr(qstr, ";; ANSWER SECTION");
+       qcmpstr = s;
+       s = strstr(pstr, ";; ANSWER SECTION");
+       pcmpstr = s;
+       if(!qcmpstr && !pcmpstr) {
+               free(qstr);
+               free(pstr);
+               free(qb);
+               free(pb);
+               return 1;
+       }
+       if(!qcmpstr || !pcmpstr) {
+               free(qstr);
+               free(pstr);
+               free(qb);
+               free(pb);
+               return 0;
+       }
+       
+       /* remove after answer section, (;; AUTH, ;; ADD, ;; MSG size ..) */
+       s = strstr(qcmpstr, ";; AUTHORITY SECTION");
+       if(!s) s = strstr(qcmpstr, ";; ADDITIONAL SECTION");
+       if(!s) s = strstr(qcmpstr, ";; MSG SIZE");
+       if(s) *s = 0;
+       s = strstr(pcmpstr, ";; AUTHORITY SECTION");
+       if(!s) s = strstr(pcmpstr, ";; ADDITIONAL SECTION");
+       if(!s) s = strstr(pcmpstr, ";; MSG SIZE");
+       if(s) *s = 0;
+
+       r = (strcmp(qcmpstr, pcmpstr) == 0);
+
+       if(!r) {
+               verbose(3, "mismatch answer section '%s' and '%s'",
+                       qcmpstr, pcmpstr);
+       }
+
+       free(qstr);
+       free(pstr);
+       free(qb);
+       free(pb);
+       return r;
+}
+
+/** match all of the packet */
+int
+match_all(uint8_t* q, size_t qlen, uint8_t* p, size_t plen, int mttl,
+       int noloc)
+{
+       char* qstr, *pstr;
+       uint8_t* qb = q, *pb = p;
+       int r;
+       /* zero TTLs */
+       qb = memdup(q, qlen);
+       pb = memdup(p, plen);
+       if(!qb || !pb) error("out of memory");
+       if(!mttl) {
+               zerottls(qb, qlen);
+               zerottls(pb, plen);
+       }
+       lowercase_pkt(qb, qlen);
+       lowercase_pkt(pb, plen);
+       qstr = sldns_wire2str_pkt(qb, qlen);
+       pstr = sldns_wire2str_pkt(pb, plen);
+       if(!qstr || !pstr) error("cannot pkt2string");
+       r = (strcmp(qstr, pstr) == 0);
+       if(!r) {
+               /* remove ;; MSG SIZE (at end of string) */
+               char* s = strstr(qstr, ";; MSG SIZE");
+               if(s) *s=0;
+               s = strstr(pstr, ";; MSG SIZE");
+               if(s) *s=0;
+               r = (strcmp(qstr, pstr) == 0);
+               if(!r && !noloc) {
+                       /* we are going to fail see if it is because of EDNS */
+                       char* a = strstr(qstr, "; EDNS");
+                       char* b = strstr(pstr, "; EDNS");
+                       if( (a&&!b) || (b&&!a) ) {
+                               verbose(3, "mismatch in EDNS\n");
+                       }
+               }
+       }
+       if(!r && noloc) {
+               /* check for reordered sections */
+               r = match_noloc(qstr, pstr, q, qlen, p, plen);
+       }
+       if(!r) {
+               verbose(3, "mismatch pkt '%s' and '%s'", qstr, pstr);
+       }
+       free(qstr);
+       free(pstr);
+       free(qb);
+       free(pb);
+       return r;
+}
+
+/** see if domain names are equal */
+static int equal_dname(uint8_t* q, size_t qlen, uint8_t* p, size_t plen)
+{
+       uint8_t* qn = get_qname(q, qlen);
+       uint8_t* pn = get_qname(p, plen);
+       char qs[512], ps[512];
+       size_t qslen = sizeof(qs), pslen = sizeof(ps);
+       char* qss = qs, *pss = ps;
+       if(!qn || !pn)
+               return 0;
+       (void)sldns_wire2str_dname_scan(&qn, &qlen, &qss, &qslen, q, qlen);
+       (void)sldns_wire2str_dname_scan(&pn, &plen, &pss, &pslen, p, plen);
+       return (strcmp(qs, ps) == 0);
+}
+
+/** see if domain names are subdomain q of p */
+static int subdomain_dname(uint8_t* q, size_t qlen, uint8_t* p, size_t plen)
+{
+       /* we use the tostring routines so as to test unbound's routines
+        * with something else */
+       uint8_t* qn = get_qname(q, qlen);
+       uint8_t* pn = get_qname(p, plen);
+       char qs[5120], ps[5120];
+       size_t qslen = sizeof(qs), pslen = sizeof(ps);
+       char* qss = qs, *pss = ps;
+       if(!qn || !pn)
+               return 0;
+       /* decompresses domain names */
+       (void)sldns_wire2str_dname_scan(&qn, &qlen, &qss, &qslen, q, qlen);
+       (void)sldns_wire2str_dname_scan(&pn, &plen, &pss, &pslen, p, plen);
+       /* same: false, (strict subdomain check)??? */
+       if(strcmp(qs, ps) == 0)
+               return 1;
+       /* qs must end in ps, at a dot, without \ in front */
+       qslen = strlen(qs);
+       pslen = strlen(ps);
+       if(qslen > pslen && strcmp(qs + (qslen-pslen), ps) == 0 &&
+               qslen + 2 >= pslen && /* space for label and dot */
+               qs[qslen-pslen-1] == '.') {
+               unsigned int slashcount = 0;
+               size_t i = qslen-pslen-2;
+               while(i>0 && qs[i]=='\\') {
+                       i++;
+                       slashcount++;
+               }
+               if(slashcount%1 == 1) return 0; /* . preceded by \ */
+               return 1;
+       }
+       return 0;
+}
+
+/** Match OPT RDATA (not the EDNS payload size or flags) */
+static int
+match_ednsdata(uint8_t* q, size_t qlen, uint8_t* p, size_t plen)
+{
+       uint8_t* walk_q = q;
+       size_t walk_qlen = qlen;
+       uint8_t* walk_p = p;
+       size_t walk_plen = plen;
+
+       if(!pkt_find_edns_opt(&walk_q, &walk_qlen))
+               walk_qlen = 0;
+       if(!pkt_find_edns_opt(&walk_p, &walk_plen))
+               walk_plen = 0;
+
+       /* class + ttl + rdlen = 8 */
+       if(walk_qlen <= 8 && walk_plen <= 8) {
+               verbose(3, "NO edns opt, move on");
+               return 1;
+       }
+       if(walk_qlen != walk_plen)
+               return 0;
+
+       return (memcmp(walk_p+8, walk_q+8, walk_qlen-8) == 0);
+}
+
+/* finds entry in list, or returns NULL */
+struct entry* 
+find_match(struct entry* entries, uint8_t* query_pkt, size_t len,
+       enum transport_type transport)
+{
+       struct entry* p = entries;
+       uint8_t* reply;
+       size_t rlen;
+       for(p=entries; p; p=p->next) {
+               verbose(3, "comparepkt: ");
+               reply = p->reply_list->reply_pkt;
+               rlen = p->reply_list->reply_len;
+               if(p->match_opcode && get_opcode(query_pkt, len) != 
+                       get_opcode(reply, rlen)) {
+                       verbose(3, "bad opcode\n");
+                       continue;
+               }
+               if(p->match_qtype && get_qtype(query_pkt, len) !=
+                       get_qtype(reply, rlen)) {
+                       verbose(3, "bad qtype %d %d\n", get_qtype(query_pkt, len), get_qtype(reply, rlen));
+                       continue;
+               }
+               if(p->match_qname) {
+                       if(!equal_dname(query_pkt, len, reply, rlen)) {
+                               verbose(3, "bad qname\n");
+                               continue;
+                       }
+               }
+               if(p->match_rcode) {
+                       if(get_rcode(query_pkt, len) != get_rcode(reply, rlen)) {
+                               char *r1 = sldns_wire2str_rcode(get_rcode(query_pkt, len));
+                               char *r2 = sldns_wire2str_rcode(get_rcode(reply, rlen));
+                               verbose(3, "bad rcode %s instead of %s\n",
+                                       r1, r2);
+                               free(r1);
+                               free(r2);
+                               continue;
+                       }
+               }
+               if(p->match_question) {
+                       if(!match_question(query_pkt, len, reply, rlen,
+                               (int)p->match_ttl)) {
+                               verbose(3, "bad question section\n");
+                               continue;
+                       }
+               }
+               if(p->match_answer) {
+                       if(!match_answer(query_pkt, len, reply, rlen,
+                               (int)p->match_ttl)) {
+                               verbose(3, "bad answer section\n");
+                               continue;
+                       }
+               }
+               if(p->match_subdomain) {
+                       if(!subdomain_dname(query_pkt, len, reply, rlen)) {
+                               verbose(3, "bad subdomain\n");
+                               continue;
+                       }
+               }
+               if(p->match_serial && get_serial(query_pkt, len) != p->ixfr_soa_serial) {
+                               verbose(3, "bad serial\n");
+                               continue;
+               }
+               if(p->match_do && !get_do_flag(query_pkt, len)) {
+                       verbose(3, "no DO bit set\n");
+                       continue;
+               }
+               if(p->match_noedns && get_has_edns(query_pkt, len)) {
+                       verbose(3, "bad; EDNS OPT present\n");
+                       continue;
+               }
+               if(p->match_ednsdata_raw && 
+                               !match_ednsdata(query_pkt, len, reply, rlen)) {
+                       verbose(3, "bad EDNS data match.\n");
+                       continue;
+               }
+               if(p->match_transport != transport_any && p->match_transport != transport) {
+                       verbose(3, "bad transport\n");
+                       continue;
+               }
+               if(p->match_all && !match_all(query_pkt, len, reply, rlen,
+                       (int)p->match_ttl, 0)) {
+                       verbose(3, "bad allmatch\n");
+                       continue;
+               }
+               verbose(3, "match!\n");
+               return p;
+       }
+       return NULL;
+}
+
+void
+adjust_packet(struct entry* match, uint8_t** answer_pkt, size_t *answer_len,
+       uint8_t* query_pkt, size_t query_len)
+{
+       uint8_t* orig = *answer_pkt;
+       size_t origlen = *answer_len;
+       uint8_t* res;
+       size_t reslen;
+
+       /* perform the copy; if possible; must be uncompressed */
+       if(match->copy_query && origlen >= LDNS_HEADER_SIZE &&
+               query_len >= LDNS_HEADER_SIZE && LDNS_QDCOUNT(query_pkt)!=0
+               && LDNS_QDCOUNT(orig)==0) {
+               /* no qname in output packet, insert it */
+               size_t dlen = get_qname_len(query_pkt, query_len);
+               reslen = origlen + dlen + 4;
+               res = (uint8_t*)malloc(reslen);
+               if(!res) {
+                       verbose(1, "out of memory; send without adjust\n");
+                       return;
+               }
+               /* copy the header, query, remainder */
+               memcpy(res, orig, LDNS_HEADER_SIZE);
+               memmove(res+LDNS_HEADER_SIZE, query_pkt+LDNS_HEADER_SIZE,
+                       dlen+4);
+               memmove(res+LDNS_HEADER_SIZE+dlen+4, orig+LDNS_HEADER_SIZE,
+                       reslen-(LDNS_HEADER_SIZE+dlen+4));
+               /* set QDCOUNT */
+               sldns_write_uint16(res+4, 1);
+       } else if(match->copy_query && origlen >= LDNS_HEADER_SIZE &&
+               query_len >= LDNS_HEADER_SIZE && LDNS_QDCOUNT(query_pkt)!=0
+               && get_qname_len(orig, origlen) == 0) {
+               /* QDCOUNT(orig)!=0 but qlen == 0, therefore, an error */
+               verbose(1, "error: malformed qname; send without adjust\n");
+               res = memdup(orig, origlen);
+               reslen = origlen;
+       } else if(match->copy_query && origlen >= LDNS_HEADER_SIZE &&
+               query_len >= LDNS_HEADER_SIZE && LDNS_QDCOUNT(query_pkt)!=0
+               && LDNS_QDCOUNT(orig)!=0) {
+               /* in this case olen != 0 and QDCOUNT(orig)!=0 */
+               /* copy query section */
+               size_t dlen = get_qname_len(query_pkt, query_len);
+               size_t olen = get_qname_len(orig, origlen);
+               reslen = origlen + dlen - olen;
+               res = (uint8_t*)malloc(reslen);
+               if(!res) {
+                       verbose(1, "out of memory; send without adjust\n");
+                       return;
+               }
+               /* copy the header, query, remainder */
+               memcpy(res, orig, LDNS_HEADER_SIZE);
+               memmove(res+LDNS_HEADER_SIZE, query_pkt+LDNS_HEADER_SIZE,
+                       dlen+4);
+               memmove(res+LDNS_HEADER_SIZE+dlen+4,
+                       orig+LDNS_HEADER_SIZE+olen+4,
+                       reslen-(LDNS_HEADER_SIZE+dlen+4));
+       } else {
+               res = memdup(orig, origlen);
+               reslen = origlen;
+       }
+       if(!res) {
+               verbose(1, "out of memory; send without adjust\n");
+               return;
+       }
+       /* copy the ID */
+       if(match->copy_id && reslen >= 2 && query_len >= 2)
+               res[1] = query_pkt[1];
+       if(match->copy_id && reslen >= 1 && query_len >= 1)
+               res[0] = query_pkt[0];
+
+       if(match->copy_ednsdata_assume_clientsubnet) {
+               /** Assume there is only one EDNS option, which is ECS.
+                * Copy source mask from query to scope mask in reply. Assume
+                * rest of ECS data in response (eg address) matches the query.
+                */
+               uint8_t* walk_q = orig;
+               size_t walk_qlen = origlen;
+               uint8_t* walk_p = res;
+               size_t walk_plen = reslen;
+
+               if(!pkt_find_edns_opt(&walk_q, &walk_qlen)) {
+                       walk_qlen = 0;
+               }
+               if(!pkt_find_edns_opt(&walk_p, &walk_plen)) {
+                       walk_plen = 0;
+               }
+               /* class + ttl + rdlen + optcode + optlen + ecs fam + ecs source
+                * + ecs scope = index 15 */
+               if(walk_qlen >= 15 && walk_plen >= 15) {
+                       walk_p[15] = walk_q[14];
+               }       
+       }
+
+       if(match->sleeptime > 0) {
+               verbose(3, "sleeping for %d seconds\n", match->sleeptime);
+#ifdef HAVE_SLEEP
+               sleep(match->sleeptime);
+#else
+               Sleep(match->sleeptime * 1000);
+#endif
+       }
+       *answer_pkt = res;
+       *answer_len = reslen;
+}
+
+/*
+ * Parses data buffer to a query, finds the correct answer 
+ * and calls the given function for every packet to send.
+ */
+void
+handle_query(uint8_t* inbuf, ssize_t inlen, struct entry* entries, int* count,
+       enum transport_type transport, void (*sendfunc)(uint8_t*, size_t, void*),
+       void* userdata, FILE* verbose_out)
+{
+       struct reply_packet *p;
+       uint8_t *outbuf = NULL;
+       size_t outlen = 0;
+       struct entry* entry = NULL;
+
+       verbose(1, "query %d: id %d: %s %d bytes: ", ++(*count),
+               (int)(inlen>=2?LDNS_ID_WIRE(inbuf):0), 
+               (transport==transport_tcp)?"TCP":"UDP", (int)inlen);
+       if(verbose_out) {
+               char* out = sldns_wire2str_pkt(inbuf, (size_t)inlen);
+               printf("%s\n", out);
+               free(out);
+       }
+
+       /* fill up answer packet */
+       entry = find_match(entries, inbuf, (size_t)inlen, transport);
+       if(!entry || !entry->reply_list) {
+               verbose(1, "no answer packet for this query, no reply.\n");
+               return;
+       }
+       for(p = entry->reply_list; p; p = p->next)
+       {
+               verbose(3, "Answer pkt:\n");
+               if (p->reply_from_hex) {
+                       /* try to adjust the hex packet, if it can be
+                        * parsed, we can use adjust rules. if not,
+                        * send packet literally */
+                       /* still try to adjust ID if others fail */
+                       outlen = sldns_buffer_limit(p->reply_from_hex);
+                       outbuf = sldns_buffer_begin(p->reply_from_hex);
+               } else {
+                       outbuf = p->reply_pkt;
+                       outlen = p->reply_len;
+               }
+               if(!outbuf) {
+                       verbose(1, "out of memory\n");
+                       return;
+               }
+               /* copies outbuf in memory allocation */
+               adjust_packet(entry, &outbuf, &outlen, inbuf, (size_t)inlen);
+               verbose(1, "Answer packet size: %u bytes.\n", (unsigned int)outlen);
+               if(verbose_out) {
+                       char* out = sldns_wire2str_pkt(outbuf, outlen);
+                       printf("%s\n", out);
+                       free(out);
+               }
+               if(p->packet_sleep) {
+                       verbose(3, "sleeping for next packet %d secs\n", 
+                               p->packet_sleep);
+#ifdef HAVE_SLEEP
+                       sleep(p->packet_sleep);
+#else
+                       Sleep(p->packet_sleep * 1000);
+#endif
+                       verbose(3, "wakeup for next packet "
+                               "(slept %d secs)\n", p->packet_sleep);
+               }
+               sendfunc(outbuf, outlen, userdata);
+               free(outbuf);
+               outbuf = NULL;
+               outlen = 0;
+       }
+}
+
+/** delete the list of reply packets */
+void delete_replylist(struct reply_packet* replist)
+{
+       struct reply_packet *p=replist, *np;
+       while(p) {
+               np = p->next;
+               free(p->reply_pkt);
+               sldns_buffer_free(p->reply_from_hex);
+               sldns_buffer_free(p->raw_ednsdata);
+               free(p);
+               p=np;
+       }
+}
+
+void delete_entry(struct entry* list)
+{
+       struct entry *p=list, *np;
+       while(p) {
+               np = p->next;
+               delete_replylist(p->reply_list);
+               free(p);
+               p = np;
+       }
+}
diff --git a/usr.sbin/unbound/testcode/testpkts.h b/usr.sbin/unbound/testcode/testpkts.h
new file mode 100644 (file)
index 0000000..b175cab
--- /dev/null
@@ -0,0 +1,290 @@
+/*
+ * testpkts. Data file parse for test packets, and query matching.
+ *
+ * Data storage for specially crafted replies for testing purposes.
+ *
+ * (c) NLnet Labs, 2005, 2006, 2007
+ * See the file LICENSE for the license
+ */
+
+#ifndef TESTPKTS_H
+#define TESTPKTS_H
+struct sldns_buffer;
+struct sldns_file_parse_state;
+
+/**
+ * \file
+ * 
+ * This is a debugging aid. It is not efficient, especially
+ * with a long config file, but it can give any reply to any query.
+ * This can help the developer pre-script replies for queries.
+ *
+ * You can specify a packet RR by RR with header flags to return.
+ *
+ * Missing features:
+ *             - matching content different from reply content.
+ *             - find way to adjust mangled packets?
+ *
+ */
+
+ /*
+       The data file format is as follows:
+       
+       ; comment.
+       ; a number of entries, these are processed first to last.
+       ; a line based format.
+
+       $ORIGIN origin
+       $TTL default_ttl
+
+       ENTRY_BEGIN
+       ; first give MATCH lines, that say what queries are matched
+       ; by this entry.
+       ; 'opcode' makes the query match the opcode from the reply
+       ; if you leave it out, any opcode matches this entry.
+       ; 'qtype' makes the query match the qtype from the reply
+       ; 'qname' makes the query match the qname from the reply
+       ; 'subdomain' makes the query match subdomains of qname from the reply
+       ; 'serial=1023' makes the query match if ixfr serial is 1023. 
+       ; 'all' has to match header byte for byte and all rrs in packet.
+       ; 'ttl' used with all, rrs in packet must also have matching TTLs.
+       ; 'DO' will match only queries with DO bit set.
+       ; 'noedns' matches queries without EDNS OPT records.
+       ; 'rcode' makes the query match the rcode from the reply
+       ; 'question' makes the query match the question section
+       ; 'answer' makes the query match the answer section
+       ; 'ednsdata' matches queries to HEX_EDNS section.
+       MATCH [opcode] [qtype] [qname] [serial=<value>] [all] [ttl]
+       MATCH [UDP|TCP] DO
+       MATCH ...
+       ; Then the REPLY header is specified.
+       REPLY opcode, rcode or flags.
+               (opcode)  QUERY IQUERY STATUS NOTIFY UPDATE
+               (rcode)   NOERROR FORMERR SERVFAIL NXDOMAIN NOTIMPL YXDOMAIN
+                               YXRRSET NXRRSET NOTAUTH NOTZONE
+               (flags)   QR AA TC RD CD RA AD DO
+       REPLY ...
+       ; any additional actions to do.
+       ; 'copy_id' copies the ID from the query to the answer.
+       ADJUST copy_id
+       ; 'copy_query' copies the query name, type and class to the answer.
+       ADJUST copy_query
+       ; 'sleep=10' sleeps for 10 seconds before giving the answer (TCP is open)
+       ADJUST [sleep=<num>]    ; sleep before giving any reply
+       ADJUST [packet_sleep=<num>]  ; sleep before this packet in sequence
+       SECTION QUESTION
+       <RRs, one per line>    ; the RRcount is determined automatically.
+       SECTION ANSWER
+       <RRs, one per line>
+       SECTION AUTHORITY
+       <RRs, one per line>
+       SECTION ADDITIONAL
+       <RRs, one per line>
+       EXTRA_PACKET            ; follow with SECTION, REPLY for more packets.
+       HEX_ANSWER_BEGIN        ; follow with hex data
+                               ; this replaces any answer packet constructed
+                               ; with the SECTION keywords (only SECTION QUERY
+                               ; is used to match queries). If the data cannot
+                               ; be parsed, ADJUST rules for the answer packet
+                               ; are ignored. Only copy_id is done.
+       HEX_ANSWER_END
+       HEX_EDNS_BEGIN          ; follow with hex data.
+                               ; Raw EDNS data to match against. It must be an 
+                               ; exact match (all options are matched) and will be 
+                               ; evaluated only when 'MATCH ednsdata' given.
+       HEX_EDNS_END
+       ENTRY_END
+
+
+       Example data file:
+$ORIGIN nlnetlabs.nl
+$TTL 3600
+
+ENTRY_BEGIN
+MATCH qname
+REPLY NOERROR
+ADJUST copy_id
+SECTION QUESTION
+www.nlnetlabs.nl.      IN      A
+SECTION ANSWER
+www.nlnetlabs.nl.      IN      A       195.169.215.155
+SECTION AUTHORITY
+nlnetlabs.nl.          IN      NS      www.nlnetlabs.nl.
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH qname
+REPLY NOERROR
+ADJUST copy_id
+SECTION QUESTION
+www2.nlnetlabs.nl.     IN      A
+HEX_ANSWER_BEGIN
+; 0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19
+;-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+ 00 bf 81 80 00 01 00 01 00 02 00 02 03 77 77 77 0b 6b 61 6e   ;          1-  20
+ 61 72 69 65 70 69 65 74 03 63 6f 6d 00 00 01 00 01 03 77 77   ;         21-  40
+ 77 0b 6b 61 6e 61 72 69 65 70 69 65 74 03 63 6f 6d 00 00 01   ;         41-  60
+ 00 01 00 01 50 8b 00 04 52 5e ed 32 0b 6b 61 6e 61 72 69 65   ;         61-  80
+ 70 69 65 74 03 63 6f 6d 00 00 02 00 01 00 01 50 8b 00 11 03   ;         81- 100
+ 6e 73 31 08 68 65 78 6f 6e 2d 69 73 02 6e 6c 00 0b 6b 61 6e   ;        101- 120
+ 61 72 69 65 70 69 65 74 03 63 6f 6d 00 00 02 00 01 00 01 50   ;        121- 140
+ 8b 00 11 03 6e 73 32 08 68 65 78 6f 6e 2d 69 73 02 6e 6c 00   ;        141- 160
+ 03 6e 73 31 08 68 65 78 6f 6e 2d 69 73 02 6e 6c 00 00 01 00   ;        161- 180
+ 01 00 00 46 53 00 04 52 5e ed 02 03 6e 73 32 08 68 65 78 6f   ;        181- 200
+ 6e 2d 69 73 02 6e 6c 00 00 01 00 01 00 00 46 53 00 04 d4 cc   ;        201- 220
+ db 5b
+HEX_ANSWER_END
+ENTRY_END
+
+
+
+   note that this file will link with your
+   void verbose(int level, char* format, ...); output function.
+*/
+
+/** Type of transport, since some entries match based on UDP or TCP of query */
+enum transport_type {transport_any = 0, transport_udp, transport_tcp };
+
+/** struct to keep a linked list of reply packets for a query */
+struct reply_packet {
+       /** next in list of reply packets, for TCP multiple pkts on wire */
+       struct reply_packet* next;
+       /** the reply pkt */
+       uint8_t* reply_pkt;
+       /** length of reply pkt */
+       size_t reply_len;
+       /** Additional EDNS data for matching queries. */
+       struct sldns_buffer* raw_ednsdata;
+       /** or reply pkt in hex if not parsable */
+       struct sldns_buffer* reply_from_hex;
+       /** seconds to sleep before giving packet */
+       unsigned int packet_sleep; 
+};
+
+/** data structure to keep the canned queries in.
+   format is the 'matching query' and the 'canned answer' */
+struct entry {
+       /* match */
+       /* How to match an incoming query with this canned reply */
+       /** match query opcode with answer opcode */
+       uint8_t match_opcode; 
+       /** match qtype with answer qtype */
+       uint8_t match_qtype;  
+       /** match qname with answer qname */
+       uint8_t match_qname;  
+       /** match rcode with answer rcode */
+       uint8_t match_rcode;
+       /** match question section */
+       uint8_t match_question;
+       /** match answer section */
+       uint8_t match_answer;
+       /** match qname as subdomain of answer qname */
+       uint8_t match_subdomain;  
+       /** match SOA serial number, from auth section */
+       uint8_t match_serial; 
+       /** match all of the packet */
+       uint8_t match_all;
+       /** match ttls in the packet */
+       uint8_t match_ttl;
+       /** match DO bit */
+       uint8_t match_do;
+       /** match absence of EDNS OPT record in query */
+       uint8_t match_noedns;
+       /** match edns data field given in hex */
+       uint8_t match_ednsdata_raw;
+       /** match query serial with this value. */
+       uint32_t ixfr_soa_serial; 
+       /** match on UDP/TCP */
+       enum transport_type match_transport; 
+
+       /** pre canned reply */
+       struct reply_packet *reply_list;
+
+       /** how to adjust the reply packet */
+       /** copy over the ID from the query into the answer */
+       uint8_t copy_id; 
+       /** copy the query nametypeclass from query into the answer */
+       uint8_t copy_query;
+       /** copy ednsdata to reply, assume it is clientsubnet and
+        * adjust scopemask to match sourcemask */
+       uint8_t copy_ednsdata_assume_clientsubnet;
+       /** in seconds */
+       unsigned int sleeptime; 
+
+       /** some number that names this entry, line number in file or so */
+       int lineno;
+
+       /** next in list */
+       struct entry* next;
+};
+
+/**
+ * reads the canned reply file and returns a list of structs 
+ * does an exit on error.
+ * @param name: name of the file to read.
+ * @param skip_whitespace: skip leftside whitespace.
+ */
+struct entry* read_datafile(const char* name, int skip_whitespace);
+
+/**
+ * Delete linked list of entries.
+ */
+void delete_entry(struct entry* list);
+
+/**
+ * Read one entry from the data file.
+ * @param in: file to read from. Filepos must be at the start of a new line.
+ * @param name: name of the file for prettier errors.
+ * @param pstate: file parse state with lineno, default_ttl,
+ *     origin and prev_rr name.
+ * @param skip_whitespace: skip leftside whitespace.
+ * @return: The entry read (malloced) or NULL if no entry could be read.
+ */
+struct entry* read_entry(FILE* in, const char* name, 
+       struct sldns_file_parse_state* pstate, int skip_whitespace);
+
+/**
+ * finds entry in list, or returns NULL.
+ */
+struct entry* find_match(struct entry* entries, uint8_t* query_pkt,
+       size_t query_pkt_len, enum transport_type transport);
+
+/**
+ * match two packets, all must match
+ * @param q: packet 1
+ * @param qlen: length of q.
+ * @param p: packet 2
+ * @param plen: length of p.
+ * @param mttl: if true, ttls must match, if false, ttls do not need to match
+ * @param noloc: if true, rrs may be reordered in their packet-section.
+ *     rrs are then matches without location of the rr being important.
+ * @return true if matched.
+ */
+int match_all(uint8_t* q, size_t qlen, uint8_t* p, size_t plen, int mttl,
+       int noloc);
+
+/**
+ * copy & adjust packet, mallocs a copy.
+ */
+void adjust_packet(struct entry* match, uint8_t** answer_pkt,
+       size_t* answer_pkt_len, uint8_t* query_pkt, size_t query_pkt_len);
+
+/**
+ * Parses data buffer to a query, finds the correct answer 
+ * and calls the given function for every packet to send.
+ * if verbose_out filename is given, packets are dumped there.
+ * @param inbuf: the packet that came in
+ * @param inlen: length of packet.
+ * @param entries: entries read in from datafile.
+ * @param count: is increased to count number of queries answered.
+ * @param transport: set to UDP or TCP to match some types of entries.
+ * @param sendfunc: called to send answer (buffer, size, userarg).
+ * @param userdata: userarg to give to sendfunc.
+ * @param verbose_out: if not NULL, verbose messages are printed there.
+ */
+void handle_query(uint8_t* inbuf, ssize_t inlen, struct entry* entries, 
+       int* count, enum transport_type transport, 
+       void (*sendfunc)(uint8_t*, size_t, void*), void* userdata,
+       FILE* verbose_out);
+
+#endif /* TESTPKTS_H */
diff --git a/usr.sbin/unbound/testcode/unitanchor.c b/usr.sbin/unbound/testcode/unitanchor.c
new file mode 100644 (file)
index 0000000..8819c5a
--- /dev/null
@@ -0,0 +1,137 @@
+/*
+ * testcode/unitanchor.c - unit test for trust anchor storage.
+ *
+ * Copyright (c) 2007, NLnet Labs. All rights reserved.
+ *
+ * This software is open source.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * Neither the name of the NLNET LABS nor the names of its contributors may
+ * be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+ * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+/**
+ * \file
+ * Calls trust anchor unit tests. Exits with code 1 on a failure. 
+ */
+
+#include "config.h"
+#include "util/log.h"
+#include "util/data/dname.h"
+#include "testcode/unitmain.h"
+#include "validator/val_anchor.h"
+#include "sldns/sbuffer.h"
+#include "sldns/rrdef.h"
+
+/** test empty set */
+static void
+test_anchor_empty(struct val_anchors* a)
+{
+       uint16_t c = LDNS_RR_CLASS_IN;
+       unit_assert(anchors_lookup(a, (uint8_t*)"\000", 1, c) == NULL);
+       unit_assert(anchors_lookup(a, (uint8_t*)"\003com\000", 5, c) == NULL);
+       unit_assert(anchors_lookup(a, 
+               (uint8_t*)"\007example\003com\000", 11, c) == NULL);
+       unit_assert(anchors_lookup(a, (uint8_t*)"\002nl\000", 4, c) == NULL);
+       unit_assert(anchors_lookup(a, 
+               (uint8_t*)"\004labs\002nl\000", 9, c) == NULL);
+       unit_assert(anchors_lookup(a, 
+               (uint8_t*)"\004fabs\002nl\000", 9, c) == NULL);
+}
+
+/** test set of one anchor */
+static void
+test_anchor_one(sldns_buffer* buff, struct val_anchors* a)
+{
+       struct trust_anchor* ta;
+       uint16_t c = LDNS_RR_CLASS_IN;
+       unit_assert(anchor_store_str(a, buff, 
+               "nl. DS 42860 5 1 14D739EB566D2B1A5E216A0BA4D17FA9B038BE4A"));
+       unit_assert(anchors_lookup(a, (uint8_t*)"\000", 1, c) == NULL);
+       unit_assert(anchors_lookup(a, (uint8_t*)"\003com\000", 5, c) == NULL);
+       unit_assert(anchors_lookup(a, 
+               (uint8_t*)"\007example\003com\000", 11, c) == NULL);
+
+       unit_assert((ta=anchors_lookup(a,
+               (uint8_t*)"\002nl\000", 4, c)) != NULL);
+       lock_basic_unlock(&ta->lock);
+
+       unit_assert((ta=anchors_lookup(a, 
+               (uint8_t*)"\004labs\002nl\000", 9, c)) != NULL);
+       lock_basic_unlock(&ta->lock);
+
+       unit_assert((ta=anchors_lookup(a, 
+               (uint8_t*)"\004fabs\002nl\000", 9, c)) != NULL);
+       lock_basic_unlock(&ta->lock);
+
+       unit_assert(anchors_lookup(a, (uint8_t*)"\002oo\000", 4, c) == NULL);
+}
+
+/** test with several anchors */
+static void
+test_anchors(sldns_buffer* buff, struct val_anchors* a)
+{
+       struct trust_anchor* ta;
+       uint16_t c = LDNS_RR_CLASS_IN;
+       unit_assert(anchor_store_str(a, buff, 
+       "labs.nl. DS 42860 5 1 14D739EB566D2B1A5E216A0BA4D17FA9B038BE4A"));
+       unit_assert(anchors_lookup(a, (uint8_t*)"\000", 1, c) == NULL);
+       unit_assert(anchors_lookup(a, (uint8_t*)"\003com\000", 5, c) == NULL);
+       unit_assert(anchors_lookup(a, 
+               (uint8_t*)"\007example\003com\000", 11, c) == NULL);
+
+       unit_assert(ta = anchors_lookup(a, (uint8_t*)"\002nl\000", 4, c));
+       unit_assert(query_dname_compare(ta->name, (uint8_t*)"\002nl\000")==0);
+       lock_basic_unlock(&ta->lock);
+
+       unit_assert(ta = anchors_lookup(a, 
+               (uint8_t*)"\004labs\002nl\000", 9, c));
+       unit_assert(query_dname_compare(ta->name, 
+               (uint8_t*)"\004labs\002nl\000") == 0);
+       lock_basic_unlock(&ta->lock);
+
+       unit_assert(ta = anchors_lookup(a, 
+               (uint8_t*)"\004fabs\002nl\000", 9, c));
+       unit_assert(query_dname_compare(ta->name, 
+               (uint8_t*)"\002nl\000") == 0);
+       lock_basic_unlock(&ta->lock);
+
+       unit_assert(anchors_lookup(a, (uint8_t*)"\002oo\000", 4, c) == NULL);
+}
+
+void anchors_test(void)
+{
+       sldns_buffer* buff = sldns_buffer_new(65800);
+       struct val_anchors* a;
+       unit_show_feature("trust anchor store");
+       unit_assert(a = anchors_create());
+       sldns_buffer_flip(buff);
+       test_anchor_empty(a);
+       test_anchor_one(buff, a);
+       test_anchors(buff, a);
+       anchors_delete(a);
+       sldns_buffer_free(buff);
+}
diff --git a/usr.sbin/unbound/testcode/unitauth.c b/usr.sbin/unbound/testcode/unitauth.c
new file mode 100644 (file)
index 0000000..4b538ef
--- /dev/null
@@ -0,0 +1,882 @@
+/*
+ * testcode/unitauth.c - unit test for authzone authoritative zone code.
+ *
+ * Copyright (c) 2017, NLnet Labs. All rights reserved.
+ *
+ * This software is open source.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * Neither the name of the NLNET LABS nor the names of its contributors may
+ * be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+ * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+/**
+ * \file
+ * Unit test for auth zone code.
+ */
+#include "config.h"
+#include "services/authzone.h"
+#include "testcode/unitmain.h"
+#include "util/regional.h"
+#include "util/net_help.h"
+#include "util/data/msgreply.h"
+#include "services/cache/dns.h"
+#include "sldns/str2wire.h"
+#include "sldns/wire2str.h"
+#include "sldns/sbuffer.h"
+
+/** verbosity for this test */
+static int vbmp = 0;
+
+/** struct for query and answer checks */
+struct q_ans {
+       /** zone to query (delegpt) */
+       const char* zone;
+       /** query name, class, type */
+       const char* query;
+       /** additional flags or "" */
+       const char* flags;
+       /** expected answer to check against, multi-line string */
+       const char* answer;
+};
+
+/** auth zone for test */
+static const char* zone_example_com =
+"example.com.  3600    IN      SOA     ns.example.org. noc.example.org. 2017042710 7200 3600 1209600 3600\n"
+"example.com.  3600    IN      A       10.0.0.1\n"
+"example.com.  3600    IN      NS      ns.example.com.\n"
+"example.com.  3600    IN      MX      50 mail.example.com.\n"
+"deep.ent.example.com. 3600    IN      A       10.0.0.9\n"
+"mail.example.com.     3600    IN      A       10.0.0.4\n"
+"ns.example.com.       3600    IN      A       10.0.0.5\n"
+"out.example.com.      3600    IN      CNAME   www.example.com.\n"
+"plan.example.com.     3600    IN      CNAME   nonexist.example.com.\n"
+"redir.example.com.    3600    IN      DNAME   redir.example.org.\n"
+"sub.example.com.      3600    IN      NS      ns1.sub.example.com.\n"
+"sub.example.com.      3600    IN      NS      ns2.sub.example.com.\n"
+"ns1.sub.example.com.  3600    IN      A       10.0.0.6\n"
+"ns2.sub.example.com.  3600    IN      AAAA    2001::7\n"
+"*.wild.example.com.   3600    IN      A       10.0.0.8\n"
+"*.wild2.example.com.  3600    IN      CNAME   www.example.com.\n"
+"*.wild3.example.com.  3600    IN      A       10.0.0.8\n"
+"*.wild3.example.com.  3600    IN      MX      50 mail.example.com.\n"
+"www.example.com.      3600    IN      A       10.0.0.2\n"
+"www.example.com.      3600    IN      A       10.0.0.3\n"
+"yy.example.com.       3600    IN      TXT     \"a\"\n"
+"yy.example.com.       3600    IN      TXT     \"b\"\n"
+"yy.example.com.       3600    IN      TXT     \"c\"\n"
+"yy.example.com.       3600    IN      TXT     \"d\"\n"
+"yy.example.com.       3600    IN      TXT     \"e\"\n"
+"yy.example.com.       3600    IN      TXT     \"f\"\n"
+
+/* and some tests for RRSIGs (rrsig is www.nlnetlabs.nl copy) */
+/* normal: domain and 1 rrsig */
+"z1.example.com.       3600    IN      A       10.0.0.10\n"
+"z1.example.com.       3600    IN      RRSIG   A 8 3 10200 20170612005010 20170515005010 42393 nlnetlabs.nl. NhEDrHkuIgHkjWhDRVsGOIJWZpSs+QdduilWFe5d+/ZhOheLJbaTYD5w6+ZZ3yPh1tNud+jlg+GyiOSVapLEO31swDCIarL1UfRjRSpxxDCHGag5Zu+S4hF+KURxO3cJk8jLBELMQyRuMRHoKrw/wsiLGVu1YpAyAPPMcjFBNbk=\n"
+/* normal: domain and 2 rrsigs */
+"z2.example.com.       3600    IN      A       10.0.0.10\n"
+"z2.example.com.       3600    IN      RRSIG   A 8 3 10200 20170612005010 20170515005010 42393 nlnetlabs.nl. NhEDrHkuIgHkjWhDRVsGOIJWZpSs+QdduilWFe5d+/ZhOheLJbaTYD5w6+ZZ3yPh1tNud+jlg+GyiOSVapLEO31swDCIarL1UfRjRSpxxDCHGag5Zu+S4hF+KURxO3cJk8jLBELMQyRuMRHoKrw/wsiLGVu1YpAyAPPMcjFBNbk=\n"
+"z2.example.com.       3600    IN      RRSIG   A 8 3 10200 20170612005010 20170515005010 12345 nlnetlabs.nl. NhEDrHkuIgHkjWhDRVsGOIJWZpSs+QdduilWFe5d+/ZhOheLJbaTYD5w6+ZZ3yPh1tNud+jlg+GyiOSVapLEO31swDCIarL1UfRjRSpxxDCHGag5Zu+S4hF+KURxO3cJk8jLBELMQyRuMRHoKrw/wsiLGVu1YpAyAPPMcjFBNbk=\n"
+/* normal: domain and 3 rrsigs */
+"z3.example.com.       3600    IN      A       10.0.0.10\n"
+"z3.example.com.       3600    IN      A       10.0.0.11\n"
+"z3.example.com.       3600    IN      RRSIG   A 8 3 10200 20170612005010 20170515005010 42393 nlnetlabs.nl. NhEDrHkuIgHkjWhDRVsGOIJWZpSs+QdduilWFe5d+/ZhOheLJbaTYD5w6+ZZ3yPh1tNud+jlg+GyiOSVapLEO31swDCIarL1UfRjRSpxxDCHGag5Zu+S4hF+KURxO3cJk8jLBELMQyRuMRHoKrw/wsiLGVu1YpAyAPPMcjFBNbk=\n"
+"z3.example.com.       3600    IN      RRSIG   A 8 3 10200 20170612005010 20170515005010 12345 nlnetlabs.nl. NhEDrHkuIgHkjWhDRVsGOIJWZpSs+QdduilWFe5d+/ZhOheLJbaTYD5w6+ZZ3yPh1tNud+jlg+GyiOSVapLEO31swDCIarL1UfRjRSpxxDCHGag5Zu+S4hF+KURxO3cJk8jLBELMQyRuMRHoKrw/wsiLGVu1YpAyAPPMcjFBNbk=\n"
+"z3.example.com.       3600    IN      RRSIG   A 8 3 10200 20170612005010 20170515005010 12356 nlnetlabs.nl. NhEDrHkuIgHkjWhDRVsGOIJWZpSs+QdduilWFe5d+/ZhOheLJbaTYD5w6+ZZ3yPh1tNud+jlg+GyiOSVapLEO31swDCIarL1UfRjRSpxxDCHGag5Zu+S4hF+KURxO3cJk8jLBELMQyRuMRHoKrw/wsiLGVu1YpAyAPPMcjFBNbk=\n"
+/* just an RRSIG rrset with nothing else */
+"z4.example.com.       3600    IN      RRSIG   A 8 3 10200 20170612005010 20170515005010 42393 nlnetlabs.nl. NhEDrHkuIgHkjWhDRVsGOIJWZpSs+QdduilWFe5d+/ZhOheLJbaTYD5w6+ZZ3yPh1tNud+jlg+GyiOSVapLEO31swDCIarL1UfRjRSpxxDCHGag5Zu+S4hF+KURxO3cJk8jLBELMQyRuMRHoKrw/wsiLGVu1YpAyAPPMcjFBNbk=\n"
+/* just an RRSIG rrset with nothing else, 2 rrsigs */
+"z5.example.com.       3600    IN      RRSIG   A 8 3 10200 20170612005010 20170515005010 42393 nlnetlabs.nl. NhEDrHkuIgHkjWhDRVsGOIJWZpSs+QdduilWFe5d+/ZhOheLJbaTYD5w6+ZZ3yPh1tNud+jlg+GyiOSVapLEO31swDCIarL1UfRjRSpxxDCHGag5Zu+S4hF+KURxO3cJk8jLBELMQyRuMRHoKrw/wsiLGVu1YpAyAPPMcjFBNbk=\n"
+"z5.example.com.       3600    IN      RRSIG   A 8 3 10200 20170612005010 20170515005010 12345 nlnetlabs.nl. NhEDrHkuIgHkjWhDRVsGOIJWZpSs+QdduilWFe5d+/ZhOheLJbaTYD5w6+ZZ3yPh1tNud+jlg+GyiOSVapLEO31swDCIarL1UfRjRSpxxDCHGag5Zu+S4hF+KURxO3cJk8jLBELMQyRuMRHoKrw/wsiLGVu1YpAyAPPMcjFBNbk=\n"
+#if 0 /* comparison of file does not work on this part because duplicates */
+      /* are removed and the rrsets are reordered */
+/* first rrsig, then A record */
+"z6.example.com.       3600    IN      RRSIG   A 8 3 10200 20170612005010 20170515005010 42393 nlnetlabs.nl. NhEDrHkuIgHkjWhDRVsGOIJWZpSs+QdduilWFe5d+/ZhOheLJbaTYD5w6+ZZ3yPh1tNud+jlg+GyiOSVapLEO31swDCIarL1UfRjRSpxxDCHGag5Zu+S4hF+KURxO3cJk8jLBELMQyRuMRHoKrw/wsiLGVu1YpAyAPPMcjFBNbk=\n"
+"z6.example.com.       3600    IN      A       10.0.0.10\n"
+/* first two rrsigs, then A record */
+"z7.example.com.       3600    IN      RRSIG   A 8 3 10200 20170612005010 20170515005010 42393 nlnetlabs.nl. NhEDrHkuIgHkjWhDRVsGOIJWZpSs+QdduilWFe5d+/ZhOheLJbaTYD5w6+ZZ3yPh1tNud+jlg+GyiOSVapLEO31swDCIarL1UfRjRSpxxDCHGag5Zu+S4hF+KURxO3cJk8jLBELMQyRuMRHoKrw/wsiLGVu1YpAyAPPMcjFBNbk=\n"
+"z7.example.com.       3600    IN      RRSIG   A 8 3 10200 20170612005010 20170515005010 12345 nlnetlabs.nl. NhEDrHkuIgHkjWhDRVsGOIJWZpSs+QdduilWFe5d+/ZhOheLJbaTYD5w6+ZZ3yPh1tNud+jlg+GyiOSVapLEO31swDCIarL1UfRjRSpxxDCHGag5Zu+S4hF+KURxO3cJk8jLBELMQyRuMRHoKrw/wsiLGVu1YpAyAPPMcjFBNbk=\n"
+"z7.example.com.       3600    IN      A       10.0.0.10\n"
+/* first two rrsigs, then two A records */
+"z8.example.com.       3600    IN      RRSIG   A 8 3 10200 20170612005010 20170515005010 42393 nlnetlabs.nl. NhEDrHkuIgHkjWhDRVsGOIJWZpSs+QdduilWFe5d+/ZhOheLJbaTYD5w6+ZZ3yPh1tNud+jlg+GyiOSVapLEO31swDCIarL1UfRjRSpxxDCHGag5Zu+S4hF+KURxO3cJk8jLBELMQyRuMRHoKrw/wsiLGVu1YpAyAPPMcjFBNbk=\n"
+"z8.example.com.       3600    IN      RRSIG   A 8 3 10200 20170612005010 20170515005010 12345 nlnetlabs.nl. NhEDrHkuIgHkjWhDRVsGOIJWZpSs+QdduilWFe5d+/ZhOheLJbaTYD5w6+ZZ3yPh1tNud+jlg+GyiOSVapLEO31swDCIarL1UfRjRSpxxDCHGag5Zu+S4hF+KURxO3cJk8jLBELMQyRuMRHoKrw/wsiLGVu1YpAyAPPMcjFBNbk=\n"
+"z8.example.com.       3600    IN      A       10.0.0.10\n"
+"z8.example.com.       3600    IN      A       10.0.0.11\n"
+/* duplicate RR, duplicate RRsig */
+"z9.example.com.       3600    IN      A       10.0.0.10\n"
+"z9.example.com.       3600    IN      A       10.0.0.11\n"
+"z9.example.com.       3600    IN      A       10.0.0.10\n"
+"z9.example.com.       3600    IN      RRSIG   A 8 3 10200 20170612005010 20170515005010 42393 nlnetlabs.nl. NhEDrHkuIgHkjWhDRVsGOIJWZpSs+QdduilWFe5d+/ZhOheLJbaTYD5w6+ZZ3yPh1tNud+jlg+GyiOSVapLEO31swDCIarL1UfRjRSpxxDCHGag5Zu+S4hF+KURxO3cJk8jLBELMQyRuMRHoKrw/wsiLGVu1YpAyAPPMcjFBNbk=\n"
+"z9.example.com.       3600    IN      RRSIG   A 8 3 10200 20170612005010 20170515005010 42393 nlnetlabs.nl. NhEDrHkuIgHkjWhDRVsGOIJWZpSs+QdduilWFe5d+/ZhOheLJbaTYD5w6+ZZ3yPh1tNud+jlg+GyiOSVapLEO31swDCIarL1UfRjRSpxxDCHGag5Zu+S4hF+KURxO3cJk8jLBELMQyRuMRHoKrw/wsiLGVu1YpAyAPPMcjFBNbk=\n"
+#endif /* if0 for duplicates and reordering */
+;
+
+/** queries for example.com: zone, query, flags, answer. end with NULL */
+static struct q_ans example_com_queries[] = {
+       { "example.com", "www.example.com. A", "",
+";flags QR AA rcode NOERROR\n"
+";answer section\n"
+"www.example.com.      3600    IN      A       10.0.0.2\n"
+"www.example.com.      3600    IN      A       10.0.0.3\n"
+       },
+
+       { "example.com", "example.com. SOA", "",
+";flags QR AA rcode NOERROR\n"
+";answer section\n"
+"example.com.  3600    IN      SOA     ns.example.org. noc.example.org. 2017042710 7200 3600 1209600 3600\n"
+       },
+
+       { "example.com", "example.com. A", "",
+";flags QR AA rcode NOERROR\n"
+";answer section\n"
+"example.com.  3600    IN      A       10.0.0.1\n"
+       },
+
+       { "example.com", "example.com. AAAA", "",
+";flags QR AA rcode NOERROR\n"
+";authority section\n"
+"example.com.  3600    IN      SOA     ns.example.org. noc.example.org. 2017042710 7200 3600 1209600 3600\n"
+       },
+
+       { "example.com", "example.com. NS", "",
+";flags QR AA rcode NOERROR\n"
+";answer section\n"
+"example.com.  3600    IN      NS      ns.example.com.\n"
+";additional section\n"
+"ns.example.com.       3600    IN      A       10.0.0.5\n"
+       },
+
+       { "example.com", "example.com. MX", "",
+";flags QR AA rcode NOERROR\n"
+";answer section\n"
+"example.com.  3600    IN      MX      50 mail.example.com.\n"
+";additional section\n"
+"mail.example.com.     3600    IN      A       10.0.0.4\n"
+       },
+
+       { "example.com", "example.com. IN ANY", "",
+";flags QR AA rcode NOERROR\n"
+";answer section\n"
+"example.com.  3600    IN      SOA     ns.example.org. noc.example.org. 2017042710 7200 3600 1209600 3600\n"
+"example.com.  3600    IN      MX      50 mail.example.com.\n"
+"example.com.  3600    IN      A       10.0.0.1\n"
+       },
+
+       { "example.com", "nonexist.example.com. A", "",
+";flags QR AA rcode NXDOMAIN\n"
+";authority section\n"
+"example.com.  3600    IN      SOA     ns.example.org. noc.example.org. 2017042710 7200 3600 1209600 3600\n"
+       },
+
+       { "example.com", "deep.ent.example.com. A", "",
+";flags QR AA rcode NOERROR\n"
+";answer section\n"
+"deep.ent.example.com. 3600    IN      A       10.0.0.9\n"
+       },
+
+       { "example.com", "ent.example.com. A", "",
+";flags QR AA rcode NOERROR\n"
+";authority section\n"
+"example.com.  3600    IN      SOA     ns.example.org. noc.example.org. 2017042710 7200 3600 1209600 3600\n"
+       },
+
+       { "example.com", "below.deep.ent.example.com. A", "",
+";flags QR AA rcode NXDOMAIN\n"
+";authority section\n"
+"example.com.  3600    IN      SOA     ns.example.org. noc.example.org. 2017042710 7200 3600 1209600 3600\n"
+       },
+
+       { "example.com", "mail.example.com. A", "",
+";flags QR AA rcode NOERROR\n"
+";answer section\n"
+"mail.example.com.     3600    IN      A       10.0.0.4\n"
+       },
+
+       { "example.com", "ns.example.com. A", "",
+";flags QR AA rcode NOERROR\n"
+";answer section\n"
+"ns.example.com.       3600    IN      A       10.0.0.5\n"
+       },
+
+       { "example.com", "out.example.com. A", "",
+";flags QR AA rcode NOERROR\n"
+";answer section\n"
+"out.example.com.      3600    IN      CNAME   www.example.com.\n"
+"www.example.com.      3600    IN      A       10.0.0.2\n"
+"www.example.com.      3600    IN      A       10.0.0.3\n"
+       },
+
+       { "example.com", "out.example.com. CNAME", "",
+";flags QR AA rcode NOERROR\n"
+";answer section\n"
+"out.example.com.      3600    IN      CNAME   www.example.com.\n"
+       },
+
+       { "example.com", "plan.example.com. A", "",
+";flags QR AA rcode NOERROR\n"
+";answer section\n"
+"plan.example.com.     3600    IN      CNAME   nonexist.example.com.\n"
+       },
+
+       { "example.com", "plan.example.com. CNAME", "",
+";flags QR AA rcode NOERROR\n"
+";answer section\n"
+"plan.example.com.     3600    IN      CNAME   nonexist.example.com.\n"
+       },
+
+       { "example.com", "redir.example.com. A", "",
+";flags QR AA rcode NOERROR\n"
+";authority section\n"
+"example.com.  3600    IN      SOA     ns.example.org. noc.example.org. 2017042710 7200 3600 1209600 3600\n"
+       },
+
+       { "example.com", "redir.example.com. DNAME", "",
+";flags QR AA rcode NOERROR\n"
+";answer section\n"
+"redir.example.com.    3600    IN      DNAME   redir.example.org.\n"
+       },
+
+       { "example.com", "abc.redir.example.com. A", "",
+";flags QR AA rcode NOERROR\n"
+";answer section\n"
+"redir.example.com.    3600    IN      DNAME   redir.example.org.\n"
+"abc.redir.example.com.        0       IN      CNAME   abc.redir.example.org.\n"
+       },
+
+       { "example.com", "foo.abc.redir.example.com. A", "",
+";flags QR AA rcode NOERROR\n"
+";answer section\n"
+"redir.example.com.    3600    IN      DNAME   redir.example.org.\n"
+"foo.abc.redir.example.com.    0       IN      CNAME   foo.abc.redir.example.org.\n"
+       },
+
+       { "example.com", "sub.example.com. NS", "",
+";flags QR rcode NOERROR\n"
+";authority section\n"
+"sub.example.com.      3600    IN      NS      ns1.sub.example.com.\n"
+"sub.example.com.      3600    IN      NS      ns2.sub.example.com.\n"
+";additional section\n"
+"ns1.sub.example.com.  3600    IN      A       10.0.0.6\n"
+"ns2.sub.example.com.  3600    IN      AAAA    2001::7\n"
+       },
+
+       { "example.com", "sub.example.com. DS", "",
+";flags QR AA rcode NOERROR\n"
+";authority section\n"
+"example.com.  3600    IN      SOA     ns.example.org. noc.example.org. 2017042710 7200 3600 1209600 3600\n"
+       },
+
+       { "example.com", "www.sub.example.com. NS", "",
+";flags QR rcode NOERROR\n"
+";authority section\n"
+"sub.example.com.      3600    IN      NS      ns1.sub.example.com.\n"
+"sub.example.com.      3600    IN      NS      ns2.sub.example.com.\n"
+";additional section\n"
+"ns1.sub.example.com.  3600    IN      A       10.0.0.6\n"
+"ns2.sub.example.com.  3600    IN      AAAA    2001::7\n"
+       },
+
+       { "example.com", "foo.abc.sub.example.com. NS", "",
+";flags QR rcode NOERROR\n"
+";authority section\n"
+"sub.example.com.      3600    IN      NS      ns1.sub.example.com.\n"
+"sub.example.com.      3600    IN      NS      ns2.sub.example.com.\n"
+";additional section\n"
+"ns1.sub.example.com.  3600    IN      A       10.0.0.6\n"
+"ns2.sub.example.com.  3600    IN      AAAA    2001::7\n"
+       },
+
+       { "example.com", "ns1.sub.example.com. A", "",
+";flags QR rcode NOERROR\n"
+";authority section\n"
+"sub.example.com.      3600    IN      NS      ns1.sub.example.com.\n"
+"sub.example.com.      3600    IN      NS      ns2.sub.example.com.\n"
+";additional section\n"
+"ns1.sub.example.com.  3600    IN      A       10.0.0.6\n"
+"ns2.sub.example.com.  3600    IN      AAAA    2001::7\n"
+       },
+
+       { "example.com", "ns1.sub.example.com. AAAA", "",
+";flags QR rcode NOERROR\n"
+";authority section\n"
+"sub.example.com.      3600    IN      NS      ns1.sub.example.com.\n"
+"sub.example.com.      3600    IN      NS      ns2.sub.example.com.\n"
+";additional section\n"
+"ns1.sub.example.com.  3600    IN      A       10.0.0.6\n"
+"ns2.sub.example.com.  3600    IN      AAAA    2001::7\n"
+       },
+
+       { "example.com", "ns2.sub.example.com. A", "",
+";flags QR rcode NOERROR\n"
+";authority section\n"
+"sub.example.com.      3600    IN      NS      ns1.sub.example.com.\n"
+"sub.example.com.      3600    IN      NS      ns2.sub.example.com.\n"
+";additional section\n"
+"ns1.sub.example.com.  3600    IN      A       10.0.0.6\n"
+"ns2.sub.example.com.  3600    IN      AAAA    2001::7\n"
+       },
+
+       { "example.com", "ns2.sub.example.com. AAAA", "",
+";flags QR rcode NOERROR\n"
+";authority section\n"
+"sub.example.com.      3600    IN      NS      ns1.sub.example.com.\n"
+"sub.example.com.      3600    IN      NS      ns2.sub.example.com.\n"
+";additional section\n"
+"ns1.sub.example.com.  3600    IN      A       10.0.0.6\n"
+"ns2.sub.example.com.  3600    IN      AAAA    2001::7\n"
+       },
+
+       { "example.com", "wild.example.com. A", "",
+";flags QR AA rcode NOERROR\n"
+";authority section\n"
+"example.com.  3600    IN      SOA     ns.example.org. noc.example.org. 2017042710 7200 3600 1209600 3600\n"
+       },
+
+       { "example.com", "*.wild.example.com. A", "",
+";flags QR AA rcode NOERROR\n"
+";answer section\n"
+"*.wild.example.com.   3600    IN      A       10.0.0.8\n"
+       },
+
+       { "example.com", "*.wild.example.com. AAAA", "",
+";flags QR AA rcode NOERROR\n"
+";authority section\n"
+"example.com.  3600    IN      SOA     ns.example.org. noc.example.org. 2017042710 7200 3600 1209600 3600\n"
+       },
+
+       { "example.com", "abc.wild.example.com. A", "",
+";flags QR AA rcode NOERROR\n"
+";answer section\n"
+"abc.wild.example.com. 3600    IN      A       10.0.0.8\n"
+       },
+
+       { "example.com", "abc.wild.example.com. AAAA", "",
+";flags QR AA rcode NOERROR\n"
+";authority section\n"
+"example.com.  3600    IN      SOA     ns.example.org. noc.example.org. 2017042710 7200 3600 1209600 3600\n"
+       },
+
+       { "example.com", "foo.abc.wild.example.com. A", "",
+";flags QR AA rcode NOERROR\n"
+";answer section\n"
+"foo.abc.wild.example.com.     3600    IN      A       10.0.0.8\n"
+       },
+
+       { "example.com", "foo.abc.wild.example.com. AAAA", "",
+";flags QR AA rcode NOERROR\n"
+";authority section\n"
+"example.com.  3600    IN      SOA     ns.example.org. noc.example.org. 2017042710 7200 3600 1209600 3600\n"
+       },
+
+       { "example.com", "wild2.example.com. A", "",
+";flags QR AA rcode NOERROR\n"
+";authority section\n"
+"example.com.  3600    IN      SOA     ns.example.org. noc.example.org. 2017042710 7200 3600 1209600 3600\n"
+       },
+
+       { "example.com", "*.wild2.example.com. A", "",
+";flags QR AA rcode NOERROR\n"
+";answer section\n"
+"*.wild2.example.com.  3600    IN      CNAME   www.example.com.\n"
+"www.example.com.      3600    IN      A       10.0.0.2\n"
+"www.example.com.      3600    IN      A       10.0.0.3\n"
+       },
+
+       { "example.com", "abc.wild2.example.com. A", "",
+";flags QR AA rcode NOERROR\n"
+";answer section\n"
+"abc.wild2.example.com.        3600    IN      CNAME   www.example.com.\n"
+"www.example.com.      3600    IN      A       10.0.0.2\n"
+"www.example.com.      3600    IN      A       10.0.0.3\n"
+       },
+
+       { "example.com", "foo.abc.wild2.example.com. A", "",
+";flags QR AA rcode NOERROR\n"
+";answer section\n"
+"foo.abc.wild2.example.com.    3600    IN      CNAME   www.example.com.\n"
+"www.example.com.      3600    IN      A       10.0.0.2\n"
+"www.example.com.      3600    IN      A       10.0.0.3\n"
+       },
+
+       { "example.com", "abc.wild2.example.com. CNAME", "",
+";flags QR AA rcode NOERROR\n"
+";answer section\n"
+"abc.wild2.example.com.        3600    IN      CNAME   www.example.com.\n"
+       },
+
+       { "example.com", "abc.wild3.example.com. IN ANY", "",
+";flags QR AA rcode NOERROR\n"
+";answer section\n"
+"abc.wild3.example.com.        3600    IN      MX      50 mail.example.com.\n"
+"abc.wild3.example.com.        3600    IN      A       10.0.0.8\n"
+       },
+
+       { "example.com", "yy.example.com. TXT", "",
+";flags QR AA rcode NOERROR\n"
+";answer section\n"
+"yy.example.com.       3600    IN      TXT     \"a\"\n"
+"yy.example.com.       3600    IN      TXT     \"b\"\n"
+"yy.example.com.       3600    IN      TXT     \"c\"\n"
+"yy.example.com.       3600    IN      TXT     \"d\"\n"
+"yy.example.com.       3600    IN      TXT     \"e\"\n"
+"yy.example.com.       3600    IN      TXT     \"f\"\n"
+       },
+
+       {NULL, NULL, NULL, NULL}
+};
+
+/** number of tmpfiles */
+static int tempno = 0;
+/** number of deleted files */
+static int delno = 0;
+
+/** cleanup tmp files at exit */
+static void
+tmpfilecleanup(void)
+{
+       int i;
+       char buf[256];
+       for(i=0; i<tempno; i++) {
+               snprintf(buf, sizeof(buf), "/tmp/unbound.unittest.%u.%d",
+                       (unsigned)getpid(), i);
+               if(vbmp) printf("cleanup: unlink %s\n", buf);
+               unlink(buf);
+       }
+}
+
+/** create temp file, return (malloced) name string, write contents to it */
+static char*
+create_tmp_file(const char* s)
+{
+       char buf[256];
+       char *fname;
+       FILE *out;
+       size_t r;
+       snprintf(buf, sizeof(buf), "/tmp/unbound.unittest.%u.%d",
+               (unsigned)getpid(), tempno++);
+       fname = strdup(buf);
+       if(!fname) fatal_exit("out of memory");
+       /* if no string, just make the name */
+       if(!s) return fname;
+       /* if string, write to file */
+       out = fopen(fname, "w");
+       if(!out) fatal_exit("cannot open %s: %s", fname, strerror(errno));
+       r = fwrite(s, 1, strlen(s), out);
+       if(r == 0) {
+               fatal_exit("write failed: %s", strerror(errno));
+       } else if(r < strlen(s)) {
+               fatal_exit("write failed: too short (disk full?)");
+       }
+       fclose(out);
+       return fname;
+}
+
+/** delete temp file and free name string */
+static void
+del_tmp_file(char* fname)
+{
+       unlink(fname);
+       free(fname);
+       delno++;
+       if(delno == tempno) {
+               /* deleted all outstanding files, back to start condition */
+               tempno = 0;
+               delno = 0;
+       }
+}
+
+/** Add zone from file for testing */
+static struct auth_zone*
+addzone(struct auth_zones* az, const char* name, char* fname)
+{
+       struct auth_zone* z;
+       size_t nmlen;
+       uint8_t* nm = sldns_str2wire_dname(name, &nmlen);
+       if(!nm) fatal_exit("out of memory");
+       lock_rw_wrlock(&az->lock);
+       z = auth_zone_create(az, nm, nmlen, LDNS_RR_CLASS_IN);
+       lock_rw_unlock(&az->lock);
+       if(!z) fatal_exit("cannot find zone");
+       auth_zone_set_zonefile(z, fname);
+       z->for_upstream = 1;
+
+       if(!auth_zone_read_zonefile(z)) {
+               fatal_exit("parse failure for auth zone %s", name);
+       }
+       lock_rw_unlock(&z->lock);
+       free(nm);
+       return z;
+}
+
+/** check that file is the same as other file */
+static void
+checkfile(char* f1, char *f2)
+{
+       char buf1[10240], buf2[10240];
+       int line = 0;
+       FILE* i1, *i2;
+       i1 = fopen(f1, "r");
+       if(!i1) fatal_exit("cannot open %s: %s", f1, strerror(errno));
+       i2 = fopen(f2, "r");
+       if(!i2) fatal_exit("cannot open %s: %s", f2, strerror(errno));
+
+       while(!feof(i1) && !feof(i2)) {
+               char* cp1, *cp2;
+               line++;
+               cp1 = fgets(buf1, (int)sizeof(buf1), i1);
+               cp2 = fgets(buf2, (int)sizeof(buf2), i2);
+               if((!cp1 && !feof(i1)) || (!cp2 && !feof(i2)))
+                       fatal_exit("fgets failed: %s", strerror(errno));
+               if(strcmp(buf1, buf2) != 0) {
+                       log_info("in files %s and %s:%d", f1, f2, line);
+                       log_info("'%s'", buf1);
+                       log_info("'%s'", buf2);
+                       fatal_exit("files are not eqaul");
+               }
+       }
+       unit_assert(feof(i1) && feof(i2));
+
+       fclose(i1);
+       fclose(i2);
+}
+
+/** check that a zone (in string) can be read and reproduced */
+static void
+check_read_exact(const char* name, const char* zone)
+{
+       struct auth_zones* az;
+       struct auth_zone* z;
+       char* fname, *outf;
+       if(vbmp) printf("check read zone %s\n", name);
+       fname = create_tmp_file(zone);
+
+       az = auth_zones_create();
+       unit_assert(az);
+       z = addzone(az, name, fname);
+       unit_assert(z);
+       outf = create_tmp_file(NULL);
+       if(!auth_zone_write_file(z, outf)) {
+               fatal_exit("write file failed for %s", fname);
+       }
+       checkfile(fname, outf);
+
+       del_tmp_file(fname);
+       del_tmp_file(outf);
+       auth_zones_delete(az);
+}
+
+/** parse q_ans structure for making query */
+static void
+q_ans_parse(struct q_ans* q, struct regional* region,
+       struct query_info** qinfo, int* fallback, uint8_t** dp_nm,
+       size_t* dp_nmlen)
+{
+       int ret;
+       uint8_t buf[65535];
+       size_t len, dname_len;
+
+       /* parse flags */
+       *fallback = 0; /* default fallback value */
+       if(strstr(q->flags, "fallback"))
+               *fallback = 1;
+       
+       /* parse zone */
+       *dp_nmlen = sizeof(buf);
+       if((ret=sldns_str2wire_dname_buf(q->zone, buf, dp_nmlen))!=0)
+               fatal_exit("cannot parse query dp zone %s : %s", q->zone,
+                       sldns_get_errorstr_parse(ret));
+       *dp_nm = regional_alloc_init(region, buf, *dp_nmlen);
+       if(!dp_nm) fatal_exit("out of memory");
+
+       /* parse query */
+       len = sizeof(buf);
+       dname_len = 0;
+       if((ret=sldns_str2wire_rr_question_buf(q->query, buf, &len, &dname_len,
+               *dp_nm, *dp_nmlen, NULL, 0))!=0)
+               fatal_exit("cannot parse query %s : %s", q->query,
+                       sldns_get_errorstr_parse(ret));
+       *qinfo = (struct query_info*)regional_alloc_zero(region,
+               sizeof(**qinfo));
+       if(!*qinfo) fatal_exit("out of memory");
+       (*qinfo)->qname = regional_alloc_init(region, buf, dname_len);
+       if(!(*qinfo)->qname) fatal_exit("out of memory");
+       (*qinfo)->qname_len = dname_len;
+       (*qinfo)->qtype = sldns_wirerr_get_type(buf, len, dname_len);
+       (*qinfo)->qclass = sldns_wirerr_get_class(buf, len, dname_len);
+}
+
+/** print flags to string */
+static void
+pr_flags(sldns_buffer* buf, uint16_t flags)
+{
+       char rcode[32];
+       sldns_buffer_printf(buf, ";flags");
+       if((flags&BIT_QR)!=0) sldns_buffer_printf(buf, " QR");
+       if((flags&BIT_AA)!=0) sldns_buffer_printf(buf, " AA");
+       if((flags&BIT_TC)!=0) sldns_buffer_printf(buf, " TC");
+       if((flags&BIT_RD)!=0) sldns_buffer_printf(buf, " RD");
+       if((flags&BIT_CD)!=0) sldns_buffer_printf(buf, " CD");
+       if((flags&BIT_RA)!=0) sldns_buffer_printf(buf, " RA");
+       if((flags&BIT_AD)!=0) sldns_buffer_printf(buf, " AD");
+       if((flags&BIT_Z)!=0) sldns_buffer_printf(buf, " Z");
+       sldns_wire2str_rcode_buf((int)(FLAGS_GET_RCODE(flags)),
+               rcode, sizeof(rcode));
+       sldns_buffer_printf(buf, " rcode %s", rcode);
+       sldns_buffer_printf(buf, "\n");
+}
+
+/** print RRs to string */
+static void
+pr_rrs(sldns_buffer* buf, struct reply_info* rep)
+{
+       char s[65536];
+       size_t i, j;
+       struct packed_rrset_data* d;
+       log_assert(rep->rrset_count == rep->an_numrrsets + rep->ns_numrrsets
+               + rep->ar_numrrsets);
+       for(i=0; i<rep->rrset_count; i++) {
+               /* section heading */
+               if(i == 0 && rep->an_numrrsets != 0)
+                       sldns_buffer_printf(buf, ";answer section\n");
+               else if(i == rep->an_numrrsets && rep->ns_numrrsets != 0)
+                       sldns_buffer_printf(buf, ";authority section\n");
+               else if(i == rep->an_numrrsets+rep->ns_numrrsets &&
+                       rep->ar_numrrsets != 0)
+                       sldns_buffer_printf(buf, ";additional section\n");
+               /* spool RRset */
+               d = (struct packed_rrset_data*)rep->rrsets[i]->entry.data;
+               for(j=0; j<d->count+d->rrsig_count; j++) {
+                       if(!packed_rr_to_string(rep->rrsets[i], j, 0,
+                               s, sizeof(s))) {
+                               fatal_exit("could not rr_to_string %d",
+                                       (int)i);
+                       }
+                       sldns_buffer_printf(buf, "%s", s);
+               }
+       }
+}
+
+/** create string for message */
+static char*
+msgtostr(struct dns_msg* msg)
+{
+       char* str;
+       sldns_buffer* buf = sldns_buffer_new(65535);
+       if(!buf) fatal_exit("out of memory");
+       if(!msg) {
+               sldns_buffer_printf(buf, "null packet\n");
+       } else {
+               pr_flags(buf, msg->rep->flags);
+               pr_rrs(buf, msg->rep);
+       }
+
+       str = strdup((char*)sldns_buffer_begin(buf));
+       if(!str) fatal_exit("out of memory");
+       sldns_buffer_free(buf);
+       return str;
+}
+
+/** find line diff between strings */
+static void
+line_diff(const char* p, const char* q, const char* pdesc, const char* qdesc)
+{
+       char* pdup, *qdup, *pl, *ql;
+       int line = 1;
+       pdup = strdup(p);
+       qdup = strdup(q);
+       if(!pdup || !qdup) fatal_exit("out of memory");
+       pl=pdup;
+       ql=qdup;
+       printf("linediff (<%s, >%s)\n", pdesc, qdesc);
+       while(pl && ql && *pl && *ql) {
+               char* ep = strchr(pl, '\n');
+               char* eq = strchr(ql, '\n');
+               /* terminate lines */
+               if(ep) *ep = 0;
+               if(eq) *eq = 0;
+               /* printout */
+               if(strcmp(pl, ql) == 0) {
+                       printf("%3d   %s\n", line, pl);
+               } else {
+                       printf("%3d < %s\n", line, pl);
+                       printf("%3d > %s\n", line, ql);
+               }
+               if(ep) *ep = '\n';
+               if(eq) *eq = '\n';
+               if(ep) pl = ep+1;
+               else pl = NULL;
+               if(eq) ql = eq+1;
+               else ql = NULL;
+               line++;
+       }
+       if(pl && *pl) {
+               printf("%3d < %s\n", line, pl);
+       }
+       if(ql && *ql) {
+               printf("%3d > %s\n", line, ql);
+       }
+       free(pdup);
+       free(qdup);
+}
+
+/** make q_ans query */
+static void
+q_ans_query(struct q_ans* q, struct auth_zones* az, struct query_info* qinfo,
+       struct regional* region, int expected_fallback, uint8_t* dp_nm,
+       size_t dp_nmlen)
+{
+       int ret, fallback = 0;
+       struct dns_msg* msg = NULL;
+       char* ans_str;
+       int oldv = verbosity;
+       /* increase verbosity to printout logic in authzone */
+       if(vbmp) verbosity = 4;
+       ret = auth_zones_lookup(az, qinfo, region, &msg, &fallback, dp_nm,
+               dp_nmlen);
+       if(vbmp) verbosity = oldv;
+
+       /* check the answer */
+       ans_str = msgtostr(msg);
+       /* printout if vbmp */
+       if(vbmp) printf("got (ret=%s%s):\n%s",
+               (ret?"ok":"fail"), (fallback?" fallback":""), ans_str);
+       /* check expected value for ret */
+       if(expected_fallback && ret != 0) {
+               /* ret is zero on fallback */
+               if(vbmp) printf("fallback expected, but "
+                       "return value is not false\n");
+               unit_assert(expected_fallback && ret == 0);
+       }
+       if(ret == 0) {
+               if(!expected_fallback) {
+                       if(vbmp) printf("return value is false, "
+                               "(unexpected)\n");
+               }
+               unit_assert(expected_fallback);
+       }
+       /* check expected value for fallback */
+       if(expected_fallback && !fallback) {
+               if(vbmp) printf("expected fallback, but fallback is no\n");
+       } else if(!expected_fallback && fallback) {
+               if(vbmp) printf("expected no fallback, but fallback is yes\n");
+       }
+       unit_assert( (expected_fallback&&fallback) ||
+               (!expected_fallback&&!fallback));
+       /* check answer string */
+       if(strcmp(q->answer, ans_str) != 0) {
+               if(vbmp) printf("wanted:\n%s", q->answer);
+               line_diff(q->answer, ans_str, "wanted", "got");
+       }
+       unit_assert(strcmp(q->answer, ans_str) == 0);
+       if(vbmp) printf("query ok\n\n");
+       free(ans_str);
+}
+
+/** check queries on a loaded zone */
+static void
+check_az_q_ans(struct auth_zones* az, struct q_ans* queries)
+{
+       struct q_ans* q;
+       struct regional* region = regional_create();
+       struct query_info* qinfo;
+       int fallback;
+       uint8_t* dp_nm;
+       size_t dp_nmlen;
+       for(q=queries; q->zone; q++) {
+               if(vbmp) printf("query %s: %s %s\n", q->zone, q->query,
+                       q->flags);
+               q_ans_parse(q, region, &qinfo, &fallback, &dp_nm, &dp_nmlen);
+               q_ans_query(q, az, qinfo, region, fallback, dp_nm, dp_nmlen);
+               regional_free_all(region);
+       }
+       regional_destroy(region);
+}
+
+/** check queries for a zone are returned as specified */
+static void
+check_queries(const char* name, const char* zone, struct q_ans* queries)
+{
+       struct auth_zones* az;
+       struct auth_zone* z;
+       char* fname;
+       if(vbmp) printf("check queries %s\n", name);
+       fname = create_tmp_file(zone);
+       az = auth_zones_create();
+       if(!az) fatal_exit("out of memory");
+       z = addzone(az, name, fname);
+       if(!z) fatal_exit("could not read zone for queries test");
+       del_tmp_file(fname);
+
+       /* run queries and test them */
+       check_az_q_ans(az, queries);
+
+       auth_zones_delete(az);
+}
+
+/** Test authzone compare_serial */
+static void
+authzone_compare_serial(void)
+{
+       if(vbmp) printf("Testing compare_serial\n");
+       unit_assert(compare_serial(0, 1) < 0);
+       unit_assert(compare_serial(1, 0) > 0);
+       unit_assert(compare_serial(0, 0) == 0);
+       unit_assert(compare_serial(1, 1) == 0);
+       unit_assert(compare_serial(0xf0000000, 0xf0000000) == 0);
+       unit_assert(compare_serial(0, 0xf0000000) > 0);
+       unit_assert(compare_serial(0xf0000000, 0) < 0);
+       unit_assert(compare_serial(0xf0000000, 0xf0000001) < 0);
+       unit_assert(compare_serial(0xf0000002, 0xf0000001) > 0);
+       unit_assert(compare_serial(0x70000000, 0x80000000) < 0);
+       unit_assert(compare_serial(0x90000000, 0x70000000) > 0);
+}
+
+/** Test authzone read from file */
+static void
+authzone_read_test(void)
+{
+       if(vbmp) printf("Testing read auth zone\n");
+       check_read_exact("example.com", zone_example_com);
+}
+
+/** Test authzone query from zone */
+static void
+authzone_query_test(void)
+{
+       if(vbmp) printf("Testing query auth zone\n");
+       check_queries("example.com", zone_example_com, example_com_queries);
+}
+
+/** test authzone code */
+void 
+authzone_test(void)
+{
+       unit_show_feature("authzone");
+       atexit(tmpfilecleanup);
+       authzone_compare_serial();
+       authzone_read_test();
+       authzone_query_test();
+}
diff --git a/usr.sbin/unbound/testcode/unitdname.c b/usr.sbin/unbound/testcode/unitdname.c
new file mode 100644 (file)
index 0000000..238c3ed
--- /dev/null
@@ -0,0 +1,861 @@
+/*
+ * testcode/unitdname.c - unit test for dname routines.
+ *
+ * Copyright (c) 2007, NLnet Labs. All rights reserved.
+ *
+ * This software is open source.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * Neither the name of the NLNET LABS nor the names of its contributors may
+ * be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+ * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+/**
+ * \file
+ * Calls dname unit tests. Exits with code 1 on a failure. 
+ */
+
+#include "config.h"
+#include "util/log.h"
+#include "testcode/unitmain.h"
+#include "util/data/dname.h"
+#include "sldns/sbuffer.h"
+#include "sldns/str2wire.h"
+
+/** put dname into buffer */
+static sldns_buffer*
+dname_to_buf(sldns_buffer* b, const char* str)
+{
+       int e;
+       size_t len = sldns_buffer_capacity(b);
+       sldns_buffer_clear(b);
+       e = sldns_str2wire_dname_buf(str, sldns_buffer_begin(b), &len);
+       if(e != 0)
+               fatal_exit("%s ldns: %s", __func__, 
+                       sldns_get_errorstr_parse(e));
+       sldns_buffer_set_position(b, len);
+       sldns_buffer_flip(b);
+       return b;
+}
+
+/** test query_dname_len function */
+static void 
+dname_test_qdl(sldns_buffer* buff)
+{
+       unit_show_func("util/data/dname.c", "query_dname_len");
+       unit_assert( query_dname_len(buff) == 0);
+       unit_assert( query_dname_len(dname_to_buf(buff, ".")) == 1 );
+       unit_assert( query_dname_len(dname_to_buf(buff, "bla.foo.")) == 9 );
+       unit_assert( query_dname_len(dname_to_buf(buff, "x.y.z.example.com."
+               )) == 19 );
+}
+
+/** test query_dname_tolower */
+static void
+dname_test_qdtl(sldns_buffer* buff)
+{
+       unit_show_func("util/data/dname.c", "query_dname_tolower");
+       sldns_buffer_write_at(buff, 0, "\012abCDeaBCde\003cOm\000", 16);
+       query_dname_tolower(sldns_buffer_begin(buff));
+       unit_assert( memcmp(sldns_buffer_begin(buff), 
+               "\012abcdeabcde\003com\000", 16) == 0);
+
+       sldns_buffer_write_at(buff, 0, "\001+\012abC{e-ZYXe\003NET\000", 18);
+       query_dname_tolower(sldns_buffer_begin(buff));
+       unit_assert( memcmp(sldns_buffer_begin(buff), 
+               "\001+\012abc{e-zyxe\003net\000", 18) == 0);
+
+       sldns_buffer_write_at(buff, 0, "\000", 1);
+       query_dname_tolower(sldns_buffer_begin(buff));
+       unit_assert( memcmp(sldns_buffer_begin(buff), "\000", 1) == 0);
+
+       sldns_buffer_write_at(buff, 0, "\002NL\000", 4);
+       query_dname_tolower(sldns_buffer_begin(buff));
+       unit_assert( memcmp(sldns_buffer_begin(buff), "\002nl\000", 4) == 0);
+}
+
+/** test query_dname_compare */
+static void
+dname_test_query_dname_compare(void)
+{
+       unit_show_func("util/data/dname.c", "query_dname_compare");
+       unit_assert(query_dname_compare((uint8_t*)"", (uint8_t*)"") == 0);
+       unit_assert(query_dname_compare((uint8_t*)"\001a", 
+                                       (uint8_t*)"\001a") == 0);
+       unit_assert(query_dname_compare((uint8_t*)"\003abc\001a", 
+                                       (uint8_t*)"\003abc\001a") == 0);
+       unit_assert(query_dname_compare((uint8_t*)"\003aBc\001a", 
+                                       (uint8_t*)"\003AbC\001A") == 0);
+       unit_assert(query_dname_compare((uint8_t*)"\003abc", 
+                                       (uint8_t*)"\003abc\001a") == -1);
+       unit_assert(query_dname_compare((uint8_t*)"\003abc\001a", 
+                                       (uint8_t*)"\003abc") == +1);
+       unit_assert(query_dname_compare((uint8_t*)"\003abc\001a", 
+                                       (uint8_t*)"") == +1);
+       unit_assert(query_dname_compare((uint8_t*)"", 
+                                       (uint8_t*)"\003abc\001a") == -1);
+       unit_assert(query_dname_compare((uint8_t*)"\003abc\001a", 
+                                       (uint8_t*)"\003xxx\001a") == -1);
+       unit_assert(query_dname_compare((uint8_t*)"\003axx\001a", 
+                                       (uint8_t*)"\003abc\001a") == 1);
+       unit_assert(query_dname_compare((uint8_t*)"\003abc\001a", 
+                                       (uint8_t*)"\003abc\001Z") == -1);
+       unit_assert(query_dname_compare((uint8_t*)"\003abc\001Z", 
+                                       (uint8_t*)"\003abc\001a") == 1);
+}
+
+/** test dname_count_labels */
+static void
+dname_test_count_labels(void)
+{
+       unit_show_func("util/data/dname.c", "dname_count_labels");
+       unit_assert(dname_count_labels((uint8_t*)"") == 1);
+       unit_assert(dname_count_labels((uint8_t*)"\003com") == 2);
+       unit_assert(dname_count_labels((uint8_t*)"\003org") == 2);
+       unit_assert(dname_count_labels((uint8_t*)"\007example\003com") == 3);
+       unit_assert(dname_count_labels((uint8_t*)"\003bla\007example\003com") 
+               == 4);
+}
+
+/** test dname_count_size_labels */
+static void
+dname_test_count_size_labels(void)
+{
+       size_t sz = 0;
+       unit_show_func("util/data/dname.c", "dname_count_size_labels");
+       unit_assert(dname_count_size_labels((uint8_t*)"", &sz) == 1);
+       unit_assert(sz == 1);
+       unit_assert(dname_count_size_labels((uint8_t*)"\003com", &sz) == 2);
+       unit_assert(sz == 5);
+       unit_assert(dname_count_size_labels((uint8_t*)"\003org", &sz) == 2);
+       unit_assert(sz == 5);
+       unit_assert(dname_count_size_labels((uint8_t*)"\007example\003com", 
+               &sz) == 3);
+       unit_assert(sz == 13);
+       unit_assert(dname_count_size_labels((uint8_t*)"\003bla\007example"
+               "\003com", &sz) == 4);
+       unit_assert(sz == 17);
+}
+
+
+/** test pkt_dname_len */
+static void
+dname_test_pkt_dname_len(sldns_buffer* buff)
+{
+       unit_show_func("util/data/dname.c", "pkt_dname_len");
+       sldns_buffer_clear(buff);
+       sldns_buffer_write(buff, "\000", 1);
+       sldns_buffer_flip(buff);
+       unit_assert( pkt_dname_len(buff) == 1 );
+       unit_assert( sldns_buffer_position(buff) == 1);
+
+       sldns_buffer_clear(buff);
+       sldns_buffer_write(buff, "\003org\000", 5);
+       sldns_buffer_flip(buff);
+       unit_assert( pkt_dname_len(buff) == 5 );
+       unit_assert( sldns_buffer_position(buff) == 5);
+
+       sldns_buffer_clear(buff);
+       sldns_buffer_write(buff, "\002os\007example\003org\000", 16);
+       sldns_buffer_flip(buff);
+       unit_assert( pkt_dname_len(buff) == 16 );
+       unit_assert( sldns_buffer_position(buff) == 16);
+
+       /* invalid compression pointer: to self */
+       sldns_buffer_clear(buff);
+       sldns_buffer_write(buff, "\300\000os\007example\003org\000", 17);
+       sldns_buffer_flip(buff);
+       unit_assert( pkt_dname_len(buff) == 0 );
+
+       /* valid compression pointer */
+       sldns_buffer_clear(buff);
+       sldns_buffer_write(buff, "\003com\000\040\300\000", 8);
+       sldns_buffer_flip(buff);
+       sldns_buffer_set_position(buff, 6);
+       unit_assert( pkt_dname_len(buff) == 5 );
+       unit_assert( sldns_buffer_position(buff) == 8);
+
+       /* unknown label type */
+       sldns_buffer_clear(buff);
+       sldns_buffer_write(buff, "\002os\107example\003org\000", 16);
+       sldns_buffer_flip(buff);
+       unit_assert( pkt_dname_len(buff) == 0 );
+
+       /* label too long */
+       sldns_buffer_clear(buff);
+       sldns_buffer_write(buff, "\002os\047example\003org\000", 16);
+       sldns_buffer_flip(buff);
+       unit_assert( pkt_dname_len(buff) == 0 );
+
+       /* label exceeds packet */
+       sldns_buffer_clear(buff);
+       sldns_buffer_write(buff, "\002os\007example\007org\004", 16);
+       sldns_buffer_flip(buff);
+       unit_assert( pkt_dname_len(buff) == 0 );
+
+       /* name very long */
+       sldns_buffer_clear(buff);
+       sldns_buffer_write(buff, 
+               "\020a1cdef5555544444"
+               "\020a2cdef5555544444"
+               "\020a3cdef5555544444"
+               "\020a4cdef5555544444"
+               "\020a5cdef5555544444"
+               "\020a6cdef5555544444"
+               "\020a7cdef5555544444"
+               "\020a8cdef5555544444"
+               "\020a9cdef5555544444"
+               "\020aAcdef5555544444"
+               "\020aBcdef5555544444"
+               "\020aCcdef5555544444"
+               "\020aDcdef5555544444"
+               "\020aEcdef5555544444"  /* 238 up to here */
+               "\007aabbccd"           /* 246 up to here */
+               "\007example\000"       /* 255 to here */
+               , 255);
+       sldns_buffer_flip(buff);
+       unit_assert( pkt_dname_len(buff) == 255 );
+       unit_assert( sldns_buffer_position(buff) == 255);
+
+       /* name too long */
+       sldns_buffer_clear(buff);
+       sldns_buffer_write(buff, 
+               "\020a1cdef5555544444"
+               "\020a2cdef5555544444"
+               "\020a3cdef5555544444"
+               "\020a4cdef5555544444"
+               "\020a5cdef5555544444"
+               "\020a6cdef5555544444"
+               "\020a7cdef5555544444"
+               "\020a8cdef5555544444"
+               "\020a9cdef5555544444"
+               "\020aAcdef5555544444"
+               "\020aBcdef5555544444"
+               "\020aCcdef5555544444"
+               "\020aXcdef5555544444"
+               "\020aXcdef5555544444"
+               "\020aXcdef5555544444"
+               "\020aDcdef5555544444"
+               "\020aEcdef5555544444"  /* 238 up to here */
+               "\007aabbccd"           /* 246 up to here */
+               "\007example\000"       /* 255 to here */
+               , 255);
+       sldns_buffer_flip(buff);
+       unit_assert( pkt_dname_len(buff) == 0 );
+}
+
+/** test dname_lab_cmp */
+static void
+dname_test_dname_lab_cmp(void)
+{
+       int ml = 0; /* number of labels that matched exactly */
+       unit_show_func("util/data/dname.c", "dname_lab_cmp");
+
+       /* test for equality succeeds */
+       unit_assert(dname_lab_cmp((uint8_t*)"", 1, (uint8_t*)"", 1, &ml) == 0);
+       unit_assert(ml == 1);
+       unit_assert(dname_lab_cmp(
+               (uint8_t*)"\003net", 2, 
+               (uint8_t*)"\003net", 2, 
+               &ml) == 0);
+       unit_assert(ml == 2);
+       unit_assert(dname_lab_cmp(
+               (uint8_t*)"\007example\003net", 3, 
+               (uint8_t*)"\007example\003net", 3, 
+               &ml) == 0);
+       unit_assert(ml == 3);
+       unit_assert(dname_lab_cmp(
+               (uint8_t*)"\004test\007example\003net", 4, 
+               (uint8_t*)"\004test\007example\003net", 4, 
+               &ml) == 0);
+       unit_assert(ml == 4);
+
+       /* root is smaller than anything else */
+       unit_assert(dname_lab_cmp(
+               (uint8_t*)"", 1, 
+               (uint8_t*)"\003net", 2, 
+               &ml) == -1);
+       unit_assert(ml == 1);
+       unit_assert(dname_lab_cmp(
+               (uint8_t*)"\003net", 2, 
+               (uint8_t*)"", 1, 
+               &ml) == 1);
+       unit_assert(ml == 1);
+       unit_assert(dname_lab_cmp(
+               (uint8_t*)"", 1, 
+               (uint8_t*)"\007example\003net", 3, 
+               &ml) == -1);
+       unit_assert(ml == 1);
+       unit_assert(dname_lab_cmp(
+               (uint8_t*)"\007example\003net", 3, 
+               (uint8_t*)"", 1, 
+               &ml) == 1);
+       unit_assert(ml == 1);
+
+       /* label length makes a difference */
+       unit_assert(dname_lab_cmp(
+               (uint8_t*)"\004neta", 2, 
+               (uint8_t*)"\003net", 2, 
+               &ml) != 0);
+       unit_assert(ml == 1);
+       unit_assert(dname_lab_cmp(
+               (uint8_t*)"\002ne", 2, 
+               (uint8_t*)"\004neta", 2, 
+               &ml) != 0);
+       unit_assert(ml == 1);
+
+       /* contents follow the zone apex */
+       unit_assert(dname_lab_cmp(
+               (uint8_t*)"\003bla\007example\003net", 4, 
+               (uint8_t*)"\007example\003net", 3, 
+               &ml) == 1);
+       unit_assert(ml == 3);
+       unit_assert(dname_lab_cmp(
+               (uint8_t*)"\007example\003net", 3, 
+               (uint8_t*)"\003bla\007example\003net", 4, 
+               &ml) == -1);
+       unit_assert(ml == 3);
+
+       /* label content makes a difference */
+       unit_assert(dname_lab_cmp(
+               (uint8_t*)"\003aag\007example\003net", 4, 
+               (uint8_t*)"\003bla\007example\003net", 4, 
+               &ml) == -1);
+       unit_assert(ml == 3);
+       unit_assert(dname_lab_cmp(
+               (uint8_t*)"\003aag\007example\003net", 4, 
+               (uint8_t*)"\003bla\007example\003net", 4, 
+               &ml) == -1);
+       unit_assert(ml == 3);
+       unit_assert(dname_lab_cmp(
+               (uint8_t*)"\003bla\003aag\007example\003net", 5, 
+               (uint8_t*)"\003aag\003bla\007example\003net", 5, 
+               &ml) == -1);
+       unit_assert(ml == 3);
+       unit_assert(dname_lab_cmp(
+               (uint8_t*)"\02sn\003opt\003aag\007example\003net", 6, 
+               (uint8_t*)"\02sn\003opt\003bla\007example\003net", 6, 
+               &ml) == -1);
+       unit_assert(ml == 3);
+
+       /* but lowercase/uppercase does not make a difference. */
+       unit_assert(dname_lab_cmp(
+               (uint8_t*)"\003bLa\007examPLe\003net", 4, 
+               (uint8_t*)"\003bla\007eXAmple\003nET", 4, 
+               &ml) == 0);
+       unit_assert(ml == 4);
+}
+
+/** test dname_subdomain_c */
+static void
+dname_test_subdomain(void)
+{
+       unit_show_func("util/data/dname.c", "dname_subdomain");
+       unit_assert(dname_subdomain_c(
+               (uint8_t*)"",
+               (uint8_t*)""));
+       unit_assert(dname_subdomain_c(
+               (uint8_t*)"\003com",
+               (uint8_t*)""));
+       unit_assert(!dname_subdomain_c(
+               (uint8_t*)"",
+               (uint8_t*)"\003com"));
+       unit_assert(dname_subdomain_c(
+               (uint8_t*)"\007example\003com",
+               (uint8_t*)"\003com"));
+       unit_assert(!dname_subdomain_c(
+               (uint8_t*)"\003com",
+               (uint8_t*)"\007example\003com"));
+       unit_assert(dname_subdomain_c(
+               (uint8_t*)"\007example\003com",
+               (uint8_t*)""));
+       unit_assert(!dname_subdomain_c(
+               (uint8_t*)"\003net",
+               (uint8_t*)"\003com"));
+       unit_assert(!dname_subdomain_c(
+               (uint8_t*)"\003net",
+               (uint8_t*)"\003org"));
+       unit_assert(!dname_subdomain_c(
+               (uint8_t*)"\007example\003net",
+               (uint8_t*)"\003org"));
+       unit_assert(!dname_subdomain_c(
+               (uint8_t*)"\003net",
+               (uint8_t*)"\007example\003org"));
+}
+
+/** test dname_strict_subdomain */
+static void
+dname_test_strict_subdomain(void)
+{
+       unit_show_func("util/data/dname.c", "dname_strict_subdomain");
+       unit_assert(!dname_strict_subdomain(
+               (uint8_t*)"", 1,
+               (uint8_t*)"", 1));
+       unit_assert(dname_strict_subdomain(
+               (uint8_t*)"\003com", 2,
+               (uint8_t*)"", 1));
+       unit_assert(!dname_strict_subdomain(
+               (uint8_t*)"", 1,
+               (uint8_t*)"\003com", 2));
+       unit_assert(dname_strict_subdomain(
+               (uint8_t*)"\007example\003com", 3,
+               (uint8_t*)"\003com", 2));
+       unit_assert(!dname_strict_subdomain(
+               (uint8_t*)"\003com", 2,
+               (uint8_t*)"\007example\003com", 3));
+       unit_assert(dname_strict_subdomain(
+               (uint8_t*)"\007example\003com", 3,
+               (uint8_t*)"", 1));
+       unit_assert(!dname_strict_subdomain(
+               (uint8_t*)"\003net", 2,
+               (uint8_t*)"\003com", 2));
+       unit_assert(!dname_strict_subdomain(
+               (uint8_t*)"\003net", 2,
+               (uint8_t*)"\003org", 2));
+       unit_assert(!dname_strict_subdomain(
+               (uint8_t*)"\007example\003net", 3,
+               (uint8_t*)"\003org", 2));
+       unit_assert(!dname_strict_subdomain(
+               (uint8_t*)"\003net", 2,
+               (uint8_t*)"\007example\003org", 3));
+}
+
+/** test dname_is_root */
+static void
+dname_test_isroot(void)
+{
+       unit_show_func("util/data/dname.c", "dname_isroot");
+       unit_assert(dname_is_root((uint8_t*)"\000"));
+       unit_assert(!dname_is_root((uint8_t*)"\001a\000"));
+       unit_assert(!dname_is_root((uint8_t*)"\005abvcd\003com\000"));
+       /* malformed dname in this test, but should work */
+       unit_assert(!dname_is_root((uint8_t*)"\077a\000"));
+       unit_assert(dname_is_root((uint8_t*)"\000"));
+}
+
+/** test dname_remove_label */
+static void
+dname_test_removelabel(void)
+{
+       uint8_t* orig = (uint8_t*)"\007example\003com\000";
+       uint8_t* n = orig;
+       size_t l = 13;
+       unit_show_func("util/data/dname.c", "dname_remove_label");
+       dname_remove_label(&n, &l);
+       unit_assert( n == orig+8 );
+       unit_assert( l == 5 );
+       dname_remove_label(&n, &l);
+       unit_assert( n == orig+12 );
+       unit_assert( l == 1 );
+       dname_remove_label(&n, &l);
+       unit_assert( n == orig+12 );
+       unit_assert( l == 1 );
+}
+
+/** test dname_signame_label_count */
+static void
+dname_test_sigcount(void)
+{
+       unit_show_func("util/data/dname.c", "dname_signame_label_count");
+       unit_assert(dname_signame_label_count((uint8_t*)"\000") == 0);
+       unit_assert(dname_signame_label_count((uint8_t*)"\001*\000") == 0);
+       unit_assert(dname_signame_label_count((uint8_t*)"\003xom\000") == 1);
+       unit_assert(dname_signame_label_count(
+               (uint8_t*)"\001*\003xom\000") == 1);
+       unit_assert(dname_signame_label_count(
+               (uint8_t*)"\007example\003xom\000") == 2);
+       unit_assert(dname_signame_label_count(
+               (uint8_t*)"\001*\007example\003xom\000") == 2);
+       unit_assert(dname_signame_label_count(
+               (uint8_t*)"\003www\007example\003xom\000") == 3);
+       unit_assert(dname_signame_label_count(
+               (uint8_t*)"\001*\003www\007example\003xom\000") == 3);
+}
+
+/** test dname_is_wild routine */
+static void
+dname_test_iswild(void)
+{
+       unit_show_func("util/data/dname.c", "dname_iswild");
+       unit_assert( !dname_is_wild((uint8_t*)"\000") );
+       unit_assert( dname_is_wild((uint8_t*)"\001*\000") );
+       unit_assert( !dname_is_wild((uint8_t*)"\003net\000") );
+       unit_assert( dname_is_wild((uint8_t*)"\001*\003net\000") );
+}
+
+/** test dname_canonical_compare */
+static void
+dname_test_canoncmp(void)
+{
+       unit_show_func("util/data/dname.c", "dname_canonical_compare");
+       /* equality */
+       unit_assert( dname_canonical_compare(
+               (uint8_t*)"\000",
+               (uint8_t*)"\000"
+               ) == 0);
+       unit_assert( dname_canonical_compare(
+               (uint8_t*)"\003net\000",
+               (uint8_t*)"\003net\000"
+               ) == 0);
+       unit_assert( dname_canonical_compare(
+               (uint8_t*)"\007example\003net\000",
+               (uint8_t*)"\007example\003net\000"
+               ) == 0);
+       unit_assert( dname_canonical_compare(
+               (uint8_t*)"\004test\007example\003net\000",
+               (uint8_t*)"\004test\007example\003net\000"
+               ) == 0);
+
+       /* subdomains */
+       unit_assert( dname_canonical_compare(
+               (uint8_t*)"\003com",
+               (uint8_t*)"\000"
+               ) == 1);
+       unit_assert( dname_canonical_compare(
+               (uint8_t*)"\000",
+               (uint8_t*)"\003com"
+               ) == -1);
+       unit_assert( dname_canonical_compare(
+               (uint8_t*)"\007example\003com",
+               (uint8_t*)"\003com"
+               ) == 1);
+       unit_assert( dname_canonical_compare(
+               (uint8_t*)"\003com",
+               (uint8_t*)"\007example\003com"
+               ) == -1);
+       unit_assert( dname_canonical_compare(
+               (uint8_t*)"\007example\003com",
+               (uint8_t*)"\000"
+               ) == 1);
+       unit_assert( dname_canonical_compare(
+               (uint8_t*)"\000",
+               (uint8_t*)"\007example\003com"
+               ) == -1);
+
+       /* compare rightmost label */
+       unit_assert( dname_canonical_compare(
+               (uint8_t*)"\003com",
+               (uint8_t*)"\003net"
+               ) == -1);
+       unit_assert( dname_canonical_compare(
+               (uint8_t*)"\003net",
+               (uint8_t*)"\003com"
+               ) == 1);
+       unit_assert( dname_canonical_compare(
+               (uint8_t*)"\003net",
+               (uint8_t*)"\003org"
+               ) == -1);
+       unit_assert( dname_canonical_compare(
+               (uint8_t*)"\007example\003net",
+               (uint8_t*)"\003org"
+               ) == -1);
+       unit_assert( dname_canonical_compare(
+               (uint8_t*)"\003org",
+               (uint8_t*)"\007example\003net"
+               ) == 1);
+
+       /* label length makes a difference; but only if rest is equal */
+       unit_assert( dname_canonical_compare(
+               (uint8_t*)"\004neta",
+               (uint8_t*)"\003net"
+               ) == 1);
+       unit_assert( dname_canonical_compare(
+               (uint8_t*)"\002ne",
+               (uint8_t*)"\004neta"
+               ) == -1);
+
+       /* label content */
+       unit_assert( dname_canonical_compare(
+               (uint8_t*)"\003aag\007example\003net",
+               (uint8_t*)"\003bla\007example\003net"
+               ) == -1);
+       unit_assert( dname_canonical_compare(
+               (uint8_t*)"\003bla\007example\003net",
+               (uint8_t*)"\003aag\007example\003net"
+               ) == 1);
+       unit_assert( dname_canonical_compare(
+               (uint8_t*)"\003bla\003aag\007example\003net",
+               (uint8_t*)"\003aag\003bla\007example\003net"
+               ) == -1);
+       unit_assert( dname_canonical_compare(
+               (uint8_t*)"\02sn\003opt\003aag\007example\003net",
+               (uint8_t*)"\02sn\003opt\003bla\007example\003net"
+               ) == -1);
+
+       /* lowercase during compare */
+       unit_assert( dname_canonical_compare(
+               (uint8_t*)"\003bLa\007examPLe\003net",
+               (uint8_t*)"\003bla\007eXAmple\003nET"
+               ) == 0);
+
+       /* example from 4034 */
+       /* example a.example yljkjljk.a.example Z.a.example zABC.a.EXAMPLE
+        z.example \001.z.example *.z.example \200.z.example */
+       unit_assert( dname_canonical_compare(
+               (uint8_t*)"",
+               (uint8_t*)"\007example"
+               ) == -1);
+       unit_assert( dname_canonical_compare(
+               (uint8_t*)"\007example",
+               (uint8_t*)"\001a\007example"
+               ) == -1);
+       unit_assert( dname_canonical_compare(
+               (uint8_t*)"\001a\007example",
+               (uint8_t*)"\010yljkjljk\001a\007example"
+               ) == -1);
+       unit_assert( dname_canonical_compare(
+               (uint8_t*)"\010yljkjljk\001a\007example",
+               (uint8_t*)"\001Z\001a\007example"
+               ) == -1);
+       unit_assert( dname_canonical_compare(
+               (uint8_t*)"\001Z\001a\007example",
+               (uint8_t*)"\004zABC\001a\007EXAMPLE"
+               ) == -1);
+       unit_assert( dname_canonical_compare(
+               (uint8_t*)"\004zABC\001a\007EXAMPLE",
+               (uint8_t*)"\001z\007example"
+               ) == -1);
+       unit_assert( dname_canonical_compare(
+               (uint8_t*)"\001z\007example",
+               (uint8_t*)"\001\001\001z\007example"
+               ) == -1);
+       unit_assert( dname_canonical_compare(
+               (uint8_t*)"\001\001\001z\007example",
+               (uint8_t*)"\001*\001z\007example"
+               ) == -1);
+       unit_assert( dname_canonical_compare(
+               (uint8_t*)"\001*\001z\007example",
+               (uint8_t*)"\001\200\001z\007example"
+               ) == -1);
+       /* same example in reverse */
+       unit_assert( dname_canonical_compare(
+               (uint8_t*)"\007example",
+               (uint8_t*)""
+               ) == 1);
+       unit_assert( dname_canonical_compare(
+               (uint8_t*)"\001a\007example",
+               (uint8_t*)"\007example"
+               ) == 1);
+       unit_assert( dname_canonical_compare(
+               (uint8_t*)"\010yljkjljk\001a\007example",
+               (uint8_t*)"\001a\007example"
+               ) == 1);
+       unit_assert( dname_canonical_compare(
+               (uint8_t*)"\001Z\001a\007example",
+               (uint8_t*)"\010yljkjljk\001a\007example"
+               ) == 1);
+       unit_assert( dname_canonical_compare(
+               (uint8_t*)"\004zABC\001a\007EXAMPLE",
+               (uint8_t*)"\001Z\001a\007example"
+               ) == 1);
+       unit_assert( dname_canonical_compare(
+               (uint8_t*)"\001z\007example",
+               (uint8_t*)"\004zABC\001a\007EXAMPLE"
+               ) == 1);
+       unit_assert( dname_canonical_compare(
+               (uint8_t*)"\001\001\001z\007example",
+               (uint8_t*)"\001z\007example"
+               ) == 1);
+       unit_assert( dname_canonical_compare(
+               (uint8_t*)"\001*\001z\007example",
+               (uint8_t*)"\001\001\001z\007example"
+               ) == 1);
+       unit_assert( dname_canonical_compare(
+               (uint8_t*)"\001\200\001z\007example",
+               (uint8_t*)"\001*\001z\007example"
+               ) == 1);
+       /* same example for equality */
+       unit_assert( dname_canonical_compare(
+               (uint8_t*)"\007example",
+               (uint8_t*)"\007example"
+               ) == 0);
+       unit_assert( dname_canonical_compare(
+               (uint8_t*)"\001a\007example",
+               (uint8_t*)"\001a\007example"
+               ) == 0);
+       unit_assert( dname_canonical_compare(
+               (uint8_t*)"\010yljkjljk\001a\007example",
+               (uint8_t*)"\010yljkjljk\001a\007example"
+               ) == 0);
+       unit_assert( dname_canonical_compare(
+               (uint8_t*)"\001Z\001a\007example",
+               (uint8_t*)"\001Z\001a\007example"
+               ) == 0);
+       unit_assert( dname_canonical_compare(
+               (uint8_t*)"\004zABC\001a\007EXAMPLE",
+               (uint8_t*)"\004zABC\001a\007EXAMPLE"
+               ) == 0);
+       unit_assert( dname_canonical_compare(
+               (uint8_t*)"\001z\007example",
+               (uint8_t*)"\001z\007example"
+               ) == 0);
+       unit_assert( dname_canonical_compare(
+               (uint8_t*)"\001\001\001z\007example",
+               (uint8_t*)"\001\001\001z\007example"
+               ) == 0);
+       unit_assert( dname_canonical_compare(
+               (uint8_t*)"\001*\001z\007example",
+               (uint8_t*)"\001*\001z\007example"
+               ) == 0);
+       unit_assert( dname_canonical_compare(
+               (uint8_t*)"\001\200\001z\007example",
+               (uint8_t*)"\001\200\001z\007example"
+               ) == 0);
+}
+
+/** Test dname_get_shared_topdomain */
+static void
+dname_test_topdomain(void)
+{
+       unit_show_func("util/data/dname.c", "dname_get_shared_topdomain");
+       unit_assert( query_dname_compare(
+               dname_get_shared_topdomain(
+                       (uint8_t*)"",
+                       (uint8_t*)""), 
+               (uint8_t*)"") == 0);
+       unit_assert( query_dname_compare(
+               dname_get_shared_topdomain(
+                       (uint8_t*)"\003www\007example\003com",
+                       (uint8_t*)"\003www\007example\003com"), 
+               (uint8_t*)"\003www\007example\003com") == 0);
+       unit_assert( query_dname_compare(
+               dname_get_shared_topdomain(
+                       (uint8_t*)"\003www\007example\003com",
+                       (uint8_t*)"\003bla\007example\003com"), 
+               (uint8_t*)"\007example\003com") == 0);
+}
+
+/** Test dname_valid */
+static void
+dname_test_valid(void)
+{
+       unit_show_func("util/data/dname.c", "dname_valid");
+       unit_assert( dname_valid( 
+                       (uint8_t*)"\003www\007example\003com", 255) == 17);
+       unit_assert( dname_valid((uint8_t*)"", 255) == 1);
+       unit_assert( dname_valid( (uint8_t*)
+               "\020a1cdef5555544444"
+               "\020a2cdef5555544444"
+               "\020a3cdef5555544444"
+               "\020a4cdef5555544444"
+               "\020a5cdef5555544444"
+               "\020a6cdef5555544444"
+               "\020a7cdef5555544444"
+               "\020a8cdef5555544444"
+               "\020a9cdef5555544444"
+               "\020aAcdef5555544444"
+               "\020aBcdef5555544444"
+               "\020aCcdef5555544444"
+               "\020aDcdef5555544444"
+               "\020aEcdef5555544444"  /* 238 up to here */
+               "\007aabbccd"           /* 246 up to here */
+               "\007example\000"       /* 255 to here */
+               , 255) == 255);
+       unit_assert( dname_valid( (uint8_t*)
+               "\020a1cdef5555544444"
+               "\020a2cdef5555544444"
+               "\020a3cdef5555544444"
+               "\020a4cdef5555544444"
+               "\020a5cdef5555544444"
+               "\020a6cdef5555544444"
+               "\020a7cdef5555544444"
+               "\020a8cdef5555544444"
+               "\020a9cdef5555544444"
+               "\020aAcdef5555544444"
+               "\020aBcdef5555544444"
+               "\020aCcdef5555544444"
+               "\020aDcdef5555544444"
+               "\020aEcdef5555544444"  /* 238 up to here */
+               "\007aabbccd"           /* 246 up to here */
+               "\010exampleX\000"      /* 256 to here */
+               , 4096) == 0);
+}
+
+/** test pkt_dname_tolower */
+static void
+dname_test_pdtl(sldns_buffer* loopbuf, sldns_buffer* boundbuf)
+{
+       unit_show_func("util/data/dname.c", "pkt_dname_tolower");
+       pkt_dname_tolower(loopbuf, sldns_buffer_at(loopbuf, 12));
+       pkt_dname_tolower(boundbuf, sldns_buffer_at(boundbuf, 12));
+}
+
+/** setup looped dname and out-of-bounds dname ptr */
+static void
+dname_setup_bufs(sldns_buffer* loopbuf, sldns_buffer* boundbuf)
+{
+       sldns_buffer_write_u16(loopbuf, 0xd54d);  /* id */
+       sldns_buffer_write_u16(loopbuf, 0x12);    /* flags  */
+       sldns_buffer_write_u16(loopbuf, 1);       /* qdcount */
+       sldns_buffer_write_u16(loopbuf, 0);       /* ancount */
+       sldns_buffer_write_u16(loopbuf, 0);       /* nscount */
+       sldns_buffer_write_u16(loopbuf, 0);       /* arcount */
+       sldns_buffer_write_u8(loopbuf, 0xc0); /* PTR back at itself */
+       sldns_buffer_write_u8(loopbuf, 0x0c);
+       sldns_buffer_flip(loopbuf);
+
+       sldns_buffer_write_u16(boundbuf, 0xd54d);  /* id */
+       sldns_buffer_write_u16(boundbuf, 0x12);    /* flags  */
+       sldns_buffer_write_u16(boundbuf, 1);       /* qdcount */
+       sldns_buffer_write_u16(boundbuf, 0);       /* ancount */
+       sldns_buffer_write_u16(boundbuf, 0);       /* nscount */
+       sldns_buffer_write_u16(boundbuf, 0);       /* arcount */
+       sldns_buffer_write_u8(boundbuf, 0x01); /* len=1 */
+       sldns_buffer_write_u8(boundbuf, (uint8_t)'A'); /* A. label */
+       sldns_buffer_write_u8(boundbuf, 0xc0); /* PTR out of bounds */
+       sldns_buffer_write_u8(boundbuf, 0xcc);
+       sldns_buffer_flip(boundbuf);
+}
+
+void dname_test(void)
+{
+       sldns_buffer* loopbuf = sldns_buffer_new(14);
+       sldns_buffer* boundbuf = sldns_buffer_new(16);
+       sldns_buffer* buff = sldns_buffer_new(65800);
+       unit_assert(loopbuf && boundbuf && buff);
+       sldns_buffer_flip(buff);
+       dname_setup_bufs(loopbuf, boundbuf);
+       dname_test_qdl(buff);
+       dname_test_qdtl(buff);
+       dname_test_pdtl(loopbuf, boundbuf);
+       dname_test_query_dname_compare();
+       dname_test_count_labels();
+       dname_test_count_size_labels();
+       dname_test_dname_lab_cmp();
+       dname_test_pkt_dname_len(buff);
+       dname_test_strict_subdomain();
+       dname_test_subdomain();
+       dname_test_isroot();
+       dname_test_removelabel();
+       dname_test_sigcount();
+       dname_test_iswild();
+       dname_test_canoncmp();
+       dname_test_topdomain();
+       dname_test_valid();
+       sldns_buffer_free(buff);
+       sldns_buffer_free(loopbuf);
+       sldns_buffer_free(boundbuf);
+}
diff --git a/usr.sbin/unbound/testcode/unitecs.c b/usr.sbin/unbound/testcode/unitecs.c
new file mode 100644 (file)
index 0000000..097ae9e
--- /dev/null
@@ -0,0 +1,284 @@
+/*
+ * testcode/unitecs.c - unit test for ecs routines.
+ *
+ * Copyright (c) 2013, NLnet Labs. All rights reserved.
+ *
+ * This software is open source.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * Neither the name of the NLNET LABS nor the names of its contributors may
+ * be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+/**
+ * \file
+ * Calls ecs related unit tests. Exits with code 1 on a failure. 
+ */
+
+#include "config.h"
+
+#ifdef CLIENT_SUBNET
+
+#include "util/log.h"
+#include "util/module.h"
+#include "testcode/unitmain.h"
+#include "edns-subnet/addrtree.h"
+#include "edns-subnet/subnetmod.h"
+
+/*
+       void printkey(addrkey_t *k, addrlen_t bits)
+       {
+               int byte;
+               int bytes = bits/8 + ((bits%8)>0);
+               char msk = 0xFF;
+               for (byte = 0; byte < bytes; byte++) {
+                       //~ if (byte+1 == bytes)
+                               //~ msk = 0xFF<<(8-bits%8);
+                       printf("%02x ", k[byte]&msk);
+               }
+       }
+
+       void print_tree(struct addrnode* node, int indent, int maxdepth)
+       {
+               struct addredge* edge;
+               int i, s, byte;
+               if (indent == 0) printf("-----Tree-----\n");
+               if (indent > maxdepth) {
+                       printf("\n");
+                       return;
+               }
+               printf("[node elem:%d] (%d)\n", node->elem != NULL, node);
+               for (i = 0; i<2; i++) {
+                       if (node->edge[i]) {
+                               for (s = 0; s < indent; s++) printf(" ");
+                               printkey(node->edge[i]->str, node->edge[i]->len);
+                               printf("(len %d bits, %d bytes) ", node->edge[i]->len, 
+                                       node->edge[i]->len/8 + ((node->edge[i]->len%8)>0));
+                               print_tree(node->edge[i]->node, indent+1, maxdepth);
+                       }
+               }       
+               if (indent == 0) printf("-----Tree-----");
+       }
+*/
+
+/* what should we check?
+ * X - is it balanced? (a node with 1 child should not have  
+ * a node with 1 child MUST have elem
+ * child must be sub of parent
+ * edge must be longer than parent edge
+ * */
+static int addrtree_inconsistent_subtree(struct addrtree* tree, 
+       struct addredge* parent_edge, addrlen_t depth)
+{
+       struct addredge* edge;
+       struct addrnode* node = parent_edge->node;
+       int childcount, i, r;
+       if (depth > tree->max_depth) return 15;
+       childcount = (node->edge[0] != NULL) + (node->edge[1] != NULL);
+       /* Only nodes with 2 children should possibly have no element. */
+       if (childcount < 2 && !node->elem) return 10;
+       for (i = 0; i<2; i++) {
+               edge = node->edge[i];
+               if (!edge) continue;
+               if (!edge->node) return 11;
+               if (!edge->str) return 12;
+               if (edge->len <= parent_edge->len) return 13;
+               if (!unittest_wrapper_addrtree_issub(parent_edge->str,
+                               parent_edge->len, edge->str, edge->len, 0))
+                       return 14;
+               if ((r = addrtree_inconsistent_subtree(tree, edge, depth+1)) != 0)
+                       return 100+r;
+       }
+       return 0;
+}
+
+static int addrtree_inconsistent(struct addrtree* tree)
+{
+       struct addredge* edge;
+       int i, r;
+       
+       if (!tree) return 0;
+       if (!tree->root) return 1;
+       
+       for (i = 0; i<2; i++) {
+               edge = tree->root->edge[i];
+               if (!edge) continue;
+               if (!edge->node) return 3;
+               if (!edge->str) return 4;
+               if ((r = addrtree_inconsistent_subtree(tree, edge, 1)) != 0)
+                       return r;
+       }
+       return 0;
+}
+
+static addrlen_t randomkey(addrkey_t **k, int maxlen)
+{
+       int byte;
+       int bits = rand() % maxlen;
+       int bytes = bits/8 + (bits%8>0); /*ceil*/
+       *k = (addrkey_t *) malloc(bytes * sizeof(addrkey_t));
+       for (byte = 0; byte < bytes; byte++) {
+               (*k)[byte] = (addrkey_t)(rand() & 0xFF);
+       }
+       return (addrlen_t)bits;
+}
+
+static void elemfree(void *envptr, void *elemptr)
+{
+       struct reply_info *elem = (struct reply_info *)elemptr;
+       (void)envptr;
+       free(elem);
+}
+
+static void consistency_test(void)
+{
+       addrlen_t l;
+       time_t i;
+       unsigned int count;
+       addrkey_t *k;
+       struct addrtree* t;
+       struct module_env env;
+       struct reply_info *elem;
+       time_t timenow = 0;
+       unit_show_func("edns-subnet/addrtree.h", "Tree consistency check");
+       srand(9195); /* just some value for reproducibility */
+
+       t = addrtree_create(100, &elemfree, &unittest_wrapper_subnetmod_sizefunc, &env, 0);
+       count = t->node_count;
+       unit_assert(count == 0);
+       for (i = 0; i < 1000; i++) {
+               l = randomkey(&k, 128);
+               elem = (struct reply_info *) calloc(1, sizeof(struct reply_info));
+               addrtree_insert(t, k, l, 64, elem, timenow + 10, timenow);
+               /* This should always hold because no items ever expire. They
+                * could be overwritten, though. */
+               unit_assert( count <= t->node_count );
+               count = t->node_count;
+               free(k);
+               unit_assert( !addrtree_inconsistent(t) );
+       }
+       addrtree_delete(t);
+
+       unit_show_func("edns-subnet/addrtree.h", "Tree consistency with purge");
+       t = addrtree_create(8, &elemfree, &unittest_wrapper_subnetmod_sizefunc, &env, 0);
+       unit_assert(t->node_count == 0);
+       for (i = 0; i < 1000; i++) {
+               l = randomkey(&k, 128);
+               elem = (struct reply_info *) calloc(1, sizeof(struct reply_info));
+               addrtree_insert(t, k, l, 64, elem, i + 10, i);
+               free(k);
+               unit_assert( !addrtree_inconsistent(t) );
+       }
+       addrtree_delete(t);
+
+       unit_show_func("edns-subnet/addrtree.h", "Tree consistency with limit");
+       t = addrtree_create(8, &elemfree, &unittest_wrapper_subnetmod_sizefunc, &env, 27);
+       unit_assert(t->node_count == 0);
+       for (i = 0; i < 1000; i++) {
+               l = randomkey(&k, 128);
+               elem = (struct reply_info *) calloc(1, sizeof(struct reply_info));
+               addrtree_insert(t, k, l, 64, elem, i + 10, i);
+               unit_assert( t->node_count <= 27);
+               free(k);
+               unit_assert( !addrtree_inconsistent(t) );
+       }
+       addrtree_delete(t);
+}
+
+static void issub_test(void)
+{
+       addrkey_t k1[] = {0x55, 0x55, 0x5A};
+       addrkey_t k2[] = {0x55, 0x5D, 0x5A};
+       unit_show_func("edns-subnet/addrtree.h", "issub");
+       unit_assert( !unittest_wrapper_addrtree_issub(k1, 24, k2, 24,  0) );
+       unit_assert(  unittest_wrapper_addrtree_issub(k1,  8, k2, 16,  0) );
+       unit_assert(  unittest_wrapper_addrtree_issub(k2, 12, k1, 13,  0) );
+       unit_assert( !unittest_wrapper_addrtree_issub(k1, 16, k2, 12,  0) );
+       unit_assert(  unittest_wrapper_addrtree_issub(k1, 12, k2, 12,  0) );
+       unit_assert( !unittest_wrapper_addrtree_issub(k1, 13, k2, 13,  0) );
+       unit_assert(  unittest_wrapper_addrtree_issub(k1, 24, k2, 24, 13) );
+       unit_assert( !unittest_wrapper_addrtree_issub(k1, 24, k2, 20, 13) );
+       unit_assert(  unittest_wrapper_addrtree_issub(k1, 20, k2, 24, 13) );
+}
+
+static void getbit_test(void)
+{
+       addrkey_t k1[] = {0x55, 0x55, 0x5A};
+       int i;
+       unit_show_func("edns-subnet/addrtree.h", "getbit");
+       for(i = 0; i<20; i++) {
+               unit_assert( unittest_wrapper_addrtree_getbit(k1, 20, (addrlen_t)i) == (i&1) );
+       }
+}
+
+static void bits_common_test(void)
+{
+       addrkey_t k1[] = {0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0};
+       addrkey_t k2[] = {0,0,0,0,0,0,0,0};
+       addrlen_t i;
+       
+       unit_show_func("edns-subnet/addrtree.h", "bits_common");
+       for(i = 0; i<64; i++) {
+               unit_assert( unittest_wrapper_addrtree_bits_common(k1, 64, k1, 64, i) == 64 );
+       }
+       for(i = 0; i<8; i++) {
+               k2[i] = k1[i]^(1<<i);
+       }
+       unit_assert( unittest_wrapper_addrtree_bits_common(k1, 64, k2, 64,  0) == 0*8+7 );
+       unit_assert( unittest_wrapper_addrtree_bits_common(k1, 64, k2, 64,  8) == 1*8+6 );
+       unit_assert( unittest_wrapper_addrtree_bits_common(k1, 64, k2, 64, 16) == 2*8+5 );
+       unit_assert( unittest_wrapper_addrtree_bits_common(k1, 64, k2, 64, 24) == 3*8+4 );
+       unit_assert( unittest_wrapper_addrtree_bits_common(k1, 64, k2, 64, 32) == 4*8+3 );
+       unit_assert( unittest_wrapper_addrtree_bits_common(k1, 64, k2, 64, 40) == 5*8+2 );
+       unit_assert( unittest_wrapper_addrtree_bits_common(k1, 64, k2, 64, 48) == 6*8+1 );
+       unit_assert( unittest_wrapper_addrtree_bits_common(k1, 64, k2, 64, 56) == 7*8+0 );
+}
+
+static void cmpbit_test(void)
+{
+       addrkey_t k1[] = {0xA5, 0x0F};
+       addrkey_t k2[] = {0x5A, 0xF0};
+       addrlen_t i;
+       
+       unit_show_func("edns-subnet/addrtree.h", "cmpbit");
+       for(i = 0; i<16; i++) {
+               unit_assert( !unittest_wrapper_addrtree_cmpbit(k1,k1,i) );
+               unit_assert(  unittest_wrapper_addrtree_cmpbit(k1,k2,i) );
+       }
+}
+
+void ecs_test(void)
+{
+       unit_show_feature("ecs");
+       cmpbit_test();
+       bits_common_test();
+       getbit_test();
+       issub_test();
+       consistency_test();
+}
+#endif /* CLIENT_SUBNET */
+
diff --git a/usr.sbin/unbound/testcode/unitldns.c b/usr.sbin/unbound/testcode/unitldns.c
new file mode 100644 (file)
index 0000000..e27e46e
--- /dev/null
@@ -0,0 +1,218 @@
+/*
+ * testcode/unitldns.c - unit test for ldns routines.
+ *
+ * Copyright (c) 2014, NLnet Labs. All rights reserved.
+ *
+ * This software is open source.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * Neither the name of the NLNET LABS nor the names of its contributors may
+ * be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+ * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+/**
+ * \file
+ * Calls ldns unit tests. Exits with code 1 on a failure. 
+ */
+
+#include "config.h"
+#include "util/log.h"
+#include "testcode/unitmain.h"
+#include "sldns/sbuffer.h"
+#include "sldns/str2wire.h"
+#include "sldns/wire2str.h"
+
+/** verbose this unit test */
+static int vbmp = 0; 
+
+/** print buffer to hex into string */
+static void
+buf_to_hex(uint8_t* b, size_t blen, char* s, size_t slen)
+{
+       const char* h = "0123456789ABCDEF";
+       size_t i;
+       if(slen < blen*2+2 && vbmp) printf("hexstring buffer too small\n");
+       unit_assert(slen >= blen*2+2);
+       for(i=0; i<blen; i++) {
+               s[i*2] = h[(b[i]&0xf0)>>4];
+               s[i*2+1] = h[b[i]&0x0f];
+       }
+       s[blen*2] = '\n';
+       s[blen*2+1] = 0;
+}
+
+/** Transform input.
+ * @param txt_in: input text format.
+ * @param wire1: output wireformat in hex (txt_in converted to wire).
+ * @param txt_out: output text format (converted from wire_out).
+ * @param wire2: output wireformat in hex, txt_out converted back to wireformat.
+ * @param bufs: size of the text buffers.
+ */
+static void
+rr_transform(char* txt_in, char* wire1, char* txt_out, char* wire2, 
+       size_t bufs)
+{
+       uint8_t b[65536];
+       size_t len;
+       int err;
+
+       len = sizeof(b);
+       err = sldns_str2wire_rr_buf(txt_in, b, &len, NULL, 3600,
+               NULL, 0, NULL, 0);
+       if(err != 0) {
+               if(vbmp) printf("sldns_str2wire_rr_buf, pos %d: %s\n",
+                       LDNS_WIREPARSE_OFFSET(err),
+                       sldns_get_errorstr_parse(err));
+       }
+       unit_assert(err == 0);
+       buf_to_hex(b, len, wire1, bufs);
+       if(vbmp) printf("wire1: %s", wire1);
+
+       err = sldns_wire2str_rr_buf(b, len, txt_out, bufs);
+       unit_assert(err < (int)bufs && err > 0);
+       if(vbmp) printf("txt: %s", txt_out);
+
+       len = sizeof(b);
+       err = sldns_str2wire_rr_buf(txt_out, b, &len, NULL, 3600,
+               NULL, 0, NULL, 0);
+       if(err != 0) {
+               if(vbmp) printf("sldns_str2wire_rr_buf-2, pos %d: %s\n",
+                       LDNS_WIREPARSE_OFFSET(err),
+                       sldns_get_errorstr_parse(err));
+       }
+       unit_assert(err == 0);
+       buf_to_hex(b, len, wire2, bufs);
+       if(vbmp) printf("wire2: %s", wire2);
+}
+
+/** Check if results are correct */
+static void
+rr_checks(char* wire_chk, char* txt_chk, char* txt_out, char* wire_out,
+       char* back)
+{
+#ifdef __APPLE__
+       /* the wiretostr on ipv6 is weird on apple, we cannot check it.
+        * skip AAAA on OSX */
+       if(strstr(txt_out, "IN  AAAA"))
+               txt_out = txt_chk; /* skip this test, but test wirefmt */
+                       /* so we know that txt_out back to wire is the same */
+#endif
+
+       if(strcmp(txt_chk, txt_out) != 0 && vbmp)
+               printf("txt different\n");
+       if(strcmp(wire_chk, wire_out) != 0 && vbmp)
+               printf("wire1 different\n");
+       if(strcmp(wire_chk, back) != 0 && vbmp)
+               printf("wire2 different\n");
+
+       unit_assert(strcmp(txt_chk, txt_out) == 0);
+       unit_assert(strcmp(wire_chk, wire_out) == 0);
+       unit_assert(strcmp(wire_chk, back) == 0);
+}
+
+/** read rrs to and from string, and wireformat
+ * Skips empty lines and comments.
+ * @param input: input file with text format.
+ * @param check: check file with hex and then textformat
+ */
+static void
+rr_test_file(const char* input, const char* check)
+{
+       size_t bufs = 131072;
+       FILE* inf, *chf, *of;
+       int lineno = 0, chlineno = 0;
+       char* txt_in = (char*)malloc(bufs);
+       char* txt_out = (char*)malloc(bufs);
+       char* txt_chk = (char*)malloc(bufs);
+       char* wire_out = (char*)malloc(bufs);
+       char* wire_chk = (char*)malloc(bufs);
+       char* back = (char*)malloc(bufs);
+       if(!txt_in || !txt_out || !txt_chk || !wire_out || !wire_chk || !back)
+               fatal_exit("malloc failure");
+       inf = fopen(input, "r");
+       if(!inf) fatal_exit("cannot open %s: %s", input, strerror(errno));
+       chf = fopen(check, "r");
+       if(!chf) fatal_exit("cannot open %s: %s", check, strerror(errno));
+
+       of = NULL;
+       if(0) {
+               /* debug: create check file */
+               of = fopen("outputfile", "w");
+               if(!of) fatal_exit("cannot write output: %s", strerror(errno));
+       }
+
+       while(fgets(txt_in, (int)bufs, inf)) {
+               lineno++;
+               if(vbmp) printf("\n%s:%d %s", input, lineno, txt_in);
+               /* skip empty lines and comments */
+               if(txt_in[0] == 0 || txt_in[0] == '\n' || txt_in[0] == ';')
+                       continue;
+               /* read check lines */
+               if(!fgets(wire_chk, (int)bufs, chf))
+                       printf("%s too short\n", check);
+               if(!fgets(txt_chk, (int)bufs, chf))
+                       printf("%s too short\n", check);
+               chlineno += 2;
+               if(vbmp) printf("%s:%d %s", check, chlineno-1, wire_chk);
+               if(vbmp) printf("%s:%d %s", check, chlineno, txt_chk);
+               /* generate results */
+               rr_transform(txt_in, wire_out, txt_out, back, bufs);
+               /* checks */
+               if(of) {
+                       fprintf(of, "%s%s", wire_out, txt_out);
+               } else {
+                       rr_checks(wire_chk, txt_chk, txt_out, wire_out, back);
+               }
+       }
+       
+       if(of) fclose(of);
+       fclose(inf);
+       fclose(chf);
+       free(txt_in);
+       free(txt_out);
+       free(txt_chk);
+       free(wire_out);
+       free(wire_chk);
+       free(back);
+}
+
+/** read rrs to and from string, to and from wireformat */
+static void
+rr_tests(void)
+{
+       rr_test_file("testdata/test_ldnsrr.1", "testdata/test_ldnsrr.c1");
+       rr_test_file("testdata/test_ldnsrr.2", "testdata/test_ldnsrr.c2");
+       rr_test_file("testdata/test_ldnsrr.3", "testdata/test_ldnsrr.c3");
+       rr_test_file("testdata/test_ldnsrr.4", "testdata/test_ldnsrr.c4");
+       rr_test_file("testdata/test_ldnsrr.5", "testdata/test_ldnsrr.c5");
+}
+
+void
+ldns_test(void)
+{
+       unit_show_feature("sldns");
+       rr_tests();
+}
diff --git a/usr.sbin/unbound/testcode/unitlruhash.c b/usr.sbin/unbound/testcode/unitlruhash.c
new file mode 100644 (file)
index 0000000..e196f0b
--- /dev/null
@@ -0,0 +1,499 @@
+/*
+ * testcode/unitlruhash.c - unit test for lruhash table.
+ *
+ * Copyright (c) 2007, NLnet Labs. All rights reserved.
+ *
+ * This software is open source.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * Neither the name of the NLNET LABS nor the names of its contributors may
+ * be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+ * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+/**
+ * \file
+ * Tests the locking LRU keeping hash table implementation.
+ */
+
+#include "config.h"
+#include "testcode/unitmain.h"
+#include "util/log.h"
+#include "util/storage/lruhash.h"
+#include "util/storage/slabhash.h" /* for the test structures */
+
+/** use this type for the lruhash test key */
+typedef struct slabhash_testkey testkey_type;
+/** use this type for the lruhash test data */
+typedef struct slabhash_testdata testdata_type;
+
+/** delete key */
+static void delkey(struct slabhash_testkey* k) {
+       lock_rw_destroy(&k->entry.lock); free(k);}
+/** delete data */
+static void deldata(struct slabhash_testdata* d) {free(d);}
+
+/** hash func, very bad to improve collisions */
+static hashvalue_type myhash(int id) {return (hashvalue_type)id & 0x0f;}
+/** allocate new key, fill in hash */
+static testkey_type* newkey(int id) {
+       testkey_type* k = (testkey_type*)calloc(1, sizeof(testkey_type));
+       if(!k) fatal_exit("out of memory");
+       k->id = id;
+       k->entry.hash = myhash(id);
+       k->entry.key = k;
+       lock_rw_init(&k->entry.lock);
+       return k;
+}
+/** new data el */
+static testdata_type* newdata(int val) {
+       testdata_type* d = (testdata_type*)calloc(1, 
+               sizeof(testdata_type));
+       if(!d) fatal_exit("out of memory");
+       d->data = val;
+       return d;
+}
+
+/** test bin_find_entry function and bin_overflow_remove */
+static void
+test_bin_find_entry(struct lruhash* table)
+{
+       testkey_type* k = newkey(12);
+       testdata_type* d = newdata(128);
+       testkey_type* k2 = newkey(12 + 1024);
+       testkey_type* k3 = newkey(14);
+       testkey_type* k4 = newkey(12 + 1024*2);
+       hashvalue_type h = myhash(12);
+       struct lruhash_bin bin;
+       memset(&bin, 0, sizeof(bin));
+       bin_init(&bin, 1);
+
+       /* remove from empty list */
+       bin_overflow_remove(&bin, &k->entry);
+
+       /* find in empty list */
+       unit_assert( bin_find_entry(table, &bin, h, k) == NULL );
+
+       /* insert */
+       lock_quick_lock(&bin.lock);
+       bin.overflow_list = &k->entry;
+       lock_quick_unlock(&bin.lock);
+
+       /* find, hash not OK. */
+       unit_assert( bin_find_entry(table, &bin, myhash(13), k) == NULL );
+
+       /* find, hash OK, but cmp not */
+       unit_assert( k->entry.hash == k2->entry.hash );
+       unit_assert( bin_find_entry(table, &bin, h, k2) == NULL );
+
+       /* find, hash OK, and cmp too */
+       unit_assert( bin_find_entry(table, &bin, h, k) == &k->entry );
+
+       /* remove the element */
+       lock_quick_lock(&bin.lock);
+       bin_overflow_remove(&bin, &k->entry);
+       lock_quick_unlock(&bin.lock);
+       unit_assert( bin_find_entry(table, &bin, h, k) == NULL );
+
+       /* prepend two different elements; so the list is long */
+       /* one has the same hash, but different cmp */
+       lock_quick_lock(&bin.lock);
+       unit_assert( k->entry.hash == k4->entry.hash );
+       k4->entry.overflow_next = &k->entry;
+       k3->entry.overflow_next = &k4->entry;
+       bin.overflow_list = &k3->entry;
+       lock_quick_unlock(&bin.lock);
+
+       /* find, hash not OK. */
+       unit_assert( bin_find_entry(table, &bin, myhash(13), k) == NULL );
+
+       /* find, hash OK, but cmp not */
+       unit_assert( k->entry.hash == k2->entry.hash );
+       unit_assert( bin_find_entry(table, &bin, h, k2) == NULL );
+
+       /* find, hash OK, and cmp too */
+       unit_assert( bin_find_entry(table, &bin, h, k) == &k->entry );
+
+       /* remove middle element */
+       unit_assert( bin_find_entry(table, &bin, k4->entry.hash, k4) 
+               == &k4->entry );
+       lock_quick_lock(&bin.lock);
+       bin_overflow_remove(&bin, &k4->entry);
+       lock_quick_unlock(&bin.lock);
+       unit_assert( bin_find_entry(table, &bin, k4->entry.hash, k4) == NULL);
+
+       /* remove last element */
+       lock_quick_lock(&bin.lock);
+       bin_overflow_remove(&bin, &k->entry);
+       lock_quick_unlock(&bin.lock);
+       unit_assert( bin_find_entry(table, &bin, h, k) == NULL );
+
+       lock_quick_destroy(&bin.lock);
+       delkey(k);
+       delkey(k2);
+       delkey(k3);
+       delkey(k4);
+       deldata(d);
+}
+
+/** test lru_front lru_remove */
+static void test_lru(struct lruhash* table)
+{
+       testkey_type* k = newkey(12);
+       testkey_type* k2 = newkey(14);
+       lock_quick_lock(&table->lock);
+
+       unit_assert( table->lru_start == NULL && table->lru_end == NULL);
+       lru_remove(table, &k->entry);
+       unit_assert( table->lru_start == NULL && table->lru_end == NULL);
+
+       /* add one */
+       lru_front(table, &k->entry);
+       unit_assert( table->lru_start == &k->entry && 
+               table->lru_end == &k->entry);
+       /* remove it */
+       lru_remove(table, &k->entry);
+       unit_assert( table->lru_start == NULL && table->lru_end == NULL);
+
+       /* add two */
+       lru_front(table, &k->entry);
+       unit_assert( table->lru_start == &k->entry && 
+               table->lru_end == &k->entry);
+       lru_front(table, &k2->entry);
+       unit_assert( table->lru_start == &k2->entry && 
+               table->lru_end == &k->entry);
+       /* remove first in list */
+       lru_remove(table, &k2->entry);
+       unit_assert( table->lru_start == &k->entry && 
+               table->lru_end == &k->entry);
+       lru_front(table, &k2->entry);
+       unit_assert( table->lru_start == &k2->entry && 
+               table->lru_end == &k->entry);
+       /* remove last in list */
+       lru_remove(table, &k->entry);
+       unit_assert( table->lru_start == &k2->entry && 
+               table->lru_end == &k2->entry);
+
+       /* empty the list */
+       lru_remove(table, &k2->entry);
+       unit_assert( table->lru_start == NULL && table->lru_end == NULL);
+       lock_quick_unlock(&table->lock);
+       delkey(k);
+       delkey(k2);
+}
+
+/** test hashtable using short sequence */
+static void
+test_short_table(struct lruhash* table) 
+{
+       testkey_type* k = newkey(12);
+       testkey_type* k2 = newkey(14);
+       testdata_type* d = newdata(128);
+       testdata_type* d2 = newdata(129);
+       
+       k->entry.data = d;
+       k2->entry.data = d2;
+
+       lruhash_insert(table, myhash(12), &k->entry, d, NULL);
+       lruhash_insert(table, myhash(14), &k2->entry, d2, NULL);
+       
+       unit_assert( lruhash_lookup(table, myhash(12), k, 0) == &k->entry);
+       lock_rw_unlock( &k->entry.lock );
+       unit_assert( lruhash_lookup(table, myhash(14), k2, 0) == &k2->entry);
+       lock_rw_unlock( &k2->entry.lock );
+       lruhash_remove(table, myhash(12), k);
+       lruhash_remove(table, myhash(14), k2);
+}
+
+/** number of hash test max */
+#define HASHTESTMAX 25
+
+/** test adding a random element */
+static void
+testadd(struct lruhash* table, testdata_type* ref[])
+{
+       int numtoadd = random() % HASHTESTMAX;
+       testdata_type* data = newdata(numtoadd);
+       testkey_type* key = newkey(numtoadd);
+       key->entry.data = data;
+       lruhash_insert(table, myhash(numtoadd), &key->entry, data, NULL);
+       ref[numtoadd] = data;
+}
+
+/** test adding a random element */
+static void
+testremove(struct lruhash* table, testdata_type* ref[])
+{
+       int num = random() % HASHTESTMAX;
+       testkey_type* key = newkey(num);
+       lruhash_remove(table, myhash(num), key);
+       ref[num] = NULL;
+       delkey(key);
+}
+
+/** test adding a random element */
+static void
+testlookup(struct lruhash* table, testdata_type* ref[])
+{
+       int num = random() % HASHTESTMAX;
+       testkey_type* key = newkey(num);
+       struct lruhash_entry* en = lruhash_lookup(table, myhash(num), key, 0);
+       testdata_type* data = en? (testdata_type*)en->data : NULL;
+       if(en) {
+               unit_assert(en->key);
+               unit_assert(en->data);
+       }
+       if(0) log_info("lookup %d got %d, expect %d", num, en? data->data :-1,
+               ref[num]? ref[num]->data : -1);
+       unit_assert( data == ref[num] );
+       if(en) { lock_rw_unlock(&en->lock); }
+       delkey(key);
+}
+
+/** check integrity of hash table */
+static void
+check_table(struct lruhash* table)
+{
+       struct lruhash_entry* p;
+       size_t c = 0;
+       lock_quick_lock(&table->lock);
+       unit_assert( table->num <= table->size);
+       unit_assert( table->size_mask == (int)table->size-1 );
+       unit_assert( (table->lru_start && table->lru_end) ||
+               (!table->lru_start && !table->lru_end) );
+       unit_assert( table->space_used <= table->space_max );
+       /* check lru list integrity */
+       if(table->lru_start)
+               unit_assert(table->lru_start->lru_prev == NULL);
+       if(table->lru_end)
+               unit_assert(table->lru_end->lru_next == NULL);
+       p = table->lru_start;
+       while(p) {
+               if(p->lru_prev) {
+                       unit_assert(p->lru_prev->lru_next == p);
+               }
+               if(p->lru_next) {
+                       unit_assert(p->lru_next->lru_prev == p);
+               }
+               c++;
+               p = p->lru_next;
+       }
+       unit_assert(c == table->num);
+
+       /* this assertion is specific to the unit test */
+       unit_assert( table->space_used == 
+               table->num * test_slabhash_sizefunc(NULL, NULL) );
+       lock_quick_unlock(&table->lock);
+}
+
+/** test adding a random element (unlimited range) */
+static void
+testadd_unlim(struct lruhash* table, testdata_type** ref)
+{
+       int numtoadd = random() % (HASHTESTMAX * 10);
+       testdata_type* data = newdata(numtoadd);
+       testkey_type* key = newkey(numtoadd);
+       key->entry.data = data;
+       lruhash_insert(table, myhash(numtoadd), &key->entry, data, NULL);
+       if(ref)
+               ref[numtoadd] = data;
+}
+
+/** test adding a random element (unlimited range) */
+static void
+testremove_unlim(struct lruhash* table, testdata_type** ref)
+{
+       int num = random() % (HASHTESTMAX*10);
+       testkey_type* key = newkey(num);
+       lruhash_remove(table, myhash(num), key);
+       if(ref)
+               ref[num] = NULL;
+       delkey(key);
+}
+
+/** test adding a random element (unlimited range) */
+static void
+testlookup_unlim(struct lruhash* table, testdata_type** ref)
+{
+       int num = random() % (HASHTESTMAX*10);
+       testkey_type* key = newkey(num);
+       struct lruhash_entry* en = lruhash_lookup(table, myhash(num), key, 0);
+       testdata_type* data = en? (testdata_type*)en->data : NULL;
+       if(en) {
+               unit_assert(en->key);
+               unit_assert(en->data);
+       }
+       if(0 && ref) log_info("lookup unlim %d got %d, expect %d", num, en ? 
+               data->data :-1, ref[num] ? ref[num]->data : -1);
+       if(data && ref) {
+               /* its okay for !data, it fell off the lru */
+               unit_assert( data == ref[num] );
+       }
+       if(en) { lock_rw_unlock(&en->lock); }
+       delkey(key);
+}
+
+/** test with long sequence of adds, removes and updates, and lookups */
+static void
+test_long_table(struct lruhash* table) 
+{
+       /* assuming it all fits in the hashtable, this check will work */
+       testdata_type* ref[HASHTESTMAX * 100];
+       size_t i;
+       memset(ref, 0, sizeof(ref));
+       /* test assumption */
+       if(0) log_info(" size %d x %d < %d", (int)test_slabhash_sizefunc(NULL, NULL), 
+               (int)HASHTESTMAX, (int)table->space_max);
+       unit_assert( test_slabhash_sizefunc(NULL, NULL)*HASHTESTMAX < table->space_max);
+       if(0) lruhash_status(table, "unit test", 1);
+       srandom(48);
+       for(i=0; i<1000; i++) {
+               /* what to do? */
+               if(i == 500) {
+                       lruhash_clear(table);
+                       memset(ref, 0, sizeof(ref));
+                       continue;
+               }
+               switch(random() % 4) {
+                       case 0:
+                       case 3:
+                               testadd(table, ref);
+                               break;
+                       case 1:
+                               testremove(table, ref);
+                               break;
+                       case 2:
+                               testlookup(table, ref);
+                               break;
+                       default:
+                               unit_assert(0);
+               }
+               if(0) lruhash_status(table, "unit test", 1);
+               check_table(table);
+               unit_assert( table->num <= HASHTESTMAX );
+       }
+
+       /* test more, but 'ref' assumption does not hold anymore */
+       for(i=0; i<1000; i++) {
+               /* what to do? */
+               switch(random() % 4) {
+                       case 0:
+                       case 3:
+                               testadd_unlim(table, ref);
+                               break;
+                       case 1:
+                               testremove_unlim(table, ref);
+                               break;
+                       case 2:
+                               testlookup_unlim(table, ref);
+                               break;
+                       default:
+                               unit_assert(0);
+               }
+               if(0) lruhash_status(table, "unlim", 1);
+               check_table(table);
+       }
+}
+
+/** structure to threaded test the lru hash table */
+struct test_thr {
+       /** thread num, first entry. */
+       int num;
+       /** id */
+       ub_thread_type id;
+       /** hash table */
+       struct lruhash* table;
+};
+
+/** main routine for threaded hash table test */
+static void*
+test_thr_main(void* arg) 
+{
+       struct test_thr* t = (struct test_thr*)arg;
+       int i;
+       log_thread_set(&t->num);
+       for(i=0; i<1000; i++) {
+               switch(random() % 4) {
+                       case 0:
+                       case 3:
+                               testadd_unlim(t->table, NULL);
+                               break;
+                       case 1:
+                               testremove_unlim(t->table, NULL);
+                               break;
+                       case 2:
+                               testlookup_unlim(t->table, NULL);
+                               break;
+                       default:
+                               unit_assert(0);
+               }
+               if(0) lruhash_status(t->table, "hashtest", 1);
+               if(i % 100 == 0) /* because of locking, not all the time */
+                       check_table(t->table);
+       }
+       check_table(t->table);
+       return NULL;
+}
+
+/** test hash table access by multiple threads */
+static void
+test_threaded_table(struct lruhash* table)
+{
+       int numth = 10;
+       struct test_thr t[100];
+       int i;
+
+       for(i=1; i<numth; i++) {
+               t[i].num = i;
+               t[i].table = table;
+               ub_thread_create(&t[i].id, test_thr_main, &t[i]);
+       }
+
+       for(i=1; i<numth; i++) {
+               ub_thread_join(t[i].id);
+       }
+       if(0) lruhash_status(table, "hashtest", 1);
+}
+
+void lruhash_test(void)
+{
+       /* start very very small array, so it can do lots of table_grow() */
+       /* also small in size so that reclaim has to be done quickly. */
+       struct lruhash* table ;
+       unit_show_feature("lruhash");
+       table = lruhash_create(2, 8192, 
+               test_slabhash_sizefunc, test_slabhash_compfunc, 
+               test_slabhash_delkey, test_slabhash_deldata, NULL);
+       test_bin_find_entry(table);
+       test_lru(table);
+       test_short_table(table);
+       test_long_table(table);
+       lruhash_delete(table);
+       table = lruhash_create(2, 8192, 
+               test_slabhash_sizefunc, test_slabhash_compfunc, 
+               test_slabhash_delkey, test_slabhash_deldata, NULL);
+       test_threaded_table(table);
+       lruhash_delete(table);
+}
diff --git a/usr.sbin/unbound/testcode/unitmain.c b/usr.sbin/unbound/testcode/unitmain.c
new file mode 100644 (file)
index 0000000..fecde80
--- /dev/null
@@ -0,0 +1,942 @@
+/*
+ * testcode/unitmain.c - unit test main program for unbound.
+ *
+ * Copyright (c) 2007, NLnet Labs. All rights reserved.
+ *
+ * This software is open source.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * Neither the name of the NLNET LABS nor the names of its contributors may
+ * be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+ * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+/**
+ * \file
+ * Unit test main program. Calls all the other unit tests.
+ * Exits with code 1 on a failure. 0 if all unit tests are successful.
+ */
+
+#include "config.h"
+#ifdef HAVE_OPENSSL_ERR_H
+#include <openssl/err.h>
+#endif
+
+#ifdef HAVE_OPENSSL_RAND_H
+#include <openssl/rand.h>
+#endif
+
+#ifdef HAVE_OPENSSL_CONF_H
+#include <openssl/conf.h>
+#endif
+
+#ifdef HAVE_OPENSSL_ENGINE_H
+#include <openssl/engine.h>
+#endif
+
+#ifdef HAVE_NSS
+/* nss3 */
+#include "nss.h"
+#endif
+
+#include "sldns/rrdef.h"
+#include "sldns/keyraw.h"
+#include "util/log.h"
+#include "testcode/unitmain.h"
+
+/** number of tests done */
+int testcount = 0;
+
+#include "util/alloc.h"
+/** test alloc code */
+static void
+alloc_test(void) {
+       alloc_special_type *t1, *t2;
+       struct alloc_cache major, minor1, minor2;
+       int i;
+
+       unit_show_feature("alloc_special_obtain");
+       alloc_init(&major, NULL, 0);
+       alloc_init(&minor1, &major, 0);
+       alloc_init(&minor2, &major, 1);
+
+       t1 = alloc_special_obtain(&minor1);
+       alloc_clear(&minor1);
+
+       alloc_special_release(&minor2, t1);
+       t2 = alloc_special_obtain(&minor2);
+       unit_assert( t1 == t2 ); /* reused */
+       alloc_special_release(&minor2, t1);
+
+       for(i=0; i<100; i++) {
+               t1 = alloc_special_obtain(&minor1);
+               alloc_special_release(&minor2, t1);
+       }
+       if(0) {
+               alloc_stats(&minor1);
+               alloc_stats(&minor2);
+               alloc_stats(&major);
+       }
+       /* reuse happened */
+       unit_assert(minor1.num_quar + minor2.num_quar + major.num_quar == 11);
+
+       alloc_clear(&minor1);
+       alloc_clear(&minor2);
+       unit_assert(major.num_quar == 11);
+       alloc_clear(&major);
+}
+
+#include "util/net_help.h"
+/** test net code */
+static void 
+net_test(void)
+{
+       const char* t4[] = {"\000\000\000\000",
+               "\200\000\000\000",
+               "\300\000\000\000",
+               "\340\000\000\000",
+               "\360\000\000\000",
+               "\370\000\000\000",
+               "\374\000\000\000",
+               "\376\000\000\000",
+               "\377\000\000\000",
+               "\377\200\000\000",
+               "\377\300\000\000",
+               "\377\340\000\000",
+               "\377\360\000\000",
+               "\377\370\000\000",
+               "\377\374\000\000",
+               "\377\376\000\000",
+               "\377\377\000\000",
+               "\377\377\200\000",
+               "\377\377\300\000",
+               "\377\377\340\000",
+               "\377\377\360\000",
+               "\377\377\370\000",
+               "\377\377\374\000",
+               "\377\377\376\000",
+               "\377\377\377\000",
+               "\377\377\377\200",
+               "\377\377\377\300",
+               "\377\377\377\340",
+               "\377\377\377\360",
+               "\377\377\377\370",
+               "\377\377\377\374",
+               "\377\377\377\376",
+               "\377\377\377\377",
+               "\377\377\377\377",
+               "\377\377\377\377",
+       };
+       unit_show_func("util/net_help.c", "str_is_ip6");
+       unit_assert( str_is_ip6("::") );
+       unit_assert( str_is_ip6("::1") );
+       unit_assert( str_is_ip6("2001:7b8:206:1:240:f4ff:fe37:8810") );
+       unit_assert( str_is_ip6("fe80::240:f4ff:fe37:8810") );
+       unit_assert( !str_is_ip6("0.0.0.0") );
+       unit_assert( !str_is_ip6("213.154.224.12") );
+       unit_assert( !str_is_ip6("213.154.224.255") );
+       unit_assert( !str_is_ip6("255.255.255.0") );
+       unit_show_func("util/net_help.c", "is_pow2");
+       unit_assert( is_pow2(0) );
+       unit_assert( is_pow2(1) );
+       unit_assert( is_pow2(2) );
+       unit_assert( is_pow2(4) );
+       unit_assert( is_pow2(8) );
+       unit_assert( is_pow2(16) );
+       unit_assert( is_pow2(1024) );
+       unit_assert( is_pow2(1024*1024) );
+       unit_assert( is_pow2(1024*1024*1024) );
+       unit_assert( !is_pow2(3) );
+       unit_assert( !is_pow2(5) );
+       unit_assert( !is_pow2(6) );
+       unit_assert( !is_pow2(7) );
+       unit_assert( !is_pow2(9) );
+       unit_assert( !is_pow2(10) );
+       unit_assert( !is_pow2(11) );
+       unit_assert( !is_pow2(17) );
+       unit_assert( !is_pow2(23) );
+       unit_assert( !is_pow2(257) );
+       unit_assert( !is_pow2(259) );
+
+       /* test addr_mask */
+       unit_show_func("util/net_help.c", "addr_mask");
+       if(1) {
+               struct sockaddr_in a4;
+               struct sockaddr_in6 a6;
+               socklen_t l4 = (socklen_t)sizeof(a4);
+               socklen_t l6 = (socklen_t)sizeof(a6);
+               int i;
+               a4.sin_family = AF_INET;
+               a6.sin6_family = AF_INET6;
+               for(i=0; i<35; i++) {
+                       /* address 255.255.255.255 */
+                       memcpy(&a4.sin_addr, "\377\377\377\377", 4);
+                       addr_mask((struct sockaddr_storage*)&a4, l4, i);
+                       unit_assert(memcmp(&a4.sin_addr, t4[i], 4) == 0);
+               }
+               memcpy(&a6.sin6_addr, "\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377", 16);
+               addr_mask((struct sockaddr_storage*)&a6, l6, 128);
+               unit_assert(memcmp(&a6.sin6_addr, "\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377", 16) == 0);
+               addr_mask((struct sockaddr_storage*)&a6, l6, 122);
+               unit_assert(memcmp(&a6.sin6_addr, "\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\300", 16) == 0);
+               addr_mask((struct sockaddr_storage*)&a6, l6, 120);
+               unit_assert(memcmp(&a6.sin6_addr, "\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\000", 16) == 0);
+               addr_mask((struct sockaddr_storage*)&a6, l6, 64);
+               unit_assert(memcmp(&a6.sin6_addr, "\377\377\377\377\377\377\377\377\000\000\000\000\000\000\000\000", 16) == 0);
+               addr_mask((struct sockaddr_storage*)&a6, l6, 0);
+               unit_assert(memcmp(&a6.sin6_addr, "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000", 16) == 0);
+       }
+
+       /* test addr_in_common */
+       unit_show_func("util/net_help.c", "addr_in_common");
+       if(1) {
+               struct sockaddr_in a4, b4;
+               struct sockaddr_in6 a6, b6;
+               socklen_t l4 = (socklen_t)sizeof(a4);
+               socklen_t l6 = (socklen_t)sizeof(a6);
+               int i;
+               a4.sin_family = AF_INET;
+               b4.sin_family = AF_INET;
+               a6.sin6_family = AF_INET6;
+               b6.sin6_family = AF_INET6;
+               memcpy(&a4.sin_addr, "abcd", 4);
+               memcpy(&b4.sin_addr, "abcd", 4);
+               unit_assert(addr_in_common((struct sockaddr_storage*)&a4, 32,
+                       (struct sockaddr_storage*)&b4, 32, l4) == 32);
+               unit_assert(addr_in_common((struct sockaddr_storage*)&a4, 34,
+                       (struct sockaddr_storage*)&b4, 32, l4) == 32);
+               for(i=0; i<=32; i++) {
+                       unit_assert(addr_in_common(
+                               (struct sockaddr_storage*)&a4, 32,
+                               (struct sockaddr_storage*)&b4, i, l4) == i);
+                       unit_assert(addr_in_common(
+                               (struct sockaddr_storage*)&a4, i,
+                               (struct sockaddr_storage*)&b4, 32, l4) == i);
+                       unit_assert(addr_in_common(
+                               (struct sockaddr_storage*)&a4, i,
+                               (struct sockaddr_storage*)&b4, i, l4) == i);
+               }
+               for(i=0; i<=32; i++) {
+                       memcpy(&a4.sin_addr, "\377\377\377\377", 4);
+                       memcpy(&b4.sin_addr, t4[i], 4);
+                       unit_assert(addr_in_common(
+                               (struct sockaddr_storage*)&a4, 32,
+                               (struct sockaddr_storage*)&b4, 32, l4) == i);
+                       unit_assert(addr_in_common(
+                               (struct sockaddr_storage*)&b4, 32,
+                               (struct sockaddr_storage*)&a4, 32, l4) == i);
+               }
+               memcpy(&a6.sin6_addr, "abcdefghabcdefgh", 16);
+               memcpy(&b6.sin6_addr, "abcdefghabcdefgh", 16);
+               unit_assert(addr_in_common((struct sockaddr_storage*)&a6, 128,
+                       (struct sockaddr_storage*)&b6, 128, l6) == 128);
+               unit_assert(addr_in_common((struct sockaddr_storage*)&a6, 129,
+                       (struct sockaddr_storage*)&b6, 128, l6) == 128);
+               for(i=0; i<=128; i++) {
+                       unit_assert(addr_in_common(
+                               (struct sockaddr_storage*)&a6, 128,
+                               (struct sockaddr_storage*)&b6, i, l6) == i);
+                       unit_assert(addr_in_common(
+                               (struct sockaddr_storage*)&a6, i,
+                               (struct sockaddr_storage*)&b6, 128, l6) == i);
+                       unit_assert(addr_in_common(
+                               (struct sockaddr_storage*)&a6, i,
+                               (struct sockaddr_storage*)&b6, i, l6) == i);
+               }
+       }
+       /* test sockaddr_cmp_addr */
+       unit_show_func("util/net_help.c", "sockaddr_cmp_addr");
+       if(1) {
+               struct sockaddr_storage a, b;
+               socklen_t alen = (socklen_t)sizeof(a);
+               socklen_t blen = (socklen_t)sizeof(b);
+               unit_assert(ipstrtoaddr("127.0.0.0", 53, &a, &alen));
+               unit_assert(ipstrtoaddr("127.255.255.255", 53, &b, &blen));
+               unit_assert(sockaddr_cmp_addr(&a, alen, &b, blen) < 0);
+               unit_assert(sockaddr_cmp_addr(&b, blen, &a, alen) > 0);
+               unit_assert(sockaddr_cmp_addr(&a, alen, &a, alen) == 0);
+               unit_assert(sockaddr_cmp_addr(&b, blen, &b, blen) == 0);
+               unit_assert(ipstrtoaddr("192.168.121.5", 53, &a, &alen));
+               unit_assert(sockaddr_cmp_addr(&a, alen, &b, blen) > 0);
+               unit_assert(sockaddr_cmp_addr(&b, blen, &a, alen) < 0);
+               unit_assert(sockaddr_cmp_addr(&a, alen, &a, alen) == 0);
+               unit_assert(ipstrtoaddr("2001:3578:ffeb::99", 53, &b, &blen));
+               unit_assert(sockaddr_cmp_addr(&b, blen, &b, blen) == 0);
+               unit_assert(sockaddr_cmp_addr(&a, alen, &b, blen) < 0);
+               unit_assert(sockaddr_cmp_addr(&b, blen, &a, alen) > 0);
+       }
+       /* test addr_is_ip4mapped */
+       unit_show_func("util/net_help.c", "addr_is_ip4mapped");
+       if(1) {
+               struct sockaddr_storage a;
+               socklen_t l = (socklen_t)sizeof(a);
+               unit_assert(ipstrtoaddr("12.13.14.15", 53, &a, &l));
+               unit_assert(!addr_is_ip4mapped(&a, l));
+               unit_assert(ipstrtoaddr("fe80::217:31ff:fe91:df", 53, &a, &l));
+               unit_assert(!addr_is_ip4mapped(&a, l));
+               unit_assert(ipstrtoaddr("ffff::217:31ff:fe91:df", 53, &a, &l));
+               unit_assert(!addr_is_ip4mapped(&a, l));
+               unit_assert(ipstrtoaddr("::ffff:31ff:fe91:df", 53, &a, &l));
+               unit_assert(!addr_is_ip4mapped(&a, l));
+               unit_assert(ipstrtoaddr("::fffe:fe91:df", 53, &a, &l));
+               unit_assert(!addr_is_ip4mapped(&a, l));
+               unit_assert(ipstrtoaddr("::ffff:127.0.0.1", 53, &a, &l));
+               unit_assert(addr_is_ip4mapped(&a, l));
+               unit_assert(ipstrtoaddr("::ffff:127.0.0.2", 53, &a, &l));
+               unit_assert(addr_is_ip4mapped(&a, l));
+               unit_assert(ipstrtoaddr("::ffff:192.168.0.2", 53, &a, &l));
+               unit_assert(addr_is_ip4mapped(&a, l));
+               unit_assert(ipstrtoaddr("2::ffff:192.168.0.2", 53, &a, &l));
+               unit_assert(!addr_is_ip4mapped(&a, l));
+       }
+       /* test addr_is_any */
+       unit_show_func("util/net_help.c", "addr_is_any");
+       if(1) {
+               struct sockaddr_storage a;
+               socklen_t l = (socklen_t)sizeof(a);
+               unit_assert(ipstrtoaddr("0.0.0.0", 53, &a, &l));
+               unit_assert(addr_is_any(&a, l));
+               unit_assert(ipstrtoaddr("0.0.0.0", 10053, &a, &l));
+               unit_assert(addr_is_any(&a, l));
+               unit_assert(ipstrtoaddr("0.0.0.0", 0, &a, &l));
+               unit_assert(addr_is_any(&a, l));
+               unit_assert(ipstrtoaddr("::0", 0, &a, &l));
+               unit_assert(addr_is_any(&a, l));
+               unit_assert(ipstrtoaddr("::0", 53, &a, &l));
+               unit_assert(addr_is_any(&a, l));
+               unit_assert(ipstrtoaddr("::1", 53, &a, &l));
+               unit_assert(!addr_is_any(&a, l));
+               unit_assert(ipstrtoaddr("2001:1667::1", 0, &a, &l));
+               unit_assert(!addr_is_any(&a, l));
+               unit_assert(ipstrtoaddr("2001::0", 0, &a, &l));
+               unit_assert(!addr_is_any(&a, l));
+               unit_assert(ipstrtoaddr("10.0.0.0", 0, &a, &l));
+               unit_assert(!addr_is_any(&a, l));
+               unit_assert(ipstrtoaddr("0.0.0.10", 0, &a, &l));
+               unit_assert(!addr_is_any(&a, l));
+               unit_assert(ipstrtoaddr("192.0.2.1", 0, &a, &l));
+               unit_assert(!addr_is_any(&a, l));
+       }
+}
+
+#include "util/config_file.h"
+/** test config_file: cfg_parse_memsize */
+static void
+config_memsize_test(void) 
+{
+       size_t v = 0;
+       unit_show_func("util/config_file.c", "cfg_parse_memsize");
+       if(0) {
+               /* these emit errors */
+               unit_assert( cfg_parse_memsize("", &v) == 0);
+               unit_assert( cfg_parse_memsize("bla", &v) == 0);
+               unit_assert( cfg_parse_memsize("nop", &v) == 0);
+               unit_assert( cfg_parse_memsize("n0b", &v) == 0);
+               unit_assert( cfg_parse_memsize("gb", &v) == 0);
+               unit_assert( cfg_parse_memsize("b", &v) == 0);
+               unit_assert( cfg_parse_memsize("kb", &v) == 0);
+               unit_assert( cfg_parse_memsize("kk kb", &v) == 0);
+       }
+       unit_assert( cfg_parse_memsize("0", &v) && v==0);
+       unit_assert( cfg_parse_memsize("1", &v) && v==1);
+       unit_assert( cfg_parse_memsize("10", &v) && v==10);
+       unit_assert( cfg_parse_memsize("10b", &v) && v==10);
+       unit_assert( cfg_parse_memsize("5b", &v) && v==5);
+       unit_assert( cfg_parse_memsize("1024", &v) && v==1024);
+       unit_assert( cfg_parse_memsize("1k", &v) && v==1024);
+       unit_assert( cfg_parse_memsize("1K", &v) && v==1024);
+       unit_assert( cfg_parse_memsize("1Kb", &v) && v==1024);
+       unit_assert( cfg_parse_memsize("1kb", &v) && v==1024);
+       unit_assert( cfg_parse_memsize("1 kb", &v) && v==1024);
+       unit_assert( cfg_parse_memsize("10 kb", &v) && v==10240);
+       unit_assert( cfg_parse_memsize("2k", &v) && v==2048);
+       unit_assert( cfg_parse_memsize("2m", &v) && v==2048*1024);
+       unit_assert( cfg_parse_memsize("3M", &v) && v==3072*1024);
+       unit_assert( cfg_parse_memsize("40m", &v) && v==40960*1024);
+       unit_assert( cfg_parse_memsize("1G", &v) && v==1024*1024*1024);
+       unit_assert( cfg_parse_memsize("1 Gb", &v) && v==1024*1024*1024);
+       unit_assert( cfg_parse_memsize("0 Gb", &v) && v==0*1024*1024);
+}
+
+/** test config_file: test tag code */
+static void
+config_tag_test(void) 
+{
+       unit_show_func("util/config_file.c", "taglist_intersect");
+       unit_assert( taglist_intersect(
+               (uint8_t*)"\000\000\000", 3, (uint8_t*)"\001\000\001", 3
+               ) == 0);
+       unit_assert( taglist_intersect(
+               (uint8_t*)"\000\000\001", 3, (uint8_t*)"\001\000\001", 3
+               ) == 1);
+       unit_assert( taglist_intersect(
+               (uint8_t*)"\001\000\000", 3, (uint8_t*)"\001\000\001", 3
+               ) == 1);
+       unit_assert( taglist_intersect(
+               (uint8_t*)"\001", 1, (uint8_t*)"\001\000\001", 3
+               ) == 1);
+       unit_assert( taglist_intersect(
+               (uint8_t*)"\001\000\001", 3, (uint8_t*)"\001", 1
+               ) == 1);
+}
+       
+#include "util/rtt.h"
+#include "util/timehist.h"
+#include "libunbound/unbound.h"
+/** test RTT code */
+static void
+rtt_test(void)
+{
+       int init = 376;
+       int i;
+       struct rtt_info r;
+       unit_show_func("util/rtt.c", "rtt_timeout");
+       rtt_init(&r);
+       /* initial value sensible */
+       unit_assert( rtt_timeout(&r) == init );
+       rtt_lost(&r, init);
+       unit_assert( rtt_timeout(&r) == init*2 );
+       rtt_lost(&r, init*2);
+       unit_assert( rtt_timeout(&r) == init*4 );
+       rtt_update(&r, 4000);
+       unit_assert( rtt_timeout(&r) >= 2000 );
+       rtt_lost(&r, rtt_timeout(&r) );
+       for(i=0; i<100; i++) {
+               rtt_lost(&r, rtt_timeout(&r) ); 
+               unit_assert( rtt_timeout(&r) > RTT_MIN_TIMEOUT-1);
+               unit_assert( rtt_timeout(&r) < RTT_MAX_TIMEOUT+1);
+       }
+       /* must be the same, timehist bucket is used in stats */
+       unit_assert(UB_STATS_BUCKET_NUM == NUM_BUCKETS_HIST);
+}
+
+#include "services/cache/infra.h"
+
+/* lookup and get key and data structs easily */
+static struct infra_data* infra_lookup_host(struct infra_cache* infra,
+       struct sockaddr_storage* addr, socklen_t addrlen, uint8_t* zone,
+       size_t zonelen, int wr, time_t now, struct infra_key** k)
+{
+       struct infra_data* d;
+       struct lruhash_entry* e = infra_lookup_nottl(infra, addr, addrlen,
+               zone, zonelen, wr);
+       if(!e) return NULL;
+       d = (struct infra_data*)e->data;
+       if(d->ttl < now) {
+               lock_rw_unlock(&e->lock);
+               return NULL;
+       }
+       *k = (struct infra_key*)e->key;
+       return d;
+}
+
+/** test host cache */
+static void
+infra_test(void)
+{
+       struct sockaddr_storage one;
+       socklen_t onelen;
+       uint8_t* zone = (uint8_t*)"\007example\003com\000";
+       size_t zonelen = 13;
+       struct infra_cache* slab;
+       struct config_file* cfg = config_create();
+       time_t now = 0;
+       uint8_t edns_lame;
+       int vs, to;
+       struct infra_key* k;
+       struct infra_data* d;
+       int init = 376;
+
+       unit_show_feature("infra cache");
+       unit_assert(ipstrtoaddr("127.0.0.1", 53, &one, &onelen));
+
+       slab = infra_create(cfg);
+       unit_assert( infra_host(slab, &one, onelen, zone, zonelen, now,
+               &vs, &edns_lame, &to) );
+       unit_assert( vs == 0 && to == init && edns_lame == 0 );
+
+       unit_assert( infra_rtt_update(slab, &one, onelen, zone, zonelen, LDNS_RR_TYPE_A, -1, init, now) );
+       unit_assert( infra_host(slab, &one, onelen, zone, zonelen, 
+                       now, &vs, &edns_lame, &to) );
+       unit_assert( vs == 0 && to == init*2 && edns_lame == 0 );
+
+       unit_assert( infra_edns_update(slab, &one, onelen, zone, zonelen, -1, now) );
+       unit_assert( infra_host(slab, &one, onelen, zone, zonelen, 
+                       now, &vs, &edns_lame, &to) );
+       unit_assert( vs == -1 && to == init*2  && edns_lame == 1);
+
+       now += cfg->host_ttl + 10;
+       unit_assert( infra_host(slab, &one, onelen, zone, zonelen, 
+                       now, &vs, &edns_lame, &to) );
+       unit_assert( vs == 0 && to == init && edns_lame == 0 );
+       
+       unit_assert( infra_set_lame(slab, &one, onelen,
+               zone, zonelen,  now, 0, 0, LDNS_RR_TYPE_A) );
+       unit_assert( (d=infra_lookup_host(slab, &one, onelen, zone, zonelen, 0, now, &k)) );
+       unit_assert( d->ttl == now+cfg->host_ttl );
+       unit_assert( d->edns_version == 0 );
+       unit_assert(!d->isdnsseclame && !d->rec_lame && d->lame_type_A &&
+               !d->lame_other);
+       lock_rw_unlock(&k->entry.lock);
+
+       /* test merge of data */
+       unit_assert( infra_set_lame(slab, &one, onelen,
+               zone, zonelen,  now, 0, 0, LDNS_RR_TYPE_AAAA) );
+       unit_assert( (d=infra_lookup_host(slab, &one, onelen, zone, zonelen, 0, now, &k)) );
+       unit_assert(!d->isdnsseclame && !d->rec_lame && d->lame_type_A &&
+               d->lame_other);
+       lock_rw_unlock(&k->entry.lock);
+
+       /* test that noEDNS cannot overwrite known-yesEDNS */
+       now += cfg->host_ttl + 10;
+       unit_assert( infra_host(slab, &one, onelen, zone, zonelen, 
+                       now, &vs, &edns_lame, &to) );
+       unit_assert( vs == 0 && to == init && edns_lame == 0 );
+
+       unit_assert( infra_edns_update(slab, &one, onelen, zone, zonelen, 0, now) );
+       unit_assert( infra_host(slab, &one, onelen, zone, zonelen, 
+                       now, &vs, &edns_lame, &to) );
+       unit_assert( vs == 0 && to == init && edns_lame == 1 );
+
+       unit_assert( infra_edns_update(slab, &one, onelen, zone, zonelen, -1, now) );
+       unit_assert( infra_host(slab, &one, onelen, zone, zonelen, 
+                       now, &vs, &edns_lame, &to) );
+       unit_assert( vs == 0 && to == init && edns_lame == 1 );
+
+       infra_delete(slab);
+       config_delete(cfg);
+}
+
+#include "util/random.h"
+/** test randomness */
+static void
+rnd_test(void)
+{
+       struct ub_randstate* r;
+       int num = 1000, i;
+       long int a[1000];
+       unsigned int seed = (unsigned)time(NULL);
+       unit_show_feature("ub_random");
+       printf("ub_random seed is %u\n", seed);
+       unit_assert( (r = ub_initstate(seed, NULL)) );
+       for(i=0; i<num; i++) {
+               a[i] = ub_random(r);
+               unit_assert(a[i] >= 0);
+               unit_assert((size_t)a[i] <= (size_t)0x7fffffff);
+               if(i > 5)
+                       unit_assert(a[i] != a[i-1] || a[i] != a[i-2] ||
+                               a[i] != a[i-3] || a[i] != a[i-4] ||
+                               a[i] != a[i-5] || a[i] != a[i-6]);
+       }
+       a[0] = ub_random_max(r, 1);
+       unit_assert(a[0] >= 0 && a[0] < 1);
+       a[0] = ub_random_max(r, 10000);
+       unit_assert(a[0] >= 0 && a[0] < 10000);
+       for(i=0; i<num; i++) {
+               a[i] = ub_random_max(r, 10);
+               unit_assert(a[i] >= 0 && a[i] < 10);
+       }
+       ub_randfree(r);
+}
+
+#include "respip/respip.h"
+#include "services/localzone.h"
+#include "util/data/packed_rrset.h"
+typedef struct addr_action {char* ip; char* sact; enum respip_action act;}
+       addr_action_t;
+
+/** Utility function that verifies that the respip set has actions as expected */
+static void
+verify_respip_set_actions(struct respip_set* set, addr_action_t actions[],
+       int actions_len)
+{
+       int i = 0;
+       struct rbtree_type* tree = respip_set_get_tree(set);
+       for (i=0; i<actions_len; i++) {
+               struct sockaddr_storage addr;
+               int net;
+               socklen_t addrlen;
+               struct resp_addr* node;
+               netblockstrtoaddr(actions[i].ip, UNBOUND_DNS_PORT, &addr,
+                       &addrlen, &net);
+               node = (struct resp_addr*)addr_tree_find(tree, &addr, addrlen, net);
+
+               /** we have the node and the node has the correct action
+                 * and has no data */
+               unit_assert(node);
+               unit_assert(actions[i].act ==
+                       resp_addr_get_action(node));
+               unit_assert(resp_addr_get_rrset(node) == NULL);
+       }
+       unit_assert(actions_len && i == actions_len);
+       unit_assert(actions_len == (int)tree->count);
+}
+
+/** Global respip actions test; apply raw config data and verify that
+  * all the nodes in the respip set, looked up by address, have expected
+  * actions */
+static void
+respip_conf_actions_test(void)
+{
+       addr_action_t config_response_ip[] = {
+               {"192.0.1.0/24", "deny", respip_deny},
+               {"192.0.2.0/24", "redirect", respip_redirect},
+               {"192.0.3.0/26", "inform", respip_inform},
+               {"192.0.4.0/27", "inform_deny", respip_inform_deny},
+               {"2001:db8:1::/48", "always_transparent", respip_always_transparent},
+               {"2001:db8:2::/49", "always_refuse", respip_always_refuse},
+               {"2001:db8:3::/50", "always_nxdomain", respip_always_nxdomain},
+       };
+       int i;
+       struct respip_set* set = respip_set_create();
+       struct config_file cfg;
+       int clen = (int)(sizeof(config_response_ip) / sizeof(addr_action_t));
+
+       unit_assert(set);
+       unit_show_feature("global respip config actions apply");
+       memset(&cfg, 0, sizeof(cfg));
+       for(i=0; i<clen; i++) {
+               char* ip = strdup(config_response_ip[i].ip);
+               char* sact = strdup(config_response_ip[i].sact);
+               unit_assert(ip && sact);
+               if(!cfg_str2list_insert(&cfg.respip_actions, ip, sact))
+                       unit_assert(0);
+       }
+       unit_assert(respip_global_apply_cfg(set, &cfg));
+       verify_respip_set_actions(set, config_response_ip, clen);
+
+       respip_set_delete(set);
+       config_deldblstrlist(cfg.respip_actions);
+}
+
+/** Per-view respip actions test; apply raw configuration with two views
+  * and verify that actions are as expected in respip sets of both views */
+static void
+respip_view_conf_actions_test(void)
+{
+       addr_action_t config_response_ip_view1[] = {
+               {"192.0.1.0/24", "deny", respip_deny},
+               {"192.0.2.0/24", "redirect", respip_redirect},
+               {"192.0.3.0/26", "inform", respip_inform},
+               {"192.0.4.0/27", "inform_deny", respip_inform_deny},
+       };
+       addr_action_t config_response_ip_view2[] = {
+               {"2001:db8:1::/48", "always_transparent", respip_always_transparent},
+               {"2001:db8:2::/49", "always_refuse", respip_always_refuse},
+               {"2001:db8:3::/50", "always_nxdomain", respip_always_nxdomain},
+       };
+       int i;
+       struct config_file cfg;
+       int clen1 = (int)(sizeof(config_response_ip_view1) / sizeof(addr_action_t));
+       int clen2 = (int)(sizeof(config_response_ip_view2) / sizeof(addr_action_t));
+       struct config_view* cv1;
+       struct config_view* cv2;
+       int have_respip_cfg = 0;
+       struct views* views = NULL;
+       struct view* v = NULL;
+
+       unit_show_feature("per-view respip config actions apply");
+       memset(&cfg, 0, sizeof(cfg));
+       cv1 = (struct config_view*)calloc(1, sizeof(struct config_view));
+       cv2 = (struct config_view*)calloc(1, sizeof(struct config_view));
+       unit_assert(cv1 && cv2);
+       cv1->name = strdup("view1");
+       cv2->name = strdup("view2");
+       unit_assert(cv1->name && cv2->name);
+       cv1->next = cv2;
+       cfg.views = cv1;
+
+       for(i=0; i<clen1; i++) {
+               char* ip = strdup(config_response_ip_view1[i].ip);
+               char* sact = strdup(config_response_ip_view1[i].sact);
+               unit_assert(ip && sact);
+               if(!cfg_str2list_insert(&cv1->respip_actions, ip, sact))
+                       unit_assert(0);
+       }
+       for(i=0; i<clen2; i++) {
+               char* ip = strdup(config_response_ip_view2[i].ip);
+               char* sact = strdup(config_response_ip_view2[i].sact);
+               unit_assert(ip && sact);
+               if(!cfg_str2list_insert(&cv2->respip_actions, ip, sact))
+                       unit_assert(0);
+       }
+       views = views_create();
+       unit_assert(views);
+       unit_assert(views_apply_cfg(views, &cfg));
+       unit_assert(respip_views_apply_cfg(views, &cfg, &have_respip_cfg));
+
+       /* now verify the respip sets in each view */
+       v = views_find_view(views, "view1", 0);
+       unit_assert(v);
+       verify_respip_set_actions(v->respip_set, config_response_ip_view1, clen1);
+       lock_rw_unlock(&v->lock);
+       v = views_find_view(views, "view2", 0);
+       unit_assert(v);
+       verify_respip_set_actions(v->respip_set, config_response_ip_view2, clen2);
+       lock_rw_unlock(&v->lock);
+
+       views_delete(views);
+       free(cv1->name);
+       free(cv1);
+       free(cv2->name);
+       free(cv2);
+}
+
+typedef struct addr_data {char* ip; char* data;} addr_data_t;
+
+/** find the respip address node in the specified tree (by address lookup)
+  * and verify type and address of the specified rdata (by index) in this
+  * node's rrset */
+static void
+verify_rrset(struct respip_set* set, const char* ipstr,
+       const char* rdatastr, size_t rdi, uint16_t type)
+{
+       struct sockaddr_storage addr;
+       int net;
+       char buf[65536];
+       socklen_t addrlen;
+       struct rbtree_type* tree;
+       struct resp_addr* node;
+       const struct ub_packed_rrset_key* rrs;
+
+       netblockstrtoaddr(ipstr, UNBOUND_DNS_PORT, &addr, &addrlen, &net);
+       tree = respip_set_get_tree(set);
+       node = (struct resp_addr*)addr_tree_find(tree, &addr, addrlen, net);
+       unit_assert(node);
+       unit_assert((rrs = resp_addr_get_rrset(node)));
+       unit_assert(ntohs(rrs->rk.type) == type);
+       packed_rr_to_string((struct ub_packed_rrset_key*)rrs,
+               rdi, 0, buf, sizeof(buf));
+       unit_assert(strstr(buf, rdatastr));
+}
+
+/** Dataset used to test redirect rrset initialization for both
+  * global and per-view respip redirect configuration */
+static addr_data_t config_response_ip_data[] = {
+       {"192.0.1.0/24", "A 1.2.3.4"},
+       {"192.0.1.0/24", "A 11.12.13.14"},
+       {"192.0.2.0/24", "CNAME www.example.com."},
+       {"2001:db8:1::/48", "AAAA 2001:db8:1::2:1"},
+};
+
+/** Populate raw respip redirect config data, used for both global and
+  * view-based respip redirect test case */
+static void
+cfg_insert_respip_data(struct config_str2list** respip_actions,
+       struct config_str2list** respip_data)
+{
+       int clen = (int)(sizeof(config_response_ip_data) / sizeof(addr_data_t));
+       int i = 0;
+
+       /* insert actions (duplicate netblocks don't matter) */
+       for(i=0; i<clen; i++) {
+               char* ip = strdup(config_response_ip_data[i].ip);
+               char* sact = strdup("redirect");
+               unit_assert(ip && sact);
+               if(!cfg_str2list_insert(respip_actions, ip, sact))
+                       unit_assert(0);
+       }
+       /* insert data */
+       for(i=0; i<clen; i++) {
+               char* ip = strdup(config_response_ip_data[i].ip);
+               char* data = strdup(config_response_ip_data[i].data);
+               unit_assert(ip && data);
+               if(!cfg_str2list_insert(respip_data, ip, data))
+                       unit_assert(0);
+       }
+}
+
+/** Test global respip redirect w/ data directives */
+static void
+respip_conf_data_test(void)
+{
+       struct respip_set* set = respip_set_create();
+       struct config_file cfg;
+
+       unit_show_feature("global respip config data apply");
+       memset(&cfg, 0, sizeof(cfg));
+
+       cfg_insert_respip_data(&cfg.respip_actions, &cfg.respip_data);
+
+       /* apply configuration and verify rrsets */
+       unit_assert(respip_global_apply_cfg(set, &cfg));
+       verify_rrset(set, "192.0.1.0/24", "1.2.3.4", 0, LDNS_RR_TYPE_A);
+       verify_rrset(set, "192.0.1.0/24", "11.12.13.14", 1, LDNS_RR_TYPE_A);
+       verify_rrset(set, "192.0.2.0/24", "www.example.com", 0, LDNS_RR_TYPE_CNAME);
+       verify_rrset(set, "2001:db8:1::/48", "2001:db8:1::2:1", 0, LDNS_RR_TYPE_AAAA);
+
+       respip_set_delete(set);
+}
+
+/** Test per-view respip redirect w/ data directives */
+static void
+respip_view_conf_data_test(void)
+{
+       struct config_file cfg;
+       struct config_view* cv;
+       int have_respip_cfg = 0;
+       struct views* views = NULL;
+       struct view* v = NULL;
+
+       unit_show_feature("per-view respip config data apply");
+       memset(&cfg, 0, sizeof(cfg));
+       cv = (struct config_view*)calloc(1, sizeof(struct config_view));
+       unit_assert(cv);
+       cv->name = strdup("view1");
+       unit_assert(cv->name);
+       cfg.views = cv;
+       cfg_insert_respip_data(&cv->respip_actions, &cv->respip_data);
+       views = views_create();
+       unit_assert(views);
+       unit_assert(views_apply_cfg(views, &cfg));
+
+       /* apply configuration and verify rrsets */
+       unit_assert(respip_views_apply_cfg(views, &cfg, &have_respip_cfg));
+       v = views_find_view(views, "view1", 0);
+       unit_assert(v);
+       verify_rrset(v->respip_set, "192.0.1.0/24", "1.2.3.4",
+               0, LDNS_RR_TYPE_A);
+       verify_rrset(v->respip_set, "192.0.1.0/24", "11.12.13.14",
+               1, LDNS_RR_TYPE_A);
+       verify_rrset(v->respip_set, "192.0.2.0/24", "www.example.com",
+               0, LDNS_RR_TYPE_CNAME);
+       verify_rrset(v->respip_set, "2001:db8:1::/48", "2001:db8:1::2:1",
+               0, LDNS_RR_TYPE_AAAA);
+       lock_rw_unlock(&v->lock);
+
+       views_delete(views);
+       free(cv->name);
+       free(cv);
+}
+
+/** respip unit tests */
+static void respip_test(void)
+{
+       respip_view_conf_data_test();
+       respip_conf_data_test();
+       respip_view_conf_actions_test();
+       respip_conf_actions_test();
+}
+
+void unit_show_func(const char* file, const char* func)
+{
+       printf("test %s:%s\n", file, func);
+}
+
+void unit_show_feature(const char* feature)
+{
+       printf("test %s functions\n", feature);
+}
+
+#ifdef USE_ECDSA_EVP_WORKAROUND
+void ecdsa_evp_workaround_init(void);
+#endif
+/**
+ * Main unit test program. Setup, teardown and report errors.
+ * @param argc: arg count.
+ * @param argv: array of commandline arguments.
+ * @return program failure if test fails.
+ */
+int 
+main(int argc, char* argv[])
+{
+       log_init(NULL, 0, NULL);
+       if(argc != 1) {
+               printf("usage: %s\n", argv[0]);
+               printf("\tperforms unit tests.\n");
+               return 1;
+       }
+       printf("Start of %s unit test.\n", PACKAGE_STRING);
+#ifdef HAVE_SSL
+#  ifdef HAVE_ERR_LOAD_CRYPTO_STRINGS
+       ERR_load_crypto_strings();
+#  endif
+#  ifdef USE_GOST
+       (void)sldns_key_EVP_load_gost_id();
+#  endif
+#  ifdef USE_ECDSA_EVP_WORKAROUND
+       ecdsa_evp_workaround_init();
+#  endif
+#elif defined(HAVE_NSS)
+       if(NSS_NoDB_Init(".") != SECSuccess)
+               fatal_exit("could not init NSS");
+#endif /* HAVE_SSL or HAVE_NSS*/
+       checklock_start();
+       authzone_test();
+       neg_test();
+       rnd_test();
+       respip_test();
+       verify_test();
+       net_test();
+       config_memsize_test();
+       config_tag_test();
+       dname_test();
+       rtt_test();
+       anchors_test();
+       alloc_test();
+       regional_test();
+       lruhash_test();
+       slabhash_test();
+       infra_test();
+       ldns_test();
+       msgparse_test();
+#ifdef CLIENT_SUBNET
+       ecs_test();
+#endif /* CLIENT_SUBNET */
+       if(log_get_lock()) {
+               lock_quick_destroy((lock_quick_type*)log_get_lock());
+       }
+       checklock_stop();
+       printf("%d checks ok.\n", testcount);
+#ifdef HAVE_SSL
+#  if defined(USE_GOST) && defined(HAVE_LDNS_KEY_EVP_UNLOAD_GOST)
+       sldns_key_EVP_unload_gost();
+#  endif
+#  ifdef HAVE_OPENSSL_CONFIG
+#  ifdef HAVE_EVP_CLEANUP
+       EVP_cleanup();
+#  endif
+       ENGINE_cleanup();
+       CONF_modules_free();
+#  endif
+#  ifdef HAVE_CRYPTO_CLEANUP_ALL_EX_DATA
+       CRYPTO_cleanup_all_ex_data();
+#  endif
+#  ifdef HAVE_ERR_FREE_STRINGS
+       ERR_free_strings();
+#  endif
+#  ifdef HAVE_RAND_CLEANUP
+       RAND_cleanup();
+#  endif
+#elif defined(HAVE_NSS)
+       if(NSS_Shutdown() != SECSuccess)
+               fatal_exit("could not shutdown NSS");
+#endif /* HAVE_SSL or HAVE_NSS */
+#ifdef HAVE_PTHREAD
+       /* dlopen frees its thread specific state */
+       pthread_exit(NULL);
+#endif
+       return 0;
+}
diff --git a/usr.sbin/unbound/testcode/unitmain.h b/usr.sbin/unbound/testcode/unitmain.h
new file mode 100644 (file)
index 0000000..e5c6109
--- /dev/null
@@ -0,0 +1,84 @@
+/*
+ * testcode/unitmain.h - unit test main program for unbound.
+ *
+ * Copyright (c) 2007, NLnet Labs. All rights reserved.
+ *
+ * This software is open source.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * Neither the name of the NLNET LABS nor the names of its contributors may
+ * be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+ * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+/**
+ * \file
+ * Declarations useful for the unit tests.
+ */
+
+#ifndef TESTCODE_UNITMAIN_H
+#define TESTCODE_UNITMAIN_H
+#include "util/log.h"
+
+/** number of tests done */
+extern int testcount;
+/** test bool x, exits on failure, increases testcount. */
+#ifdef DEBUG_UNBOUND
+#define unit_assert(x) do {testcount++; log_assert(x);} while(0)
+#else
+#define unit_assert(x) do {testcount++; if(!(x)) { fprintf(stderr, "assertion failure %s:%d\n", __FILE__, __LINE__); exit(1);}} while(0)
+#endif
+
+/** we are now testing this function */
+void unit_show_func(const char* file, const char* func);
+/** we are testing this functionality */
+void unit_show_feature(const char* feature);
+
+/** unit test lruhashtable implementation */
+void lruhash_test(void);
+/** unit test slabhashtable implementation */
+void slabhash_test(void);
+/** unit test for msgreply and msgparse */
+void msgparse_test(void);
+/** unit test dname handling functions */
+void dname_test(void);
+/** unit test trust anchor storage functions */
+void anchors_test(void);
+/** unit test for verification functions */
+void verify_test(void);
+/** unit test for negative cache functions */
+void neg_test(void);
+/** unit test for regional allocator functions */
+void regional_test(void);
+#ifdef CLIENT_SUBNET
+/** Unit test for ECS functions */
+void ecs_test(void);
+#endif /* CLIENT_SUBNET */
+/** unit test for ldns functions */
+void ldns_test(void);
+/** unit test for auth zone functions */
+void authzone_test(void);
+
+#endif /* TESTCODE_UNITMAIN_H */
diff --git a/usr.sbin/unbound/testcode/unitmsgparse.c b/usr.sbin/unbound/testcode/unitmsgparse.c
new file mode 100644 (file)
index 0000000..627d10b
--- /dev/null
@@ -0,0 +1,542 @@
+/*
+ * testcode/unitmsgparse.c - unit test for msg parse routines.
+ *
+ * Copyright (c) 2007, NLnet Labs. All rights reserved.
+ *
+ * This software is open source.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * Neither the name of the NLNET LABS nor the names of its contributors may
+ * be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+ * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+/**
+ * \file
+ * Calls msg parse unit tests. Exits with code 1 on a failure. 
+ */
+
+#include "config.h"
+#include <sys/time.h>
+#include "util/log.h"
+#include "testcode/unitmain.h"
+#include "util/data/msgparse.h"
+#include "util/data/msgreply.h"
+#include "util/data/msgencode.h"
+#include "util/data/dname.h"
+#include "util/alloc.h"
+#include "util/regional.h"
+#include "util/net_help.h"
+#include "testcode/readhex.h"
+#include "testcode/testpkts.h"
+#include "sldns/sbuffer.h"
+#include "sldns/str2wire.h"
+#include "sldns/wire2str.h"
+
+/** verbose message parse unit test */
+static int vbmp = 0;
+/** do not accept formerr */
+static int check_formerr_gone = 0;
+/** if matching within a section should disregard the order of RRs. */
+static int matches_nolocation = 0;
+/** see if RRSIGs are properly matched to RRsets. */
+static int check_rrsigs = 0;
+/** do not check buffer sameness */
+static int check_nosameness = 0;
+
+/** see if buffers contain the same packet */
+static int
+test_buffers(sldns_buffer* pkt, sldns_buffer* out)
+{
+       /* check binary same */
+       if(sldns_buffer_limit(pkt) == sldns_buffer_limit(out) &&
+               memcmp(sldns_buffer_begin(pkt), sldns_buffer_begin(out),
+                       sldns_buffer_limit(pkt)) == 0) {
+               if(vbmp) printf("binary the same (length=%u)\n",
+                               (unsigned)sldns_buffer_limit(pkt));
+               return 1;
+       }
+
+       if(vbmp) {
+               size_t sz = 16;
+               size_t count;
+               size_t lim = sldns_buffer_limit(out);
+               if(sldns_buffer_limit(pkt) < lim)
+                       lim = sldns_buffer_limit(pkt);
+               for(count=0; count<lim; count+=sz) {
+                       size_t rem = sz;
+                       if(lim-count < sz) rem = lim-count;
+                       if(memcmp(sldns_buffer_at(pkt, count), 
+                               sldns_buffer_at(out, count), rem) == 0) {
+                               log_info("same %d %d", (int)count, (int)rem);
+                               log_hex("same: ", sldns_buffer_at(pkt, count),
+                                       rem);
+                       } else {
+                               log_info("diff %d %d", (int)count, (int)rem);
+                               log_hex("difp: ", sldns_buffer_at(pkt, count),
+                                       rem);
+                               log_hex("difo: ", sldns_buffer_at(out, count),
+                                       rem);
+                       }
+               }
+       }
+
+       /* check if it 'means the same' */
+       if(vbmp) {
+               char* s1, *s2;
+               log_buf(0, "orig in hex", pkt);
+               log_buf(0, "unbound out in hex", out);
+               printf("\npacket from unbound (%d):\n", 
+                       (int)sldns_buffer_limit(out));
+               s1 = sldns_wire2str_pkt(sldns_buffer_begin(out),
+                       sldns_buffer_limit(out));
+               printf("%s\n", s1?s1:"null");
+               free(s1);
+
+               printf("\npacket original (%d):\n", 
+                       (int)sldns_buffer_limit(pkt));
+               s2 = sldns_wire2str_pkt(sldns_buffer_begin(pkt),
+                       sldns_buffer_limit(pkt));
+               printf("%s\n", s2?s2:"null");
+               free(s2);
+               printf("\n");
+       }
+       /* if it had two EDNS sections, skip comparison */
+       if(1) {
+               char* s = sldns_wire2str_pkt(sldns_buffer_begin(pkt),
+                       sldns_buffer_limit(pkt));
+               char* e1 = strstr(s, "; EDNS:");
+               if(e1 && strstr(e1+4, "; EDNS:")) {
+                       free(s);
+                       return 0;
+               }
+               free(s);
+       }
+       /* compare packets */
+       unit_assert(match_all(sldns_buffer_begin(pkt), sldns_buffer_limit(pkt),
+               sldns_buffer_begin(out), sldns_buffer_limit(out), 1,
+               matches_nolocation));
+       return 0;
+}
+
+/** check if unbound formerr equals ldns formerr */
+static void
+checkformerr(sldns_buffer* pkt)
+{
+       int status = 0;
+       char* s = sldns_wire2str_pkt(sldns_buffer_begin(pkt),
+               sldns_buffer_limit(pkt));
+       if(!s) fatal_exit("out of memory");
+       if(strstr(s, "Error")) status = 1;
+       if(strstr(s, "error")) status = 1;
+       if(status == 0) {
+               printf("Formerr, but ldns gives packet:\n");
+               printf("%s\n", s);
+               free(s);
+               exit(1);
+       }
+       free(s);
+       unit_assert(status != 0);
+}
+
+/** performance test message encoding */
+static void
+perf_encode(struct query_info* qi, struct reply_info* rep, uint16_t id, 
+       uint16_t flags, sldns_buffer* out, time_t timenow, 
+       struct edns_data* edns)
+{
+       static int num = 0;
+       int ret;
+       size_t max = 10000;
+       size_t i;
+       struct timeval start, end;
+       double dt;
+       struct regional* r2 = regional_create();
+       if(gettimeofday(&start, NULL) < 0)
+               fatal_exit("gettimeofday: %s", strerror(errno));
+       /* encode a couple times */
+       for(i=0; i<max; i++) {
+               ret = reply_info_encode(qi, rep, id, flags, out, timenow,
+                       r2, 65535, (int)(edns->bits & EDNS_DO) );
+               unit_assert(ret != 0); /* udp packets should fit */
+               attach_edns_record(out, edns);
+               regional_free_all(r2);
+       }
+       if(gettimeofday(&end, NULL) < 0)
+               fatal_exit("gettimeofday: %s", strerror(errno));
+       /* time in millisec */
+       dt = (double)(end.tv_sec - start.tv_sec)*1000. + 
+               ((double)end.tv_usec - (double)start.tv_usec)/1000.;
+       printf("[%d] did %u in %g msec for %f encode/sec size %d\n", num++,
+               (unsigned)max, dt, (double)max / (dt/1000.), 
+               (int)sldns_buffer_limit(out));
+       regional_destroy(r2);
+}
+
+/** perf test a packet */
+static void
+perftestpkt(sldns_buffer* pkt, struct alloc_cache* alloc, sldns_buffer* out, 
+       const char* hex)
+{
+       struct query_info qi;
+       struct reply_info* rep = 0;
+       int ret;
+       uint16_t id;
+       uint16_t flags;
+       time_t timenow = 0;
+       struct regional* region = regional_create();
+       struct edns_data edns;
+
+       hex_to_buf(pkt, hex);
+       memmove(&id, sldns_buffer_begin(pkt), sizeof(id));
+       if(sldns_buffer_limit(pkt) < 2)
+               flags = 0;
+       else    memmove(&flags, sldns_buffer_at(pkt, 2), sizeof(flags));
+       flags = ntohs(flags);
+       ret = reply_info_parse(pkt, alloc, &qi, &rep, region, &edns);
+       if(ret != 0) {
+               char rbuf[16];
+               sldns_wire2str_rcode_buf(ret, rbuf, sizeof(rbuf));
+               if(vbmp) printf("parse code %d: %s\n", ret, rbuf);
+               if(ret == LDNS_RCODE_FORMERR)
+                       checkformerr(pkt);
+               unit_assert(ret != LDNS_RCODE_SERVFAIL);
+       } else {
+               perf_encode(&qi, rep, id, flags, out, timenow, &edns);
+       } 
+
+       query_info_clear(&qi);
+       reply_info_parsedelete(rep, alloc);
+       regional_destroy(region);
+}
+
+/** print packed rrset */
+static void
+print_rrset(struct ub_packed_rrset_key* rrset)
+{
+       struct packed_rrset_data* d = (struct packed_rrset_data*)rrset->
+                       entry.data;
+       char buf[65535];
+       size_t i;
+       for(i=0; i<d->count+d->rrsig_count; i++) {
+               if(!packed_rr_to_string(rrset, i, 0, buf, sizeof(buf)))
+                       printf("failedtoconvert %d\n", (int)i);
+               else
+                       printf("%s\n", buf);
+       }
+}
+
+/** debug print a packet that failed */
+static void
+print_packet_rrsets(struct query_info* qinfo, struct reply_info* rep)
+{
+       size_t i;
+       log_query_info(0, "failed query", qinfo);
+       printf(";; ANSWER SECTION (%d rrsets)\n", (int)rep->an_numrrsets);
+       for(i=0; i<rep->an_numrrsets; i++) {
+               printf("; rrset %d\n", (int)i);
+               print_rrset(rep->rrsets[i]);
+       }
+       printf(";; AUTHORITY SECTION (%d rrsets)\n", (int)rep->ns_numrrsets);
+       for(i=rep->an_numrrsets; i<rep->an_numrrsets+rep->ns_numrrsets; i++) {
+               printf("; rrset %d\n", (int)i);
+               print_rrset(rep->rrsets[i]);
+       }
+       printf(";; ADDITIONAL SECTION (%d rrsets)\n", (int)rep->ar_numrrsets);
+       for(i=rep->an_numrrsets+rep->ns_numrrsets; i<rep->rrset_count; i++) {
+               printf("; rrset %d\n", (int)i);
+               print_rrset(rep->rrsets[i]);
+       }
+       printf(";; packet end\n");
+}
+
+/** check that there is no data element that matches the RRSIG */
+static int
+no_data_for_rrsig(struct reply_info* rep, struct ub_packed_rrset_key* rrsig)
+{
+       size_t i;
+       for(i=0; i<rep->rrset_count; i++) {
+               if(ntohs(rep->rrsets[i]->rk.type) == LDNS_RR_TYPE_RRSIG)
+                       continue;
+               if(query_dname_compare(rep->rrsets[i]->rk.dname, 
+                       rrsig->rk.dname) == 0)
+                       /* only name is compared right now */
+                       return 0;
+       }
+       return 1;
+}
+
+/** check RRSIGs in packet */
+static void
+check_the_rrsigs(struct query_info* qinfo, struct reply_info* rep)
+{
+       /* every RRSIG must be matched to an RRset */
+       size_t i;
+       for(i=0; i<rep->rrset_count; i++) {
+               struct ub_packed_rrset_key* s = rep->rrsets[i];
+               if(ntohs(s->rk.type) == LDNS_RR_TYPE_RRSIG) {
+                       /* see if really a problem, i.e. is there a data
+                        * element. */
+                       if(no_data_for_rrsig(rep, rep->rrsets[i]))
+                               continue;
+                       log_dns_msg("rrsig failed for packet", qinfo, rep);
+                       print_packet_rrsets(qinfo, rep);
+                       printf("failed rrset is nr %d\n", (int)i);
+                       unit_assert(0);
+               }
+       }
+}
+
+/** test a packet */
+static void
+testpkt(sldns_buffer* pkt, struct alloc_cache* alloc, sldns_buffer* out, 
+       const char* hex)
+{
+       struct query_info qi;
+       struct reply_info* rep = 0;
+       int ret;
+       uint16_t id;
+       uint16_t flags;
+       uint32_t timenow = 0;
+       struct regional* region = regional_create();
+       struct edns_data edns;
+
+       hex_to_buf(pkt, hex);
+       memmove(&id, sldns_buffer_begin(pkt), sizeof(id));
+       if(sldns_buffer_limit(pkt) < 2)
+               flags = 0;
+       else    memmove(&flags, sldns_buffer_at(pkt, 2), sizeof(flags));
+       flags = ntohs(flags);
+       ret = reply_info_parse(pkt, alloc, &qi, &rep, region, &edns);
+       if(ret != 0) {
+               char rbuf[16];
+               sldns_wire2str_rcode_buf(ret, rbuf, sizeof(rbuf));
+               if(vbmp) printf("parse code %d: %s\n", ret, rbuf);
+               if(ret == LDNS_RCODE_FORMERR) {
+                       unit_assert(!check_formerr_gone);
+                       checkformerr(pkt);
+               }
+               unit_assert(ret != LDNS_RCODE_SERVFAIL);
+       } else if(!check_formerr_gone) {
+               const size_t lim = 512;
+               ret = reply_info_encode(&qi, rep, id, flags, out, timenow,
+                       region, 65535, (int)(edns.bits & EDNS_DO) );
+               unit_assert(ret != 0); /* udp packets should fit */
+               attach_edns_record(out, &edns);
+               if(vbmp) printf("inlen %u outlen %u\n", 
+                       (unsigned)sldns_buffer_limit(pkt),
+                       (unsigned)sldns_buffer_limit(out));
+               if(!check_nosameness)
+                       test_buffers(pkt, out);
+               if(check_rrsigs)
+                       check_the_rrsigs(&qi, rep);
+
+               if(sldns_buffer_limit(out) > lim) {
+                       ret = reply_info_encode(&qi, rep, id, flags, out, 
+                               timenow, region, 
+                               lim - calc_edns_field_size(&edns),
+                               (int)(edns.bits & EDNS_DO));
+                       unit_assert(ret != 0); /* should fit, but with TC */
+                       attach_edns_record(out, &edns);
+                       if( LDNS_QDCOUNT(sldns_buffer_begin(out)) !=
+                               LDNS_QDCOUNT(sldns_buffer_begin(pkt)) ||
+                               LDNS_ANCOUNT(sldns_buffer_begin(out)) !=
+                               LDNS_ANCOUNT(sldns_buffer_begin(pkt)) ||
+                               LDNS_NSCOUNT(sldns_buffer_begin(out)) !=
+                               LDNS_NSCOUNT(sldns_buffer_begin(pkt)))
+                               unit_assert(
+                               LDNS_TC_WIRE(sldns_buffer_begin(out)));
+                               /* must set TC bit if shortened */
+                       unit_assert(sldns_buffer_limit(out) <= lim);
+               }
+       } 
+
+       query_info_clear(&qi);
+       reply_info_parsedelete(rep, alloc);
+       regional_destroy(region);
+}
+
+/** simple test of parsing */
+static void
+simpletest(sldns_buffer* pkt, struct alloc_cache* alloc, sldns_buffer* out)
+{
+       /* a root query  drill -q - */
+       testpkt(pkt, alloc, out, 
+               " c5 40 01 00 00 01 00 00 00 00 00 00 00 00 02 00 01 ");
+
+       /* very small packet */
+       testpkt(pkt, alloc, out, 
+"; 0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19\n"
+";-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --\n"
+"74 0c 85 83 00 01 00 00 00 01 00 00 03 62 6c 61 09 6e 6c 6e    ;          1-  20\n"
+"65 74 6c 61 62 73 02 6e 6c 00 00 0f 00 01 09 6e 6c 6e 65 74    ;         21-  40\n"
+"6c 61 62 73 02 6e 6c 00 00 06 00 01 00 00 46 50 00 40 04 6f    ;         41-  60\n"
+"70 65 6e 09 6e 6c 6e 65 74 6c 61 62 73 02 6e 6c 00 0a 68 6f    ;         61-  80\n"
+"73 74 6d 61 73 74 65 72 09 6e 6c 6e 65 74 6c 61 62 73 02 6e    ;         81- 100\n"
+"6c 00 77 a1 02 58 00 00 70 80 00 00 1c 20 00 09 3a 80 00 00    ;        101- 120\n"
+"46 50\n");
+       
+       /* a root reply  drill -w - */
+       testpkt(pkt, alloc, out, 
+       " ; 0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19\n"
+       " ;-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --\n"
+       " 97 3f 81 80 00 01 00 0d 00 00 00 02 00 00 02 00 01 00 00 02    ;          1-  20\n"
+       " 00 01 00 06 6d 38 00 14 01 49 0c 52 4f 4f 54 2d 53 45 52 56    ;         21-  40\n"
+       " 45 52 53 03 4e 45 54 00 00 00 02 00 01 00 06 6d 38 00 14 01    ;         41-  60\n"
+       " 4a 0c 52 4f 4f 54 2d 53 45 52 56 45 52 53 03 4e 45 54 00 00    ;         61-  80\n"
+       " 00 02 00 01 00 06 6d 38 00 14 01 4b 0c 52 4f 4f 54 2d 53 45    ;         81- 100\n"
+       " 52 56 45 52 53 03 4e 45 54 00 00 00 02 00 01 00 06 6d 38 00    ;        101- 120\n"
+       " 14 01 4c 0c 52 4f 4f 54 2d 53 45 52 56 45 52 53 03 4e 45 54    ;        121- 140\n"
+       " 00 00 00 02 00 01 00 06 6d 38 00 14 01 4d 0c 52 4f 4f 54 2d    ;        141- 160\n"
+       " 53 45 52 56 45 52 53 03 4e 45 54 00 00 00 02 00 01 00 06 6d    ;        161- 180\n"
+       " 38 00 14 01 41 0c 52 4f 4f 54 2d 53 45 52 56 45 52 53 03 4e    ;        181- 200\n"
+       " 45 54 00 00 00 02 00 01 00 06 6d 38 00 14 01 42 0c 52 4f 4f    ;        201- 220\n"
+       " 54 2d 53 45 52 56 45 52 53 03 4e 45 54 00 00 00 02 00 01 00    ;        221- 240\n"
+       " 06 6d 38 00 14 01 43 0c 52 4f 4f 54 2d 53 45 52 56 45 52 53    ;        241- 260\n"
+       " 03 4e 45 54 00 00 00 02 00 01 00 06 6d 38 00 14 01 44 0c 52    ;        261- 280\n"
+       " 4f 4f 54 2d 53 45 52 56 45 52 53 03 4e 45 54 00 00 00 02 00    ;        281- 300\n"
+       " 01 00 06 6d 38 00 14 01 45 0c 52 4f 4f 54 2d 53 45 52 56 45    ;        301- 320\n"
+       " 52 53 03 4e 45 54 00 00 00 02 00 01 00 06 6d 38 00 14 01 46    ;        321- 340\n"
+       " 0c 52 4f 4f 54 2d 53 45 52 56 45 52 53 03 4e 45 54 00 00 00    ;        341- 360\n"
+       " 02 00 01 00 06 6d 38 00 14 01 47 0c 52 4f 4f 54 2d 53 45 52    ;        361- 380\n"
+       " 56 45 52 53 03 4e 45 54 00 00 00 02 00 01 00 06 6d 38 00 14    ;        381- 400\n"
+       " 01 48 0c 52 4f 4f 54 2d 53 45 52 56 45 52 53 03 4e 45 54 00    ;        401- 420\n"
+       " 01 41 0c 52 4f 4f 54 2d 53 45 52 56 45 52 53 03 4e 45 54 00    ;        421- 440\n"
+       " 00 01 00 01 00 02 64 b9 00 04 c6 29 00 04 01 4a 0c 52 4f 4f    ;        441- 460\n"
+       " 54 2d 53 45 52 56 45 52 53 03 4e 45 54 00 00 01 00 01 00 02    ;        461- 480\n"
+       " 64 b9 00 04 c0 3a 80 1e  ");
+
+       /* root delegation from unbound trace with new AAAA glue */
+       perftestpkt(pkt, alloc, out,
+       "55BC84000001000D00000014000002000100000200010007E900001401610C726F6F742D73657276657273036E65740000000200010007E90000040162C01E00000200010007E90000040163C01E00000200010007E90000040164C01E00000200010007E90000040165C01E00000200010007E90000040166C01E00000200010007E90000040167C01E00000200010007E90000040168C01E00000200010007E90000040169C01E00000200010007E9000004016AC01E00000200010007E9000004016BC01E00000200010007E9000004016CC01E00000200010007E9000004016DC01EC01C000100010007E9000004C6290004C03B000100010007E9000004C0E44FC9C04A000100010007E9000004C021040CC059000100010007E900000480080A5AC068000100010007E9000004C0CBE60AC077000100010007E9000004C00505F1C086000100010007E9000004C0702404C095000100010007E9000004803F0235C0A4000100010007E9000004C0249411C0B3000100010007E9000004C03A801EC0C2000100010007E9000004C1000E81C0D1000100010007E9000004C707532AC0E0000100010007E9000004CA0C1B21C01C001C00010007E900001020010503BA3E00000000000000020030C077001C00010007E900001020010500002F0000000000000000000FC095001C00010007E90000102001050000010000"
+       "00000000803F0235C0B3001C00010007E9000010200105030C2700000000000000020030C0C2001C00010007E9000010200107FD000000000000000000000001C0E0001C00010007E900001020010DC30000000000000000000000350000291000000000000000"
+       );
+}
+
+/** simple test of parsing, pcat file */
+static void
+testfromfile(sldns_buffer* pkt, struct alloc_cache* alloc, sldns_buffer* out,
+       const char* fname)
+{
+       FILE* in = fopen(fname, "r");
+       char buf[102400];
+       int no=0;
+       if(!in) {
+               perror("fname");
+               return;
+       }
+       while(fgets(buf, (int)sizeof(buf), in)) {
+               if(buf[0] == ';') /* comment */
+                       continue;
+               if(strlen(buf) < 10) /* skip pcat line numbers. */
+                       continue;
+               if(vbmp) {
+                       printf("test no %d: %s", no, buf);
+                       fflush(stdout);
+               }
+               testpkt(pkt, alloc, out, buf);
+               no++;
+       }
+       fclose(in);
+}
+
+/** simple test of parsing, drill file */
+static void
+testfromdrillfile(sldns_buffer* pkt, struct alloc_cache* alloc, 
+       sldns_buffer* out, const char* fname)
+{
+       /*  ;-- is used to indicate a new message */
+       FILE* in = fopen(fname, "r");
+       char buf[102400];
+       char* np = buf;
+       buf[0]=0;
+       if(!in) {
+               perror("fname");
+               return;
+       }
+       while(fgets(np, (int)sizeof(buf) - (np-buf), in)) {
+               if(strncmp(np, ";--", 3) == 0) {
+                       /* new entry */
+                       /* test previous */
+                       if(np != buf)
+                               testpkt(pkt, alloc, out, buf);
+                       /* set for new entry */
+                       np = buf;
+                       buf[0]=0;
+                       continue;
+               }
+               if(np[0] == ';') /* comment */
+                       continue;
+               np = &np[strlen(np)];
+       }
+       testpkt(pkt, alloc, out, buf);
+       fclose(in);
+}
+
+void msgparse_test(void)
+{
+       time_t origttl = MAX_NEG_TTL;
+       sldns_buffer* pkt = sldns_buffer_new(65553);
+       sldns_buffer* out = sldns_buffer_new(65553);
+       struct alloc_cache super_a, alloc;
+       MAX_NEG_TTL = 86400;
+       /* init */
+       alloc_init(&super_a, NULL, 0);
+       alloc_init(&alloc, &super_a, 2);
+
+       unit_show_feature("message parse");
+       simpletest(pkt, &alloc, out);
+       /* plain hex dumps, like pcat */
+       testfromfile(pkt, &alloc, out, "testdata/test_packets.1");
+       testfromfile(pkt, &alloc, out, "testdata/test_packets.2");
+       testfromfile(pkt, &alloc, out, "testdata/test_packets.3");
+       /* like from drill -w - */
+       testfromdrillfile(pkt, &alloc, out, "testdata/test_packets.4");
+       testfromdrillfile(pkt, &alloc, out, "testdata/test_packets.5");
+
+       matches_nolocation = 1; /* RR order not important for the next test */
+       testfromdrillfile(pkt, &alloc, out, "testdata/test_packets.6");
+       check_rrsigs = 1;
+       testfromdrillfile(pkt, &alloc, out, "testdata/test_packets.7");
+       check_rrsigs = 0;
+       matches_nolocation = 0; 
+
+       check_formerr_gone = 1;
+       testfromdrillfile(pkt, &alloc, out, "testdata/test_packets.8");
+       check_formerr_gone = 0;
+
+       check_rrsigs = 1;
+       check_nosameness = 1;
+       testfromdrillfile(pkt, &alloc, out, "testdata/test_packets.9");
+       check_nosameness = 0;
+       check_rrsigs = 0;
+
+       /* cleanup */
+       alloc_clear(&alloc);
+       alloc_clear(&super_a);
+       sldns_buffer_free(pkt);
+       sldns_buffer_free(out);
+       MAX_NEG_TTL = origttl;
+}
diff --git a/usr.sbin/unbound/testcode/unitneg.c b/usr.sbin/unbound/testcode/unitneg.c
new file mode 100644 (file)
index 0000000..4cd9b30
--- /dev/null
@@ -0,0 +1,543 @@
+/*
+ * testcode/unitneg.c - unit test for negative cache routines.
+ *
+ * Copyright (c) 2008, NLnet Labs. All rights reserved.
+ *
+ * This software is open source.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * Neither the name of the NLNET LABS nor the names of its contributors may
+ * be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+ * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+/**
+ * \file
+ * Calls negative cache unit tests. Exits with code 1 on a failure. 
+ */
+
+#include "config.h"
+#include "util/log.h"
+#include "util/net_help.h"
+#include "util/data/packed_rrset.h"
+#include "util/data/dname.h"
+#include "testcode/unitmain.h"
+#include "validator/val_neg.h"
+#include "sldns/rrdef.h"
+
+/** verbose unit test for negative cache */
+static int negverbose = 0;
+
+/** debug printout of neg cache */
+static void print_neg_cache(struct val_neg_cache* neg)
+{
+       char buf[1024];
+       struct val_neg_zone* z;
+       struct val_neg_data* d;
+       printf("neg_cache print\n");
+       printf("memuse %d of %d\n", (int)neg->use, (int)neg->max);
+       printf("maxiter %d\n", (int)neg->nsec3_max_iter);
+       printf("%d zones\n", (int)neg->tree.count);
+       RBTREE_FOR(z, struct val_neg_zone*, &neg->tree) {
+               dname_str(z->name, buf);
+               printf("%24s", buf);
+               printf(" len=%2.2d labs=%d inuse=%d count=%d tree.count=%d\n",
+                       (int)z->len, z->labs, (int)z->in_use, z->count,
+                       (int)z->tree.count);
+       }
+       RBTREE_FOR(z, struct val_neg_zone*, &neg->tree) {
+               printf("\n");
+               dname_print(stdout, NULL, z->name);
+               printf(" zone details\n");
+               printf("len=%2.2d labs=%d inuse=%d count=%d tree.count=%d\n",
+                       (int)z->len, z->labs, (int)z->in_use, z->count,
+                       (int)z->tree.count);
+               if(z->parent) {
+                       printf("parent=");
+                       dname_print(stdout, NULL, z->parent->name);
+                       printf("\n");
+               } else {
+                       printf("parent=NULL\n");
+               }
+
+               RBTREE_FOR(d, struct val_neg_data*, &z->tree) {
+                       dname_str(d->name, buf);
+                       printf("%24s", buf);
+                       printf(" len=%2.2d labs=%d inuse=%d count=%d\n",
+                               (int)d->len, d->labs, (int)d->in_use, d->count);
+               }
+       }
+}
+
+/** get static pointer to random zone name */
+static char* get_random_zone(void)
+{
+       static char zname[36];
+       int labels = random() % 3;
+       int i;
+       char* p = zname;
+       int labnum;
+
+       for(i=0; i<labels; i++) {
+               labnum = random()%10;
+               snprintf(p, sizeof(zname)-(p-zname), "\003%3.3d", labnum);
+               p+=4;
+       }
+       snprintf(p, sizeof(zname)-(p-zname), "\007example\003com");
+       return zname;
+}
+
+/** get static pointer to random data names from and to */
+static void get_random_data(char** fromp, char** top, char* zname)
+{
+       static char buf1[256], buf2[256];
+       int type;
+       int lab1, lab2;
+       int labnum1[10], labnum2[10];
+       int i;
+       char* p;
+
+       *fromp = buf1;
+       *top = buf2;
+       type = random()%10;
+
+       if(type == 0) {
+               /* ENT */
+               lab1 = random() %3 + 1;
+               lab2 = lab1 + random()%3 + 1;
+               for(i=0; i<lab1; i++) {
+                       labnum1[i] = random()%100;
+                       labnum2[i] = labnum1[i];
+               }
+               for(i=lab1; i<lab2; i++) {
+                       labnum2[i] = random()%100;
+               }
+       } else if(type == 1) {
+               /* end of zone */
+               lab2 = 0;
+               lab1 = random()%3 + 1;
+               for(i=0; i<lab1; i++) {
+                       labnum1[i] = random()%100;
+               }
+       } else if(type == 2) {
+               /* start of zone */
+               lab1 = 0;
+               lab2 = random()%3 + 1;
+               for(i=0; i<lab2; i++) {
+                       labnum2[i] = random()%100;
+               }
+       } else {
+               /* normal item */
+               int common = random()%3;
+               lab1 = random() %3 + 1;
+               lab2 = random() %3 + 1;
+               for(i=0; i<common; i++) {
+                       labnum1[i] = random()%100;
+                       labnum2[i] = labnum1[i];
+               }
+               labnum1[common] = random()%100;
+               labnum2[common] = labnum1[common] + random()%20;
+               for(i=common; i<lab1; i++)
+                       labnum1[i] = random()%100;
+               for(i=common; i<lab2; i++)
+                       labnum2[i] = random()%100;
+       } 
+
+       /* construct first */
+       p = buf1;
+       for(i=0; i<lab1; i++) {
+               snprintf(p, 256-(p-buf1), "\003%3.3d", labnum1[i]);
+               p+=4;
+       }
+       snprintf(p, 256-(p-buf1), "%s", zname);
+
+       /* construct 2nd */
+       p = buf2+2;
+       for(i=0; i<lab2; i++) {
+               snprintf(p, 256-(p-buf2)-3, "\003%3.3d", labnum2[i]);
+               p+=4;
+       }
+       snprintf(p, 256-(p-buf2)-3, "%s", zname);
+       buf2[0] = (char)(strlen(buf2+2)+1);
+       buf2[1] = 0;
+
+       if(negverbose) {
+               log_nametypeclass(0, "add from", (uint8_t*)buf1, 0, 0);
+               log_nametypeclass(0, "add to  ", (uint8_t*)buf2+2, 0, 0);
+       }
+}
+
+/** add a random item */
+static void add_item(struct val_neg_cache* neg)
+{
+       struct val_neg_zone* z;
+       struct packed_rrset_data rd;
+       struct ub_packed_rrset_key nsec;
+       size_t rr_len;
+       time_t rr_ttl;
+       uint8_t* rr_data;
+       char* zname = get_random_zone();
+       char* from, *to;
+
+       lock_basic_lock(&neg->lock);
+       if(negverbose)
+               log_nametypeclass(0, "add to zone", (uint8_t*)zname, 0, 0);
+       z = neg_find_zone(neg, (uint8_t*)zname, strlen(zname)+1, 
+               LDNS_RR_CLASS_IN);
+       if(!z) {
+               z = neg_create_zone(neg,  (uint8_t*)zname, strlen(zname)+1,
+                               LDNS_RR_CLASS_IN);
+       }
+       unit_assert(z);
+       val_neg_zone_take_inuse(z);
+
+       /* construct random NSEC item */
+       get_random_data(&from, &to, zname);
+
+       /* create nsec and insert it */
+       memset(&rd, 0, sizeof(rd));
+       memset(&nsec, 0, sizeof(nsec));
+       nsec.rk.dname = (uint8_t*)from;
+       nsec.rk.dname_len = strlen(from)+1;
+       nsec.rk.type = htons(LDNS_RR_TYPE_NSEC);
+       nsec.rk.rrset_class = htons(LDNS_RR_CLASS_IN);
+       nsec.entry.data = &rd;
+       rd.security = sec_status_secure;
+       rd.count = 1;
+       rd.rr_len = &rr_len;
+       rr_len = 19;
+       rd.rr_ttl = &rr_ttl;
+       rr_ttl = 0;
+       rd.rr_data = &rr_data;
+       rr_data = (uint8_t*)to;
+
+       neg_insert_data(neg, z, &nsec);
+       lock_basic_unlock(&neg->lock);
+}
+
+/** remove a random item */
+static void remove_item(struct val_neg_cache* neg)
+{
+       int n, i;
+       struct val_neg_data* d;
+       rbnode_type* walk;
+       struct val_neg_zone* z;
+       
+       lock_basic_lock(&neg->lock);
+       if(neg->tree.count == 0) {
+               lock_basic_unlock(&neg->lock);
+               return; /* nothing to delete */
+       }
+
+       /* pick a random zone */
+       walk = rbtree_first(&neg->tree); /* first highest parent, big count */
+       z = (struct val_neg_zone*)walk;
+       n = random() % (int)(z->count);
+       if(negverbose)
+               printf("neg stress delete zone %d\n", n);
+       i=0;
+       walk = rbtree_first(&neg->tree);
+       z = (struct val_neg_zone*)walk;
+       while(i!=n+1 && walk && walk != RBTREE_NULL && !z->in_use) {
+               walk = rbtree_next(walk);
+               z = (struct val_neg_zone*)walk;
+               if(z->in_use)
+                       i++;
+       }
+       if(!walk || walk == RBTREE_NULL) {
+               lock_basic_unlock(&neg->lock);
+               return;
+       }
+       if(!z->in_use) {
+               lock_basic_unlock(&neg->lock);
+               return;
+       }
+       if(negverbose)
+               log_nametypeclass(0, "delete zone", z->name, 0, 0);
+
+       /* pick a random nsec item. - that is in use */
+       walk = rbtree_first(&z->tree); /* first is highest parent */
+       d = (struct val_neg_data*)walk;
+       n = random() % (int)(d->count);
+       if(negverbose)
+               printf("neg stress delete item %d\n", n);
+       i=0;
+       walk = rbtree_first(&z->tree);
+       d = (struct val_neg_data*)walk;
+       while(i!=n+1 && walk && walk != RBTREE_NULL && !d->in_use) {
+               walk = rbtree_next(walk);
+               d = (struct val_neg_data*)walk;
+               if(d->in_use)
+                       i++;
+       }
+       if(!walk || walk == RBTREE_NULL) {
+               lock_basic_unlock(&neg->lock);
+               return;
+       }
+       if(d->in_use) {
+               if(negverbose)
+                       log_nametypeclass(0, "neg delete item:", d->name, 0, 0);
+               neg_delete_data(neg, d);
+       }
+       lock_basic_unlock(&neg->lock);
+}
+
+/** sum up the zone trees */
+static size_t sumtrees_all(struct val_neg_cache* neg)
+{
+       size_t res = 0;
+       struct val_neg_zone* z;
+       RBTREE_FOR(z, struct val_neg_zone*, &neg->tree) {
+               res += z->tree.count;
+       }
+       return res;
+}
+
+/** sum up the zone trees, in_use only */
+static size_t sumtrees_inuse(struct val_neg_cache* neg)
+{
+       size_t res = 0;
+       struct val_neg_zone* z;
+       struct val_neg_data* d;
+       RBTREE_FOR(z, struct val_neg_zone*, &neg->tree) {
+               /* get count of highest parent for num in use */
+               d = (struct val_neg_data*)rbtree_first(&z->tree);
+               if(d && (rbnode_type*)d!=RBTREE_NULL)
+                       res += d->count;
+       }
+       return res;
+}
+
+/** check if lru is still valid */
+static void check_lru(struct val_neg_cache* neg)
+{
+       struct val_neg_data* p, *np;
+       size_t num = 0;
+       size_t inuse;
+       p = neg->first;
+       while(p) {
+               if(!p->prev) {
+                       unit_assert(neg->first == p);
+               }
+               np = p->next;
+               if(np) {
+                       unit_assert(np->prev == p);
+               } else {
+                       unit_assert(neg->last == p);
+               }
+               num++;
+               p = np;
+       }
+       inuse = sumtrees_inuse(neg);
+       if(negverbose)
+               printf("num lru %d, inuse %d, all %d\n",
+                       (int)num, (int)sumtrees_inuse(neg), 
+                       (int)sumtrees_all(neg));
+       unit_assert( num == inuse);
+       unit_assert( inuse <= sumtrees_all(neg));
+}
+
+/** sum up number of items inuse in subtree */
+static int sum_subtree_inuse(struct val_neg_zone* zone, 
+       struct val_neg_data* data)
+{
+       struct val_neg_data* d;
+       int num = 0;
+       RBTREE_FOR(d, struct val_neg_data*, &zone->tree) {
+               if(dname_subdomain_c(d->name, data->name)) {
+                       if(d->in_use)
+                               num++;
+               }
+       }
+       return num;
+}
+
+/** sum up number of items inuse in subtree */
+static int sum_zone_subtree_inuse(struct val_neg_cache* neg,
+       struct val_neg_zone* zone)
+{
+       struct val_neg_zone* z;
+       int num = 0;
+       RBTREE_FOR(z, struct val_neg_zone*, &neg->tree) {
+               if(dname_subdomain_c(z->name, zone->name)) {
+                       if(z->in_use)
+                               num++;
+               }
+       }
+       return num;
+}
+
+/** check point in data tree */
+static void check_data(struct val_neg_zone* zone, struct val_neg_data* data)
+{
+       unit_assert(data->count > 0);
+       if(data->parent) {
+               unit_assert(data->parent->count >= data->count);
+               if(data->parent->in_use) {
+                       unit_assert(data->parent->count > data->count);
+               }
+               unit_assert(data->parent->labs == data->labs-1);
+               /* and parent must be one label shorter */
+               unit_assert(data->name[0] == (data->len-data->parent->len-1));
+               unit_assert(query_dname_compare(data->name + data->name[0]+1,
+                       data->parent->name) == 0);
+       } else {
+               /* must be apex */
+               unit_assert(dname_is_root(data->name));
+       }
+       /* tree property: */
+       unit_assert(data->count == sum_subtree_inuse(zone, data));
+}
+
+/** check if tree of data in zone is valid */
+static void checkzonetree(struct val_neg_zone* zone)
+{
+       struct val_neg_data* d;
+
+       /* check all data in tree */
+       RBTREE_FOR(d, struct val_neg_data*, &zone->tree) {
+               check_data(zone, d);
+       }
+}
+
+/** check if negative cache is still valid */
+static void check_zone_invariants(struct val_neg_cache* neg, 
+       struct val_neg_zone* zone)
+{
+       unit_assert(zone->nsec3_hash == 0);
+       unit_assert(zone->tree.cmp == &val_neg_data_compare);
+       unit_assert(zone->count != 0);
+
+       if(zone->tree.count == 0)
+               unit_assert(!zone->in_use);
+       else {
+               if(!zone->in_use) {
+                       /* details on error */
+                       log_nametypeclass(0, "zone", zone->name, 0, 0);
+                       log_err("inuse %d count=%d tree.count=%d",
+                               zone->in_use, zone->count, 
+                               (int)zone->tree.count);
+                       if(negverbose)
+                               print_neg_cache(neg);
+               }
+               unit_assert(zone->in_use);
+       }
+       
+       if(zone->parent) {
+               unit_assert(zone->parent->count >= zone->count);
+               if(zone->parent->in_use) {
+                       unit_assert(zone->parent->count > zone->count);
+               }
+               unit_assert(zone->parent->labs == zone->labs-1);
+               /* and parent must be one label shorter */
+               unit_assert(zone->name[0] == (zone->len-zone->parent->len-1));
+               unit_assert(query_dname_compare(zone->name + zone->name[0]+1,
+                       zone->parent->name) == 0);
+       } else {
+               /* must be apex */
+               unit_assert(dname_is_root(zone->name));
+       }
+       /* tree property: */
+       unit_assert(zone->count == sum_zone_subtree_inuse(neg, zone));
+
+       /* check structure of zone data tree */
+       checkzonetree(zone);
+}
+
+/** check if negative cache is still valid */
+static void check_neg_invariants(struct val_neg_cache* neg)
+{
+       struct val_neg_zone* z;
+       /* check structure of LRU list */
+       lock_basic_lock(&neg->lock);
+       check_lru(neg);
+       unit_assert(neg->max == 1024*1024);
+       unit_assert(neg->nsec3_max_iter == 1500);
+       unit_assert(neg->tree.cmp == &val_neg_zone_compare);
+
+       if(neg->tree.count == 0) {
+               /* empty */
+               unit_assert(neg->tree.count == 0);
+               unit_assert(neg->first == NULL);
+               unit_assert(neg->last == NULL);
+               unit_assert(neg->use == 0);
+               lock_basic_unlock(&neg->lock);
+               return;
+       }
+
+       unit_assert(neg->first != NULL);
+       unit_assert(neg->last != NULL);
+
+       RBTREE_FOR(z, struct val_neg_zone*, &neg->tree) {
+               check_zone_invariants(neg, z);
+       }
+       lock_basic_unlock(&neg->lock);
+}
+
+/** perform stress test on insert and delete in neg cache */
+static void stress_test(struct val_neg_cache* neg)
+{
+       int i;
+       if(negverbose)
+               printf("negcache test\n");
+       for(i=0; i<100; i++) {
+               if(random() % 10 < 8)
+                       add_item(neg);
+               else    remove_item(neg);
+               check_neg_invariants(neg);
+       }
+       /* empty it */
+       if(negverbose)
+               printf("neg stress empty\n");
+       while(neg->first) {
+               remove_item(neg);
+               check_neg_invariants(neg);
+       }
+       if(negverbose)
+               printf("neg stress emptied\n");
+       unit_assert(neg->first == NULL);
+       /* insert again */
+       for(i=0; i<100; i++) {
+               if(random() % 10 < 8)
+                       add_item(neg);
+               else    remove_item(neg);
+               check_neg_invariants(neg);
+       }
+}
+
+void neg_test(void)
+{
+       struct val_neg_cache* neg;
+       srandom(48);
+       unit_show_feature("negative cache");
+
+       /* create with defaults */
+       neg = val_neg_create(NULL, 1500);
+       unit_assert(neg);
+       
+       stress_test(neg);
+
+       neg_cache_delete(neg);
+}
diff --git a/usr.sbin/unbound/testcode/unitregional.c b/usr.sbin/unbound/testcode/unitregional.c
new file mode 100644 (file)
index 0000000..49c8147
--- /dev/null
@@ -0,0 +1,244 @@
+/*
+ * testcode/unitregional.c - unit test for regional allocator.
+ *
+ * Copyright (c) 2010, NLnet Labs. All rights reserved.
+ *
+ * This software is open source.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * Neither the name of the NLNET LABS nor the names of its contributors may
+ * be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+ * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+/**
+ * \file
+ * Tests the regional special purpose allocator.
+ */
+
+#include "config.h"
+#include "testcode/unitmain.h"
+#include "util/log.h"
+#include "util/regional.h"
+
+/** test regional corner cases, zero, one, end of structure */
+static void
+corner_cases(struct regional* r)
+{
+       size_t s; /* shadow count of allocated memory */
+       void* a;
+       size_t minsize = sizeof(uint64_t);
+       size_t mysize;
+       char* str;
+       unit_assert(r);
+       /* alloc cases:
+        * 0, 1, 2.
+        * smaller than LARGE_OBJECT_SIZE.
+        * smaller but does not fit in remainder in regional.
+        * smaller but exactly fits in remainder of regional.
+        * size is remainder of regional - 8.
+        * size is remainder of regional + 8.
+        * larger than LARGE_OBJECT_SIZE.
+        */
+       s = sizeof(struct regional);
+       unit_assert((s % minsize) == 0);
+       unit_assert(r->available == r->first_size - s);
+       unit_assert(r->large_list == NULL);
+       unit_assert(r->next == NULL);
+
+       /* Note an alloc of 0 gets a pointer to current last
+        * position (where you should then use 0 bytes) */
+       a = regional_alloc(r, 0);
+       unit_assert(a);
+       s+=0;
+       unit_assert(r->available == r->first_size - s);
+
+       a = regional_alloc(r, 1);
+       unit_assert(a);
+       memset(a, 0x42, 1);
+       s+=minsize;
+       unit_assert(r->available == r->first_size - s);
+
+       a = regional_alloc(r, 2);
+       unit_assert(a);
+       memset(a, 0x42, 2);
+       s+=minsize;
+       unit_assert(r->available == r->first_size - s);
+
+       a = regional_alloc(r, 128);
+       unit_assert(a);
+       memset(a, 0x42, 128);
+       s+=128;
+       unit_assert(r->available == r->first_size - s);
+
+       unit_assert(r->large_list == NULL);
+       a = regional_alloc(r, 10240);
+       unit_assert(a);
+       unit_assert(r->large_list != NULL);
+       memset(a, 0x42, 10240);
+       /* s does not change */
+       unit_assert(r->available == r->first_size - s);
+       unit_assert(r->total_large == 10240+minsize);
+
+       /* go towards the end of the current chunk */
+       while(r->available > 1024) {
+               a = regional_alloc(r, 1024);
+               unit_assert(a);
+               memset(a, 0x42, 1024);
+               s += 1024;
+               unit_assert(r->available == r->first_size - s);
+       }
+
+       unit_assert(r->next == NULL);
+       mysize = 1280; /* does not fit in current chunk */
+       a = regional_alloc(r, mysize);
+       memset(a, 0x42, mysize);
+       unit_assert(r->next != NULL);
+       unit_assert(a);
+
+       /* go towards the end of the current chunk */
+       while(r->available > 864) {
+               a = regional_alloc(r, 864);
+               unit_assert(a);
+               memset(a, 0x42, 864);
+               s += 864;
+       }
+
+       mysize = r->available; /* exactly fits */
+       a = regional_alloc(r, mysize);
+       memset(a, 0x42, mysize);
+       unit_assert(a);
+       unit_assert(r->available == 0); /* implementation does not go ahead*/
+
+       a = regional_alloc(r, 8192); /* another large allocation */
+       unit_assert(a);
+       memset(a, 0x42, 8192);
+       unit_assert(r->available == 0);
+       unit_assert(r->total_large == 10240 + 8192 + 2*minsize);
+
+       a = regional_alloc(r, 32); /* make new chunk */
+       unit_assert(a);
+       memset(a, 0x42, 32);
+       unit_assert(r->available > 0);
+       unit_assert(r->total_large == 10240 + 8192 + 2*minsize);
+
+       /* go towards the end of the current chunk */
+       while(r->available > 1320) {
+               a = regional_alloc(r, 1320);
+               unit_assert(a);
+               memset(a, 0x42, 1320);
+               s += 1320;
+       }
+
+       mysize = r->available + 8; /* exact + 8 ; does not fit */
+       a = regional_alloc(r, mysize);
+       memset(a, 0x42, mysize);
+       unit_assert(a);
+       unit_assert(r->available > 0); /* new chunk */
+
+       /* go towards the end of the current chunk */
+       while(r->available > 1480) {
+               a = regional_alloc(r, 1480);
+               unit_assert(a);
+               memset(a, 0x42, 1480);
+               s += 1480;
+       }
+
+       mysize = r->available - 8; /* exact - 8 ; fits. */
+       a = regional_alloc(r, mysize);
+       memset(a, 0x42, mysize);
+       unit_assert(a);
+       unit_assert(r->available == 8);
+
+       /* test if really copied over */
+       str = "test12345";
+       a = regional_alloc_init(r, str, 8);
+       unit_assert(a);
+       unit_assert(memcmp(a, str, 8) == 0);
+
+       /* test if really zeroed */
+       a = regional_alloc_zero(r, 32);
+       str="\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";
+       unit_assert(a);
+       unit_assert(memcmp(a, str, 32) == 0);
+
+       /* test if copied over (and null byte) */
+       str = "an interesting string";
+       a = regional_strdup(r, str);
+       unit_assert(a);
+       unit_assert(memcmp(a, str, strlen(str)+1) == 0);
+
+       regional_free_all(r);
+}
+
+/** test specific cases */
+static void
+specific_cases(void)
+{
+       struct regional* r = regional_create();
+       corner_cases(r);
+       regional_destroy(r);
+       r = regional_create_custom(2048); /* a small regional */
+       unit_assert(r->first_size == 2048);
+       unit_assert(regional_get_mem(r) == 2048);
+       corner_cases(r);
+       unit_assert(regional_get_mem(r) == 2048);
+       regional_destroy(r);
+}
+
+/** put random stuff in a region and free it */
+static void
+burden_test(size_t max)
+{
+       size_t get;
+       void* a;
+       int i;
+       struct regional* r = regional_create_custom(2048);
+       for(i=0; i<1000; i++) {
+               get = random() % max;
+               a = regional_alloc(r, get);
+               unit_assert(a);
+               memset(a, 0x54, get);
+       }
+       regional_free_all(r);
+       regional_destroy(r);
+}
+
+/** randomly allocate stuff */
+static void
+random_burden(void)
+{
+       size_t max_alloc = 2048 + 128; /* small chance of LARGE */
+       int i;
+       for(i=0; i<100; i++)
+               burden_test(max_alloc);
+}
+
+void regional_test(void)
+{
+       unit_show_feature("regional");
+       specific_cases();
+       random_burden();
+}
diff --git a/usr.sbin/unbound/testcode/unitslabhash.c b/usr.sbin/unbound/testcode/unitslabhash.c
new file mode 100644 (file)
index 0000000..565d361
--- /dev/null
@@ -0,0 +1,376 @@
+/*
+ * testcode/unitslabhash.c - unit test for slabhash table.
+ *
+ * Copyright (c) 2007, NLnet Labs. All rights reserved.
+ *
+ * This software is open source.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * Neither the name of the NLNET LABS nor the names of its contributors may
+ * be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+ * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+/**
+ * \file
+ * Tests the locking LRU keeping hash table implementation.
+ */
+
+#include "config.h"
+#include "testcode/unitmain.h"
+#include "util/log.h"
+#include "util/storage/slabhash.h"
+
+/** use this type for the slabhash test key */
+typedef struct slabhash_testkey testkey_type;
+/** use this type for the slabhash test data */
+typedef struct slabhash_testdata testdata_type;
+
+/** delete key */
+static void delkey(struct slabhash_testkey* k) {
+       lock_rw_destroy(&k->entry.lock); free(k);}
+
+/** hash func, very bad to improve collisions, both high and low bits */
+static hashvalue_type myhash(int id) {
+       hashvalue_type h = (hashvalue_type)id & 0x0f;
+       h |= (h << 28);
+       return h;
+}
+
+/** allocate new key, fill in hash */
+static testkey_type* newkey(int id) {
+       testkey_type* k = (testkey_type*)calloc(1, sizeof(testkey_type));
+       if(!k) fatal_exit("out of memory");
+       k->id = id;
+       k->entry.hash = myhash(id);
+       k->entry.key = k;
+       lock_rw_init(&k->entry.lock);
+       return k;
+}
+/** new data el */
+static testdata_type* newdata(int val) {
+       testdata_type* d = (testdata_type*)calloc(1, 
+               sizeof(testdata_type));
+       if(!d) fatal_exit("out of memory");
+       d->data = val;
+       return d;
+}
+
+/** test hashtable using short sequence */
+static void
+test_short_table(struct slabhash* table) 
+{
+       testkey_type* k = newkey(12);
+       testkey_type* k2 = newkey(14);
+       testdata_type* d = newdata(128);
+       testdata_type* d2 = newdata(129);
+       
+       k->entry.data = d;
+       k2->entry.data = d2;
+
+       slabhash_insert(table, myhash(12), &k->entry, d, NULL);
+       slabhash_insert(table, myhash(14), &k2->entry, d2, NULL);
+       
+       unit_assert( slabhash_lookup(table, myhash(12), k, 0) == &k->entry);
+       lock_rw_unlock( &k->entry.lock );
+       unit_assert( slabhash_lookup(table, myhash(14), k2, 0) == &k2->entry);
+       lock_rw_unlock( &k2->entry.lock );
+       slabhash_remove(table, myhash(12), k);
+       slabhash_remove(table, myhash(14), k2);
+}
+
+/** number of hash test max */
+#define HASHTESTMAX 32
+
+/** test adding a random element */
+static void
+testadd(struct slabhash* table, testdata_type* ref[])
+{
+       int numtoadd = random() % HASHTESTMAX;
+       testdata_type* data = newdata(numtoadd);
+       testkey_type* key = newkey(numtoadd);
+       key->entry.data = data;
+       slabhash_insert(table, myhash(numtoadd), &key->entry, data, NULL);
+       ref[numtoadd] = data;
+}
+
+/** test adding a random element */
+static void
+testremove(struct slabhash* table, testdata_type* ref[])
+{
+       int num = random() % HASHTESTMAX;
+       testkey_type* key = newkey(num);
+       slabhash_remove(table, myhash(num), key);
+       ref[num] = NULL;
+       delkey(key);
+}
+
+/** test adding a random element */
+static void
+testlookup(struct slabhash* table, testdata_type* ref[])
+{
+       int num = random() % HASHTESTMAX;
+       testkey_type* key = newkey(num);
+       struct lruhash_entry* en = slabhash_lookup(table, myhash(num), key, 0);
+       testdata_type* data = en? (testdata_type*)en->data : NULL;
+       if(en) {
+               unit_assert(en->key);
+               unit_assert(en->data);
+       }
+       if(0) log_info("lookup %d got %d, expect %d", num, en? data->data :-1,
+               ref[num]? ref[num]->data : -1);
+       unit_assert( data == ref[num] );
+       if(en) { lock_rw_unlock(&en->lock); }
+       delkey(key);
+}
+
+/** check integrity of hash table */
+static void
+check_lru_table(struct lruhash* table)
+{
+       struct lruhash_entry* p;
+       size_t c = 0;
+       lock_quick_lock(&table->lock);
+       unit_assert( table->num <= table->size);
+       unit_assert( table->size_mask == (int)table->size-1 );
+       unit_assert( (table->lru_start && table->lru_end) ||
+               (!table->lru_start && !table->lru_end) );
+       unit_assert( table->space_used <= table->space_max );
+       /* check lru list integrity */
+       if(table->lru_start)
+               unit_assert(table->lru_start->lru_prev == NULL);
+       if(table->lru_end)
+               unit_assert(table->lru_end->lru_next == NULL);
+       p = table->lru_start;
+       while(p) {
+               if(p->lru_prev) {
+                       unit_assert(p->lru_prev->lru_next == p);
+               }
+               if(p->lru_next) {
+                       unit_assert(p->lru_next->lru_prev == p);
+               }
+               c++;
+               p = p->lru_next;
+       }
+       unit_assert(c == table->num);
+
+       /* this assertion is specific to the unit test */
+       unit_assert( table->space_used == 
+               table->num * test_slabhash_sizefunc(NULL, NULL) );
+       lock_quick_unlock(&table->lock);
+}
+
+/** check integrity of hash table */
+static void
+check_table(struct slabhash* table)
+{
+       size_t i;
+       for(i=0; i<table->size; i++)
+               check_lru_table(table->array[i]);
+}
+
+/** test adding a random element (unlimited range) */
+static void
+testadd_unlim(struct slabhash* table, testdata_type** ref)
+{
+       int numtoadd = random() % (HASHTESTMAX * 10);
+       testdata_type* data = newdata(numtoadd);
+       testkey_type* key = newkey(numtoadd);
+       key->entry.data = data;
+       slabhash_insert(table, myhash(numtoadd), &key->entry, data, NULL);
+       if(ref)
+               ref[numtoadd] = data;
+}
+
+/** test adding a random element (unlimited range) */
+static void
+testremove_unlim(struct slabhash* table, testdata_type** ref)
+{
+       int num = random() % (HASHTESTMAX*10);
+       testkey_type* key = newkey(num);
+       slabhash_remove(table, myhash(num), key);
+       if(ref)
+               ref[num] = NULL;
+       delkey(key);
+}
+
+/** test adding a random element (unlimited range) */
+static void
+testlookup_unlim(struct slabhash* table, testdata_type** ref)
+{
+       int num = random() % (HASHTESTMAX*10);
+       testkey_type* key = newkey(num);
+       struct lruhash_entry* en = slabhash_lookup(table, myhash(num), key, 0);
+       testdata_type* data = en? (testdata_type*)en->data : NULL;
+       if(en) {
+               unit_assert(en->key);
+               unit_assert(en->data);
+       }
+       if(0 && ref) log_info("lookup unlim %d got %d, expect %d", num, en ? 
+               data->data :-1, ref[num] ? ref[num]->data : -1);
+       if(data && ref) {
+               /* its okay for !data, it fell off the lru */
+               unit_assert( data == ref[num] );
+       }
+       if(en) { lock_rw_unlock(&en->lock); }
+       delkey(key);
+}
+
+/** test with long sequence of adds, removes and updates, and lookups */
+static void
+test_long_table(struct slabhash* table) 
+{
+       /* assuming it all fits in the hashtable, this check will work */
+       testdata_type* ref[HASHTESTMAX * 100];
+       size_t i;
+       memset(ref, 0, sizeof(ref));
+       /* test assumption */
+       if(0) slabhash_status(table, "unit test", 1);
+       srandom(48);
+       for(i=0; i<1000; i++) {
+               /* what to do? */
+               if(i == 500) {
+                       slabhash_clear(table);
+                       memset(ref, 0, sizeof(ref));
+                       continue;
+               }
+               switch(random() % 4) {
+                       case 0:
+                       case 3:
+                               testadd(table, ref);
+                               break;
+                       case 1:
+                               testremove(table, ref);
+                               break;
+                       case 2:
+                               testlookup(table, ref);
+                               break;
+                       default:
+                               unit_assert(0);
+               }
+               if(0) slabhash_status(table, "unit test", 1);
+               check_table(table);
+       }
+
+       /* test more, but 'ref' assumption does not hold anymore */
+       for(i=0; i<1000; i++) {
+               /* what to do? */
+               switch(random() % 4) {
+                       case 0:
+                       case 3:
+                               testadd_unlim(table, ref);
+                               break;
+                       case 1:
+                               testremove_unlim(table, ref);
+                               break;
+                       case 2:
+                               testlookup_unlim(table, ref);
+                               break;
+                       default:
+                               unit_assert(0);
+               }
+               if(0) slabhash_status(table, "unlim", 1);
+               check_table(table);
+       }
+}
+
+/** structure to threaded test the lru hash table */
+struct slab_test_thr {
+       /** thread num, first entry. */
+       int num;
+       /** id */
+       ub_thread_type id;
+       /** hash table */
+       struct slabhash* table;
+};
+
+/** main routine for threaded hash table test */
+static void*
+test_thr_main(void* arg) 
+{
+       struct slab_test_thr* t = (struct slab_test_thr*)arg;
+       int i;
+       log_thread_set(&t->num);
+       for(i=0; i<1000; i++) {
+               switch(random() % 4) {
+                       case 0:
+                       case 3:
+                               testadd_unlim(t->table, NULL);
+                               break;
+                       case 1:
+                               testremove_unlim(t->table, NULL);
+                               break;
+                       case 2:
+                               testlookup_unlim(t->table, NULL);
+                               break;
+                       default:
+                               unit_assert(0);
+               }
+               if(0) slabhash_status(t->table, "hashtest", 1);
+               if(i % 100 == 0) /* because of locking, not all the time */
+                       check_table(t->table);
+       }
+       check_table(t->table);
+       return NULL;
+}
+
+/** test hash table access by multiple threads */
+static void
+test_threaded_table(struct slabhash* table)
+{
+       int numth = 10;
+       struct slab_test_thr t[100];
+       int i;
+
+       for(i=1; i<numth; i++) {
+               t[i].num = i;
+               t[i].table = table;
+               ub_thread_create(&t[i].id, test_thr_main, &t[i]);
+       }
+
+       for(i=1; i<numth; i++) {
+               ub_thread_join(t[i].id);
+       }
+       if(0) slabhash_status(table, "hashtest", 1);
+}
+
+void slabhash_test(void)
+{
+       /* start very very small array, so it can do lots of table_grow() */
+       /* also small in size so that reclaim has to be done quickly. */
+       struct slabhash* table;
+       unit_show_feature("slabhash");
+       table = slabhash_create(4, 2, 10400, 
+               test_slabhash_sizefunc, test_slabhash_compfunc, 
+               test_slabhash_delkey, test_slabhash_deldata, NULL);
+       test_short_table(table);
+       test_long_table(table);
+       slabhash_delete(table);
+       table = slabhash_create(4, 2, 10400, 
+               test_slabhash_sizefunc, test_slabhash_compfunc, 
+               test_slabhash_delkey, test_slabhash_deldata, NULL);
+       test_threaded_table(table);
+       slabhash_delete(table);
+}
diff --git a/usr.sbin/unbound/testcode/unitverify.c b/usr.sbin/unbound/testcode/unitverify.c
new file mode 100644 (file)
index 0000000..95676e1
--- /dev/null
@@ -0,0 +1,561 @@
+/*
+ * testcode/unitverify.c - unit test for signature verification routines.
+ *
+ * Copyright (c) 2007, NLnet Labs. All rights reserved.
+ *
+ * This software is open source.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * Neither the name of the NLNET LABS nor the names of its contributors may
+ * be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+ * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+/**
+ * \file
+ * Calls verification unit tests. Exits with code 1 on a failure. 
+ */
+
+#include "config.h"
+#include "util/log.h"
+#include "testcode/unitmain.h"
+#include "validator/val_sigcrypt.h"
+#include "validator/val_secalgo.h"
+#include "validator/val_nsec.h"
+#include "validator/val_nsec3.h"
+#include "validator/validator.h"
+#include "testcode/testpkts.h"
+#include "util/data/msgreply.h"
+#include "util/data/msgparse.h"
+#include "util/data/dname.h"
+#include "util/regional.h"
+#include "util/alloc.h"
+#include "util/rbtree.h"
+#include "util/net_help.h"
+#include "util/module.h"
+#include "util/config_file.h"
+#include "sldns/sbuffer.h"
+#include "sldns/keyraw.h"
+#include "sldns/str2wire.h"
+#include "sldns/wire2str.h"
+
+/** verbose signature test */
+static int vsig = 0;
+
+/** entry to packet buffer with wireformat */
+static void
+entry_to_buf(struct entry* e, sldns_buffer* pkt)
+{
+       unit_assert(e->reply_list);
+       if(e->reply_list->reply_from_hex) {
+               sldns_buffer_copy(pkt, e->reply_list->reply_from_hex);
+       } else {
+               sldns_buffer_clear(pkt);
+               sldns_buffer_write(pkt, e->reply_list->reply_pkt,
+                       e->reply_list->reply_len);
+               sldns_buffer_flip(pkt);
+       }
+}
+
+/** entry to reply info conversion */
+static void
+entry_to_repinfo(struct entry* e, struct alloc_cache* alloc, 
+       struct regional* region, sldns_buffer* pkt, struct query_info* qi, 
+       struct reply_info** rep)
+{
+       int ret;
+       struct edns_data edns;
+       entry_to_buf(e, pkt);
+       /* lock alloc lock to please lock checking software. 
+        * alloc_special_obtain assumes it is talking to a ub-alloc,
+        * and does not need to perform locking. Here the alloc is
+        * the only one, so we lock it here */
+       lock_quick_lock(&alloc->lock);
+       ret = reply_info_parse(pkt, alloc, qi, rep, region, &edns);
+       lock_quick_unlock(&alloc->lock);
+       if(ret != 0) {
+               char rcode[16];
+               sldns_wire2str_rcode_buf(ret, rcode, sizeof(rcode));
+               printf("parse code %d: %s\n", ret, rcode);
+               unit_assert(ret != 0);
+       }
+}
+
+/** extract DNSKEY rrset from answer and convert it */
+static struct ub_packed_rrset_key* 
+extract_keys(struct entry* e, struct alloc_cache* alloc, 
+       struct regional* region, sldns_buffer* pkt)
+{
+       struct ub_packed_rrset_key* dnskey = NULL;
+       struct query_info qinfo;
+       struct reply_info* rep = NULL;
+       size_t i;
+
+       entry_to_repinfo(e, alloc, region, pkt, &qinfo, &rep);
+       for(i=0; i<rep->an_numrrsets; i++) {
+               if(ntohs(rep->rrsets[i]->rk.type) == LDNS_RR_TYPE_DNSKEY) {
+                       dnskey = rep->rrsets[i];
+                       rep->rrsets[i] = NULL;
+                       break;
+               }
+       }
+       unit_assert(dnskey);
+
+       reply_info_parsedelete(rep, alloc);
+       query_info_clear(&qinfo);
+       return dnskey;
+}
+
+/** return true if answer should be bogus */
+static int
+should_be_bogus(struct ub_packed_rrset_key* rrset, struct query_info* qinfo)
+{
+       struct packed_rrset_data* d = (struct packed_rrset_data*)rrset->
+               entry.data;
+       if(d->rrsig_count == 0)
+               return 1;
+       /* name 'bogus' as first label signals bogus */
+       if(rrset->rk.dname_len > 6 && memcmp(rrset->rk.dname+1, "bogus", 5)==0)
+               return 1;
+       if(qinfo->qname_len > 6 && memcmp(qinfo->qname+1, "bogus", 5)==0)
+               return 1;
+       return 0;
+}
+
+/** return number of rrs in an rrset */
+static size_t
+rrset_get_count(struct ub_packed_rrset_key* rrset)
+{
+       struct packed_rrset_data* d = (struct packed_rrset_data*)
+       rrset->entry.data;
+       if(!d) return 0;
+       return d->count;
+}
+
+/** setup sig alg list from dnskey */
+static void
+setup_sigalg(struct ub_packed_rrset_key* dnskey, uint8_t* sigalg)
+{
+       uint8_t a[ALGO_NEEDS_MAX];
+       size_t i, n = 0;
+       memset(a, 0, sizeof(a));
+       for(i=0; i<rrset_get_count(dnskey); i++) {
+               uint8_t algo = (uint8_t)dnskey_get_algo(dnskey, i);
+               if(a[algo] == 0) {
+                       a[algo] = 1;
+                       sigalg[n++] = algo;
+               }
+       }
+       sigalg[n] = 0;
+}
+
+/** verify and test one rrset against the key rrset */
+static void
+verifytest_rrset(struct module_env* env, struct val_env* ve, 
+       struct ub_packed_rrset_key* rrset, struct ub_packed_rrset_key* dnskey,
+       struct query_info* qinfo)
+{
+       enum sec_status sec;
+       char* reason = NULL;
+       uint8_t sigalg[ALGO_NEEDS_MAX+1];
+       if(vsig) {
+               log_nametypeclass(VERB_QUERY, "verify of rrset",
+                       rrset->rk.dname, ntohs(rrset->rk.type),
+                       ntohs(rrset->rk.rrset_class));
+       }
+       setup_sigalg(dnskey, sigalg); /* check all algorithms in the dnskey */
+       /* ok to give null as qstate here, won't be used for answer section. */
+       sec = dnskeyset_verify_rrset(env, ve, rrset, dnskey, sigalg, &reason,
+               LDNS_SECTION_ANSWER, NULL);
+       if(vsig) {
+               printf("verify outcome is: %s %s\n", sec_status_to_string(sec),
+                       reason?reason:"");
+       }
+       if(should_be_bogus(rrset, qinfo)) {
+               unit_assert(sec == sec_status_bogus);
+       } else {
+               unit_assert(sec == sec_status_secure);
+       }
+}
+
+/** verify and test an entry - every rr in the message */
+static void
+verifytest_entry(struct entry* e, struct alloc_cache* alloc, 
+       struct regional* region, sldns_buffer* pkt, 
+       struct ub_packed_rrset_key* dnskey, struct module_env* env, 
+       struct val_env* ve)
+{
+       struct query_info qinfo;
+       struct reply_info* rep = NULL;
+       size_t i;
+
+       regional_free_all(region);
+       if(vsig) {
+               char* s = sldns_wire2str_pkt(e->reply_list->reply_pkt,
+                       e->reply_list->reply_len);
+               printf("verifying pkt:\n%s\n", s?s:"outofmemory");
+               free(s);
+       }
+       entry_to_repinfo(e, alloc, region, pkt, &qinfo, &rep);
+
+       for(i=0; i<rep->rrset_count; i++) {
+               verifytest_rrset(env, ve, rep->rrsets[i], dnskey, &qinfo);
+       }
+
+       reply_info_parsedelete(rep, alloc);
+       query_info_clear(&qinfo);
+}
+
+/** find RRset in reply by type */
+static struct ub_packed_rrset_key*
+find_rrset_type(struct reply_info* rep, uint16_t type)
+{
+       size_t i;
+       for(i=0; i<rep->rrset_count; i++) {
+               if(ntohs(rep->rrsets[i]->rk.type) == type)
+                       return rep->rrsets[i];
+       }
+       return NULL;
+}
+
+/** DS sig test an entry - get DNSKEY and DS in entry and verify */
+static void
+dstest_entry(struct entry* e, struct alloc_cache* alloc, 
+       struct regional* region, sldns_buffer* pkt, struct module_env* env)
+{
+       struct query_info qinfo;
+       struct reply_info* rep = NULL;
+       struct ub_packed_rrset_key* ds, *dnskey;
+       int ret;
+
+       regional_free_all(region);
+       if(vsig) {
+               char* s = sldns_wire2str_pkt(e->reply_list->reply_pkt,
+                       e->reply_list->reply_len);
+               printf("verifying DS-DNSKEY match:\n%s\n", s?s:"outofmemory");
+               free(s);
+       }
+       entry_to_repinfo(e, alloc, region, pkt, &qinfo, &rep);
+       ds = find_rrset_type(rep, LDNS_RR_TYPE_DS);
+       dnskey = find_rrset_type(rep, LDNS_RR_TYPE_DNSKEY);
+       /* check test is OK */
+       unit_assert(ds && dnskey);
+
+       ret = ds_digest_match_dnskey(env, dnskey, 0, ds, 0);
+       if(strncmp((char*)qinfo.qname, "\003yes", 4) == 0) {
+               if(vsig) {
+                       printf("result(yes)= %s\n", ret?"yes":"no");
+               }
+               unit_assert(ret);
+       } else if (strncmp((char*)qinfo.qname, "\002no", 3) == 0) {
+               if(vsig) {
+                       printf("result(no)= %s\n", ret?"yes":"no");
+               }
+               unit_assert(!ret);
+               verbose(VERB_QUERY, "DS fail: OK; matched unit test");
+       } else {
+               fatal_exit("Bad qname in DS unit test, yes or no");
+       }
+
+       reply_info_parsedelete(rep, alloc);
+       query_info_clear(&qinfo);
+}
+
+/** verify from a file */
+static void
+verifytest_file(const char* fname, const char* at_date)
+{
+       /* 
+        * The file contains a list of ldns-testpkts entries.
+        * The first entry must be a query for DNSKEY.
+        * The answer rrset is the keyset that will be used for verification
+        */
+       struct ub_packed_rrset_key* dnskey;
+       struct regional* region = regional_create();
+       struct alloc_cache alloc;
+       sldns_buffer* buf = sldns_buffer_new(65535);
+       struct entry* e;
+       struct entry* list = read_datafile(fname, 1);
+       struct module_env env;
+       struct val_env ve;
+       time_t now = time(NULL);
+       unit_show_func("signature verify", fname);
+
+       if(!list)
+               fatal_exit("could not read %s: %s", fname, strerror(errno));
+       alloc_init(&alloc, NULL, 1);
+       memset(&env, 0, sizeof(env));
+       memset(&ve, 0, sizeof(ve));
+       env.scratch = region;
+       env.scratch_buffer = buf;
+       env.now = &now;
+       ve.date_override = cfg_convert_timeval(at_date);
+       unit_assert(region && buf);
+       dnskey = extract_keys(list, &alloc, region, buf);
+       if(vsig) log_nametypeclass(VERB_QUERY, "test dnskey",
+                       dnskey->rk.dname, ntohs(dnskey->rk.type), 
+                       ntohs(dnskey->rk.rrset_class));
+       /* ready to go! */
+       for(e = list->next; e; e = e->next) {
+               verifytest_entry(e, &alloc, region, buf, dnskey, &env, &ve);
+       }
+
+       ub_packed_rrset_parsedelete(dnskey, &alloc);
+       delete_entry(list);
+       regional_destroy(region);
+       alloc_clear(&alloc);
+       sldns_buffer_free(buf);
+}
+
+/** verify DS matches DNSKEY from a file */
+static void
+dstest_file(const char* fname)
+{
+       /* 
+        * The file contains a list of ldns-testpkts entries.
+        * The first entry must be a query for DNSKEY.
+        * The answer rrset is the keyset that will be used for verification
+        */
+       struct regional* region = regional_create();
+       struct alloc_cache alloc;
+       sldns_buffer* buf = sldns_buffer_new(65535);
+       struct entry* e;
+       struct entry* list = read_datafile(fname, 1);
+       struct module_env env;
+       unit_show_func("DS verify", fname);
+
+       if(!list)
+               fatal_exit("could not read %s: %s", fname, strerror(errno));
+       alloc_init(&alloc, NULL, 1);
+       memset(&env, 0, sizeof(env));
+       env.scratch = region;
+       env.scratch_buffer = buf;
+       unit_assert(region && buf);
+
+       /* ready to go! */
+       for(e = list; e; e = e->next) {
+               dstest_entry(e, &alloc, region, buf, &env);
+       }
+
+       delete_entry(list);
+       regional_destroy(region);
+       alloc_clear(&alloc);
+       sldns_buffer_free(buf);
+}
+
+/** helper for unittest of NSEC routines */
+static int
+unitest_nsec_has_type_rdata(char* bitmap, size_t len, uint16_t type)
+{
+       return nsecbitmap_has_type_rdata((uint8_t*)bitmap, len, type);
+}
+
+/** Test NSEC type bitmap routine */
+static void
+nsectest(void)
+{
+       /* bitmap starts at type bitmap rdata field */
+       /* from rfc 4034 example */
+       char* bitmap = "\000\006\100\001\000\000\000\003"
+               "\004\033\000\000\000\000\000\000"
+               "\000\000\000\000\000\000\000\000"
+               "\000\000\000\000\000\000\000\000"
+               "\000\000\000\000\040";
+       size_t len = 37;
+
+       unit_assert(!unitest_nsec_has_type_rdata(bitmap, len, 0));
+       unit_assert(unitest_nsec_has_type_rdata(bitmap, len, LDNS_RR_TYPE_A));
+       unit_assert(!unitest_nsec_has_type_rdata(bitmap, len, 2));
+       unit_assert(!unitest_nsec_has_type_rdata(bitmap, len, 3));
+       unit_assert(!unitest_nsec_has_type_rdata(bitmap, len, 4));
+       unit_assert(!unitest_nsec_has_type_rdata(bitmap, len, 5));
+       unit_assert(!unitest_nsec_has_type_rdata(bitmap, len, 6));
+       unit_assert(!unitest_nsec_has_type_rdata(bitmap, len, 7));
+       unit_assert(!unitest_nsec_has_type_rdata(bitmap, len, 8));
+       unit_assert(!unitest_nsec_has_type_rdata(bitmap, len, 9));
+       unit_assert(!unitest_nsec_has_type_rdata(bitmap, len, 10));
+       unit_assert(!unitest_nsec_has_type_rdata(bitmap, len, 11));
+       unit_assert(!unitest_nsec_has_type_rdata(bitmap, len, 12));
+       unit_assert(!unitest_nsec_has_type_rdata(bitmap, len, 13));
+       unit_assert(!unitest_nsec_has_type_rdata(bitmap, len, 14));
+       unit_assert(unitest_nsec_has_type_rdata(bitmap, len, LDNS_RR_TYPE_MX));
+       unit_assert(unitest_nsec_has_type_rdata(bitmap, len, LDNS_RR_TYPE_RRSIG));
+       unit_assert(unitest_nsec_has_type_rdata(bitmap, len, LDNS_RR_TYPE_NSEC));
+       unit_assert(unitest_nsec_has_type_rdata(bitmap, len, 1234));
+       unit_assert(!unitest_nsec_has_type_rdata(bitmap, len, 1233));
+       unit_assert(!unitest_nsec_has_type_rdata(bitmap, len, 1235));
+       unit_assert(!unitest_nsec_has_type_rdata(bitmap, len, 1236));
+       unit_assert(!unitest_nsec_has_type_rdata(bitmap, len, 1237));
+       unit_assert(!unitest_nsec_has_type_rdata(bitmap, len, 1238));
+       unit_assert(!unitest_nsec_has_type_rdata(bitmap, len, 1239));
+       unit_assert(!unitest_nsec_has_type_rdata(bitmap, len, 1240));
+       unit_assert(!unitest_nsec_has_type_rdata(bitmap, len, 2230));
+}
+
+/** Test hash algo - NSEC3 hash it and compare result */
+static void
+nsec3_hash_test_entry(struct entry* e, rbtree_type* ct,
+       struct alloc_cache* alloc, struct regional* region, 
+       sldns_buffer* buf)
+{
+       struct query_info qinfo;
+       struct reply_info* rep = NULL;
+       struct ub_packed_rrset_key* answer, *nsec3;
+       struct nsec3_cached_hash* hash = NULL;
+       int ret;
+       uint8_t* qname;
+
+       if(vsig) {
+               char* s = sldns_wire2str_pkt(e->reply_list->reply_pkt,
+                       e->reply_list->reply_len);
+               printf("verifying NSEC3 hash:\n%s\n", s?s:"outofmemory");
+               free(s);
+       }
+       entry_to_repinfo(e, alloc, region, buf, &qinfo, &rep);
+       nsec3 = find_rrset_type(rep, LDNS_RR_TYPE_NSEC3);
+       answer = find_rrset_type(rep, LDNS_RR_TYPE_AAAA);
+       qname = regional_alloc_init(region, qinfo.qname, qinfo.qname_len);
+       /* check test is OK */
+       unit_assert(nsec3 && answer && qname);
+
+       ret = nsec3_hash_name(ct, region, buf, nsec3, 0, qname,
+               qinfo.qname_len, &hash);
+       if(ret != 1) {
+               printf("Bad nsec3_hash_name retcode %d\n", ret);
+               unit_assert(ret == 1);
+       }
+       unit_assert(hash->dname && hash->hash && hash->hash_len &&
+               hash->b32 && hash->b32_len);
+       unit_assert(hash->b32_len == (size_t)answer->rk.dname[0]);
+       /* does not do lowercasing. */
+       unit_assert(memcmp(hash->b32, answer->rk.dname+1, hash->b32_len) 
+               == 0);
+
+       reply_info_parsedelete(rep, alloc);
+       query_info_clear(&qinfo);
+}
+
+
+/** Read file to test NSEC3 hash algo */
+static void
+nsec3_hash_test(const char* fname)
+{
+       /* 
+        * The list contains a list of ldns-testpkts entries.
+        * Every entry is a test.
+        *      The qname is hashed.
+        *      The answer section AAAA RR name is the required result.
+        *      The auth section NSEC3 is used to get hash parameters.
+        * The hash cache is maintained per file.
+        *
+        * The test does not perform canonicalization during the compare.
+        */
+       rbtree_type ct;
+       struct regional* region = regional_create();
+       struct alloc_cache alloc;
+       sldns_buffer* buf = sldns_buffer_new(65535);
+       struct entry* e;
+       struct entry* list = read_datafile(fname, 1);
+       unit_show_func("NSEC3 hash", fname);
+
+       if(!list)
+               fatal_exit("could not read %s: %s", fname, strerror(errno));
+       rbtree_init(&ct, &nsec3_hash_cmp);
+       alloc_init(&alloc, NULL, 1);
+       unit_assert(region && buf);
+
+       /* ready to go! */
+       for(e = list; e; e = e->next) {
+               nsec3_hash_test_entry(e, &ct, &alloc, region, buf);
+       }
+
+       delete_entry(list);
+       regional_destroy(region);
+       alloc_clear(&alloc);
+       sldns_buffer_free(buf);
+}
+
+void 
+verify_test(void)
+{
+       unit_show_feature("signature verify");
+#ifdef USE_SHA1
+       verifytest_file("testdata/test_signatures.1", "20070818005004");
+#endif
+#if defined(USE_DSA) && defined(USE_SHA1)
+       verifytest_file("testdata/test_signatures.2", "20080414005004");
+       verifytest_file("testdata/test_signatures.3", "20080416005004");
+       verifytest_file("testdata/test_signatures.4", "20080416005004");
+       verifytest_file("testdata/test_signatures.5", "20080416005004");
+       verifytest_file("testdata/test_signatures.6", "20080416005004");
+       verifytest_file("testdata/test_signatures.7", "20070829144150");
+#endif /* USE_DSA */
+#ifdef USE_SHA1
+       verifytest_file("testdata/test_signatures.8", "20070829144150");
+#endif
+#if (defined(HAVE_EVP_SHA256) || defined(HAVE_NSS) || defined(HAVE_NETTLE)) && defined(USE_SHA2)
+       verifytest_file("testdata/test_sigs.rsasha256", "20070829144150");
+#  ifdef USE_SHA1
+       verifytest_file("testdata/test_sigs.sha1_and_256", "20070829144150");
+#  endif
+       verifytest_file("testdata/test_sigs.rsasha256_draft", "20090101000000");
+#endif
+#if (defined(HAVE_EVP_SHA512) || defined(HAVE_NSS) || defined(HAVE_NETTLE)) && defined(USE_SHA2)
+       verifytest_file("testdata/test_sigs.rsasha512_draft", "20070829144150");
+       verifytest_file("testdata/test_signatures.9", "20171215000000");
+#endif
+#ifdef USE_SHA1
+       verifytest_file("testdata/test_sigs.hinfo", "20090107100022");
+       verifytest_file("testdata/test_sigs.revoked", "20080414005004");
+#endif
+#ifdef USE_GOST
+       if(sldns_key_EVP_load_gost_id())
+         verifytest_file("testdata/test_sigs.gost", "20090807060504");
+       else printf("Warning: skipped GOST, openssl does not provide gost.\n");
+#endif
+#ifdef USE_ECDSA
+       /* test for support in case we use libNSS and ECC is removed */
+       if(dnskey_algo_id_is_supported(LDNS_ECDSAP256SHA256)) {
+               verifytest_file("testdata/test_sigs.ecdsa_p256", "20100908100439");
+               verifytest_file("testdata/test_sigs.ecdsa_p384", "20100908100439");
+       }
+       dstest_file("testdata/test_ds.sha384");
+#endif
+#ifdef USE_ED25519
+       if(dnskey_algo_id_is_supported(LDNS_ED25519)) {
+               verifytest_file("testdata/test_sigs.ed25519", "20170530140439");
+       }
+#endif
+#ifdef USE_ED448
+       if(dnskey_algo_id_is_supported(LDNS_ED448)) {
+               verifytest_file("testdata/test_sigs.ed448", "20180408143630");
+       }
+#endif
+#ifdef USE_SHA1
+       dstest_file("testdata/test_ds.sha1");
+#endif
+       nsectest();
+       nsec3_hash_test("testdata/test_nsec3_hash.1");
+}