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