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 */
70 struct 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  */
82 struct 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 */
94 static int num_wait = 0;
95 
96 /** usage information for asynclook */
usage(char * argv[])97 static 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 */
114 static void
print_result(struct lookinfo * info)115 print_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 */
141 static void
lookup_is_done(void * mydata,int err,struct ub_result * result)142 lookup_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 */
154 static void
checkerr(const char * desc,int err)155 checkerr(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 */
172 struct 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 */
190 static int q_is_localhost = 0;
191 
192 /** check result structure for the 'correct' answer */
193 static void
ext_check_result(const char * desc,int err,struct ub_result * result)194 ext_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 */
266 static void
ext_callback(void * mydata,int err,struct ub_result * result)267 ext_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 */
297 static void*
ext_thread(void * arg)298 ext_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 */
358 static int
ext_test(struct ub_ctx * ctx,int argc,char ** argv)359 ext_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. */
397 extern int optind;
398 /** getopt global, in case header files fail to declare it. */
399 extern char* optarg;
400 
401 /** main program for asynclook */
main(int argc,char ** argv)402 int 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