diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 15:59:48 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 15:59:48 +0000 |
commit | 3b9b6d0b8e7f798023c9d109c490449d528fde80 (patch) | |
tree | 2e1c188dd7b8d7475cd163de9ae02c428343669b /lib/dns/nta.c | |
parent | Initial commit. (diff) | |
download | bind9-upstream.tar.xz bind9-upstream.zip |
Adding upstream version 1:9.18.19.upstream/1%9.18.19upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'lib/dns/nta.c')
-rw-r--r-- | lib/dns/nta.c | 703 |
1 files changed, 703 insertions, 0 deletions
diff --git a/lib/dns/nta.c b/lib/dns/nta.c new file mode 100644 index 0000000..5614eb8 --- /dev/null +++ b/lib/dns/nta.c @@ -0,0 +1,703 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/*! \file */ + +#include <inttypes.h> +#include <stdbool.h> + +#include <isc/buffer.h> +#include <isc/log.h> +#include <isc/mem.h> +#include <isc/print.h> +#include <isc/result.h> +#include <isc/rwlock.h> +#include <isc/string.h> +#include <isc/task.h> +#include <isc/time.h> +#include <isc/timer.h> +#include <isc/util.h> + +#include <dns/db.h> +#include <dns/fixedname.h> +#include <dns/log.h> +#include <dns/name.h> +#include <dns/nta.h> +#include <dns/rbt.h> +#include <dns/rdataset.h> +#include <dns/resolver.h> +#include <dns/time.h> + +struct dns_nta { + unsigned int magic; + isc_refcount_t refcount; + dns_ntatable_t *ntatable; + bool forced; + isc_timer_t *timer; + dns_fetch_t *fetch; + dns_rdataset_t rdataset; + dns_rdataset_t sigrdataset; + dns_fixedname_t fn; + dns_name_t *name; + isc_stdtime_t expiry; +}; + +#define NTA_MAGIC ISC_MAGIC('N', 'T', 'A', 'n') +#define VALID_NTA(nn) ISC_MAGIC_VALID(nn, NTA_MAGIC) + +/* + * Obtain a reference to the nta object. Released by + * nta_detach. + */ +static void +nta_ref(dns_nta_t *nta) { + isc_refcount_increment(&nta->refcount); +} + +static void +nta_detach(isc_mem_t *mctx, dns_nta_t **ntap) { + REQUIRE(ntap != NULL && VALID_NTA(*ntap)); + dns_nta_t *nta = *ntap; + *ntap = NULL; + + if (isc_refcount_decrement(&nta->refcount) == 1) { + isc_refcount_destroy(&nta->refcount); + nta->magic = 0; + if (nta->timer != NULL) { + (void)isc_timer_reset(nta->timer, + isc_timertype_inactive, NULL, + NULL, true); + isc_timer_destroy(&nta->timer); + } + if (dns_rdataset_isassociated(&nta->rdataset)) { + dns_rdataset_disassociate(&nta->rdataset); + } + if (dns_rdataset_isassociated(&nta->sigrdataset)) { + dns_rdataset_disassociate(&nta->sigrdataset); + } + if (nta->fetch != NULL) { + dns_resolver_cancelfetch(nta->fetch); + dns_resolver_destroyfetch(&nta->fetch); + } + isc_mem_put(mctx, nta, sizeof(dns_nta_t)); + } +} + +static void +free_nta(void *data, void *arg) { + dns_nta_t *nta = (dns_nta_t *)data; + isc_mem_t *mctx = (isc_mem_t *)arg; + + nta_detach(mctx, &nta); +} + +isc_result_t +dns_ntatable_create(dns_view_t *view, isc_taskmgr_t *taskmgr, + isc_timermgr_t *timermgr, dns_ntatable_t **ntatablep) { + dns_ntatable_t *ntatable; + isc_result_t result; + + REQUIRE(ntatablep != NULL && *ntatablep == NULL); + + ntatable = isc_mem_get(view->mctx, sizeof(*ntatable)); + + ntatable->task = NULL; + result = isc_task_create(taskmgr, 0, &ntatable->task); + if (result != ISC_R_SUCCESS) { + goto cleanup_ntatable; + } + isc_task_setname(ntatable->task, "ntatable", ntatable); + + ntatable->table = NULL; + result = dns_rbt_create(view->mctx, free_nta, view->mctx, + &ntatable->table); + if (result != ISC_R_SUCCESS) { + goto cleanup_task; + } + + isc_rwlock_init(&ntatable->rwlock, 0, 0); + + ntatable->shuttingdown = false; + ntatable->timermgr = timermgr; + ntatable->taskmgr = taskmgr; + + ntatable->view = view; + isc_refcount_init(&ntatable->references, 1); + + ntatable->magic = NTATABLE_MAGIC; + *ntatablep = ntatable; + + return (ISC_R_SUCCESS); + +cleanup_task: + isc_task_detach(&ntatable->task); + +cleanup_ntatable: + isc_mem_put(view->mctx, ntatable, sizeof(*ntatable)); + + return (result); +} + +void +dns_ntatable_attach(dns_ntatable_t *source, dns_ntatable_t **targetp) { + REQUIRE(VALID_NTATABLE(source)); + REQUIRE(targetp != NULL && *targetp == NULL); + + isc_refcount_increment(&source->references); + + *targetp = source; +} + +void +dns_ntatable_detach(dns_ntatable_t **ntatablep) { + dns_ntatable_t *ntatable; + + REQUIRE(ntatablep != NULL && VALID_NTATABLE(*ntatablep)); + + ntatable = *ntatablep; + *ntatablep = NULL; + + if (isc_refcount_decrement(&ntatable->references) == 1) { + dns_rbt_destroy(&ntatable->table); + isc_rwlock_destroy(&ntatable->rwlock); + isc_refcount_destroy(&ntatable->references); + if (ntatable->task != NULL) { + isc_task_detach(&ntatable->task); + } + ntatable->timermgr = NULL; + ntatable->taskmgr = NULL; + ntatable->magic = 0; + isc_mem_put(ntatable->view->mctx, ntatable, sizeof(*ntatable)); + } +} + +static void +fetch_done(isc_task_t *task, isc_event_t *event) { + dns_fetchevent_t *devent = (dns_fetchevent_t *)event; + dns_nta_t *nta = devent->ev_arg; + isc_result_t eresult = devent->result; + dns_ntatable_t *ntatable = nta->ntatable; + dns_view_t *view = ntatable->view; + isc_stdtime_t now; + + UNUSED(task); + + if (dns_rdataset_isassociated(&nta->rdataset)) { + dns_rdataset_disassociate(&nta->rdataset); + } + if (dns_rdataset_isassociated(&nta->sigrdataset)) { + dns_rdataset_disassociate(&nta->sigrdataset); + } + if (nta->fetch == devent->fetch) { + nta->fetch = NULL; + } + dns_resolver_destroyfetch(&devent->fetch); + + if (devent->node != NULL) { + dns_db_detachnode(devent->db, &devent->node); + } + if (devent->db != NULL) { + dns_db_detach(&devent->db); + } + + isc_event_free(&event); + isc_stdtime_get(&now); + + switch (eresult) { + case ISC_R_SUCCESS: + case DNS_R_NCACHENXDOMAIN: + case DNS_R_NXDOMAIN: + case DNS_R_NCACHENXRRSET: + case DNS_R_NXRRSET: + if (nta->expiry > now) { + nta->expiry = now; + } + break; + default: + break; + } + + /* + * If we're expiring before the next recheck, we might + * as well stop the timer now. + */ + if (nta->timer != NULL && nta->expiry - now < view->nta_recheck) { + (void)isc_timer_reset(nta->timer, isc_timertype_inactive, NULL, + NULL, true); + } + nta_detach(view->mctx, &nta); + dns_view_weakdetach(&view); +} + +static void +checkbogus(isc_task_t *task, isc_event_t *event) { + dns_nta_t *nta = event->ev_arg; + dns_ntatable_t *ntatable = nta->ntatable; + dns_view_t *view = NULL; + isc_result_t result; + + if (nta->fetch != NULL) { + dns_resolver_cancelfetch(nta->fetch); + nta->fetch = NULL; + } + if (dns_rdataset_isassociated(&nta->rdataset)) { + dns_rdataset_disassociate(&nta->rdataset); + } + if (dns_rdataset_isassociated(&nta->sigrdataset)) { + dns_rdataset_disassociate(&nta->sigrdataset); + } + + isc_event_free(&event); + + nta_ref(nta); + dns_view_weakattach(ntatable->view, &view); + result = dns_resolver_createfetch( + view->resolver, nta->name, dns_rdatatype_nsec, NULL, NULL, NULL, + NULL, 0, DNS_FETCHOPT_NONTA, 0, NULL, task, fetch_done, nta, + &nta->rdataset, &nta->sigrdataset, &nta->fetch); + if (result != ISC_R_SUCCESS) { + nta_detach(view->mctx, &nta); + dns_view_weakdetach(&view); + } +} + +static isc_result_t +settimer(dns_ntatable_t *ntatable, dns_nta_t *nta, uint32_t lifetime) { + isc_result_t result; + isc_interval_t interval; + dns_view_t *view; + + REQUIRE(VALID_NTATABLE(ntatable)); + REQUIRE(VALID_NTA(nta)); + + if (ntatable->timermgr == NULL) { + return (ISC_R_SUCCESS); + } + + view = ntatable->view; + if (view->nta_recheck == 0 || lifetime <= view->nta_recheck) { + return (ISC_R_SUCCESS); + } + + isc_interval_set(&interval, view->nta_recheck, 0); + result = isc_timer_create(ntatable->timermgr, isc_timertype_ticker, + NULL, &interval, ntatable->task, checkbogus, + nta, &nta->timer); + if (result != ISC_R_SUCCESS) { + isc_timer_destroy(&nta->timer); + } + return (result); +} + +static isc_result_t +nta_create(dns_ntatable_t *ntatable, const dns_name_t *name, + dns_nta_t **target) { + dns_nta_t *nta = NULL; + dns_view_t *view; + + REQUIRE(VALID_NTATABLE(ntatable)); + REQUIRE(target != NULL && *target == NULL); + + view = ntatable->view; + + nta = isc_mem_get(view->mctx, sizeof(dns_nta_t)); + + nta->ntatable = ntatable; + nta->expiry = 0; + nta->timer = NULL; + nta->fetch = NULL; + dns_rdataset_init(&nta->rdataset); + dns_rdataset_init(&nta->sigrdataset); + + isc_refcount_init(&nta->refcount, 1); + + nta->name = dns_fixedname_initname(&nta->fn); + dns_name_copy(name, nta->name); + + nta->magic = NTA_MAGIC; + + *target = nta; + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_ntatable_add(dns_ntatable_t *ntatable, const dns_name_t *name, bool force, + isc_stdtime_t now, uint32_t lifetime) { + isc_result_t result = ISC_R_SUCCESS; + dns_nta_t *nta = NULL; + dns_rbtnode_t *node; + dns_view_t *view; + + REQUIRE(VALID_NTATABLE(ntatable)); + + view = ntatable->view; + + RWLOCK(&ntatable->rwlock, isc_rwlocktype_write); + + if (ntatable->shuttingdown) { + goto unlock; + } + + result = nta_create(ntatable, name, &nta); + if (result != ISC_R_SUCCESS) { + goto unlock; + } + + nta->expiry = now + lifetime; + nta->forced = force; + + node = NULL; + result = dns_rbt_addnode(ntatable->table, name, &node); + if (result == ISC_R_SUCCESS) { + if (!force) { + (void)settimer(ntatable, nta, lifetime); + } + node->data = nta; + nta = NULL; + } else if (result == ISC_R_EXISTS) { + dns_nta_t *n = node->data; + if (n == NULL) { + if (!force) { + (void)settimer(ntatable, nta, lifetime); + } + node->data = nta; + nta = NULL; + } else { + n->expiry = nta->expiry; + nta_detach(view->mctx, &nta); + } + result = ISC_R_SUCCESS; + } + +unlock: + RWUNLOCK(&ntatable->rwlock, isc_rwlocktype_write); + + if (nta != NULL) { + nta_detach(view->mctx, &nta); + } + + return (result); +} + +/* + * Caller must hold a write lock on rwlock. + */ +static isc_result_t +deletenode(dns_ntatable_t *ntatable, const dns_name_t *name) { + isc_result_t result; + dns_rbtnode_t *node = NULL; + + REQUIRE(VALID_NTATABLE(ntatable)); + REQUIRE(name != NULL); + + result = dns_rbt_findnode(ntatable->table, name, NULL, &node, NULL, + DNS_RBTFIND_NOOPTIONS, NULL, NULL); + if (result == ISC_R_SUCCESS) { + if (node->data != NULL) { + result = dns_rbt_deletenode(ntatable->table, node, + false); + } else { + result = ISC_R_NOTFOUND; + } + } else if (result == DNS_R_PARTIALMATCH) { + result = ISC_R_NOTFOUND; + } + + return (result); +} + +isc_result_t +dns_ntatable_delete(dns_ntatable_t *ntatable, const dns_name_t *name) { + isc_result_t result; + + RWLOCK(&ntatable->rwlock, isc_rwlocktype_write); + result = deletenode(ntatable, name); + RWUNLOCK(&ntatable->rwlock, isc_rwlocktype_write); + + return (result); +} + +bool +dns_ntatable_covered(dns_ntatable_t *ntatable, isc_stdtime_t now, + const dns_name_t *name, const dns_name_t *anchor) { + isc_result_t result; + dns_fixedname_t fn; + dns_rbtnode_t *node; + dns_name_t *foundname; + dns_nta_t *nta = NULL; + bool answer = false; + isc_rwlocktype_t locktype = isc_rwlocktype_read; + + REQUIRE(ntatable == NULL || VALID_NTATABLE(ntatable)); + REQUIRE(dns_name_isabsolute(name)); + + if (ntatable == NULL) { + return (false); + } + + foundname = dns_fixedname_initname(&fn); + +relock: + RWLOCK(&ntatable->rwlock, locktype); +again: + node = NULL; + result = dns_rbt_findnode(ntatable->table, name, foundname, &node, NULL, + DNS_RBTFIND_NOOPTIONS, NULL, NULL); + if (result == DNS_R_PARTIALMATCH) { + if (dns_name_issubdomain(foundname, anchor)) { + result = ISC_R_SUCCESS; + } + } + if (result == ISC_R_SUCCESS) { + nta = (dns_nta_t *)node->data; + answer = (nta->expiry > now); + } + + /* Deal with expired NTA */ + if (result == ISC_R_SUCCESS && !answer) { + char nb[DNS_NAME_FORMATSIZE]; + + if (locktype == isc_rwlocktype_read) { + RWUNLOCK(&ntatable->rwlock, locktype); + locktype = isc_rwlocktype_write; + goto relock; + } + + dns_name_format(foundname, nb, sizeof(nb)); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC, + DNS_LOGMODULE_NTA, ISC_LOG_INFO, + "deleting expired NTA at %s", nb); + + if (nta->timer != NULL) { + (void)isc_timer_reset(nta->timer, + isc_timertype_inactive, NULL, + NULL, true); + isc_timer_destroy(&nta->timer); + } + + result = deletenode(ntatable, foundname); + if (result != ISC_R_SUCCESS) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC, + DNS_LOGMODULE_NTA, ISC_LOG_INFO, + "deleting NTA failed: %s", + isc_result_totext(result)); + } + goto again; + } + RWUNLOCK(&ntatable->rwlock, locktype); + + return (answer); +} + +static isc_result_t +putstr(isc_buffer_t **b, const char *str) { + isc_result_t result; + + result = isc_buffer_reserve(b, strlen(str)); + if (result != ISC_R_SUCCESS) { + return (result); + } + + isc_buffer_putstr(*b, str); + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_ntatable_totext(dns_ntatable_t *ntatable, const char *view, + isc_buffer_t **buf) { + isc_result_t result; + dns_rbtnode_t *node; + dns_rbtnodechain_t chain; + bool first = true; + isc_stdtime_t now; + + REQUIRE(VALID_NTATABLE(ntatable)); + + isc_stdtime_get(&now); + + RWLOCK(&ntatable->rwlock, isc_rwlocktype_read); + dns_rbtnodechain_init(&chain); + result = dns_rbtnodechain_first(&chain, ntatable->table, NULL, NULL); + if (result != ISC_R_SUCCESS && result != DNS_R_NEWORIGIN) { + if (result == ISC_R_NOTFOUND) { + result = ISC_R_SUCCESS; + } + goto cleanup; + } + for (;;) { + dns_rbtnodechain_current(&chain, NULL, NULL, &node); + if (node->data != NULL) { + dns_nta_t *n = (dns_nta_t *)node->data; + char nbuf[DNS_NAME_FORMATSIZE]; + char tbuf[ISC_FORMATHTTPTIMESTAMP_SIZE]; + char obuf[DNS_NAME_FORMATSIZE + + ISC_FORMATHTTPTIMESTAMP_SIZE + + sizeof("expired: \n")]; + dns_fixedname_t fn; + dns_name_t *name; + isc_time_t t; + + name = dns_fixedname_initname(&fn); + dns_rbt_fullnamefromnode(node, name); + dns_name_format(name, nbuf, sizeof(nbuf)); + + if (n->expiry != 0xffffffffU) { + /* Normal NTA entries */ + isc_time_set(&t, n->expiry, 0); + isc_time_formattimestamp(&t, tbuf, + sizeof(tbuf)); + + snprintf(obuf, sizeof(obuf), "%s%s%s%s: %s %s", + first ? "" : "\n", nbuf, + view != NULL ? "/" : "", + view != NULL ? view : "", + n->expiry <= now ? "expired" + : "expiry", + tbuf); + } else { + /* "validate-except" entries */ + snprintf(obuf, sizeof(obuf), "%s%s%s%s: %s", + first ? "" : "\n", nbuf, + view != NULL ? "/" : "", + view != NULL ? view : "", "permanent"); + } + + first = false; + result = putstr(buf, obuf); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + } + result = dns_rbtnodechain_next(&chain, NULL, NULL); + if (result != ISC_R_SUCCESS && result != DNS_R_NEWORIGIN) { + if (result == ISC_R_NOMORE) { + result = ISC_R_SUCCESS; + } + break; + } + } + +cleanup: + dns_rbtnodechain_invalidate(&chain); + RWUNLOCK(&ntatable->rwlock, isc_rwlocktype_read); + return (result); +} + +isc_result_t +dns_ntatable_save(dns_ntatable_t *ntatable, FILE *fp) { + isc_result_t result; + dns_rbtnode_t *node; + dns_rbtnodechain_t chain; + isc_stdtime_t now; + bool written = false; + + REQUIRE(VALID_NTATABLE(ntatable)); + + isc_stdtime_get(&now); + + RWLOCK(&ntatable->rwlock, isc_rwlocktype_read); + dns_rbtnodechain_init(&chain); + result = dns_rbtnodechain_first(&chain, ntatable->table, NULL, NULL); + if (result != ISC_R_SUCCESS && result != DNS_R_NEWORIGIN) { + goto cleanup; + } + + for (;;) { + dns_rbtnodechain_current(&chain, NULL, NULL, &node); + if (node->data != NULL) { + isc_buffer_t b; + char nbuf[DNS_NAME_FORMATSIZE + 1], tbuf[80]; + dns_fixedname_t fn; + dns_name_t *name; + dns_nta_t *n = (dns_nta_t *)node->data; + + /* + * Skip this node if the expiry is already in the + * past, or if this is a "validate-except" entry. + */ + if (n->expiry <= now || n->expiry == 0xffffffffU) { + goto skip; + } + + name = dns_fixedname_initname(&fn); + dns_rbt_fullnamefromnode(node, name); + + isc_buffer_init(&b, nbuf, sizeof(nbuf)); + result = dns_name_totext(name, false, &b); + if (result != ISC_R_SUCCESS) { + goto skip; + } + + /* Zero terminate. */ + isc_buffer_putuint8(&b, 0); + + isc_buffer_init(&b, tbuf, sizeof(tbuf)); + dns_time32_totext(n->expiry, &b); + + /* Zero terminate. */ + isc_buffer_putuint8(&b, 0); + + fprintf(fp, "%s %s %s\n", nbuf, + n->forced ? "forced" : "regular", tbuf); + written = true; + } + skip: + result = dns_rbtnodechain_next(&chain, NULL, NULL); + if (result != ISC_R_SUCCESS && result != DNS_R_NEWORIGIN) { + if (result == ISC_R_NOMORE) { + result = ISC_R_SUCCESS; + } + break; + } + } + +cleanup: + dns_rbtnodechain_invalidate(&chain); + RWUNLOCK(&ntatable->rwlock, isc_rwlocktype_read); + + if (result != ISC_R_SUCCESS) { + return (result); + } else { + return (written ? ISC_R_SUCCESS : ISC_R_NOTFOUND); + } +} + +void +dns_ntatable_shutdown(dns_ntatable_t *ntatable) { + isc_result_t result; + dns_rbtnode_t *node; + dns_rbtnodechain_t chain; + + REQUIRE(VALID_NTATABLE(ntatable)); + + RWLOCK(&ntatable->rwlock, isc_rwlocktype_write); + ntatable->shuttingdown = true; + + dns_rbtnodechain_init(&chain); + result = dns_rbtnodechain_first(&chain, ntatable->table, NULL, NULL); + while (result == ISC_R_SUCCESS || result == DNS_R_NEWORIGIN) { + dns_rbtnodechain_current(&chain, NULL, NULL, &node); + if (node->data != NULL) { + dns_nta_t *nta = (dns_nta_t *)node->data; + if (nta->timer != NULL) { + (void)isc_timer_reset(nta->timer, + isc_timertype_inactive, + NULL, NULL, true); + } + } + result = dns_rbtnodechain_next(&chain, NULL, NULL); + } + + dns_rbtnodechain_invalidate(&chain); + RWUNLOCK(&ntatable->rwlock, isc_rwlocktype_write); +} |