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