From 712b2f300c854afbcde4b11834961322e3c11161 Mon Sep 17 00:00:00 2001 From: sthen Date: Mon, 17 Sep 2018 09:43:42 +0000 Subject: [PATCH] import unbound 1.7.3, testing from benno@ and Brad. --- usr.sbin/unbound/cachedb/redis.c | 283 ++++ usr.sbin/unbound/cachedb/redis.h | 45 + usr.sbin/unbound/daemon/cachedump.h | 7 +- usr.sbin/unbound/testcode/asynclook.c | 558 +++++++ usr.sbin/unbound/testcode/checklocks.c | 859 +++++++++++ usr.sbin/unbound/testcode/checklocks.h | 343 +++++ usr.sbin/unbound/testcode/delayer.c | 1185 ++++++++++++++ usr.sbin/unbound/testcode/do-tests.sh | 63 + usr.sbin/unbound/testcode/fake_event.c | 1792 ++++++++++++++++++++++ usr.sbin/unbound/testcode/fake_event.h | 75 + usr.sbin/unbound/testcode/lock_verify.c | 426 +++++ usr.sbin/unbound/testcode/memstats.c | 249 +++ usr.sbin/unbound/testcode/mini_tdir.sh | 171 +++ usr.sbin/unbound/testcode/mini_tpkg.sh | 128 ++ usr.sbin/unbound/testcode/perf.c | 654 ++++++++ usr.sbin/unbound/testcode/petal.c | 671 ++++++++ usr.sbin/unbound/testcode/pktview.c | 202 +++ usr.sbin/unbound/testcode/readhex.c | 85 + usr.sbin/unbound/testcode/readhex.h | 52 + usr.sbin/unbound/testcode/replay.c | 1044 +++++++++++++ usr.sbin/unbound/testcode/replay.h | 471 ++++++ usr.sbin/unbound/testcode/run_vm.sh | 76 + usr.sbin/unbound/testcode/signit.c | 284 ++++ usr.sbin/unbound/testcode/streamtcp.1 | 66 + usr.sbin/unbound/testcode/streamtcp.c | 435 ++++++ usr.sbin/unbound/testcode/testbound.c | 555 +++++++ usr.sbin/unbound/testcode/testpkts.c | 1705 ++++++++++++++++++++ usr.sbin/unbound/testcode/testpkts.h | 290 ++++ usr.sbin/unbound/testcode/unitanchor.c | 137 ++ usr.sbin/unbound/testcode/unitauth.c | 882 +++++++++++ usr.sbin/unbound/testcode/unitdname.c | 861 +++++++++++ usr.sbin/unbound/testcode/unitecs.c | 284 ++++ usr.sbin/unbound/testcode/unitldns.c | 218 +++ usr.sbin/unbound/testcode/unitlruhash.c | 499 ++++++ usr.sbin/unbound/testcode/unitmain.c | 942 ++++++++++++ usr.sbin/unbound/testcode/unitmain.h | 84 + usr.sbin/unbound/testcode/unitmsgparse.c | 542 +++++++ usr.sbin/unbound/testcode/unitneg.c | 543 +++++++ usr.sbin/unbound/testcode/unitregional.c | 244 +++ usr.sbin/unbound/testcode/unitslabhash.c | 376 +++++ usr.sbin/unbound/testcode/unitverify.c | 561 +++++++ 41 files changed, 18944 insertions(+), 3 deletions(-) create mode 100644 usr.sbin/unbound/cachedb/redis.c create mode 100644 usr.sbin/unbound/cachedb/redis.h create mode 100644 usr.sbin/unbound/testcode/asynclook.c create mode 100644 usr.sbin/unbound/testcode/checklocks.c create mode 100644 usr.sbin/unbound/testcode/checklocks.h create mode 100644 usr.sbin/unbound/testcode/delayer.c create mode 100755 usr.sbin/unbound/testcode/do-tests.sh create mode 100644 usr.sbin/unbound/testcode/fake_event.c create mode 100644 usr.sbin/unbound/testcode/fake_event.h create mode 100644 usr.sbin/unbound/testcode/lock_verify.c create mode 100644 usr.sbin/unbound/testcode/memstats.c create mode 100755 usr.sbin/unbound/testcode/mini_tdir.sh create mode 100755 usr.sbin/unbound/testcode/mini_tpkg.sh create mode 100644 usr.sbin/unbound/testcode/perf.c create mode 100644 usr.sbin/unbound/testcode/petal.c create mode 100644 usr.sbin/unbound/testcode/pktview.c create mode 100644 usr.sbin/unbound/testcode/readhex.c create mode 100644 usr.sbin/unbound/testcode/readhex.h create mode 100644 usr.sbin/unbound/testcode/replay.c create mode 100644 usr.sbin/unbound/testcode/replay.h create mode 100644 usr.sbin/unbound/testcode/run_vm.sh create mode 100644 usr.sbin/unbound/testcode/signit.c create mode 100644 usr.sbin/unbound/testcode/streamtcp.1 create mode 100644 usr.sbin/unbound/testcode/streamtcp.c create mode 100644 usr.sbin/unbound/testcode/testbound.c create mode 100644 usr.sbin/unbound/testcode/testpkts.c create mode 100644 usr.sbin/unbound/testcode/testpkts.h create mode 100644 usr.sbin/unbound/testcode/unitanchor.c create mode 100644 usr.sbin/unbound/testcode/unitauth.c create mode 100644 usr.sbin/unbound/testcode/unitdname.c create mode 100644 usr.sbin/unbound/testcode/unitecs.c create mode 100644 usr.sbin/unbound/testcode/unitldns.c create mode 100644 usr.sbin/unbound/testcode/unitlruhash.c create mode 100644 usr.sbin/unbound/testcode/unitmain.c create mode 100644 usr.sbin/unbound/testcode/unitmain.h create mode 100644 usr.sbin/unbound/testcode/unitmsgparse.c create mode 100644 usr.sbin/unbound/testcode/unitneg.c create mode 100644 usr.sbin/unbound/testcode/unitregional.c create mode 100644 usr.sbin/unbound/testcode/unitslabhash.c create mode 100644 usr.sbin/unbound/testcode/unitverify.c diff --git a/usr.sbin/unbound/cachedb/redis.c b/usr.sbin/unbound/cachedb/redis.c new file mode 100644 index 00000000000..3dfbf8f7a25 --- /dev/null +++ b/usr.sbin/unbound/cachedb/redis.c @@ -0,0 +1,283 @@ +/* + * cachedb/redis.c - cachedb redis module + * + * Copyright (c) 2018, NLnet Labs. All rights reserved. + * + * This software is open source. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * Neither the name of the NLNET LABS nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * \file + * + * This file contains a module that uses the redis database to cache + * dns responses. + */ + +#include "config.h" +#ifdef USE_CACHEDB +#include "cachedb/redis.h" +#include "cachedb/cachedb.h" +#include "util/alloc.h" +#include "util/config_file.h" +#include "sldns/sbuffer.h" + +#ifdef USE_REDIS +#include "hiredis/hiredis.h" + +struct redis_moddata { + redisContext** ctxs; /* thread-specific redis contexts */ + int numctxs; /* number of ctx entries */ + const char* server_host; /* server's IP address or host name */ + int server_port; /* server's TCP port */ + struct timeval timeout; /* timeout for connection setup and commands */ +}; + +static redisContext* +redis_connect(const struct redis_moddata* moddata) +{ + redisContext* ctx; + + ctx = redisConnectWithTimeout(moddata->server_host, + moddata->server_port, moddata->timeout); + if(!ctx || ctx->err) { + const char *errstr = "out of memory"; + if(ctx) + errstr = ctx->errstr; + log_err("failed to connect to redis server: %s", errstr); + goto fail; + } + if(redisSetTimeout(ctx, moddata->timeout) != REDIS_OK) { + log_err("failed to set redis timeout"); + goto fail; + } + return ctx; + + fail: + if(ctx) + redisFree(ctx); + return NULL; +} + +static int +redis_init(struct module_env* env, struct cachedb_env* cachedb_env) +{ + int i; + struct redis_moddata* moddata = NULL; + + verbose(VERB_ALGO, "redis_init"); + + moddata = calloc(1, sizeof(struct redis_moddata)); + if(!moddata) { + log_err("out of memory"); + return 0; + } + moddata->numctxs = env->cfg->num_threads; + moddata->ctxs = calloc(env->cfg->num_threads, sizeof(redisContext*)); + if(!moddata->ctxs) { + log_err("out of memory"); + free(moddata); + return 0; + } + /* note: server_host is a shallow reference to configured string. + * we don't have to free it in this module. */ + moddata->server_host = env->cfg->redis_server_host; + moddata->server_port = env->cfg->redis_server_port; + moddata->timeout.tv_sec = env->cfg->redis_timeout / 1000; + moddata->timeout.tv_usec = (env->cfg->redis_timeout % 1000) * 1000; + for(i = 0; i < moddata->numctxs; i++) + moddata->ctxs[i] = redis_connect(moddata); + cachedb_env->backend_data = moddata; + return 1; +} + +static void +redis_deinit(struct module_env* env, struct cachedb_env* cachedb_env) +{ + struct redis_moddata* moddata = (struct redis_moddata*) + cachedb_env->backend_data; + (void)env; + + verbose(VERB_ALGO, "redis_deinit"); + + if(!moddata) + return; + if(moddata->ctxs) { + int i; + for(i = 0; i < moddata->numctxs; i++) { + if(moddata->ctxs[i]) + redisFree(moddata->ctxs[i]); + } + free(moddata->ctxs); + } + free(moddata); +} + +/* + * Send a redis command and get a reply. Unified so that it can be used for + * both SET and GET. If 'data' is non-NULL the command is supposed to be + * SET and GET otherwise, but the implementation of this function is agnostic + * about the semantics (except for logging): 'command', 'data', and 'data_len' + * are opaquely passed to redisCommand(). + * This function first checks whether a connection with a redis server has + * been established; if not it tries to set up a new one. + * It returns redisReply returned from redisCommand() or NULL if some low + * level error happens. The caller is responsible to check the return value, + * if it's non-NULL, it has to free it with freeReplyObject(). + */ +static redisReply* +redis_command(struct module_env* env, struct cachedb_env* cachedb_env, + const char* command, const uint8_t* data, size_t data_len) +{ + redisContext* ctx; + redisReply* rep; + struct redis_moddata* d = (struct redis_moddata*) + cachedb_env->backend_data; + + /* We assume env->alloc->thread_num is a unique ID for each thread + * in [0, num-of-threads). We could treat it as an error condition + * if the assumption didn't hold, but it seems to be a fundamental + * assumption throughout the unbound architecture, so we simply assert + * it. */ + log_assert(env->alloc->thread_num < d->numctxs); + ctx = d->ctxs[env->alloc->thread_num]; + + /* If we've not established a connection to the server or we've closed + * it on a failure, try to re-establish a new one. Failures will be + * logged in redis_connect(). */ + if(!ctx) { + ctx = redis_connect(d); + d->ctxs[env->alloc->thread_num] = ctx; + } + if(!ctx) + return NULL; + + /* Send the command and get a reply, synchronously. */ + rep = (redisReply*)redisCommand(ctx, command, data, data_len); + if(!rep) { + /* Once an error as a NULL-reply is returned the context cannot + * be reused and we'll need to set up a new connection. */ + log_err("redis_command: failed to receive a reply, " + "closing connection: %s", ctx->errstr); + redisFree(ctx); + d->ctxs[env->alloc->thread_num] = NULL; + return NULL; + } + + /* Check error in reply to unify logging in that case. + * The caller may perform context-dependent checks and logging. */ + if(rep->type == REDIS_REPLY_ERROR) + log_err("redis: %s resulted in an error: %s", + data ? "set" : "get", rep->str); + + return rep; +} + +static int +redis_lookup(struct module_env* env, struct cachedb_env* cachedb_env, + char* key, struct sldns_buffer* result_buffer) +{ + redisReply* rep; + char cmdbuf[4+(CACHEDB_HASHSIZE/8)*2+1]; /* "GET " + key */ + int n; + int ret = 0; + + verbose(VERB_ALGO, "redis_lookup of %s", key); + + n = snprintf(cmdbuf, sizeof(cmdbuf), "GET %s", key); + if(n < 0 || n >= (int)sizeof(cmdbuf)) { + log_err("redis_lookup: unexpected failure to build command"); + return 0; + } + + rep = redis_command(env, cachedb_env, cmdbuf, NULL, 0); + if(!rep) + return 0; + switch (rep->type) { + case REDIS_REPLY_NIL: + verbose(VERB_ALGO, "redis_lookup: no data cached"); + break; + case REDIS_REPLY_STRING: + verbose(VERB_ALGO, "redis_lookup found %d bytes", + (int)rep->len); + if((size_t)rep->len > sldns_buffer_capacity(result_buffer)) { + log_err("redis_lookup: replied data too long: %lu", + (size_t)rep->len); + break; + } + sldns_buffer_clear(result_buffer); + sldns_buffer_write(result_buffer, rep->str, rep->len); + sldns_buffer_flip(result_buffer); + ret = 1; + break; + case REDIS_REPLY_ERROR: + break; /* already logged */ + default: + log_err("redis_lookup: unexpected type of reply for (%d)", + rep->type); + break; + } + freeReplyObject(rep); + return ret; +} + +static void +redis_store(struct module_env* env, struct cachedb_env* cachedb_env, + char* key, uint8_t* data, size_t data_len) +{ + redisReply* rep; + char cmdbuf[4+(CACHEDB_HASHSIZE/8)*2+3+1]; /* "SET " + key + " %b" */ + int n; + + verbose(VERB_ALGO, "redis_store %s (%d bytes)", key, (int)data_len); + + /* build command to set to a binary safe string */ + n = snprintf(cmdbuf, sizeof(cmdbuf), "SET %s %%b", key); + if(n < 0 || n >= (int)sizeof(cmdbuf)) { + log_err("redis_store: unexpected failure to build command"); + return; + } + + rep = redis_command(env, cachedb_env, cmdbuf, data, data_len); + if(rep) { + verbose(VERB_ALGO, "redis_store set completed"); + if(rep->type != REDIS_REPLY_STATUS && + rep->type != REDIS_REPLY_ERROR) { + log_err("redis_store: unexpected type of reply (%d)", + rep->type); + } + freeReplyObject(rep); + } +} + +struct cachedb_backend redis_backend = { "redis", + redis_init, redis_deinit, redis_lookup, redis_store +}; +#endif /* USE_REDIS */ +#endif /* USE_CACHEDB */ diff --git a/usr.sbin/unbound/cachedb/redis.h b/usr.sbin/unbound/cachedb/redis.h new file mode 100644 index 00000000000..2da2a64f2db --- /dev/null +++ b/usr.sbin/unbound/cachedb/redis.h @@ -0,0 +1,45 @@ +/* + * cachedb/redis.h - cachedb redis module + * + * Copyright (c) 2018, NLnet Labs. All rights reserved. + * + * This software is open source. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * Neither the name of the NLNET LABS nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * \file + * + * This file contains a module that uses the redis database to cache + * dns responses. + */ + +/** the redis backend definition, contains callable functions + * and name string */ +extern struct cachedb_backend redis_backend; diff --git a/usr.sbin/unbound/daemon/cachedump.h b/usr.sbin/unbound/daemon/cachedump.h index 0f2feabcb7f..72c172d4414 100644 --- a/usr.sbin/unbound/daemon/cachedump.h +++ b/usr.sbin/unbound/daemon/cachedump.h @@ -72,6 +72,7 @@ #ifndef DAEMON_DUMPCACHE_H #define DAEMON_DUMPCACHE_H struct worker; +#include "daemon/remote.h" /** * Dump cache(s) to text @@ -80,7 +81,7 @@ struct worker; * ptrs to the caches. * @return false on ssl print error. */ -int dump_cache(SSL* ssl, struct worker* worker); +int dump_cache(RES* ssl, struct worker* worker); /** * Load cache(s) from text @@ -89,7 +90,7 @@ int dump_cache(SSL* ssl, struct worker* worker); * ptrs to the caches. * @return false on ssl error. */ -int load_cache(SSL* ssl, struct worker* worker); +int load_cache(RES* ssl, struct worker* worker); /** * Print the delegation used to lookup for this name. @@ -101,7 +102,7 @@ int load_cache(SSL* ssl, struct worker* worker); * @param nmlabs: labels in name. * @return false on ssl error. */ -int print_deleg_lookup(SSL* ssl, struct worker* worker, uint8_t* nm, +int print_deleg_lookup(RES* ssl, struct worker* worker, uint8_t* nm, size_t nmlen, int nmlabs); #endif /* DAEMON_DUMPCACHE_H */ diff --git a/usr.sbin/unbound/testcode/asynclook.c b/usr.sbin/unbound/testcode/asynclook.c new file mode 100644 index 00000000000..06bcf5ab862 --- /dev/null +++ b/usr.sbin/unbound/testcode/asynclook.c @@ -0,0 +1,558 @@ +/* + * testcode/asynclook.c - debug program perform async libunbound queries. + * + * Copyright (c) 2008, NLnet Labs. All rights reserved. + * + * This software is open source. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * Neither the name of the NLNET LABS nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * \file + * + * This program shows the results from several background lookups, + * while printing time in the foreground. + */ + +#include "config.h" +#ifdef HAVE_GETOPT_H +#include +#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 +#endif +#ifdef HAVE_OPENSSL_ERR_H +#include +#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; inumq; i++) { + lock_basic_init(&async_ids[i].lock); + } + } + for(i=0; inumq; 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; inumq; 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 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 +#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: */ + 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: */ + 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: */ + 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; icreate_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 + +/** How many threads to allocate for */ +#define THRDEBUG_MAX_THREADS 32 /* threads */ +/** do we check locking order */ +extern int check_locking_order; + +/** + * Protection memory area. + * It is copied to a holding buffer to compare against later. + * Note that it may encompass the lock structure. + */ +struct protected_area { + /** where the memory region starts */ + void* region; + /** size of the region */ + size_t size; + /** backbuffer that holds a copy, of same size. */ + void* hold; + /** next protected area in list */ + struct protected_area* next; +}; + +/** + * Per thread information for locking debug wrappers. + */ +struct thr_check { + /** thread id */ + pthread_t id; + /** real thread func */ + void* (*func)(void*); + /** func user arg */ + void* arg; + /** number of thread in list structure */ + int num; + /** instance number - how many locks have been created by thread */ + int locks_created; + /** file to write locking order information to */ + FILE* order_info; + /** + * List of locks that this thread is holding, double + * linked list. The first element is the most recent lock acquired. + * So it represents the stack of locks acquired. (of all types). + */ + struct checked_lock *holding_first, *holding_last; + /** if the thread is currently waiting for a lock, which one */ + struct checked_lock* waiting; +}; + +/** + * One structure for all types of locks. + */ +struct checked_lock { + /** mutex for exclusive access to this structure */ + pthread_mutex_t lock; + /** list of memory regions protected by this checked lock */ + struct protected_area* prot; + /** where was this lock created */ + const char* create_func, *create_file; + /** where was this lock created */ + int create_line; + /** unique instance identifier */ + int create_thread, create_instance; + /** contention count */ + size_t contention_count; + /** number of times locked, ever */ + size_t history_count; + /** hold count (how many threads are holding this lock) */ + int hold_count; + /** how many threads are waiting for this lock */ + int wait_count; + /** who touched it last */ + const char* holder_func, *holder_file; + /** who touched it last */ + int holder_line; + /** who owns the lock now */ + struct thr_check* holder; + /** for rwlocks, the writelock holder */ + struct thr_check* writeholder; + + /** next lock a thread is holding (less recent) */ + struct checked_lock* next_held_lock[THRDEBUG_MAX_THREADS]; + /** prev lock a thread is holding (more recent) */ + struct checked_lock* prev_held_lock[THRDEBUG_MAX_THREADS]; + + /** type of lock */ + enum check_lock_type { + /** basic mutex */ + check_lock_mutex, + /** fast spinlock */ + check_lock_spinlock, + /** rwlock */ + check_lock_rwlock + } type; + /** the lock itself, see type to disambiguate the union */ + union { + /** mutex */ + pthread_mutex_t mutex; + /** spinlock */ + pthread_spinlock_t spinlock; + /** rwlock */ + pthread_rwlock_t rwlock; + } u; +}; + +/** + * Additional call for the user to specify what areas are protected + * @param lock: the lock that protects the area. It can be inside the area. + * The lock must be inited. Call with user lock. (any type). + * It demangles the lock itself (struct checked_lock**). + * @param area: ptr to mem. + * @param size: length of area. + * You can call it multiple times with the same lock to give several areas. + * Call it when you are done initializing the area, since it will be copied + * at this time and protected right away against unauthorised changes until + * the next lock() call is done. + */ +void lock_protect(void* lock, void* area, size_t size); + +/** + * Remove protected area from lock. + * No need to call this when deleting the lock. + * @param lock: the lock, any type, (struct checked_lock**). + * @param area: pointer to memory. + */ +void lock_unprotect(void* lock, void* area); + +/** + * Get memory associated with a checked lock + * @param lock: the checked lock, any type. (struct checked_lock**). + * @return: in bytes, including protected areas. + */ +size_t lock_get_mem(void* lock); + +/** + * Initialise checklock. Sets up internal debug structures. + */ +void checklock_start(void); + +/** + * Cleanup internal debug state. + */ +void checklock_stop(void); + +/** + * Init locks. + * @param type: what type of lock this is. + * @param lock: ptr to user alloced ptr structure. This is inited. + * So an alloc is done and the ptr is stored as result. + * @param func: caller function name. + * @param file: caller file name. + * @param line: caller line number. + */ +void checklock_init(enum check_lock_type type, struct checked_lock** lock, + const char* func, const char* file, int line); + +/** + * Destroy locks. Free the structure. + * @param type: what type of lock this is. + * @param lock: ptr to user alloced structure. This is destroyed. + * @param func: caller function name. + * @param file: caller file name. + * @param line: caller line number. + */ +void checklock_destroy(enum check_lock_type type, struct checked_lock** lock, + const char* func, const char* file, int line); + +/** + * Acquire readlock. + * @param type: what type of lock this is. Had better be a rwlock. + * @param lock: ptr to lock. + * @param func: caller function name. + * @param file: caller file name. + * @param line: caller line number. + */ +void checklock_rdlock(enum check_lock_type type, struct checked_lock* lock, + const char* func, const char* file, int line); + +/** + * Acquire writelock. + * @param type: what type of lock this is. Had better be a rwlock. + * @param lock: ptr to lock. + * @param func: caller function name. + * @param file: caller file name. + * @param line: caller line number. + */ +void checklock_wrlock(enum check_lock_type type, struct checked_lock* lock, + const char* func, const char* file, int line); + +/** + * Locks. + * @param type: what type of lock this is. Had better be mutex or spinlock. + * @param lock: the lock. + * @param func: caller function name. + * @param file: caller file name. + * @param line: caller line number. + */ +void checklock_lock(enum check_lock_type type, struct checked_lock* lock, + const char* func, const char* file, int line); + +/** + * Unlocks. + * @param type: what type of lock this is. + * @param lock: the lock. + * @param func: caller function name. + * @param file: caller file name. + * @param line: caller line number. + */ +void checklock_unlock(enum check_lock_type type, struct checked_lock* lock, + const char* func, const char* file, int line); + +/** + * Create thread. + * @param thr: Thread id, where to store result. + * @param func: thread start function. + * @param arg: user argument. + */ +void checklock_thrcreate(pthread_t* thr, void* (*func)(void*), void* arg); + +/** + * Wait for thread to exit. Returns thread return value. + * @param thread: thread to wait for. + */ +void checklock_thrjoin(pthread_t thread); + +/** structures to enable compiler type checking on the locks. + * Also the pointer makes it so that the lock can be part of the protected + * region without any possible problem (since the ptr will stay the same.) + * i.e. there can be contention and readlocks stored in checked_lock, while + * the protected area stays the same, even though it contains (ptr to) lock. + */ +struct checked_lock_rw { struct checked_lock* c_rw; }; +/** structures to enable compiler type checking on the locks. */ +struct checked_lock_mutex { struct checked_lock* c_m; }; +/** structures to enable compiler type checking on the locks. */ +struct checked_lock_spl { struct checked_lock* c_spl; }; + +/** debugging rwlock */ +typedef struct checked_lock_rw lock_rw_type; +#define lock_rw_init(lock) checklock_init(check_lock_rwlock, &((lock)->c_rw), __func__, __FILE__, __LINE__) +#define lock_rw_destroy(lock) checklock_destroy(check_lock_rwlock, &((lock)->c_rw), __func__, __FILE__, __LINE__) +#define lock_rw_rdlock(lock) checklock_rdlock(check_lock_rwlock, (lock)->c_rw, __func__, __FILE__, __LINE__) +#define lock_rw_wrlock(lock) checklock_wrlock(check_lock_rwlock, (lock)->c_rw, __func__, __FILE__, __LINE__) +#define lock_rw_unlock(lock) checklock_unlock(check_lock_rwlock, (lock)->c_rw, __func__, __FILE__, __LINE__) + +/** debugging mutex */ +typedef struct checked_lock_mutex lock_basic_type; +#define lock_basic_init(lock) checklock_init(check_lock_mutex, &((lock)->c_m), __func__, __FILE__, __LINE__) +#define lock_basic_destroy(lock) checklock_destroy(check_lock_mutex, &((lock)->c_m), __func__, __FILE__, __LINE__) +#define lock_basic_lock(lock) checklock_lock(check_lock_mutex, (lock)->c_m, __func__, __FILE__, __LINE__) +#define lock_basic_unlock(lock) checklock_unlock(check_lock_mutex, (lock)->c_m, __func__, __FILE__, __LINE__) + +/** debugging spinlock */ +typedef struct checked_lock_spl lock_quick_type; +#define lock_quick_init(lock) checklock_init(check_lock_spinlock, &((lock)->c_spl), __func__, __FILE__, __LINE__) +#define lock_quick_destroy(lock) checklock_destroy(check_lock_spinlock, &((lock)->c_spl), __func__, __FILE__, __LINE__) +#define lock_quick_lock(lock) checklock_lock(check_lock_spinlock, (lock)->c_spl, __func__, __FILE__, __LINE__) +#define lock_quick_unlock(lock) checklock_unlock(check_lock_spinlock, (lock)->c_spl, __func__, __FILE__, __LINE__) + +/** we use the pthread id, our thr_check structure is kept behind the scenes */ +typedef pthread_t ub_thread_type; +#define ub_thread_create(thr, func, arg) checklock_thrcreate(thr, func, arg) +#define ub_thread_self() pthread_self() +#define ub_thread_join(thread) checklock_thrjoin(thread) + +typedef pthread_key_t ub_thread_key_type; +#define ub_thread_key_create(key, f) LOCKRET(pthread_key_create(key, f)) +#define ub_thread_key_set(key, v) LOCKRET(pthread_setspecific(key, v)) +#define ub_thread_key_get(key) pthread_getspecific(key) + +#endif /* USE_THREAD_DEBUG */ +#endif /* TESTCODE_CHECK_LOCKS_H */ diff --git a/usr.sbin/unbound/testcode/delayer.c b/usr.sbin/unbound/testcode/delayer.c new file mode 100644 index 00000000000..5489b591e33 --- /dev/null +++ b/usr.sbin/unbound/testcode/delayer.c @@ -0,0 +1,1185 @@ +/* + * testcode/delayer.c - debug program that delays queries to a server. + * + * Copyright (c) 2008, NLnet Labs. All rights reserved. + * + * This software is open source. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * Neither the name of the NLNET LABS nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * \file + * + * This program delays queries made. It performs as a proxy to another + * server and delays queries to it. + */ + +#include "config.h" +#ifdef HAVE_GETOPT_H +#include +#endif +#ifdef HAVE_TIME_H +#include +#endif +#include +#include "util/net_help.h" +#include "util/config_file.h" +#include "sldns/sbuffer.h" +#include + +/** 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; is, (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; ilastuse = *now; + ring_add(ring, pkt, now, delay, p); + p->numwait++; + log_addr(1, "recv from client", &p->addr, p->addr_len); + } +} + +/** delete tcp proxy */ +static void +tcp_proxy_delete(struct tcp_proxy* p) +{ + struct tcp_send_list* s, *sn; + if(!p) + return; + log_addr(1, "delete tcp proxy", &p->addr, p->addr_len); + s = p->querylist; + while(s) { + sn = s->next; + free(s->item); + free(s); + s = sn; + } + s = p->answerlist; + while(s) { + sn = s->next; + free(s->item); + free(s); + s = sn; + } +#ifndef USE_WINSOCK + close(p->client_s); + if(p->server_s != -1) + close(p->server_s); +#else + closesocket(p->client_s); + if(p->server_s != -1) + closesocket(p->server_s); +#endif + free(p); +} + +/** accept new TCP connections, and set them up */ +static void +service_tcp_listen(int s, fd_set* rorig, int* max, struct tcp_proxy** proxies, + struct sockaddr_storage* srv_addr, socklen_t srv_len, + struct timeval* now, struct timeval* tcp_timeout) +{ + int newfd; + struct sockaddr_storage addr; + struct tcp_proxy* p; + socklen_t addr_len; + newfd = accept(s, (struct sockaddr*)&addr, &addr_len); + if(newfd == -1) { +#ifndef USE_WINSOCK + if(errno == EAGAIN || errno == EINTR) + return; + fatal_exit("accept: %s", strerror(errno)); +#else + if(WSAGetLastError() == WSAEWOULDBLOCK || + WSAGetLastError() == WSAEINPROGRESS || + WSAGetLastError() == WSAECONNRESET) + return; + fatal_exit("accept: %s", wsa_strerror(WSAGetLastError())); +#endif + } + p = (struct tcp_proxy*)calloc(1, sizeof(*p)); + if(!p) fatal_exit("out of memory"); + memmove(&p->addr, &addr, addr_len); + p->addr_len = addr_len; + log_addr(1, "new tcp proxy", &p->addr, p->addr_len); + p->client_s = newfd; + p->server_s = socket(addr_is_ip6(srv_addr, srv_len)?AF_INET6:AF_INET, + SOCK_STREAM, 0); + if(p->server_s == -1) { +#ifndef USE_WINSOCK + fatal_exit("tcp socket: %s", strerror(errno)); +#else + fatal_exit("tcp socket: %s", wsa_strerror(WSAGetLastError())); +#endif + } + fd_set_nonblock(p->client_s); + fd_set_nonblock(p->server_s); + if(connect(p->server_s, (struct sockaddr*)srv_addr, srv_len) == -1) { +#ifndef USE_WINSOCK + if(errno != EINPROGRESS) { + log_err("tcp connect: %s", strerror(errno)); + close(p->server_s); + close(p->client_s); +#else + if(WSAGetLastError() != WSAEWOULDBLOCK && + WSAGetLastError() != WSAEINPROGRESS) { + log_err("tcp connect: %s", + wsa_strerror(WSAGetLastError())); + closesocket(p->server_s); + closesocket(p->client_s); +#endif + free(p); + return; + } + } + p->timeout = *now; + dl_tv_add(&p->timeout, tcp_timeout); + + /* listen to client and server */ + FD_SET(FD_SET_T p->client_s, rorig); + FD_SET(FD_SET_T p->server_s, rorig); + if(p->client_s+1 > *max) + *max = p->client_s+1; + if(p->server_s+1 > *max) + *max = p->server_s+1; + + /* add into proxy list */ + p->next = *proxies; + *proxies = p; +} + +/** relay TCP, read a part */ +static int +tcp_relay_read(int s, struct tcp_send_list** first, + struct tcp_send_list** last, struct timeval* now, + struct timeval* delay, sldns_buffer* pkt) +{ + struct tcp_send_list* item; + ssize_t r = recv(s, (void*)sldns_buffer_begin(pkt), + sldns_buffer_capacity(pkt), 0); + if(r == -1) { +#ifndef USE_WINSOCK + if(errno == EINTR || errno == EAGAIN) + return 1; + log_err("tcp read: %s", strerror(errno)); +#else + if(WSAGetLastError() == WSAEINPROGRESS || + WSAGetLastError() == WSAEWOULDBLOCK) + return 1; + log_err("tcp read: %s", wsa_strerror(WSAGetLastError())); +#endif + return 0; + } else if(r == 0) { + /* connection closed */ + return 0; + } + item = (struct tcp_send_list*)malloc(sizeof(*item)); + if(!item) { + log_err("out of memory"); + return 0; + } + verbose(1, "read item len %d", (int)r); + item->len = (size_t)r; + item->item = memdup(sldns_buffer_begin(pkt), item->len); + if(!item->item) { + free(item); + log_err("out of memory"); + return 0; + } + item->done = 0; + item->wait = *now; + dl_tv_add(&item->wait, delay); + item->next = NULL; + + /* link in */ + if(*first) { + (*last)->next = item; + } else { + *first = item; + } + *last = item; + return 1; +} + +/** relay TCP, write a part */ +static int +tcp_relay_write(int s, struct tcp_send_list** first, + struct tcp_send_list** last, struct timeval* now) +{ + ssize_t r; + struct tcp_send_list* p; + while(*first) { + p = *first; + /* is the item ready? */ + if(!dl_tv_smaller(&p->wait, now)) + return 1; + /* write it */ + r = send(s, (void*)(p->item + p->done), p->len - p->done, 0); + if(r == -1) { +#ifndef USE_WINSOCK + if(errno == EAGAIN || errno == EINTR) + return 1; + log_err("tcp write: %s", strerror(errno)); +#else + if(WSAGetLastError() == WSAEWOULDBLOCK || + WSAGetLastError() == WSAEINPROGRESS) + return 1; + log_err("tcp write: %s", + wsa_strerror(WSAGetLastError())); +#endif + return 0; + } else if(r == 0) { + /* closed */ + return 0; + } + /* account it */ + p->done += (size_t)r; + verbose(1, "write item %d of %d", (int)p->done, (int)p->len); + if(p->done >= p->len) { + free(p->item); + *first = p->next; + if(!*first) + *last = NULL; + free(p); + } else { + /* partial write */ + return 1; + } + } + return 1; +} + +/** perform TCP relaying */ +static void +service_tcp_relay(struct tcp_proxy** tcp_proxies, struct timeval* now, + struct timeval* delay, struct timeval* tcp_timeout, sldns_buffer* pkt, + fd_set* rset, fd_set* rorig, fd_set* worig) +{ + struct tcp_proxy* p, **prev; + struct timeval tout; + int delete_it; + p = *tcp_proxies; + prev = tcp_proxies; + tout = *now; + dl_tv_add(&tout, tcp_timeout); + + while(p) { + delete_it = 0; + /* can we receive further queries? */ + if(!delete_it && FD_ISSET(p->client_s, rset)) { + p->timeout = tout; + log_addr(1, "read tcp query", &p->addr, p->addr_len); + if(!tcp_relay_read(p->client_s, &p->querylist, + &p->querylast, now, delay, pkt)) + delete_it = 1; + } + /* can we receive further answers? */ + if(!delete_it && p->server_s != -1 && + FD_ISSET(p->server_s, rset)) { + p->timeout = tout; + log_addr(1, "read tcp answer", &p->addr, p->addr_len); + if(!tcp_relay_read(p->server_s, &p->answerlist, + &p->answerlast, now, delay, pkt)) { +#ifndef USE_WINSOCK + close(p->server_s); +#else + closesocket(p->server_s); +#endif + FD_CLR(FD_SET_T p->server_s, worig); + FD_CLR(FD_SET_T p->server_s, rorig); + p->server_s = -1; + } + } + /* can we send on further queries */ + if(!delete_it && p->querylist && p->server_s != -1) { + p->timeout = tout; + if(dl_tv_smaller(&p->querylist->wait, now)) + log_addr(1, "write tcp query", + &p->addr, p->addr_len); + if(!tcp_relay_write(p->server_s, &p->querylist, + &p->querylast, now)) + delete_it = 1; + if(p->querylist && p->server_s != -1 && + dl_tv_smaller(&p->querylist->wait, now)) + FD_SET(FD_SET_T p->server_s, worig); + else FD_CLR(FD_SET_T p->server_s, worig); + } + + /* can we send on further answers */ + if(!delete_it && p->answerlist) { + p->timeout = tout; + if(dl_tv_smaller(&p->answerlist->wait, now)) + log_addr(1, "write tcp answer", + &p->addr, p->addr_len); + if(!tcp_relay_write(p->client_s, &p->answerlist, + &p->answerlast, now)) + delete_it = 1; + if(p->answerlist && dl_tv_smaller(&p->answerlist->wait, + now)) + FD_SET(FD_SET_T p->client_s, worig); + else FD_CLR(FD_SET_T p->client_s, worig); + if(!p->answerlist && p->server_s == -1) + delete_it = 1; + } + + /* does this entry timeout? (unused too long) */ + if(dl_tv_smaller(&p->timeout, now)) { + delete_it = 1; + } + if(delete_it) { + struct tcp_proxy* np = p->next; + *prev = np; + FD_CLR(FD_SET_T p->client_s, rorig); + FD_CLR(FD_SET_T p->client_s, worig); + if(p->server_s != -1) { + FD_CLR(FD_SET_T p->server_s, rorig); + FD_CLR(FD_SET_T p->server_s, worig); + } + tcp_proxy_delete(p); + p = np; + continue; + } + + prev = &p->next; + p = p->next; + } +} + +/** find waiting time */ +static int +service_findwait(struct timeval* now, struct timeval* wait, + struct ringbuf* ring, struct tcp_proxy* tcplist) +{ + /* first item is the time to wait */ + struct timeval* peek = ring_peek_time(ring); + struct timeval tcv; + int have_tcpval = 0; + struct tcp_proxy* p; + + /* also for TCP list the first in sendlists is the time to wait */ + for(p=tcplist; p; p=p->next) { + if(!have_tcpval) + tcv = p->timeout; + have_tcpval = 1; + if(dl_tv_smaller(&p->timeout, &tcv)) + tcv = p->timeout; + if(p->querylist && dl_tv_smaller(&p->querylist->wait, &tcv)) + tcv = p->querylist->wait; + if(p->answerlist && dl_tv_smaller(&p->answerlist->wait, &tcv)) + tcv = p->answerlist->wait; + } + if(peek) { + /* peek can be unaligned */ + /* use wait as a temp variable */ + memmove(wait, peek, sizeof(*wait)); + if(!have_tcpval) + tcv = *wait; + else if(dl_tv_smaller(wait, &tcv)) + tcv = *wait; + have_tcpval = 1; + } + if(have_tcpval) { + *wait = tcv; + dl_tv_subtract(wait, now); + return 1; + } + /* nothing, block */ + return 0; +} + +/** clear proxy list */ +static void +proxy_list_clear(struct proxy* p) +{ + char from[109]; + struct proxy* np; + int i=0, port; + while(p) { + np = p->next; + port = (int)ntohs(((struct sockaddr_in*)&p->addr)->sin_port); + if(addr_is_ip6(&p->addr, p->addr_len)) { + if(inet_ntop(AF_INET6, + &((struct sockaddr_in6*)&p->addr)->sin6_addr, + from, (socklen_t)sizeof(from)) == 0) + (void)strlcpy(from, "err", sizeof(from)); + } else { + if(inet_ntop(AF_INET, + &((struct sockaddr_in*)&p->addr)->sin_addr, + from, (socklen_t)sizeof(from)) == 0) + (void)strlcpy(from, "err", sizeof(from)); + } + printf("client[%d]: last %s@%d of %d : %u in, %u out, " + "%u returned\n", i++, from, port, (int)p->numreuse+1, + (unsigned)p->numwait, (unsigned)p->numsent, + (unsigned)p->numreturn); +#ifndef USE_WINSOCK + close(p->s); +#else + closesocket(p->s); +#endif + free(p); + p = np; + } +} + +/** clear TCP proxy list */ +static void +tcp_proxy_list_clear(struct tcp_proxy* p) +{ + struct tcp_proxy* np; + while(p) { + np = p->next; + tcp_proxy_delete(p); + p = np; + } +} + +/** delayer service loop */ +static void +service_loop(int udp_s, int listen_s, struct ringbuf* ring, + struct timeval* delay, struct timeval* reuse, + struct sockaddr_storage* srv_addr, socklen_t srv_len, + sldns_buffer* pkt) +{ + fd_set rset, rorig; + fd_set wset, worig; + struct timeval now, wait; + int max, have_wait = 0; + struct proxy* proxies = NULL; + struct tcp_proxy* tcp_proxies = NULL; + struct timeval tcp_timeout; + tcp_timeout.tv_sec = 120; + tcp_timeout.tv_usec = 0; +#ifndef S_SPLINT_S + FD_ZERO(&rorig); + FD_ZERO(&worig); + FD_SET(FD_SET_T udp_s, &rorig); + FD_SET(FD_SET_T listen_s, &rorig); +#endif + max = udp_s + 1; + if(listen_s + 1 > max) max = listen_s + 1; + while(!do_quit) { + /* wait for events */ + rset = rorig; + wset = worig; + if(have_wait) + verbose(1, "wait for %d.%6.6d", + (unsigned)wait.tv_sec, (unsigned)wait.tv_usec); + else verbose(1, "wait"); + if(select(max, &rset, &wset, NULL, have_wait?&wait:NULL) < 0) { + if(errno == EAGAIN || errno == EINTR) + continue; + fatal_exit("select: %s", strerror(errno)); + } + /* get current time */ + if(gettimeofday(&now, NULL) < 0) { + if(errno == EAGAIN || errno == EINTR) + continue; + fatal_exit("gettimeofday: %s", strerror(errno)); + } + verbose(1, "process at %u.%6.6u\n", + (unsigned)now.tv_sec, (unsigned)now.tv_usec); + /* sendout delayed queries to master server (frees up buffer)*/ + service_send(ring, &now, pkt, srv_addr, srv_len); + /* proxy return replies */ + service_proxy(&rset, udp_s, proxies, pkt, &now); + /* see what can be received to start waiting */ + service_recv(udp_s, ring, pkt, &rorig, &max, &proxies, + srv_addr, srv_len, &now, delay, reuse); + /* see if there are new tcp connections */ + service_tcp_listen(listen_s, &rorig, &max, &tcp_proxies, + srv_addr, srv_len, &now, &tcp_timeout); + /* service tcp connections */ + service_tcp_relay(&tcp_proxies, &now, delay, &tcp_timeout, + pkt, &rset, &rorig, &worig); + /* see what next timeout is (if any) */ + have_wait = service_findwait(&now, &wait, ring, tcp_proxies); + } + proxy_list_clear(proxies); + tcp_proxy_list_clear(tcp_proxies); +} + +/** delayer main service routine */ +static void +service(const char* bind_str, int bindport, const char* serv_str, + size_t memsize, int delay_msec) +{ + struct sockaddr_storage bind_addr, srv_addr; + socklen_t bind_len, srv_len; + struct ringbuf* ring = ring_create(memsize); + struct timeval delay, reuse; + sldns_buffer* pkt; + int i, s, listen_s; +#ifndef S_SPLINT_S + delay.tv_sec = delay_msec / 1000; + delay.tv_usec = (delay_msec % 1000)*1000; +#endif + reuse = delay; /* reuse is max(4*delay, 1 second) */ + dl_tv_add(&reuse, &delay); + dl_tv_add(&reuse, &delay); + dl_tv_add(&reuse, &delay); + if(reuse.tv_sec == 0) + reuse.tv_sec = 1; + if(!extstrtoaddr(serv_str, &srv_addr, &srv_len)) { + printf("cannot parse forward address: %s\n", serv_str); + exit(1); + } + pkt = sldns_buffer_new(65535); + if(!pkt) + fatal_exit("out of memory"); + if( signal(SIGINT, delayer_sigh) == SIG_ERR || +#ifdef SIGHUP + signal(SIGHUP, delayer_sigh) == SIG_ERR || +#endif +#ifdef SIGQUIT + signal(SIGQUIT, delayer_sigh) == SIG_ERR || +#endif +#ifdef SIGBREAK + signal(SIGBREAK, delayer_sigh) == SIG_ERR || +#endif +#ifdef SIGALRM + signal(SIGALRM, delayer_sigh) == SIG_ERR || +#endif + signal(SIGTERM, delayer_sigh) == SIG_ERR) + fatal_exit("could not bind to signal"); + /* bind UDP port */ + if((s = socket(str_is_ip6(bind_str)?AF_INET6:AF_INET, + SOCK_DGRAM, 0)) == -1) { +#ifndef USE_WINSOCK + fatal_exit("socket: %s", strerror(errno)); +#else + fatal_exit("socket: %s", wsa_strerror(WSAGetLastError())); +#endif + } + i=0; + if(bindport == 0) { + bindport = 1024 + arc4random()%64000; + i = 100; + } + while(1) { + if(!ipstrtoaddr(bind_str, bindport, &bind_addr, &bind_len)) { + printf("cannot parse listen address: %s\n", bind_str); + exit(1); + } + if(bind(s, (struct sockaddr*)&bind_addr, bind_len) == -1) { +#ifndef USE_WINSOCK + log_err("bind: %s", strerror(errno)); +#else + log_err("bind: %s", wsa_strerror(WSAGetLastError())); +#endif + if(i--==0) + fatal_exit("cannot bind any port"); + bindport = 1024 + arc4random()%64000; + } else break; + } + fd_set_nonblock(s); + /* and TCP port */ + if((listen_s = socket(str_is_ip6(bind_str)?AF_INET6:AF_INET, + SOCK_STREAM, 0)) == -1) { +#ifndef USE_WINSOCK + fatal_exit("tcp socket: %s", strerror(errno)); +#else + fatal_exit("tcp socket: %s", wsa_strerror(WSAGetLastError())); +#endif + } +#ifdef SO_REUSEADDR + if(1) { + int on = 1; + if(setsockopt(listen_s, SOL_SOCKET, SO_REUSEADDR, (void*)&on, + (socklen_t)sizeof(on)) < 0) +#ifndef USE_WINSOCK + fatal_exit("setsockopt(.. SO_REUSEADDR ..) failed: %s", + strerror(errno)); +#else + fatal_exit("setsockopt(.. SO_REUSEADDR ..) failed: %s", + wsa_strerror(WSAGetLastError())); +#endif + } +#endif + if(bind(listen_s, (struct sockaddr*)&bind_addr, bind_len) == -1) { +#ifndef USE_WINSOCK + fatal_exit("tcp bind: %s", strerror(errno)); +#else + fatal_exit("tcp bind: %s", wsa_strerror(WSAGetLastError())); +#endif + } + if(listen(listen_s, 5) == -1) { +#ifndef USE_WINSOCK + fatal_exit("tcp listen: %s", strerror(errno)); +#else + fatal_exit("tcp listen: %s", wsa_strerror(WSAGetLastError())); +#endif + } + fd_set_nonblock(listen_s); + printf("listening on port: %d\n", bindport); + + /* process loop */ + do_quit = 0; + service_loop(s, listen_s, ring, &delay, &reuse, &srv_addr, srv_len, + pkt); + + /* cleanup */ + verbose(1, "cleanup"); +#ifndef USE_WINSOCK + close(s); + close(listen_s); +#else + closesocket(s); + closesocket(listen_s); +#endif + sldns_buffer_free(pkt); + ring_delete(ring); +} + +/** getopt global, in case header files fail to declare it. */ +extern int optind; +/** getopt global, in case header files fail to declare it. */ +extern char* optarg; + +/** main program for delayer */ +int main(int argc, char** argv) +{ + int c; /* defaults */ + const char* server = "127.0.0.1@53"; + const char* bindto = "0.0.0.0"; + int bindport = 0; + size_t memsize = 10*1024*1024; + int delay = 100; + + verbosity = 0; + log_init(0, 0, 0); + log_ident_set("delayer"); + if(argc == 1) usage(argv); + while( (c=getopt(argc, argv, "b:d:f:hm:p:")) != -1) { + switch(c) { + case 'b': + bindto = optarg; + break; + case 'd': + if(atoi(optarg)==0 && strcmp(optarg,"0")!=0) { + printf("bad delay: %s\n", optarg); + return 1; + } + delay = atoi(optarg); + break; + case 'f': + server = optarg; + break; + case 'm': + if(!cfg_parse_memsize(optarg, &memsize)) { + printf("bad memsize: %s\n", optarg); + return 1; + } + break; + case 'p': + if(atoi(optarg)==0 && strcmp(optarg,"0")!=0) { + printf("bad port nr: %s\n", optarg); + return 1; + } + bindport = atoi(optarg); + break; + case 'h': + case '?': + default: + usage(argv); + } + } + argc -= optind; + argv += optind; + if(argc != 0) + usage(argv); + + printf("bind to %s @ %d and forward to %s after %d msec\n", + bindto, bindport, server, delay); + service(bindto, bindport, server, memsize, delay); + return 0; +} diff --git a/usr.sbin/unbound/testcode/do-tests.sh b/usr.sbin/unbound/testcode/do-tests.sh new file mode 100755 index 00000000000..5439f0f285e --- /dev/null +++ b/usr.sbin/unbound/testcode/do-tests.sh @@ -0,0 +1,63 @@ +#!/usr/bin/env bash +. testdata/common.sh + +NEED_SPLINT='00-lint.tdir' +NEED_DOXYGEN='01-doc.tdir' +NEED_XXD='fwd_compress_c00c.tdir fwd_zero.tdir' +NEED_NC='fwd_compress_c00c.tdir fwd_zero.tdir' +NEED_CURL='06-ianaports.tdir root_anchor.tdir' +NEED_WHOAMI='07-confroot.tdir' +NEED_IPV6='fwd_ancil.tdir fwd_tcp_tc6.tdir stub_udp6.tdir edns_cache.tdir' +NEED_NOMINGW='tcp_sigpipe.tdir 07-confroot.tdir 08-host-lib.tdir fwd_ancil.tdir' +NEED_DNSCRYPT_PROXY='dnscrypt_queries.tdir dnscrypt_queries_chacha.tdir' + +# test if dig and ldns-testns are available. +test_tool_avail "dig" +test_tool_avail "ldns-testns" + +# test for ipv6, uses streamtcp peculiarity. +if ./streamtcp -f ::1 2>&1 | grep "not supported" >/dev/null 2>&1; then + HAVE_IPV6=no +else + HAVE_IPV6=yes +fi + +# test mingw. no signals and so on. +if uname | grep MINGW >/dev/null; then + HAVE_MINGW=yes +else + HAVE_MINGW=no +fi + +cd testdata; +sh ../testcode/mini_tdir.sh clean +rm -f .perfstats.txt +for test in `ls -d *.tdir`; do + SKIP=0 + skip_if_in_list $test "$NEED_SPLINT" "splint" + skip_if_in_list $test "$NEED_DOXYGEN" "doxygen" + skip_if_in_list $test "$NEED_CURL" "curl" + skip_if_in_list $test "$NEED_XXD" "xxd" + skip_if_in_list $test "$NEED_NC" "nc" + skip_if_in_list $test "$NEED_WHOAMI" "whoami" + skip_if_in_list $test "$NEED_DNSCRYPT_PROXY" "dnscrypt-proxy" + + if echo $NEED_IPV6 | grep $test >/dev/null; then + if test "$HAVE_IPV6" = no; then + SKIP=1; + fi + fi + if echo $NEED_NOMINGW | grep $test >/dev/null; then + if test "$HAVE_MINGW" = yes; then + SKIP=1; + fi + fi + if test $SKIP -eq 0; then + echo $test + sh ../testcode/mini_tdir.sh -a ../.. exe $test + else + echo "skip $test" + fi +done +sh ../testcode/mini_tdir.sh report +cat .perfstats.txt diff --git a/usr.sbin/unbound/testcode/fake_event.c b/usr.sbin/unbound/testcode/fake_event.c new file mode 100644 index 00000000000..80e3685c09e --- /dev/null +++ b/usr.sbin/unbound/testcode/fake_event.c @@ -0,0 +1,1792 @@ +/* + * testcode/fake_event.c - fake event handling that replays existing scenario. + * + * Copyright (c) 2007, NLnet Labs. All rights reserved. + * + * This software is open source. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * Neither the name of the NLNET LABS nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * \file + * Event service that replays a scenario. + * This implements the same exported symbols as the files: + * util/netevent.c + * services/listen_dnsport.c + * services/outside_network.c + * But these do not actually access the network or events, instead + * the scenario is played. + */ + +#include "config.h" +#include "testcode/fake_event.h" +#include "util/netevent.h" +#include "util/net_help.h" +#include "util/data/msgparse.h" +#include "util/data/msgreply.h" +#include "util/data/msgencode.h" +#include "util/data/dname.h" +#include "util/config_file.h" +#include "services/listen_dnsport.h" +#include "services/outside_network.h" +#include "services/cache/infra.h" +#include "testcode/replay.h" +#include "testcode/testpkts.h" +#include "util/log.h" +#include "util/fptr_wlist.h" +#include "sldns/sbuffer.h" +#include "sldns/wire2str.h" +#include "sldns/str2wire.h" +#include +struct worker; +struct daemon_remote; + +/** unique code to check that fake_commpoint is that structure */ +#define FAKE_COMMPOINT_TYPECODE 97347923 +/** fake commpoint, stores information */ +struct fake_commpoint { + /** typecode */ + int typecode; + /** if this is a udp outgoing type of commpoint */ + int type_udp_out; + /** if this is a tcp outgoing type of commpoint */ + int type_tcp_out; + /** if this is a http outgoing type of commpoint. */ + int type_http_out; + + /** the callback, stored for usage */ + comm_point_callback_type* cb; + /** the callback userarg, stored for usage */ + void* cb_arg; + /** runtime ptr */ + struct replay_runtime* runtime; + /** the pending entry for this commpoint (if any) */ + struct fake_pending* pending; +}; + +/** Global variable: the scenario. Saved here for when event_init is done. */ +static struct replay_scenario* saved_scenario = NULL; + +/** add timers and the values do not overflow or become negative */ +static void +timeval_add(struct timeval* d, const struct timeval* add) +{ +#ifndef S_SPLINT_S + d->tv_sec += add->tv_sec; + d->tv_usec += add->tv_usec; + if(d->tv_usec > 1000000) { + d->tv_usec -= 1000000; + d->tv_sec++; + } +#endif +} + +void +fake_temp_file(const char* adj, const char* id, char* buf, size_t len) +{ +#ifdef USE_WINSOCK + snprintf(buf, len, "testbound_%u%s%s.tmp", + (unsigned)getpid(), adj, id); +#else + snprintf(buf, len, "/tmp/testbound_%u%s%s.tmp", + (unsigned)getpid(), adj, id); +#endif +} + +void +fake_event_init(struct replay_scenario* scen) +{ + saved_scenario = scen; +} + +void +fake_event_cleanup(void) +{ + replay_scenario_delete(saved_scenario); + saved_scenario = NULL; +} + +/** helper function that logs a sldns_pkt packet to logfile */ +static void +log_pkt(const char* desc, uint8_t* pkt, size_t len) +{ + char* str = sldns_wire2str_pkt(pkt, len); + if(!str) + fatal_exit("%s: (failed out of memory wire2str_pkt)", desc); + else { + log_info("%s%s", desc, str); + free(str); + } +} + +/** + * Returns a string describing the event type. + */ +static const char* +repevt_string(enum replay_event_type t) +{ + switch(t) { + case repevt_nothing: return "NOTHING"; + case repevt_front_query: return "QUERY"; + case repevt_front_reply: return "CHECK_ANSWER"; + case repevt_timeout: return "TIMEOUT"; + case repevt_time_passes: return "TIME_PASSES"; + case repevt_back_reply: return "REPLY"; + case repevt_back_query: return "CHECK_OUT_QUERY"; + case repevt_autotrust_check: return "CHECK_AUTOTRUST"; + case repevt_tempfile_check: return "CHECK_TEMPFILE"; + case repevt_error: return "ERROR"; + case repevt_assign: return "ASSIGN"; + case repevt_traffic: return "TRAFFIC"; + case repevt_infra_rtt: return "INFRA_RTT"; + default: return "UNKNOWN"; + } +} + +/** delete a fake pending */ +static void +delete_fake_pending(struct fake_pending* pend) +{ + if(!pend) + return; + free(pend->zone); + sldns_buffer_free(pend->buffer); + free(pend->pkt); + free(pend); +} + +/** delete a replay answer */ +static void +delete_replay_answer(struct replay_answer* a) +{ + if(!a) + return; + if(a->repinfo.c) { + sldns_buffer_free(a->repinfo.c->buffer); + free(a->repinfo.c); + } + free(a->pkt); + free(a); +} + +/** + * return: true if pending query matches the now event. + */ +static int +pending_matches_current(struct replay_runtime* runtime, + struct entry** entry, struct fake_pending **pend) +{ + struct fake_pending* p; + struct entry* e; + if(!runtime->now || runtime->now->evt_type != repevt_back_query + || !runtime->pending_list) + return 0; + /* see if any of the pending queries matches */ + for(p = runtime->pending_list; p; p = p->next) { + if(runtime->now->addrlen != 0 && + sockaddr_cmp(&p->addr, p->addrlen, &runtime->now->addr, + runtime->now->addrlen) != 0) + continue; + if((e=find_match(runtime->now->match, p->pkt, p->pkt_len, + p->transport))) { + *entry = e; + *pend = p; + return 1; + } + } + return 0; +} + +/** + * Find the range that matches this pending message. + * @param runtime: runtime with current moment, and range list. + * @param entry: returns the pointer to entry that matches. + * @param pend: the pending that the entry must match. + * @return: true if a match is found. + */ +static int +pending_find_match(struct replay_runtime* runtime, struct entry** entry, + struct fake_pending* pend) +{ + int timenow = runtime->now->time_step; + struct replay_range* p = runtime->scenario->range_list; + while(p) { + if(p->start_step <= timenow && timenow <= p->end_step && + (p->addrlen == 0 || sockaddr_cmp(&p->addr, p->addrlen, + &pend->addr, pend->addrlen) == 0) && + (*entry = find_match(p->match, pend->pkt, pend->pkt_len, + pend->transport))) { + log_info("matched query time %d in range [%d, %d] " + "with entry line %d", timenow, + p->start_step, p->end_step, (*entry)->lineno); + if(p->addrlen != 0) + log_addr(0, "matched ip", &p->addr, p->addrlen); + log_pkt("matched pkt: ", + (*entry)->reply_list->reply_pkt, + (*entry)->reply_list->reply_len); + return 1; + } + p = p->next_range; + } + return 0; +} + +/** + * See if outgoing pending query matches an entry. + * @param runtime: runtime. + * @param entry: if true, the entry that matches is returned. + * @param pend: if true, the outgoing message that matches is returned. + * @return: true if pending query matches the now event. + */ +static int +pending_matches_range(struct replay_runtime* runtime, + struct entry** entry, struct fake_pending** pend) +{ + struct fake_pending* p = runtime->pending_list; + /* slow, O(N*N), but it works as advertised with weird matching */ + while(p) { + if(p->tcp_pkt_counter != 0) { + /* continue tcp transfer */ + *pend = p; + return 1; + } + if(pending_find_match(runtime, entry, p)) { + *pend = p; + return 1; + } + p = p->next; + } + return 0; +} + +/** + * Remove the item from the pending list. + */ +static void +pending_list_delete(struct replay_runtime* runtime, struct fake_pending* pend) +{ + struct fake_pending** prev = &runtime->pending_list; + struct fake_pending* p = runtime->pending_list; + + while(p) { + if(p == pend) { + *prev = p->next; + delete_fake_pending(pend); + return; + } + + prev = &p->next; + p = p->next; + } +} + +/** number of replies in entry */ +static int +count_reply_packets(struct entry* entry) +{ + int count = 0; + struct reply_packet* reppkt = entry->reply_list; + while(reppkt) { + count++; + reppkt = reppkt->next; + } + return count; +} + +/** + * Fill buffer with reply from the entry. + */ +static void +fill_buffer_with_reply(sldns_buffer* buffer, struct entry* entry, uint8_t* q, + size_t qlen, int tcp_pkt_counter) +{ + struct reply_packet* reppkt; + uint8_t* c; + size_t clen; + log_assert(entry && entry->reply_list); + sldns_buffer_clear(buffer); + reppkt = entry->reply_list; + if(tcp_pkt_counter > 0) { + int i = tcp_pkt_counter; + while(reppkt && i--) + reppkt = reppkt->next; + if(!reppkt) fatal_exit("extra packet read from TCP stream but none is available"); + log_pkt("extra_packet ", reppkt->reply_pkt, reppkt->reply_len); + } + if(reppkt->reply_from_hex) { + c = sldns_buffer_begin(reppkt->reply_from_hex); + clen = sldns_buffer_limit(reppkt->reply_from_hex); + if(!c) fatal_exit("out of memory"); + } else { + c = reppkt->reply_pkt; + clen = reppkt->reply_len; + } + if(c) { + if(q) adjust_packet(entry, &c, &clen, q, qlen); + sldns_buffer_write(buffer, c, clen); + if(q) free(c); + } + sldns_buffer_flip(buffer); +} + +/** + * Perform range entry on pending message. + * @param runtime: runtime buffer size preference. + * @param entry: entry that codes for the reply to do. + * @param pend: pending query that is answered, callback called. + */ +static void +answer_callback_from_entry(struct replay_runtime* runtime, + struct entry* entry, struct fake_pending* pend) +{ + struct comm_point c; + struct comm_reply repinfo; + void* cb_arg = pend->cb_arg; + comm_point_callback_type* cb = pend->callback; + + memset(&c, 0, sizeof(c)); + c.fd = -1; + c.buffer = sldns_buffer_new(runtime->bufsize); + c.type = comm_udp; + if(pend->transport == transport_tcp) + c.type = comm_tcp; + fill_buffer_with_reply(c.buffer, entry, pend->pkt, pend->pkt_len, + pend->tcp_pkt_counter); + repinfo.c = &c; + repinfo.addrlen = pend->addrlen; + memcpy(&repinfo.addr, &pend->addr, pend->addrlen); + if(!pend->serviced) { + if(entry->reply_list->next && + pend->tcp_pkt_counter < count_reply_packets(entry)) { + /* go to next packet next time */ + pend->tcp_pkt_counter++; + } else { + pending_list_delete(runtime, pend); + } + } + if((*cb)(&c, cb_arg, NETEVENT_NOERROR, &repinfo)) { + fatal_exit("testbound: unexpected: callback returned 1"); + } + sldns_buffer_free(c.buffer); +} + +/** Check the now moment answer check event */ +static void +answer_check_it(struct replay_runtime* runtime) +{ + struct replay_answer* ans = runtime->answer_list, + *prev = NULL; + log_assert(runtime && runtime->now && + runtime->now->evt_type == repevt_front_reply); + while(ans) { + enum transport_type tr = transport_tcp; + if(ans->repinfo.c->type == comm_udp) + tr = transport_udp; + if((runtime->now->addrlen == 0 || sockaddr_cmp( + &runtime->now->addr, runtime->now->addrlen, + &ans->repinfo.addr, ans->repinfo.addrlen) == 0) && + find_match(runtime->now->match, ans->pkt, + ans->pkt_len, tr)) { + log_info("testbound matched event entry from line %d", + runtime->now->match->lineno); + log_info("testbound: do STEP %d %s", + runtime->now->time_step, + repevt_string(runtime->now->evt_type)); + if(prev) + prev->next = ans->next; + else runtime->answer_list = ans->next; + if(!ans->next) + runtime->answer_last = prev; + delete_replay_answer(ans); + return; + } else { + prev = ans; + ans = ans->next; + } + } + log_info("testbound: do STEP %d %s", runtime->now->time_step, + repevt_string(runtime->now->evt_type)); + fatal_exit("testbound: not matched"); +} + +/** + * Create commpoint (as return address) for a fake incoming query. + */ +static void +fake_front_query(struct replay_runtime* runtime, struct replay_moment *todo) +{ + struct comm_reply repinfo; + memset(&repinfo, 0, sizeof(repinfo)); + repinfo.c = (struct comm_point*)calloc(1, sizeof(struct comm_point)); + repinfo.addrlen = (socklen_t)sizeof(struct sockaddr_in); + if(todo->addrlen != 0) { + repinfo.addrlen = todo->addrlen; + memcpy(&repinfo.addr, &todo->addr, todo->addrlen); + } + repinfo.c->fd = -1; + repinfo.c->ev = (struct internal_event*)runtime; + repinfo.c->buffer = sldns_buffer_new(runtime->bufsize); + if(todo->match->match_transport == transport_tcp) + repinfo.c->type = comm_tcp; + else repinfo.c->type = comm_udp; + fill_buffer_with_reply(repinfo.c->buffer, todo->match, NULL, 0, 0); + log_info("testbound: incoming QUERY"); + log_pkt("query pkt", todo->match->reply_list->reply_pkt, + todo->match->reply_list->reply_len); + /* call the callback for incoming queries */ + if((*runtime->callback_query)(repinfo.c, runtime->cb_arg, + NETEVENT_NOERROR, &repinfo)) { + /* send immediate reply */ + comm_point_send_reply(&repinfo); + } + /* clear it again, in case copy not done properly */ + memset(&repinfo, 0, sizeof(repinfo)); +} + +/** + * Perform callback for fake pending message. + */ +static void +fake_pending_callback(struct replay_runtime* runtime, + struct replay_moment* todo, int error) +{ + struct fake_pending* p = runtime->pending_list; + struct comm_reply repinfo; + struct comm_point c; + void* cb_arg; + comm_point_callback_type* cb; + + memset(&c, 0, sizeof(c)); + if(!p) fatal_exit("No pending queries."); + cb_arg = p->cb_arg; + cb = p->callback; + c.buffer = sldns_buffer_new(runtime->bufsize); + c.type = comm_udp; + if(p->transport == transport_tcp) + c.type = comm_tcp; + if(todo->evt_type == repevt_back_reply && todo->match) { + fill_buffer_with_reply(c.buffer, todo->match, p->pkt, + p->pkt_len, p->tcp_pkt_counter); + } + repinfo.c = &c; + repinfo.addrlen = p->addrlen; + memcpy(&repinfo.addr, &p->addr, p->addrlen); + if(!p->serviced) { + if(todo->match->reply_list->next && !error && + p->tcp_pkt_counter < count_reply_packets(todo->match)) { + /* go to next packet next time */ + p->tcp_pkt_counter++; + } else { + pending_list_delete(runtime, p); + } + } + if((*cb)(&c, cb_arg, error, &repinfo)) { + fatal_exit("unexpected: pending callback returned 1"); + } + /* delete the pending item. */ + sldns_buffer_free(c.buffer); +} + +/** pass time */ +static void +moment_assign(struct replay_runtime* runtime, struct replay_moment* mom) +{ + char* value = macro_process(runtime->vars, runtime, mom->string); + if(!value) + fatal_exit("could not process macro step %d", mom->time_step); + log_info("assign %s = %s", mom->variable, value); + if(!macro_assign(runtime->vars, mom->variable, value)) + fatal_exit("out of memory storing macro"); + free(value); + if(verbosity >= VERB_ALGO) + macro_print_debug(runtime->vars); +} + +/** pass time */ +static void +time_passes(struct replay_runtime* runtime, struct replay_moment* mom) +{ + struct fake_timer *t; + struct timeval tv = mom->elapse; + if(mom->string) { + char* xp = macro_process(runtime->vars, runtime, mom->string); + double sec; + if(!xp) fatal_exit("could not macro expand %s", mom->string); + verbose(VERB_ALGO, "EVAL %s", mom->string); + sec = atof(xp); + free(xp); +#ifndef S_SPLINT_S + tv.tv_sec = sec; + tv.tv_usec = (int)((sec - (double)tv.tv_sec) *1000000. + 0.5); +#endif + } + timeval_add(&runtime->now_tv, &tv); + runtime->now_secs = (time_t)runtime->now_tv.tv_sec; +#ifndef S_SPLINT_S + log_info("elapsed %d.%6.6d now %d.%6.6d", + (int)tv.tv_sec, (int)tv.tv_usec, + (int)runtime->now_tv.tv_sec, (int)runtime->now_tv.tv_usec); +#endif + /* see if any timers have fired; and run them */ + while( (t=replay_get_oldest_timer(runtime)) ) { + t->enabled = 0; + log_info("fake_timer callback"); + fptr_ok(fptr_whitelist_comm_timer(t->cb)); + (*t->cb)(t->cb_arg); + } +} + +/** check autotrust file contents */ +static void +autotrust_check(struct replay_runtime* runtime, struct replay_moment* mom) +{ + char name[1024], line[1024]; + FILE *in; + int lineno = 0, oke=1; + char* expanded; + struct config_strlist* p; + line[sizeof(line)-1] = 0; + log_assert(mom->autotrust_id); + fake_temp_file("_auto_", mom->autotrust_id, name, sizeof(name)); + in = fopen(name, "r"); + if(!in) fatal_exit("could not open %s: %s", name, strerror(errno)); + for(p=mom->file_content; p; p=p->next) { + lineno++; + if(!fgets(line, (int)sizeof(line)-1, in)) { + log_err("autotrust check failed, could not read line"); + log_err("file %s, line %d", name, lineno); + log_err("should be: %s", p->str); + fatal_exit("autotrust_check failed"); + } + if(line[0]) line[strlen(line)-1] = 0; /* remove newline */ + expanded = macro_process(runtime->vars, runtime, p->str); + if(!expanded) + fatal_exit("could not expand macro line %d", lineno); + if(verbosity >= 7 && strcmp(p->str, expanded) != 0) + log_info("expanded '%s' to '%s'", p->str, expanded); + if(strcmp(expanded, line) != 0) { + log_err("mismatch in file %s, line %d", name, lineno); + log_err("file has : %s", line); + log_err("should be: %s", expanded); + free(expanded); + oke = 0; + continue; + } + free(expanded); + fprintf(stderr, "%s:%2d ok : %s\n", name, lineno, line); + } + if(fgets(line, (int)sizeof(line)-1, in)) { + log_err("autotrust check failed, extra lines in %s after %d", + name, lineno); + do { + fprintf(stderr, "file has: %s", line); + } while(fgets(line, (int)sizeof(line)-1, in)); + oke = 0; + } + fclose(in); + if(!oke) + fatal_exit("autotrust_check STEP %d failed", mom->time_step); + log_info("autotrust %s is OK", mom->autotrust_id); +} + +/** check tempfile file contents */ +static void +tempfile_check(struct replay_runtime* runtime, struct replay_moment* mom) +{ + char name[1024], line[1024]; + FILE *in; + int lineno = 0, oke=1; + char* expanded; + struct config_strlist* p; + line[sizeof(line)-1] = 0; + log_assert(mom->autotrust_id); + fake_temp_file("_temp_", mom->autotrust_id, name, sizeof(name)); + in = fopen(name, "r"); + if(!in) fatal_exit("could not open %s: %s", name, strerror(errno)); + for(p=mom->file_content; p; p=p->next) { + lineno++; + if(!fgets(line, (int)sizeof(line)-1, in)) { + log_err("tempfile check failed, could not read line"); + log_err("file %s, line %d", name, lineno); + log_err("should be: %s", p->str); + fatal_exit("tempfile_check failed"); + } + if(line[0]) line[strlen(line)-1] = 0; /* remove newline */ + expanded = macro_process(runtime->vars, runtime, p->str); + if(!expanded) + fatal_exit("could not expand macro line %d", lineno); + if(verbosity >= 7 && strcmp(p->str, expanded) != 0) + log_info("expanded '%s' to '%s'", p->str, expanded); + if(strcmp(expanded, line) != 0) { + log_err("mismatch in file %s, line %d", name, lineno); + log_err("file has : %s", line); + log_err("should be: %s", expanded); + free(expanded); + oke = 0; + continue; + } + free(expanded); + fprintf(stderr, "%s:%2d ok : %s\n", name, lineno, line); + } + if(fgets(line, (int)sizeof(line)-1, in)) { + log_err("tempfile check failed, extra lines in %s after %d", + name, lineno); + do { + fprintf(stderr, "file has: %s", line); + } while(fgets(line, (int)sizeof(line)-1, in)); + oke = 0; + } + fclose(in); + if(!oke) + fatal_exit("tempfile_check STEP %d failed", mom->time_step); + log_info("tempfile %s is OK", mom->autotrust_id); +} + +/** Store RTT in infra cache */ +static void +do_infra_rtt(struct replay_runtime* runtime) +{ + struct replay_moment* now = runtime->now; + int rto; + size_t dplen = 0; + uint8_t* dp = sldns_str2wire_dname(now->variable, &dplen); + if(!dp) fatal_exit("cannot parse %s", now->variable); + rto = infra_rtt_update(runtime->infra, &now->addr, now->addrlen, + dp, dplen, LDNS_RR_TYPE_A, atoi(now->string), + -1, runtime->now_secs); + log_addr(0, "INFRA_RTT for", &now->addr, now->addrlen); + log_info("INFRA_RTT(%s roundtrip %d): rto of %d", now->variable, + atoi(now->string), rto); + if(rto == 0) fatal_exit("infra_rtt_update failed"); + free(dp); +} + +/** perform exponential backoff on the timeout */ +static void +expon_timeout_backoff(struct replay_runtime* runtime) +{ + struct fake_pending* p = runtime->pending_list; + int rtt, vs; + uint8_t edns_lame_known; + int last_rtt, rto; + if(!p) return; /* no pending packet to backoff */ + if(!infra_host(runtime->infra, &p->addr, p->addrlen, p->zone, + p->zonelen, runtime->now_secs, &vs, &edns_lame_known, &rtt)) + return; + last_rtt = rtt; + rto = infra_rtt_update(runtime->infra, &p->addr, p->addrlen, p->zone, + p->zonelen, p->qtype, -1, last_rtt, runtime->now_secs); + log_info("infra_rtt_update returned rto %d", rto); +} + +/** + * Advance to the next moment. + */ +static void +advance_moment(struct replay_runtime* runtime) +{ + if(!runtime->now) + runtime->now = runtime->scenario->mom_first; + else runtime->now = runtime->now->mom_next; +} + +/** + * Perform actions or checks determined by the moment. + * Also advances the time by one step. + * @param runtime: scenario runtime information. + */ +static void +do_moment_and_advance(struct replay_runtime* runtime) +{ + struct replay_moment* mom; + if(!runtime->now) { + advance_moment(runtime); + return; + } + log_info("testbound: do STEP %d %s", runtime->now->time_step, + repevt_string(runtime->now->evt_type)); + switch(runtime->now->evt_type) { + case repevt_nothing: + advance_moment(runtime); + break; + case repevt_front_query: + /* advance moment before doing the step, so that the next + moment which may check some result of the mom step + can catch those results. */ + mom = runtime->now; + advance_moment(runtime); + fake_front_query(runtime, mom); + break; + case repevt_front_reply: + if(runtime->answer_list) + log_err("testbound: There are unmatched answers."); + fatal_exit("testbound: query answer not matched"); + break; + case repevt_timeout: + mom = runtime->now; + advance_moment(runtime); + expon_timeout_backoff(runtime); + fake_pending_callback(runtime, mom, NETEVENT_TIMEOUT); + break; + case repevt_back_reply: + mom = runtime->now; + advance_moment(runtime); + fake_pending_callback(runtime, mom, NETEVENT_NOERROR); + break; + case repevt_back_query: + /* Back queries are matched when they are sent out. */ + log_err("No query matching the current moment was sent."); + fatal_exit("testbound: back query not matched"); + break; + case repevt_error: + mom = runtime->now; + advance_moment(runtime); + fake_pending_callback(runtime, mom, NETEVENT_CLOSED); + break; + case repevt_time_passes: + time_passes(runtime, runtime->now); + advance_moment(runtime); + break; + case repevt_autotrust_check: + autotrust_check(runtime, runtime->now); + advance_moment(runtime); + break; + case repevt_tempfile_check: + tempfile_check(runtime, runtime->now); + advance_moment(runtime); + break; + case repevt_assign: + moment_assign(runtime, runtime->now); + advance_moment(runtime); + break; + case repevt_traffic: + advance_moment(runtime); + break; + case repevt_infra_rtt: + do_infra_rtt(runtime); + advance_moment(runtime); + break; + default: + fatal_exit("testbound: unknown event type %d", + runtime->now->evt_type); + } +} + +/** run the scenario in event callbacks */ +static void +run_scenario(struct replay_runtime* runtime) +{ + struct entry* entry = NULL; + struct fake_pending* pending = NULL; + int max_rounds = 5000; + int rounds = 0; + runtime->now = runtime->scenario->mom_first; + log_info("testbound: entering fake runloop"); + do { + /* if moment matches pending query do it. */ + /* else if moment matches given answer, do it */ + /* else if precoded_range matches pending, do it */ + /* else do the current moment */ + if(pending_matches_current(runtime, &entry, &pending)) { + log_info("testbound: do STEP %d CHECK_OUT_QUERY", + runtime->now->time_step); + advance_moment(runtime); + if(entry->copy_id) + answer_callback_from_entry(runtime, entry, + pending); + } else if(runtime->answer_list && runtime->now && + runtime->now->evt_type == repevt_front_reply) { + answer_check_it(runtime); + advance_moment(runtime); + } else if(pending_matches_range(runtime, &entry, &pending)) { + answer_callback_from_entry(runtime, entry, pending); + } else { + do_moment_and_advance(runtime); + } + log_info("testbound: end of event stage"); + rounds++; + if(rounds > max_rounds) + fatal_exit("testbound: too many rounds, it loops."); + } while(runtime->now); + + if(runtime->pending_list) { + struct fake_pending* p; + log_err("testbound: there are still messages pending."); + for(p = runtime->pending_list; p; p=p->next) { + log_pkt("pending msg", p->pkt, p->pkt_len); + log_addr(0, "pending to", &p->addr, p->addrlen); + } + fatal_exit("testbound: there are still messages pending."); + } + if(runtime->answer_list) { + fatal_exit("testbound: there are unmatched answers."); + } + log_info("testbound: exiting fake runloop."); + runtime->exit_cleanly = 1; +} + +/*********** Dummy routines ***********/ + +struct listen_dnsport* +listen_create(struct comm_base* base, struct listen_port* ATTR_UNUSED(ports), + size_t bufsize, int ATTR_UNUSED(tcp_accept_count), + void* ATTR_UNUSED(sslctx), struct dt_env* ATTR_UNUSED(dtenv), + comm_point_callback_type* cb, void* cb_arg) +{ + struct replay_runtime* runtime = (struct replay_runtime*)base; + struct listen_dnsport* l= calloc(1, sizeof(struct listen_dnsport)); + if(!l) + return NULL; + l->base = base; + l->udp_buff = sldns_buffer_new(bufsize); + if(!l->udp_buff) { + free(l); + return NULL; + } + runtime->callback_query = cb; + runtime->cb_arg = cb_arg; + runtime->bufsize = bufsize; + return l; +} + +void +listen_delete(struct listen_dnsport* listen) +{ + if(!listen) + return; + sldns_buffer_free(listen->udp_buff); + free(listen); +} + +struct comm_base* +comm_base_create(int ATTR_UNUSED(sigs)) +{ + /* we return the runtime structure instead. */ + struct replay_runtime* runtime = (struct replay_runtime*) + calloc(1, sizeof(struct replay_runtime)); + runtime->scenario = saved_scenario; + runtime->vars = macro_store_create(); + if(!runtime->vars) fatal_exit("out of memory"); + return (struct comm_base*)runtime; +} + +void +comm_base_delete(struct comm_base* b) +{ + struct replay_runtime* runtime = (struct replay_runtime*)b; + struct fake_pending* p, *np; + struct replay_answer* a, *na; + struct fake_timer* t, *nt; + if(!runtime) + return; + runtime->scenario= NULL; + p = runtime->pending_list; + while(p) { + np = p->next; + delete_fake_pending(p); + p = np; + } + a = runtime->answer_list; + while(a) { + na = a->next; + delete_replay_answer(a); + a = na; + } + t = runtime->timer_list; + while(t) { + nt = t->next; + free(t); + t = nt; + } + macro_store_delete(runtime->vars); + free(runtime); +} + +void +comm_base_timept(struct comm_base* b, time_t** tt, struct timeval** tv) +{ + struct replay_runtime* runtime = (struct replay_runtime*)b; + *tt = &runtime->now_secs; + *tv = &runtime->now_tv; +} + +void +comm_base_dispatch(struct comm_base* b) +{ + struct replay_runtime* runtime = (struct replay_runtime*)b; + run_scenario(runtime); + if(runtime->sig_cb) + (*runtime->sig_cb)(SIGTERM, runtime->sig_cb_arg); + else exit(0); /* OK exit when LIBEVENT_SIGNAL_PROBLEM exists */ +} + +void +comm_base_exit(struct comm_base* b) +{ + struct replay_runtime* runtime = (struct replay_runtime*)b; + if(!runtime->exit_cleanly) { + /* some sort of failure */ + fatal_exit("testbound: comm_base_exit was called."); + } +} + +struct comm_signal* +comm_signal_create(struct comm_base* base, + void (*callback)(int, void*), void* cb_arg) +{ + struct replay_runtime* runtime = (struct replay_runtime*)base; + runtime->sig_cb = callback; + runtime->sig_cb_arg = cb_arg; + return calloc(1, sizeof(struct comm_signal)); +} + +int +comm_signal_bind(struct comm_signal* ATTR_UNUSED(comsig), int + ATTR_UNUSED(sig)) +{ + return 1; +} + +void +comm_signal_delete(struct comm_signal* comsig) +{ + free(comsig); +} + +void +comm_point_send_reply(struct comm_reply* repinfo) +{ + struct replay_answer* ans = (struct replay_answer*)calloc(1, + sizeof(struct replay_answer)); + struct replay_runtime* runtime = (struct replay_runtime*)repinfo->c->ev; + log_info("testbound: comm_point_send_reply fake"); + /* dump it into the todo list */ + log_assert(ans); + memcpy(&ans->repinfo, repinfo, sizeof(struct comm_reply)); + ans->next = NULL; + if(runtime->answer_last) + runtime->answer_last->next = ans; + else runtime->answer_list = ans; + runtime->answer_last = ans; + + /* try to parse packet */ + ans->pkt = memdup(sldns_buffer_begin(ans->repinfo.c->buffer), + sldns_buffer_limit(ans->repinfo.c->buffer)); + ans->pkt_len = sldns_buffer_limit(ans->repinfo.c->buffer); + if(!ans->pkt) fatal_exit("out of memory"); + log_pkt("reply pkt: ", ans->pkt, ans->pkt_len); +} + +void +comm_point_drop_reply(struct comm_reply* repinfo) +{ + log_info("comm_point_drop_reply fake"); + if(repinfo->c) { + sldns_buffer_free(repinfo->c->buffer); + free(repinfo->c); + } +} + +struct outside_network* +outside_network_create(struct comm_base* base, size_t bufsize, + size_t ATTR_UNUSED(num_ports), char** ATTR_UNUSED(ifs), + int ATTR_UNUSED(num_ifs), int ATTR_UNUSED(do_ip4), + int ATTR_UNUSED(do_ip6), size_t ATTR_UNUSED(num_tcp), + struct infra_cache* infra, + struct ub_randstate* ATTR_UNUSED(rnd), + int ATTR_UNUSED(use_caps_for_id), int* ATTR_UNUSED(availports), + int ATTR_UNUSED(numavailports), size_t ATTR_UNUSED(unwanted_threshold), + int ATTR_UNUSED(outgoing_tcp_mss), + void (*unwanted_action)(void*), void* ATTR_UNUSED(unwanted_param), + int ATTR_UNUSED(do_udp), void* ATTR_UNUSED(sslctx), + int ATTR_UNUSED(delayclose), struct dt_env* ATTR_UNUSED(dtenv)) +{ + struct replay_runtime* runtime = (struct replay_runtime*)base; + struct outside_network* outnet = calloc(1, + sizeof(struct outside_network)); + (void)unwanted_action; + if(!outnet) + return NULL; + runtime->infra = infra; + outnet->base = base; + outnet->udp_buff = sldns_buffer_new(bufsize); + if(!outnet->udp_buff) { + free(outnet); + return NULL; + } + return outnet; +} + +void +outside_network_delete(struct outside_network* outnet) +{ + if(!outnet) + return; + sldns_buffer_free(outnet->udp_buff); + free(outnet); +} + +void +outside_network_quit_prepare(struct outside_network* ATTR_UNUSED(outnet)) +{ +} + +struct pending* +pending_udp_query(struct serviced_query* sq, sldns_buffer* packet, + int timeout, comm_point_callback_type* callback, void* callback_arg) +{ + struct replay_runtime* runtime = (struct replay_runtime*) + sq->outnet->base; + struct fake_pending* pend = (struct fake_pending*)calloc(1, + sizeof(struct fake_pending)); + log_assert(pend); + pend->buffer = sldns_buffer_new(sldns_buffer_capacity(packet)); + log_assert(pend->buffer); + sldns_buffer_write(pend->buffer, sldns_buffer_begin(packet), + sldns_buffer_limit(packet)); + sldns_buffer_flip(pend->buffer); + memcpy(&pend->addr, &sq->addr, sq->addrlen); + pend->addrlen = sq->addrlen; + pend->callback = callback; + pend->cb_arg = callback_arg; + pend->timeout = timeout/1000; + pend->transport = transport_udp; + pend->pkt = NULL; + pend->zone = NULL; + pend->serviced = 0; + pend->runtime = runtime; + pend->pkt_len = sldns_buffer_limit(packet); + pend->pkt = memdup(sldns_buffer_begin(packet), pend->pkt_len); + if(!pend->pkt) fatal_exit("out of memory"); + log_pkt("pending udp pkt: ", pend->pkt, pend->pkt_len); + + /* see if it matches the current moment */ + if(runtime->now && runtime->now->evt_type == repevt_back_query && + (runtime->now->addrlen == 0 || sockaddr_cmp( + &runtime->now->addr, runtime->now->addrlen, + &pend->addr, pend->addrlen) == 0) && + find_match(runtime->now->match, pend->pkt, pend->pkt_len, + pend->transport)) { + log_info("testbound: matched pending to event. " + "advance time between events."); + log_info("testbound: do STEP %d %s", runtime->now->time_step, + repevt_string(runtime->now->evt_type)); + advance_moment(runtime); + /* still create the pending, because we need it to callback */ + } + log_info("testbound: created fake pending"); + /* add to list */ + pend->next = runtime->pending_list; + runtime->pending_list = pend; + return (struct pending*)pend; +} + +struct waiting_tcp* +pending_tcp_query(struct serviced_query* sq, sldns_buffer* packet, + int timeout, comm_point_callback_type* callback, void* callback_arg) +{ + struct replay_runtime* runtime = (struct replay_runtime*) + sq->outnet->base; + struct fake_pending* pend = (struct fake_pending*)calloc(1, + sizeof(struct fake_pending)); + log_assert(pend); + pend->buffer = sldns_buffer_new(sldns_buffer_capacity(packet)); + log_assert(pend->buffer); + sldns_buffer_write(pend->buffer, sldns_buffer_begin(packet), + sldns_buffer_limit(packet)); + sldns_buffer_flip(pend->buffer); + memcpy(&pend->addr, &sq->addr, sq->addrlen); + pend->addrlen = sq->addrlen; + pend->callback = callback; + pend->cb_arg = callback_arg; + pend->timeout = timeout/1000; + pend->transport = transport_tcp; + pend->pkt = NULL; + pend->zone = NULL; + pend->runtime = runtime; + pend->serviced = 0; + pend->pkt_len = sldns_buffer_limit(packet); + pend->pkt = memdup(sldns_buffer_begin(packet), pend->pkt_len); + if(!pend->pkt) fatal_exit("out of memory"); + log_pkt("pending tcp pkt: ", pend->pkt, pend->pkt_len); + + /* see if it matches the current moment */ + if(runtime->now && runtime->now->evt_type == repevt_back_query && + (runtime->now->addrlen == 0 || sockaddr_cmp( + &runtime->now->addr, runtime->now->addrlen, + &pend->addr, pend->addrlen) == 0) && + find_match(runtime->now->match, pend->pkt, pend->pkt_len, + pend->transport)) { + log_info("testbound: matched pending to event. " + "advance time between events."); + log_info("testbound: do STEP %d %s", runtime->now->time_step, + repevt_string(runtime->now->evt_type)); + advance_moment(runtime); + /* still create the pending, because we need it to callback */ + } + log_info("testbound: created fake pending"); + /* add to list */ + pend->next = runtime->pending_list; + runtime->pending_list = pend; + return (struct waiting_tcp*)pend; +} + +struct serviced_query* outnet_serviced_query(struct outside_network* outnet, + struct query_info* qinfo, uint16_t flags, int dnssec, + int ATTR_UNUSED(want_dnssec), int ATTR_UNUSED(nocaps), + int ATTR_UNUSED(tcp_upstream), int ATTR_UNUSED(ssl_upstream), + char* ATTR_UNUSED(tls_auth_name), struct sockaddr_storage* addr, + socklen_t addrlen, uint8_t* zone, size_t zonelen, + struct module_qstate* qstate, comm_point_callback_type* callback, + void* callback_arg, sldns_buffer* ATTR_UNUSED(buff), + struct module_env* ATTR_UNUSED(env)) +{ + struct replay_runtime* runtime = (struct replay_runtime*)outnet->base; + struct fake_pending* pend = (struct fake_pending*)calloc(1, + sizeof(struct fake_pending)); + char z[256]; + log_assert(pend); + log_nametypeclass(VERB_OPS, "pending serviced query", + qinfo->qname, qinfo->qtype, qinfo->qclass); + dname_str(zone, z); + verbose(VERB_OPS, "pending serviced query zone %s flags%s%s%s%s", + z, (flags&BIT_RD)?" RD":"", (flags&BIT_CD)?" CD":"", + (flags&~(BIT_RD|BIT_CD))?" MORE":"", (dnssec)?" DO":""); + + /* create packet with EDNS */ + pend->buffer = sldns_buffer_new(512); + log_assert(pend->buffer); + sldns_buffer_write_u16(pend->buffer, 0); /* id */ + sldns_buffer_write_u16(pend->buffer, flags); + sldns_buffer_write_u16(pend->buffer, 1); /* qdcount */ + sldns_buffer_write_u16(pend->buffer, 0); /* ancount */ + sldns_buffer_write_u16(pend->buffer, 0); /* nscount */ + sldns_buffer_write_u16(pend->buffer, 0); /* arcount */ + sldns_buffer_write(pend->buffer, qinfo->qname, qinfo->qname_len); + sldns_buffer_write_u16(pend->buffer, qinfo->qtype); + sldns_buffer_write_u16(pend->buffer, qinfo->qclass); + sldns_buffer_flip(pend->buffer); + if(1) { + struct edns_data edns; + if(!inplace_cb_query_call(env, qinfo, flags, addr, addrlen, + zone, zonelen, qstate, qstate->region)) { + free(pend); + return NULL; + } + /* add edns */ + edns.edns_present = 1; + edns.ext_rcode = 0; + edns.edns_version = EDNS_ADVERTISED_VERSION; + edns.udp_size = EDNS_ADVERTISED_SIZE; + edns.bits = 0; + edns.opt_list = qstate->edns_opts_back_out; + if(dnssec) + edns.bits = EDNS_DO; + attach_edns_record(pend->buffer, &edns); + } + memcpy(&pend->addr, addr, addrlen); + pend->addrlen = addrlen; + pend->zone = memdup(zone, zonelen); + pend->zonelen = zonelen; + pend->qtype = (int)qinfo->qtype; + log_assert(pend->zone); + pend->callback = callback; + pend->cb_arg = callback_arg; + pend->timeout = UDP_AUTH_QUERY_TIMEOUT/1000; + pend->transport = transport_udp; /* pretend UDP */ + pend->pkt = NULL; + pend->runtime = runtime; + pend->serviced = 1; + pend->pkt_len = sldns_buffer_limit(pend->buffer); + pend->pkt = memdup(sldns_buffer_begin(pend->buffer), pend->pkt_len); + if(!pend->pkt) fatal_exit("out of memory"); + /*log_pkt("pending serviced query: ", pend->pkt, pend->pkt_len);*/ + + /* see if it matches the current moment */ + if(runtime->now && runtime->now->evt_type == repevt_back_query && + (runtime->now->addrlen == 0 || sockaddr_cmp( + &runtime->now->addr, runtime->now->addrlen, + &pend->addr, pend->addrlen) == 0) && + find_match(runtime->now->match, pend->pkt, pend->pkt_len, + pend->transport)) { + log_info("testbound: matched pending to event. " + "advance time between events."); + log_info("testbound: do STEP %d %s", runtime->now->time_step, + repevt_string(runtime->now->evt_type)); + advance_moment(runtime); + /* still create the pending, because we need it to callback */ + } + log_info("testbound: created fake pending"); + /* add to list */ + pend->next = runtime->pending_list; + runtime->pending_list = pend; + return (struct serviced_query*)pend; +} + +void outnet_serviced_query_stop(struct serviced_query* sq, void* cb_arg) +{ + struct fake_pending* pend = (struct fake_pending*)sq; + struct replay_runtime* runtime = pend->runtime; + /* delete from the list */ + struct fake_pending* p = runtime->pending_list, *prev=NULL; + while(p) { + if(p == pend) { + log_assert(p->cb_arg == cb_arg); + (void)cb_arg; + log_info("serviced pending delete"); + if(prev) + prev->next = p->next; + else runtime->pending_list = p->next; + sldns_buffer_free(p->buffer); + free(p->pkt); + free(p->zone); + free(p); + return; + } + prev = p; + p = p->next; + } + log_info("double delete of pending serviced query"); +} + +struct listen_port* listening_ports_open(struct config_file* ATTR_UNUSED(cfg), + int* ATTR_UNUSED(reuseport)) +{ + return calloc(1, 1); +} + +void listening_ports_free(struct listen_port* list) +{ + free(list); +} + +struct comm_point* comm_point_create_local(struct comm_base* ATTR_UNUSED(base), + int ATTR_UNUSED(fd), size_t ATTR_UNUSED(bufsize), + comm_point_callback_type* ATTR_UNUSED(callback), + void* ATTR_UNUSED(callback_arg)) +{ + struct fake_commpoint* fc = (struct fake_commpoint*)calloc(1, + sizeof(*fc)); + if(!fc) return NULL; + fc->typecode = FAKE_COMMPOINT_TYPECODE; + return (struct comm_point*)fc; +} + +struct comm_point* comm_point_create_raw(struct comm_base* ATTR_UNUSED(base), + int ATTR_UNUSED(fd), int ATTR_UNUSED(writing), + comm_point_callback_type* ATTR_UNUSED(callback), + void* ATTR_UNUSED(callback_arg)) +{ + /* no pipe comm possible */ + struct fake_commpoint* fc = (struct fake_commpoint*)calloc(1, + sizeof(*fc)); + if(!fc) return NULL; + fc->typecode = FAKE_COMMPOINT_TYPECODE; + return (struct comm_point*)fc; +} + +void comm_point_start_listening(struct comm_point* ATTR_UNUSED(c), + int ATTR_UNUSED(newfd), int ATTR_UNUSED(sec)) +{ + /* no bg write pipe comm possible */ +} + +void comm_point_stop_listening(struct comm_point* ATTR_UNUSED(c)) +{ + /* no bg write pipe comm possible */ +} + +/* only cmd com _local gets deleted */ +void comm_point_delete(struct comm_point* c) +{ + struct fake_commpoint* fc = (struct fake_commpoint*)c; + if(c == NULL) return; + log_assert(fc->typecode == FAKE_COMMPOINT_TYPECODE); + if(fc->type_tcp_out) { + /* remove tcp pending, so no more callbacks to it */ + pending_list_delete(fc->runtime, fc->pending); + } + free(c); +} + +size_t listen_get_mem(struct listen_dnsport* ATTR_UNUSED(listen)) +{ + return 0; +} + +size_t outnet_get_mem(struct outside_network* ATTR_UNUSED(outnet)) +{ + return 0; +} + +size_t comm_point_get_mem(struct comm_point* ATTR_UNUSED(c)) +{ + return 0; +} + +size_t serviced_get_mem(struct serviced_query* ATTR_UNUSED(c)) +{ + return 0; +} + +/* fake for fptr wlist */ +int outnet_udp_cb(struct comm_point* ATTR_UNUSED(c), + void* ATTR_UNUSED(arg), int ATTR_UNUSED(error), + struct comm_reply *ATTR_UNUSED(reply_info)) +{ + log_assert(0); + return 0; +} + +int outnet_tcp_cb(struct comm_point* ATTR_UNUSED(c), + void* ATTR_UNUSED(arg), int ATTR_UNUSED(error), + struct comm_reply *ATTR_UNUSED(reply_info)) +{ + log_assert(0); + return 0; +} + +void pending_udp_timer_cb(void *ATTR_UNUSED(arg)) +{ + log_assert(0); +} + +void pending_udp_timer_delay_cb(void *ATTR_UNUSED(arg)) +{ + log_assert(0); +} + +void outnet_tcptimer(void* ATTR_UNUSED(arg)) +{ + log_assert(0); +} + +void comm_point_udp_callback(int ATTR_UNUSED(fd), short ATTR_UNUSED(event), + void* ATTR_UNUSED(arg)) +{ + log_assert(0); +} + +void comm_point_udp_ancil_callback(int ATTR_UNUSED(fd), + short ATTR_UNUSED(event), void* ATTR_UNUSED(arg)) +{ + log_assert(0); +} + +void comm_point_tcp_accept_callback(int ATTR_UNUSED(fd), + short ATTR_UNUSED(event), void* ATTR_UNUSED(arg)) +{ + log_assert(0); +} + +void comm_point_tcp_handle_callback(int ATTR_UNUSED(fd), + short ATTR_UNUSED(event), void* ATTR_UNUSED(arg)) +{ + log_assert(0); +} + +void comm_timer_callback(int ATTR_UNUSED(fd), + short ATTR_UNUSED(event), void* ATTR_UNUSED(arg)) +{ + log_assert(0); +} + +void comm_signal_callback(int ATTR_UNUSED(fd), + short ATTR_UNUSED(event), void* ATTR_UNUSED(arg)) +{ + log_assert(0); +} + +void comm_point_http_handle_callback(int ATTR_UNUSED(fd), + short ATTR_UNUSED(event), void* ATTR_UNUSED(arg)) +{ + log_assert(0); +} + +void comm_point_local_handle_callback(int ATTR_UNUSED(fd), + short ATTR_UNUSED(event), void* ATTR_UNUSED(arg)) +{ + log_assert(0); +} + +void comm_point_raw_handle_callback(int ATTR_UNUSED(fd), + short ATTR_UNUSED(event), void* ATTR_UNUSED(arg)) +{ + log_assert(0); +} + +void comm_base_handle_slow_accept(int ATTR_UNUSED(fd), + short ATTR_UNUSED(event), void* ATTR_UNUSED(arg)) +{ + log_assert(0); +} + +int serviced_udp_callback(struct comm_point* ATTR_UNUSED(c), + void* ATTR_UNUSED(arg), int ATTR_UNUSED(error), + struct comm_reply* ATTR_UNUSED(reply_info)) +{ + log_assert(0); + return 0; +} + +int serviced_tcp_callback(struct comm_point* ATTR_UNUSED(c), + void* ATTR_UNUSED(arg), int ATTR_UNUSED(error), + struct comm_reply* ATTR_UNUSED(reply_info)) +{ + log_assert(0); + return 0; +} + +int pending_cmp(const void* ATTR_UNUSED(a), const void* ATTR_UNUSED(b)) +{ + log_assert(0); + return 0; +} + +int serviced_cmp(const void* ATTR_UNUSED(a), const void* ATTR_UNUSED(b)) +{ + log_assert(0); + return 0; +} + +/* timers in testbound for autotrust. statistics tested in tdir. */ +struct comm_timer* comm_timer_create(struct comm_base* base, + void (*cb)(void*), void* cb_arg) +{ + struct replay_runtime* runtime = (struct replay_runtime*)base; + struct fake_timer* t = (struct fake_timer*)calloc(1, sizeof(*t)); + t->cb = cb; + t->cb_arg = cb_arg; + fptr_ok(fptr_whitelist_comm_timer(t->cb)); /* check in advance */ + t->runtime = runtime; + t->next = runtime->timer_list; + runtime->timer_list = t; + return (struct comm_timer*)t; +} + +void comm_timer_disable(struct comm_timer* timer) +{ + struct fake_timer* t = (struct fake_timer*)timer; + log_info("fake timer disabled"); + t->enabled = 0; +} + +void comm_timer_set(struct comm_timer* timer, struct timeval* tv) +{ + struct fake_timer* t = (struct fake_timer*)timer; + t->enabled = 1; + t->tv = *tv; + log_info("fake timer set %d.%6.6d", + (int)t->tv.tv_sec, (int)t->tv.tv_usec); + timeval_add(&t->tv, &t->runtime->now_tv); +} + +void comm_timer_delete(struct comm_timer* timer) +{ + struct fake_timer* t = (struct fake_timer*)timer; + struct fake_timer** pp, *p; + if(!t) return; + + /* remove from linked list */ + pp = &t->runtime->timer_list; + p = t->runtime->timer_list; + while(p) { + if(p == t) { + /* snip from list */ + *pp = p->next; + break; + } + pp = &p->next; + p = p->next; + } + + free(timer); +} + +void comm_base_set_slow_accept_handlers(struct comm_base* ATTR_UNUSED(b), + void (*stop_acc)(void*), void (*start_acc)(void*), + void* ATTR_UNUSED(arg)) +{ + /* ignore this */ + (void)stop_acc; + (void)start_acc; +} + +struct ub_event_base* comm_base_internal(struct comm_base* ATTR_UNUSED(b)) +{ + /* no pipe comm possible in testbound */ + return NULL; +} + +void daemon_remote_exec(struct worker* ATTR_UNUSED(worker)) +{ +} + +void listen_start_accept(struct listen_dnsport* ATTR_UNUSED(listen)) +{ +} + +void listen_stop_accept(struct listen_dnsport* ATTR_UNUSED(listen)) +{ +} + +void daemon_remote_start_accept(struct daemon_remote* ATTR_UNUSED(rc)) +{ +} + +void daemon_remote_stop_accept(struct daemon_remote* ATTR_UNUSED(rc)) +{ +} + +int create_udp_sock(int ATTR_UNUSED(family), int ATTR_UNUSED(socktype), + struct sockaddr* ATTR_UNUSED(addr), socklen_t ATTR_UNUSED(addrlen), + int ATTR_UNUSED(v6only), int* ATTR_UNUSED(inuse), + int* ATTR_UNUSED(noproto), int ATTR_UNUSED(rcv), int ATTR_UNUSED(snd), + int ATTR_UNUSED(listen), int* ATTR_UNUSED(reuseport), + int ATTR_UNUSED(transparent), int ATTR_UNUSED(freebind), + int ATTR_UNUSED(use_systemd)) +{ + /* if you actually print to this, it'll be stdout during test */ + return 1; +} + +struct comm_point* comm_point_create_udp(struct comm_base *ATTR_UNUSED(base), + int ATTR_UNUSED(fd), sldns_buffer* ATTR_UNUSED(buffer), + comm_point_callback_type* ATTR_UNUSED(callback), + void* ATTR_UNUSED(callback_arg)) +{ + log_assert(0); + return NULL; +} + +struct comm_point* comm_point_create_tcp_out(struct comm_base* + ATTR_UNUSED(base), size_t ATTR_UNUSED(bufsize), + comm_point_callback_type* ATTR_UNUSED(callback), + void* ATTR_UNUSED(callback_arg)) +{ + log_assert(0); + return NULL; +} + +struct comm_point* outnet_comm_point_for_udp(struct outside_network* outnet, + comm_point_callback_type* cb, void* cb_arg, + struct sockaddr_storage* ATTR_UNUSED(to_addr), + socklen_t ATTR_UNUSED(to_addrlen)) +{ + struct replay_runtime* runtime = (struct replay_runtime*) + outnet->base; + struct fake_commpoint* fc = (struct fake_commpoint*)calloc(1, + sizeof(*fc)); + if(!fc) return NULL; + fc->typecode = FAKE_COMMPOINT_TYPECODE; + fc->type_udp_out = 1; + fc->cb = cb; + fc->cb_arg = cb_arg; + fc->runtime = runtime; + /* used by authzone transfers */ + return (struct comm_point*)fc; +} + +struct comm_point* outnet_comm_point_for_tcp(struct outside_network* outnet, + comm_point_callback_type* cb, void* cb_arg, + struct sockaddr_storage* to_addr, socklen_t to_addrlen, + struct sldns_buffer* query, int timeout) +{ + struct replay_runtime* runtime = (struct replay_runtime*) + outnet->base; + struct fake_commpoint* fc = (struct fake_commpoint*)calloc(1, + sizeof(*fc)); + struct fake_pending* pend = (struct fake_pending*)calloc(1, + sizeof(struct fake_pending)); + if(!fc || !pend) { + free(fc); + free(pend); + return NULL; + } + fc->typecode = FAKE_COMMPOINT_TYPECODE; + fc->type_tcp_out = 1; + fc->cb = cb; + fc->cb_arg = cb_arg; + fc->runtime = runtime; + fc->pending = pend; + + /* used by authzone transfers */ + /* create pending item */ + pend->buffer = sldns_buffer_new(sldns_buffer_limit(query)+10); + if(!pend->buffer) { + free(fc); + free(pend); + return NULL; + } + sldns_buffer_copy(pend->buffer, query); + memcpy(&pend->addr, to_addr, to_addrlen); + pend->addrlen = to_addrlen; + pend->zone = NULL; + pend->zonelen = 0; + if(LDNS_QDCOUNT(sldns_buffer_begin(query)) > 0) { + char buf[512]; + char addrbuf[128]; + (void)sldns_wire2str_rrquestion_buf(sldns_buffer_at(query, LDNS_HEADER_SIZE), sldns_buffer_limit(query)-LDNS_HEADER_SIZE, buf, sizeof(buf)); + addr_to_str((struct sockaddr_storage*)to_addr, to_addrlen, + addrbuf, sizeof(addrbuf)); + if(verbosity >= VERB_ALGO) { + if(buf[0] != 0) buf[strlen(buf)-1] = 0; /* del newline*/ + log_info("tcp to %s: %s", addrbuf, buf); + } + log_assert(sldns_buffer_limit(query)-LDNS_HEADER_SIZE >= 2); + pend->qtype = (int)sldns_buffer_read_u16_at(query, + LDNS_HEADER_SIZE+ + dname_valid(sldns_buffer_at(query, LDNS_HEADER_SIZE), + sldns_buffer_limit(query)-LDNS_HEADER_SIZE)); + } + pend->callback = cb; + pend->cb_arg = cb_arg; + pend->timeout = timeout; + pend->transport = transport_tcp; + pend->pkt = NULL; + pend->runtime = runtime; + pend->serviced = 0; + pend->pkt_len = sldns_buffer_limit(pend->buffer); + pend->pkt = memdup(sldns_buffer_begin(pend->buffer), pend->pkt_len); + if(!pend->pkt) fatal_exit("out of memory"); + + log_info("testbound: created fake pending for tcp_out"); + + /* add to list */ + pend->next = runtime->pending_list; + runtime->pending_list = pend; + + return (struct comm_point*)fc; +} + +struct comm_point* outnet_comm_point_for_http(struct outside_network* outnet, + comm_point_callback_type* cb, void* cb_arg, + struct sockaddr_storage* to_addr, socklen_t to_addrlen, int timeout, + int ssl, char* host, char* path) +{ + struct replay_runtime* runtime = (struct replay_runtime*) + outnet->base; + struct fake_commpoint* fc = (struct fake_commpoint*)calloc(1, + sizeof(*fc)); + if(!fc) { + return NULL; + } + fc->typecode = FAKE_COMMPOINT_TYPECODE; + fc->type_http_out = 1; + fc->cb = cb; + fc->cb_arg = cb_arg; + fc->runtime = runtime; + + (void)to_addr; + (void)to_addrlen; + (void)timeout; + + (void)ssl; + (void)host; + (void)path; + + /* handle http comm point and return contents from test script */ + return (struct comm_point*)fc; +} + +int comm_point_send_udp_msg(struct comm_point *c, sldns_buffer* packet, + struct sockaddr* addr, socklen_t addrlen) +{ + struct fake_commpoint* fc = (struct fake_commpoint*)c; + struct replay_runtime* runtime = fc->runtime; + struct fake_pending* pend = (struct fake_pending*)calloc(1, + sizeof(struct fake_pending)); + if(!pend) { + log_err("malloc failure"); + return 0; + } + fc->pending = pend; + /* used by authzone transfers */ + /* create pending item */ + pend->buffer = sldns_buffer_new(sldns_buffer_limit(packet) + 10); + if(!pend->buffer) { + free(pend); + return 0; + } + sldns_buffer_copy(pend->buffer, packet); + memcpy(&pend->addr, addr, addrlen); + pend->addrlen = addrlen; + pend->zone = NULL; + pend->zonelen = 0; + if(LDNS_QDCOUNT(sldns_buffer_begin(packet)) > 0) { + char buf[512]; + char addrbuf[128]; + (void)sldns_wire2str_rrquestion_buf(sldns_buffer_at(packet, LDNS_HEADER_SIZE), sldns_buffer_limit(packet)-LDNS_HEADER_SIZE, buf, sizeof(buf)); + addr_to_str((struct sockaddr_storage*)addr, addrlen, + addrbuf, sizeof(addrbuf)); + if(verbosity >= VERB_ALGO) { + if(buf[0] != 0) buf[strlen(buf)-1] = 0; /* del newline*/ + log_info("udp to %s: %s", addrbuf, buf); + } + log_assert(sldns_buffer_limit(packet)-LDNS_HEADER_SIZE >= 2); + pend->qtype = (int)sldns_buffer_read_u16_at(packet, + LDNS_HEADER_SIZE+ + dname_valid(sldns_buffer_at(packet, LDNS_HEADER_SIZE), + sldns_buffer_limit(packet)-LDNS_HEADER_SIZE)); + } + pend->callback = fc->cb; + pend->cb_arg = fc->cb_arg; + pend->timeout = UDP_AUTH_QUERY_TIMEOUT/1000; + pend->transport = transport_udp; + pend->pkt = NULL; + pend->runtime = runtime; + pend->serviced = 0; + pend->pkt_len = sldns_buffer_limit(pend->buffer); + pend->pkt = memdup(sldns_buffer_begin(pend->buffer), pend->pkt_len); + if(!pend->pkt) fatal_exit("out of memory"); + + log_info("testbound: created fake pending for send_udp_msg"); + + /* add to list */ + pend->next = runtime->pending_list; + runtime->pending_list = pend; + + return 1; +} + +int outnet_get_tcp_fd(struct sockaddr_storage* ATTR_UNUSED(addr), + socklen_t ATTR_UNUSED(addrlen), int ATTR_UNUSED(tcp_mss)) +{ + log_assert(0); + return -1; +} + +int outnet_tcp_connect(int ATTR_UNUSED(s), struct sockaddr_storage* ATTR_UNUSED(addr), + socklen_t ATTR_UNUSED(addrlen)) +{ + log_assert(0); + return 0; +} + +/*********** End of Dummy routines ***********/ diff --git a/usr.sbin/unbound/testcode/fake_event.h b/usr.sbin/unbound/testcode/fake_event.h new file mode 100644 index 00000000000..97ebb41cba0 --- /dev/null +++ b/usr.sbin/unbound/testcode/fake_event.h @@ -0,0 +1,75 @@ +/* + * testcode/fake_event.h - fake event handling that replays existing scenario. + * + * Copyright (c) 2007, NLnet Labs. All rights reserved. + * + * This software is open source. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * Neither the name of the NLNET LABS nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * \file + * Event service that replays a scenario. + * This implements the same exported symbols as the files: + * util/netevent.c + * services/listen_dnsport.c + * services/outside_network.c + * But these do not actually access the network or events, instead + * the scenario is played. + */ + +#ifndef TESTCODE_FAKE_EVENT_H +#define TESTCODE_FAKE_EVENT_H +struct replay_scenario; + +/** + * Initialise fake event services. + * + * The fake event services will automatically start when the main program + * calls netevent.h functions, such as comm_base_dispatch(). + * + * @param scen: Set the scenario to use for upcoming event handling. + */ +void fake_event_init(struct replay_scenario* scen); + +/** + * Deinit fake event services. + */ +void fake_event_cleanup(void); + +/** + * Get filename to store temporary config stuff. The pid is added. in /tmp. + * @param adj: adjective, like "_cfg_", "_auto_" + * @param id: identifier, like "example.com". + * @param buf: where to store. + * @param len: length of buf. + */ +void fake_temp_file(const char* adj, const char* id, char* buf, size_t len); + +#endif /* TESTCODE_FAKE_EVENT_H */ diff --git a/usr.sbin/unbound/testcode/lock_verify.c b/usr.sbin/unbound/testcode/lock_verify.c new file mode 100644 index 00000000000..666a7029d64 --- /dev/null +++ b/usr.sbin/unbound/testcode/lock_verify.c @@ -0,0 +1,426 @@ +/* + * testcode/lock_verify.c - verifier program for lock traces, checks order. + * + * Copyright (c) 2007, NLnet Labs. All rights reserved. + * + * This software is open source. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * Neither the name of the NLNET LABS nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * \file + * + * This file checks the lock traces generated by checklock.c. + * Checks if locks are consistently locked in the same order. + * If not, this can lead to deadlock if threads execute the different + * ordering at the same time. + * + */ + +#include "config.h" +#ifdef HAVE_TIME_H +#include +#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 \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; icount, (int)(time(NULL)-starttime), + errors_detected); + if(errors_detected) return 1; + return 0; +} diff --git a/usr.sbin/unbound/testcode/memstats.c b/usr.sbin/unbound/testcode/memstats.c new file mode 100644 index 00000000000..dc29058ad77 --- /dev/null +++ b/usr.sbin/unbound/testcode/memstats.c @@ -0,0 +1,249 @@ +/* + * testcode/memstats.c - debug tool to show memory allocation statistics. + * + * Copyright (c) 2007, NLnet Labs. All rights reserved. + * + * This software is open source. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * Neither the name of the NLNET LABS nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * \file + * + * This program reads a log file and prints the memory allocation summed + * up. + */ +#include "config.h" +#include "util/log.h" +#include "util/rbtree.h" +#include "util/locks.h" +#include "util/fptr_wlist.h" +#include + +/** + * 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 \n"); + printf("statistics are printed on stdout.\n"); + exit(1); +} + +/** match logfile line to see if it needs accounting processing */ +static int +match(char* line) +{ + /* f.e.: + * [1187340064] unbound[24604:0] info: ul/rb.c:81 r_create malloc(12) + * 0123456789 123456789 123456789 123456789 + * But now also: + * Sep 16 15:18:20 unbound[1:0] info: ul/nh.c:143 memdup malloc(11) + */ + if(strlen(line) < 32) /* up to 'info: ' */ + return 0; + if(!strstr(line, " info: ")) + return 0; + if(strstr(line, "info: stat ")) + return 0; /* skip the hex dumps */ + if(strstr(line+30, "malloc(")) + return 1; + else if(strstr(line+30, "calloc(")) + return 1; + /* skip reallocs */ + return 0; +} + +/** find or alloc codeline in tree */ +static struct codeline* +get_codeline(rbtree_type* tree, char* key, char* func) +{ + struct codeline* cl = (struct codeline*)rbtree_search(tree, key); + if(!cl) { + cl = calloc(1, sizeof(*cl)); + if(!cl) return 0; + cl->codeline = strdup(key); + if(!cl->codeline) return 0; + cl->func = strdup(func); + if(!cl->func) return 0; + cl->alloc = 0; + cl->node.key = cl->codeline; + (void)rbtree_insert(tree, &cl->node); + } + return cl; +} + +/** read up the malloc stats */ +static void +read_malloc_stat(char* line, rbtree_type* tree) +{ + char codeline[10240]; + char name[10240]; + int skip = 0; + long num = 0; + struct codeline* cl = 0; + line = strstr(line, "info: ")+6; + if(sscanf(line, "%s %s %n", codeline, name, &skip) != 2) { + printf("%s\n", line); + fatal_exit("unhandled malloc"); + } + if(sscanf(line+skip+7, "%ld", &num) != 1) { + printf("%s\n%s\n", line, line+skip+7); + fatal_exit("unhandled malloc"); + } + cl = get_codeline(tree, codeline, name); + if(!cl) + fatal_exit("alloc failure"); + cl->alloc += num; + cl->calls ++; +} + +/** read up the calloc stats */ +static void +read_calloc_stat(char* line, rbtree_type* tree) +{ + char codeline[10240]; + char name[10240]; + int skip = 0; + long num = 0, sz = 0; + struct codeline* cl = 0; + line = strstr(line, "info: ")+6; + if(sscanf(line, "%s %s %n", codeline, name, &skip) != 2) { + printf("%s\n", line); + fatal_exit("unhandled calloc"); + } + if(sscanf(line+skip+7, "%ld, %ld", &num, &sz) != 2) { + printf("%s\n%s\n", line, line+skip+7); + fatal_exit("unhandled calloc"); + } + + cl = get_codeline(tree, codeline, name); + if(!cl) + fatal_exit("alloc failure"); + cl->alloc += num*sz; + cl->calls ++; +} + +/** get size of file */ +static off_t +get_file_size(const char* fname) +{ + struct stat s; + if(stat(fname, &s) < 0) { + fatal_exit("could not stat %s: %s", fname, strerror(errno)); + } + return s.st_size; +} + +/** read the logfile */ +static void +readfile(rbtree_type* tree, const char* fname) +{ + off_t total = get_file_size(fname); + off_t done = (off_t)0; + int report = 0; + FILE* in = fopen(fname, "r"); + char buf[102400]; + if(!in) + fatal_exit("could not open %s: %s", fname, strerror(errno)); + printf("Reading %s of size " ARG_LL "d\n", fname, (long long)total); + while(fgets(buf, 102400, in)) { + buf[102400-1] = 0; + done += (off_t)strlen(buf); + /* progress count */ + if((int)(((double)done / (double)total)*100.) > report) { + report = (int)(((double)done / (double)total)*100.); + fprintf(stderr, " %d%%", report); + } + + if(!match(buf)) + continue; + else if(strstr(buf+30, "malloc(")) + read_malloc_stat(buf, tree); + else if(strstr(buf+30, "calloc(")) + read_calloc_stat(buf, tree); + else { + printf("%s\n", buf); + fatal_exit("unhandled input"); + } + } + fprintf(stderr, " done\n"); + fclose(in); +} + +/** print memory stats */ +static void +printstats(rbtree_type* tree) +{ + struct codeline* cl; + uint64_t total = 0, tcalls = 0; + RBTREE_FOR(cl, struct codeline*, tree) { + printf("%12lld / %8lld in %s %s\n", (long long)cl->alloc, + (long long)cl->calls, cl->codeline, cl->func); + total += cl->alloc; + tcalls += cl->calls; + } + printf("------------\n"); + printf("%12lld / %8lld total in %ld code lines\n", (long long)total, + (long long)tcalls, (long)tree->count); + printf("\n"); +} + +/** main program */ +int main(int argc, const char* argv[]) +{ + rbtree_type* tree = 0; + log_init(NULL, 0, 0); + if(argc != 2) { + usage(); + } + tree = rbtree_create(codeline_cmp); + if(!tree) + fatal_exit("alloc failure"); + readfile(tree, argv[1]); + printstats(tree); + return 0; +} diff --git a/usr.sbin/unbound/testcode/mini_tdir.sh b/usr.sbin/unbound/testcode/mini_tdir.sh new file mode 100755 index 00000000000..96745515e3e --- /dev/null +++ b/usr.sbin/unbound/testcode/mini_tdir.sh @@ -0,0 +1,171 @@ +# tdir that only exes the files. +args="../.." +if test "$1" = "-a"; then + args=$2 + shift + shift +fi + +if test "$1" = "clean"; then + echo "rm -f result.* .done* .tdir.var.master .tdir.var.test" + rm -f result.* .done* .tdir.var.master .tdir.var.test + exit 0 +fi +if test "$1" = "fake"; then + echo "minitdir fake $2" + echo "fake" > .done-`basename $2 .tdir` + exit 0 +fi +if test "$1" = "-f" && test "$2" = "report"; then + echo "Minitdir Long Report" + pass=0 + fail=0 + skip=0 + echo " STATUS ELAPSED TESTNAME TESTDESCRIPTION" + for result in *.tdir; do + name=`basename $result .tdir` + timelen=" " + desc="" + if test -f "result.$name"; then + timestart=`grep ^DateRunStart: "result.$name" | sed -e 's/DateRunStart: //'` + timeend=`grep ^DateRunEnd: "result.$name" | sed -e 's/DateRunEnd: //'` + timesec=`expr $timeend - $timestart` + timelen=`printf %4ds $timesec` + if test $? -ne 0; then + timelen="$timesec""s" + fi + desc=`grep ^Description: "result.$name" | sed -e 's/Description: //'` + fi + if test -f ".done-$name"; then + if test "$1" != "-q"; then + echo "** PASSED ** $timelen $name: $desc" + pass=`expr $pass + 1` + fi + else + if test -f "result.$name"; then + echo "!! FAILED !! $timelen $name: $desc" + fail=`expr $fail + 1` + else + echo ".> SKIPPED<< $timelen $name: $desc" + skip=`expr $skip + 1` + fi + fi + done + echo "" + if test "$skip" = "0"; then + echo "$pass pass, $fail fail" + else + echo "$pass pass, $fail fail, $skip skip" + fi + echo "" + exit 0 +fi +if test "$1" = "report" || test "$2" = "report"; then + echo "Minitdir Report" + for result in *.tdir; do + name=`basename $result .tdir` + if test -f ".done-$name"; then + if test "$1" != "-q"; then + echo "** PASSED ** : $name" + fi + else + if test -f "result.$name"; then + echo "!! FAILED !! : $name" + else + echo ">> SKIPPED<< : $name" + fi + fi + done + exit 0 +fi + +if test "$1" != 'exe'; then + # usage + echo "mini tdir. Reduced functionality for old shells." + echo " tdir exe " + echo " tdir fake " + echo " tdir clean" + echo " tdir [-q|-f] report" + exit 1 +fi +shift + +# do not execute if the disk is too full +#DISKLIMIT=100000 +# This check is not portable (to Solaris 10). +#avail=`df . | tail -1 | awk '{print $4}'` +#if test "$avail" -lt "$DISKLIMIT"; then + #echo "minitdir: The disk is too full! Only $avail." + #exit 1 +#fi + +name=`basename $1 .tdir` +dir=$name.$$ +result=result.$name +done=.done-$name +success="no" +if test -x "`which bash`"; then + shell="bash" +else + shell="sh" +fi + +# check already done +if test -f .done-$name; then + echo "minitdir .done-$name exists. skip test." + exit 0 +fi + +# Copy +echo "minitdir copy $1 to $dir" +mkdir $dir +cp -a $name.tdir/* $dir/ +cd $dir + +# EXE +echo "minitdir exe $name" > $result +grep "Description:" $name.dsc >> $result 2>&1 +echo "DateRunStart: "`date "+%s" 2>/dev/null` >> $result +if test -f $name.pre; then + echo "minitdir exe $name.pre" + echo "minitdir exe $name.pre" >> $result + $shell $name.pre $args >> $result + if test $? -ne 0; then + echo "Warning: $name.pre did not exit successfully" + fi +fi +if test -f $name.test; then + echo "minitdir exe $name.test" + echo "minitdir exe $name.test" >> $result + $shell $name.test $args >>$result 2>&1 + if test $? -ne 0; then + echo "$name: FAILED" >> $result + echo "$name: FAILED" + success="no" + else + echo "$name: PASSED" >> $result + echo "$name: PASSED" > ../.done-$name + echo "$name: PASSED" + success="yes" + fi +fi +if test -f $name.post; then + echo "minitdir exe $name.post" + echo "minitdir exe $name.post" >> $result + $shell $name.post $args >> $result + if test $? -ne 0; then + echo "Warning: $name.post did not exit successfully" + fi +fi +echo "DateRunEnd: "`date "+%s" 2>/dev/null` >> $result + +mv $result .. +cd .. +rm -rf $dir +# compat for windows where deletion may not succeed initially (files locked +# by processes that still have to exit). +if test $? -eq 1; then + echo "minitdir waiting for processes to terminate" + sleep 2 # some time to exit, and try again + rm -rf $dir +fi diff --git a/usr.sbin/unbound/testcode/mini_tpkg.sh b/usr.sbin/unbound/testcode/mini_tpkg.sh new file mode 100755 index 00000000000..ebf27a7d48a --- /dev/null +++ b/usr.sbin/unbound/testcode/mini_tpkg.sh @@ -0,0 +1,128 @@ +# tpkg that only exes the files. +args="../.." +if test "$1" = "-a"; then + args=$2 + shift + shift +fi + +if test "$1" = "clean"; then + echo "rm -f result.* .done* .tpkg.var.master .tpkg.var.test" + rm -f result.* .done* .tpkg.var.master .tpkg.var.test + exit 0 +fi +if test "$1" = "fake"; then + echo "minitpkg fake $2" + echo "fake" > .done-`basename $2 .tpkg` + exit 0 +fi +if test "$1" = "report" || test "$2" = "report"; then + echo "Minitpkg Report" + for result in *.tpkg; do + name=`basename $result .tpkg` + if test -f ".done-$name"; then + if test "$1" != "-q"; then + echo "** PASSED ** : $name" + fi + else + if test -f "result.$name"; then + echo "!! FAILED !! : $name" + else + echo ">> SKIPPED<< : $name" + fi + fi + done + exit 0 +fi + +if test "$1" != 'exe'; then + # usage + echo "mini tpkg. Reduced functionality for old shells." + echo " tpkg exe " + echo " tpkg fake " + echo " tpkg clean" + echo " tpkg [-q] report" + exit 1 +fi +shift + +# do not execute if the disk is too full +#DISKLIMIT=100000 +# This check is not portable (to Solaris 10). +#avail=`df . | tail -1 | awk '{print $4}'` +#if test "$avail" -lt "$DISKLIMIT"; then + #echo "minitpkg: The disk is too full! Only $avail." + #exit 1 +#fi + +name=`basename $1 .tpkg` +dir=$name.$$ +result=result.$name +done=.done-$name +success="no" +if test -x "`which bash`"; then + shell="bash" +else + shell="sh" +fi + +# check already done +if test -f .done-$name; then + echo "minitpkg .done-$name exists. skip test." + exit 0 +fi + +# Extract +echo "minitpkg extract $1 to $dir" +mkdir $dir +gzip -cd $name.tpkg | (cd $dir; tar xf -) +cd $dir +mv $name.dir/* . + +# EXE +echo "minitpkg exe $name" > $result +grep "Description:" $name.dsc >> $result 2>&1 +echo "DateRunStart: "`date "+%s" 2>/dev/null` >> $result +if test -f $name.pre; then + echo "minitpkg exe $name.pre" + echo "minitpkg exe $name.pre" >> $result + $shell $name.pre $args >> $result + if test $? -ne 0; then + echo "Warning: $name.pre did not exit successfully" + fi +fi +if test -f $name.test; then + echo "minitpkg exe $name.test" + echo "minitpkg exe $name.test" >> $result + $shell $name.test $args >>$result 2>&1 + if test $? -ne 0; then + echo "$name: FAILED" >> $result + echo "$name: FAILED" + success="no" + else + echo "$name: PASSED" >> $result + echo "$name: PASSED" > ../.done-$name + echo "$name: PASSED" + success="yes" + fi +fi +if test -f $name.post; then + echo "minitpkg exe $name.post" + echo "minitpkg exe $name.post" >> $result + $shell $name.post $args >> $result + if test $? -ne 0; then + echo "Warning: $name.post did not exit successfully" + fi +fi +echo "DateRunEnd: "`date "+%s" 2>/dev/null` >> $result + +mv $result .. +cd .. +rm -rf $dir +# compat for windows where deletion may not succeed initially (files locked +# by processes that still have to exit). +if test $? -eq 1; then + echo "minitpkg waiting for processes to terminate" + sleep 2 # some time to exit, and try again + rm -rf $dir +fi diff --git a/usr.sbin/unbound/testcode/perf.c b/usr.sbin/unbound/testcode/perf.c new file mode 100644 index 00000000000..d11357c4aca --- /dev/null +++ b/usr.sbin/unbound/testcode/perf.c @@ -0,0 +1,654 @@ +/* + * testcode/perf.c - debug program to estimate name server performance. + * + * Copyright (c) 2008, NLnet Labs. All rights reserved. + * + * This software is open source. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * Neither the name of the NLNET LABS nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * \file + * + * This program estimates DNS name server performance. + */ + +#include "config.h" +#ifdef HAVE_GETOPT_H +#include +#endif +#include +#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 + +/** 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; iio_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; iio_num; i++) { +#ifndef USE_WINSOCK + close(info->io[i].fd); +#else + closesocket(info->io[i].fd); +#endif + } + free(info->io); + } + for(i=0; iqlist_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; iio_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 && iio_num; i++) { + if(FD_ISSET(info->io[i].fd, &rset)) { + perfreply(info, i, &now); + num--; + } + } +} + +/** show end stats */ +static void +perfendstats(struct perfinfo* info) +{ + double dt, qps; + struct timeval timeout, now; + int i, lost; + if(gettimeofday(&now, NULL) < 0) + fatal_exit("gettimeofday: %s", strerror(errno)); + timeout = now; + perf_tv_subtract(&timeout, &info->since); + stat_printout(info, &now, &timeout); + + timeout = now; + perf_tv_subtract(&timeout, &info->start); + dt = (double)(timeout.tv_sec*1000000 + timeout.tv_usec) / 1000000.0; + qps = (double)(info->total_recv) / dt; + lost = (int)(info->total_sent - info->total_recv) - (int)info->io_num; + if(!info->quiet) { + printf("overall time: %g sec\n", + (double)timeout.tv_sec + + (double)timeout.tv_usec/1000000.); + if(lost > 0) + printf("Packets lost: %d\n", (int)lost); + + for(i=0; i<(int)(sizeof(info->by_rcode)/sizeof(size_t)); i++) + { + if(info->by_rcode[i] > 0) { + char rc[16]; + sldns_wire2str_rcode_buf(i, rc, sizeof(rc)); + printf("%d(%5s): %u replies\n", + i, rc, (unsigned)info->by_rcode[i]); + } + } + } + printf("average qps: %g\n", qps); +} + +/** perform the performance test */ +static void +perfmain(struct perfinfo* info) +{ + perfsetup(info); + while(!info->exit) { + perfselect(info); + } + perfendstats(info); + perffree(info); +} + +/** parse a query line to a packet into buffer */ +static int +qlist_parse_line(sldns_buffer* buf, char* p) +{ + char nm[1024], cl[1024], tp[1024], fl[1024]; + int r; + int rec = 1, edns = 0; + struct query_info qinfo; + nm[0] = 0; cl[0] = 0; tp[0] = 0; fl[0] = 0; + r = sscanf(p, " %1023s %1023s %1023s %1023s", nm, cl, tp, fl); + if(r != 3 && r != 4) + return 0; + /*printf("nm='%s', cl='%s', tp='%s', fl='%s'\n", nm, cl, tp, fl);*/ + if(strcmp(tp, "IN") == 0 || strcmp(tp, "CH") == 0) { + qinfo.qtype = sldns_get_rr_type_by_name(cl); + qinfo.qclass = sldns_get_rr_class_by_name(tp); + } else { + qinfo.qtype = sldns_get_rr_type_by_name(tp); + qinfo.qclass = sldns_get_rr_class_by_name(cl); + } + if(fl[0] == '+') rec = 1; + else if(fl[0] == '-') rec = 0; + else if(fl[0] == 'E') edns = 1; + if((fl[0] == '+' || fl[0] == '-') && fl[1] == 'E') + edns = 1; + qinfo.qname = sldns_str2wire_dname(nm, &qinfo.qname_len); + if(!qinfo.qname) + return 0; + qinfo.local_alias = NULL; + qinfo_query_encode(buf, &qinfo); + sldns_buffer_write_u16_at(buf, 0, 0); /* zero ID */ + if(rec) LDNS_RD_SET(sldns_buffer_begin(buf)); + if(edns) { + struct edns_data ed; + memset(&ed, 0, sizeof(ed)); + ed.edns_present = 1; + ed.udp_size = EDNS_ADVERTISED_SIZE; + /* Set DO bit in all EDNS datagrams ... */ + ed.bits = EDNS_DO; + attach_edns_record(buf, &ed); + } + free(qinfo.qname); + return 1; +} + +/** grow query list capacity */ +static void +qlist_grow_capacity(struct perfinfo* info) +{ + size_t newcap = (size_t)((info->qlist_capacity==0)?16: + info->qlist_capacity*2); + uint8_t** d = (uint8_t**)calloc(sizeof(uint8_t*), newcap); + size_t* l = (size_t*)calloc(sizeof(size_t), newcap); + if(!d || !l) fatal_exit("out of memory"); + memcpy(d, info->qlist_data, sizeof(uint8_t*)* + info->qlist_capacity); + memcpy(l, info->qlist_len, sizeof(size_t)* + info->qlist_capacity); + free(info->qlist_data); + free(info->qlist_len); + info->qlist_data = d; + info->qlist_len = l; + info->qlist_capacity = newcap; +} + +/** setup query list in info */ +static void +qlist_add_line(struct perfinfo* info, char* line, int no) +{ + if(!qlist_parse_line(info->buf, line)) { + printf("error parsing query %d: %s\n", no, line); + exit(1); + } + sldns_buffer_write_u16_at(info->buf, 0, (uint16_t)info->qlist_size); + if(info->qlist_size + 1 > info->qlist_capacity) { + qlist_grow_capacity(info); + } + info->qlist_len[info->qlist_size] = sldns_buffer_limit(info->buf); + info->qlist_data[info->qlist_size] = memdup( + sldns_buffer_begin(info->buf), sldns_buffer_limit(info->buf)); + if(!info->qlist_data[info->qlist_size]) + fatal_exit("out of memory"); + info->qlist_size ++; +} + +/** setup query list in info */ +static void +qlist_read_file(struct perfinfo* info, char* fname) +{ + char buf[1024]; + char *p; + FILE* in = fopen(fname, "r"); + int lineno = 0; + if(!in) { + perror(fname); + exit(1); + } + while(fgets(buf, (int)sizeof(buf), in)) { + lineno++; + buf[sizeof(buf)-1] = 0; + p = buf; + while(*p == ' ' || *p == '\t') + p++; + if(p[0] == 0 || p[0] == '\n' || p[0] == ';' || p[0] == '#') + continue; + qlist_add_line(info, p, lineno); + } + printf("Read %s, got %u queries\n", fname, (unsigned)info->qlist_size); + fclose(in); +} + +/** getopt global, in case header files fail to declare it. */ +extern int optind; +/** getopt global, in case header files fail to declare it. */ +extern char* optarg; + +/** main program for perf */ +int main(int argc, char* argv[]) +{ + char* nm = argv[0]; + int c; + struct perfinfo info; +#ifdef USE_WINSOCK + int r; + WSADATA wsa_data; +#endif + + /* defaults */ + memset(&info, 0, sizeof(info)); + info.io_num = 16; + + log_init(NULL, 0, NULL); + log_ident_set("perf"); + checklock_start(); +#ifdef USE_WINSOCK + if((r = WSAStartup(MAKEWORD(2,2), &wsa_data)) != 0) + fatal_exit("WSAStartup failed: %s", wsa_strerror(r)); +#endif + + info.buf = sldns_buffer_new(65553); + if(!info.buf) fatal_exit("out of memory"); + + /* parse the options */ + while( (c=getopt(argc, argv, "d:ha:f:q")) != -1) { + switch(c) { + case 'q': + info.quiet = 1; + break; + case 'd': + if(atoi(optarg)==0 && strcmp(optarg, "0")!=0) { + printf("-d not a number %s", optarg); + return 1; + } + info.duration = atoi(optarg); + break; + case 'a': + qlist_add_line(&info, optarg, 0); + break; + case 'f': + qlist_read_file(&info, optarg); + break; + case '?': + case 'h': + default: + usage(nm); + } + } + argc -= optind; + argv += optind; + + if(argc != 1) { + printf("error: pass server IP address on commandline.\n"); + usage(nm); + } + if(!extstrtoaddr(argv[0], &info.dest, &info.destlen)) { + printf("Could not parse ip: %s\n", argv[0]); + return 1; + } + if(info.qlist_size == 0) { + printf("No queries to make, use -f or -a.\n"); + return 1; + } + + /* do the performance test */ + perfmain(&info); + + sldns_buffer_free(info.buf); +#ifdef USE_WINSOCK + WSACleanup(); +#endif + checklock_stop(); + return 0; +} diff --git a/usr.sbin/unbound/testcode/petal.c b/usr.sbin/unbound/testcode/petal.c new file mode 100644 index 00000000000..1c26fa70034 --- /dev/null +++ b/usr.sbin/unbound/testcode/petal.c @@ -0,0 +1,671 @@ +/* + * petal.c - https daemon that is small and beautiful. + * + * Copyright (c) 2010, NLnet Labs. All rights reserved. + * + * This software is open source. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * Neither the name of the NLNET LABS nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * \file + * + * HTTP1.1/SSL server. + */ + +#include "config.h" +#ifdef HAVE_GETOPT_H +#include +#endif +#ifdef HAVE_OPENSSL_SSL_H +#include +#endif +#ifdef HAVE_OPENSSL_ERR_H +#include +#endif +#ifdef HAVE_OPENSSL_RAND_H +#include +#endif +#include +#include +#include +#include +#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= 3) + {printf("chunk len %x\n", (unsigned)red); fflush(stdout);} + at += r; + avail -= r; + if(red != 0) { + if(red > avail) break; /* robust */ + memmove(at, tmpbuf, red); + at += red; + avail -= red; + snprintf(at, avail, "\r\n"); + r = strlen(at); + at += r; + avail -= r; + } + if(in && feof(in) && red != 0) { + snprintf(at, avail, "0\r\n"); + r = strlen(at); + at += r; + avail -= r; + } + if(!in || feof(in)) { + snprintf(at, avail, "\r\n"); + r = strlen(at); + at += r; + avail -= r; + } + /* send chunk */ + if(SSL_write(ssl, buf, at-buf) <= 0) { + /* SSL error */ + break; + } + + /* setup for next chunk */ + at = buf; + avail = sizeof(buf); + } while(in && !feof(in) && !ferror(in)); + + free(tmpbuf); + if(in) fclose(in); +} + +/** provide service to the ssl descriptor */ +static void +service_ssl(SSL* ssl, struct sockaddr_storage* from, socklen_t falen) +{ + char file[1024]; + char host[1024]; + char combined[2048]; + int vs = 11; + if(!read_http_headers(ssl, file, sizeof(file), host, sizeof(host), + &vs)) + return; + adjust_host_file(host, file); + if(host[0] == 0 || !host_name_is_safe(host)) + (void)strlcpy(host, "default", sizeof(host)); + if(!file_name_is_safe(file)) { + return; + } + snprintf(combined, sizeof(combined), "%s%s", host, file); + if(verb) { + char out[100]; + void* a = &((struct sockaddr_in*)from)->sin_addr; + if(falen != (socklen_t)sizeof(struct sockaddr_in)) + a = &((struct sockaddr_in6*)from)->sin6_addr; + out[0]=0; + (void)inet_ntop((int)((struct sockaddr_in*)from)->sin_family, + a, out, (socklen_t)sizeof(out)); + printf("%s requests %s\n", out, combined); + fflush(stdout); + } + if(vs == 10) + provide_file_10(ssl, combined); + else provide_file_chunked(ssl, combined); +} + +/** provide ssl service */ +static void +do_service(char* addr, int port, char* key, char* cert) +{ + SSL_CTX* sslctx = setup_ctx(key, cert); + int fd = setup_fd(addr, port); + int go = 1; + if(fd == -1) print_exit("could not setup sockets"); + if(verb) {printf("petal start\n"); fflush(stdout);} + while(go) { + struct sockaddr_storage from; + socklen_t flen = (socklen_t)sizeof(from); + int s = accept(fd, (struct sockaddr*)&from, &flen); + if(verb) fflush(stdout); + if(s != -1) { + SSL* ssl = setup_ssl(s, sslctx); + if(verb) fflush(stdout); + if(ssl) { + service_ssl(ssl, &from, flen); + if(verb) fflush(stdout); + SSL_shutdown(ssl); + SSL_free(ssl); + } + fd_close(s); + } else if (verb >=2) log_errno("accept"); + if(verb) fflush(stdout); + } + /* if we get a kill signal, the process dies and the OS reaps us */ + if(verb) printf("petal end\n"); + fd_close(fd); + SSL_CTX_free(sslctx); +} + +/** getopt global, in case header files fail to declare it. */ +extern int optind; +/** getopt global, in case header files fail to declare it. */ +extern char* optarg; + +/** Main routine for petal */ +int main(int argc, char* argv[]) +{ + int c; + int port = 443; + char* addr = "127.0.0.1", *key = "petal.key", *cert = "petal.pem"; +#ifdef USE_WINSOCK + WSADATA wsa_data; + if((c=WSAStartup(MAKEWORD(2,2), &wsa_data)) != 0) + { printf("WSAStartup failed\n"); exit(1); } + atexit((void (*)(void))WSACleanup); +#endif + + /* parse the options */ + while( (c=getopt(argc, argv, "a:c:k:hp:v")) != -1) { + switch(c) { + case 'a': + addr = optarg; + break; + case 'c': + cert = optarg; + break; + case 'k': + key = optarg; + break; + case 'p': + port = atoi(optarg); + break; + case 'v': + verb++; + break; + case '?': + case 'h': + default: + usage(); + } + } + argc -= optind; + argv += optind; + if(argc != 0) + usage(); + +#ifdef SIGPIPE + (void)signal(SIGPIPE, SIG_IGN); +#endif +#ifdef HAVE_ERR_LOAD_CRYPTO_STRINGS + ERR_load_crypto_strings(); +#endif +#if OPENSSL_VERSION_NUMBER < 0x10100000 || !defined(HAVE_OPENSSL_INIT_SSL) + ERR_load_SSL_strings(); +#endif +#if OPENSSL_VERSION_NUMBER < 0x10100000 || !defined(HAVE_OPENSSL_INIT_CRYPTO) + OpenSSL_add_all_algorithms(); +#else + OPENSSL_init_crypto(OPENSSL_INIT_ADD_ALL_CIPHERS + | OPENSSL_INIT_ADD_ALL_DIGESTS + | OPENSSL_INIT_LOAD_CRYPTO_STRINGS, NULL); +#endif +#if OPENSSL_VERSION_NUMBER < 0x10100000 || !defined(HAVE_OPENSSL_INIT_SSL) + (void)SSL_library_init(); +#else + (void)OPENSSL_init_ssl(OPENSSL_INIT_LOAD_SSL_STRINGS, NULL); +#endif + + do_service(addr, port, key, cert); + +#ifdef HAVE_CRYPTO_CLEANUP_ALL_EX_DATA + CRYPTO_cleanup_all_ex_data(); +#endif +#ifdef HAVE_ERR_FREE_STRINGS + ERR_free_strings(); +#endif + return 0; +} diff --git a/usr.sbin/unbound/testcode/pktview.c b/usr.sbin/unbound/testcode/pktview.c new file mode 100644 index 00000000000..12e0d8edbb6 --- /dev/null +++ b/usr.sbin/unbound/testcode/pktview.c @@ -0,0 +1,202 @@ +/* + * testcode/pktview.c - debug program to disassemble a DNS packet. + * + * Copyright (c) 2007, NLnet Labs. All rights reserved. + * + * This software is open source. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * Neither the name of the NLNET LABS nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * \file + * + * This program shows a dns packet wire format. + */ + +#include "config.h" +#include "util/log.h" +#include "util/data/dname.h" +#include "util/data/msgparse.h" +#include "testcode/unitmain.h" +#include "testcode/readhex.h" +#include "sldns/sbuffer.h" +#include "sldns/parseutil.h" + +/** usage information for pktview */ +static void usage(char* argv[]) +{ + printf("usage: %s\n", argv[0]); + printf("present hex packet on stdin.\n"); + exit(1); +} + +/** read hex input */ +static void read_input(sldns_buffer* pkt, FILE* in) +{ + char buf[102400]; + char* np = buf; + while(fgets(np, (int)sizeof(buf) - (np-buf), in)) { + if(buf[0] == ';') /* comment */ + continue; + np = &np[strlen(np)]; + } + hex_to_buf(pkt, buf); +} + +/** analyze domain name in packet, possibly compressed */ +static void analyze_dname(sldns_buffer* pkt) +{ + size_t oldpos = sldns_buffer_position(pkt); + size_t len; + printf("[pos %d] dname: ", (int)oldpos); + dname_print(stdout, pkt, sldns_buffer_current(pkt)); + len = pkt_dname_len(pkt); + printf(" len=%d", (int)len); + if(sldns_buffer_position(pkt)-oldpos != len) + printf(" comprlen=%d\n", + (int)(sldns_buffer_position(pkt)-oldpos)); + else printf("\n"); +} + +/** analyze rdata in packet */ +static void analyze_rdata(sldns_buffer*pkt, const sldns_rr_descriptor* desc, + uint16_t rdlen) +{ + int rdf = 0; + int count = (int)desc->_dname_count; + size_t len, oldpos; + while(rdlen > 0 && count) { + switch(desc->_wireformat[rdf]) { + case LDNS_RDF_TYPE_DNAME: + oldpos = sldns_buffer_position(pkt); + analyze_dname(pkt); + rdlen -= sldns_buffer_position(pkt)-oldpos; + count --; + len = 0; + break; + case LDNS_RDF_TYPE_STR: + len = sldns_buffer_current(pkt)[0] + 1; + break; + default: + len = get_rdf_size(desc->_wireformat[rdf]); + } + if(len) { + printf(" wf[%d]", (int)len); + sldns_buffer_skip(pkt, (ssize_t)len); + rdlen -= len; + } + rdf++; + } + if(rdlen) { + size_t i; + printf(" remain[%d]\n", (int)rdlen); + for(i=0; i_name: "??" , (int)type); + printf(" class %s(%d) ", sldns_lookup_by_id(sldns_rr_classes, + (int)dclass)?sldns_lookup_by_id(sldns_rr_classes, + (int)dclass)->name:"??", (int)dclass); + if(q) { + printf("\n"); + } else { + ttl = sldns_buffer_read_u32(pkt); + printf(" ttl %d (0x%x)", (int)ttl, (unsigned)ttl); + len = sldns_buffer_read_u16(pkt); + printf(" rdata len %d:\n", (int)len); + if(sldns_rr_descript(type)) + analyze_rdata(pkt, sldns_rr_descript(type), len); + else sldns_buffer_skip(pkt, (ssize_t)len); + } +} + +/** analyse pkt */ +static void analyze(sldns_buffer* pkt) +{ + uint16_t i, f, qd, an, ns, ar; + int rrnum = 0; + printf("packet length %d\n", (int)sldns_buffer_limit(pkt)); + if(sldns_buffer_limit(pkt) < 12) return; + + i = sldns_buffer_read_u16(pkt); + printf("id (hostorder): %d (0x%x)\n", (int)i, (unsigned)i); + f = sldns_buffer_read_u16(pkt); + printf("flags: 0x%x\n", (unsigned)f); + qd = sldns_buffer_read_u16(pkt); + printf("qdcount: %d\n", (int)qd); + an = sldns_buffer_read_u16(pkt); + printf("ancount: %d\n", (int)an); + ns = sldns_buffer_read_u16(pkt); + printf("nscount: %d\n", (int)ns); + ar = sldns_buffer_read_u16(pkt); + printf("arcount: %d\n", (int)ar); + + printf(";-- query section\n"); + while(sldns_buffer_remaining(pkt) > 0) { + if(rrnum == (int)qd) + printf(";-- answer section\n"); + if(rrnum == (int)qd+(int)an) + printf(";-- authority section\n"); + if(rrnum == (int)qd+(int)an+(int)ns) + printf(";-- additional section\n"); + printf("rr %d ", rrnum); + analyze_rr(pkt, rrnum < (int)qd); + rrnum++; + } +} + +/** main program for pktview */ +int main(int argc, char* argv[]) +{ + sldns_buffer* pkt = sldns_buffer_new(65553); + if(argc != 1) { + usage(argv); + } + if(!pkt) fatal_exit("out of memory"); + + read_input(pkt, stdin); + analyze(pkt); + + sldns_buffer_free(pkt); + return 0; +} diff --git a/usr.sbin/unbound/testcode/readhex.c b/usr.sbin/unbound/testcode/readhex.c new file mode 100644 index 00000000000..e871def0ec4 --- /dev/null +++ b/usr.sbin/unbound/testcode/readhex.c @@ -0,0 +1,85 @@ +/* + * testcode/readhex.c - read hex data. + * + * Copyright (c) 2007, NLnet Labs. All rights reserved. + * + * This software is open source. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * Neither the name of the NLNET LABS nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ +/** + * \file + * Declarations useful for the unit tests. + */ +#include "config.h" +#include +#include "testcode/readhex.h" +#include "util/log.h" +#include "sldns/sbuffer.h" +#include "sldns/parseutil.h" + +/** skip whitespace */ +static void +skip_whites(const char** p) +{ + while(1) { + while(isspace((unsigned char)**p)) + (*p)++; + if(**p == ';') { + /* comment, skip until newline */ + while(**p && **p != '\n') + (*p)++; + if(**p == '\n') + (*p)++; + } else return; + } +} + +/* takes a hex string and puts into buffer */ +void hex_to_buf(sldns_buffer* pkt, const char* hex) +{ + const char* p = hex; + int val; + sldns_buffer_clear(pkt); + while(*p) { + skip_whites(&p); + if(sldns_buffer_position(pkt) == sldns_buffer_limit(pkt)) + fatal_exit("hex_to_buf: buffer too small"); + if(!isalnum((unsigned char)*p)) + break; + val = sldns_hexdigit_to_int(*p++) << 4; + skip_whites(&p); + log_assert(*p && isalnum((unsigned char)*p)); + val |= sldns_hexdigit_to_int(*p++); + sldns_buffer_write_u8(pkt, (uint8_t)val); + skip_whites(&p); + } + sldns_buffer_flip(pkt); +} + diff --git a/usr.sbin/unbound/testcode/readhex.h b/usr.sbin/unbound/testcode/readhex.h new file mode 100644 index 00000000000..be6424539e3 --- /dev/null +++ b/usr.sbin/unbound/testcode/readhex.h @@ -0,0 +1,52 @@ +/* + * testcode/readhex.h - read hex data. + * + * Copyright (c) 2007, NLnet Labs. All rights reserved. + * + * This software is open source. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * Neither the name of the NLNET LABS nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ +/** + * \file + * Declarations useful for the unit tests. + */ + +#ifndef TESTCODE_READHEX_H +#define TESTCODE_READHEX_H +struct sldns_buffer; + +/** + * Helper to convert hex string to packet buffer. + * @param pkt: buffer to put result in. + * @param hex: string of hex data. Spaces and ';...' comments are skipped. + */ +void hex_to_buf(struct sldns_buffer* pkt, const char* hex); + +#endif /* TESTCODE_READHEX_H */ diff --git a/usr.sbin/unbound/testcode/replay.c b/usr.sbin/unbound/testcode/replay.c new file mode 100644 index 00000000000..08d87470bd0 --- /dev/null +++ b/usr.sbin/unbound/testcode/replay.c @@ -0,0 +1,1044 @@ +/* + * testcode/replay.c - store and use a replay of events for the DNS resolver. + * + * Copyright (c) 2007, NLnet Labs. All rights reserved. + * + * This software is open source. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * Neither the name of the NLNET LABS nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * \file + * Store and use a replay of events for the DNS resolver. + * Used to test known scenarios to get known outcomes. + */ + +#include "config.h" +/* for strtod prototype */ +#include +#include +#include +#include "util/log.h" +#include "util/net_help.h" +#include "util/config_file.h" +#include "testcode/replay.h" +#include "testcode/testpkts.h" +#include "testcode/fake_event.h" +#include "sldns/str2wire.h" + +/** max length of lines in file */ +#define MAX_LINE_LEN 10240 + +/** + * Expand a macro + * @param store: value storage + * @param runtime: replay runtime for other stuff. + * @param text: the macro text, after the ${, Updated to after the } when + * done (successfully). + * @return expanded text, malloced. NULL on failure. + */ +static char* macro_expand(rbtree_type* store, + struct replay_runtime* runtime, char** text); + +/** compare of time values */ +static int +timeval_smaller(const struct timeval* x, const struct timeval* y) +{ +#ifndef S_SPLINT_S + if(x->tv_sec < y->tv_sec) + return 1; + else if(x->tv_sec == y->tv_sec) { + if(x->tv_usec <= y->tv_usec) + return 1; + else return 0; + } + else return 0; +#endif +} + +/** parse keyword in string. + * @param line: if found, the line is advanced to after the keyword. + * @param keyword: string. + * @return: true if found, false if not. + */ +static int +parse_keyword(char** line, const char* keyword) +{ + size_t len = (size_t)strlen(keyword); + if(strncmp(*line, keyword, len) == 0) { + *line += len; + return 1; + } + return 0; +} + +/** delete moment */ +static void +replay_moment_delete(struct replay_moment* mom) +{ + if(!mom) + return; + if(mom->match) { + delete_entry(mom->match); + } + free(mom->autotrust_id); + free(mom->string); + free(mom->variable); + config_delstrlist(mom->file_content); + free(mom); +} + +/** delete range */ +static void +replay_range_delete(struct replay_range* rng) +{ + if(!rng) + return; + delete_entry(rng->match); + free(rng); +} + +/** strip whitespace from end of string */ +static void +strip_end_white(char* p) +{ + size_t i; + for(i = strlen(p); i > 0; i--) { + if(isspace((unsigned char)p[i-1])) + p[i-1] = 0; + else return; + } +} + +/** + * Read a range from file. + * @param remain: Rest of line (after RANGE keyword). + * @param in: file to read from. + * @param name: name to print in errors. + * @param pstate: read state structure with + * with lineno : incremented as lines are read. + * ttl, origin, prev for readentry. + * @param line: line buffer. + * @return: range object to add to list, or NULL on error. + */ +static struct replay_range* +replay_range_read(char* remain, FILE* in, const char* name, + struct sldns_file_parse_state* pstate, char* line) +{ + struct replay_range* rng = (struct replay_range*)malloc( + sizeof(struct replay_range)); + off_t pos; + char *parse; + struct entry* entry, *last = NULL; + if(!rng) + return NULL; + memset(rng, 0, sizeof(*rng)); + /* read time range */ + if(sscanf(remain, " %d %d", &rng->start_step, &rng->end_step)!=2) { + log_err("Could not read time range: %s", line); + free(rng); + return NULL; + } + /* read entries */ + pos = ftello(in); + while(fgets(line, MAX_LINE_LEN-1, in)) { + pstate->lineno++; + parse = line; + while(isspace((unsigned char)*parse)) + parse++; + if(!*parse || *parse == ';') { + pos = ftello(in); + continue; + } + if(parse_keyword(&parse, "ADDRESS")) { + while(isspace((unsigned char)*parse)) + parse++; + strip_end_white(parse); + if(!extstrtoaddr(parse, &rng->addr, &rng->addrlen)) { + log_err("Line %d: could not read ADDRESS: %s", + pstate->lineno, parse); + free(rng); + return NULL; + } + pos = ftello(in); + continue; + } + if(parse_keyword(&parse, "RANGE_END")) { + return rng; + } + /* set position before line; read entry */ + pstate->lineno--; + fseeko(in, pos, SEEK_SET); + entry = read_entry(in, name, pstate, 1); + if(!entry) + fatal_exit("%d: bad entry", pstate->lineno); + entry->next = NULL; + if(last) + last->next = entry; + else rng->match = entry; + last = entry; + + pos = ftello(in); + } + replay_range_delete(rng); + return NULL; +} + +/** Read FILE match content */ +static void +read_file_content(FILE* in, int* lineno, struct replay_moment* mom) +{ + char line[MAX_LINE_LEN]; + char* remain = line; + struct config_strlist** last = &mom->file_content; + line[MAX_LINE_LEN-1]=0; + if(!fgets(line, MAX_LINE_LEN-1, in)) + fatal_exit("FILE_BEGIN expected at line %d", *lineno); + if(!parse_keyword(&remain, "FILE_BEGIN")) + fatal_exit("FILE_BEGIN expected at line %d", *lineno); + while(fgets(line, MAX_LINE_LEN-1, in)) { + (*lineno)++; + if(strncmp(line, "FILE_END", 8) == 0) { + return; + } + if(line[0]) line[strlen(line)-1] = 0; /* remove newline */ + if(!cfg_strlist_insert(last, strdup(line))) + fatal_exit("malloc failure"); + last = &( (*last)->next ); + } + fatal_exit("no FILE_END in input file"); +} + +/** read assign step info */ +static void +read_assign_step(char* remain, struct replay_moment* mom) +{ + char buf[1024]; + char eq; + int skip; + buf[sizeof(buf)-1]=0; + if(sscanf(remain, " %1023s %c %n", buf, &eq, &skip) != 2) + fatal_exit("cannot parse assign: %s", remain); + mom->variable = strdup(buf); + if(eq != '=') + fatal_exit("no '=' in assign: %s", remain); + remain += skip; + if(remain[0]) remain[strlen(remain)-1]=0; /* remove newline */ + mom->string = strdup(remain); + if(!mom->variable || !mom->string) + fatal_exit("out of memory"); +} + +/** + * Read a replay moment 'STEP' from file. + * @param remain: Rest of line (after STEP keyword). + * @param in: file to read from. + * @param name: name to print in errors. + * @param pstate: with lineno, ttl, origin, prev for parse state. + * lineno is incremented. + * @return: range object to add to list, or NULL on error. + */ +static struct replay_moment* +replay_moment_read(char* remain, FILE* in, const char* name, + struct sldns_file_parse_state* pstate) +{ + struct replay_moment* mom = (struct replay_moment*)malloc( + sizeof(struct replay_moment)); + int skip = 0; + int readentry = 0; + if(!mom) + return NULL; + memset(mom, 0, sizeof(*mom)); + if(sscanf(remain, " %d%n", &mom->time_step, &skip) != 1) { + log_err("%d: cannot read number: %s", pstate->lineno, remain); + free(mom); + return NULL; + } + remain += skip; + while(isspace((unsigned char)*remain)) + remain++; + if(parse_keyword(&remain, "NOTHING")) { + mom->evt_type = repevt_nothing; + } else if(parse_keyword(&remain, "QUERY")) { + mom->evt_type = repevt_front_query; + readentry = 1; + if(!extstrtoaddr("127.0.0.1", &mom->addr, &mom->addrlen)) + fatal_exit("internal error"); + } else if(parse_keyword(&remain, "CHECK_ANSWER")) { + mom->evt_type = repevt_front_reply; + readentry = 1; + } else if(parse_keyword(&remain, "CHECK_OUT_QUERY")) { + mom->evt_type = repevt_back_query; + readentry = 1; + } else if(parse_keyword(&remain, "REPLY")) { + mom->evt_type = repevt_back_reply; + readentry = 1; + } else if(parse_keyword(&remain, "TIMEOUT")) { + mom->evt_type = repevt_timeout; + } else if(parse_keyword(&remain, "TIME_PASSES")) { + mom->evt_type = repevt_time_passes; + while(isspace((unsigned char)*remain)) + remain++; + if(parse_keyword(&remain, "EVAL")) { + while(isspace((unsigned char)*remain)) + remain++; + mom->string = strdup(remain); + if(!mom->string) fatal_exit("out of memory"); + if(strlen(mom->string)>0) + mom->string[strlen(mom->string)-1]=0; + remain += strlen(mom->string); + } + } else if(parse_keyword(&remain, "CHECK_AUTOTRUST")) { + mom->evt_type = repevt_autotrust_check; + while(isspace((unsigned char)*remain)) + remain++; + if(strlen(remain)>0 && remain[strlen(remain)-1]=='\n') + remain[strlen(remain)-1] = 0; + mom->autotrust_id = strdup(remain); + if(!mom->autotrust_id) fatal_exit("out of memory"); + read_file_content(in, &pstate->lineno, mom); + } else if(parse_keyword(&remain, "CHECK_TEMPFILE")) { + mom->evt_type = repevt_tempfile_check; + while(isspace((unsigned char)*remain)) + remain++; + if(strlen(remain)>0 && remain[strlen(remain)-1]=='\n') + remain[strlen(remain)-1] = 0; + mom->autotrust_id = strdup(remain); + if(!mom->autotrust_id) fatal_exit("out of memory"); + read_file_content(in, &pstate->lineno, mom); + } else if(parse_keyword(&remain, "ERROR")) { + mom->evt_type = repevt_error; + } else if(parse_keyword(&remain, "TRAFFIC")) { + mom->evt_type = repevt_traffic; + } else if(parse_keyword(&remain, "ASSIGN")) { + mom->evt_type = repevt_assign; + read_assign_step(remain, mom); + } else if(parse_keyword(&remain, "INFRA_RTT")) { + char *s, *m; + mom->evt_type = repevt_infra_rtt; + while(isspace((unsigned char)*remain)) + remain++; + s = remain; + remain = strchr(s, ' '); + if(!remain) fatal_exit("expected three args for INFRA_RTT"); + remain[0] = 0; + remain++; + while(isspace((unsigned char)*remain)) + remain++; + m = strchr(remain, ' '); + if(!m) fatal_exit("expected three args for INFRA_RTT"); + m[0] = 0; + m++; + while(isspace((unsigned char)*m)) + m++; + if(!extstrtoaddr(s, &mom->addr, &mom->addrlen)) + fatal_exit("bad infra_rtt address %s", s); + if(strlen(m)>0 && m[strlen(m)-1]=='\n') + m[strlen(m)-1] = 0; + mom->variable = strdup(remain); + mom->string = strdup(m); + if(!mom->string) fatal_exit("out of memory"); + if(!mom->variable) fatal_exit("out of memory"); + } else { + log_err("%d: unknown event type %s", pstate->lineno, remain); + free(mom); + return NULL; + } + while(isspace((unsigned char)*remain)) + remain++; + if(parse_keyword(&remain, "ADDRESS")) { + while(isspace((unsigned char)*remain)) + remain++; + if(strlen(remain) > 0) /* remove \n */ + remain[strlen(remain)-1] = 0; + if(!extstrtoaddr(remain, &mom->addr, &mom->addrlen)) { + log_err("line %d: could not parse ADDRESS: %s", + pstate->lineno, remain); + free(mom); + return NULL; + } + } + if(parse_keyword(&remain, "ELAPSE")) { + double sec; + errno = 0; + sec = strtod(remain, &remain); + if(sec == 0. && errno != 0) { + log_err("line %d: could not parse ELAPSE: %s (%s)", + pstate->lineno, remain, strerror(errno)); + free(mom); + return NULL; + } +#ifndef S_SPLINT_S + mom->elapse.tv_sec = (int)sec; + mom->elapse.tv_usec = (int)((sec - (double)mom->elapse.tv_sec) + *1000000. + 0.5); +#endif + } + + if(readentry) { + mom->match = read_entry(in, name, pstate, 1); + if(!mom->match) { + free(mom); + return NULL; + } + } + + return mom; +} + +/** makes scenario with title on rest of line */ +static struct replay_scenario* +make_scenario(char* line) +{ + struct replay_scenario* scen; + while(isspace((unsigned char)*line)) + line++; + if(!*line) { + log_err("scenario: no title given"); + return NULL; + } + scen = (struct replay_scenario*)malloc(sizeof(struct replay_scenario)); + if(!scen) + return NULL; + memset(scen, 0, sizeof(*scen)); + scen->title = strdup(line); + if(!scen->title) { + free(scen); + return NULL; + } + return scen; +} + +struct replay_scenario* +replay_scenario_read(FILE* in, const char* name, int* lineno) +{ + char line[MAX_LINE_LEN]; + char *parse; + struct replay_scenario* scen = NULL; + struct sldns_file_parse_state pstate; + line[MAX_LINE_LEN-1]=0; + memset(&pstate, 0, sizeof(pstate)); + pstate.default_ttl = 3600; + pstate.lineno = *lineno; + + while(fgets(line, MAX_LINE_LEN-1, in)) { + parse=line; + pstate.lineno++; + (*lineno)++; + while(isspace((unsigned char)*parse)) + parse++; + if(!*parse) + continue; /* empty line */ + if(parse_keyword(&parse, ";")) + continue; /* comment */ + if(parse_keyword(&parse, "SCENARIO_BEGIN")) { + scen = make_scenario(parse); + if(!scen) + fatal_exit("%d: could not make scen", *lineno); + continue; + } + if(!scen) + fatal_exit("%d: expected SCENARIO", *lineno); + if(parse_keyword(&parse, "RANGE_BEGIN")) { + struct replay_range* newr = replay_range_read(parse, + in, name, &pstate, line); + if(!newr) + fatal_exit("%d: bad range", pstate.lineno); + *lineno = pstate.lineno; + newr->next_range = scen->range_list; + scen->range_list = newr; + } else if(parse_keyword(&parse, "STEP")) { + struct replay_moment* mom = replay_moment_read(parse, + in, name, &pstate); + if(!mom) + fatal_exit("%d: bad moment", pstate.lineno); + *lineno = pstate.lineno; + if(scen->mom_last && + scen->mom_last->time_step >= mom->time_step) + fatal_exit("%d: time goes backwards", *lineno); + if(scen->mom_last) + scen->mom_last->mom_next = mom; + else scen->mom_first = mom; + scen->mom_last = mom; + } else if(parse_keyword(&parse, "SCENARIO_END")) { + struct replay_moment *p = scen->mom_first; + int num = 0; + while(p) { + num++; + p = p->mom_next; + } + log_info("Scenario has %d steps", num); + return scen; + } + } + log_err("scenario read failed at line %d (no SCENARIO_END?)", *lineno); + replay_scenario_delete(scen); + return NULL; +} + +void +replay_scenario_delete(struct replay_scenario* scen) +{ + struct replay_moment* mom, *momn; + struct replay_range* rng, *rngn; + if(!scen) + return; + free(scen->title); + mom = scen->mom_first; + while(mom) { + momn = mom->mom_next; + replay_moment_delete(mom); + mom = momn; + } + rng = scen->range_list; + while(rng) { + rngn = rng->next_range; + replay_range_delete(rng); + rng = rngn; + } + free(scen); +} + +/** fetch oldest timer in list that is enabled */ +static struct fake_timer* +first_timer(struct replay_runtime* runtime) +{ + struct fake_timer* p, *res = NULL; + for(p=runtime->timer_list; p; p=p->next) { + if(!p->enabled) + continue; + if(!res) + res = p; + else if(timeval_smaller(&p->tv, &res->tv)) + res = p; + } + return res; +} + +struct fake_timer* +replay_get_oldest_timer(struct replay_runtime* runtime) +{ + struct fake_timer* t = first_timer(runtime); + if(t && timeval_smaller(&t->tv, &runtime->now_tv)) + return t; + return NULL; +} + +int +replay_var_compare(const void* a, const void* b) +{ + struct replay_var* x = (struct replay_var*)a; + struct replay_var* y = (struct replay_var*)b; + return strcmp(x->name, y->name); +} + +rbtree_type* +macro_store_create(void) +{ + return rbtree_create(&replay_var_compare); +} + +/** helper function to delete macro values */ +static void +del_macro(rbnode_type* x, void* ATTR_UNUSED(arg)) +{ + struct replay_var* v = (struct replay_var*)x; + free(v->name); + free(v->value); + free(v); +} + +void +macro_store_delete(rbtree_type* store) +{ + if(!store) + return; + traverse_postorder(store, del_macro, NULL); + free(store); +} + +/** return length of macro */ +static size_t +macro_length(char* text) +{ + /* we are after ${, looking for } */ + int depth = 0; + size_t len = 0; + while(*text) { + len++; + if(*text == '}') { + if(depth == 0) + break; + depth--; + } else if(text[0] == '$' && text[1] == '{') { + depth++; + } + text++; + } + return len; +} + +/** insert new stuff at start of buffer */ +static int +do_buf_insert(char* buf, size_t remain, char* after, char* inserted) +{ + char* save = strdup(after); + size_t len; + if(!save) return 0; + if(strlen(inserted) > remain) { + free(save); + return 0; + } + len = strlcpy(buf, inserted, remain); + buf += len; + remain -= len; + (void)strlcpy(buf, save, remain); + free(save); + return 1; +} + +/** do macro recursion */ +static char* +do_macro_recursion(rbtree_type* store, struct replay_runtime* runtime, + char* at, size_t remain) +{ + char* after = at+2; + char* expand = macro_expand(store, runtime, &after); + if(!expand) + return NULL; /* expansion failed */ + if(!do_buf_insert(at, remain, after, expand)) { + free(expand); + return NULL; + } + free(expand); + return at; /* and parse over the expanded text to see if again */ +} + +/** get var from store */ +static struct replay_var* +macro_getvar(rbtree_type* store, char* name) +{ + struct replay_var k; + k.node.key = &k; + k.name = name; + return (struct replay_var*)rbtree_search(store, &k); +} + +/** do macro variable */ +static char* +do_macro_variable(rbtree_type* store, char* buf, size_t remain) +{ + struct replay_var* v; + char* at = buf+1; + char* name = at; + char sv; + if(at[0]==0) + return NULL; /* no variable name after $ */ + while(*at && (isalnum((unsigned char)*at) || *at=='_')) { + at++; + } + /* terminator, we are working in macro_expand() buffer */ + sv = *at; + *at = 0; + v = macro_getvar(store, name); + *at = sv; + + if(!v) { + log_err("variable is not defined: $%s", name); + return NULL; /* variable undefined is error for now */ + } + + /* insert the variable contents */ + if(!do_buf_insert(buf, remain, at, v->value)) + return NULL; + return buf; /* and expand the variable contents */ +} + +/** do ctime macro on argument */ +static char* +do_macro_ctime(char* arg) +{ + char buf[32]; + time_t tt = (time_t)atoi(arg); + if(tt == 0 && strcmp(arg, "0") != 0) { + log_err("macro ctime: expected number, not: %s", arg); + return NULL; + } + ctime_r(&tt, buf); + if(buf[0]) buf[strlen(buf)-1]=0; /* remove trailing newline */ + return strdup(buf); +} + +/** perform arithmetic operator */ +static double +perform_arith(double x, char op, double y, double* res) +{ + switch(op) { + case '+': + *res = x+y; + break; + case '-': + *res = x-y; + break; + case '/': + *res = x/y; + break; + case '*': + *res = x*y; + break; + default: + return 0; + } + + return 1; +} + +/** do macro arithmetic on two numbers and operand */ +static char* +do_macro_arith(char* orig, size_t remain, char** arithstart) +{ + double x, y, result; + char operator; + int skip; + char buf[32]; + char* at; + /* not yet done? we want number operand number expanded first. */ + if(!*arithstart) { + /* remember start pos of expr, skip the first number */ + at = orig; + *arithstart = at; + while(*at && (isdigit((unsigned char)*at) || *at == '.')) + at++; + return at; + } + /* move back to start */ + remain += (size_t)(orig - *arithstart); + at = *arithstart; + + /* parse operands */ + if(sscanf(at, " %lf %c %lf%n", &x, &operator, &y, &skip) != 3) { + *arithstart = NULL; + return do_macro_arith(orig, remain, arithstart); + } + if(isdigit((unsigned char)operator)) { + *arithstart = orig; + return at+skip; /* do nothing, but setup for later number */ + } + + /* calculate result */ + if(!perform_arith(x, operator, y, &result)) { + log_err("unknown operator: %s", at); + return NULL; + } + + /* put result back in buffer */ + snprintf(buf, sizeof(buf), "%.12g", result); + if(!do_buf_insert(at, remain, at+skip, buf)) + return NULL; + + /* the result can be part of another expression, restart that */ + *arithstart = NULL; + return at; +} + +/** Do range macro on expanded buffer */ +static char* +do_macro_range(char* buf) +{ + double x, y, z; + if(sscanf(buf, " %lf %lf %lf", &x, &y, &z) != 3) { + log_err("range func requires 3 args: %s", buf); + return NULL; + } + if(x <= y && y <= z) { + char res[1024]; + snprintf(res, sizeof(res), "%.24g", y); + return strdup(res); + } + fatal_exit("value %.24g not in range [%.24g, %.24g]", y, x, z); + return NULL; +} + +static char* +macro_expand(rbtree_type* store, struct replay_runtime* runtime, char** text) +{ + char buf[10240]; + char* at = *text; + size_t len = macro_length(at); + int dofunc = 0; + char* arithstart = NULL; + if(len >= sizeof(buf)) + return NULL; /* too long */ + buf[0] = 0; + (void)strlcpy(buf, at, len+1-1); /* do not copy last '}' character */ + at = buf; + + /* check for functions */ + if(strcmp(buf, "time") == 0) { + snprintf(buf, sizeof(buf), ARG_LL "d", (long long)runtime->now_secs); + *text += len; + return strdup(buf); + } else if(strcmp(buf, "timeout") == 0) { + time_t res = 0; + struct fake_timer* t = first_timer(runtime); + if(t && (time_t)t->tv.tv_sec >= runtime->now_secs) + res = (time_t)t->tv.tv_sec - runtime->now_secs; + snprintf(buf, sizeof(buf), ARG_LL "d", (long long)res); + *text += len; + return strdup(buf); + } else if(strncmp(buf, "ctime ", 6) == 0 || + strncmp(buf, "ctime\t", 6) == 0) { + at += 6; + dofunc = 1; + } else if(strncmp(buf, "range ", 6) == 0 || + strncmp(buf, "range\t", 6) == 0) { + at += 6; + dofunc = 1; + } + + /* actual macro text expansion */ + while(*at) { + size_t remain = sizeof(buf)-strlen(buf); + if(strncmp(at, "${", 2) == 0) { + at = do_macro_recursion(store, runtime, at, remain); + } else if(*at == '$') { + at = do_macro_variable(store, at, remain); + } else if(isdigit((unsigned char)*at)) { + at = do_macro_arith(at, remain, &arithstart); + } else { + /* copy until whitespace or operator */ + if(*at && (isalnum((unsigned char)*at) || *at=='_')) { + at++; + while(*at && (isalnum((unsigned char)*at) || *at=='_')) + at++; + } else at++; + } + if(!at) return NULL; /* failure */ + } + *text += len; + if(dofunc) { + /* post process functions, buf has the argument(s) */ + if(strncmp(buf, "ctime", 5) == 0) { + return do_macro_ctime(buf+6); + } else if(strncmp(buf, "range", 5) == 0) { + return do_macro_range(buf+6); + } + } + return strdup(buf); +} + +char* +macro_process(rbtree_type* store, struct replay_runtime* runtime, char* text) +{ + char buf[10240]; + char* next, *expand; + char* at = text; + if(!strstr(text, "${")) + return strdup(text); /* no macros */ + buf[0] = 0; + buf[sizeof(buf)-1]=0; + while( (next=strstr(at, "${")) ) { + /* copy text before next macro */ + if((size_t)(next-at) >= sizeof(buf)-strlen(buf)) + return NULL; /* string too long */ + (void)strlcpy(buf+strlen(buf), at, (size_t)(next-at+1)); + /* process the macro itself */ + next += 2; + expand = macro_expand(store, runtime, &next); + if(!expand) return NULL; /* expansion failed */ + (void)strlcpy(buf+strlen(buf), expand, sizeof(buf)-strlen(buf)); + free(expand); + at = next; + } + /* copy remainder fixed text */ + (void)strlcpy(buf+strlen(buf), at, sizeof(buf)-strlen(buf)); + return strdup(buf); +} + +char* +macro_lookup(rbtree_type* store, char* name) +{ + struct replay_var* x = macro_getvar(store, name); + if(!x) return strdup(""); + return strdup(x->value); +} + +void macro_print_debug(rbtree_type* store) +{ + struct replay_var* x; + RBTREE_FOR(x, struct replay_var*, store) { + log_info("%s = %s", x->name, x->value); + } +} + +int +macro_assign(rbtree_type* store, char* name, char* value) +{ + struct replay_var* x = macro_getvar(store, name); + if(x) { + free(x->value); + } else { + x = (struct replay_var*)malloc(sizeof(*x)); + if(!x) return 0; + x->node.key = x; + x->name = strdup(name); + if(!x->name) { + free(x); + return 0; + } + (void)rbtree_insert(store, &x->node); + } + x->value = strdup(value); + return x->value != NULL; +} + +/* testbound assert function for selftest. counts the number of tests */ +#define tb_assert(x) \ + do { if(!(x)) fatal_exit("%s:%d: %s: assertion %s failed", \ + __FILE__, __LINE__, __func__, #x); \ + num_asserts++; \ + } while(0); + +void testbound_selftest(void) +{ + /* test the macro store */ + rbtree_type* store = macro_store_create(); + char* v; + int r; + int num_asserts = 0; + tb_assert(store); + + v = macro_lookup(store, "bla"); + tb_assert(strcmp(v, "") == 0); + free(v); + + v = macro_lookup(store, "vlerk"); + tb_assert(strcmp(v, "") == 0); + free(v); + + r = macro_assign(store, "bla", "waarde1"); + tb_assert(r); + + v = macro_lookup(store, "vlerk"); + tb_assert(strcmp(v, "") == 0); + free(v); + + v = macro_lookup(store, "bla"); + tb_assert(strcmp(v, "waarde1") == 0); + free(v); + + r = macro_assign(store, "vlerk", "kanteel"); + tb_assert(r); + + v = macro_lookup(store, "bla"); + tb_assert(strcmp(v, "waarde1") == 0); + free(v); + + v = macro_lookup(store, "vlerk"); + tb_assert(strcmp(v, "kanteel") == 0); + free(v); + + r = macro_assign(store, "bla", "ww"); + tb_assert(r); + + v = macro_lookup(store, "bla"); + tb_assert(strcmp(v, "ww") == 0); + free(v); + + tb_assert( macro_length("}") == 1); + tb_assert( macro_length("blabla}") == 7); + tb_assert( macro_length("bla${zoink}bla}") == 7+8); + tb_assert( macro_length("bla${zoink}${bla}bla}") == 7+8+6); + + v = macro_process(store, NULL, ""); + tb_assert( v && strcmp(v, "") == 0); + free(v); + + v = macro_process(store, NULL, "${}"); + tb_assert( v && strcmp(v, "") == 0); + free(v); + + v = macro_process(store, NULL, "blabla ${} dinges"); + tb_assert( v && strcmp(v, "blabla dinges") == 0); + free(v); + + v = macro_process(store, NULL, "1${$bla}2${$bla}3"); + tb_assert( v && strcmp(v, "1ww2ww3") == 0); + free(v); + + v = macro_process(store, NULL, "it is ${ctime 123456}"); + tb_assert( v && strcmp(v, "it is Fri Jan 2 10:17:36 1970") == 0); + free(v); + + r = macro_assign(store, "t1", "123456"); + tb_assert(r); + v = macro_process(store, NULL, "it is ${ctime ${$t1}}"); + tb_assert( v && strcmp(v, "it is Fri Jan 2 10:17:36 1970") == 0); + free(v); + + v = macro_process(store, NULL, "it is ${ctime $t1}"); + tb_assert( v && strcmp(v, "it is Fri Jan 2 10:17:36 1970") == 0); + free(v); + + r = macro_assign(store, "x", "1"); + tb_assert(r); + r = macro_assign(store, "y", "2"); + tb_assert(r); + v = macro_process(store, NULL, "${$x + $x}"); + tb_assert( v && strcmp(v, "2") == 0); + free(v); + v = macro_process(store, NULL, "${$x - $x}"); + tb_assert( v && strcmp(v, "0") == 0); + free(v); + v = macro_process(store, NULL, "${$y * $y}"); + tb_assert( v && strcmp(v, "4") == 0); + free(v); + v = macro_process(store, NULL, "${32 / $y + $x + $y}"); + tb_assert( v && strcmp(v, "19") == 0); + free(v); + + v = macro_process(store, NULL, "${32 / ${$y+$y} + ${${100*3}/3}}"); + tb_assert( v && strcmp(v, "108") == 0); + free(v); + + v = macro_process(store, NULL, "${1 2 33 2 1}"); + tb_assert( v && strcmp(v, "1 2 33 2 1") == 0); + free(v); + + v = macro_process(store, NULL, "${123 3 + 5}"); + tb_assert( v && strcmp(v, "123 8") == 0); + free(v); + + v = macro_process(store, NULL, "${123 glug 3 + 5}"); + tb_assert( v && strcmp(v, "123 glug 8") == 0); + free(v); + + macro_store_delete(store); + printf("selftest successful (%d checks).\n", num_asserts); +} diff --git a/usr.sbin/unbound/testcode/replay.h b/usr.sbin/unbound/testcode/replay.h new file mode 100644 index 00000000000..81f0a2c275b --- /dev/null +++ b/usr.sbin/unbound/testcode/replay.h @@ -0,0 +1,471 @@ +/* + * testcode/replay.h - store and use a replay of events for the DNS resolver. + * + * Copyright (c) 2007, NLnet Labs. All rights reserved. + * + * This software is open source. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * Neither the name of the NLNET LABS nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * \file + * Store and use a replay of events for the DNS resolver. + * Used to test known scenarios to get known outcomes. + * + *
+ * 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
+ * 
+ * 
+ */ + +#ifndef TESTCODE_REPLAY_H +#define TESTCODE_REPLAY_H +#include "util/netevent.h" +#include "testcode/testpkts.h" +#include "util/rbtree.h" +struct replay_answer; +struct replay_moment; +struct replay_range; +struct fake_pending; +struct fake_timer; +struct replay_var; +struct infra_cache; +struct sldns_buffer; + +/** + * A replay scenario. + */ +struct replay_scenario { + /** name of replay scenario. malloced string. */ + char* title; + + /** The list of replay moments. Linked list. Time increases in list. */ + struct replay_moment* mom_first; + /** The last element in list of replay moments. */ + struct replay_moment* mom_last; + + /** + * List of matching answers. This is to ease replay scenario + * creation. It lists queries (to the network) and what answer + * should be returned. The matching answers are valid for a range + * of time steps. + * So: timestep, parts of query, destination --> answer. + */ + struct replay_range* range_list; +}; + +/** + * A replay moment. + * Basically, it consists of events to a fake select() call. + * This is a recording of an event that happens. + * And if output is presented, what is done with that. + */ +struct replay_moment { + /** + * The replay time step number. Starts at 0, time is incremented + * every time the fake select() is run. + */ + int time_step; + /** Next replay moment in list of replay moments. */ + struct replay_moment* mom_next; + + /** what happens this moment? */ + enum replay_event_type { + /** nothing happens, as if this event is not there. */ + repevt_nothing, + /** incoming query */ + repevt_front_query, + /** test fails if reply to query does not match */ + repevt_front_reply, + /** timeout */ + repevt_timeout, + /** time passes */ + repevt_time_passes, + /** reply arrives from the network */ + repevt_back_reply, + /** test fails if query to the network does not match */ + repevt_back_query, + /** check autotrust key file */ + repevt_autotrust_check, + /** check a temp file */ + repevt_tempfile_check, + /** an error happens to outbound query */ + repevt_error, + /** assignment to a variable */ + repevt_assign, + /** store infra rtt cache entry: addr and string (int) */ + repevt_infra_rtt, + /** cause traffic to flow */ + repevt_traffic + } + /** variable with what is to happen this moment */ + evt_type; + + /** The sent packet must match this. Incoming events, the data. */ + struct entry* match; + + /** the amount of time that passes */ + struct timeval elapse; + + /** address that must be matched, or packet remote host address. */ + struct sockaddr_storage addr; + /** length of addr, if 0, then any address will do */ + socklen_t addrlen; + + /** macro name, for assign. */ + char* variable; + /** string argument, for assign. */ + char* string; + + /** the autotrust file id to check */ + char* autotrust_id; + /** file contents to match, one string per line */ + struct config_strlist* file_content; +}; + +/** + * Range of timesteps, and canned replies to matching queries. + */ +struct replay_range { + /** time range when this is valid. Including start and end step. */ + int start_step; + /** end step of time range. */ + int end_step; + /** address of where this range is served. */ + struct sockaddr_storage addr; + /** length of addr, if 0, then any address will do */ + socklen_t addrlen; + + /** Matching list */ + struct entry* match; + + /** next in list of time ranges. */ + struct replay_range* next_range; +}; + +/** + * Replay storage of runtime information. + */ +struct replay_runtime { + /** + * The scenario + */ + struct replay_scenario* scenario; + /** + * Current moment. + */ + struct replay_moment* now; + + /** + * List of pending queries in order they were sent out. First + * one has been sent out most recently. Last one in list is oldest. + */ + struct fake_pending* pending_list; + + /** + * List of answers to queries from clients. These need to be checked. + */ + struct replay_answer* answer_list; + + /** last element in answer list. */ + struct replay_answer* answer_last; + + /** list of fake timer callbacks that are pending */ + struct fake_timer* timer_list; + + /** callback to call for incoming queries */ + comm_point_callback_type* callback_query; + /** user argument for incoming query callback */ + void *cb_arg; + + /** ref the infra cache (was passed to outside_network_create) */ + struct infra_cache* infra; + + /** the current time in seconds */ + time_t now_secs; + /** the current time in microseconds */ + struct timeval now_tv; + + /** signal handler callback */ + void (*sig_cb)(int, void*); + /** signal handler user arg */ + void *sig_cb_arg; + /** time to exit cleanly */ + int exit_cleanly; + + /** size of buffers */ + size_t bufsize; + + /** + * Tree of macro values. Of type replay_var + */ + rbtree_type* vars; +}; + +/** + * Pending queries to network, fake replay version. + */ +struct fake_pending { + /** what is important only that we remember the query, copied here. */ + struct sldns_buffer* buffer; + /** and to what address this is sent to. */ + struct sockaddr_storage addr; + /** len of addr */ + socklen_t addrlen; + /** zone name, uncompressed wire format (as used when sent) */ + uint8_t* zone; + /** length of zone name */ + size_t zonelen; + /** qtype */ + int qtype; + /** The callback function to call when answer arrives (or timeout) */ + comm_point_callback_type* callback; + /** callback user argument */ + void* cb_arg; + /** original timeout in seconds from 'then' */ + int timeout; + + /** next in pending list */ + struct fake_pending* next; + /** the buffer parsed into a sldns_pkt */ + uint8_t* pkt; + size_t pkt_len; + /** by what transport was the query sent out */ + enum transport_type transport; + /** if this is a serviced query */ + int serviced; + /** if we are handling a multi pkt tcp stream, non 0 and the pkt nr*/ + int tcp_pkt_counter; + /** the runtime structure this is part of */ + struct replay_runtime* runtime; +}; + +/** + * An answer that is pending to happen. + */ +struct replay_answer { + /** Next in list */ + struct replay_answer* next; + /** reply information */ + struct comm_reply repinfo; + /** the answer preparsed as ldns pkt */ + uint8_t* pkt; + size_t pkt_len; +}; + +/** + * Timers with callbacks, fake replay version. + */ +struct fake_timer { + /** next in list */ + struct fake_timer* next; + /** the runtime structure this is part of */ + struct replay_runtime* runtime; + /** the callback to call */ + void (*cb)(void*); + /** the callback user argument */ + void* cb_arg; + /** if timer is enabled */ + int enabled; + /** when the timer expires */ + struct timeval tv; +}; + +/** + * Replay macro variable. And its value. + */ +struct replay_var { + /** rbtree node. Key is this structure. Sorted by name. */ + rbnode_type node; + /** the variable name */ + char* name; + /** the variable value */ + char* value; +}; + +/** + * Read a replay scenario from the file. + * @param in: file to read from. + * @param name: name to print in errors. + * @param lineno: incremented for every line read. + * @return: Scenario. NULL if no scenario read. + */ +struct replay_scenario* replay_scenario_read(FILE* in, const char* name, + int* lineno); + +/** + * Delete scenario. + * @param scen: to delete. + */ +void replay_scenario_delete(struct replay_scenario* scen); + +/** compare two replay_vars */ +int replay_var_compare(const void* a, const void* b); + +/** get oldest enabled fake timer */ +struct fake_timer* replay_get_oldest_timer(struct replay_runtime* runtime); + +/** + * Create variable storage + * @return new or NULL on failure. + */ +rbtree_type* macro_store_create(void); + +/** + * Delete variable storage + * @param store: the macro storage to free up. + */ +void macro_store_delete(rbtree_type* store); + +/** + * Apply macro substitution to string. + * @param store: variable store. + * @param runtime: the runtime to look up values as needed. + * @param text: string to work on. + * @return newly malloced string with result. + */ +char* macro_process(rbtree_type* store, struct replay_runtime* runtime, + char* text); + +/** + * Look up a macro value. Like calling ${$name}. + * @param store: variable store + * @param name: macro name + * @return newly malloced string with result or strdup("") if not found. + * or NULL on malloc failure. + */ +char* macro_lookup(rbtree_type* store, char* name); + +/** + * Set macro value. + * @param store: variable store + * @param name: macro name + * @param value: text to set it to. Not expanded. + * @return false on failure. + */ +int macro_assign(rbtree_type* store, char* name, char* value); + +/** Print macro variables stored as debug info */ +void macro_print_debug(rbtree_type* store); + +/** testbounds self test */ +void testbound_selftest(void); + +#endif /* TESTCODE_REPLAY_H */ diff --git a/usr.sbin/unbound/testcode/run_vm.sh b/usr.sbin/unbound/testcode/run_vm.sh new file mode 100644 index 00000000000..d4c2a2e11dd --- /dev/null +++ b/usr.sbin/unbound/testcode/run_vm.sh @@ -0,0 +1,76 @@ +#!/usr/local/bin/bash +# run tdir tests from within a VM. Looks for loopback addr. +# if run not from within a VM, runs the tests as usual. +# with one argument: run that tdir, otherwise, run all tdirs. + +get_lo0_ip4() { + if test -x /sbin/ifconfig + then + LO0_IP4=`/sbin/ifconfig lo0 | grep '[^0-9]127\.' | sed -e 's/^[^1]*\(127\.[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\)[^0-9]*.*$/\1/g'` + if ( echo $LO0_IP4 | grep '^127\.[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*$' > /dev/null ) + then + return + fi + fi + LO0_IP4=127.0.0.1 +} +get_lo0_ip4 +export LO0_IP4 +if test "x$LO0_IP4" = "x127.0.0.1" +then + ALT_LOOPBACK=false +else + ALT_LOOPBACK=true +fi +cd testdata +TPKG=../testcode/mini_tdir.sh +#RUNLIST=`(ls -1d *.tdir|grep -v '^0[016]')` +RUNLIST=`(ls -1d *.tdir)` +if test "$#" = "1"; then RUNLIST="$1"; fi + +# fix up tdir that was edited on keyboard interrupt. +cleanup() { + echo cleanup + if test -f "$t.bak"; then mv "$t.bak" "$t"; fi + exit 0 +} +trap cleanup SIGINT + +for t in $RUNLIST +do + if ! $ALT_LOOPBACK + then + $TPKG exe $t + continue + fi + # We have alternative 127.0.0.1 number + if ( echo $t | grep '6\.tdir$' ) # skip IPv6 tests + then + continue + elif test "$t" = "edns_cache.tdir" # This one is IPv6 too! + then + continue + fi + cp -ap "$t" "$t.bak" + find "${t}" -type f \ + -exec grep -q -e '127\.0\.0\.1' -e '@localhost' {} \; -print | { + while read f + do + sed "s/127\.0\.0\.1/${LO0_IP4}/g" "$f" > "$f._" + mv "$f._" "$f" + sed "s/@localhost/@${LO0_IP4}/g" "$f" > "$f._" + mv "$f._" "$f" + done + } + find "${t}" -type d -name "127.0.0.1" -print | { + while read d + do + mv -v "$d" "${d%127.0.0.1}${LO0_IP4}" + done + } + $TPKG exe $t + rm -fr "${t}" + mv "$t.bak" "$t" +done +# get out of testdata/ +cd .. diff --git a/usr.sbin/unbound/testcode/signit.c b/usr.sbin/unbound/testcode/signit.c new file mode 100644 index 00000000000..0eca0e088ee --- /dev/null +++ b/usr.sbin/unbound/testcode/signit.c @@ -0,0 +1,284 @@ +/* + * testcode/signit.c - debug tool to sign rrsets with given keys. + * + * Copyright (c) 2007, NLnet Labs. All rights reserved. + * + * This software is open source. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * Neither the name of the NLNET LABS nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * \file + * + * This program signs rrsets with the given keys. It can be used to + * construct input to test the validator with. + */ +#include "config.h" +#include +#include + +#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; iexpi); + ldns_key_set_inception(k, set->incep); + s = ldns_str2rdf_dname(&rdf, set->owner); + if(s != LDNS_STATUS_OK) + fatal_exit("bad owner name %s: %s", set->owner, + ldns_get_errorstr_by_id(s)); + ldns_key_set_pubkey_owner(k, rdf); + ldns_key_set_flags(k, set->flags); + ldns_key_set_keytag(k, set->keytag); + b = ldns_key_list_push_key(keys, k); + assert(b); + } + return keys; +} + +/** read list of rrs from the file */ +static ldns_rr_list* +read_rrs(FILE* in) +{ + uint32_t my_ttl = 3600; + ldns_rdf *my_origin = NULL; + ldns_rdf *my_prev = NULL; + ldns_status s; + int line_nr = 1; + int b; + + ldns_rr_list* list; + ldns_rr *rr; + + list = ldns_rr_list_new(); + if(!list) fatal_exit("alloc error"); + + while(!feof(in)) { + s = ldns_rr_new_frm_fp_l(&rr, in, &my_ttl, &my_origin, + &my_prev, &line_nr); + if(s == LDNS_STATUS_SYNTAX_TTL || + s == LDNS_STATUS_SYNTAX_ORIGIN || + s == LDNS_STATUS_SYNTAX_EMPTY) + continue; + else if(s != LDNS_STATUS_OK) + fatal_exit("parse error in line %d: %s", line_nr, + ldns_get_errorstr_by_id(s)); + b = ldns_rr_list_push_rr(list, rr); + assert(b); + } + printf("read %d lines\n", line_nr); + + return list; +} + +/** sign the rrs with the keys */ +static void +signit(ldns_rr_list* rrs, ldns_key_list* keys) +{ + ldns_rr_list* rrset; + ldns_rr_list* sigs; + + while(ldns_rr_list_rr_count(rrs) > 0) { + rrset = ldns_rr_list_pop_rrset(rrs); + if(!rrset) fatal_exit("copy alloc failure"); + sigs = ldns_sign_public(rrset, keys); + if(!sigs) fatal_exit("failed to sign"); + ldns_rr_list_print(stdout, rrset); + ldns_rr_list_print(stdout, sigs); + printf("\n"); + ldns_rr_list_free(rrset); + ldns_rr_list_free(sigs); + } +} + +/** process keys and signit */ +static void +process_keys(int argc, char* argv[]) +{ + ldns_rr_list* rrs; + ldns_key_list* keys; + struct keysets settings; + assert(argc == 6); + + parse_cmdline(argv, &settings); + keys = read_keys(1, argv+5, &settings); + rrs = read_rrs(stdin); + signit(rrs, keys); + + ldns_rr_list_deep_free(rrs); + ldns_key_list_free(keys); +} + +/** process nsec3 params and perform hashing */ +static void +process_nsec3(int argc, char* argv[]) +{ + char line[10240]; + ldns_rdf* salt; + ldns_rdf* in, *out; + ldns_status status; + status = ldns_str2rdf_nsec3_salt(&salt, argv[5]); + if(status != LDNS_STATUS_OK) + fatal_exit("Could not parse salt %s: %s", argv[5], + ldns_get_errorstr_by_id(status)); + assert(argc == 6); + while(fgets(line, (int)sizeof(line), stdin)) { + if(strlen(line) > 0) + line[strlen(line)-1] = 0; /* remove trailing newline */ + if(line[0]==0) + continue; + status = ldns_str2rdf_dname(&in, line); + if(status != LDNS_STATUS_OK) + fatal_exit("Could not parse name %s: %s", line, + ldns_get_errorstr_by_id(status)); + ldns_rdf_print(stdout, in); + printf(" -> "); + /* arg 3 is flags, unused */ + out = ldns_nsec3_hash_name(in, (uint8_t)atoi(argv[2]), + (uint16_t)atoi(argv[4]), + ldns_rdf_data(salt)[0], ldns_rdf_data(salt)+1); + if(!out) + fatal_exit("Could not hash %s", line); + ldns_rdf_print(stdout, out); + printf("\n"); + ldns_rdf_deep_free(in); + ldns_rdf_deep_free(out); + } + ldns_rdf_deep_free(salt); +} + +/** main program */ +int main(int argc, char* argv[]) +{ + if(argc != 6) { + usage(); + } + if(strcmp(argv[1], "NSEC3PARAM") == 0) { + process_nsec3(argc, argv); + return 0; + } + process_keys(argc, argv); + return 0; +} diff --git a/usr.sbin/unbound/testcode/streamtcp.1 b/usr.sbin/unbound/testcode/streamtcp.1 new file mode 100644 index 00000000000..7c738d9d278 --- /dev/null +++ b/usr.sbin/unbound/testcode/streamtcp.1 @@ -0,0 +1,66 @@ +.TH "unbound\-streamtcp" "1" "Mar 21, 2013" "NLnet Labs" "unbound" +.\" +.\" unbound-streamtcp.1 -- unbound DNS lookup utility +.\" +.SH "NAME" +.LP +.B unbound\-streamtcp +\- unbound DNS lookup utility +.SH "SYNOPSIS" +.LP +.B unbound\-streamtcp +.RB [ \-unsh ] +.RB [ \-f +.IR ipaddr[@port] ] +.I name +.I type +.I class +.SH "DESCRIPTION" +.LP +.B unbound\-streamtcp +sends a DNS Query of the given \fBtype\fR and \fBclass\fR for the given \fBname\fR +to the DNS server over TCP and displays the response. +.P +If the server to query is not given using the \fB\-f\fR option then localhost +(127.0.0.1) is used. More queries can be given on one commandline, they +are resolved in sequence. +.P +The available options are: +.TP +.I name +This name is resolved (looked up in the DNS). +.TP +.I type +Specify the type of data to lookup. +.TP +.I class +Specify the class to lookup for. +.TP +.B \-u +Use UDP instead of TCP. No retries are attempted. +.TP +.B \-n +Do not wait for the answer. +.TP +.B \-s +Use SSL. +.TP +.B \-h +Print program usage. +.TP +.B \-f \fIipaddr[@port] +Specify the server to send the queries to. If not specified localhost (127.0.0.1) is used. +.SH "EXAMPLES" +.LP +Some examples of use. +.P +$ unbound\-streamtcp www.example.com A IN +.P +$ unbound\-streamtcp \-f 192.168.1.1 www.example.com SOA IN +.P +$ unbound\-streamtcp \-f 192.168.1.1@1234 153.1.168.192.in\-addr.arpa. PTR IN +.SH "EXIT CODE" +The unbound\-streamtcp program exits with status code 1 on error, +0 on no error. +.SH "AUTHOR" +This manual page was written by Tomas Hozza . diff --git a/usr.sbin/unbound/testcode/streamtcp.c b/usr.sbin/unbound/testcode/streamtcp.c new file mode 100644 index 00000000000..0a636395fd3 --- /dev/null +++ b/usr.sbin/unbound/testcode/streamtcp.c @@ -0,0 +1,435 @@ +/* + * testcode/streamtcp.c - debug program perform multiple DNS queries on tcp. + * + * Copyright (c) 2008, NLnet Labs. All rights reserved. + * + * This software is open source. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * Neither the name of the NLNET LABS nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * \file + * + * This program performs multiple DNS queries on a TCP stream. + */ + +#include "config.h" +#ifdef HAVE_GETOPT_H +#include +#endif +#include +#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 +#include +#include + +#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 +#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 + +/** 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; ititle); + 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 +#include +#include +#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= '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_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 pslen && strcmp(qs + (qslen-pslen), ps) == 0 && + qslen + 2 >= pslen && /* space for label and dot */ + qs[qslen-pslen-1] == '.') { + unsigned int slashcount = 0; + size_t i = qslen-pslen-2; + while(i>0 && qs[i]=='\\') { + i++; + slashcount++; + } + if(slashcount%1 == 1) return 0; /* . preceded by \ */ + return 1; + } + return 0; +} + +/** Match OPT RDATA (not the EDNS payload size or flags) */ +static int +match_ednsdata(uint8_t* q, size_t qlen, uint8_t* p, size_t plen) +{ + uint8_t* walk_q = q; + size_t walk_qlen = qlen; + uint8_t* walk_p = p; + size_t walk_plen = plen; + + if(!pkt_find_edns_opt(&walk_q, &walk_qlen)) + walk_qlen = 0; + if(!pkt_find_edns_opt(&walk_p, &walk_plen)) + walk_plen = 0; + + /* class + ttl + rdlen = 8 */ + if(walk_qlen <= 8 && walk_plen <= 8) { + verbose(3, "NO edns opt, move on"); + return 1; + } + if(walk_qlen != walk_plen) + return 0; + + return (memcmp(walk_p+8, walk_q+8, walk_qlen-8) == 0); +} + +/* finds entry in list, or returns NULL */ +struct entry* +find_match(struct entry* entries, uint8_t* query_pkt, size_t len, + enum transport_type transport) +{ + struct entry* p = entries; + uint8_t* reply; + size_t rlen; + for(p=entries; p; p=p->next) { + verbose(3, "comparepkt: "); + reply = p->reply_list->reply_pkt; + rlen = p->reply_list->reply_len; + if(p->match_opcode && get_opcode(query_pkt, len) != + get_opcode(reply, rlen)) { + verbose(3, "bad opcode\n"); + continue; + } + if(p->match_qtype && get_qtype(query_pkt, len) != + get_qtype(reply, rlen)) { + verbose(3, "bad qtype %d %d\n", get_qtype(query_pkt, len), get_qtype(reply, rlen)); + continue; + } + if(p->match_qname) { + if(!equal_dname(query_pkt, len, reply, rlen)) { + verbose(3, "bad qname\n"); + continue; + } + } + if(p->match_rcode) { + if(get_rcode(query_pkt, len) != get_rcode(reply, rlen)) { + char *r1 = sldns_wire2str_rcode(get_rcode(query_pkt, len)); + char *r2 = sldns_wire2str_rcode(get_rcode(reply, rlen)); + verbose(3, "bad rcode %s instead of %s\n", + r1, r2); + free(r1); + free(r2); + continue; + } + } + if(p->match_question) { + if(!match_question(query_pkt, len, reply, rlen, + (int)p->match_ttl)) { + verbose(3, "bad question section\n"); + continue; + } + } + if(p->match_answer) { + if(!match_answer(query_pkt, len, reply, rlen, + (int)p->match_ttl)) { + verbose(3, "bad answer section\n"); + continue; + } + } + if(p->match_subdomain) { + if(!subdomain_dname(query_pkt, len, reply, rlen)) { + verbose(3, "bad subdomain\n"); + continue; + } + } + if(p->match_serial && get_serial(query_pkt, len) != p->ixfr_soa_serial) { + verbose(3, "bad serial\n"); + continue; + } + if(p->match_do && !get_do_flag(query_pkt, len)) { + verbose(3, "no DO bit set\n"); + continue; + } + if(p->match_noedns && get_has_edns(query_pkt, len)) { + verbose(3, "bad; EDNS OPT present\n"); + continue; + } + if(p->match_ednsdata_raw && + !match_ednsdata(query_pkt, len, reply, rlen)) { + verbose(3, "bad EDNS data match.\n"); + continue; + } + if(p->match_transport != transport_any && p->match_transport != transport) { + verbose(3, "bad transport\n"); + continue; + } + if(p->match_all && !match_all(query_pkt, len, reply, rlen, + (int)p->match_ttl, 0)) { + verbose(3, "bad allmatch\n"); + continue; + } + verbose(3, "match!\n"); + return p; + } + return NULL; +} + +void +adjust_packet(struct entry* match, uint8_t** answer_pkt, size_t *answer_len, + uint8_t* query_pkt, size_t query_len) +{ + uint8_t* orig = *answer_pkt; + size_t origlen = *answer_len; + uint8_t* res; + size_t reslen; + + /* perform the copy; if possible; must be uncompressed */ + if(match->copy_query && origlen >= LDNS_HEADER_SIZE && + query_len >= LDNS_HEADER_SIZE && LDNS_QDCOUNT(query_pkt)!=0 + && LDNS_QDCOUNT(orig)==0) { + /* no qname in output packet, insert it */ + size_t dlen = get_qname_len(query_pkt, query_len); + reslen = origlen + dlen + 4; + res = (uint8_t*)malloc(reslen); + if(!res) { + verbose(1, "out of memory; send without adjust\n"); + return; + } + /* copy the header, query, remainder */ + memcpy(res, orig, LDNS_HEADER_SIZE); + memmove(res+LDNS_HEADER_SIZE, query_pkt+LDNS_HEADER_SIZE, + dlen+4); + memmove(res+LDNS_HEADER_SIZE+dlen+4, orig+LDNS_HEADER_SIZE, + reslen-(LDNS_HEADER_SIZE+dlen+4)); + /* set QDCOUNT */ + sldns_write_uint16(res+4, 1); + } else if(match->copy_query && origlen >= LDNS_HEADER_SIZE && + query_len >= LDNS_HEADER_SIZE && LDNS_QDCOUNT(query_pkt)!=0 + && get_qname_len(orig, origlen) == 0) { + /* QDCOUNT(orig)!=0 but qlen == 0, therefore, an error */ + verbose(1, "error: malformed qname; send without adjust\n"); + res = memdup(orig, origlen); + reslen = origlen; + } else if(match->copy_query && origlen >= LDNS_HEADER_SIZE && + query_len >= LDNS_HEADER_SIZE && LDNS_QDCOUNT(query_pkt)!=0 + && LDNS_QDCOUNT(orig)!=0) { + /* in this case olen != 0 and QDCOUNT(orig)!=0 */ + /* copy query section */ + size_t dlen = get_qname_len(query_pkt, query_len); + size_t olen = get_qname_len(orig, origlen); + reslen = origlen + dlen - olen; + res = (uint8_t*)malloc(reslen); + if(!res) { + verbose(1, "out of memory; send without adjust\n"); + return; + } + /* copy the header, query, remainder */ + memcpy(res, orig, LDNS_HEADER_SIZE); + memmove(res+LDNS_HEADER_SIZE, query_pkt+LDNS_HEADER_SIZE, + dlen+4); + memmove(res+LDNS_HEADER_SIZE+dlen+4, + orig+LDNS_HEADER_SIZE+olen+4, + reslen-(LDNS_HEADER_SIZE+dlen+4)); + } else { + res = memdup(orig, origlen); + reslen = origlen; + } + if(!res) { + verbose(1, "out of memory; send without adjust\n"); + return; + } + /* copy the ID */ + if(match->copy_id && reslen >= 2 && query_len >= 2) + res[1] = query_pkt[1]; + if(match->copy_id && reslen >= 1 && query_len >= 1) + res[0] = query_pkt[0]; + + if(match->copy_ednsdata_assume_clientsubnet) { + /** Assume there is only one EDNS option, which is ECS. + * Copy source mask from query to scope mask in reply. Assume + * rest of ECS data in response (eg address) matches the query. + */ + uint8_t* walk_q = orig; + size_t walk_qlen = origlen; + uint8_t* walk_p = res; + size_t walk_plen = reslen; + + if(!pkt_find_edns_opt(&walk_q, &walk_qlen)) { + walk_qlen = 0; + } + if(!pkt_find_edns_opt(&walk_p, &walk_plen)) { + walk_plen = 0; + } + /* class + ttl + rdlen + optcode + optlen + ecs fam + ecs source + * + ecs scope = index 15 */ + if(walk_qlen >= 15 && walk_plen >= 15) { + walk_p[15] = walk_q[14]; + } + } + + if(match->sleeptime > 0) { + verbose(3, "sleeping for %d seconds\n", match->sleeptime); +#ifdef HAVE_SLEEP + sleep(match->sleeptime); +#else + Sleep(match->sleeptime * 1000); +#endif + } + *answer_pkt = res; + *answer_len = reslen; +} + +/* + * Parses data buffer to a query, finds the correct answer + * and calls the given function for every packet to send. + */ +void +handle_query(uint8_t* inbuf, ssize_t inlen, struct entry* entries, int* count, + enum transport_type transport, void (*sendfunc)(uint8_t*, size_t, void*), + void* userdata, FILE* verbose_out) +{ + struct reply_packet *p; + uint8_t *outbuf = NULL; + size_t outlen = 0; + struct entry* entry = NULL; + + verbose(1, "query %d: id %d: %s %d bytes: ", ++(*count), + (int)(inlen>=2?LDNS_ID_WIRE(inbuf):0), + (transport==transport_tcp)?"TCP":"UDP", (int)inlen); + if(verbose_out) { + char* out = sldns_wire2str_pkt(inbuf, (size_t)inlen); + printf("%s\n", out); + free(out); + } + + /* fill up answer packet */ + entry = find_match(entries, inbuf, (size_t)inlen, transport); + if(!entry || !entry->reply_list) { + verbose(1, "no answer packet for this query, no reply.\n"); + return; + } + for(p = entry->reply_list; p; p = p->next) + { + verbose(3, "Answer pkt:\n"); + if (p->reply_from_hex) { + /* try to adjust the hex packet, if it can be + * parsed, we can use adjust rules. if not, + * send packet literally */ + /* still try to adjust ID if others fail */ + outlen = sldns_buffer_limit(p->reply_from_hex); + outbuf = sldns_buffer_begin(p->reply_from_hex); + } else { + outbuf = p->reply_pkt; + outlen = p->reply_len; + } + if(!outbuf) { + verbose(1, "out of memory\n"); + return; + } + /* copies outbuf in memory allocation */ + adjust_packet(entry, &outbuf, &outlen, inbuf, (size_t)inlen); + verbose(1, "Answer packet size: %u bytes.\n", (unsigned int)outlen); + if(verbose_out) { + char* out = sldns_wire2str_pkt(outbuf, outlen); + printf("%s\n", out); + free(out); + } + if(p->packet_sleep) { + verbose(3, "sleeping for next packet %d secs\n", + p->packet_sleep); +#ifdef HAVE_SLEEP + sleep(p->packet_sleep); +#else + Sleep(p->packet_sleep * 1000); +#endif + verbose(3, "wakeup for next packet " + "(slept %d secs)\n", p->packet_sleep); + } + sendfunc(outbuf, outlen, userdata); + free(outbuf); + outbuf = NULL; + outlen = 0; + } +} + +/** delete the list of reply packets */ +void delete_replylist(struct reply_packet* replist) +{ + struct reply_packet *p=replist, *np; + while(p) { + np = p->next; + free(p->reply_pkt); + sldns_buffer_free(p->reply_from_hex); + sldns_buffer_free(p->raw_ednsdata); + free(p); + p=np; + } +} + +void delete_entry(struct entry* list) +{ + struct entry *p=list, *np; + while(p) { + np = p->next; + delete_replylist(p->reply_list); + free(p); + p = np; + } +} diff --git a/usr.sbin/unbound/testcode/testpkts.h b/usr.sbin/unbound/testcode/testpkts.h new file mode 100644 index 00000000000..b175cab066a --- /dev/null +++ b/usr.sbin/unbound/testcode/testpkts.h @@ -0,0 +1,290 @@ +/* + * testpkts. Data file parse for test packets, and query matching. + * + * Data storage for specially crafted replies for testing purposes. + * + * (c) NLnet Labs, 2005, 2006, 2007 + * See the file LICENSE for the license + */ + +#ifndef TESTPKTS_H +#define TESTPKTS_H +struct sldns_buffer; +struct sldns_file_parse_state; + +/** + * \file + * + * This is a debugging aid. It is not efficient, especially + * with a long config file, but it can give any reply to any query. + * This can help the developer pre-script replies for queries. + * + * You can specify a packet RR by RR with header flags to return. + * + * Missing features: + * - matching content different from reply content. + * - find way to adjust mangled packets? + * + */ + + /* + The data file format is as follows: + + ; comment. + ; a number of entries, these are processed first to last. + ; a line based format. + + $ORIGIN origin + $TTL default_ttl + + ENTRY_BEGIN + ; first give MATCH lines, that say what queries are matched + ; by this entry. + ; 'opcode' makes the query match the opcode from the reply + ; if you leave it out, any opcode matches this entry. + ; 'qtype' makes the query match the qtype from the reply + ; 'qname' makes the query match the qname from the reply + ; 'subdomain' makes the query match subdomains of qname from the reply + ; 'serial=1023' makes the query match if ixfr serial is 1023. + ; 'all' has to match header byte for byte and all rrs in packet. + ; 'ttl' used with all, rrs in packet must also have matching TTLs. + ; 'DO' will match only queries with DO bit set. + ; 'noedns' matches queries without EDNS OPT records. + ; 'rcode' makes the query match the rcode from the reply + ; 'question' makes the query match the question section + ; 'answer' makes the query match the answer section + ; 'ednsdata' matches queries to HEX_EDNS section. + MATCH [opcode] [qtype] [qname] [serial=] [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=] ; sleep before giving any reply + ADJUST [packet_sleep=] ; sleep before this packet in sequence + SECTION QUESTION + ; the RRcount is determined automatically. + SECTION ANSWER + + SECTION AUTHORITY + + SECTION ADDITIONAL + + EXTRA_PACKET ; follow with SECTION, REPLY for more packets. + HEX_ANSWER_BEGIN ; follow with hex data + ; this replaces any answer packet constructed + ; with the SECTION keywords (only SECTION QUERY + ; is used to match queries). If the data cannot + ; be parsed, ADJUST rules for the answer packet + ; are ignored. Only copy_id is done. + HEX_ANSWER_END + HEX_EDNS_BEGIN ; follow with hex data. + ; Raw EDNS data to match against. It must be an + ; exact match (all options are matched) and will be + ; evaluated only when 'MATCH ednsdata' given. + HEX_EDNS_END + ENTRY_END + + + Example data file: +$ORIGIN nlnetlabs.nl +$TTL 3600 + +ENTRY_BEGIN +MATCH qname +REPLY NOERROR +ADJUST copy_id +SECTION QUESTION +www.nlnetlabs.nl. IN A +SECTION ANSWER +www.nlnetlabs.nl. IN A 195.169.215.155 +SECTION AUTHORITY +nlnetlabs.nl. IN NS www.nlnetlabs.nl. +ENTRY_END + +ENTRY_BEGIN +MATCH qname +REPLY NOERROR +ADJUST copy_id +SECTION QUESTION +www2.nlnetlabs.nl. IN A +HEX_ANSWER_BEGIN +; 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 +;-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- + 00 bf 81 80 00 01 00 01 00 02 00 02 03 77 77 77 0b 6b 61 6e ; 1- 20 + 61 72 69 65 70 69 65 74 03 63 6f 6d 00 00 01 00 01 03 77 77 ; 21- 40 + 77 0b 6b 61 6e 61 72 69 65 70 69 65 74 03 63 6f 6d 00 00 01 ; 41- 60 + 00 01 00 01 50 8b 00 04 52 5e ed 32 0b 6b 61 6e 61 72 69 65 ; 61- 80 + 70 69 65 74 03 63 6f 6d 00 00 02 00 01 00 01 50 8b 00 11 03 ; 81- 100 + 6e 73 31 08 68 65 78 6f 6e 2d 69 73 02 6e 6c 00 0b 6b 61 6e ; 101- 120 + 61 72 69 65 70 69 65 74 03 63 6f 6d 00 00 02 00 01 00 01 50 ; 121- 140 + 8b 00 11 03 6e 73 32 08 68 65 78 6f 6e 2d 69 73 02 6e 6c 00 ; 141- 160 + 03 6e 73 31 08 68 65 78 6f 6e 2d 69 73 02 6e 6c 00 00 01 00 ; 161- 180 + 01 00 00 46 53 00 04 52 5e ed 02 03 6e 73 32 08 68 65 78 6f ; 181- 200 + 6e 2d 69 73 02 6e 6c 00 00 01 00 01 00 00 46 53 00 04 d4 cc ; 201- 220 + db 5b +HEX_ANSWER_END +ENTRY_END + + + + note that this file will link with your + void verbose(int level, char* format, ...); output function. +*/ + +/** Type of transport, since some entries match based on UDP or TCP of query */ +enum transport_type {transport_any = 0, transport_udp, transport_tcp }; + +/** struct to keep a linked list of reply packets for a query */ +struct reply_packet { + /** next in list of reply packets, for TCP multiple pkts on wire */ + struct reply_packet* next; + /** the reply pkt */ + uint8_t* reply_pkt; + /** length of reply pkt */ + size_t reply_len; + /** Additional EDNS data for matching queries. */ + struct sldns_buffer* raw_ednsdata; + /** or reply pkt in hex if not parsable */ + struct sldns_buffer* reply_from_hex; + /** seconds to sleep before giving packet */ + unsigned int packet_sleep; +}; + +/** data structure to keep the canned queries in. + format is the 'matching query' and the 'canned answer' */ +struct entry { + /* match */ + /* How to match an incoming query with this canned reply */ + /** match query opcode with answer opcode */ + uint8_t match_opcode; + /** match qtype with answer qtype */ + uint8_t match_qtype; + /** match qname with answer qname */ + uint8_t match_qname; + /** match rcode with answer rcode */ + uint8_t match_rcode; + /** match question section */ + uint8_t match_question; + /** match answer section */ + uint8_t match_answer; + /** match qname as subdomain of answer qname */ + uint8_t match_subdomain; + /** match SOA serial number, from auth section */ + uint8_t match_serial; + /** match all of the packet */ + uint8_t match_all; + /** match ttls in the packet */ + uint8_t match_ttl; + /** match DO bit */ + uint8_t match_do; + /** match absence of EDNS OPT record in query */ + uint8_t match_noedns; + /** match edns data field given in hex */ + uint8_t match_ednsdata_raw; + /** match query serial with this value. */ + uint32_t ixfr_soa_serial; + /** match on UDP/TCP */ + enum transport_type match_transport; + + /** pre canned reply */ + struct reply_packet *reply_list; + + /** how to adjust the reply packet */ + /** copy over the ID from the query into the answer */ + uint8_t copy_id; + /** copy the query nametypeclass from query into the answer */ + uint8_t copy_query; + /** copy ednsdata to reply, assume it is clientsubnet and + * adjust scopemask to match sourcemask */ + uint8_t copy_ednsdata_assume_clientsubnet; + /** in seconds */ + unsigned int sleeptime; + + /** some number that names this entry, line number in file or so */ + int lineno; + + /** next in list */ + struct entry* next; +}; + +/** + * reads the canned reply file and returns a list of structs + * does an exit on error. + * @param name: name of the file to read. + * @param skip_whitespace: skip leftside whitespace. + */ +struct entry* read_datafile(const char* name, int skip_whitespace); + +/** + * Delete linked list of entries. + */ +void delete_entry(struct entry* list); + +/** + * Read one entry from the data file. + * @param in: file to read from. Filepos must be at the start of a new line. + * @param name: name of the file for prettier errors. + * @param pstate: file parse state with lineno, default_ttl, + * origin and prev_rr name. + * @param skip_whitespace: skip leftside whitespace. + * @return: The entry read (malloced) or NULL if no entry could be read. + */ +struct entry* read_entry(FILE* in, const char* name, + struct sldns_file_parse_state* pstate, int skip_whitespace); + +/** + * finds entry in list, or returns NULL. + */ +struct entry* find_match(struct entry* entries, uint8_t* query_pkt, + size_t query_pkt_len, enum transport_type transport); + +/** + * match two packets, all must match + * @param q: packet 1 + * @param qlen: length of q. + * @param p: packet 2 + * @param plen: length of p. + * @param mttl: if true, ttls must match, if false, ttls do not need to match + * @param noloc: if true, rrs may be reordered in their packet-section. + * rrs are then matches without location of the rr being important. + * @return true if matched. + */ +int match_all(uint8_t* q, size_t qlen, uint8_t* p, size_t plen, int mttl, + int noloc); + +/** + * copy & adjust packet, mallocs a copy. + */ +void adjust_packet(struct entry* match, uint8_t** answer_pkt, + size_t* answer_pkt_len, uint8_t* query_pkt, size_t query_pkt_len); + +/** + * Parses data buffer to a query, finds the correct answer + * and calls the given function for every packet to send. + * if verbose_out filename is given, packets are dumped there. + * @param inbuf: the packet that came in + * @param inlen: length of packet. + * @param entries: entries read in from datafile. + * @param count: is increased to count number of queries answered. + * @param transport: set to UDP or TCP to match some types of entries. + * @param sendfunc: called to send answer (buffer, size, userarg). + * @param userdata: userarg to give to sendfunc. + * @param verbose_out: if not NULL, verbose messages are printed there. + */ +void handle_query(uint8_t* inbuf, ssize_t inlen, struct entry* entries, + int* count, enum transport_type transport, + void (*sendfunc)(uint8_t*, size_t, void*), void* userdata, + FILE* verbose_out); + +#endif /* TESTPKTS_H */ diff --git a/usr.sbin/unbound/testcode/unitanchor.c b/usr.sbin/unbound/testcode/unitanchor.c new file mode 100644 index 00000000000..8819c5ab664 --- /dev/null +++ b/usr.sbin/unbound/testcode/unitanchor.c @@ -0,0 +1,137 @@ +/* + * testcode/unitanchor.c - unit test for trust anchor storage. + * + * Copyright (c) 2007, NLnet Labs. All rights reserved. + * + * This software is open source. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * Neither the name of the NLNET LABS nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ +/** + * \file + * Calls trust anchor unit tests. Exits with code 1 on a failure. + */ + +#include "config.h" +#include "util/log.h" +#include "util/data/dname.h" +#include "testcode/unitmain.h" +#include "validator/val_anchor.h" +#include "sldns/sbuffer.h" +#include "sldns/rrdef.h" + +/** test empty set */ +static void +test_anchor_empty(struct val_anchors* a) +{ + uint16_t c = LDNS_RR_CLASS_IN; + unit_assert(anchors_lookup(a, (uint8_t*)"\000", 1, c) == NULL); + unit_assert(anchors_lookup(a, (uint8_t*)"\003com\000", 5, c) == NULL); + unit_assert(anchors_lookup(a, + (uint8_t*)"\007example\003com\000", 11, c) == NULL); + unit_assert(anchors_lookup(a, (uint8_t*)"\002nl\000", 4, c) == NULL); + unit_assert(anchors_lookup(a, + (uint8_t*)"\004labs\002nl\000", 9, c) == NULL); + unit_assert(anchors_lookup(a, + (uint8_t*)"\004fabs\002nl\000", 9, c) == NULL); +} + +/** test set of one anchor */ +static void +test_anchor_one(sldns_buffer* buff, struct val_anchors* a) +{ + struct trust_anchor* ta; + uint16_t c = LDNS_RR_CLASS_IN; + unit_assert(anchor_store_str(a, buff, + "nl. DS 42860 5 1 14D739EB566D2B1A5E216A0BA4D17FA9B038BE4A")); + unit_assert(anchors_lookup(a, (uint8_t*)"\000", 1, c) == NULL); + unit_assert(anchors_lookup(a, (uint8_t*)"\003com\000", 5, c) == NULL); + unit_assert(anchors_lookup(a, + (uint8_t*)"\007example\003com\000", 11, c) == NULL); + + unit_assert((ta=anchors_lookup(a, + (uint8_t*)"\002nl\000", 4, c)) != NULL); + lock_basic_unlock(&ta->lock); + + unit_assert((ta=anchors_lookup(a, + (uint8_t*)"\004labs\002nl\000", 9, c)) != NULL); + lock_basic_unlock(&ta->lock); + + unit_assert((ta=anchors_lookup(a, + (uint8_t*)"\004fabs\002nl\000", 9, c)) != NULL); + lock_basic_unlock(&ta->lock); + + unit_assert(anchors_lookup(a, (uint8_t*)"\002oo\000", 4, c) == NULL); +} + +/** test with several anchors */ +static void +test_anchors(sldns_buffer* buff, struct val_anchors* a) +{ + struct trust_anchor* ta; + uint16_t c = LDNS_RR_CLASS_IN; + unit_assert(anchor_store_str(a, buff, + "labs.nl. DS 42860 5 1 14D739EB566D2B1A5E216A0BA4D17FA9B038BE4A")); + unit_assert(anchors_lookup(a, (uint8_t*)"\000", 1, c) == NULL); + unit_assert(anchors_lookup(a, (uint8_t*)"\003com\000", 5, c) == NULL); + unit_assert(anchors_lookup(a, + (uint8_t*)"\007example\003com\000", 11, c) == NULL); + + unit_assert(ta = anchors_lookup(a, (uint8_t*)"\002nl\000", 4, c)); + unit_assert(query_dname_compare(ta->name, (uint8_t*)"\002nl\000")==0); + lock_basic_unlock(&ta->lock); + + unit_assert(ta = anchors_lookup(a, + (uint8_t*)"\004labs\002nl\000", 9, c)); + unit_assert(query_dname_compare(ta->name, + (uint8_t*)"\004labs\002nl\000") == 0); + lock_basic_unlock(&ta->lock); + + unit_assert(ta = anchors_lookup(a, + (uint8_t*)"\004fabs\002nl\000", 9, c)); + unit_assert(query_dname_compare(ta->name, + (uint8_t*)"\002nl\000") == 0); + lock_basic_unlock(&ta->lock); + + unit_assert(anchors_lookup(a, (uint8_t*)"\002oo\000", 4, c) == NULL); +} + +void anchors_test(void) +{ + sldns_buffer* buff = sldns_buffer_new(65800); + struct val_anchors* a; + unit_show_feature("trust anchor store"); + unit_assert(a = anchors_create()); + sldns_buffer_flip(buff); + test_anchor_empty(a); + test_anchor_one(buff, a); + test_anchors(buff, a); + anchors_delete(a); + sldns_buffer_free(buff); +} diff --git a/usr.sbin/unbound/testcode/unitauth.c b/usr.sbin/unbound/testcode/unitauth.c new file mode 100644 index 00000000000..4b538ef6268 --- /dev/null +++ b/usr.sbin/unbound/testcode/unitauth.c @@ -0,0 +1,882 @@ +/* + * testcode/unitauth.c - unit test for authzone authoritative zone code. + * + * Copyright (c) 2017, NLnet Labs. All rights reserved. + * + * This software is open source. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * Neither the name of the NLNET LABS nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ +/** + * \file + * Unit test for auth zone code. + */ +#include "config.h" +#include "services/authzone.h" +#include "testcode/unitmain.h" +#include "util/regional.h" +#include "util/net_help.h" +#include "util/data/msgreply.h" +#include "services/cache/dns.h" +#include "sldns/str2wire.h" +#include "sldns/wire2str.h" +#include "sldns/sbuffer.h" + +/** verbosity for this test */ +static int vbmp = 0; + +/** struct for query and answer checks */ +struct q_ans { + /** zone to query (delegpt) */ + const char* zone; + /** query name, class, type */ + const char* query; + /** additional flags or "" */ + const char* flags; + /** expected answer to check against, multi-line string */ + const char* answer; +}; + +/** auth zone for test */ +static const char* zone_example_com = +"example.com. 3600 IN SOA ns.example.org. noc.example.org. 2017042710 7200 3600 1209600 3600\n" +"example.com. 3600 IN A 10.0.0.1\n" +"example.com. 3600 IN NS ns.example.com.\n" +"example.com. 3600 IN MX 50 mail.example.com.\n" +"deep.ent.example.com. 3600 IN A 10.0.0.9\n" +"mail.example.com. 3600 IN A 10.0.0.4\n" +"ns.example.com. 3600 IN A 10.0.0.5\n" +"out.example.com. 3600 IN CNAME www.example.com.\n" +"plan.example.com. 3600 IN CNAME nonexist.example.com.\n" +"redir.example.com. 3600 IN DNAME redir.example.org.\n" +"sub.example.com. 3600 IN NS ns1.sub.example.com.\n" +"sub.example.com. 3600 IN NS ns2.sub.example.com.\n" +"ns1.sub.example.com. 3600 IN A 10.0.0.6\n" +"ns2.sub.example.com. 3600 IN AAAA 2001::7\n" +"*.wild.example.com. 3600 IN A 10.0.0.8\n" +"*.wild2.example.com. 3600 IN CNAME www.example.com.\n" +"*.wild3.example.com. 3600 IN A 10.0.0.8\n" +"*.wild3.example.com. 3600 IN MX 50 mail.example.com.\n" +"www.example.com. 3600 IN A 10.0.0.2\n" +"www.example.com. 3600 IN A 10.0.0.3\n" +"yy.example.com. 3600 IN TXT \"a\"\n" +"yy.example.com. 3600 IN TXT \"b\"\n" +"yy.example.com. 3600 IN TXT \"c\"\n" +"yy.example.com. 3600 IN TXT \"d\"\n" +"yy.example.com. 3600 IN TXT \"e\"\n" +"yy.example.com. 3600 IN TXT \"f\"\n" + +/* and some tests for RRSIGs (rrsig is www.nlnetlabs.nl copy) */ +/* normal: domain and 1 rrsig */ +"z1.example.com. 3600 IN A 10.0.0.10\n" +"z1.example.com. 3600 IN RRSIG A 8 3 10200 20170612005010 20170515005010 42393 nlnetlabs.nl. NhEDrHkuIgHkjWhDRVsGOIJWZpSs+QdduilWFe5d+/ZhOheLJbaTYD5w6+ZZ3yPh1tNud+jlg+GyiOSVapLEO31swDCIarL1UfRjRSpxxDCHGag5Zu+S4hF+KURxO3cJk8jLBELMQyRuMRHoKrw/wsiLGVu1YpAyAPPMcjFBNbk=\n" +/* normal: domain and 2 rrsigs */ +"z2.example.com. 3600 IN A 10.0.0.10\n" +"z2.example.com. 3600 IN RRSIG A 8 3 10200 20170612005010 20170515005010 42393 nlnetlabs.nl. NhEDrHkuIgHkjWhDRVsGOIJWZpSs+QdduilWFe5d+/ZhOheLJbaTYD5w6+ZZ3yPh1tNud+jlg+GyiOSVapLEO31swDCIarL1UfRjRSpxxDCHGag5Zu+S4hF+KURxO3cJk8jLBELMQyRuMRHoKrw/wsiLGVu1YpAyAPPMcjFBNbk=\n" +"z2.example.com. 3600 IN RRSIG A 8 3 10200 20170612005010 20170515005010 12345 nlnetlabs.nl. NhEDrHkuIgHkjWhDRVsGOIJWZpSs+QdduilWFe5d+/ZhOheLJbaTYD5w6+ZZ3yPh1tNud+jlg+GyiOSVapLEO31swDCIarL1UfRjRSpxxDCHGag5Zu+S4hF+KURxO3cJk8jLBELMQyRuMRHoKrw/wsiLGVu1YpAyAPPMcjFBNbk=\n" +/* normal: domain and 3 rrsigs */ +"z3.example.com. 3600 IN A 10.0.0.10\n" +"z3.example.com. 3600 IN A 10.0.0.11\n" +"z3.example.com. 3600 IN RRSIG A 8 3 10200 20170612005010 20170515005010 42393 nlnetlabs.nl. NhEDrHkuIgHkjWhDRVsGOIJWZpSs+QdduilWFe5d+/ZhOheLJbaTYD5w6+ZZ3yPh1tNud+jlg+GyiOSVapLEO31swDCIarL1UfRjRSpxxDCHGag5Zu+S4hF+KURxO3cJk8jLBELMQyRuMRHoKrw/wsiLGVu1YpAyAPPMcjFBNbk=\n" +"z3.example.com. 3600 IN RRSIG A 8 3 10200 20170612005010 20170515005010 12345 nlnetlabs.nl. NhEDrHkuIgHkjWhDRVsGOIJWZpSs+QdduilWFe5d+/ZhOheLJbaTYD5w6+ZZ3yPh1tNud+jlg+GyiOSVapLEO31swDCIarL1UfRjRSpxxDCHGag5Zu+S4hF+KURxO3cJk8jLBELMQyRuMRHoKrw/wsiLGVu1YpAyAPPMcjFBNbk=\n" +"z3.example.com. 3600 IN RRSIG A 8 3 10200 20170612005010 20170515005010 12356 nlnetlabs.nl. NhEDrHkuIgHkjWhDRVsGOIJWZpSs+QdduilWFe5d+/ZhOheLJbaTYD5w6+ZZ3yPh1tNud+jlg+GyiOSVapLEO31swDCIarL1UfRjRSpxxDCHGag5Zu+S4hF+KURxO3cJk8jLBELMQyRuMRHoKrw/wsiLGVu1YpAyAPPMcjFBNbk=\n" +/* just an RRSIG rrset with nothing else */ +"z4.example.com. 3600 IN RRSIG A 8 3 10200 20170612005010 20170515005010 42393 nlnetlabs.nl. NhEDrHkuIgHkjWhDRVsGOIJWZpSs+QdduilWFe5d+/ZhOheLJbaTYD5w6+ZZ3yPh1tNud+jlg+GyiOSVapLEO31swDCIarL1UfRjRSpxxDCHGag5Zu+S4hF+KURxO3cJk8jLBELMQyRuMRHoKrw/wsiLGVu1YpAyAPPMcjFBNbk=\n" +/* just an RRSIG rrset with nothing else, 2 rrsigs */ +"z5.example.com. 3600 IN RRSIG A 8 3 10200 20170612005010 20170515005010 42393 nlnetlabs.nl. NhEDrHkuIgHkjWhDRVsGOIJWZpSs+QdduilWFe5d+/ZhOheLJbaTYD5w6+ZZ3yPh1tNud+jlg+GyiOSVapLEO31swDCIarL1UfRjRSpxxDCHGag5Zu+S4hF+KURxO3cJk8jLBELMQyRuMRHoKrw/wsiLGVu1YpAyAPPMcjFBNbk=\n" +"z5.example.com. 3600 IN RRSIG A 8 3 10200 20170612005010 20170515005010 12345 nlnetlabs.nl. NhEDrHkuIgHkjWhDRVsGOIJWZpSs+QdduilWFe5d+/ZhOheLJbaTYD5w6+ZZ3yPh1tNud+jlg+GyiOSVapLEO31swDCIarL1UfRjRSpxxDCHGag5Zu+S4hF+KURxO3cJk8jLBELMQyRuMRHoKrw/wsiLGVu1YpAyAPPMcjFBNbk=\n" +#if 0 /* comparison of file does not work on this part because duplicates */ + /* are removed and the rrsets are reordered */ +/* first rrsig, then A record */ +"z6.example.com. 3600 IN RRSIG A 8 3 10200 20170612005010 20170515005010 42393 nlnetlabs.nl. NhEDrHkuIgHkjWhDRVsGOIJWZpSs+QdduilWFe5d+/ZhOheLJbaTYD5w6+ZZ3yPh1tNud+jlg+GyiOSVapLEO31swDCIarL1UfRjRSpxxDCHGag5Zu+S4hF+KURxO3cJk8jLBELMQyRuMRHoKrw/wsiLGVu1YpAyAPPMcjFBNbk=\n" +"z6.example.com. 3600 IN A 10.0.0.10\n" +/* first two rrsigs, then A record */ +"z7.example.com. 3600 IN RRSIG A 8 3 10200 20170612005010 20170515005010 42393 nlnetlabs.nl. NhEDrHkuIgHkjWhDRVsGOIJWZpSs+QdduilWFe5d+/ZhOheLJbaTYD5w6+ZZ3yPh1tNud+jlg+GyiOSVapLEO31swDCIarL1UfRjRSpxxDCHGag5Zu+S4hF+KURxO3cJk8jLBELMQyRuMRHoKrw/wsiLGVu1YpAyAPPMcjFBNbk=\n" +"z7.example.com. 3600 IN RRSIG A 8 3 10200 20170612005010 20170515005010 12345 nlnetlabs.nl. NhEDrHkuIgHkjWhDRVsGOIJWZpSs+QdduilWFe5d+/ZhOheLJbaTYD5w6+ZZ3yPh1tNud+jlg+GyiOSVapLEO31swDCIarL1UfRjRSpxxDCHGag5Zu+S4hF+KURxO3cJk8jLBELMQyRuMRHoKrw/wsiLGVu1YpAyAPPMcjFBNbk=\n" +"z7.example.com. 3600 IN A 10.0.0.10\n" +/* first two rrsigs, then two A records */ +"z8.example.com. 3600 IN RRSIG A 8 3 10200 20170612005010 20170515005010 42393 nlnetlabs.nl. NhEDrHkuIgHkjWhDRVsGOIJWZpSs+QdduilWFe5d+/ZhOheLJbaTYD5w6+ZZ3yPh1tNud+jlg+GyiOSVapLEO31swDCIarL1UfRjRSpxxDCHGag5Zu+S4hF+KURxO3cJk8jLBELMQyRuMRHoKrw/wsiLGVu1YpAyAPPMcjFBNbk=\n" +"z8.example.com. 3600 IN RRSIG A 8 3 10200 20170612005010 20170515005010 12345 nlnetlabs.nl. NhEDrHkuIgHkjWhDRVsGOIJWZpSs+QdduilWFe5d+/ZhOheLJbaTYD5w6+ZZ3yPh1tNud+jlg+GyiOSVapLEO31swDCIarL1UfRjRSpxxDCHGag5Zu+S4hF+KURxO3cJk8jLBELMQyRuMRHoKrw/wsiLGVu1YpAyAPPMcjFBNbk=\n" +"z8.example.com. 3600 IN A 10.0.0.10\n" +"z8.example.com. 3600 IN A 10.0.0.11\n" +/* duplicate RR, duplicate RRsig */ +"z9.example.com. 3600 IN A 10.0.0.10\n" +"z9.example.com. 3600 IN A 10.0.0.11\n" +"z9.example.com. 3600 IN A 10.0.0.10\n" +"z9.example.com. 3600 IN RRSIG A 8 3 10200 20170612005010 20170515005010 42393 nlnetlabs.nl. NhEDrHkuIgHkjWhDRVsGOIJWZpSs+QdduilWFe5d+/ZhOheLJbaTYD5w6+ZZ3yPh1tNud+jlg+GyiOSVapLEO31swDCIarL1UfRjRSpxxDCHGag5Zu+S4hF+KURxO3cJk8jLBELMQyRuMRHoKrw/wsiLGVu1YpAyAPPMcjFBNbk=\n" +"z9.example.com. 3600 IN RRSIG A 8 3 10200 20170612005010 20170515005010 42393 nlnetlabs.nl. NhEDrHkuIgHkjWhDRVsGOIJWZpSs+QdduilWFe5d+/ZhOheLJbaTYD5w6+ZZ3yPh1tNud+jlg+GyiOSVapLEO31swDCIarL1UfRjRSpxxDCHGag5Zu+S4hF+KURxO3cJk8jLBELMQyRuMRHoKrw/wsiLGVu1YpAyAPPMcjFBNbk=\n" +#endif /* if0 for duplicates and reordering */ +; + +/** queries for example.com: zone, query, flags, answer. end with NULL */ +static struct q_ans example_com_queries[] = { + { "example.com", "www.example.com. A", "", +";flags QR AA rcode NOERROR\n" +";answer section\n" +"www.example.com. 3600 IN A 10.0.0.2\n" +"www.example.com. 3600 IN A 10.0.0.3\n" + }, + + { "example.com", "example.com. SOA", "", +";flags QR AA rcode NOERROR\n" +";answer section\n" +"example.com. 3600 IN SOA ns.example.org. noc.example.org. 2017042710 7200 3600 1209600 3600\n" + }, + + { "example.com", "example.com. A", "", +";flags QR AA rcode NOERROR\n" +";answer section\n" +"example.com. 3600 IN A 10.0.0.1\n" + }, + + { "example.com", "example.com. AAAA", "", +";flags QR AA rcode NOERROR\n" +";authority section\n" +"example.com. 3600 IN SOA ns.example.org. noc.example.org. 2017042710 7200 3600 1209600 3600\n" + }, + + { "example.com", "example.com. NS", "", +";flags QR AA rcode NOERROR\n" +";answer section\n" +"example.com. 3600 IN NS ns.example.com.\n" +";additional section\n" +"ns.example.com. 3600 IN A 10.0.0.5\n" + }, + + { "example.com", "example.com. MX", "", +";flags QR AA rcode NOERROR\n" +";answer section\n" +"example.com. 3600 IN MX 50 mail.example.com.\n" +";additional section\n" +"mail.example.com. 3600 IN A 10.0.0.4\n" + }, + + { "example.com", "example.com. IN ANY", "", +";flags QR AA rcode NOERROR\n" +";answer section\n" +"example.com. 3600 IN SOA ns.example.org. noc.example.org. 2017042710 7200 3600 1209600 3600\n" +"example.com. 3600 IN MX 50 mail.example.com.\n" +"example.com. 3600 IN A 10.0.0.1\n" + }, + + { "example.com", "nonexist.example.com. A", "", +";flags QR AA rcode NXDOMAIN\n" +";authority section\n" +"example.com. 3600 IN SOA ns.example.org. noc.example.org. 2017042710 7200 3600 1209600 3600\n" + }, + + { "example.com", "deep.ent.example.com. A", "", +";flags QR AA rcode NOERROR\n" +";answer section\n" +"deep.ent.example.com. 3600 IN A 10.0.0.9\n" + }, + + { "example.com", "ent.example.com. A", "", +";flags QR AA rcode NOERROR\n" +";authority section\n" +"example.com. 3600 IN SOA ns.example.org. noc.example.org. 2017042710 7200 3600 1209600 3600\n" + }, + + { "example.com", "below.deep.ent.example.com. A", "", +";flags QR AA rcode NXDOMAIN\n" +";authority section\n" +"example.com. 3600 IN SOA ns.example.org. noc.example.org. 2017042710 7200 3600 1209600 3600\n" + }, + + { "example.com", "mail.example.com. A", "", +";flags QR AA rcode NOERROR\n" +";answer section\n" +"mail.example.com. 3600 IN A 10.0.0.4\n" + }, + + { "example.com", "ns.example.com. A", "", +";flags QR AA rcode NOERROR\n" +";answer section\n" +"ns.example.com. 3600 IN A 10.0.0.5\n" + }, + + { "example.com", "out.example.com. A", "", +";flags QR AA rcode NOERROR\n" +";answer section\n" +"out.example.com. 3600 IN CNAME www.example.com.\n" +"www.example.com. 3600 IN A 10.0.0.2\n" +"www.example.com. 3600 IN A 10.0.0.3\n" + }, + + { "example.com", "out.example.com. CNAME", "", +";flags QR AA rcode NOERROR\n" +";answer section\n" +"out.example.com. 3600 IN CNAME www.example.com.\n" + }, + + { "example.com", "plan.example.com. A", "", +";flags QR AA rcode NOERROR\n" +";answer section\n" +"plan.example.com. 3600 IN CNAME nonexist.example.com.\n" + }, + + { "example.com", "plan.example.com. CNAME", "", +";flags QR AA rcode NOERROR\n" +";answer section\n" +"plan.example.com. 3600 IN CNAME nonexist.example.com.\n" + }, + + { "example.com", "redir.example.com. A", "", +";flags QR AA rcode NOERROR\n" +";authority section\n" +"example.com. 3600 IN SOA ns.example.org. noc.example.org. 2017042710 7200 3600 1209600 3600\n" + }, + + { "example.com", "redir.example.com. DNAME", "", +";flags QR AA rcode NOERROR\n" +";answer section\n" +"redir.example.com. 3600 IN DNAME redir.example.org.\n" + }, + + { "example.com", "abc.redir.example.com. A", "", +";flags QR AA rcode NOERROR\n" +";answer section\n" +"redir.example.com. 3600 IN DNAME redir.example.org.\n" +"abc.redir.example.com. 0 IN CNAME abc.redir.example.org.\n" + }, + + { "example.com", "foo.abc.redir.example.com. A", "", +";flags QR AA rcode NOERROR\n" +";answer section\n" +"redir.example.com. 3600 IN DNAME redir.example.org.\n" +"foo.abc.redir.example.com. 0 IN CNAME foo.abc.redir.example.org.\n" + }, + + { "example.com", "sub.example.com. NS", "", +";flags QR rcode NOERROR\n" +";authority section\n" +"sub.example.com. 3600 IN NS ns1.sub.example.com.\n" +"sub.example.com. 3600 IN NS ns2.sub.example.com.\n" +";additional section\n" +"ns1.sub.example.com. 3600 IN A 10.0.0.6\n" +"ns2.sub.example.com. 3600 IN AAAA 2001::7\n" + }, + + { "example.com", "sub.example.com. DS", "", +";flags QR AA rcode NOERROR\n" +";authority section\n" +"example.com. 3600 IN SOA ns.example.org. noc.example.org. 2017042710 7200 3600 1209600 3600\n" + }, + + { "example.com", "www.sub.example.com. NS", "", +";flags QR rcode NOERROR\n" +";authority section\n" +"sub.example.com. 3600 IN NS ns1.sub.example.com.\n" +"sub.example.com. 3600 IN NS ns2.sub.example.com.\n" +";additional section\n" +"ns1.sub.example.com. 3600 IN A 10.0.0.6\n" +"ns2.sub.example.com. 3600 IN AAAA 2001::7\n" + }, + + { "example.com", "foo.abc.sub.example.com. NS", "", +";flags QR rcode NOERROR\n" +";authority section\n" +"sub.example.com. 3600 IN NS ns1.sub.example.com.\n" +"sub.example.com. 3600 IN NS ns2.sub.example.com.\n" +";additional section\n" +"ns1.sub.example.com. 3600 IN A 10.0.0.6\n" +"ns2.sub.example.com. 3600 IN AAAA 2001::7\n" + }, + + { "example.com", "ns1.sub.example.com. A", "", +";flags QR rcode NOERROR\n" +";authority section\n" +"sub.example.com. 3600 IN NS ns1.sub.example.com.\n" +"sub.example.com. 3600 IN NS ns2.sub.example.com.\n" +";additional section\n" +"ns1.sub.example.com. 3600 IN A 10.0.0.6\n" +"ns2.sub.example.com. 3600 IN AAAA 2001::7\n" + }, + + { "example.com", "ns1.sub.example.com. AAAA", "", +";flags QR rcode NOERROR\n" +";authority section\n" +"sub.example.com. 3600 IN NS ns1.sub.example.com.\n" +"sub.example.com. 3600 IN NS ns2.sub.example.com.\n" +";additional section\n" +"ns1.sub.example.com. 3600 IN A 10.0.0.6\n" +"ns2.sub.example.com. 3600 IN AAAA 2001::7\n" + }, + + { "example.com", "ns2.sub.example.com. A", "", +";flags QR rcode NOERROR\n" +";authority section\n" +"sub.example.com. 3600 IN NS ns1.sub.example.com.\n" +"sub.example.com. 3600 IN NS ns2.sub.example.com.\n" +";additional section\n" +"ns1.sub.example.com. 3600 IN A 10.0.0.6\n" +"ns2.sub.example.com. 3600 IN AAAA 2001::7\n" + }, + + { "example.com", "ns2.sub.example.com. AAAA", "", +";flags QR rcode NOERROR\n" +";authority section\n" +"sub.example.com. 3600 IN NS ns1.sub.example.com.\n" +"sub.example.com. 3600 IN NS ns2.sub.example.com.\n" +";additional section\n" +"ns1.sub.example.com. 3600 IN A 10.0.0.6\n" +"ns2.sub.example.com. 3600 IN AAAA 2001::7\n" + }, + + { "example.com", "wild.example.com. A", "", +";flags QR AA rcode NOERROR\n" +";authority section\n" +"example.com. 3600 IN SOA ns.example.org. noc.example.org. 2017042710 7200 3600 1209600 3600\n" + }, + + { "example.com", "*.wild.example.com. A", "", +";flags QR AA rcode NOERROR\n" +";answer section\n" +"*.wild.example.com. 3600 IN A 10.0.0.8\n" + }, + + { "example.com", "*.wild.example.com. AAAA", "", +";flags QR AA rcode NOERROR\n" +";authority section\n" +"example.com. 3600 IN SOA ns.example.org. noc.example.org. 2017042710 7200 3600 1209600 3600\n" + }, + + { "example.com", "abc.wild.example.com. A", "", +";flags QR AA rcode NOERROR\n" +";answer section\n" +"abc.wild.example.com. 3600 IN A 10.0.0.8\n" + }, + + { "example.com", "abc.wild.example.com. AAAA", "", +";flags QR AA rcode NOERROR\n" +";authority section\n" +"example.com. 3600 IN SOA ns.example.org. noc.example.org. 2017042710 7200 3600 1209600 3600\n" + }, + + { "example.com", "foo.abc.wild.example.com. A", "", +";flags QR AA rcode NOERROR\n" +";answer section\n" +"foo.abc.wild.example.com. 3600 IN A 10.0.0.8\n" + }, + + { "example.com", "foo.abc.wild.example.com. AAAA", "", +";flags QR AA rcode NOERROR\n" +";authority section\n" +"example.com. 3600 IN SOA ns.example.org. noc.example.org. 2017042710 7200 3600 1209600 3600\n" + }, + + { "example.com", "wild2.example.com. A", "", +";flags QR AA rcode NOERROR\n" +";authority section\n" +"example.com. 3600 IN SOA ns.example.org. noc.example.org. 2017042710 7200 3600 1209600 3600\n" + }, + + { "example.com", "*.wild2.example.com. A", "", +";flags QR AA rcode NOERROR\n" +";answer section\n" +"*.wild2.example.com. 3600 IN CNAME www.example.com.\n" +"www.example.com. 3600 IN A 10.0.0.2\n" +"www.example.com. 3600 IN A 10.0.0.3\n" + }, + + { "example.com", "abc.wild2.example.com. A", "", +";flags QR AA rcode NOERROR\n" +";answer section\n" +"abc.wild2.example.com. 3600 IN CNAME www.example.com.\n" +"www.example.com. 3600 IN A 10.0.0.2\n" +"www.example.com. 3600 IN A 10.0.0.3\n" + }, + + { "example.com", "foo.abc.wild2.example.com. A", "", +";flags QR AA rcode NOERROR\n" +";answer section\n" +"foo.abc.wild2.example.com. 3600 IN CNAME www.example.com.\n" +"www.example.com. 3600 IN A 10.0.0.2\n" +"www.example.com. 3600 IN A 10.0.0.3\n" + }, + + { "example.com", "abc.wild2.example.com. CNAME", "", +";flags QR AA rcode NOERROR\n" +";answer section\n" +"abc.wild2.example.com. 3600 IN CNAME www.example.com.\n" + }, + + { "example.com", "abc.wild3.example.com. IN ANY", "", +";flags QR AA rcode NOERROR\n" +";answer section\n" +"abc.wild3.example.com. 3600 IN MX 50 mail.example.com.\n" +"abc.wild3.example.com. 3600 IN A 10.0.0.8\n" + }, + + { "example.com", "yy.example.com. TXT", "", +";flags QR AA rcode NOERROR\n" +";answer section\n" +"yy.example.com. 3600 IN TXT \"a\"\n" +"yy.example.com. 3600 IN TXT \"b\"\n" +"yy.example.com. 3600 IN TXT \"c\"\n" +"yy.example.com. 3600 IN TXT \"d\"\n" +"yy.example.com. 3600 IN TXT \"e\"\n" +"yy.example.com. 3600 IN TXT \"f\"\n" + }, + + {NULL, NULL, NULL, NULL} +}; + +/** number of tmpfiles */ +static int tempno = 0; +/** number of deleted files */ +static int delno = 0; + +/** cleanup tmp files at exit */ +static void +tmpfilecleanup(void) +{ + int i; + char buf[256]; + for(i=0; ilock); + 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; irrset_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; jcount+d->rrsig_count; j++) { + if(!packed_rr_to_string(rep->rrsets[i], j, 0, + s, sizeof(s))) { + fatal_exit("could not rr_to_string %d", + (int)i); + } + sldns_buffer_printf(buf, "%s", s); + } + } +} + +/** create string for message */ +static char* +msgtostr(struct dns_msg* msg) +{ + char* str; + sldns_buffer* buf = sldns_buffer_new(65535); + if(!buf) fatal_exit("out of memory"); + if(!msg) { + sldns_buffer_printf(buf, "null packet\n"); + } else { + pr_flags(buf, msg->rep->flags); + pr_rrs(buf, msg->rep); + } + + str = strdup((char*)sldns_buffer_begin(buf)); + if(!str) fatal_exit("out of memory"); + sldns_buffer_free(buf); + return str; +} + +/** find line diff between strings */ +static void +line_diff(const char* p, const char* q, const char* pdesc, const char* qdesc) +{ + char* pdup, *qdup, *pl, *ql; + int line = 1; + pdup = strdup(p); + qdup = strdup(q); + if(!pdup || !qdup) fatal_exit("out of memory"); + pl=pdup; + ql=qdup; + printf("linediff (<%s, >%s)\n", pdesc, qdesc); + while(pl && ql && *pl && *ql) { + char* ep = strchr(pl, '\n'); + char* eq = strchr(ql, '\n'); + /* terminate lines */ + if(ep) *ep = 0; + if(eq) *eq = 0; + /* printout */ + if(strcmp(pl, ql) == 0) { + printf("%3d %s\n", line, pl); + } else { + printf("%3d < %s\n", line, pl); + printf("%3d > %s\n", line, ql); + } + if(ep) *ep = '\n'; + if(eq) *eq = '\n'; + if(ep) pl = ep+1; + else pl = NULL; + if(eq) ql = eq+1; + else ql = NULL; + line++; + } + if(pl && *pl) { + printf("%3d < %s\n", line, pl); + } + if(ql && *ql) { + printf("%3d > %s\n", line, ql); + } + free(pdup); + free(qdup); +} + +/** make q_ans query */ +static void +q_ans_query(struct q_ans* q, struct auth_zones* az, struct query_info* qinfo, + struct regional* region, int expected_fallback, uint8_t* dp_nm, + size_t dp_nmlen) +{ + int ret, fallback = 0; + struct dns_msg* msg = NULL; + char* ans_str; + int oldv = verbosity; + /* increase verbosity to printout logic in authzone */ + if(vbmp) verbosity = 4; + ret = auth_zones_lookup(az, qinfo, region, &msg, &fallback, dp_nm, + dp_nmlen); + if(vbmp) verbosity = oldv; + + /* check the answer */ + ans_str = msgtostr(msg); + /* printout if vbmp */ + if(vbmp) printf("got (ret=%s%s):\n%s", + (ret?"ok":"fail"), (fallback?" fallback":""), ans_str); + /* check expected value for ret */ + if(expected_fallback && ret != 0) { + /* ret is zero on fallback */ + if(vbmp) printf("fallback expected, but " + "return value is not false\n"); + unit_assert(expected_fallback && ret == 0); + } + if(ret == 0) { + if(!expected_fallback) { + if(vbmp) printf("return value is false, " + "(unexpected)\n"); + } + unit_assert(expected_fallback); + } + /* check expected value for fallback */ + if(expected_fallback && !fallback) { + if(vbmp) printf("expected fallback, but fallback is no\n"); + } else if(!expected_fallback && fallback) { + if(vbmp) printf("expected no fallback, but fallback is yes\n"); + } + unit_assert( (expected_fallback&&fallback) || + (!expected_fallback&&!fallback)); + /* check answer string */ + if(strcmp(q->answer, ans_str) != 0) { + if(vbmp) printf("wanted:\n%s", q->answer); + line_diff(q->answer, ans_str, "wanted", "got"); + } + unit_assert(strcmp(q->answer, ans_str) == 0); + if(vbmp) printf("query ok\n\n"); + free(ans_str); +} + +/** check queries on a loaded zone */ +static void +check_az_q_ans(struct auth_zones* az, struct q_ans* queries) +{ + struct q_ans* q; + struct regional* region = regional_create(); + struct query_info* qinfo; + int fallback; + uint8_t* dp_nm; + size_t dp_nmlen; + for(q=queries; q->zone; q++) { + if(vbmp) printf("query %s: %s %s\n", q->zone, q->query, + q->flags); + q_ans_parse(q, region, &qinfo, &fallback, &dp_nm, &dp_nmlen); + q_ans_query(q, az, qinfo, region, fallback, dp_nm, dp_nmlen); + regional_free_all(region); + } + regional_destroy(region); +} + +/** check queries for a zone are returned as specified */ +static void +check_queries(const char* name, const char* zone, struct q_ans* queries) +{ + struct auth_zones* az; + struct auth_zone* z; + char* fname; + if(vbmp) printf("check queries %s\n", name); + fname = create_tmp_file(zone); + az = auth_zones_create(); + if(!az) fatal_exit("out of memory"); + z = addzone(az, name, fname); + if(!z) fatal_exit("could not read zone for queries test"); + del_tmp_file(fname); + + /* run queries and test them */ + check_az_q_ans(az, queries); + + auth_zones_delete(az); +} + +/** Test authzone compare_serial */ +static void +authzone_compare_serial(void) +{ + if(vbmp) printf("Testing compare_serial\n"); + unit_assert(compare_serial(0, 1) < 0); + unit_assert(compare_serial(1, 0) > 0); + unit_assert(compare_serial(0, 0) == 0); + unit_assert(compare_serial(1, 1) == 0); + unit_assert(compare_serial(0xf0000000, 0xf0000000) == 0); + unit_assert(compare_serial(0, 0xf0000000) > 0); + unit_assert(compare_serial(0xf0000000, 0) < 0); + unit_assert(compare_serial(0xf0000000, 0xf0000001) < 0); + unit_assert(compare_serial(0xf0000002, 0xf0000001) > 0); + unit_assert(compare_serial(0x70000000, 0x80000000) < 0); + unit_assert(compare_serial(0x90000000, 0x70000000) > 0); +} + +/** Test authzone read from file */ +static void +authzone_read_test(void) +{ + if(vbmp) printf("Testing read auth zone\n"); + check_read_exact("example.com", zone_example_com); +} + +/** Test authzone query from zone */ +static void +authzone_query_test(void) +{ + if(vbmp) printf("Testing query auth zone\n"); + check_queries("example.com", zone_example_com, example_com_queries); +} + +/** test authzone code */ +void +authzone_test(void) +{ + unit_show_feature("authzone"); + atexit(tmpfilecleanup); + authzone_compare_serial(); + authzone_read_test(); + authzone_query_test(); +} diff --git a/usr.sbin/unbound/testcode/unitdname.c b/usr.sbin/unbound/testcode/unitdname.c new file mode 100644 index 00000000000..238c3edf7ad --- /dev/null +++ b/usr.sbin/unbound/testcode/unitdname.c @@ -0,0 +1,861 @@ +/* + * testcode/unitdname.c - unit test for dname routines. + * + * Copyright (c) 2007, NLnet Labs. All rights reserved. + * + * This software is open source. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * Neither the name of the NLNET LABS nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ +/** + * \file + * Calls dname unit tests. Exits with code 1 on a failure. + */ + +#include "config.h" +#include "util/log.h" +#include "testcode/unitmain.h" +#include "util/data/dname.h" +#include "sldns/sbuffer.h" +#include "sldns/str2wire.h" + +/** put dname into buffer */ +static sldns_buffer* +dname_to_buf(sldns_buffer* b, const char* str) +{ + int e; + size_t len = sldns_buffer_capacity(b); + sldns_buffer_clear(b); + e = sldns_str2wire_dname_buf(str, sldns_buffer_begin(b), &len); + if(e != 0) + fatal_exit("%s ldns: %s", __func__, + sldns_get_errorstr_parse(e)); + sldns_buffer_set_position(b, len); + sldns_buffer_flip(b); + return b; +} + +/** test query_dname_len function */ +static void +dname_test_qdl(sldns_buffer* buff) +{ + unit_show_func("util/data/dname.c", "query_dname_len"); + unit_assert( query_dname_len(buff) == 0); + unit_assert( query_dname_len(dname_to_buf(buff, ".")) == 1 ); + unit_assert( query_dname_len(dname_to_buf(buff, "bla.foo.")) == 9 ); + unit_assert( query_dname_len(dname_to_buf(buff, "x.y.z.example.com." + )) == 19 ); +} + +/** test query_dname_tolower */ +static void +dname_test_qdtl(sldns_buffer* buff) +{ + unit_show_func("util/data/dname.c", "query_dname_tolower"); + sldns_buffer_write_at(buff, 0, "\012abCDeaBCde\003cOm\000", 16); + query_dname_tolower(sldns_buffer_begin(buff)); + unit_assert( memcmp(sldns_buffer_begin(buff), + "\012abcdeabcde\003com\000", 16) == 0); + + sldns_buffer_write_at(buff, 0, "\001+\012abC{e-ZYXe\003NET\000", 18); + query_dname_tolower(sldns_buffer_begin(buff)); + unit_assert( memcmp(sldns_buffer_begin(buff), + "\001+\012abc{e-zyxe\003net\000", 18) == 0); + + sldns_buffer_write_at(buff, 0, "\000", 1); + query_dname_tolower(sldns_buffer_begin(buff)); + unit_assert( memcmp(sldns_buffer_begin(buff), "\000", 1) == 0); + + sldns_buffer_write_at(buff, 0, "\002NL\000", 4); + query_dname_tolower(sldns_buffer_begin(buff)); + unit_assert( memcmp(sldns_buffer_begin(buff), "\002nl\000", 4) == 0); +} + +/** test query_dname_compare */ +static void +dname_test_query_dname_compare(void) +{ + unit_show_func("util/data/dname.c", "query_dname_compare"); + unit_assert(query_dname_compare((uint8_t*)"", (uint8_t*)"") == 0); + unit_assert(query_dname_compare((uint8_t*)"\001a", + (uint8_t*)"\001a") == 0); + unit_assert(query_dname_compare((uint8_t*)"\003abc\001a", + (uint8_t*)"\003abc\001a") == 0); + unit_assert(query_dname_compare((uint8_t*)"\003aBc\001a", + (uint8_t*)"\003AbC\001A") == 0); + unit_assert(query_dname_compare((uint8_t*)"\003abc", + (uint8_t*)"\003abc\001a") == -1); + unit_assert(query_dname_compare((uint8_t*)"\003abc\001a", + (uint8_t*)"\003abc") == +1); + unit_assert(query_dname_compare((uint8_t*)"\003abc\001a", + (uint8_t*)"") == +1); + unit_assert(query_dname_compare((uint8_t*)"", + (uint8_t*)"\003abc\001a") == -1); + unit_assert(query_dname_compare((uint8_t*)"\003abc\001a", + (uint8_t*)"\003xxx\001a") == -1); + unit_assert(query_dname_compare((uint8_t*)"\003axx\001a", + (uint8_t*)"\003abc\001a") == 1); + unit_assert(query_dname_compare((uint8_t*)"\003abc\001a", + (uint8_t*)"\003abc\001Z") == -1); + unit_assert(query_dname_compare((uint8_t*)"\003abc\001Z", + (uint8_t*)"\003abc\001a") == 1); +} + +/** test dname_count_labels */ +static void +dname_test_count_labels(void) +{ + unit_show_func("util/data/dname.c", "dname_count_labels"); + unit_assert(dname_count_labels((uint8_t*)"") == 1); + unit_assert(dname_count_labels((uint8_t*)"\003com") == 2); + unit_assert(dname_count_labels((uint8_t*)"\003org") == 2); + unit_assert(dname_count_labels((uint8_t*)"\007example\003com") == 3); + unit_assert(dname_count_labels((uint8_t*)"\003bla\007example\003com") + == 4); +} + +/** test dname_count_size_labels */ +static void +dname_test_count_size_labels(void) +{ + size_t sz = 0; + unit_show_func("util/data/dname.c", "dname_count_size_labels"); + unit_assert(dname_count_size_labels((uint8_t*)"", &sz) == 1); + unit_assert(sz == 1); + unit_assert(dname_count_size_labels((uint8_t*)"\003com", &sz) == 2); + unit_assert(sz == 5); + unit_assert(dname_count_size_labels((uint8_t*)"\003org", &sz) == 2); + unit_assert(sz == 5); + unit_assert(dname_count_size_labels((uint8_t*)"\007example\003com", + &sz) == 3); + unit_assert(sz == 13); + unit_assert(dname_count_size_labels((uint8_t*)"\003bla\007example" + "\003com", &sz) == 4); + unit_assert(sz == 17); +} + + +/** test pkt_dname_len */ +static void +dname_test_pkt_dname_len(sldns_buffer* buff) +{ + unit_show_func("util/data/dname.c", "pkt_dname_len"); + sldns_buffer_clear(buff); + sldns_buffer_write(buff, "\000", 1); + sldns_buffer_flip(buff); + unit_assert( pkt_dname_len(buff) == 1 ); + unit_assert( sldns_buffer_position(buff) == 1); + + sldns_buffer_clear(buff); + sldns_buffer_write(buff, "\003org\000", 5); + sldns_buffer_flip(buff); + unit_assert( pkt_dname_len(buff) == 5 ); + unit_assert( sldns_buffer_position(buff) == 5); + + sldns_buffer_clear(buff); + sldns_buffer_write(buff, "\002os\007example\003org\000", 16); + sldns_buffer_flip(buff); + unit_assert( pkt_dname_len(buff) == 16 ); + unit_assert( sldns_buffer_position(buff) == 16); + + /* invalid compression pointer: to self */ + sldns_buffer_clear(buff); + sldns_buffer_write(buff, "\300\000os\007example\003org\000", 17); + sldns_buffer_flip(buff); + unit_assert( pkt_dname_len(buff) == 0 ); + + /* valid compression pointer */ + sldns_buffer_clear(buff); + sldns_buffer_write(buff, "\003com\000\040\300\000", 8); + sldns_buffer_flip(buff); + sldns_buffer_set_position(buff, 6); + unit_assert( pkt_dname_len(buff) == 5 ); + unit_assert( sldns_buffer_position(buff) == 8); + + /* unknown label type */ + sldns_buffer_clear(buff); + sldns_buffer_write(buff, "\002os\107example\003org\000", 16); + sldns_buffer_flip(buff); + unit_assert( pkt_dname_len(buff) == 0 ); + + /* label too long */ + sldns_buffer_clear(buff); + sldns_buffer_write(buff, "\002os\047example\003org\000", 16); + sldns_buffer_flip(buff); + unit_assert( pkt_dname_len(buff) == 0 ); + + /* label exceeds packet */ + sldns_buffer_clear(buff); + sldns_buffer_write(buff, "\002os\007example\007org\004", 16); + sldns_buffer_flip(buff); + unit_assert( pkt_dname_len(buff) == 0 ); + + /* name very long */ + sldns_buffer_clear(buff); + sldns_buffer_write(buff, + "\020a1cdef5555544444" + "\020a2cdef5555544444" + "\020a3cdef5555544444" + "\020a4cdef5555544444" + "\020a5cdef5555544444" + "\020a6cdef5555544444" + "\020a7cdef5555544444" + "\020a8cdef5555544444" + "\020a9cdef5555544444" + "\020aAcdef5555544444" + "\020aBcdef5555544444" + "\020aCcdef5555544444" + "\020aDcdef5555544444" + "\020aEcdef5555544444" /* 238 up to here */ + "\007aabbccd" /* 246 up to here */ + "\007example\000" /* 255 to here */ + , 255); + sldns_buffer_flip(buff); + unit_assert( pkt_dname_len(buff) == 255 ); + unit_assert( sldns_buffer_position(buff) == 255); + + /* name too long */ + sldns_buffer_clear(buff); + sldns_buffer_write(buff, + "\020a1cdef5555544444" + "\020a2cdef5555544444" + "\020a3cdef5555544444" + "\020a4cdef5555544444" + "\020a5cdef5555544444" + "\020a6cdef5555544444" + "\020a7cdef5555544444" + "\020a8cdef5555544444" + "\020a9cdef5555544444" + "\020aAcdef5555544444" + "\020aBcdef5555544444" + "\020aCcdef5555544444" + "\020aXcdef5555544444" + "\020aXcdef5555544444" + "\020aXcdef5555544444" + "\020aDcdef5555544444" + "\020aEcdef5555544444" /* 238 up to here */ + "\007aabbccd" /* 246 up to here */ + "\007example\000" /* 255 to here */ + , 255); + sldns_buffer_flip(buff); + unit_assert( pkt_dname_len(buff) == 0 ); +} + +/** test dname_lab_cmp */ +static void +dname_test_dname_lab_cmp(void) +{ + int ml = 0; /* number of labels that matched exactly */ + unit_show_func("util/data/dname.c", "dname_lab_cmp"); + + /* test for equality succeeds */ + unit_assert(dname_lab_cmp((uint8_t*)"", 1, (uint8_t*)"", 1, &ml) == 0); + unit_assert(ml == 1); + unit_assert(dname_lab_cmp( + (uint8_t*)"\003net", 2, + (uint8_t*)"\003net", 2, + &ml) == 0); + unit_assert(ml == 2); + unit_assert(dname_lab_cmp( + (uint8_t*)"\007example\003net", 3, + (uint8_t*)"\007example\003net", 3, + &ml) == 0); + unit_assert(ml == 3); + unit_assert(dname_lab_cmp( + (uint8_t*)"\004test\007example\003net", 4, + (uint8_t*)"\004test\007example\003net", 4, + &ml) == 0); + unit_assert(ml == 4); + + /* root is smaller than anything else */ + unit_assert(dname_lab_cmp( + (uint8_t*)"", 1, + (uint8_t*)"\003net", 2, + &ml) == -1); + unit_assert(ml == 1); + unit_assert(dname_lab_cmp( + (uint8_t*)"\003net", 2, + (uint8_t*)"", 1, + &ml) == 1); + unit_assert(ml == 1); + unit_assert(dname_lab_cmp( + (uint8_t*)"", 1, + (uint8_t*)"\007example\003net", 3, + &ml) == -1); + unit_assert(ml == 1); + unit_assert(dname_lab_cmp( + (uint8_t*)"\007example\003net", 3, + (uint8_t*)"", 1, + &ml) == 1); + unit_assert(ml == 1); + + /* label length makes a difference */ + unit_assert(dname_lab_cmp( + (uint8_t*)"\004neta", 2, + (uint8_t*)"\003net", 2, + &ml) != 0); + unit_assert(ml == 1); + unit_assert(dname_lab_cmp( + (uint8_t*)"\002ne", 2, + (uint8_t*)"\004neta", 2, + &ml) != 0); + unit_assert(ml == 1); + + /* contents follow the zone apex */ + unit_assert(dname_lab_cmp( + (uint8_t*)"\003bla\007example\003net", 4, + (uint8_t*)"\007example\003net", 3, + &ml) == 1); + unit_assert(ml == 3); + unit_assert(dname_lab_cmp( + (uint8_t*)"\007example\003net", 3, + (uint8_t*)"\003bla\007example\003net", 4, + &ml) == -1); + unit_assert(ml == 3); + + /* label content makes a difference */ + unit_assert(dname_lab_cmp( + (uint8_t*)"\003aag\007example\003net", 4, + (uint8_t*)"\003bla\007example\003net", 4, + &ml) == -1); + unit_assert(ml == 3); + unit_assert(dname_lab_cmp( + (uint8_t*)"\003aag\007example\003net", 4, + (uint8_t*)"\003bla\007example\003net", 4, + &ml) == -1); + unit_assert(ml == 3); + unit_assert(dname_lab_cmp( + (uint8_t*)"\003bla\003aag\007example\003net", 5, + (uint8_t*)"\003aag\003bla\007example\003net", 5, + &ml) == -1); + unit_assert(ml == 3); + unit_assert(dname_lab_cmp( + (uint8_t*)"\02sn\003opt\003aag\007example\003net", 6, + (uint8_t*)"\02sn\003opt\003bla\007example\003net", 6, + &ml) == -1); + unit_assert(ml == 3); + + /* but lowercase/uppercase does not make a difference. */ + unit_assert(dname_lab_cmp( + (uint8_t*)"\003bLa\007examPLe\003net", 4, + (uint8_t*)"\003bla\007eXAmple\003nET", 4, + &ml) == 0); + unit_assert(ml == 4); +} + +/** test dname_subdomain_c */ +static void +dname_test_subdomain(void) +{ + unit_show_func("util/data/dname.c", "dname_subdomain"); + unit_assert(dname_subdomain_c( + (uint8_t*)"", + (uint8_t*)"")); + unit_assert(dname_subdomain_c( + (uint8_t*)"\003com", + (uint8_t*)"")); + unit_assert(!dname_subdomain_c( + (uint8_t*)"", + (uint8_t*)"\003com")); + unit_assert(dname_subdomain_c( + (uint8_t*)"\007example\003com", + (uint8_t*)"\003com")); + unit_assert(!dname_subdomain_c( + (uint8_t*)"\003com", + (uint8_t*)"\007example\003com")); + unit_assert(dname_subdomain_c( + (uint8_t*)"\007example\003com", + (uint8_t*)"")); + unit_assert(!dname_subdomain_c( + (uint8_t*)"\003net", + (uint8_t*)"\003com")); + unit_assert(!dname_subdomain_c( + (uint8_t*)"\003net", + (uint8_t*)"\003org")); + unit_assert(!dname_subdomain_c( + (uint8_t*)"\007example\003net", + (uint8_t*)"\003org")); + unit_assert(!dname_subdomain_c( + (uint8_t*)"\003net", + (uint8_t*)"\007example\003org")); +} + +/** test dname_strict_subdomain */ +static void +dname_test_strict_subdomain(void) +{ + unit_show_func("util/data/dname.c", "dname_strict_subdomain"); + unit_assert(!dname_strict_subdomain( + (uint8_t*)"", 1, + (uint8_t*)"", 1)); + unit_assert(dname_strict_subdomain( + (uint8_t*)"\003com", 2, + (uint8_t*)"", 1)); + unit_assert(!dname_strict_subdomain( + (uint8_t*)"", 1, + (uint8_t*)"\003com", 2)); + unit_assert(dname_strict_subdomain( + (uint8_t*)"\007example\003com", 3, + (uint8_t*)"\003com", 2)); + unit_assert(!dname_strict_subdomain( + (uint8_t*)"\003com", 2, + (uint8_t*)"\007example\003com", 3)); + unit_assert(dname_strict_subdomain( + (uint8_t*)"\007example\003com", 3, + (uint8_t*)"", 1)); + unit_assert(!dname_strict_subdomain( + (uint8_t*)"\003net", 2, + (uint8_t*)"\003com", 2)); + unit_assert(!dname_strict_subdomain( + (uint8_t*)"\003net", 2, + (uint8_t*)"\003org", 2)); + unit_assert(!dname_strict_subdomain( + (uint8_t*)"\007example\003net", 3, + (uint8_t*)"\003org", 2)); + unit_assert(!dname_strict_subdomain( + (uint8_t*)"\003net", 2, + (uint8_t*)"\007example\003org", 3)); +} + +/** test dname_is_root */ +static void +dname_test_isroot(void) +{ + unit_show_func("util/data/dname.c", "dname_isroot"); + unit_assert(dname_is_root((uint8_t*)"\000")); + unit_assert(!dname_is_root((uint8_t*)"\001a\000")); + unit_assert(!dname_is_root((uint8_t*)"\005abvcd\003com\000")); + /* malformed dname in this test, but should work */ + unit_assert(!dname_is_root((uint8_t*)"\077a\000")); + unit_assert(dname_is_root((uint8_t*)"\000")); +} + +/** test dname_remove_label */ +static void +dname_test_removelabel(void) +{ + uint8_t* orig = (uint8_t*)"\007example\003com\000"; + uint8_t* n = orig; + size_t l = 13; + unit_show_func("util/data/dname.c", "dname_remove_label"); + dname_remove_label(&n, &l); + unit_assert( n == orig+8 ); + unit_assert( l == 5 ); + dname_remove_label(&n, &l); + unit_assert( n == orig+12 ); + unit_assert( l == 1 ); + dname_remove_label(&n, &l); + unit_assert( n == orig+12 ); + unit_assert( l == 1 ); +} + +/** test dname_signame_label_count */ +static void +dname_test_sigcount(void) +{ + unit_show_func("util/data/dname.c", "dname_signame_label_count"); + unit_assert(dname_signame_label_count((uint8_t*)"\000") == 0); + unit_assert(dname_signame_label_count((uint8_t*)"\001*\000") == 0); + unit_assert(dname_signame_label_count((uint8_t*)"\003xom\000") == 1); + unit_assert(dname_signame_label_count( + (uint8_t*)"\001*\003xom\000") == 1); + unit_assert(dname_signame_label_count( + (uint8_t*)"\007example\003xom\000") == 2); + unit_assert(dname_signame_label_count( + (uint8_t*)"\001*\007example\003xom\000") == 2); + unit_assert(dname_signame_label_count( + (uint8_t*)"\003www\007example\003xom\000") == 3); + unit_assert(dname_signame_label_count( + (uint8_t*)"\001*\003www\007example\003xom\000") == 3); +} + +/** test dname_is_wild routine */ +static void +dname_test_iswild(void) +{ + unit_show_func("util/data/dname.c", "dname_iswild"); + unit_assert( !dname_is_wild((uint8_t*)"\000") ); + unit_assert( dname_is_wild((uint8_t*)"\001*\000") ); + unit_assert( !dname_is_wild((uint8_t*)"\003net\000") ); + unit_assert( dname_is_wild((uint8_t*)"\001*\003net\000") ); +} + +/** test dname_canonical_compare */ +static void +dname_test_canoncmp(void) +{ + unit_show_func("util/data/dname.c", "dname_canonical_compare"); + /* equality */ + unit_assert( dname_canonical_compare( + (uint8_t*)"\000", + (uint8_t*)"\000" + ) == 0); + unit_assert( dname_canonical_compare( + (uint8_t*)"\003net\000", + (uint8_t*)"\003net\000" + ) == 0); + unit_assert( dname_canonical_compare( + (uint8_t*)"\007example\003net\000", + (uint8_t*)"\007example\003net\000" + ) == 0); + unit_assert( dname_canonical_compare( + (uint8_t*)"\004test\007example\003net\000", + (uint8_t*)"\004test\007example\003net\000" + ) == 0); + + /* subdomains */ + unit_assert( dname_canonical_compare( + (uint8_t*)"\003com", + (uint8_t*)"\000" + ) == 1); + unit_assert( dname_canonical_compare( + (uint8_t*)"\000", + (uint8_t*)"\003com" + ) == -1); + unit_assert( dname_canonical_compare( + (uint8_t*)"\007example\003com", + (uint8_t*)"\003com" + ) == 1); + unit_assert( dname_canonical_compare( + (uint8_t*)"\003com", + (uint8_t*)"\007example\003com" + ) == -1); + unit_assert( dname_canonical_compare( + (uint8_t*)"\007example\003com", + (uint8_t*)"\000" + ) == 1); + unit_assert( dname_canonical_compare( + (uint8_t*)"\000", + (uint8_t*)"\007example\003com" + ) == -1); + + /* compare rightmost label */ + unit_assert( dname_canonical_compare( + (uint8_t*)"\003com", + (uint8_t*)"\003net" + ) == -1); + unit_assert( dname_canonical_compare( + (uint8_t*)"\003net", + (uint8_t*)"\003com" + ) == 1); + unit_assert( dname_canonical_compare( + (uint8_t*)"\003net", + (uint8_t*)"\003org" + ) == -1); + unit_assert( dname_canonical_compare( + (uint8_t*)"\007example\003net", + (uint8_t*)"\003org" + ) == -1); + unit_assert( dname_canonical_compare( + (uint8_t*)"\003org", + (uint8_t*)"\007example\003net" + ) == 1); + + /* label length makes a difference; but only if rest is equal */ + unit_assert( dname_canonical_compare( + (uint8_t*)"\004neta", + (uint8_t*)"\003net" + ) == 1); + unit_assert( dname_canonical_compare( + (uint8_t*)"\002ne", + (uint8_t*)"\004neta" + ) == -1); + + /* label content */ + unit_assert( dname_canonical_compare( + (uint8_t*)"\003aag\007example\003net", + (uint8_t*)"\003bla\007example\003net" + ) == -1); + unit_assert( dname_canonical_compare( + (uint8_t*)"\003bla\007example\003net", + (uint8_t*)"\003aag\007example\003net" + ) == 1); + unit_assert( dname_canonical_compare( + (uint8_t*)"\003bla\003aag\007example\003net", + (uint8_t*)"\003aag\003bla\007example\003net" + ) == -1); + unit_assert( dname_canonical_compare( + (uint8_t*)"\02sn\003opt\003aag\007example\003net", + (uint8_t*)"\02sn\003opt\003bla\007example\003net" + ) == -1); + + /* lowercase during compare */ + unit_assert( dname_canonical_compare( + (uint8_t*)"\003bLa\007examPLe\003net", + (uint8_t*)"\003bla\007eXAmple\003nET" + ) == 0); + + /* example from 4034 */ + /* example a.example yljkjljk.a.example Z.a.example zABC.a.EXAMPLE + z.example \001.z.example *.z.example \200.z.example */ + unit_assert( dname_canonical_compare( + (uint8_t*)"", + (uint8_t*)"\007example" + ) == -1); + unit_assert( dname_canonical_compare( + (uint8_t*)"\007example", + (uint8_t*)"\001a\007example" + ) == -1); + unit_assert( dname_canonical_compare( + (uint8_t*)"\001a\007example", + (uint8_t*)"\010yljkjljk\001a\007example" + ) == -1); + unit_assert( dname_canonical_compare( + (uint8_t*)"\010yljkjljk\001a\007example", + (uint8_t*)"\001Z\001a\007example" + ) == -1); + unit_assert( dname_canonical_compare( + (uint8_t*)"\001Z\001a\007example", + (uint8_t*)"\004zABC\001a\007EXAMPLE" + ) == -1); + unit_assert( dname_canonical_compare( + (uint8_t*)"\004zABC\001a\007EXAMPLE", + (uint8_t*)"\001z\007example" + ) == -1); + unit_assert( dname_canonical_compare( + (uint8_t*)"\001z\007example", + (uint8_t*)"\001\001\001z\007example" + ) == -1); + unit_assert( dname_canonical_compare( + (uint8_t*)"\001\001\001z\007example", + (uint8_t*)"\001*\001z\007example" + ) == -1); + unit_assert( dname_canonical_compare( + (uint8_t*)"\001*\001z\007example", + (uint8_t*)"\001\200\001z\007example" + ) == -1); + /* same example in reverse */ + unit_assert( dname_canonical_compare( + (uint8_t*)"\007example", + (uint8_t*)"" + ) == 1); + unit_assert( dname_canonical_compare( + (uint8_t*)"\001a\007example", + (uint8_t*)"\007example" + ) == 1); + unit_assert( dname_canonical_compare( + (uint8_t*)"\010yljkjljk\001a\007example", + (uint8_t*)"\001a\007example" + ) == 1); + unit_assert( dname_canonical_compare( + (uint8_t*)"\001Z\001a\007example", + (uint8_t*)"\010yljkjljk\001a\007example" + ) == 1); + unit_assert( dname_canonical_compare( + (uint8_t*)"\004zABC\001a\007EXAMPLE", + (uint8_t*)"\001Z\001a\007example" + ) == 1); + unit_assert( dname_canonical_compare( + (uint8_t*)"\001z\007example", + (uint8_t*)"\004zABC\001a\007EXAMPLE" + ) == 1); + unit_assert( dname_canonical_compare( + (uint8_t*)"\001\001\001z\007example", + (uint8_t*)"\001z\007example" + ) == 1); + unit_assert( dname_canonical_compare( + (uint8_t*)"\001*\001z\007example", + (uint8_t*)"\001\001\001z\007example" + ) == 1); + unit_assert( dname_canonical_compare( + (uint8_t*)"\001\200\001z\007example", + (uint8_t*)"\001*\001z\007example" + ) == 1); + /* same example for equality */ + unit_assert( dname_canonical_compare( + (uint8_t*)"\007example", + (uint8_t*)"\007example" + ) == 0); + unit_assert( dname_canonical_compare( + (uint8_t*)"\001a\007example", + (uint8_t*)"\001a\007example" + ) == 0); + unit_assert( dname_canonical_compare( + (uint8_t*)"\010yljkjljk\001a\007example", + (uint8_t*)"\010yljkjljk\001a\007example" + ) == 0); + unit_assert( dname_canonical_compare( + (uint8_t*)"\001Z\001a\007example", + (uint8_t*)"\001Z\001a\007example" + ) == 0); + unit_assert( dname_canonical_compare( + (uint8_t*)"\004zABC\001a\007EXAMPLE", + (uint8_t*)"\004zABC\001a\007EXAMPLE" + ) == 0); + unit_assert( dname_canonical_compare( + (uint8_t*)"\001z\007example", + (uint8_t*)"\001z\007example" + ) == 0); + unit_assert( dname_canonical_compare( + (uint8_t*)"\001\001\001z\007example", + (uint8_t*)"\001\001\001z\007example" + ) == 0); + unit_assert( dname_canonical_compare( + (uint8_t*)"\001*\001z\007example", + (uint8_t*)"\001*\001z\007example" + ) == 0); + unit_assert( dname_canonical_compare( + (uint8_t*)"\001\200\001z\007example", + (uint8_t*)"\001\200\001z\007example" + ) == 0); +} + +/** Test dname_get_shared_topdomain */ +static void +dname_test_topdomain(void) +{ + unit_show_func("util/data/dname.c", "dname_get_shared_topdomain"); + unit_assert( query_dname_compare( + dname_get_shared_topdomain( + (uint8_t*)"", + (uint8_t*)""), + (uint8_t*)"") == 0); + unit_assert( query_dname_compare( + dname_get_shared_topdomain( + (uint8_t*)"\003www\007example\003com", + (uint8_t*)"\003www\007example\003com"), + (uint8_t*)"\003www\007example\003com") == 0); + unit_assert( query_dname_compare( + dname_get_shared_topdomain( + (uint8_t*)"\003www\007example\003com", + (uint8_t*)"\003bla\007example\003com"), + (uint8_t*)"\007example\003com") == 0); +} + +/** Test dname_valid */ +static void +dname_test_valid(void) +{ + unit_show_func("util/data/dname.c", "dname_valid"); + unit_assert( dname_valid( + (uint8_t*)"\003www\007example\003com", 255) == 17); + unit_assert( dname_valid((uint8_t*)"", 255) == 1); + unit_assert( dname_valid( (uint8_t*) + "\020a1cdef5555544444" + "\020a2cdef5555544444" + "\020a3cdef5555544444" + "\020a4cdef5555544444" + "\020a5cdef5555544444" + "\020a6cdef5555544444" + "\020a7cdef5555544444" + "\020a8cdef5555544444" + "\020a9cdef5555544444" + "\020aAcdef5555544444" + "\020aBcdef5555544444" + "\020aCcdef5555544444" + "\020aDcdef5555544444" + "\020aEcdef5555544444" /* 238 up to here */ + "\007aabbccd" /* 246 up to here */ + "\007example\000" /* 255 to here */ + , 255) == 255); + unit_assert( dname_valid( (uint8_t*) + "\020a1cdef5555544444" + "\020a2cdef5555544444" + "\020a3cdef5555544444" + "\020a4cdef5555544444" + "\020a5cdef5555544444" + "\020a6cdef5555544444" + "\020a7cdef5555544444" + "\020a8cdef5555544444" + "\020a9cdef5555544444" + "\020aAcdef5555544444" + "\020aBcdef5555544444" + "\020aCcdef5555544444" + "\020aDcdef5555544444" + "\020aEcdef5555544444" /* 238 up to here */ + "\007aabbccd" /* 246 up to here */ + "\010exampleX\000" /* 256 to here */ + , 4096) == 0); +} + +/** test pkt_dname_tolower */ +static void +dname_test_pdtl(sldns_buffer* loopbuf, sldns_buffer* boundbuf) +{ + unit_show_func("util/data/dname.c", "pkt_dname_tolower"); + pkt_dname_tolower(loopbuf, sldns_buffer_at(loopbuf, 12)); + pkt_dname_tolower(boundbuf, sldns_buffer_at(boundbuf, 12)); +} + +/** setup looped dname and out-of-bounds dname ptr */ +static void +dname_setup_bufs(sldns_buffer* loopbuf, sldns_buffer* boundbuf) +{ + sldns_buffer_write_u16(loopbuf, 0xd54d); /* id */ + sldns_buffer_write_u16(loopbuf, 0x12); /* flags */ + sldns_buffer_write_u16(loopbuf, 1); /* qdcount */ + sldns_buffer_write_u16(loopbuf, 0); /* ancount */ + sldns_buffer_write_u16(loopbuf, 0); /* nscount */ + sldns_buffer_write_u16(loopbuf, 0); /* arcount */ + sldns_buffer_write_u8(loopbuf, 0xc0); /* PTR back at itself */ + sldns_buffer_write_u8(loopbuf, 0x0c); + sldns_buffer_flip(loopbuf); + + sldns_buffer_write_u16(boundbuf, 0xd54d); /* id */ + sldns_buffer_write_u16(boundbuf, 0x12); /* flags */ + sldns_buffer_write_u16(boundbuf, 1); /* qdcount */ + sldns_buffer_write_u16(boundbuf, 0); /* ancount */ + sldns_buffer_write_u16(boundbuf, 0); /* nscount */ + sldns_buffer_write_u16(boundbuf, 0); /* arcount */ + sldns_buffer_write_u8(boundbuf, 0x01); /* len=1 */ + sldns_buffer_write_u8(boundbuf, (uint8_t)'A'); /* A. label */ + sldns_buffer_write_u8(boundbuf, 0xc0); /* PTR out of bounds */ + sldns_buffer_write_u8(boundbuf, 0xcc); + sldns_buffer_flip(boundbuf); +} + +void dname_test(void) +{ + sldns_buffer* loopbuf = sldns_buffer_new(14); + sldns_buffer* boundbuf = sldns_buffer_new(16); + sldns_buffer* buff = sldns_buffer_new(65800); + unit_assert(loopbuf && boundbuf && buff); + sldns_buffer_flip(buff); + dname_setup_bufs(loopbuf, boundbuf); + dname_test_qdl(buff); + dname_test_qdtl(buff); + dname_test_pdtl(loopbuf, boundbuf); + dname_test_query_dname_compare(); + dname_test_count_labels(); + dname_test_count_size_labels(); + dname_test_dname_lab_cmp(); + dname_test_pkt_dname_len(buff); + dname_test_strict_subdomain(); + dname_test_subdomain(); + dname_test_isroot(); + dname_test_removelabel(); + dname_test_sigcount(); + dname_test_iswild(); + dname_test_canoncmp(); + dname_test_topdomain(); + dname_test_valid(); + sldns_buffer_free(buff); + sldns_buffer_free(loopbuf); + sldns_buffer_free(boundbuf); +} diff --git a/usr.sbin/unbound/testcode/unitecs.c b/usr.sbin/unbound/testcode/unitecs.c new file mode 100644 index 00000000000..097ae9ebba6 --- /dev/null +++ b/usr.sbin/unbound/testcode/unitecs.c @@ -0,0 +1,284 @@ +/* + * testcode/unitecs.c - unit test for ecs routines. + * + * Copyright (c) 2013, NLnet Labs. All rights reserved. + * + * This software is open source. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * Neither the name of the NLNET LABS nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + */ + +/** + * \file + * Calls ecs related unit tests. Exits with code 1 on a failure. + */ + +#include "config.h" + +#ifdef CLIENT_SUBNET + +#include "util/log.h" +#include "util/module.h" +#include "testcode/unitmain.h" +#include "edns-subnet/addrtree.h" +#include "edns-subnet/subnetmod.h" + +/* + void printkey(addrkey_t *k, addrlen_t bits) + { + int byte; + int bytes = bits/8 + ((bits%8)>0); + char msk = 0xFF; + for (byte = 0; byte < bytes; byte++) { + //~ if (byte+1 == bytes) + //~ msk = 0xFF<<(8-bits%8); + printf("%02x ", k[byte]&msk); + } + } + + void print_tree(struct addrnode* node, int indent, int maxdepth) + { + struct addredge* edge; + int i, s, byte; + if (indent == 0) printf("-----Tree-----\n"); + if (indent > maxdepth) { + printf("\n"); + return; + } + printf("[node elem:%d] (%d)\n", node->elem != NULL, node); + for (i = 0; i<2; i++) { + if (node->edge[i]) { + for (s = 0; s < indent; s++) printf(" "); + printkey(node->edge[i]->str, node->edge[i]->len); + printf("(len %d bits, %d bytes) ", node->edge[i]->len, + node->edge[i]->len/8 + ((node->edge[i]->len%8)>0)); + print_tree(node->edge[i]->node, indent+1, maxdepth); + } + } + if (indent == 0) printf("-----Tree-----"); + } +*/ + +/* what should we check? + * X - is it balanced? (a node with 1 child should not have + * a node with 1 child MUST have elem + * child must be sub of parent + * edge must be longer than parent edge + * */ +static int addrtree_inconsistent_subtree(struct addrtree* tree, + struct addredge* parent_edge, addrlen_t depth) +{ + struct addredge* edge; + struct addrnode* node = parent_edge->node; + int childcount, i, r; + if (depth > tree->max_depth) return 15; + childcount = (node->edge[0] != NULL) + (node->edge[1] != NULL); + /* Only nodes with 2 children should possibly have no element. */ + if (childcount < 2 && !node->elem) return 10; + for (i = 0; i<2; i++) { + edge = node->edge[i]; + if (!edge) continue; + if (!edge->node) return 11; + if (!edge->str) return 12; + if (edge->len <= parent_edge->len) return 13; + if (!unittest_wrapper_addrtree_issub(parent_edge->str, + parent_edge->len, edge->str, edge->len, 0)) + return 14; + if ((r = addrtree_inconsistent_subtree(tree, edge, depth+1)) != 0) + return 100+r; + } + return 0; +} + +static int addrtree_inconsistent(struct addrtree* tree) +{ + struct addredge* edge; + int i, r; + + if (!tree) return 0; + if (!tree->root) return 1; + + for (i = 0; i<2; i++) { + edge = tree->root->edge[i]; + if (!edge) continue; + if (!edge->node) return 3; + if (!edge->str) return 4; + if ((r = addrtree_inconsistent_subtree(tree, edge, 1)) != 0) + return r; + } + return 0; +} + +static addrlen_t randomkey(addrkey_t **k, int maxlen) +{ + int byte; + int bits = rand() % maxlen; + int bytes = bits/8 + (bits%8>0); /*ceil*/ + *k = (addrkey_t *) malloc(bytes * sizeof(addrkey_t)); + for (byte = 0; byte < bytes; byte++) { + (*k)[byte] = (addrkey_t)(rand() & 0xFF); + } + return (addrlen_t)bits; +} + +static void elemfree(void *envptr, void *elemptr) +{ + struct reply_info *elem = (struct reply_info *)elemptr; + (void)envptr; + free(elem); +} + +static void consistency_test(void) +{ + addrlen_t l; + time_t i; + unsigned int count; + addrkey_t *k; + struct addrtree* t; + struct module_env env; + struct reply_info *elem; + time_t timenow = 0; + unit_show_func("edns-subnet/addrtree.h", "Tree consistency check"); + srand(9195); /* just some value for reproducibility */ + + t = addrtree_create(100, &elemfree, &unittest_wrapper_subnetmod_sizefunc, &env, 0); + count = t->node_count; + unit_assert(count == 0); + for (i = 0; i < 1000; i++) { + l = randomkey(&k, 128); + elem = (struct reply_info *) calloc(1, sizeof(struct reply_info)); + addrtree_insert(t, k, l, 64, elem, timenow + 10, timenow); + /* This should always hold because no items ever expire. They + * could be overwritten, though. */ + unit_assert( count <= t->node_count ); + count = t->node_count; + free(k); + unit_assert( !addrtree_inconsistent(t) ); + } + addrtree_delete(t); + + unit_show_func("edns-subnet/addrtree.h", "Tree consistency with purge"); + t = addrtree_create(8, &elemfree, &unittest_wrapper_subnetmod_sizefunc, &env, 0); + unit_assert(t->node_count == 0); + for (i = 0; i < 1000; i++) { + l = randomkey(&k, 128); + elem = (struct reply_info *) calloc(1, sizeof(struct reply_info)); + addrtree_insert(t, k, l, 64, elem, i + 10, i); + free(k); + unit_assert( !addrtree_inconsistent(t) ); + } + addrtree_delete(t); + + unit_show_func("edns-subnet/addrtree.h", "Tree consistency with limit"); + t = addrtree_create(8, &elemfree, &unittest_wrapper_subnetmod_sizefunc, &env, 27); + unit_assert(t->node_count == 0); + for (i = 0; i < 1000; i++) { + l = randomkey(&k, 128); + elem = (struct reply_info *) calloc(1, sizeof(struct reply_info)); + addrtree_insert(t, k, l, 64, elem, i + 10, i); + unit_assert( t->node_count <= 27); + free(k); + unit_assert( !addrtree_inconsistent(t) ); + } + addrtree_delete(t); +} + +static void issub_test(void) +{ + addrkey_t k1[] = {0x55, 0x55, 0x5A}; + addrkey_t k2[] = {0x55, 0x5D, 0x5A}; + unit_show_func("edns-subnet/addrtree.h", "issub"); + unit_assert( !unittest_wrapper_addrtree_issub(k1, 24, k2, 24, 0) ); + unit_assert( unittest_wrapper_addrtree_issub(k1, 8, k2, 16, 0) ); + unit_assert( unittest_wrapper_addrtree_issub(k2, 12, k1, 13, 0) ); + unit_assert( !unittest_wrapper_addrtree_issub(k1, 16, k2, 12, 0) ); + unit_assert( unittest_wrapper_addrtree_issub(k1, 12, k2, 12, 0) ); + unit_assert( !unittest_wrapper_addrtree_issub(k1, 13, k2, 13, 0) ); + unit_assert( unittest_wrapper_addrtree_issub(k1, 24, k2, 24, 13) ); + unit_assert( !unittest_wrapper_addrtree_issub(k1, 24, k2, 20, 13) ); + unit_assert( unittest_wrapper_addrtree_issub(k1, 20, k2, 24, 13) ); +} + +static void getbit_test(void) +{ + addrkey_t k1[] = {0x55, 0x55, 0x5A}; + int i; + unit_show_func("edns-subnet/addrtree.h", "getbit"); + for(i = 0; i<20; i++) { + unit_assert( unittest_wrapper_addrtree_getbit(k1, 20, (addrlen_t)i) == (i&1) ); + } +} + +static void bits_common_test(void) +{ + addrkey_t k1[] = {0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0}; + addrkey_t k2[] = {0,0,0,0,0,0,0,0}; + addrlen_t i; + + unit_show_func("edns-subnet/addrtree.h", "bits_common"); + for(i = 0; i<64; i++) { + unit_assert( unittest_wrapper_addrtree_bits_common(k1, 64, k1, 64, i) == 64 ); + } + for(i = 0; i<8; i++) { + k2[i] = k1[i]^(1<= blen*2+2); + for(i=0; i>4]; + s[i*2+1] = h[b[i]&0x0f]; + } + s[blen*2] = '\n'; + s[blen*2+1] = 0; +} + +/** Transform input. + * @param txt_in: input text format. + * @param wire1: output wireformat in hex (txt_in converted to wire). + * @param txt_out: output text format (converted from wire_out). + * @param wire2: output wireformat in hex, txt_out converted back to wireformat. + * @param bufs: size of the text buffers. + */ +static void +rr_transform(char* txt_in, char* wire1, char* txt_out, char* wire2, + size_t bufs) +{ + uint8_t b[65536]; + size_t len; + int err; + + len = sizeof(b); + err = sldns_str2wire_rr_buf(txt_in, b, &len, NULL, 3600, + NULL, 0, NULL, 0); + if(err != 0) { + if(vbmp) printf("sldns_str2wire_rr_buf, pos %d: %s\n", + LDNS_WIREPARSE_OFFSET(err), + sldns_get_errorstr_parse(err)); + } + unit_assert(err == 0); + buf_to_hex(b, len, wire1, bufs); + if(vbmp) printf("wire1: %s", wire1); + + err = sldns_wire2str_rr_buf(b, len, txt_out, bufs); + unit_assert(err < (int)bufs && err > 0); + if(vbmp) printf("txt: %s", txt_out); + + len = sizeof(b); + err = sldns_str2wire_rr_buf(txt_out, b, &len, NULL, 3600, + NULL, 0, NULL, 0); + if(err != 0) { + if(vbmp) printf("sldns_str2wire_rr_buf-2, pos %d: %s\n", + LDNS_WIREPARSE_OFFSET(err), + sldns_get_errorstr_parse(err)); + } + unit_assert(err == 0); + buf_to_hex(b, len, wire2, bufs); + if(vbmp) printf("wire2: %s", wire2); +} + +/** Check if results are correct */ +static void +rr_checks(char* wire_chk, char* txt_chk, char* txt_out, char* wire_out, + char* back) +{ +#ifdef __APPLE__ + /* the wiretostr on ipv6 is weird on apple, we cannot check it. + * skip AAAA on OSX */ + if(strstr(txt_out, "IN AAAA")) + txt_out = txt_chk; /* skip this test, but test wirefmt */ + /* so we know that txt_out back to wire is the same */ +#endif + + if(strcmp(txt_chk, txt_out) != 0 && vbmp) + printf("txt different\n"); + if(strcmp(wire_chk, wire_out) != 0 && vbmp) + printf("wire1 different\n"); + if(strcmp(wire_chk, back) != 0 && vbmp) + printf("wire2 different\n"); + + unit_assert(strcmp(txt_chk, txt_out) == 0); + unit_assert(strcmp(wire_chk, wire_out) == 0); + unit_assert(strcmp(wire_chk, back) == 0); +} + +/** read rrs to and from string, and wireformat + * Skips empty lines and comments. + * @param input: input file with text format. + * @param check: check file with hex and then textformat + */ +static void +rr_test_file(const char* input, const char* check) +{ + size_t bufs = 131072; + FILE* inf, *chf, *of; + int lineno = 0, chlineno = 0; + char* txt_in = (char*)malloc(bufs); + char* txt_out = (char*)malloc(bufs); + char* txt_chk = (char*)malloc(bufs); + char* wire_out = (char*)malloc(bufs); + char* wire_chk = (char*)malloc(bufs); + char* back = (char*)malloc(bufs); + if(!txt_in || !txt_out || !txt_chk || !wire_out || !wire_chk || !back) + fatal_exit("malloc failure"); + inf = fopen(input, "r"); + if(!inf) fatal_exit("cannot open %s: %s", input, strerror(errno)); + chf = fopen(check, "r"); + if(!chf) fatal_exit("cannot open %s: %s", check, strerror(errno)); + + of = NULL; + if(0) { + /* debug: create check file */ + of = fopen("outputfile", "w"); + if(!of) fatal_exit("cannot write output: %s", strerror(errno)); + } + + while(fgets(txt_in, (int)bufs, inf)) { + lineno++; + if(vbmp) printf("\n%s:%d %s", input, lineno, txt_in); + /* skip empty lines and comments */ + if(txt_in[0] == 0 || txt_in[0] == '\n' || txt_in[0] == ';') + continue; + /* read check lines */ + if(!fgets(wire_chk, (int)bufs, chf)) + printf("%s too short\n", check); + if(!fgets(txt_chk, (int)bufs, chf)) + printf("%s too short\n", check); + chlineno += 2; + if(vbmp) printf("%s:%d %s", check, chlineno-1, wire_chk); + if(vbmp) printf("%s:%d %s", check, chlineno, txt_chk); + /* generate results */ + rr_transform(txt_in, wire_out, txt_out, back, bufs); + /* checks */ + if(of) { + fprintf(of, "%s%s", wire_out, txt_out); + } else { + rr_checks(wire_chk, txt_chk, txt_out, wire_out, back); + } + } + + if(of) fclose(of); + fclose(inf); + fclose(chf); + free(txt_in); + free(txt_out); + free(txt_chk); + free(wire_out); + free(wire_chk); + free(back); +} + +/** read rrs to and from string, to and from wireformat */ +static void +rr_tests(void) +{ + rr_test_file("testdata/test_ldnsrr.1", "testdata/test_ldnsrr.c1"); + rr_test_file("testdata/test_ldnsrr.2", "testdata/test_ldnsrr.c2"); + rr_test_file("testdata/test_ldnsrr.3", "testdata/test_ldnsrr.c3"); + rr_test_file("testdata/test_ldnsrr.4", "testdata/test_ldnsrr.c4"); + rr_test_file("testdata/test_ldnsrr.5", "testdata/test_ldnsrr.c5"); +} + +void +ldns_test(void) +{ + unit_show_feature("sldns"); + rr_tests(); +} diff --git a/usr.sbin/unbound/testcode/unitlruhash.c b/usr.sbin/unbound/testcode/unitlruhash.c new file mode 100644 index 00000000000..e196f0b6321 --- /dev/null +++ b/usr.sbin/unbound/testcode/unitlruhash.c @@ -0,0 +1,499 @@ +/* + * testcode/unitlruhash.c - unit test for lruhash table. + * + * Copyright (c) 2007, NLnet Labs. All rights reserved. + * + * This software is open source. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * Neither the name of the NLNET LABS nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ +/** + * \file + * Tests the locking LRU keeping hash table implementation. + */ + +#include "config.h" +#include "testcode/unitmain.h" +#include "util/log.h" +#include "util/storage/lruhash.h" +#include "util/storage/slabhash.h" /* for the test structures */ + +/** use this type for the lruhash test key */ +typedef struct slabhash_testkey testkey_type; +/** use this type for the lruhash test data */ +typedef struct slabhash_testdata testdata_type; + +/** delete key */ +static void delkey(struct slabhash_testkey* k) { + lock_rw_destroy(&k->entry.lock); free(k);} +/** delete data */ +static void deldata(struct slabhash_testdata* d) {free(d);} + +/** hash func, very bad to improve collisions */ +static hashvalue_type myhash(int id) {return (hashvalue_type)id & 0x0f;} +/** allocate new key, fill in hash */ +static testkey_type* newkey(int id) { + testkey_type* k = (testkey_type*)calloc(1, sizeof(testkey_type)); + if(!k) fatal_exit("out of memory"); + k->id = id; + k->entry.hash = myhash(id); + k->entry.key = k; + lock_rw_init(&k->entry.lock); + return k; +} +/** new data el */ +static testdata_type* newdata(int val) { + testdata_type* d = (testdata_type*)calloc(1, + sizeof(testdata_type)); + if(!d) fatal_exit("out of memory"); + d->data = val; + return d; +} + +/** test bin_find_entry function and bin_overflow_remove */ +static void +test_bin_find_entry(struct lruhash* table) +{ + testkey_type* k = newkey(12); + testdata_type* d = newdata(128); + testkey_type* k2 = newkey(12 + 1024); + testkey_type* k3 = newkey(14); + testkey_type* k4 = newkey(12 + 1024*2); + hashvalue_type h = myhash(12); + struct lruhash_bin bin; + memset(&bin, 0, sizeof(bin)); + bin_init(&bin, 1); + + /* remove from empty list */ + bin_overflow_remove(&bin, &k->entry); + + /* find in empty list */ + unit_assert( bin_find_entry(table, &bin, h, k) == NULL ); + + /* insert */ + lock_quick_lock(&bin.lock); + bin.overflow_list = &k->entry; + lock_quick_unlock(&bin.lock); + + /* find, hash not OK. */ + unit_assert( bin_find_entry(table, &bin, myhash(13), k) == NULL ); + + /* find, hash OK, but cmp not */ + unit_assert( k->entry.hash == k2->entry.hash ); + unit_assert( bin_find_entry(table, &bin, h, k2) == NULL ); + + /* find, hash OK, and cmp too */ + unit_assert( bin_find_entry(table, &bin, h, k) == &k->entry ); + + /* remove the element */ + lock_quick_lock(&bin.lock); + bin_overflow_remove(&bin, &k->entry); + lock_quick_unlock(&bin.lock); + unit_assert( bin_find_entry(table, &bin, h, k) == NULL ); + + /* prepend two different elements; so the list is long */ + /* one has the same hash, but different cmp */ + lock_quick_lock(&bin.lock); + unit_assert( k->entry.hash == k4->entry.hash ); + k4->entry.overflow_next = &k->entry; + k3->entry.overflow_next = &k4->entry; + bin.overflow_list = &k3->entry; + lock_quick_unlock(&bin.lock); + + /* find, hash not OK. */ + unit_assert( bin_find_entry(table, &bin, myhash(13), k) == NULL ); + + /* find, hash OK, but cmp not */ + unit_assert( k->entry.hash == k2->entry.hash ); + unit_assert( bin_find_entry(table, &bin, h, k2) == NULL ); + + /* find, hash OK, and cmp too */ + unit_assert( bin_find_entry(table, &bin, h, k) == &k->entry ); + + /* remove middle element */ + unit_assert( bin_find_entry(table, &bin, k4->entry.hash, k4) + == &k4->entry ); + lock_quick_lock(&bin.lock); + bin_overflow_remove(&bin, &k4->entry); + lock_quick_unlock(&bin.lock); + unit_assert( bin_find_entry(table, &bin, k4->entry.hash, k4) == NULL); + + /* remove last element */ + lock_quick_lock(&bin.lock); + bin_overflow_remove(&bin, &k->entry); + lock_quick_unlock(&bin.lock); + unit_assert( bin_find_entry(table, &bin, h, k) == NULL ); + + lock_quick_destroy(&bin.lock); + delkey(k); + delkey(k2); + delkey(k3); + delkey(k4); + deldata(d); +} + +/** test lru_front lru_remove */ +static void test_lru(struct lruhash* table) +{ + testkey_type* k = newkey(12); + testkey_type* k2 = newkey(14); + lock_quick_lock(&table->lock); + + unit_assert( table->lru_start == NULL && table->lru_end == NULL); + lru_remove(table, &k->entry); + unit_assert( table->lru_start == NULL && table->lru_end == NULL); + + /* add one */ + lru_front(table, &k->entry); + unit_assert( table->lru_start == &k->entry && + table->lru_end == &k->entry); + /* remove it */ + lru_remove(table, &k->entry); + unit_assert( table->lru_start == NULL && table->lru_end == NULL); + + /* add two */ + lru_front(table, &k->entry); + unit_assert( table->lru_start == &k->entry && + table->lru_end == &k->entry); + lru_front(table, &k2->entry); + unit_assert( table->lru_start == &k2->entry && + table->lru_end == &k->entry); + /* remove first in list */ + lru_remove(table, &k2->entry); + unit_assert( table->lru_start == &k->entry && + table->lru_end == &k->entry); + lru_front(table, &k2->entry); + unit_assert( table->lru_start == &k2->entry && + table->lru_end == &k->entry); + /* remove last in list */ + lru_remove(table, &k->entry); + unit_assert( table->lru_start == &k2->entry && + table->lru_end == &k2->entry); + + /* empty the list */ + lru_remove(table, &k2->entry); + unit_assert( table->lru_start == NULL && table->lru_end == NULL); + lock_quick_unlock(&table->lock); + delkey(k); + delkey(k2); +} + +/** test hashtable using short sequence */ +static void +test_short_table(struct lruhash* table) +{ + testkey_type* k = newkey(12); + testkey_type* k2 = newkey(14); + testdata_type* d = newdata(128); + testdata_type* d2 = newdata(129); + + k->entry.data = d; + k2->entry.data = d2; + + lruhash_insert(table, myhash(12), &k->entry, d, NULL); + lruhash_insert(table, myhash(14), &k2->entry, d2, NULL); + + unit_assert( lruhash_lookup(table, myhash(12), k, 0) == &k->entry); + lock_rw_unlock( &k->entry.lock ); + unit_assert( lruhash_lookup(table, myhash(14), k2, 0) == &k2->entry); + lock_rw_unlock( &k2->entry.lock ); + lruhash_remove(table, myhash(12), k); + lruhash_remove(table, myhash(14), k2); +} + +/** number of hash test max */ +#define HASHTESTMAX 25 + +/** test adding a random element */ +static void +testadd(struct lruhash* table, testdata_type* ref[]) +{ + int numtoadd = random() % HASHTESTMAX; + testdata_type* data = newdata(numtoadd); + testkey_type* key = newkey(numtoadd); + key->entry.data = data; + lruhash_insert(table, myhash(numtoadd), &key->entry, data, NULL); + ref[numtoadd] = data; +} + +/** test adding a random element */ +static void +testremove(struct lruhash* table, testdata_type* ref[]) +{ + int num = random() % HASHTESTMAX; + testkey_type* key = newkey(num); + lruhash_remove(table, myhash(num), key); + ref[num] = NULL; + delkey(key); +} + +/** test adding a random element */ +static void +testlookup(struct lruhash* table, testdata_type* ref[]) +{ + int num = random() % HASHTESTMAX; + testkey_type* key = newkey(num); + struct lruhash_entry* en = lruhash_lookup(table, myhash(num), key, 0); + testdata_type* data = en? (testdata_type*)en->data : NULL; + if(en) { + unit_assert(en->key); + unit_assert(en->data); + } + if(0) log_info("lookup %d got %d, expect %d", num, en? data->data :-1, + ref[num]? ref[num]->data : -1); + unit_assert( data == ref[num] ); + if(en) { lock_rw_unlock(&en->lock); } + delkey(key); +} + +/** check integrity of hash table */ +static void +check_table(struct lruhash* table) +{ + struct lruhash_entry* p; + size_t c = 0; + lock_quick_lock(&table->lock); + unit_assert( table->num <= table->size); + unit_assert( table->size_mask == (int)table->size-1 ); + unit_assert( (table->lru_start && table->lru_end) || + (!table->lru_start && !table->lru_end) ); + unit_assert( table->space_used <= table->space_max ); + /* check lru list integrity */ + if(table->lru_start) + unit_assert(table->lru_start->lru_prev == NULL); + if(table->lru_end) + unit_assert(table->lru_end->lru_next == NULL); + p = table->lru_start; + while(p) { + if(p->lru_prev) { + unit_assert(p->lru_prev->lru_next == p); + } + if(p->lru_next) { + unit_assert(p->lru_next->lru_prev == p); + } + c++; + p = p->lru_next; + } + unit_assert(c == table->num); + + /* this assertion is specific to the unit test */ + unit_assert( table->space_used == + table->num * test_slabhash_sizefunc(NULL, NULL) ); + lock_quick_unlock(&table->lock); +} + +/** test adding a random element (unlimited range) */ +static void +testadd_unlim(struct lruhash* table, testdata_type** ref) +{ + int numtoadd = random() % (HASHTESTMAX * 10); + testdata_type* data = newdata(numtoadd); + testkey_type* key = newkey(numtoadd); + key->entry.data = data; + lruhash_insert(table, myhash(numtoadd), &key->entry, data, NULL); + if(ref) + ref[numtoadd] = data; +} + +/** test adding a random element (unlimited range) */ +static void +testremove_unlim(struct lruhash* table, testdata_type** ref) +{ + int num = random() % (HASHTESTMAX*10); + testkey_type* key = newkey(num); + lruhash_remove(table, myhash(num), key); + if(ref) + ref[num] = NULL; + delkey(key); +} + +/** test adding a random element (unlimited range) */ +static void +testlookup_unlim(struct lruhash* table, testdata_type** ref) +{ + int num = random() % (HASHTESTMAX*10); + testkey_type* key = newkey(num); + struct lruhash_entry* en = lruhash_lookup(table, myhash(num), key, 0); + testdata_type* data = en? (testdata_type*)en->data : NULL; + if(en) { + unit_assert(en->key); + unit_assert(en->data); + } + if(0 && ref) log_info("lookup unlim %d got %d, expect %d", num, en ? + data->data :-1, ref[num] ? ref[num]->data : -1); + if(data && ref) { + /* its okay for !data, it fell off the lru */ + unit_assert( data == ref[num] ); + } + if(en) { lock_rw_unlock(&en->lock); } + delkey(key); +} + +/** test with long sequence of adds, removes and updates, and lookups */ +static void +test_long_table(struct lruhash* table) +{ + /* assuming it all fits in the hashtable, this check will work */ + testdata_type* ref[HASHTESTMAX * 100]; + size_t i; + memset(ref, 0, sizeof(ref)); + /* test assumption */ + if(0) log_info(" size %d x %d < %d", (int)test_slabhash_sizefunc(NULL, NULL), + (int)HASHTESTMAX, (int)table->space_max); + unit_assert( test_slabhash_sizefunc(NULL, NULL)*HASHTESTMAX < table->space_max); + if(0) lruhash_status(table, "unit test", 1); + srandom(48); + for(i=0; i<1000; i++) { + /* what to do? */ + if(i == 500) { + lruhash_clear(table); + memset(ref, 0, sizeof(ref)); + continue; + } + switch(random() % 4) { + case 0: + case 3: + testadd(table, ref); + break; + case 1: + testremove(table, ref); + break; + case 2: + testlookup(table, ref); + break; + default: + unit_assert(0); + } + if(0) lruhash_status(table, "unit test", 1); + check_table(table); + unit_assert( table->num <= HASHTESTMAX ); + } + + /* test more, but 'ref' assumption does not hold anymore */ + for(i=0; i<1000; i++) { + /* what to do? */ + switch(random() % 4) { + case 0: + case 3: + testadd_unlim(table, ref); + break; + case 1: + testremove_unlim(table, ref); + break; + case 2: + testlookup_unlim(table, ref); + break; + default: + unit_assert(0); + } + if(0) lruhash_status(table, "unlim", 1); + check_table(table); + } +} + +/** structure to threaded test the lru hash table */ +struct test_thr { + /** thread num, first entry. */ + int num; + /** id */ + ub_thread_type id; + /** hash table */ + struct lruhash* table; +}; + +/** main routine for threaded hash table test */ +static void* +test_thr_main(void* arg) +{ + struct test_thr* t = (struct test_thr*)arg; + int i; + log_thread_set(&t->num); + for(i=0; i<1000; i++) { + switch(random() % 4) { + case 0: + case 3: + testadd_unlim(t->table, NULL); + break; + case 1: + testremove_unlim(t->table, NULL); + break; + case 2: + testlookup_unlim(t->table, NULL); + break; + default: + unit_assert(0); + } + if(0) lruhash_status(t->table, "hashtest", 1); + if(i % 100 == 0) /* because of locking, not all the time */ + check_table(t->table); + } + check_table(t->table); + return NULL; +} + +/** test hash table access by multiple threads */ +static void +test_threaded_table(struct lruhash* table) +{ + int numth = 10; + struct test_thr t[100]; + int i; + + for(i=1; i +#endif + +#ifdef HAVE_OPENSSL_RAND_H +#include +#endif + +#ifdef HAVE_OPENSSL_CONF_H +#include +#endif + +#ifdef HAVE_OPENSSL_ENGINE_H +#include +#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= 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= 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; icount); +} + +/** 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; iname = strdup("view1"); + cv2->name = strdup("view2"); + unit_assert(cv1->name && cv2->name); + cv1->next = cv2; + cfg.views = cv1; + + for(i=0; irespip_actions, ip, sact)) + unit_assert(0); + } + for(i=0; irespip_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; iname = strdup("view1"); + unit_assert(cv->name); + cfg.views = cv; + cfg_insert_respip_data(&cv->respip_actions, &cv->respip_data); + views = views_create(); + unit_assert(views); + unit_assert(views_apply_cfg(views, &cfg)); + + /* apply configuration and verify rrsets */ + unit_assert(respip_views_apply_cfg(views, &cfg, &have_respip_cfg)); + v = views_find_view(views, "view1", 0); + unit_assert(v); + verify_rrset(v->respip_set, "192.0.1.0/24", "1.2.3.4", + 0, LDNS_RR_TYPE_A); + verify_rrset(v->respip_set, "192.0.1.0/24", "11.12.13.14", + 1, LDNS_RR_TYPE_A); + verify_rrset(v->respip_set, "192.0.2.0/24", "www.example.com", + 0, LDNS_RR_TYPE_CNAME); + verify_rrset(v->respip_set, "2001:db8:1::/48", "2001:db8:1::2:1", + 0, LDNS_RR_TYPE_AAAA); + lock_rw_unlock(&v->lock); + + views_delete(views); + free(cv->name); + free(cv); +} + +/** respip unit tests */ +static void respip_test(void) +{ + respip_view_conf_data_test(); + respip_conf_data_test(); + respip_view_conf_actions_test(); + respip_conf_actions_test(); +} + +void unit_show_func(const char* file, const char* func) +{ + printf("test %s:%s\n", file, func); +} + +void unit_show_feature(const char* feature) +{ + printf("test %s functions\n", feature); +} + +#ifdef USE_ECDSA_EVP_WORKAROUND +void ecdsa_evp_workaround_init(void); +#endif +/** + * Main unit test program. Setup, teardown and report errors. + * @param argc: arg count. + * @param argv: array of commandline arguments. + * @return program failure if test fails. + */ +int +main(int argc, char* argv[]) +{ + log_init(NULL, 0, NULL); + if(argc != 1) { + printf("usage: %s\n", argv[0]); + printf("\tperforms unit tests.\n"); + return 1; + } + printf("Start of %s unit test.\n", PACKAGE_STRING); +#ifdef HAVE_SSL +# ifdef HAVE_ERR_LOAD_CRYPTO_STRINGS + ERR_load_crypto_strings(); +# endif +# ifdef USE_GOST + (void)sldns_key_EVP_load_gost_id(); +# endif +# ifdef USE_ECDSA_EVP_WORKAROUND + ecdsa_evp_workaround_init(); +# endif +#elif defined(HAVE_NSS) + if(NSS_NoDB_Init(".") != SECSuccess) + fatal_exit("could not init NSS"); +#endif /* HAVE_SSL or HAVE_NSS*/ + checklock_start(); + authzone_test(); + neg_test(); + rnd_test(); + respip_test(); + verify_test(); + net_test(); + config_memsize_test(); + config_tag_test(); + dname_test(); + rtt_test(); + anchors_test(); + alloc_test(); + regional_test(); + lruhash_test(); + slabhash_test(); + infra_test(); + ldns_test(); + msgparse_test(); +#ifdef CLIENT_SUBNET + ecs_test(); +#endif /* CLIENT_SUBNET */ + if(log_get_lock()) { + lock_quick_destroy((lock_quick_type*)log_get_lock()); + } + checklock_stop(); + printf("%d checks ok.\n", testcount); +#ifdef HAVE_SSL +# if defined(USE_GOST) && defined(HAVE_LDNS_KEY_EVP_UNLOAD_GOST) + sldns_key_EVP_unload_gost(); +# endif +# ifdef HAVE_OPENSSL_CONFIG +# ifdef HAVE_EVP_CLEANUP + EVP_cleanup(); +# endif + ENGINE_cleanup(); + CONF_modules_free(); +# endif +# ifdef HAVE_CRYPTO_CLEANUP_ALL_EX_DATA + CRYPTO_cleanup_all_ex_data(); +# endif +# ifdef HAVE_ERR_FREE_STRINGS + ERR_free_strings(); +# endif +# ifdef HAVE_RAND_CLEANUP + RAND_cleanup(); +# endif +#elif defined(HAVE_NSS) + if(NSS_Shutdown() != SECSuccess) + fatal_exit("could not shutdown NSS"); +#endif /* HAVE_SSL or HAVE_NSS */ +#ifdef HAVE_PTHREAD + /* dlopen frees its thread specific state */ + pthread_exit(NULL); +#endif + return 0; +} diff --git a/usr.sbin/unbound/testcode/unitmain.h b/usr.sbin/unbound/testcode/unitmain.h new file mode 100644 index 00000000000..e5c6109a2aa --- /dev/null +++ b/usr.sbin/unbound/testcode/unitmain.h @@ -0,0 +1,84 @@ +/* + * testcode/unitmain.h - unit test main program for unbound. + * + * Copyright (c) 2007, NLnet Labs. All rights reserved. + * + * This software is open source. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * Neither the name of the NLNET LABS nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ +/** + * \file + * Declarations useful for the unit tests. + */ + +#ifndef TESTCODE_UNITMAIN_H +#define TESTCODE_UNITMAIN_H +#include "util/log.h" + +/** number of tests done */ +extern int testcount; +/** test bool x, exits on failure, increases testcount. */ +#ifdef DEBUG_UNBOUND +#define unit_assert(x) do {testcount++; log_assert(x);} while(0) +#else +#define unit_assert(x) do {testcount++; if(!(x)) { fprintf(stderr, "assertion failure %s:%d\n", __FILE__, __LINE__); exit(1);}} while(0) +#endif + +/** we are now testing this function */ +void unit_show_func(const char* file, const char* func); +/** we are testing this functionality */ +void unit_show_feature(const char* feature); + +/** unit test lruhashtable implementation */ +void lruhash_test(void); +/** unit test slabhashtable implementation */ +void slabhash_test(void); +/** unit test for msgreply and msgparse */ +void msgparse_test(void); +/** unit test dname handling functions */ +void dname_test(void); +/** unit test trust anchor storage functions */ +void anchors_test(void); +/** unit test for verification functions */ +void verify_test(void); +/** unit test for negative cache functions */ +void neg_test(void); +/** unit test for regional allocator functions */ +void regional_test(void); +#ifdef CLIENT_SUBNET +/** Unit test for ECS functions */ +void ecs_test(void); +#endif /* CLIENT_SUBNET */ +/** unit test for ldns functions */ +void ldns_test(void); +/** unit test for auth zone functions */ +void authzone_test(void); + +#endif /* TESTCODE_UNITMAIN_H */ diff --git a/usr.sbin/unbound/testcode/unitmsgparse.c b/usr.sbin/unbound/testcode/unitmsgparse.c new file mode 100644 index 00000000000..627d10b78ec --- /dev/null +++ b/usr.sbin/unbound/testcode/unitmsgparse.c @@ -0,0 +1,542 @@ +/* + * testcode/unitmsgparse.c - unit test for msg parse routines. + * + * Copyright (c) 2007, NLnet Labs. All rights reserved. + * + * This software is open source. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * Neither the name of the NLNET LABS nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ +/** + * \file + * Calls msg parse unit tests. Exits with code 1 on a failure. + */ + +#include "config.h" +#include +#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; countbits & 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; icount+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; ian_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; ian_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; irrset_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; irrset_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; irrset_count; i++) { + struct ub_packed_rrset_key* s = rep->rrsets[i]; + if(ntohs(s->rk.type) == LDNS_RR_TYPE_RRSIG) { + /* see if really a problem, i.e. is there a data + * element. */ + if(no_data_for_rrsig(rep, rep->rrsets[i])) + continue; + log_dns_msg("rrsig failed for packet", qinfo, rep); + print_packet_rrsets(qinfo, rep); + printf("failed rrset is nr %d\n", (int)i); + unit_assert(0); + } + } +} + +/** test a packet */ +static void +testpkt(sldns_buffer* pkt, struct alloc_cache* alloc, sldns_buffer* out, + const char* hex) +{ + struct query_info qi; + struct reply_info* rep = 0; + int ret; + uint16_t id; + uint16_t flags; + uint32_t timenow = 0; + struct regional* region = regional_create(); + struct edns_data edns; + + hex_to_buf(pkt, hex); + memmove(&id, sldns_buffer_begin(pkt), sizeof(id)); + if(sldns_buffer_limit(pkt) < 2) + flags = 0; + else memmove(&flags, sldns_buffer_at(pkt, 2), sizeof(flags)); + flags = ntohs(flags); + ret = reply_info_parse(pkt, alloc, &qi, &rep, region, &edns); + if(ret != 0) { + char rbuf[16]; + sldns_wire2str_rcode_buf(ret, rbuf, sizeof(rbuf)); + if(vbmp) printf("parse code %d: %s\n", ret, rbuf); + if(ret == LDNS_RCODE_FORMERR) { + unit_assert(!check_formerr_gone); + checkformerr(pkt); + } + unit_assert(ret != LDNS_RCODE_SERVFAIL); + } else if(!check_formerr_gone) { + const size_t lim = 512; + ret = reply_info_encode(&qi, rep, id, flags, out, timenow, + region, 65535, (int)(edns.bits & EDNS_DO) ); + unit_assert(ret != 0); /* udp packets should fit */ + attach_edns_record(out, &edns); + if(vbmp) printf("inlen %u outlen %u\n", + (unsigned)sldns_buffer_limit(pkt), + (unsigned)sldns_buffer_limit(out)); + if(!check_nosameness) + test_buffers(pkt, out); + if(check_rrsigs) + check_the_rrsigs(&qi, rep); + + if(sldns_buffer_limit(out) > lim) { + ret = reply_info_encode(&qi, rep, id, flags, out, + timenow, region, + lim - calc_edns_field_size(&edns), + (int)(edns.bits & EDNS_DO)); + unit_assert(ret != 0); /* should fit, but with TC */ + attach_edns_record(out, &edns); + if( LDNS_QDCOUNT(sldns_buffer_begin(out)) != + LDNS_QDCOUNT(sldns_buffer_begin(pkt)) || + LDNS_ANCOUNT(sldns_buffer_begin(out)) != + LDNS_ANCOUNT(sldns_buffer_begin(pkt)) || + LDNS_NSCOUNT(sldns_buffer_begin(out)) != + LDNS_NSCOUNT(sldns_buffer_begin(pkt))) + unit_assert( + LDNS_TC_WIRE(sldns_buffer_begin(out))); + /* must set TC bit if shortened */ + unit_assert(sldns_buffer_limit(out) <= lim); + } + } + + query_info_clear(&qi); + reply_info_parsedelete(rep, alloc); + regional_destroy(region); +} + +/** simple test of parsing */ +static void +simpletest(sldns_buffer* pkt, struct alloc_cache* alloc, sldns_buffer* out) +{ + /* a root query drill -q - */ + testpkt(pkt, alloc, out, + " c5 40 01 00 00 01 00 00 00 00 00 00 00 00 02 00 01 "); + + /* very small packet */ + testpkt(pkt, alloc, out, +"; 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19\n" +";-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --\n" +"74 0c 85 83 00 01 00 00 00 01 00 00 03 62 6c 61 09 6e 6c 6e ; 1- 20\n" +"65 74 6c 61 62 73 02 6e 6c 00 00 0f 00 01 09 6e 6c 6e 65 74 ; 21- 40\n" +"6c 61 62 73 02 6e 6c 00 00 06 00 01 00 00 46 50 00 40 04 6f ; 41- 60\n" +"70 65 6e 09 6e 6c 6e 65 74 6c 61 62 73 02 6e 6c 00 0a 68 6f ; 61- 80\n" +"73 74 6d 61 73 74 65 72 09 6e 6c 6e 65 74 6c 61 62 73 02 6e ; 81- 100\n" +"6c 00 77 a1 02 58 00 00 70 80 00 00 1c 20 00 09 3a 80 00 00 ; 101- 120\n" +"46 50\n"); + + /* a root reply drill -w - */ + testpkt(pkt, alloc, out, + " ; 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19\n" + " ;-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --\n" + " 97 3f 81 80 00 01 00 0d 00 00 00 02 00 00 02 00 01 00 00 02 ; 1- 20\n" + " 00 01 00 06 6d 38 00 14 01 49 0c 52 4f 4f 54 2d 53 45 52 56 ; 21- 40\n" + " 45 52 53 03 4e 45 54 00 00 00 02 00 01 00 06 6d 38 00 14 01 ; 41- 60\n" + " 4a 0c 52 4f 4f 54 2d 53 45 52 56 45 52 53 03 4e 45 54 00 00 ; 61- 80\n" + " 00 02 00 01 00 06 6d 38 00 14 01 4b 0c 52 4f 4f 54 2d 53 45 ; 81- 100\n" + " 52 56 45 52 53 03 4e 45 54 00 00 00 02 00 01 00 06 6d 38 00 ; 101- 120\n" + " 14 01 4c 0c 52 4f 4f 54 2d 53 45 52 56 45 52 53 03 4e 45 54 ; 121- 140\n" + " 00 00 00 02 00 01 00 06 6d 38 00 14 01 4d 0c 52 4f 4f 54 2d ; 141- 160\n" + " 53 45 52 56 45 52 53 03 4e 45 54 00 00 00 02 00 01 00 06 6d ; 161- 180\n" + " 38 00 14 01 41 0c 52 4f 4f 54 2d 53 45 52 56 45 52 53 03 4e ; 181- 200\n" + " 45 54 00 00 00 02 00 01 00 06 6d 38 00 14 01 42 0c 52 4f 4f ; 201- 220\n" + " 54 2d 53 45 52 56 45 52 53 03 4e 45 54 00 00 00 02 00 01 00 ; 221- 240\n" + " 06 6d 38 00 14 01 43 0c 52 4f 4f 54 2d 53 45 52 56 45 52 53 ; 241- 260\n" + " 03 4e 45 54 00 00 00 02 00 01 00 06 6d 38 00 14 01 44 0c 52 ; 261- 280\n" + " 4f 4f 54 2d 53 45 52 56 45 52 53 03 4e 45 54 00 00 00 02 00 ; 281- 300\n" + " 01 00 06 6d 38 00 14 01 45 0c 52 4f 4f 54 2d 53 45 52 56 45 ; 301- 320\n" + " 52 53 03 4e 45 54 00 00 00 02 00 01 00 06 6d 38 00 14 01 46 ; 321- 340\n" + " 0c 52 4f 4f 54 2d 53 45 52 56 45 52 53 03 4e 45 54 00 00 00 ; 341- 360\n" + " 02 00 01 00 06 6d 38 00 14 01 47 0c 52 4f 4f 54 2d 53 45 52 ; 361- 380\n" + " 56 45 52 53 03 4e 45 54 00 00 00 02 00 01 00 06 6d 38 00 14 ; 381- 400\n" + " 01 48 0c 52 4f 4f 54 2d 53 45 52 56 45 52 53 03 4e 45 54 00 ; 401- 420\n" + " 01 41 0c 52 4f 4f 54 2d 53 45 52 56 45 52 53 03 4e 45 54 00 ; 421- 440\n" + " 00 01 00 01 00 02 64 b9 00 04 c6 29 00 04 01 4a 0c 52 4f 4f ; 441- 460\n" + " 54 2d 53 45 52 56 45 52 53 03 4e 45 54 00 00 01 00 01 00 02 ; 461- 480\n" + " 64 b9 00 04 c0 3a 80 1e "); + + /* root delegation from unbound trace with new AAAA glue */ + perftestpkt(pkt, alloc, out, + "55BC84000001000D00000014000002000100000200010007E900001401610C726F6F742D73657276657273036E65740000000200010007E90000040162C01E00000200010007E90000040163C01E00000200010007E90000040164C01E00000200010007E90000040165C01E00000200010007E90000040166C01E00000200010007E90000040167C01E00000200010007E90000040168C01E00000200010007E90000040169C01E00000200010007E9000004016AC01E00000200010007E9000004016BC01E00000200010007E9000004016CC01E00000200010007E9000004016DC01EC01C000100010007E9000004C6290004C03B000100010007E9000004C0E44FC9C04A000100010007E9000004C021040CC059000100010007E900000480080A5AC068000100010007E9000004C0CBE60AC077000100010007E9000004C00505F1C086000100010007E9000004C0702404C095000100010007E9000004803F0235C0A4000100010007E9000004C0249411C0B3000100010007E9000004C03A801EC0C2000100010007E9000004C1000E81C0D1000100010007E9000004C707532AC0E0000100010007E9000004CA0C1B21C01C001C00010007E900001020010503BA3E00000000000000020030C077001C00010007E900001020010500002F0000000000000000000FC095001C00010007E90000102001050000010000" + "00000000803F0235C0B3001C00010007E9000010200105030C2700000000000000020030C0C2001C00010007E9000010200107FD000000000000000000000001C0E0001C00010007E900001020010DC30000000000000000000000350000291000000000000000" + ); +} + +/** simple test of parsing, pcat file */ +static void +testfromfile(sldns_buffer* pkt, struct alloc_cache* alloc, sldns_buffer* out, + const char* fname) +{ + FILE* in = fopen(fname, "r"); + char buf[102400]; + int no=0; + if(!in) { + perror("fname"); + return; + } + while(fgets(buf, (int)sizeof(buf), in)) { + if(buf[0] == ';') /* comment */ + continue; + if(strlen(buf) < 10) /* skip pcat line numbers. */ + continue; + if(vbmp) { + printf("test no %d: %s", no, buf); + fflush(stdout); + } + testpkt(pkt, alloc, out, buf); + no++; + } + fclose(in); +} + +/** simple test of parsing, drill file */ +static void +testfromdrillfile(sldns_buffer* pkt, struct alloc_cache* alloc, + sldns_buffer* out, const char* fname) +{ + /* ;-- is used to indicate a new message */ + FILE* in = fopen(fname, "r"); + char buf[102400]; + char* np = buf; + buf[0]=0; + if(!in) { + perror("fname"); + return; + } + while(fgets(np, (int)sizeof(buf) - (np-buf), in)) { + if(strncmp(np, ";--", 3) == 0) { + /* new entry */ + /* test previous */ + if(np != buf) + testpkt(pkt, alloc, out, buf); + /* set for new entry */ + np = buf; + buf[0]=0; + continue; + } + if(np[0] == ';') /* comment */ + continue; + np = &np[strlen(np)]; + } + testpkt(pkt, alloc, out, buf); + fclose(in); +} + +void msgparse_test(void) +{ + time_t origttl = MAX_NEG_TTL; + sldns_buffer* pkt = sldns_buffer_new(65553); + sldns_buffer* out = sldns_buffer_new(65553); + struct alloc_cache super_a, alloc; + MAX_NEG_TTL = 86400; + /* init */ + alloc_init(&super_a, NULL, 0); + alloc_init(&alloc, &super_a, 2); + + unit_show_feature("message parse"); + simpletest(pkt, &alloc, out); + /* plain hex dumps, like pcat */ + testfromfile(pkt, &alloc, out, "testdata/test_packets.1"); + testfromfile(pkt, &alloc, out, "testdata/test_packets.2"); + testfromfile(pkt, &alloc, out, "testdata/test_packets.3"); + /* like from drill -w - */ + testfromdrillfile(pkt, &alloc, out, "testdata/test_packets.4"); + testfromdrillfile(pkt, &alloc, out, "testdata/test_packets.5"); + + matches_nolocation = 1; /* RR order not important for the next test */ + testfromdrillfile(pkt, &alloc, out, "testdata/test_packets.6"); + check_rrsigs = 1; + testfromdrillfile(pkt, &alloc, out, "testdata/test_packets.7"); + check_rrsigs = 0; + matches_nolocation = 0; + + check_formerr_gone = 1; + testfromdrillfile(pkt, &alloc, out, "testdata/test_packets.8"); + check_formerr_gone = 0; + + check_rrsigs = 1; + check_nosameness = 1; + testfromdrillfile(pkt, &alloc, out, "testdata/test_packets.9"); + check_nosameness = 0; + check_rrsigs = 0; + + /* cleanup */ + alloc_clear(&alloc); + alloc_clear(&super_a); + sldns_buffer_free(pkt); + sldns_buffer_free(out); + MAX_NEG_TTL = origttl; +} diff --git a/usr.sbin/unbound/testcode/unitneg.c b/usr.sbin/unbound/testcode/unitneg.c new file mode 100644 index 00000000000..4cd9b306c72 --- /dev/null +++ b/usr.sbin/unbound/testcode/unitneg.c @@ -0,0 +1,543 @@ +/* + * testcode/unitneg.c - unit test for negative cache routines. + * + * Copyright (c) 2008, NLnet Labs. All rights reserved. + * + * This software is open source. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * Neither the name of the NLNET LABS nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ +/** + * \file + * Calls negative cache unit tests. Exits with code 1 on a failure. + */ + +#include "config.h" +#include "util/log.h" +#include "util/net_help.h" +#include "util/data/packed_rrset.h" +#include "util/data/dname.h" +#include "testcode/unitmain.h" +#include "validator/val_neg.h" +#include "sldns/rrdef.h" + +/** verbose unit test for negative cache */ +static int negverbose = 0; + +/** debug printout of neg cache */ +static void print_neg_cache(struct val_neg_cache* neg) +{ + char buf[1024]; + struct val_neg_zone* z; + struct val_neg_data* d; + printf("neg_cache print\n"); + printf("memuse %d of %d\n", (int)neg->use, (int)neg->max); + printf("maxiter %d\n", (int)neg->nsec3_max_iter); + printf("%d zones\n", (int)neg->tree.count); + RBTREE_FOR(z, struct val_neg_zone*, &neg->tree) { + dname_str(z->name, buf); + printf("%24s", buf); + printf(" len=%2.2d labs=%d inuse=%d count=%d tree.count=%d\n", + (int)z->len, z->labs, (int)z->in_use, z->count, + (int)z->tree.count); + } + RBTREE_FOR(z, struct val_neg_zone*, &neg->tree) { + printf("\n"); + dname_print(stdout, NULL, z->name); + printf(" zone details\n"); + printf("len=%2.2d labs=%d inuse=%d count=%d tree.count=%d\n", + (int)z->len, z->labs, (int)z->in_use, z->count, + (int)z->tree.count); + if(z->parent) { + printf("parent="); + dname_print(stdout, NULL, z->parent->name); + printf("\n"); + } else { + printf("parent=NULL\n"); + } + + RBTREE_FOR(d, struct val_neg_data*, &z->tree) { + dname_str(d->name, buf); + printf("%24s", buf); + printf(" len=%2.2d labs=%d inuse=%d count=%d\n", + (int)d->len, d->labs, (int)d->in_use, d->count); + } + } +} + +/** get static pointer to random zone name */ +static char* get_random_zone(void) +{ + static char zname[36]; + int labels = random() % 3; + int i; + char* p = zname; + int labnum; + + for(i=0; ilock); + if(negverbose) + log_nametypeclass(0, "add to zone", (uint8_t*)zname, 0, 0); + z = neg_find_zone(neg, (uint8_t*)zname, strlen(zname)+1, + LDNS_RR_CLASS_IN); + if(!z) { + z = neg_create_zone(neg, (uint8_t*)zname, strlen(zname)+1, + LDNS_RR_CLASS_IN); + } + unit_assert(z); + val_neg_zone_take_inuse(z); + + /* construct random NSEC item */ + get_random_data(&from, &to, zname); + + /* create nsec and insert it */ + memset(&rd, 0, sizeof(rd)); + memset(&nsec, 0, sizeof(nsec)); + nsec.rk.dname = (uint8_t*)from; + nsec.rk.dname_len = strlen(from)+1; + nsec.rk.type = htons(LDNS_RR_TYPE_NSEC); + nsec.rk.rrset_class = htons(LDNS_RR_CLASS_IN); + nsec.entry.data = &rd; + rd.security = sec_status_secure; + rd.count = 1; + rd.rr_len = &rr_len; + rr_len = 19; + rd.rr_ttl = &rr_ttl; + rr_ttl = 0; + rd.rr_data = &rr_data; + rr_data = (uint8_t*)to; + + neg_insert_data(neg, z, &nsec); + lock_basic_unlock(&neg->lock); +} + +/** remove a random item */ +static void remove_item(struct val_neg_cache* neg) +{ + int n, i; + struct val_neg_data* d; + rbnode_type* walk; + struct val_neg_zone* z; + + lock_basic_lock(&neg->lock); + if(neg->tree.count == 0) { + lock_basic_unlock(&neg->lock); + return; /* nothing to delete */ + } + + /* pick a random zone */ + walk = rbtree_first(&neg->tree); /* first highest parent, big count */ + z = (struct val_neg_zone*)walk; + n = random() % (int)(z->count); + if(negverbose) + printf("neg stress delete zone %d\n", n); + i=0; + walk = rbtree_first(&neg->tree); + z = (struct val_neg_zone*)walk; + while(i!=n+1 && walk && walk != RBTREE_NULL && !z->in_use) { + walk = rbtree_next(walk); + z = (struct val_neg_zone*)walk; + if(z->in_use) + i++; + } + if(!walk || walk == RBTREE_NULL) { + lock_basic_unlock(&neg->lock); + return; + } + if(!z->in_use) { + lock_basic_unlock(&neg->lock); + return; + } + if(negverbose) + log_nametypeclass(0, "delete zone", z->name, 0, 0); + + /* pick a random nsec item. - that is in use */ + walk = rbtree_first(&z->tree); /* first is highest parent */ + d = (struct val_neg_data*)walk; + n = random() % (int)(d->count); + if(negverbose) + printf("neg stress delete item %d\n", n); + i=0; + walk = rbtree_first(&z->tree); + d = (struct val_neg_data*)walk; + while(i!=n+1 && walk && walk != RBTREE_NULL && !d->in_use) { + walk = rbtree_next(walk); + d = (struct val_neg_data*)walk; + if(d->in_use) + i++; + } + if(!walk || walk == RBTREE_NULL) { + lock_basic_unlock(&neg->lock); + return; + } + if(d->in_use) { + if(negverbose) + log_nametypeclass(0, "neg delete item:", d->name, 0, 0); + neg_delete_data(neg, d); + } + lock_basic_unlock(&neg->lock); +} + +/** sum up the zone trees */ +static size_t sumtrees_all(struct val_neg_cache* neg) +{ + size_t res = 0; + struct val_neg_zone* z; + RBTREE_FOR(z, struct val_neg_zone*, &neg->tree) { + res += z->tree.count; + } + return res; +} + +/** sum up the zone trees, in_use only */ +static size_t sumtrees_inuse(struct val_neg_cache* neg) +{ + size_t res = 0; + struct val_neg_zone* z; + struct val_neg_data* d; + RBTREE_FOR(z, struct val_neg_zone*, &neg->tree) { + /* get count of highest parent for num in use */ + d = (struct val_neg_data*)rbtree_first(&z->tree); + if(d && (rbnode_type*)d!=RBTREE_NULL) + res += d->count; + } + return res; +} + +/** check if lru is still valid */ +static void check_lru(struct val_neg_cache* neg) +{ + struct val_neg_data* p, *np; + size_t num = 0; + size_t inuse; + p = neg->first; + while(p) { + if(!p->prev) { + unit_assert(neg->first == p); + } + np = p->next; + if(np) { + unit_assert(np->prev == p); + } else { + unit_assert(neg->last == p); + } + num++; + p = np; + } + inuse = sumtrees_inuse(neg); + if(negverbose) + printf("num lru %d, inuse %d, all %d\n", + (int)num, (int)sumtrees_inuse(neg), + (int)sumtrees_all(neg)); + unit_assert( num == inuse); + unit_assert( inuse <= sumtrees_all(neg)); +} + +/** sum up number of items inuse in subtree */ +static int sum_subtree_inuse(struct val_neg_zone* zone, + struct val_neg_data* data) +{ + struct val_neg_data* d; + int num = 0; + RBTREE_FOR(d, struct val_neg_data*, &zone->tree) { + if(dname_subdomain_c(d->name, data->name)) { + if(d->in_use) + num++; + } + } + return num; +} + +/** sum up number of items inuse in subtree */ +static int sum_zone_subtree_inuse(struct val_neg_cache* neg, + struct val_neg_zone* zone) +{ + struct val_neg_zone* z; + int num = 0; + RBTREE_FOR(z, struct val_neg_zone*, &neg->tree) { + if(dname_subdomain_c(z->name, zone->name)) { + if(z->in_use) + num++; + } + } + return num; +} + +/** check point in data tree */ +static void check_data(struct val_neg_zone* zone, struct val_neg_data* data) +{ + unit_assert(data->count > 0); + if(data->parent) { + unit_assert(data->parent->count >= data->count); + if(data->parent->in_use) { + unit_assert(data->parent->count > data->count); + } + unit_assert(data->parent->labs == data->labs-1); + /* and parent must be one label shorter */ + unit_assert(data->name[0] == (data->len-data->parent->len-1)); + unit_assert(query_dname_compare(data->name + data->name[0]+1, + data->parent->name) == 0); + } else { + /* must be apex */ + unit_assert(dname_is_root(data->name)); + } + /* tree property: */ + unit_assert(data->count == sum_subtree_inuse(zone, data)); +} + +/** check if tree of data in zone is valid */ +static void checkzonetree(struct val_neg_zone* zone) +{ + struct val_neg_data* d; + + /* check all data in tree */ + RBTREE_FOR(d, struct val_neg_data*, &zone->tree) { + check_data(zone, d); + } +} + +/** check if negative cache is still valid */ +static void check_zone_invariants(struct val_neg_cache* neg, + struct val_neg_zone* zone) +{ + unit_assert(zone->nsec3_hash == 0); + unit_assert(zone->tree.cmp == &val_neg_data_compare); + unit_assert(zone->count != 0); + + if(zone->tree.count == 0) + unit_assert(!zone->in_use); + else { + if(!zone->in_use) { + /* details on error */ + log_nametypeclass(0, "zone", zone->name, 0, 0); + log_err("inuse %d count=%d tree.count=%d", + zone->in_use, zone->count, + (int)zone->tree.count); + if(negverbose) + print_neg_cache(neg); + } + unit_assert(zone->in_use); + } + + if(zone->parent) { + unit_assert(zone->parent->count >= zone->count); + if(zone->parent->in_use) { + unit_assert(zone->parent->count > zone->count); + } + unit_assert(zone->parent->labs == zone->labs-1); + /* and parent must be one label shorter */ + unit_assert(zone->name[0] == (zone->len-zone->parent->len-1)); + unit_assert(query_dname_compare(zone->name + zone->name[0]+1, + zone->parent->name) == 0); + } else { + /* must be apex */ + unit_assert(dname_is_root(zone->name)); + } + /* tree property: */ + unit_assert(zone->count == sum_zone_subtree_inuse(neg, zone)); + + /* check structure of zone data tree */ + checkzonetree(zone); +} + +/** check if negative cache is still valid */ +static void check_neg_invariants(struct val_neg_cache* neg) +{ + struct val_neg_zone* z; + /* check structure of LRU list */ + lock_basic_lock(&neg->lock); + check_lru(neg); + unit_assert(neg->max == 1024*1024); + unit_assert(neg->nsec3_max_iter == 1500); + unit_assert(neg->tree.cmp == &val_neg_zone_compare); + + if(neg->tree.count == 0) { + /* empty */ + unit_assert(neg->tree.count == 0); + unit_assert(neg->first == NULL); + unit_assert(neg->last == NULL); + unit_assert(neg->use == 0); + lock_basic_unlock(&neg->lock); + return; + } + + unit_assert(neg->first != NULL); + unit_assert(neg->last != NULL); + + RBTREE_FOR(z, struct val_neg_zone*, &neg->tree) { + check_zone_invariants(neg, z); + } + lock_basic_unlock(&neg->lock); +} + +/** perform stress test on insert and delete in neg cache */ +static void stress_test(struct val_neg_cache* neg) +{ + int i; + if(negverbose) + printf("negcache test\n"); + for(i=0; i<100; i++) { + if(random() % 10 < 8) + add_item(neg); + else remove_item(neg); + check_neg_invariants(neg); + } + /* empty it */ + if(negverbose) + printf("neg stress empty\n"); + while(neg->first) { + remove_item(neg); + check_neg_invariants(neg); + } + if(negverbose) + printf("neg stress emptied\n"); + unit_assert(neg->first == NULL); + /* insert again */ + for(i=0; i<100; i++) { + if(random() % 10 < 8) + add_item(neg); + else remove_item(neg); + check_neg_invariants(neg); + } +} + +void neg_test(void) +{ + struct val_neg_cache* neg; + srandom(48); + unit_show_feature("negative cache"); + + /* create with defaults */ + neg = val_neg_create(NULL, 1500); + unit_assert(neg); + + stress_test(neg); + + neg_cache_delete(neg); +} diff --git a/usr.sbin/unbound/testcode/unitregional.c b/usr.sbin/unbound/testcode/unitregional.c new file mode 100644 index 00000000000..49c8147c944 --- /dev/null +++ b/usr.sbin/unbound/testcode/unitregional.c @@ -0,0 +1,244 @@ +/* + * testcode/unitregional.c - unit test for regional allocator. + * + * Copyright (c) 2010, NLnet Labs. All rights reserved. + * + * This software is open source. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * Neither the name of the NLNET LABS nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ +/** + * \file + * Tests the regional special purpose allocator. + */ + +#include "config.h" +#include "testcode/unitmain.h" +#include "util/log.h" +#include "util/regional.h" + +/** test regional corner cases, zero, one, end of structure */ +static void +corner_cases(struct regional* r) +{ + size_t s; /* shadow count of allocated memory */ + void* a; + size_t minsize = sizeof(uint64_t); + size_t mysize; + char* str; + unit_assert(r); + /* alloc cases: + * 0, 1, 2. + * smaller than LARGE_OBJECT_SIZE. + * smaller but does not fit in remainder in regional. + * smaller but exactly fits in remainder of regional. + * size is remainder of regional - 8. + * size is remainder of regional + 8. + * larger than LARGE_OBJECT_SIZE. + */ + s = sizeof(struct regional); + unit_assert((s % minsize) == 0); + unit_assert(r->available == r->first_size - s); + unit_assert(r->large_list == NULL); + unit_assert(r->next == NULL); + + /* Note an alloc of 0 gets a pointer to current last + * position (where you should then use 0 bytes) */ + a = regional_alloc(r, 0); + unit_assert(a); + s+=0; + unit_assert(r->available == r->first_size - s); + + a = regional_alloc(r, 1); + unit_assert(a); + memset(a, 0x42, 1); + s+=minsize; + unit_assert(r->available == r->first_size - s); + + a = regional_alloc(r, 2); + unit_assert(a); + memset(a, 0x42, 2); + s+=minsize; + unit_assert(r->available == r->first_size - s); + + a = regional_alloc(r, 128); + unit_assert(a); + memset(a, 0x42, 128); + s+=128; + unit_assert(r->available == r->first_size - s); + + unit_assert(r->large_list == NULL); + a = regional_alloc(r, 10240); + unit_assert(a); + unit_assert(r->large_list != NULL); + memset(a, 0x42, 10240); + /* s does not change */ + unit_assert(r->available == r->first_size - s); + unit_assert(r->total_large == 10240+minsize); + + /* go towards the end of the current chunk */ + while(r->available > 1024) { + a = regional_alloc(r, 1024); + unit_assert(a); + memset(a, 0x42, 1024); + s += 1024; + unit_assert(r->available == r->first_size - s); + } + + unit_assert(r->next == NULL); + mysize = 1280; /* does not fit in current chunk */ + a = regional_alloc(r, mysize); + memset(a, 0x42, mysize); + unit_assert(r->next != NULL); + unit_assert(a); + + /* go towards the end of the current chunk */ + while(r->available > 864) { + a = regional_alloc(r, 864); + unit_assert(a); + memset(a, 0x42, 864); + s += 864; + } + + mysize = r->available; /* exactly fits */ + a = regional_alloc(r, mysize); + memset(a, 0x42, mysize); + unit_assert(a); + unit_assert(r->available == 0); /* implementation does not go ahead*/ + + a = regional_alloc(r, 8192); /* another large allocation */ + unit_assert(a); + memset(a, 0x42, 8192); + unit_assert(r->available == 0); + unit_assert(r->total_large == 10240 + 8192 + 2*minsize); + + a = regional_alloc(r, 32); /* make new chunk */ + unit_assert(a); + memset(a, 0x42, 32); + unit_assert(r->available > 0); + unit_assert(r->total_large == 10240 + 8192 + 2*minsize); + + /* go towards the end of the current chunk */ + while(r->available > 1320) { + a = regional_alloc(r, 1320); + unit_assert(a); + memset(a, 0x42, 1320); + s += 1320; + } + + mysize = r->available + 8; /* exact + 8 ; does not fit */ + a = regional_alloc(r, mysize); + memset(a, 0x42, mysize); + unit_assert(a); + unit_assert(r->available > 0); /* new chunk */ + + /* go towards the end of the current chunk */ + while(r->available > 1480) { + a = regional_alloc(r, 1480); + unit_assert(a); + memset(a, 0x42, 1480); + s += 1480; + } + + mysize = r->available - 8; /* exact - 8 ; fits. */ + a = regional_alloc(r, mysize); + memset(a, 0x42, mysize); + unit_assert(a); + unit_assert(r->available == 8); + + /* test if really copied over */ + str = "test12345"; + a = regional_alloc_init(r, str, 8); + unit_assert(a); + unit_assert(memcmp(a, str, 8) == 0); + + /* test if really zeroed */ + a = regional_alloc_zero(r, 32); + str="\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"; + unit_assert(a); + unit_assert(memcmp(a, str, 32) == 0); + + /* test if copied over (and null byte) */ + str = "an interesting string"; + a = regional_strdup(r, str); + unit_assert(a); + unit_assert(memcmp(a, str, strlen(str)+1) == 0); + + regional_free_all(r); +} + +/** test specific cases */ +static void +specific_cases(void) +{ + struct regional* r = regional_create(); + corner_cases(r); + regional_destroy(r); + r = regional_create_custom(2048); /* a small regional */ + unit_assert(r->first_size == 2048); + unit_assert(regional_get_mem(r) == 2048); + corner_cases(r); + unit_assert(regional_get_mem(r) == 2048); + regional_destroy(r); +} + +/** put random stuff in a region and free it */ +static void +burden_test(size_t max) +{ + size_t get; + void* a; + int i; + struct regional* r = regional_create_custom(2048); + for(i=0; i<1000; i++) { + get = random() % max; + a = regional_alloc(r, get); + unit_assert(a); + memset(a, 0x54, get); + } + regional_free_all(r); + regional_destroy(r); +} + +/** randomly allocate stuff */ +static void +random_burden(void) +{ + size_t max_alloc = 2048 + 128; /* small chance of LARGE */ + int i; + for(i=0; i<100; i++) + burden_test(max_alloc); +} + +void regional_test(void) +{ + unit_show_feature("regional"); + specific_cases(); + random_burden(); +} diff --git a/usr.sbin/unbound/testcode/unitslabhash.c b/usr.sbin/unbound/testcode/unitslabhash.c new file mode 100644 index 00000000000..565d361394d --- /dev/null +++ b/usr.sbin/unbound/testcode/unitslabhash.c @@ -0,0 +1,376 @@ +/* + * testcode/unitslabhash.c - unit test for slabhash table. + * + * Copyright (c) 2007, NLnet Labs. All rights reserved. + * + * This software is open source. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * Neither the name of the NLNET LABS nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ +/** + * \file + * Tests the locking LRU keeping hash table implementation. + */ + +#include "config.h" +#include "testcode/unitmain.h" +#include "util/log.h" +#include "util/storage/slabhash.h" + +/** use this type for the slabhash test key */ +typedef struct slabhash_testkey testkey_type; +/** use this type for the slabhash test data */ +typedef struct slabhash_testdata testdata_type; + +/** delete key */ +static void delkey(struct slabhash_testkey* k) { + lock_rw_destroy(&k->entry.lock); free(k);} + +/** hash func, very bad to improve collisions, both high and low bits */ +static hashvalue_type myhash(int id) { + hashvalue_type h = (hashvalue_type)id & 0x0f; + h |= (h << 28); + return h; +} + +/** allocate new key, fill in hash */ +static testkey_type* newkey(int id) { + testkey_type* k = (testkey_type*)calloc(1, sizeof(testkey_type)); + if(!k) fatal_exit("out of memory"); + k->id = id; + k->entry.hash = myhash(id); + k->entry.key = k; + lock_rw_init(&k->entry.lock); + return k; +} +/** new data el */ +static testdata_type* newdata(int val) { + testdata_type* d = (testdata_type*)calloc(1, + sizeof(testdata_type)); + if(!d) fatal_exit("out of memory"); + d->data = val; + return d; +} + +/** test hashtable using short sequence */ +static void +test_short_table(struct slabhash* table) +{ + testkey_type* k = newkey(12); + testkey_type* k2 = newkey(14); + testdata_type* d = newdata(128); + testdata_type* d2 = newdata(129); + + k->entry.data = d; + k2->entry.data = d2; + + slabhash_insert(table, myhash(12), &k->entry, d, NULL); + slabhash_insert(table, myhash(14), &k2->entry, d2, NULL); + + unit_assert( slabhash_lookup(table, myhash(12), k, 0) == &k->entry); + lock_rw_unlock( &k->entry.lock ); + unit_assert( slabhash_lookup(table, myhash(14), k2, 0) == &k2->entry); + lock_rw_unlock( &k2->entry.lock ); + slabhash_remove(table, myhash(12), k); + slabhash_remove(table, myhash(14), k2); +} + +/** number of hash test max */ +#define HASHTESTMAX 32 + +/** test adding a random element */ +static void +testadd(struct slabhash* table, testdata_type* ref[]) +{ + int numtoadd = random() % HASHTESTMAX; + testdata_type* data = newdata(numtoadd); + testkey_type* key = newkey(numtoadd); + key->entry.data = data; + slabhash_insert(table, myhash(numtoadd), &key->entry, data, NULL); + ref[numtoadd] = data; +} + +/** test adding a random element */ +static void +testremove(struct slabhash* table, testdata_type* ref[]) +{ + int num = random() % HASHTESTMAX; + testkey_type* key = newkey(num); + slabhash_remove(table, myhash(num), key); + ref[num] = NULL; + delkey(key); +} + +/** test adding a random element */ +static void +testlookup(struct slabhash* table, testdata_type* ref[]) +{ + int num = random() % HASHTESTMAX; + testkey_type* key = newkey(num); + struct lruhash_entry* en = slabhash_lookup(table, myhash(num), key, 0); + testdata_type* data = en? (testdata_type*)en->data : NULL; + if(en) { + unit_assert(en->key); + unit_assert(en->data); + } + if(0) log_info("lookup %d got %d, expect %d", num, en? data->data :-1, + ref[num]? ref[num]->data : -1); + unit_assert( data == ref[num] ); + if(en) { lock_rw_unlock(&en->lock); } + delkey(key); +} + +/** check integrity of hash table */ +static void +check_lru_table(struct lruhash* table) +{ + struct lruhash_entry* p; + size_t c = 0; + lock_quick_lock(&table->lock); + unit_assert( table->num <= table->size); + unit_assert( table->size_mask == (int)table->size-1 ); + unit_assert( (table->lru_start && table->lru_end) || + (!table->lru_start && !table->lru_end) ); + unit_assert( table->space_used <= table->space_max ); + /* check lru list integrity */ + if(table->lru_start) + unit_assert(table->lru_start->lru_prev == NULL); + if(table->lru_end) + unit_assert(table->lru_end->lru_next == NULL); + p = table->lru_start; + while(p) { + if(p->lru_prev) { + unit_assert(p->lru_prev->lru_next == p); + } + if(p->lru_next) { + unit_assert(p->lru_next->lru_prev == p); + } + c++; + p = p->lru_next; + } + unit_assert(c == table->num); + + /* this assertion is specific to the unit test */ + unit_assert( table->space_used == + table->num * test_slabhash_sizefunc(NULL, NULL) ); + lock_quick_unlock(&table->lock); +} + +/** check integrity of hash table */ +static void +check_table(struct slabhash* table) +{ + size_t i; + for(i=0; isize; 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; ireply_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; ian_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; irk.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; irrset_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; irrset_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"); +} -- 2.20.1