1 1.1 christos /* $NetBSD: lookup.c,v 1.1 2024/02/18 20:57:32 christos Exp $ */ 2 1.1 christos 3 1.1 christos /* 4 1.1 christos * Copyright (C) Internet Systems Consortium, Inc. ("ISC") 5 1.1 christos * 6 1.1 christos * SPDX-License-Identifier: MPL-2.0 7 1.1 christos * 8 1.1 christos * This Source Code Form is subject to the terms of the Mozilla Public 9 1.1 christos * License, v. 2.0. If a copy of the MPL was not distributed with this 10 1.1 christos * file, you can obtain one at https://mozilla.org/MPL/2.0/. 11 1.1 christos * 12 1.1 christos * See the COPYRIGHT file distributed with this work for additional 13 1.1 christos * information regarding copyright ownership. 14 1.1 christos */ 15 1.1 christos 16 1.1 christos /*! \file */ 17 1.1 christos 18 1.1 christos #include <stdbool.h> 19 1.1 christos 20 1.1 christos #include <isc/mem.h> 21 1.1 christos #include <isc/netaddr.h> 22 1.1 christos #include <isc/string.h> /* Required for HP/UX (and others?) */ 23 1.1 christos #include <isc/task.h> 24 1.1 christos #include <isc/util.h> 25 1.1 christos 26 1.1 christos #include <dns/db.h> 27 1.1 christos #include <dns/events.h> 28 1.1 christos #include <dns/lookup.h> 29 1.1 christos #include <dns/rdata.h> 30 1.1 christos #include <dns/rdataset.h> 31 1.1 christos #include <dns/rdatastruct.h> 32 1.1 christos #include <dns/resolver.h> 33 1.1 christos #include <dns/result.h> 34 1.1 christos #include <dns/view.h> 35 1.1 christos 36 1.1 christos struct dns_lookup { 37 1.1 christos /* Unlocked. */ 38 1.1 christos unsigned int magic; 39 1.1 christos isc_mem_t *mctx; 40 1.1 christos isc_mutex_t lock; 41 1.1 christos dns_rdatatype_t type; 42 1.1 christos dns_fixedname_t name; 43 1.1 christos /* Locked by lock. */ 44 1.1 christos unsigned int options; 45 1.1 christos isc_task_t *task; 46 1.1 christos dns_view_t *view; 47 1.1 christos dns_lookupevent_t *event; 48 1.1 christos dns_fetch_t *fetch; 49 1.1 christos unsigned int restarts; 50 1.1 christos bool canceled; 51 1.1 christos dns_rdataset_t rdataset; 52 1.1 christos dns_rdataset_t sigrdataset; 53 1.1 christos }; 54 1.1 christos 55 1.1 christos #define LOOKUP_MAGIC ISC_MAGIC('l', 'o', 'o', 'k') 56 1.1 christos #define VALID_LOOKUP(l) ISC_MAGIC_VALID((l), LOOKUP_MAGIC) 57 1.1 christos 58 1.1 christos #define MAX_RESTARTS 16 59 1.1 christos 60 1.1 christos static void 61 1.1 christos lookup_find(dns_lookup_t *lookup, dns_fetchevent_t *event); 62 1.1 christos 63 1.1 christos static void 64 1.1 christos fetch_done(isc_task_t *task, isc_event_t *event) { 65 1.1 christos dns_lookup_t *lookup = event->ev_arg; 66 1.1 christos dns_fetchevent_t *fevent; 67 1.1 christos 68 1.1 christos UNUSED(task); 69 1.1 christos REQUIRE(event->ev_type == DNS_EVENT_FETCHDONE); 70 1.1 christos REQUIRE(VALID_LOOKUP(lookup)); 71 1.1 christos REQUIRE(lookup->task == task); 72 1.1 christos fevent = (dns_fetchevent_t *)event; 73 1.1 christos REQUIRE(fevent->fetch == lookup->fetch); 74 1.1 christos 75 1.1 christos lookup_find(lookup, fevent); 76 1.1 christos } 77 1.1 christos 78 1.1 christos static isc_result_t 79 1.1 christos start_fetch(dns_lookup_t *lookup) { 80 1.1 christos isc_result_t result; 81 1.1 christos 82 1.1 christos /* 83 1.1 christos * The caller must be holding the lookup's lock. 84 1.1 christos */ 85 1.1 christos 86 1.1 christos REQUIRE(lookup->fetch == NULL); 87 1.1 christos 88 1.1 christos result = dns_resolver_createfetch( 89 1.1 christos lookup->view->resolver, dns_fixedname_name(&lookup->name), 90 1.1 christos lookup->type, NULL, NULL, NULL, NULL, 0, 0, 0, NULL, 91 1.1 christos lookup->task, fetch_done, lookup, &lookup->rdataset, 92 1.1 christos &lookup->sigrdataset, &lookup->fetch); 93 1.1 christos 94 1.1 christos return (result); 95 1.1 christos } 96 1.1 christos 97 1.1 christos static isc_result_t 98 1.1 christos build_event(dns_lookup_t *lookup) { 99 1.1 christos dns_name_t *name = NULL; 100 1.1 christos dns_rdataset_t *rdataset = NULL; 101 1.1 christos dns_rdataset_t *sigrdataset = NULL; 102 1.1 christos 103 1.1 christos name = isc_mem_get(lookup->mctx, sizeof(dns_name_t)); 104 1.1 christos dns_name_init(name, NULL); 105 1.1 christos dns_name_dup(dns_fixedname_name(&lookup->name), lookup->mctx, name); 106 1.1 christos 107 1.1 christos if (dns_rdataset_isassociated(&lookup->rdataset)) { 108 1.1 christos rdataset = isc_mem_get(lookup->mctx, sizeof(dns_rdataset_t)); 109 1.1 christos dns_rdataset_init(rdataset); 110 1.1 christos dns_rdataset_clone(&lookup->rdataset, rdataset); 111 1.1 christos } 112 1.1 christos 113 1.1 christos if (dns_rdataset_isassociated(&lookup->sigrdataset)) { 114 1.1 christos sigrdataset = isc_mem_get(lookup->mctx, sizeof(dns_rdataset_t)); 115 1.1 christos dns_rdataset_init(sigrdataset); 116 1.1 christos dns_rdataset_clone(&lookup->sigrdataset, sigrdataset); 117 1.1 christos } 118 1.1 christos 119 1.1 christos lookup->event->name = name; 120 1.1 christos lookup->event->rdataset = rdataset; 121 1.1 christos lookup->event->sigrdataset = sigrdataset; 122 1.1 christos 123 1.1 christos return (ISC_R_SUCCESS); 124 1.1 christos } 125 1.1 christos 126 1.1 christos static isc_result_t 127 1.1 christos view_find(dns_lookup_t *lookup, dns_name_t *foundname) { 128 1.1 christos isc_result_t result; 129 1.1 christos dns_name_t *name = dns_fixedname_name(&lookup->name); 130 1.1 christos dns_rdatatype_t type; 131 1.1 christos 132 1.1 christos if (lookup->type == dns_rdatatype_rrsig) { 133 1.1 christos type = dns_rdatatype_any; 134 1.1 christos } else { 135 1.1 christos type = lookup->type; 136 1.1 christos } 137 1.1 christos 138 1.1 christos result = dns_view_find(lookup->view, name, type, 0, 0, false, false, 139 1.1 christos &lookup->event->db, &lookup->event->node, 140 1.1 christos foundname, &lookup->rdataset, 141 1.1 christos &lookup->sigrdataset); 142 1.1 christos return (result); 143 1.1 christos } 144 1.1 christos 145 1.1 christos static void 146 1.1 christos lookup_find(dns_lookup_t *lookup, dns_fetchevent_t *event) { 147 1.1 christos isc_result_t result; 148 1.1 christos bool want_restart; 149 1.1 christos bool send_event; 150 1.1 christos dns_name_t *name, *fname, *prefix; 151 1.1 christos dns_fixedname_t foundname, fixed; 152 1.1 christos dns_rdata_t rdata = DNS_RDATA_INIT; 153 1.1 christos unsigned int nlabels; 154 1.1 christos int order; 155 1.1 christos dns_namereln_t namereln; 156 1.1 christos dns_rdata_cname_t cname; 157 1.1 christos dns_rdata_dname_t dname; 158 1.1 christos 159 1.1 christos REQUIRE(VALID_LOOKUP(lookup)); 160 1.1 christos 161 1.1 christos LOCK(&lookup->lock); 162 1.1 christos 163 1.1 christos result = ISC_R_SUCCESS; 164 1.1 christos name = dns_fixedname_name(&lookup->name); 165 1.1 christos 166 1.1 christos do { 167 1.1 christos lookup->restarts++; 168 1.1 christos want_restart = false; 169 1.1 christos send_event = true; 170 1.1 christos 171 1.1 christos if (event == NULL && !lookup->canceled) { 172 1.1 christos fname = dns_fixedname_initname(&foundname); 173 1.1 christos INSIST(!dns_rdataset_isassociated(&lookup->rdataset)); 174 1.1 christos INSIST(!dns_rdataset_isassociated( 175 1.1 christos &lookup->sigrdataset)); 176 1.1 christos /* 177 1.1 christos * If we have restarted then clear the old node. 178 1.1 christos */ 179 1.1 christos if (lookup->event->node != NULL) { 180 1.1 christos INSIST(lookup->event->db != NULL); 181 1.1 christos dns_db_detachnode(lookup->event->db, 182 1.1 christos &lookup->event->node); 183 1.1 christos } 184 1.1 christos if (lookup->event->db != NULL) { 185 1.1 christos dns_db_detach(&lookup->event->db); 186 1.1 christos } 187 1.1 christos result = view_find(lookup, fname); 188 1.1 christos if (result == ISC_R_NOTFOUND) { 189 1.1 christos /* 190 1.1 christos * We don't know anything about the name. 191 1.1 christos * Launch a fetch. 192 1.1 christos */ 193 1.1 christos if (lookup->event->node != NULL) { 194 1.1 christos INSIST(lookup->event->db != NULL); 195 1.1 christos dns_db_detachnode(lookup->event->db, 196 1.1 christos &lookup->event->node); 197 1.1 christos } 198 1.1 christos if (lookup->event->db != NULL) { 199 1.1 christos dns_db_detach(&lookup->event->db); 200 1.1 christos } 201 1.1 christos result = start_fetch(lookup); 202 1.1 christos if (result == ISC_R_SUCCESS) { 203 1.1 christos send_event = false; 204 1.1 christos } 205 1.1 christos goto done; 206 1.1 christos } 207 1.1 christos } else if (event != NULL) { 208 1.1 christos result = event->result; 209 1.1 christos fname = dns_fixedname_name(&event->foundname); 210 1.1 christos dns_resolver_destroyfetch(&lookup->fetch); 211 1.1 christos INSIST(event->rdataset == &lookup->rdataset); 212 1.1 christos INSIST(event->sigrdataset == &lookup->sigrdataset); 213 1.1 christos } else { 214 1.1 christos fname = NULL; /* Silence compiler warning. */ 215 1.1 christos } 216 1.1 christos 217 1.1 christos /* 218 1.1 christos * If we've been canceled, forget about the result. 219 1.1 christos */ 220 1.1 christos if (lookup->canceled) { 221 1.1 christos result = ISC_R_CANCELED; 222 1.1 christos } 223 1.1 christos 224 1.1 christos switch (result) { 225 1.1 christos case ISC_R_SUCCESS: 226 1.1 christos result = build_event(lookup); 227 1.1 christos if (event == NULL) { 228 1.1 christos break; 229 1.1 christos } 230 1.1 christos if (event->db != NULL) { 231 1.1 christos dns_db_attach(event->db, &lookup->event->db); 232 1.1 christos } 233 1.1 christos if (event->node != NULL) { 234 1.1 christos dns_db_attachnode(lookup->event->db, 235 1.1 christos event->node, 236 1.1 christos &lookup->event->node); 237 1.1 christos } 238 1.1 christos break; 239 1.1 christos case DNS_R_CNAME: 240 1.1 christos /* 241 1.1 christos * Copy the CNAME's target into the lookup's 242 1.1 christos * query name and start over. 243 1.1 christos */ 244 1.1 christos result = dns_rdataset_first(&lookup->rdataset); 245 1.1 christos if (result != ISC_R_SUCCESS) { 246 1.1 christos break; 247 1.1 christos } 248 1.1 christos dns_rdataset_current(&lookup->rdataset, &rdata); 249 1.1 christos result = dns_rdata_tostruct(&rdata, &cname, NULL); 250 1.1 christos dns_rdata_reset(&rdata); 251 1.1 christos if (result != ISC_R_SUCCESS) { 252 1.1 christos break; 253 1.1 christos } 254 1.1 christos dns_name_copynf(&cname.cname, name); 255 1.1 christos dns_rdata_freestruct(&cname); 256 1.1 christos want_restart = true; 257 1.1 christos send_event = false; 258 1.1 christos break; 259 1.1 christos case DNS_R_DNAME: 260 1.1 christos namereln = dns_name_fullcompare(name, fname, &order, 261 1.1 christos &nlabels); 262 1.1 christos INSIST(namereln == dns_namereln_subdomain); 263 1.1 christos /* 264 1.1 christos * Get the target name of the DNAME. 265 1.1 christos */ 266 1.1 christos result = dns_rdataset_first(&lookup->rdataset); 267 1.1 christos if (result != ISC_R_SUCCESS) { 268 1.1 christos break; 269 1.1 christos } 270 1.1 christos dns_rdataset_current(&lookup->rdataset, &rdata); 271 1.1 christos result = dns_rdata_tostruct(&rdata, &dname, NULL); 272 1.1 christos dns_rdata_reset(&rdata); 273 1.1 christos if (result != ISC_R_SUCCESS) { 274 1.1 christos break; 275 1.1 christos } 276 1.1 christos /* 277 1.1 christos * Construct the new query name and start over. 278 1.1 christos */ 279 1.1 christos prefix = dns_fixedname_initname(&fixed); 280 1.1 christos dns_name_split(name, nlabels, prefix, NULL); 281 1.1 christos result = dns_name_concatenate(prefix, &dname.dname, 282 1.1 christos name, NULL); 283 1.1 christos dns_rdata_freestruct(&dname); 284 1.1 christos if (result == ISC_R_SUCCESS) { 285 1.1 christos want_restart = true; 286 1.1 christos send_event = false; 287 1.1 christos } 288 1.1 christos break; 289 1.1 christos default: 290 1.1 christos send_event = true; 291 1.1 christos } 292 1.1 christos 293 1.1 christos if (dns_rdataset_isassociated(&lookup->rdataset)) { 294 1.1 christos dns_rdataset_disassociate(&lookup->rdataset); 295 1.1 christos } 296 1.1 christos if (dns_rdataset_isassociated(&lookup->sigrdataset)) { 297 1.1 christos dns_rdataset_disassociate(&lookup->sigrdataset); 298 1.1 christos } 299 1.1 christos 300 1.1 christos done: 301 1.1 christos if (event != NULL) { 302 1.1 christos if (event->node != NULL) { 303 1.1 christos dns_db_detachnode(event->db, &event->node); 304 1.1 christos } 305 1.1 christos if (event->db != NULL) { 306 1.1 christos dns_db_detach(&event->db); 307 1.1 christos } 308 1.1 christos isc_event_free(ISC_EVENT_PTR(&event)); 309 1.1 christos } 310 1.1 christos 311 1.1 christos /* 312 1.1 christos * Limit the number of restarts. 313 1.1 christos */ 314 1.1 christos if (want_restart && lookup->restarts == MAX_RESTARTS) { 315 1.1 christos want_restart = false; 316 1.1 christos result = ISC_R_QUOTA; 317 1.1 christos send_event = true; 318 1.1 christos } 319 1.1 christos } while (want_restart); 320 1.1 christos 321 1.1 christos if (send_event) { 322 1.1 christos lookup->event->result = result; 323 1.1 christos lookup->event->ev_sender = lookup; 324 1.1 christos isc_task_sendanddetach(&lookup->task, 325 1.1 christos (isc_event_t **)(void *)&lookup->event); 326 1.1 christos dns_view_detach(&lookup->view); 327 1.1 christos } 328 1.1 christos 329 1.1 christos UNLOCK(&lookup->lock); 330 1.1 christos } 331 1.1 christos 332 1.1 christos static void 333 1.1 christos levent_destroy(isc_event_t *event) { 334 1.1 christos dns_lookupevent_t *levent; 335 1.1 christos isc_mem_t *mctx; 336 1.1 christos 337 1.1 christos REQUIRE(event->ev_type == DNS_EVENT_LOOKUPDONE); 338 1.1 christos mctx = event->ev_destroy_arg; 339 1.1 christos levent = (dns_lookupevent_t *)event; 340 1.1 christos 341 1.1 christos if (levent->name != NULL) { 342 1.1 christos if (dns_name_dynamic(levent->name)) { 343 1.1 christos dns_name_free(levent->name, mctx); 344 1.1 christos } 345 1.1 christos isc_mem_put(mctx, levent->name, sizeof(dns_name_t)); 346 1.1 christos } 347 1.1 christos if (levent->rdataset != NULL) { 348 1.1 christos dns_rdataset_disassociate(levent->rdataset); 349 1.1 christos isc_mem_put(mctx, levent->rdataset, sizeof(dns_rdataset_t)); 350 1.1 christos } 351 1.1 christos if (levent->sigrdataset != NULL) { 352 1.1 christos dns_rdataset_disassociate(levent->sigrdataset); 353 1.1 christos isc_mem_put(mctx, levent->sigrdataset, sizeof(dns_rdataset_t)); 354 1.1 christos } 355 1.1 christos if (levent->node != NULL) { 356 1.1 christos dns_db_detachnode(levent->db, &levent->node); 357 1.1 christos } 358 1.1 christos if (levent->db != NULL) { 359 1.1 christos dns_db_detach(&levent->db); 360 1.1 christos } 361 1.1 christos isc_mem_put(mctx, event, event->ev_size); 362 1.1 christos } 363 1.1 christos 364 1.1 christos isc_result_t 365 1.1 christos dns_lookup_create(isc_mem_t *mctx, const dns_name_t *name, dns_rdatatype_t type, 366 1.1 christos dns_view_t *view, unsigned int options, isc_task_t *task, 367 1.1 christos isc_taskaction_t action, void *arg, dns_lookup_t **lookupp) { 368 1.1 christos dns_lookup_t *lookup; 369 1.1 christos isc_event_t *ievent; 370 1.1 christos 371 1.1 christos lookup = isc_mem_get(mctx, sizeof(*lookup)); 372 1.1 christos lookup->mctx = NULL; 373 1.1 christos isc_mem_attach(mctx, &lookup->mctx); 374 1.1 christos lookup->options = options; 375 1.1 christos 376 1.1 christos ievent = isc_event_allocate(mctx, lookup, DNS_EVENT_LOOKUPDONE, action, 377 1.1 christos arg, sizeof(*lookup->event)); 378 1.1 christos lookup->event = (dns_lookupevent_t *)ievent; 379 1.1 christos lookup->event->ev_destroy = levent_destroy; 380 1.1 christos lookup->event->ev_destroy_arg = mctx; 381 1.1 christos lookup->event->result = ISC_R_FAILURE; 382 1.1 christos lookup->event->name = NULL; 383 1.1 christos lookup->event->rdataset = NULL; 384 1.1 christos lookup->event->sigrdataset = NULL; 385 1.1 christos lookup->event->db = NULL; 386 1.1 christos lookup->event->node = NULL; 387 1.1 christos 388 1.1 christos lookup->task = NULL; 389 1.1 christos isc_task_attach(task, &lookup->task); 390 1.1 christos 391 1.1 christos isc_mutex_init(&lookup->lock); 392 1.1 christos 393 1.1 christos dns_fixedname_init(&lookup->name); 394 1.1 christos 395 1.1 christos dns_name_copynf(name, dns_fixedname_name(&lookup->name)); 396 1.1 christos 397 1.1 christos lookup->type = type; 398 1.1 christos lookup->view = NULL; 399 1.1 christos dns_view_attach(view, &lookup->view); 400 1.1 christos lookup->fetch = NULL; 401 1.1 christos lookup->restarts = 0; 402 1.1 christos lookup->canceled = false; 403 1.1 christos dns_rdataset_init(&lookup->rdataset); 404 1.1 christos dns_rdataset_init(&lookup->sigrdataset); 405 1.1 christos lookup->magic = LOOKUP_MAGIC; 406 1.1 christos 407 1.1 christos *lookupp = lookup; 408 1.1 christos 409 1.1 christos lookup_find(lookup, NULL); 410 1.1 christos 411 1.1 christos return (ISC_R_SUCCESS); 412 1.1 christos } 413 1.1 christos 414 1.1 christos void 415 1.1 christos dns_lookup_cancel(dns_lookup_t *lookup) { 416 1.1 christos REQUIRE(VALID_LOOKUP(lookup)); 417 1.1 christos 418 1.1 christos LOCK(&lookup->lock); 419 1.1 christos 420 1.1 christos if (!lookup->canceled) { 421 1.1 christos lookup->canceled = true; 422 1.1 christos if (lookup->fetch != NULL) { 423 1.1 christos INSIST(lookup->view != NULL); 424 1.1 christos dns_resolver_cancelfetch(lookup->fetch); 425 1.1 christos } 426 1.1 christos } 427 1.1 christos 428 1.1 christos UNLOCK(&lookup->lock); 429 1.1 christos } 430 1.1 christos 431 1.1 christos void 432 1.1 christos dns_lookup_destroy(dns_lookup_t **lookupp) { 433 1.1 christos dns_lookup_t *lookup; 434 1.1 christos 435 1.1 christos REQUIRE(lookupp != NULL); 436 1.1 christos lookup = *lookupp; 437 1.1 christos *lookupp = NULL; 438 1.1 christos REQUIRE(VALID_LOOKUP(lookup)); 439 1.1 christos REQUIRE(lookup->event == NULL); 440 1.1 christos REQUIRE(lookup->task == NULL); 441 1.1 christos REQUIRE(lookup->view == NULL); 442 1.1 christos if (dns_rdataset_isassociated(&lookup->rdataset)) { 443 1.1 christos dns_rdataset_disassociate(&lookup->rdataset); 444 1.1 christos } 445 1.1 christos if (dns_rdataset_isassociated(&lookup->sigrdataset)) { 446 1.1 christos dns_rdataset_disassociate(&lookup->sigrdataset); 447 1.1 christos } 448 1.1 christos 449 1.1 christos isc_mutex_destroy(&lookup->lock); 450 1.1 christos lookup->magic = 0; 451 1.1 christos isc_mem_putanddetach(&lookup->mctx, lookup, sizeof(*lookup)); 452 1.1 christos } 453