529 lines
13 KiB
C
529 lines
13 KiB
C
/*
|
|
* testcode/asynclook.c - debug program perform async libunbound queries.
|
|
*
|
|
* Copyright (c) 2008, NLnet Labs. All rights reserved.
|
|
*
|
|
* This software is open source.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
*
|
|
* Redistributions of source code must retain the above copyright notice,
|
|
* this list of conditions and the following disclaimer.
|
|
*
|
|
* Redistributions in binary form must reproduce the above copyright notice,
|
|
* this list of conditions and the following disclaimer in the documentation
|
|
* and/or other materials provided with the distribution.
|
|
*
|
|
* Neither the name of the NLNET LABS nor the names of its contributors may
|
|
* be used to endorse or promote products derived from this software without
|
|
* specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
* HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
|
|
* TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
|
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
|
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
|
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
/**
|
|
* \file
|
|
*
|
|
* This program shows the results from several background lookups,
|
|
* while printing time in the foreground.
|
|
*/
|
|
|
|
#include "config.h"
|
|
#ifdef HAVE_GETOPT_H
|
|
#include <getopt.h>
|
|
#endif
|
|
#include "libunbound/unbound.h"
|
|
#include "libunbound/context.h"
|
|
#include "util/locks.h"
|
|
#include "util/log.h"
|
|
#include "sldns/rrdef.h"
|
|
#ifdef UNBOUND_ALLOC_LITE
|
|
#undef malloc
|
|
#undef calloc
|
|
#undef realloc
|
|
#undef free
|
|
#undef strdup
|
|
#endif
|
|
|
|
/** keeping track of the async ids */
|
|
struct track_id {
|
|
/** the id to pass to libunbound to cancel */
|
|
int id;
|
|
/** true if cancelled */
|
|
int cancel;
|
|
/** a lock on this structure for thread safety */
|
|
lock_basic_type lock;
|
|
};
|
|
|
|
/**
|
|
* result list for the lookups
|
|
*/
|
|
struct lookinfo {
|
|
/** name to look up */
|
|
char* name;
|
|
/** tracking number that can be used to cancel the query */
|
|
int async_id;
|
|
/** error code from libunbound */
|
|
int err;
|
|
/** result from lookup */
|
|
struct ub_result* result;
|
|
};
|
|
|
|
/** global variable to see how many queries we have left */
|
|
static int num_wait = 0;
|
|
|
|
/** usage information for asynclook */
|
|
static void usage(char* argv[])
|
|
{
|
|
printf("usage: %s [options] name ...\n", argv[0]);
|
|
printf("names are looked up at the same time, asynchronously.\n");
|
|
printf(" -b : use blocking requests\n");
|
|
printf(" -c : cancel the requests\n");
|
|
printf(" -d : enable debug output\n");
|
|
printf(" -f addr : use addr, forward to that server\n");
|
|
printf(" -h : this help message\n");
|
|
printf(" -H fname : read hosts from fname\n");
|
|
printf(" -r fname : read resolv.conf from fname\n");
|
|
printf(" -t : use a resolver thread instead of forking a process\n");
|
|
printf(" -x : perform extended threaded test\n");
|
|
exit(1);
|
|
}
|
|
|
|
/** print result from lookup nicely */
|
|
static void
|
|
print_result(struct lookinfo* info)
|
|
{
|
|
char buf[100];
|
|
if(info->err) /* error (from libunbound) */
|
|
printf("%s: error %s\n", info->name,
|
|
ub_strerror(info->err));
|
|
else if(!info->result)
|
|
printf("%s: cancelled\n", info->name);
|
|
else if(info->result->havedata)
|
|
printf("%s: %s\n", info->name,
|
|
inet_ntop(AF_INET, info->result->data[0],
|
|
buf, (socklen_t)sizeof(buf)));
|
|
else {
|
|
/* there is no data, why that? */
|
|
if(info->result->rcode == 0 /*noerror*/ ||
|
|
info->result->nxdomain)
|
|
printf("%s: no data %s\n", info->name,
|
|
info->result->nxdomain?"(no such host)":
|
|
"(no IP4 address)");
|
|
else /* some error (from the server) */
|
|
printf("%s: DNS error %d\n", info->name,
|
|
info->result->rcode);
|
|
}
|
|
}
|
|
|
|
/** this is a function of type ub_callback_t */
|
|
static void
|
|
lookup_is_done(void* mydata, int err, struct ub_result* result)
|
|
{
|
|
/* cast mydata back to the correct type */
|
|
struct lookinfo* info = (struct lookinfo*)mydata;
|
|
fprintf(stderr, "name %s resolved\n", info->name);
|
|
info->err = err;
|
|
info->result = result;
|
|
/* one less to wait for */
|
|
num_wait--;
|
|
}
|
|
|
|
/** check error, if bad, exit with error message */
|
|
static void
|
|
checkerr(const char* desc, int err)
|
|
{
|
|
if(err != 0) {
|
|
printf("%s error: %s\n", desc, ub_strerror(err));
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
#ifdef THREADS_DISABLED
|
|
/** only one process can communicate with async worker */
|
|
#define NUMTHR 1
|
|
#else /* have threads */
|
|
/** number of threads to make in extended test */
|
|
#define NUMTHR 10
|
|
#endif
|
|
|
|
/** struct for extended thread info */
|
|
struct ext_thr_info {
|
|
/** thread num for debug */
|
|
int thread_num;
|
|
/** thread id */
|
|
ub_thread_type tid;
|
|
/** context */
|
|
struct ub_ctx* ctx;
|
|
/** size of array to query */
|
|
int argc;
|
|
/** array of names to query */
|
|
char** argv;
|
|
/** number of queries to do */
|
|
int numq;
|
|
};
|
|
|
|
/** if true, we are testing against 'localhost' and extra checking is done */
|
|
static int q_is_localhost = 0;
|
|
|
|
/** check result structure for the 'correct' answer */
|
|
static void
|
|
ext_check_result(const char* desc, int err, struct ub_result* result)
|
|
{
|
|
checkerr(desc, err);
|
|
if(result == NULL) {
|
|
printf("%s: error result is NULL.\n", desc);
|
|
exit(1);
|
|
}
|
|
if(q_is_localhost) {
|
|
if(strcmp(result->qname, "localhost") != 0) {
|
|
printf("%s: error result has wrong qname.\n", desc);
|
|
exit(1);
|
|
}
|
|
if(result->qtype != LDNS_RR_TYPE_A) {
|
|
printf("%s: error result has wrong qtype.\n", desc);
|
|
exit(1);
|
|
}
|
|
if(result->qclass != LDNS_RR_CLASS_IN) {
|
|
printf("%s: error result has wrong qclass.\n", desc);
|
|
exit(1);
|
|
}
|
|
if(result->data == NULL) {
|
|
printf("%s: error result->data is NULL.\n", desc);
|
|
exit(1);
|
|
}
|
|
if(result->len == NULL) {
|
|
printf("%s: error result->len is NULL.\n", desc);
|
|
exit(1);
|
|
}
|
|
if(result->rcode != 0) {
|
|
printf("%s: error result->rcode is set.\n", desc);
|
|
exit(1);
|
|
}
|
|
if(result->havedata == 0) {
|
|
printf("%s: error result->havedata is unset.\n", desc);
|
|
exit(1);
|
|
}
|
|
if(result->nxdomain != 0) {
|
|
printf("%s: error result->nxdomain is set.\n", desc);
|
|
exit(1);
|
|
}
|
|
if(result->secure || result->bogus) {
|
|
printf("%s: error result->secure or bogus is set.\n",
|
|
desc);
|
|
exit(1);
|
|
}
|
|
if(result->data[0] == NULL) {
|
|
printf("%s: error result->data[0] is NULL.\n", desc);
|
|
exit(1);
|
|
}
|
|
if(result->len[0] != 4) {
|
|
printf("%s: error result->len[0] is wrong.\n", desc);
|
|
exit(1);
|
|
}
|
|
if(result->len[1] != 0 || result->data[1] != NULL) {
|
|
printf("%s: error result->data[1] or len[1] is "
|
|
"wrong.\n", desc);
|
|
exit(1);
|
|
}
|
|
if(result->answer_packet == NULL) {
|
|
printf("%s: error result->answer_packet is NULL.\n",
|
|
desc);
|
|
exit(1);
|
|
}
|
|
if(result->answer_len != 54) {
|
|
printf("%s: error result->answer_len is wrong.\n",
|
|
desc);
|
|
exit(1);
|
|
}
|
|
}
|
|
}
|
|
|
|
/** extended bg result callback, this function is ub_callback_t */
|
|
static void
|
|
ext_callback(void* mydata, int err, struct ub_result* result)
|
|
{
|
|
struct track_id* my_id = (struct track_id*)mydata;
|
|
int doprint = 0;
|
|
if(my_id) {
|
|
/* I have an id, make sure we are not cancelled */
|
|
lock_basic_lock(&my_id->lock);
|
|
if(doprint)
|
|
printf("cb %d: ", my_id->id);
|
|
if(my_id->cancel) {
|
|
printf("error: query id=%d returned, but was cancelled\n",
|
|
my_id->id);
|
|
abort();
|
|
exit(1);
|
|
}
|
|
lock_basic_unlock(&my_id->lock);
|
|
}
|
|
ext_check_result("ext_callback", err, result);
|
|
log_assert(result);
|
|
if(doprint) {
|
|
struct lookinfo pi;
|
|
pi.name = result?result->qname:"noname";
|
|
pi.result = result;
|
|
pi.err = 0;
|
|
print_result(&pi);
|
|
}
|
|
ub_resolve_free(result);
|
|
}
|
|
|
|
/** extended thread worker */
|
|
static void*
|
|
ext_thread(void* arg)
|
|
{
|
|
struct ext_thr_info* inf = (struct ext_thr_info*)arg;
|
|
int i, r;
|
|
struct ub_result* result;
|
|
struct track_id* async_ids = NULL;
|
|
log_thread_set(&inf->thread_num);
|
|
if(inf->thread_num > NUMTHR*2/3) {
|
|
async_ids = (struct track_id*)calloc((size_t)inf->numq, sizeof(struct track_id));
|
|
if(!async_ids) {
|
|
printf("out of memory\n");
|
|
exit(1);
|
|
}
|
|
for(i=0; i<inf->numq; i++) {
|
|
lock_basic_init(&async_ids[i].lock);
|
|
}
|
|
}
|
|
for(i=0; i<inf->numq; i++) {
|
|
if(async_ids) {
|
|
r = ub_resolve_async(inf->ctx,
|
|
inf->argv[i%inf->argc], LDNS_RR_TYPE_A,
|
|
LDNS_RR_CLASS_IN, &async_ids[i], ext_callback,
|
|
&async_ids[i].id);
|
|
checkerr("ub_resolve_async", r);
|
|
if(i > 100) {
|
|
lock_basic_lock(&async_ids[i-100].lock);
|
|
r = ub_cancel(inf->ctx, async_ids[i-100].id);
|
|
if(r != UB_NOID)
|
|
async_ids[i-100].cancel=1;
|
|
lock_basic_unlock(&async_ids[i-100].lock);
|
|
if(r != UB_NOID)
|
|
checkerr("ub_cancel", r);
|
|
}
|
|
} else if(inf->thread_num > NUMTHR/2) {
|
|
/* async */
|
|
r = ub_resolve_async(inf->ctx,
|
|
inf->argv[i%inf->argc], LDNS_RR_TYPE_A,
|
|
LDNS_RR_CLASS_IN, NULL, ext_callback, NULL);
|
|
checkerr("ub_resolve_async", r);
|
|
} else {
|
|
/* blocking */
|
|
r = ub_resolve(inf->ctx, inf->argv[i%inf->argc],
|
|
LDNS_RR_TYPE_A, LDNS_RR_CLASS_IN, &result);
|
|
ext_check_result("ub_resolve", r, result);
|
|
ub_resolve_free(result);
|
|
}
|
|
}
|
|
if(inf->thread_num > NUMTHR/2) {
|
|
r = ub_wait(inf->ctx);
|
|
checkerr("ub_ctx_wait", r);
|
|
}
|
|
/* if these locks are destroyed, or if the async_ids is freed, then
|
|
a use-after-free happens in another thread.
|
|
The allocation is only part of this test, though. */
|
|
/*
|
|
if(async_ids) {
|
|
for(i=0; i<inf->numq; i++) {
|
|
lock_basic_destroy(&async_ids[i].lock);
|
|
}
|
|
}
|
|
free(async_ids);
|
|
*/
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/** perform extended threaded test */
|
|
static int
|
|
ext_test(struct ub_ctx* ctx, int argc, char** argv)
|
|
{
|
|
struct ext_thr_info inf[NUMTHR];
|
|
int i;
|
|
if(argc == 1 && strcmp(argv[0], "localhost") == 0)
|
|
q_is_localhost = 1;
|
|
printf("extended test start (%d threads)\n", NUMTHR);
|
|
for(i=0; i<NUMTHR; i++) {
|
|
/* 0 = this, 1 = library bg worker */
|
|
inf[i].thread_num = i+2;
|
|
inf[i].ctx = ctx;
|
|
inf[i].argc = argc;
|
|
inf[i].argv = argv;
|
|
inf[i].numq = 100;
|
|
ub_thread_create(&inf[i].tid, ext_thread, &inf[i]);
|
|
}
|
|
/* the work happens here */
|
|
for(i=0; i<NUMTHR; i++) {
|
|
ub_thread_join(inf[i].tid);
|
|
}
|
|
printf("extended test end\n");
|
|
ub_ctx_delete(ctx);
|
|
checklock_stop();
|
|
return 0;
|
|
}
|
|
|
|
/** getopt global, in case header files fail to declare it. */
|
|
extern int optind;
|
|
/** getopt global, in case header files fail to declare it. */
|
|
extern char* optarg;
|
|
|
|
/** main program for asynclook */
|
|
int main(int argc, char** argv)
|
|
{
|
|
int c;
|
|
struct ub_ctx* ctx;
|
|
struct lookinfo* lookups;
|
|
int i, r, cancel=0, blocking=0, ext=0;
|
|
|
|
/* init log now because solaris thr_key_create() is not threadsafe */
|
|
log_init(0,0,0);
|
|
/* lock debug start (if any) */
|
|
checklock_start();
|
|
|
|
/* create context */
|
|
ctx = ub_ctx_create();
|
|
if(!ctx) {
|
|
printf("could not create context, %s\n", strerror(errno));
|
|
return 1;
|
|
}
|
|
|
|
/* command line options */
|
|
if(argc == 1) {
|
|
usage(argv);
|
|
}
|
|
while( (c=getopt(argc, argv, "bcdf:hH:r:tx")) != -1) {
|
|
switch(c) {
|
|
case 'd':
|
|
r = ub_ctx_debuglevel(ctx, 3);
|
|
checkerr("ub_ctx_debuglevel", r);
|
|
break;
|
|
case 't':
|
|
r = ub_ctx_async(ctx, 1);
|
|
checkerr("ub_ctx_async", r);
|
|
break;
|
|
case 'c':
|
|
cancel = 1;
|
|
break;
|
|
case 'b':
|
|
blocking = 1;
|
|
break;
|
|
case 'r':
|
|
r = ub_ctx_resolvconf(ctx, optarg);
|
|
if(r != 0) {
|
|
printf("ub_ctx_resolvconf "
|
|
"error: %s : %s\n",
|
|
ub_strerror(r),
|
|
strerror(errno));
|
|
return 1;
|
|
}
|
|
break;
|
|
case 'H':
|
|
r = ub_ctx_hosts(ctx, optarg);
|
|
if(r != 0) {
|
|
printf("ub_ctx_hosts "
|
|
"error: %s : %s\n",
|
|
ub_strerror(r),
|
|
strerror(errno));
|
|
return 1;
|
|
}
|
|
break;
|
|
case 'f':
|
|
r = ub_ctx_set_fwd(ctx, optarg);
|
|
checkerr("ub_ctx_set_fwd", r);
|
|
break;
|
|
case 'x':
|
|
ext = 1;
|
|
break;
|
|
case 'h':
|
|
case '?':
|
|
default:
|
|
usage(argv);
|
|
}
|
|
}
|
|
argc -= optind;
|
|
argv += optind;
|
|
|
|
if(ext)
|
|
return ext_test(ctx, argc, argv);
|
|
|
|
/* allocate array for results. */
|
|
lookups = (struct lookinfo*)calloc((size_t)argc,
|
|
sizeof(struct lookinfo));
|
|
if(!lookups) {
|
|
printf("out of memory\n");
|
|
return 1;
|
|
}
|
|
|
|
/* perform asynchronous calls */
|
|
num_wait = argc;
|
|
for(i=0; i<argc; i++) {
|
|
lookups[i].name = argv[i];
|
|
if(blocking) {
|
|
fprintf(stderr, "lookup %s\n", argv[i]);
|
|
r = ub_resolve(ctx, argv[i], LDNS_RR_TYPE_A,
|
|
LDNS_RR_CLASS_IN, &lookups[i].result);
|
|
checkerr("ub_resolve", r);
|
|
} else {
|
|
fprintf(stderr, "start async lookup %s\n", argv[i]);
|
|
r = ub_resolve_async(ctx, argv[i], LDNS_RR_TYPE_A,
|
|
LDNS_RR_CLASS_IN, &lookups[i], &lookup_is_done,
|
|
&lookups[i].async_id);
|
|
checkerr("ub_resolve_async", r);
|
|
}
|
|
}
|
|
if(blocking)
|
|
num_wait = 0;
|
|
else if(cancel) {
|
|
for(i=0; i<argc; i++) {
|
|
fprintf(stderr, "cancel %s\n", argv[i]);
|
|
r = ub_cancel(ctx, lookups[i].async_id);
|
|
if(r != UB_NOID)
|
|
checkerr("ub_cancel", r);
|
|
}
|
|
num_wait = 0;
|
|
}
|
|
|
|
/* wait while the hostnames are looked up. Do something useful here */
|
|
if(num_wait > 0)
|
|
for(i=0; i<1000; i++) {
|
|
usleep(100000);
|
|
fprintf(stderr, "%g seconds passed\n", 0.1*(double)i);
|
|
r = ub_process(ctx);
|
|
checkerr("ub_process", r);
|
|
if(num_wait == 0)
|
|
break;
|
|
}
|
|
if(i>=999) {
|
|
printf("timed out\n");
|
|
return 0;
|
|
}
|
|
printf("lookup complete\n");
|
|
|
|
/* print lookup results */
|
|
for(i=0; i<argc; i++) {
|
|
print_result(&lookups[i]);
|
|
ub_resolve_free(lookups[i].result);
|
|
}
|
|
|
|
ub_ctx_delete(ctx);
|
|
free(lookups);
|
|
checklock_stop();
|
|
return 0;
|
|
}
|