--- /dev/null
+/*
+ * 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 */
--- /dev/null
+/*
+ * 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;
#ifndef DAEMON_DUMPCACHE_H
#define DAEMON_DUMPCACHE_H
struct worker;
+#include "daemon/remote.h"
/**
* Dump cache(s) to text
* 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
* 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.
* @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 */
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/**
+ * 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 */
--- /dev/null
+/**
+ * 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 */
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+#!/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
--- /dev/null
+/*
+ * 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 ***********/
--- /dev/null
+/*
+ * 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 */
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+# 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
--- /dev/null
+# 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
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * 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);
+}
+
--- /dev/null
+/*
+ * 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 */
--- /dev/null
+/*
+ * 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);
+}
--- /dev/null
+/*
+ * 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 */
--- /dev/null
+#!/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 ..
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+.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>.
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * 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 */
+
--- /dev/null
+/*
+ * 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;
+ }
+}
--- /dev/null
+/*
+ * 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 */
--- /dev/null
+/*
+ * 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);
+}
--- /dev/null
+/*
+ * 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();
+}
--- /dev/null
+/*
+ * 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);
+}
--- /dev/null
+/*
+ * 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 */
+
--- /dev/null
+/*
+ * 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();
+}
--- /dev/null
+/*
+ * 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);
+}
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * 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 */
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * 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);
+}
--- /dev/null
+/*
+ * 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();
+}
--- /dev/null
+/*
+ * 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);
+}
--- /dev/null
+/*
+ * 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");
+}