1 /* $NetBSD: ns.c,v 1.3 2025/01/26 16:25:51 christos Exp $ */ 2 3 /* 4 * Copyright (C) Internet Systems Consortium, Inc. ("ISC") 5 * 6 * SPDX-License-Identifier: MPL-2.0 7 * 8 * This Source Code Form is subject to the terms of the Mozilla Public 9 * License, v. 2.0. If a copy of the MPL was not distributed with this 10 * file, you can obtain one at https://mozilla.org/MPL/2.0/. 11 * 12 * See the COPYRIGHT file distributed with this work for additional 13 * information regarding copyright ownership. 14 */ 15 16 /*! \file */ 17 18 #include <inttypes.h> 19 #include <stdbool.h> 20 #include <stdlib.h> 21 #include <time.h> 22 #include <unistd.h> 23 24 #include <isc/buffer.h> 25 #include <isc/file.h> 26 #include <isc/hash.h> 27 #include <isc/job.h> 28 #include <isc/loop.h> 29 #include <isc/managers.h> 30 #include <isc/mem.h> 31 #include <isc/netmgr.h> 32 #include <isc/os.h> 33 #include <isc/random.h> 34 #include <isc/result.h> 35 #include <isc/stdio.h> 36 #include <isc/string.h> 37 #include <isc/timer.h> 38 #include <isc/util.h> 39 40 #include <dns/cache.h> 41 #include <dns/db.h> 42 #include <dns/dispatch.h> 43 #include <dns/fixedname.h> 44 #include <dns/log.h> 45 #include <dns/name.h> 46 #include <dns/view.h> 47 #include <dns/zone.h> 48 49 #include <ns/client.h> 50 #include <ns/hooks.h> 51 #include <ns/interfacemgr.h> 52 #include <ns/server.h> 53 54 #include <tests/ns.h> 55 56 dns_dispatchmgr_t *dispatchmgr = NULL; 57 ns_interfacemgr_t *interfacemgr = NULL; 58 ns_server_t *sctx = NULL; 59 60 static isc_result_t 61 matchview(isc_netaddr_t *srcaddr, isc_netaddr_t *destaddr, 62 dns_message_t *message, dns_aclenv_t *env, ns_server_t *lsctx, 63 isc_loop_t *loop, isc_job_cb cb, void *cbarg, 64 isc_result_t *sigresultp, isc_result_t *viewmatchresultp, 65 dns_view_t **viewp) { 66 UNUSED(srcaddr); 67 UNUSED(destaddr); 68 UNUSED(message); 69 UNUSED(env); 70 UNUSED(lsctx); 71 UNUSED(loop); 72 UNUSED(cb); 73 UNUSED(cbarg); 74 UNUSED(sigresultp); 75 UNUSED(viewp); 76 77 *viewmatchresultp = ISC_R_NOTIMPLEMENTED; 78 return ISC_R_NOTIMPLEMENTED; 79 } 80 81 static void 82 scan_interfaces(void *arg) { 83 UNUSED(arg); 84 ns_interfacemgr_scan(interfacemgr, true, false); 85 } 86 87 int 88 setup_server(void **state) { 89 isc_result_t result; 90 ns_listenlist_t *listenon = NULL; 91 in_port_t port = 5300 + isc_random8(); 92 93 setup_managers(state); 94 95 ns_server_create(mctx, matchview, &sctx); 96 97 result = dns_dispatchmgr_create(mctx, loopmgr, netmgr, &dispatchmgr); 98 if (result != ISC_R_SUCCESS) { 99 goto cleanup; 100 } 101 102 result = ns_interfacemgr_create(mctx, sctx, loopmgr, netmgr, 103 dispatchmgr, NULL, &interfacemgr); 104 if (result != ISC_R_SUCCESS) { 105 goto cleanup; 106 } 107 108 result = ns_listenlist_default(mctx, port, true, AF_INET, &listenon); 109 if (result != ISC_R_SUCCESS) { 110 goto cleanup; 111 } 112 113 ns_interfacemgr_setlistenon4(interfacemgr, listenon); 114 ns_listenlist_detach(&listenon); 115 116 isc_loop_setup(mainloop, scan_interfaces, NULL); 117 118 return 0; 119 120 cleanup: 121 teardown_server(state); 122 return -1; 123 } 124 125 void 126 shutdown_interfacemgr(void *arg ISC_ATTR_UNUSED) { 127 if (interfacemgr != NULL) { 128 ns_interfacemgr_shutdown(interfacemgr); 129 ns_interfacemgr_detach(&interfacemgr); 130 } 131 } 132 133 int 134 teardown_server(void **state) { 135 shutdown_interfacemgr(NULL); 136 137 if (dispatchmgr != NULL) { 138 dns_dispatchmgr_detach(&dispatchmgr); 139 } 140 141 if (sctx != NULL) { 142 ns_server_detach(&sctx); 143 } 144 145 teardown_managers(state); 146 return 0; 147 } 148 149 static dns_zone_t *served_zone = NULL; 150 151 isc_result_t 152 ns_test_serve_zone(const char *zonename, const char *filename, 153 dns_view_t *view) { 154 isc_result_t result; 155 dns_db_t *db = NULL; 156 157 /* 158 * Prepare zone structure for further processing. 159 */ 160 result = dns_test_makezone(zonename, &served_zone, view, false); 161 if (result != ISC_R_SUCCESS) { 162 return result; 163 } 164 165 /* 166 * Start zone manager. 167 */ 168 dns_test_setupzonemgr(); 169 170 /* 171 * Add the zone to the zone manager. 172 */ 173 result = dns_test_managezone(served_zone); 174 if (result != ISC_R_SUCCESS) { 175 goto close_zonemgr; 176 } 177 178 view->nocookieudp = 512; 179 180 /* 181 * Set path to the master file for the zone and then load it. 182 */ 183 dns_zone_setfile(served_zone, filename, dns_masterformat_text, 184 &dns_master_style_default); 185 result = dns_zone_load(served_zone, false); 186 if (result != ISC_R_SUCCESS) { 187 goto release_zone; 188 } 189 190 /* 191 * The zone should now be loaded; test it. 192 */ 193 result = dns_zone_getdb(served_zone, &db); 194 if (result != ISC_R_SUCCESS) { 195 goto release_zone; 196 } 197 if (db != NULL) { 198 dns_db_detach(&db); 199 } 200 201 return ISC_R_SUCCESS; 202 203 release_zone: 204 dns_test_releasezone(served_zone); 205 close_zonemgr: 206 dns_test_closezonemgr(); 207 dns_zone_detach(&served_zone); 208 209 return result; 210 } 211 212 void 213 ns_test_cleanup_zone(void) { 214 dns_test_releasezone(served_zone); 215 dns_test_closezonemgr(); 216 217 dns_zone_detach(&served_zone); 218 } 219 220 void 221 ns_test_getclient(ns_interface_t *ifp0, bool tcp, ns_client_t **clientp) { 222 ns_client_t *client; 223 ns_clientmgr_t *clientmgr; 224 int i; 225 226 UNUSED(ifp0); 227 UNUSED(tcp); 228 229 clientmgr = ns_interfacemgr_getclientmgr(interfacemgr); 230 231 client = isc_mem_get(clientmgr->mctx, sizeof(*client)); 232 ns__client_setup(client, clientmgr, true); 233 234 for (i = 0; i < 32; i++) { 235 if (atomic_load(&client_addrs[i]) == (uintptr_t)NULL || 236 atomic_load(&client_addrs[i]) == (uintptr_t)client) 237 { 238 break; 239 } 240 } 241 REQUIRE(i < 32); 242 243 atomic_store(&client_refs[i], 2); 244 atomic_store(&client_addrs[i], (uintptr_t)client); 245 client->handle = (isc_nmhandle_t *)client; /* Hack */ 246 *clientp = client; 247 } 248 249 /*% 250 * Synthesize a DNS message based on supplied QNAME, QTYPE and flags, then 251 * parse it and store the results in client->message. 252 */ 253 static isc_result_t 254 attach_query_msg_to_client(ns_client_t *client, const char *qnamestr, 255 dns_rdatatype_t qtype, unsigned int qflags) { 256 dns_rdataset_t *qrdataset = NULL; 257 dns_message_t *message = NULL; 258 unsigned char query[65535]; 259 dns_name_t *qname = NULL; 260 isc_buffer_t querybuf; 261 dns_compress_t cctx; 262 isc_result_t result; 263 264 REQUIRE(client != NULL); 265 REQUIRE(qnamestr != NULL); 266 267 /* 268 * Create a new DNS message holding a query. 269 */ 270 dns_message_create(mctx, NULL, NULL, DNS_MESSAGE_INTENTRENDER, 271 &message); 272 273 /* 274 * Set query ID to a random value. 275 */ 276 message->id = isc_random16(); 277 278 /* 279 * Set query flags as requested by the caller. 280 */ 281 message->flags = qflags; 282 283 /* 284 * Allocate structures required to construct the query. 285 */ 286 dns_message_gettemprdataset(message, &qrdataset); 287 dns_message_gettempname(message, &qname); 288 289 /* 290 * Convert "qnamestr" to a DNS name, create a question rdataset of 291 * class IN and type "qtype", link the two and add the result to the 292 * QUESTION section of the query. 293 */ 294 result = dns_name_fromstring(qname, qnamestr, dns_rootname, 0, mctx); 295 if (result != ISC_R_SUCCESS) { 296 goto put_name; 297 } 298 dns_rdataset_makequestion(qrdataset, dns_rdataclass_in, qtype); 299 ISC_LIST_APPEND(qname->list, qrdataset, link); 300 dns_message_addname(message, qname, DNS_SECTION_QUESTION); 301 302 /* 303 * Render the query. 304 */ 305 dns_compress_init(&cctx, mctx, 0); 306 isc_buffer_init(&querybuf, query, sizeof(query)); 307 result = dns_message_renderbegin(message, &cctx, &querybuf); 308 if (result != ISC_R_SUCCESS) { 309 goto destroy_message; 310 } 311 result = dns_message_rendersection(message, DNS_SECTION_QUESTION, 0); 312 if (result != ISC_R_SUCCESS) { 313 goto destroy_message; 314 } 315 result = dns_message_renderend(message); 316 if (result != ISC_R_SUCCESS) { 317 goto destroy_message; 318 } 319 dns_compress_invalidate(&cctx); 320 321 /* 322 * Destroy the created message as it was rendered into "querybuf" and 323 * the latter is all we are going to need from now on. 324 */ 325 dns_message_detach(&message); 326 327 /* 328 * Parse the rendered query, storing results in client->message. 329 */ 330 isc_buffer_first(&querybuf); 331 return dns_message_parse(client->message, &querybuf, 0); 332 333 put_name: 334 dns_message_puttempname(message, &qname); 335 dns_message_puttemprdataset(message, &qrdataset); 336 destroy_message: 337 dns_message_detach(&message); 338 339 return result; 340 } 341 342 /*% 343 * A hook action which stores the query context pointed to by "arg" at 344 * "data". Causes execution to be interrupted at hook insertion 345 * point. 346 */ 347 static ns_hookresult_t 348 extract_qctx(void *arg, void *data, isc_result_t *resultp) { 349 query_ctx_t **qctxp; 350 query_ctx_t *qctx; 351 352 REQUIRE(arg != NULL); 353 REQUIRE(data != NULL); 354 REQUIRE(resultp != NULL); 355 356 /* 357 * qctx is a stack variable in lib/ns/query.c. Its contents need to be 358 * duplicated or otherwise they will become invalidated once the stack 359 * gets unwound. 360 */ 361 qctx = isc_mem_get(mctx, sizeof(*qctx)); 362 if (qctx != NULL) { 363 memmove(qctx, (query_ctx_t *)arg, sizeof(*qctx)); 364 } 365 366 qctxp = (query_ctx_t **)data; 367 /* 368 * If memory allocation failed, the supplied pointer will simply be set 369 * to NULL. We rely on the user of this hook to react properly. 370 */ 371 *qctxp = qctx; 372 *resultp = ISC_R_UNSET; 373 374 return NS_HOOK_RETURN; 375 } 376 377 /*% 378 * Initialize a query context for "client" and store it in "qctxp". 379 * 380 * Requires: 381 * 382 * \li "client->message" to hold a parsed DNS query. 383 */ 384 static isc_result_t 385 create_qctx_for_client(ns_client_t *client, query_ctx_t **qctxp) { 386 ns_hooktable_t *saved_hook_table = NULL, *query_hooks = NULL; 387 const ns_hook_t hook = { 388 .action = extract_qctx, 389 .action_data = qctxp, 390 }; 391 392 REQUIRE(client != NULL); 393 REQUIRE(qctxp != NULL); 394 REQUIRE(*qctxp == NULL); 395 396 /* 397 * Call ns_query_start() to initialize a query context for given 398 * client, but first hook into query_setup() so that we can just 399 * extract an initialized query context, without kicking off any 400 * further processing. Make sure we do not overwrite any previously 401 * set hooks. 402 */ 403 404 ns_hooktable_create(mctx, &query_hooks); 405 ns_hook_add(query_hooks, mctx, NS_QUERY_SETUP, &hook); 406 407 saved_hook_table = ns__hook_table; 408 ns__hook_table = query_hooks; 409 410 ns_query_start(client, client->handle); 411 412 ns__hook_table = saved_hook_table; 413 ns_hooktable_free(mctx, (void **)&query_hooks); 414 415 isc_nmhandle_detach(&client->reqhandle); 416 417 if (*qctxp == NULL) { 418 return ISC_R_NOMEMORY; 419 } else { 420 return ISC_R_SUCCESS; 421 } 422 } 423 424 isc_result_t 425 ns_test_qctx_create(const ns_test_qctx_create_params_t *params, 426 query_ctx_t **qctxp) { 427 ns_client_t *client = NULL; 428 isc_result_t result; 429 isc_nmhandle_t *handle = NULL; 430 431 REQUIRE(params != NULL); 432 REQUIRE(params->qname != NULL); 433 REQUIRE(qctxp != NULL); 434 REQUIRE(*qctxp == NULL); 435 436 /* 437 * Allocate and initialize a client structure. 438 */ 439 ns_test_getclient(NULL, false, &client); 440 client->tnow = isc_time_now(); 441 442 /* 443 * Every client needs to belong to a view. 444 */ 445 result = dns_test_makeview("view", false, params->with_cache, 446 &client->view); 447 if (result != ISC_R_SUCCESS) { 448 goto detach_client; 449 } 450 451 /* 452 * Synthesize a DNS query using given QNAME, QTYPE and flags, storing 453 * it in client->message. 454 */ 455 result = attach_query_msg_to_client(client, params->qname, 456 params->qtype, params->qflags); 457 if (result != ISC_R_SUCCESS) { 458 goto detach_view; 459 } 460 461 /* 462 * Allow recursion for the client. As NS_CLIENTATTR_RA normally gets 463 * set in ns_client_request(), i.e. earlier than the unit tests hook 464 * into the call chain, just set it manually. 465 */ 466 client->attributes |= NS_CLIENTATTR_RA; 467 468 /* 469 * Create a query context for a client sending the previously 470 * synthesized query. 471 */ 472 result = create_qctx_for_client(client, qctxp); 473 if (result != ISC_R_SUCCESS) { 474 goto detach_query; 475 } 476 477 /* 478 * The reference count for "client" is now at 2, so we need to 479 * decrement it in order for it to drop to zero when "qctx" gets 480 * destroyed. 481 */ 482 handle = client->handle; 483 isc_nmhandle_detach(&handle); 484 485 return ISC_R_SUCCESS; 486 487 detach_query: 488 dns_message_detach(&client->message); 489 detach_view: 490 dns_view_detach(&client->view); 491 detach_client: 492 isc_nmhandle_detach(&client->handle); 493 494 return result; 495 } 496 497 void 498 ns_test_qctx_destroy(query_ctx_t **qctxp) { 499 query_ctx_t *qctx; 500 501 REQUIRE(qctxp != NULL); 502 REQUIRE(*qctxp != NULL); 503 504 qctx = *qctxp; 505 *qctxp = NULL; 506 507 if (qctx->zone != NULL) { 508 dns_zone_detach(&qctx->zone); 509 } 510 if (qctx->db != NULL) { 511 dns_db_detach(&qctx->db); 512 } 513 if (qctx->client != NULL) { 514 isc_nmhandle_detach(&qctx->client->handle); 515 } 516 517 isc_mem_put(mctx, qctx, sizeof(*qctx)); 518 } 519 520 ns_hookresult_t 521 ns_test_hook_catch_call(void *arg, void *data, isc_result_t *resultp) { 522 UNUSED(arg); 523 UNUSED(data); 524 525 *resultp = ISC_R_UNSET; 526 527 return NS_HOOK_RETURN; 528 } 529 530 isc_result_t 531 ns_test_loaddb(dns_db_t **db, dns_dbtype_t dbtype, const char *origin, 532 const char *testfile) { 533 isc_result_t result; 534 dns_fixedname_t fixed; 535 dns_name_t *name = NULL; 536 const char *dbimp = (dbtype == dns_dbtype_zone) ? ZONEDB_DEFAULT 537 : CACHEDB_DEFAULT; 538 539 name = dns_fixedname_initname(&fixed); 540 541 result = dns_name_fromstring(name, origin, dns_rootname, 0, NULL); 542 if (result != ISC_R_SUCCESS) { 543 return result; 544 } 545 546 result = dns_db_create(mctx, dbimp, name, dbtype, dns_rdataclass_in, 0, 547 NULL, db); 548 if (result != ISC_R_SUCCESS) { 549 return result; 550 } 551 552 result = dns_db_load(*db, testfile, dns_masterformat_text, 0); 553 return result; 554 } 555 556 static int 557 fromhex(char c) { 558 if (c >= '0' && c <= '9') { 559 return c - '0'; 560 } else if (c >= 'a' && c <= 'f') { 561 return c - 'a' + 10; 562 } else if (c >= 'A' && c <= 'F') { 563 return c - 'A' + 10; 564 } 565 566 printf("bad input format: %02x\n", c); 567 exit(3); 568 } 569 570 isc_result_t 571 ns_test_getdata(const char *file, unsigned char *buf, size_t bufsiz, 572 size_t *sizep) { 573 isc_result_t result; 574 unsigned char *bp; 575 char *rp, *wp; 576 char s[BUFSIZ]; 577 size_t len, i; 578 FILE *f = NULL; 579 int n; 580 581 result = isc_stdio_open(file, "r", &f); 582 if (result != ISC_R_SUCCESS) { 583 return result; 584 } 585 586 bp = buf; 587 while (fgets(s, sizeof(s), f) != NULL) { 588 rp = s; 589 wp = s; 590 len = 0; 591 while (*rp != '\0') { 592 if (*rp == '#') { 593 break; 594 } 595 if (*rp != ' ' && *rp != '\t' && *rp != '\r' && 596 *rp != '\n') 597 { 598 *wp++ = *rp; 599 len++; 600 } 601 rp++; 602 } 603 if (len == 0U) { 604 continue; 605 } 606 if (len % 2 != 0U) { 607 result = ISC_R_UNEXPECTEDEND; 608 goto cleanup; 609 } 610 if (len > bufsiz * 2) { 611 result = ISC_R_NOSPACE; 612 goto cleanup; 613 } 614 rp = s; 615 for (i = 0; i < len; i += 2) { 616 n = fromhex(*rp++); 617 n *= 16; 618 n += fromhex(*rp++); 619 *bp++ = n; 620 } 621 } 622 623 *sizep = bp - buf; 624 625 result = ISC_R_SUCCESS; 626 627 cleanup: 628 isc_stdio_close(f); 629 return result; 630 } 631