xref: /netbsd-src/external/bsd/unbound/dist/testcode/asynclook.c (revision bdc22b2e01993381dcefeff2bc9b56ca75a4235c)
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 
60 /** keeping track of the async ids */
61 struct track_id {
62 	/** the id to pass to libunbound to cancel */
63 	int id;
64 	/** true if cancelled */
65 	int cancel;
66 	/** a lock on this structure for thread safety */
67 	lock_basic_type lock;
68 };
69 
70 /**
71  * result list for the lookups
72  */
73 struct lookinfo {
74 	/** name to look up */
75 	char* name;
76 	/** tracking number that can be used to cancel the query */
77 	int async_id;
78 	/** error code from libunbound */
79 	int err;
80 	/** result from lookup */
81 	struct ub_result* result;
82 };
83 
84 /** global variable to see how many queries we have left */
85 static int num_wait = 0;
86 
87 /** usage information for asynclook */
88 static void usage(char* argv[])
89 {
90 	printf("usage: %s [options] name ...\n", argv[0]);
91 	printf("names are looked up at the same time, asynchronously.\n");
92 	printf("	-b : use blocking requests\n");
93 	printf("	-c : cancel the requests\n");
94 	printf("	-d : enable debug output\n");
95 	printf("	-f addr : use addr, forward to that server\n");
96 	printf("	-h : this help message\n");
97 	printf("	-H fname : read hosts from fname\n");
98 	printf("	-r fname : read resolv.conf from fname\n");
99 	printf("	-t : use a resolver thread instead of forking a process\n");
100 	printf("	-x : perform extended threaded test\n");
101 	exit(1);
102 }
103 
104 /** print result from lookup nicely */
105 static void
106 print_result(struct lookinfo* info)
107 {
108 	char buf[100];
109 	if(info->err) /* error (from libunbound) */
110 		printf("%s: error %s\n", info->name,
111 			ub_strerror(info->err));
112 	else if(!info->result)
113 		printf("%s: cancelled\n", info->name);
114 	else if(info->result->havedata)
115 		printf("%s: %s\n", info->name,
116 			inet_ntop(AF_INET, info->result->data[0],
117 			buf, (socklen_t)sizeof(buf)));
118 	else {
119 		/* there is no data, why that? */
120 		if(info->result->rcode == 0 /*noerror*/ ||
121 			info->result->nxdomain)
122 			printf("%s: no data %s\n", info->name,
123 			info->result->nxdomain?"(no such host)":
124 			"(no IP4 address)");
125 		else	/* some error (from the server) */
126 			printf("%s: DNS error %d\n", info->name,
127 				info->result->rcode);
128 	}
129 }
130 
131 /** this is a function of type ub_callback_t */
132 static void
133 lookup_is_done(void* mydata, int err, struct ub_result* result)
134 {
135 	/* cast mydata back to the correct type */
136 	struct lookinfo* info = (struct lookinfo*)mydata;
137 	fprintf(stderr, "name %s resolved\n", info->name);
138 	info->err = err;
139 	info->result = result;
140 	/* one less to wait for */
141 	num_wait--;
142 }
143 
144 /** check error, if bad, exit with error message */
145 static void
146 checkerr(const char* desc, int err)
147 {
148 	if(err != 0) {
149 		printf("%s error: %s\n", desc, ub_strerror(err));
150 		exit(1);
151 	}
152 }
153 
154 #ifdef THREADS_DISABLED
155 /** only one process can communicate with async worker */
156 #define NUMTHR 1
157 #else /* have threads */
158 /** number of threads to make in extended test */
159 #define NUMTHR 10
160 #endif
161 
162 /** struct for extended thread info */
163 struct ext_thr_info {
164 	/** thread num for debug */
165 	int thread_num;
166 	/** thread id */
167 	ub_thread_type tid;
168 	/** context */
169 	struct ub_ctx* ctx;
170 	/** size of array to query */
171 	int argc;
172 	/** array of names to query */
173 	char** argv;
174 	/** number of queries to do */
175 	int numq;
176 };
177 
178 /** if true, we are testing against 'localhost' and extra checking is done */
179 static int q_is_localhost = 0;
180 
181 /** check result structure for the 'correct' answer */
182 static void
183 ext_check_result(const char* desc, int err, struct ub_result* result)
184 {
185 	checkerr(desc, err);
186 	if(result == NULL) {
187 		printf("%s: error result is NULL.\n", desc);
188 		exit(1);
189 	}
190 	if(q_is_localhost) {
191 		if(strcmp(result->qname, "localhost") != 0) {
192 			printf("%s: error result has wrong qname.\n", desc);
193 			exit(1);
194 		}
195 		if(result->qtype != LDNS_RR_TYPE_A) {
196 			printf("%s: error result has wrong qtype.\n", desc);
197 			exit(1);
198 		}
199 		if(result->qclass != LDNS_RR_CLASS_IN) {
200 			printf("%s: error result has wrong qclass.\n", desc);
201 			exit(1);
202 		}
203 		if(result->data == NULL) {
204 			printf("%s: error result->data is NULL.\n", desc);
205 			exit(1);
206 		}
207 		if(result->len == NULL) {
208 			printf("%s: error result->len is NULL.\n", desc);
209 			exit(1);
210 		}
211 		if(result->rcode != 0) {
212 			printf("%s: error result->rcode is set.\n", desc);
213 			exit(1);
214 		}
215 		if(result->havedata == 0) {
216 			printf("%s: error result->havedata is unset.\n", desc);
217 			exit(1);
218 		}
219 		if(result->nxdomain != 0) {
220 			printf("%s: error result->nxdomain is set.\n", desc);
221 			exit(1);
222 		}
223 		if(result->secure || result->bogus) {
224 			printf("%s: error result->secure or bogus is set.\n",
225 				desc);
226 			exit(1);
227 		}
228 		if(result->data[0] == NULL) {
229 			printf("%s: error result->data[0] is NULL.\n", desc);
230 			exit(1);
231 		}
232 		if(result->len[0] != 4) {
233 			printf("%s: error result->len[0] is wrong.\n", desc);
234 			exit(1);
235 		}
236 		if(result->len[1] != 0 || result->data[1] != NULL) {
237 			printf("%s: error result->data[1] or len[1] is "
238 				"wrong.\n", desc);
239 			exit(1);
240 		}
241 		if(result->answer_packet == NULL) {
242 			printf("%s: error result->answer_packet is NULL.\n",
243 				desc);
244 			exit(1);
245 		}
246 		if(result->answer_len != 54) {
247 			printf("%s: error result->answer_len is wrong.\n",
248 				desc);
249 			exit(1);
250 		}
251 	}
252 }
253 
254 /** extended bg result callback, this function is ub_callback_t */
255 static void
256 ext_callback(void* mydata, int err, struct ub_result* result)
257 {
258 	struct track_id* my_id = (struct track_id*)mydata;
259 	int doprint = 0;
260 	if(my_id) {
261 		/* I have an id, make sure we are not cancelled */
262 		lock_basic_lock(&my_id->lock);
263 		if(doprint)
264 			printf("cb %d: ", my_id->id);
265 		if(my_id->cancel) {
266 			printf("error: query id=%d returned, but was cancelled\n",
267 				my_id->id);
268 			abort();
269 			exit(1);
270 		}
271 		lock_basic_unlock(&my_id->lock);
272 	}
273 	ext_check_result("ext_callback", err, result);
274 	log_assert(result);
275 	if(doprint) {
276 		struct lookinfo pi;
277 		pi.name = result?result->qname:"noname";
278 		pi.result = result;
279 		pi.err = 0;
280 		print_result(&pi);
281 	}
282 	ub_resolve_free(result);
283 }
284 
285 /** extended thread worker */
286 static void*
287 ext_thread(void* arg)
288 {
289 	struct ext_thr_info* inf = (struct ext_thr_info*)arg;
290 	int i, r;
291 	struct ub_result* result;
292 	struct track_id* async_ids = NULL;
293 	log_thread_set(&inf->thread_num);
294 	if(inf->thread_num > NUMTHR*2/3) {
295 		async_ids = (struct track_id*)calloc((size_t)inf->numq, sizeof(struct track_id));
296 		if(!async_ids) {
297 			printf("out of memory\n");
298 			exit(1);
299 		}
300 		for(i=0; i<inf->numq; i++) {
301 			lock_basic_init(&async_ids[i].lock);
302 		}
303 	}
304 	for(i=0; i<inf->numq; i++) {
305 		if(async_ids) {
306 			r = ub_resolve_async(inf->ctx,
307 				inf->argv[i%inf->argc], LDNS_RR_TYPE_A,
308 				LDNS_RR_CLASS_IN, &async_ids[i], ext_callback,
309 				&async_ids[i].id);
310 			checkerr("ub_resolve_async", r);
311 			if(i > 100) {
312 				lock_basic_lock(&async_ids[i-100].lock);
313 				r = ub_cancel(inf->ctx, async_ids[i-100].id);
314 				if(r != UB_NOID)
315 					async_ids[i-100].cancel=1;
316 				lock_basic_unlock(&async_ids[i-100].lock);
317 				if(r != UB_NOID)
318 					checkerr("ub_cancel", r);
319 			}
320 		} else if(inf->thread_num > NUMTHR/2) {
321 			/* async */
322 			r = ub_resolve_async(inf->ctx,
323 				inf->argv[i%inf->argc], LDNS_RR_TYPE_A,
324 				LDNS_RR_CLASS_IN, NULL, ext_callback, NULL);
325 			checkerr("ub_resolve_async", r);
326 		} else  {
327 			/* blocking */
328 			r = ub_resolve(inf->ctx, inf->argv[i%inf->argc],
329 				LDNS_RR_TYPE_A, LDNS_RR_CLASS_IN, &result);
330 			ext_check_result("ub_resolve", r, result);
331 			ub_resolve_free(result);
332 		}
333 	}
334 	if(inf->thread_num > NUMTHR/2) {
335 		r = ub_wait(inf->ctx);
336 		checkerr("ub_ctx_wait", r);
337 	}
338 	/* if these locks are destroyed, or if the async_ids is freed, then
339 	   a use-after-free happens in another thread.
340 	   The allocation is only part of this test, though. */
341 	/*
342 	if(async_ids) {
343 		for(i=0; i<inf->numq; i++) {
344 			lock_basic_destroy(&async_ids[i].lock);
345 		}
346 	}
347 	free(async_ids);
348 	*/
349 
350 	return NULL;
351 }
352 
353 /** perform extended threaded test */
354 static int
355 ext_test(struct ub_ctx* ctx, int argc, char** argv)
356 {
357 	struct ext_thr_info inf[NUMTHR];
358 	int i;
359 	if(argc == 1 && strcmp(argv[0], "localhost") == 0)
360 		q_is_localhost = 1;
361 	printf("extended test start (%d threads)\n", NUMTHR);
362 	for(i=0; i<NUMTHR; i++) {
363 		/* 0 = this, 1 = library bg worker */
364 		inf[i].thread_num = i+2;
365 		inf[i].ctx = ctx;
366 		inf[i].argc = argc;
367 		inf[i].argv = argv;
368 		inf[i].numq = 100;
369 		ub_thread_create(&inf[i].tid, ext_thread, &inf[i]);
370 	}
371 	/* the work happens here */
372 	for(i=0; i<NUMTHR; i++) {
373 		ub_thread_join(inf[i].tid);
374 	}
375 	printf("extended test end\n");
376 	ub_ctx_delete(ctx);
377 	checklock_stop();
378 	return 0;
379 }
380 
381 /** getopt global, in case header files fail to declare it. */
382 extern int optind;
383 /** getopt global, in case header files fail to declare it. */
384 extern char* optarg;
385 
386 /** main program for asynclook */
387 int main(int argc, char** argv)
388 {
389 	int c;
390 	struct ub_ctx* ctx;
391 	struct lookinfo* lookups;
392 	int i, r, cancel=0, blocking=0, ext=0;
393 
394 	/* init log now because solaris thr_key_create() is not threadsafe */
395 	log_init(0,0,0);
396 	/* lock debug start (if any) */
397 	checklock_start();
398 
399 	/* create context */
400 	ctx = ub_ctx_create();
401 	if(!ctx) {
402 		printf("could not create context, %s\n", strerror(errno));
403 		return 1;
404 	}
405 
406 	/* command line options */
407 	if(argc == 1) {
408 		usage(argv);
409 	}
410 	while( (c=getopt(argc, argv, "bcdf:hH:r:tx")) != -1) {
411 		switch(c) {
412 			case 'd':
413 				r = ub_ctx_debuglevel(ctx, 3);
414 				checkerr("ub_ctx_debuglevel", r);
415 				break;
416 			case 't':
417 				r = ub_ctx_async(ctx, 1);
418 				checkerr("ub_ctx_async", r);
419 				break;
420 			case 'c':
421 				cancel = 1;
422 				break;
423 			case 'b':
424 				blocking = 1;
425 				break;
426 			case 'r':
427 				r = ub_ctx_resolvconf(ctx, optarg);
428 				if(r != 0) {
429 					printf("ub_ctx_resolvconf "
430 						"error: %s : %s\n",
431 						ub_strerror(r),
432 						strerror(errno));
433 					return 1;
434 				}
435 				break;
436 			case 'H':
437 				r = ub_ctx_hosts(ctx, optarg);
438 				if(r != 0) {
439 					printf("ub_ctx_hosts "
440 						"error: %s : %s\n",
441 						ub_strerror(r),
442 						strerror(errno));
443 					return 1;
444 				}
445 				break;
446 			case 'f':
447 				r = ub_ctx_set_fwd(ctx, optarg);
448 				checkerr("ub_ctx_set_fwd", r);
449 				break;
450 			case 'x':
451 				ext = 1;
452 				break;
453 			case 'h':
454 			case '?':
455 			default:
456 				usage(argv);
457 		}
458 	}
459 	argc -= optind;
460 	argv += optind;
461 
462 	if(ext)
463 		return ext_test(ctx, argc, argv);
464 
465 	/* allocate array for results. */
466 	lookups = (struct lookinfo*)calloc((size_t)argc,
467 		sizeof(struct lookinfo));
468 	if(!lookups) {
469 		printf("out of memory\n");
470 		return 1;
471 	}
472 
473 	/* perform asynchronous calls */
474 	num_wait = argc;
475 	for(i=0; i<argc; i++) {
476 		lookups[i].name = argv[i];
477 		if(blocking) {
478 			fprintf(stderr, "lookup %s\n", argv[i]);
479 			r = ub_resolve(ctx, argv[i], LDNS_RR_TYPE_A,
480 				LDNS_RR_CLASS_IN, &lookups[i].result);
481 			checkerr("ub_resolve", r);
482 		} else {
483 			fprintf(stderr, "start async lookup %s\n", argv[i]);
484 			r = ub_resolve_async(ctx, argv[i], LDNS_RR_TYPE_A,
485 				LDNS_RR_CLASS_IN, &lookups[i], &lookup_is_done,
486 				&lookups[i].async_id);
487 			checkerr("ub_resolve_async", r);
488 		}
489 	}
490 	if(blocking)
491 		num_wait = 0;
492 	else if(cancel) {
493 		for(i=0; i<argc; i++) {
494 			fprintf(stderr, "cancel %s\n", argv[i]);
495 			r = ub_cancel(ctx, lookups[i].async_id);
496 			if(r != UB_NOID)
497 				checkerr("ub_cancel", r);
498 		}
499 		num_wait = 0;
500 	}
501 
502 	/* wait while the hostnames are looked up. Do something useful here */
503 	if(num_wait > 0)
504 	    for(i=0; i<1000; i++) {
505 		usleep(100000);
506 		fprintf(stderr, "%g seconds passed\n", 0.1*(double)i);
507 		r = ub_process(ctx);
508 		checkerr("ub_process", r);
509 		if(num_wait == 0)
510 			break;
511 	}
512 	if(i>=999) {
513 		printf("timed out\n");
514 		return 0;
515 	}
516 	printf("lookup complete\n");
517 
518 	/* print lookup results */
519 	for(i=0; i<argc; i++) {
520 		print_result(&lookups[i]);
521 		ub_resolve_free(lookups[i].result);
522 	}
523 
524 	ub_ctx_delete(ctx);
525 	free(lookups);
526 	checklock_stop();
527 	return 0;
528 }
529