1/*
2 * testcode/asynclook.c - debug program perform async libunbound queries.
3 *
4 * Copyright (c) 2008, NLnet Labs. All rights reserved.
5 *
6 * This software is open source.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 *
12 * Redistributions of source code must retain the above copyright notice,
13 * this list of conditions and the following disclaimer.
14 *
15 * Redistributions in binary form must reproduce the above copyright notice,
16 * this list of conditions and the following disclaimer in the documentation
17 * and/or other materials provided with the distribution.
18 *
19 * Neither the name of the NLNET LABS nor the names of its contributors may
20 * be used to endorse or promote products derived from this software without
21 * specific prior written permission.
22 *
23 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
24 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
25 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
26 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
27 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
28 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
29 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
30 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
31 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
32 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
33 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34 */
35
36/**
37 * \file
38 *
39 * This program shows the results from several background lookups,
40 * while printing time in the foreground.
41 */
42
43#include "config.h"
44#ifdef HAVE_GETOPT_H
45#include <getopt.h>
46#endif
47#include "libunbound/unbound.h"
48#include "libunbound/context.h"
49#include "util/locks.h"
50#include "util/log.h"
51#include "sldns/rrdef.h"
52#ifdef UNBOUND_ALLOC_LITE
53#undef malloc
54#undef calloc
55#undef realloc
56#undef free
57#undef strdup
58#endif
59#ifdef HAVE_SSL
60#ifdef HAVE_OPENSSL_SSL_H
61#include <openssl/ssl.h>
62#endif
63#ifdef HAVE_OPENSSL_ERR_H
64#include <openssl/err.h>
65#endif
66#endif /* HAVE_SSL */
67
68
69/** keeping track of the async ids */
70struct track_id {
71	/** the id to pass to libunbound to cancel */
72	int id;
73	/** true if cancelled */
74	int cancel;
75	/** a lock on this structure for thread safety */
76	lock_basic_type lock;
77};
78
79/**
80 * result list for the lookups
81 */
82struct lookinfo {
83	/** name to look up */
84	char* name;
85	/** tracking number that can be used to cancel the query */
86	int async_id;
87	/** error code from libunbound */
88	int err;
89	/** result from lookup */
90	struct ub_result* result;
91};
92
93/** global variable to see how many queries we have left */
94static int num_wait = 0;
95
96/** usage information for asynclook */
97static void usage(char* argv[])
98{
99	printf("usage: %s [options] name ...\n", argv[0]);
100	printf("names are looked up at the same time, asynchronously.\n");
101	printf("	-b : use blocking requests\n");
102	printf("	-c : cancel the requests\n");
103	printf("	-d : enable debug output\n");
104	printf("	-f addr : use addr, forward to that server\n");
105	printf("	-h : this help message\n");
106	printf("	-H fname : read hosts from fname\n");
107	printf("	-r fname : read resolv.conf from fname\n");
108	printf("	-t : use a resolver thread instead of forking a process\n");
109	printf("	-x : perform extended threaded test\n");
110	exit(1);
111}
112
113/** print result from lookup nicely */
114static void
115print_result(struct lookinfo* info)
116{
117	char buf[100];
118	if(info->err) /* error (from libunbound) */
119		printf("%s: error %s\n", info->name,
120			ub_strerror(info->err));
121	else if(!info->result)
122		printf("%s: cancelled\n", info->name);
123	else if(info->result->havedata)
124		printf("%s: %s\n", info->name,
125			inet_ntop(AF_INET, info->result->data[0],
126			buf, (socklen_t)sizeof(buf)));
127	else {
128		/* there is no data, why that? */
129		if(info->result->rcode == 0 /*noerror*/ ||
130			info->result->nxdomain)
131			printf("%s: no data %s\n", info->name,
132			info->result->nxdomain?"(no such host)":
133			"(no IP4 address)");
134		else	/* some error (from the server) */
135			printf("%s: DNS error %d\n", info->name,
136				info->result->rcode);
137	}
138}
139
140/** this is a function of type ub_callback_t */
141static void
142lookup_is_done(void* mydata, int err, struct ub_result* result)
143{
144	/* cast mydata back to the correct type */
145	struct lookinfo* info = (struct lookinfo*)mydata;
146	fprintf(stderr, "name %s resolved\n", info->name);
147	info->err = err;
148	info->result = result;
149	/* one less to wait for */
150	num_wait--;
151}
152
153/** check error, if bad, exit with error message */
154static void
155checkerr(const char* desc, int err)
156{
157	if(err != 0) {
158		printf("%s error: %s\n", desc, ub_strerror(err));
159		exit(1);
160	}
161}
162
163#ifdef THREADS_DISABLED
164/** only one process can communicate with async worker */
165#define NUMTHR 1
166#else /* have threads */
167/** number of threads to make in extended test */
168#define NUMTHR 10
169#endif
170
171/** struct for extended thread info */
172struct ext_thr_info {
173	/** thread num for debug */
174	int thread_num;
175	/** thread id */
176	ub_thread_type tid;
177	/** context */
178	struct ub_ctx* ctx;
179	/** size of array to query */
180	int argc;
181	/** array of names to query */
182	char** argv;
183	/** number of queries to do */
184	int numq;
185	/** list of ids to free once threads are done */
186	struct track_id* id_list;
187};
188
189/** if true, we are testing against 'localhost' and extra checking is done */
190static int q_is_localhost = 0;
191
192/** check result structure for the 'correct' answer */
193static void
194ext_check_result(const char* desc, int err, struct ub_result* result)
195{
196	checkerr(desc, err);
197	if(result == NULL) {
198		printf("%s: error result is NULL.\n", desc);
199		exit(1);
200	}
201	if(q_is_localhost) {
202		if(strcmp(result->qname, "localhost") != 0) {
203			printf("%s: error result has wrong qname.\n", desc);
204			exit(1);
205		}
206		if(result->qtype != LDNS_RR_TYPE_A) {
207			printf("%s: error result has wrong qtype.\n", desc);
208			exit(1);
209		}
210		if(result->qclass != LDNS_RR_CLASS_IN) {
211			printf("%s: error result has wrong qclass.\n", desc);
212			exit(1);
213		}
214		if(result->data == NULL) {
215			printf("%s: error result->data is NULL.\n", desc);
216			exit(1);
217		}
218		if(result->len == NULL) {
219			printf("%s: error result->len is NULL.\n", desc);
220			exit(1);
221		}
222		if(result->rcode != 0) {
223			printf("%s: error result->rcode is set.\n", desc);
224			exit(1);
225		}
226		if(result->havedata == 0) {
227			printf("%s: error result->havedata is unset.\n", desc);
228			exit(1);
229		}
230		if(result->nxdomain != 0) {
231			printf("%s: error result->nxdomain is set.\n", desc);
232			exit(1);
233		}
234		if(result->secure || result->bogus) {
235			printf("%s: error result->secure or bogus is set.\n",
236				desc);
237			exit(1);
238		}
239		if(result->data[0] == NULL) {
240			printf("%s: error result->data[0] is NULL.\n", desc);
241			exit(1);
242		}
243		if(result->len[0] != 4) {
244			printf("%s: error result->len[0] is wrong.\n", desc);
245			exit(1);
246		}
247		if(result->len[1] != 0 || result->data[1] != NULL) {
248			printf("%s: error result->data[1] or len[1] is "
249				"wrong.\n", desc);
250			exit(1);
251		}
252		if(result->answer_packet == NULL) {
253			printf("%s: error result->answer_packet is NULL.\n",
254				desc);
255			exit(1);
256		}
257		if(result->answer_len != 54) {
258			printf("%s: error result->answer_len is wrong.\n",
259				desc);
260			exit(1);
261		}
262	}
263}
264
265/** extended bg result callback, this function is ub_callback_t */
266static void
267ext_callback(void* mydata, int err, struct ub_result* result)
268{
269	struct track_id* my_id = (struct track_id*)mydata;
270	int doprint = 0;
271	if(my_id) {
272		/* I have an id, make sure we are not cancelled */
273		lock_basic_lock(&my_id->lock);
274		if(doprint)
275			printf("cb %d: ", my_id->id);
276		if(my_id->cancel) {
277			printf("error: query id=%d returned, but was cancelled\n",
278				my_id->id);
279			abort();
280			exit(1);
281		}
282		lock_basic_unlock(&my_id->lock);
283	}
284	ext_check_result("ext_callback", err, result);
285	log_assert(result);
286	if(doprint) {
287		struct lookinfo pi;
288		pi.name = result?result->qname:"noname";
289		pi.result = result;
290		pi.err = 0;
291		print_result(&pi);
292	}
293	ub_resolve_free(result);
294}
295
296/** extended thread worker */
297static void*
298ext_thread(void* arg)
299{
300	struct ext_thr_info* inf = (struct ext_thr_info*)arg;
301	int i, r;
302	struct ub_result* result;
303	struct track_id* async_ids = NULL;
304	log_thread_set(&inf->thread_num);
305	if(inf->thread_num > NUMTHR*2/3) {
306		async_ids = (struct track_id*)calloc((size_t)inf->numq, sizeof(struct track_id));
307		if(!async_ids) {
308			printf("out of memory\n");
309			exit(1);
310		}
311		for(i=0; i<inf->numq; i++) {
312			lock_basic_init(&async_ids[i].lock);
313		}
314		inf->id_list = async_ids;
315	}
316	for(i=0; i<inf->numq; i++) {
317		if(async_ids) {
318			r = ub_resolve_async(inf->ctx,
319				inf->argv[i%inf->argc], LDNS_RR_TYPE_A,
320				LDNS_RR_CLASS_IN, &async_ids[i], ext_callback,
321				&async_ids[i].id);
322			checkerr("ub_resolve_async", r);
323			if(i > 100) {
324				lock_basic_lock(&async_ids[i-100].lock);
325				r = ub_cancel(inf->ctx, async_ids[i-100].id);
326				if(r != UB_NOID)
327					async_ids[i-100].cancel=1;
328				lock_basic_unlock(&async_ids[i-100].lock);
329				if(r != UB_NOID)
330					checkerr("ub_cancel", r);
331			}
332		} else if(inf->thread_num > NUMTHR/2) {
333			/* async */
334			r = ub_resolve_async(inf->ctx,
335				inf->argv[i%inf->argc], LDNS_RR_TYPE_A,
336				LDNS_RR_CLASS_IN, NULL, ext_callback, NULL);
337			checkerr("ub_resolve_async", r);
338		} else  {
339			/* blocking */
340			r = ub_resolve(inf->ctx, inf->argv[i%inf->argc],
341				LDNS_RR_TYPE_A, LDNS_RR_CLASS_IN, &result);
342			ext_check_result("ub_resolve", r, result);
343			ub_resolve_free(result);
344		}
345	}
346	if(inf->thread_num > NUMTHR/2) {
347		r = ub_wait(inf->ctx);
348		checkerr("ub_ctx_wait", r);
349	}
350	/* if these locks are destroyed, or if the async_ids is freed, then
351	   a use-after-free happens in another thread.
352	   The allocation is only part of this test, though. */
353
354	return NULL;
355}
356
357/** perform extended threaded test */
358static int
359ext_test(struct ub_ctx* ctx, int argc, char** argv)
360{
361	struct ext_thr_info inf[NUMTHR];
362	int i;
363	if(argc == 1 && strcmp(argv[0], "localhost") == 0)
364		q_is_localhost = 1;
365	printf("extended test start (%d threads)\n", NUMTHR);
366	for(i=0; i<NUMTHR; i++) {
367		/* 0 = this, 1 = library bg worker */
368		inf[i].thread_num = i+2;
369		inf[i].ctx = ctx;
370		inf[i].argc = argc;
371		inf[i].argv = argv;
372		inf[i].numq = 100;
373		inf[i].id_list = NULL;
374		ub_thread_create(&inf[i].tid, ext_thread, &inf[i]);
375	}
376	/* the work happens here */
377	for(i=0; i<NUMTHR; i++) {
378		ub_thread_join(inf[i].tid);
379	}
380	printf("extended test end\n");
381	/* free the id lists */
382	for(i=0; i<NUMTHR; i++) {
383		if(inf[i].id_list) {
384			int j;
385			for(j=0; j<inf[i].numq; j++) {
386				lock_basic_destroy(&inf[i].id_list[j].lock);
387			}
388			free(inf[i].id_list);
389		}
390	}
391	ub_ctx_delete(ctx);
392	checklock_stop();
393	return 0;
394}
395
396/** getopt global, in case header files fail to declare it. */
397extern int optind;
398/** getopt global, in case header files fail to declare it. */
399extern char* optarg;
400
401/** main program for asynclook */
402int main(int argc, char** argv)
403{
404	int c;
405	struct ub_ctx* ctx;
406	struct lookinfo* lookups;
407	int i, r, cancel=0, blocking=0, ext=0;
408
409	checklock_start();
410	/* init log now because solaris thr_key_create() is not threadsafe */
411	log_init(0,0,0);
412	/* lock debug start (if any) */
413
414	/* create context */
415	ctx = ub_ctx_create();
416	if(!ctx) {
417		printf("could not create context, %s\n", strerror(errno));
418		return 1;
419	}
420
421	/* command line options */
422	if(argc == 1) {
423		usage(argv);
424	}
425	while( (c=getopt(argc, argv, "bcdf:hH:r:tx")) != -1) {
426		switch(c) {
427			case 'd':
428				r = ub_ctx_debuglevel(ctx, 3);
429				checkerr("ub_ctx_debuglevel", r);
430				break;
431			case 't':
432				r = ub_ctx_async(ctx, 1);
433				checkerr("ub_ctx_async", r);
434				break;
435			case 'c':
436				cancel = 1;
437				break;
438			case 'b':
439				blocking = 1;
440				break;
441			case 'r':
442				r = ub_ctx_resolvconf(ctx, optarg);
443				if(r != 0) {
444					printf("ub_ctx_resolvconf "
445						"error: %s : %s\n",
446						ub_strerror(r),
447						strerror(errno));
448					return 1;
449				}
450				break;
451			case 'H':
452				r = ub_ctx_hosts(ctx, optarg);
453				if(r != 0) {
454					printf("ub_ctx_hosts "
455						"error: %s : %s\n",
456						ub_strerror(r),
457						strerror(errno));
458					return 1;
459				}
460				break;
461			case 'f':
462				r = ub_ctx_set_fwd(ctx, optarg);
463				checkerr("ub_ctx_set_fwd", r);
464				break;
465			case 'x':
466				ext = 1;
467				break;
468			case 'h':
469			case '?':
470			default:
471				usage(argv);
472		}
473	}
474	argc -= optind;
475	argv += optind;
476
477#ifdef HAVE_SSL
478#ifdef HAVE_ERR_LOAD_CRYPTO_STRINGS
479	ERR_load_crypto_strings();
480#endif
481#if OPENSSL_VERSION_NUMBER < 0x10100000 || !defined(HAVE_OPENSSL_INIT_SSL)
482	ERR_load_SSL_strings();
483#endif
484#if OPENSSL_VERSION_NUMBER < 0x10100000 || !defined(HAVE_OPENSSL_INIT_CRYPTO)
485#  ifndef S_SPLINT_S
486	OpenSSL_add_all_algorithms();
487#  endif
488#else
489	OPENSSL_init_crypto(OPENSSL_INIT_ADD_ALL_CIPHERS
490		| OPENSSL_INIT_ADD_ALL_DIGESTS
491		| OPENSSL_INIT_LOAD_CRYPTO_STRINGS, NULL);
492#endif
493#if OPENSSL_VERSION_NUMBER < 0x10100000 || !defined(HAVE_OPENSSL_INIT_SSL)
494	(void)SSL_library_init();
495#else
496	(void)OPENSSL_init_ssl(OPENSSL_INIT_LOAD_SSL_STRINGS, NULL);
497#endif
498#endif /* HAVE_SSL */
499
500	if(ext)
501		return ext_test(ctx, argc, argv);
502
503	/* allocate array for results. */
504	lookups = (struct lookinfo*)calloc((size_t)argc,
505		sizeof(struct lookinfo));
506	if(!lookups) {
507		printf("out of memory\n");
508		return 1;
509	}
510
511	/* perform asynchronous calls */
512	num_wait = argc;
513	for(i=0; i<argc; i++) {
514		lookups[i].name = argv[i];
515		if(blocking) {
516			fprintf(stderr, "lookup %s\n", argv[i]);
517			r = ub_resolve(ctx, argv[i], LDNS_RR_TYPE_A,
518				LDNS_RR_CLASS_IN, &lookups[i].result);
519			checkerr("ub_resolve", r);
520		} else {
521			fprintf(stderr, "start async lookup %s\n", argv[i]);
522			r = ub_resolve_async(ctx, argv[i], LDNS_RR_TYPE_A,
523				LDNS_RR_CLASS_IN, &lookups[i], &lookup_is_done,
524				&lookups[i].async_id);
525			checkerr("ub_resolve_async", r);
526		}
527	}
528	if(blocking)
529		num_wait = 0;
530	else if(cancel) {
531		for(i=0; i<argc; i++) {
532			fprintf(stderr, "cancel %s\n", argv[i]);
533			r = ub_cancel(ctx, lookups[i].async_id);
534			if(r != UB_NOID)
535				checkerr("ub_cancel", r);
536		}
537		num_wait = 0;
538	}
539
540	/* wait while the hostnames are looked up. Do something useful here */
541	if(num_wait > 0)
542	    for(i=0; i<1000; i++) {
543		usleep(100000);
544		fprintf(stderr, "%g seconds passed\n", 0.1*(double)i);
545		r = ub_process(ctx);
546		checkerr("ub_process", r);
547		if(num_wait == 0)
548			break;
549	}
550	if(i>=999) {
551		printf("timed out\n");
552		return 0;
553	}
554	printf("lookup complete\n");
555
556	/* print lookup results */
557	for(i=0; i<argc; i++) {
558		print_result(&lookups[i]);
559		ub_resolve_free(lookups[i].result);
560	}
561
562	ub_ctx_delete(ctx);
563	free(lookups);
564	checklock_stop();
565	return 0;
566}
567