diff options
Diffstat (limited to '')
-rw-r--r-- | lib/dns/client.c | 1353 |
1 files changed, 1353 insertions, 0 deletions
diff --git a/lib/dns/client.c b/lib/dns/client.c new file mode 100644 index 0000000..9cfc810 --- /dev/null +++ b/lib/dns/client.c @@ -0,0 +1,1353 @@ +/* + * 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. + */ + +#include <stdbool.h> +#include <stddef.h> + +#include <isc/app.h> +#include <isc/buffer.h> +#include <isc/md.h> +#include <isc/mem.h> +#include <isc/mutex.h> +#include <isc/portset.h> +#include <isc/refcount.h> +#include <isc/safe.h> +#include <isc/sockaddr.h> +#include <isc/socket.h> +#include <isc/task.h> +#include <isc/timer.h> +#include <isc/util.h> + +#include <dns/adb.h> +#include <dns/client.h> +#include <dns/db.h> +#include <dns/dispatch.h> +#include <dns/events.h> +#include <dns/forward.h> +#include <dns/keytable.h> +#include <dns/message.h> +#include <dns/name.h> +#include <dns/rdata.h> +#include <dns/rdatalist.h> +#include <dns/rdataset.h> +#include <dns/rdatasetiter.h> +#include <dns/rdatastruct.h> +#include <dns/rdatatype.h> +#include <dns/request.h> +#include <dns/resolver.h> +#include <dns/result.h> +#include <dns/tsec.h> +#include <dns/tsig.h> +#include <dns/view.h> + +#include <dst/dst.h> + +#define DNS_CLIENT_MAGIC ISC_MAGIC('D', 'N', 'S', 'c') +#define DNS_CLIENT_VALID(c) ISC_MAGIC_VALID(c, DNS_CLIENT_MAGIC) + +#define RCTX_MAGIC ISC_MAGIC('R', 'c', 't', 'x') +#define RCTX_VALID(c) ISC_MAGIC_VALID(c, RCTX_MAGIC) + +#define UCTX_MAGIC ISC_MAGIC('U', 'c', 't', 'x') +#define UCTX_VALID(c) ISC_MAGIC_VALID(c, UCTX_MAGIC) + +#define MAX_RESTARTS 16 + +#ifdef TUNE_LARGE +#define RESOLVER_NTASKS 523 +#else /* ifdef TUNE_LARGE */ +#define RESOLVER_NTASKS 31 +#endif /* TUNE_LARGE */ + +#define CHECK(r) \ + do { \ + result = (r); \ + if (result != ISC_R_SUCCESS) \ + goto cleanup; \ + } while (0) + +/*% + * DNS client object + */ +struct dns_client { + /* Unlocked */ + unsigned int magic; + unsigned int attributes; + isc_mutex_t lock; + isc_mem_t *mctx; + isc_appctx_t *actx; + isc_taskmgr_t *taskmgr; + isc_task_t *task; + isc_socketmgr_t *socketmgr; + isc_timermgr_t *timermgr; + dns_dispatchmgr_t *dispatchmgr; + dns_dispatch_t *dispatchv4; + dns_dispatch_t *dispatchv6; + + unsigned int find_timeout; + unsigned int find_udpretries; + + isc_refcount_t references; + + /* Locked */ + dns_viewlist_t viewlist; + ISC_LIST(struct resctx) resctxs; +}; + +#define DEF_FIND_TIMEOUT 5 +#define DEF_FIND_UDPRETRIES 3 + +/*% + * Internal state for a single name resolution procedure + */ +typedef struct resctx { + /* Unlocked */ + unsigned int magic; + isc_mutex_t lock; + dns_client_t *client; + bool want_dnssec; + bool want_validation; + bool want_cdflag; + bool want_tcp; + + /* Locked */ + ISC_LINK(struct resctx) link; + isc_task_t *task; + dns_view_t *view; + unsigned int restarts; + dns_fixedname_t name; + dns_rdatatype_t type; + dns_fetch_t *fetch; + dns_namelist_t namelist; + isc_result_t result; + dns_clientresevent_t *event; + bool canceled; + dns_rdataset_t *rdataset; + dns_rdataset_t *sigrdataset; +} resctx_t; + +/*% + * Argument of an internal event for synchronous name resolution. + */ +typedef struct resarg { + /* Unlocked */ + isc_appctx_t *actx; + dns_client_t *client; + isc_mutex_t lock; + + /* Locked */ + isc_result_t result; + isc_result_t vresult; + dns_namelist_t *namelist; + dns_clientrestrans_t *trans; + bool canceled; +} resarg_t; + +static void +client_resfind(resctx_t *rctx, dns_fetchevent_t *event); + +/* + * Try honoring the operating system's preferred ephemeral port range. + */ +static isc_result_t +setsourceports(isc_mem_t *mctx, dns_dispatchmgr_t *manager) { + isc_portset_t *v4portset = NULL, *v6portset = NULL; + in_port_t udpport_low, udpport_high; + isc_result_t result; + + result = isc_portset_create(mctx, &v4portset); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + result = isc_net_getudpportrange(AF_INET, &udpport_low, &udpport_high); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + isc_portset_addrange(v4portset, udpport_low, udpport_high); + + result = isc_portset_create(mctx, &v6portset); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + result = isc_net_getudpportrange(AF_INET6, &udpport_low, &udpport_high); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + isc_portset_addrange(v6portset, udpport_low, udpport_high); + + result = dns_dispatchmgr_setavailports(manager, v4portset, v6portset); + +cleanup: + if (v4portset != NULL) { + isc_portset_destroy(mctx, &v4portset); + } + if (v6portset != NULL) { + isc_portset_destroy(mctx, &v6portset); + } + + return (result); +} + +static isc_result_t +getudpdispatch(int family, dns_dispatchmgr_t *dispatchmgr, + isc_socketmgr_t *socketmgr, isc_taskmgr_t *taskmgr, + bool is_shared, dns_dispatch_t **dispp, + const isc_sockaddr_t *localaddr) { + unsigned int attrs, attrmask; + dns_dispatch_t *disp; + unsigned buffersize, maxbuffers, maxrequests, buckets, increment; + isc_result_t result; + isc_sockaddr_t anyaddr; + + attrs = 0; + attrs |= DNS_DISPATCHATTR_UDP; + switch (family) { + case AF_INET: + attrs |= DNS_DISPATCHATTR_IPV4; + break; + case AF_INET6: + attrs |= DNS_DISPATCHATTR_IPV6; + break; + default: + UNREACHABLE(); + } + attrmask = 0; + attrmask |= DNS_DISPATCHATTR_UDP; + attrmask |= DNS_DISPATCHATTR_TCP; + attrmask |= DNS_DISPATCHATTR_IPV4; + attrmask |= DNS_DISPATCHATTR_IPV6; + + if (localaddr == NULL) { + isc_sockaddr_anyofpf(&anyaddr, family); + localaddr = &anyaddr; + } + + buffersize = 4096; + maxbuffers = is_shared ? 1000 : 8; + maxrequests = 32768; + buckets = is_shared ? 16411 : 3; + increment = is_shared ? 16433 : 5; + + disp = NULL; + result = dns_dispatch_getudp(dispatchmgr, socketmgr, taskmgr, localaddr, + buffersize, maxbuffers, maxrequests, + buckets, increment, attrs, attrmask, + &disp); + if (result == ISC_R_SUCCESS) { + *dispp = disp; + } + + return (result); +} + +static isc_result_t +createview(isc_mem_t *mctx, dns_rdataclass_t rdclass, unsigned int options, + isc_taskmgr_t *taskmgr, unsigned int ntasks, + isc_socketmgr_t *socketmgr, isc_timermgr_t *timermgr, + dns_dispatchmgr_t *dispatchmgr, dns_dispatch_t *dispatchv4, + dns_dispatch_t *dispatchv6, dns_view_t **viewp) { + isc_result_t result; + dns_view_t *view = NULL; + const char *dbtype; + + result = dns_view_create(mctx, rdclass, DNS_CLIENTVIEW_NAME, &view); + if (result != ISC_R_SUCCESS) { + return (result); + } + + /* Initialize view security roots */ + result = dns_view_initsecroots(view, mctx); + if (result != ISC_R_SUCCESS) { + dns_view_detach(&view); + return (result); + } + + result = dns_view_createresolver(view, taskmgr, ntasks, 1, socketmgr, + timermgr, 0, dispatchmgr, dispatchv4, + dispatchv6); + if (result != ISC_R_SUCCESS) { + dns_view_detach(&view); + return (result); + } + + /* + * Set cache DB. + * XXX: it may be better if specific DB implementations can be + * specified via some configuration knob. + */ + if ((options & DNS_CLIENTCREATEOPT_USECACHE) != 0) { + dbtype = "rbt"; + } else { + dbtype = "ecdb"; + } + result = dns_db_create(mctx, dbtype, dns_rootname, dns_dbtype_cache, + rdclass, 0, NULL, &view->cachedb); + if (result != ISC_R_SUCCESS) { + dns_view_detach(&view); + return (result); + } + + *viewp = view; + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_client_create(isc_mem_t *mctx, isc_appctx_t *actx, isc_taskmgr_t *taskmgr, + isc_socketmgr_t *socketmgr, isc_timermgr_t *timermgr, + unsigned int options, dns_client_t **clientp, + const isc_sockaddr_t *localaddr4, + const isc_sockaddr_t *localaddr6) { + isc_result_t result; + dns_client_t *client = NULL; + dns_dispatchmgr_t *dispatchmgr = NULL; + dns_dispatch_t *dispatchv4 = NULL; + dns_dispatch_t *dispatchv6 = NULL; + dns_view_t *view = NULL; + + REQUIRE(mctx != NULL); + REQUIRE(taskmgr != NULL); + REQUIRE(timermgr != NULL); + REQUIRE(socketmgr != NULL); + REQUIRE(clientp != NULL && *clientp == NULL); + + client = isc_mem_get(mctx, sizeof(*client)); + + isc_mutex_init(&client->lock); + + client->actx = actx; + client->taskmgr = taskmgr; + client->socketmgr = socketmgr; + client->timermgr = timermgr; + + client->task = NULL; + result = isc_task_create(client->taskmgr, 0, &client->task); + if (result != ISC_R_SUCCESS) { + goto cleanup_lock; + } + + result = dns_dispatchmgr_create(mctx, &dispatchmgr); + if (result != ISC_R_SUCCESS) { + goto cleanup_task; + } + client->dispatchmgr = dispatchmgr; + (void)setsourceports(mctx, dispatchmgr); + + /* + * If only one address family is specified, use it. + * If neither family is specified, or if both are, use both. + */ + client->dispatchv4 = NULL; + if (localaddr4 != NULL || localaddr6 == NULL) { + result = getudpdispatch(AF_INET, dispatchmgr, socketmgr, + taskmgr, true, &dispatchv4, localaddr4); + if (result == ISC_R_SUCCESS) { + client->dispatchv4 = dispatchv4; + } + } + + client->dispatchv6 = NULL; + if (localaddr6 != NULL || localaddr4 == NULL) { + result = getudpdispatch(AF_INET6, dispatchmgr, socketmgr, + taskmgr, true, &dispatchv6, localaddr6); + if (result == ISC_R_SUCCESS) { + client->dispatchv6 = dispatchv6; + } + } + + /* We need at least one of the dispatchers */ + if (dispatchv4 == NULL && dispatchv6 == NULL) { + INSIST(result != ISC_R_SUCCESS); + goto cleanup_dispatchmgr; + } + + isc_refcount_init(&client->references, 1); + + /* Create the default view for class IN */ + result = createview(mctx, dns_rdataclass_in, options, taskmgr, + RESOLVER_NTASKS, socketmgr, timermgr, dispatchmgr, + dispatchv4, dispatchv6, &view); + if (result != ISC_R_SUCCESS) { + goto cleanup_references; + } + + ISC_LIST_INIT(client->viewlist); + ISC_LIST_APPEND(client->viewlist, view, link); + + dns_view_freeze(view); /* too early? */ + + ISC_LIST_INIT(client->resctxs); + + client->mctx = NULL; + isc_mem_attach(mctx, &client->mctx); + + client->find_timeout = DEF_FIND_TIMEOUT; + client->find_udpretries = DEF_FIND_UDPRETRIES; + client->attributes = 0; + + client->magic = DNS_CLIENT_MAGIC; + + *clientp = client; + + return (ISC_R_SUCCESS); + +cleanup_references: + isc_refcount_decrementz(&client->references); + isc_refcount_destroy(&client->references); +cleanup_dispatchmgr: + if (dispatchv4 != NULL) { + dns_dispatch_detach(&dispatchv4); + } + if (dispatchv6 != NULL) { + dns_dispatch_detach(&dispatchv6); + } + dns_dispatchmgr_destroy(&dispatchmgr); +cleanup_task: + isc_task_detach(&client->task); +cleanup_lock: + isc_mutex_destroy(&client->lock); + isc_mem_put(mctx, client, sizeof(*client)); + + return (result); +} + +static void +destroyclient(dns_client_t *client) { + dns_view_t *view; + + isc_refcount_destroy(&client->references); + + while ((view = ISC_LIST_HEAD(client->viewlist)) != NULL) { + ISC_LIST_UNLINK(client->viewlist, view, link); + dns_view_detach(&view); + } + + if (client->dispatchv4 != NULL) { + dns_dispatch_detach(&client->dispatchv4); + } + if (client->dispatchv6 != NULL) { + dns_dispatch_detach(&client->dispatchv6); + } + + dns_dispatchmgr_destroy(&client->dispatchmgr); + + isc_task_detach(&client->task); + + isc_mutex_destroy(&client->lock); + client->magic = 0; + + isc_mem_putanddetach(&client->mctx, client, sizeof(*client)); +} + +void +dns_client_destroy(dns_client_t **clientp) { + dns_client_t *client; + + REQUIRE(clientp != NULL); + client = *clientp; + *clientp = NULL; + REQUIRE(DNS_CLIENT_VALID(client)); + + if (isc_refcount_decrement(&client->references) == 1) { + destroyclient(client); + } +} + +isc_result_t +dns_client_setservers(dns_client_t *client, dns_rdataclass_t rdclass, + const dns_name_t *name_space, isc_sockaddrlist_t *addrs) { + isc_result_t result; + dns_view_t *view = NULL; + + REQUIRE(DNS_CLIENT_VALID(client)); + REQUIRE(addrs != NULL); + + if (name_space == NULL) { + name_space = dns_rootname; + } + + LOCK(&client->lock); + result = dns_viewlist_find(&client->viewlist, DNS_CLIENTVIEW_NAME, + rdclass, &view); + if (result != ISC_R_SUCCESS) { + UNLOCK(&client->lock); + return (result); + } + UNLOCK(&client->lock); + + result = dns_fwdtable_add(view->fwdtable, name_space, addrs, + dns_fwdpolicy_only); + + dns_view_detach(&view); + + return (result); +} + +isc_result_t +dns_client_clearservers(dns_client_t *client, dns_rdataclass_t rdclass, + const dns_name_t *name_space) { + isc_result_t result; + dns_view_t *view = NULL; + + REQUIRE(DNS_CLIENT_VALID(client)); + + if (name_space == NULL) { + name_space = dns_rootname; + } + + LOCK(&client->lock); + result = dns_viewlist_find(&client->viewlist, DNS_CLIENTVIEW_NAME, + rdclass, &view); + if (result != ISC_R_SUCCESS) { + UNLOCK(&client->lock); + return (result); + } + UNLOCK(&client->lock); + + result = dns_fwdtable_delete(view->fwdtable, name_space); + + dns_view_detach(&view); + + return (result); +} + +static isc_result_t +getrdataset(isc_mem_t *mctx, dns_rdataset_t **rdatasetp) { + dns_rdataset_t *rdataset; + + REQUIRE(mctx != NULL); + REQUIRE(rdatasetp != NULL && *rdatasetp == NULL); + + rdataset = isc_mem_get(mctx, sizeof(*rdataset)); + + dns_rdataset_init(rdataset); + + *rdatasetp = rdataset; + + return (ISC_R_SUCCESS); +} + +static void +putrdataset(isc_mem_t *mctx, dns_rdataset_t **rdatasetp) { + dns_rdataset_t *rdataset; + + REQUIRE(rdatasetp != NULL); + rdataset = *rdatasetp; + *rdatasetp = NULL; + REQUIRE(rdataset != NULL); + + if (dns_rdataset_isassociated(rdataset)) { + dns_rdataset_disassociate(rdataset); + } + + isc_mem_put(mctx, rdataset, sizeof(*rdataset)); +} + +static void +fetch_done(isc_task_t *task, isc_event_t *event) { + resctx_t *rctx = event->ev_arg; + dns_fetchevent_t *fevent; + + REQUIRE(event->ev_type == DNS_EVENT_FETCHDONE); + REQUIRE(RCTX_VALID(rctx)); + REQUIRE(rctx->task == task); + fevent = (dns_fetchevent_t *)event; + + client_resfind(rctx, fevent); +} + +static isc_result_t +start_fetch(resctx_t *rctx) { + isc_result_t result; + int fopts = 0; + + /* + * The caller must be holding the rctx's lock. + */ + + REQUIRE(rctx->fetch == NULL); + + if (!rctx->want_cdflag) { + fopts |= DNS_FETCHOPT_NOCDFLAG; + } + if (!rctx->want_validation) { + fopts |= DNS_FETCHOPT_NOVALIDATE; + } + if (rctx->want_tcp) { + fopts |= DNS_FETCHOPT_TCP; + } + + result = dns_resolver_createfetch( + rctx->view->resolver, dns_fixedname_name(&rctx->name), + rctx->type, NULL, NULL, NULL, NULL, 0, fopts, 0, NULL, + rctx->task, fetch_done, rctx, rctx->rdataset, rctx->sigrdataset, + &rctx->fetch); + + return (result); +} + +static isc_result_t +view_find(resctx_t *rctx, dns_db_t **dbp, dns_dbnode_t **nodep, + dns_name_t *foundname) { + isc_result_t result; + dns_name_t *name = dns_fixedname_name(&rctx->name); + dns_rdatatype_t type; + + if (rctx->type == dns_rdatatype_rrsig) { + type = dns_rdatatype_any; + } else { + type = rctx->type; + } + + result = dns_view_find(rctx->view, name, type, 0, 0, false, false, dbp, + nodep, foundname, rctx->rdataset, + rctx->sigrdataset); + + return (result); +} + +static void +client_resfind(resctx_t *rctx, dns_fetchevent_t *event) { + isc_mem_t *mctx; + isc_result_t tresult, result = ISC_R_SUCCESS; + isc_result_t vresult = ISC_R_SUCCESS; + bool want_restart; + bool send_event = false; + dns_name_t *name, *prefix; + dns_fixedname_t foundname, fixed; + dns_rdataset_t *trdataset; + dns_rdata_t rdata = DNS_RDATA_INIT; + unsigned int nlabels; + int order; + dns_namereln_t namereln; + dns_rdata_cname_t cname; + dns_rdata_dname_t dname; + + REQUIRE(RCTX_VALID(rctx)); + + LOCK(&rctx->lock); + + mctx = rctx->view->mctx; + + name = dns_fixedname_name(&rctx->name); + + do { + dns_name_t *fname = NULL; + dns_name_t *ansname = NULL; + dns_db_t *db = NULL; + dns_dbnode_t *node = NULL; + + rctx->restarts++; + want_restart = false; + + if (event == NULL && !rctx->canceled) { + fname = dns_fixedname_initname(&foundname); + INSIST(!dns_rdataset_isassociated(rctx->rdataset)); + INSIST(rctx->sigrdataset == NULL || + !dns_rdataset_isassociated(rctx->sigrdataset)); + result = view_find(rctx, &db, &node, fname); + if (result == ISC_R_NOTFOUND) { + /* + * We don't know anything about the name. + * Launch a fetch. + */ + if (node != NULL) { + INSIST(db != NULL); + dns_db_detachnode(db, &node); + } + if (db != NULL) { + dns_db_detach(&db); + } + result = start_fetch(rctx); + if (result != ISC_R_SUCCESS) { + putrdataset(mctx, &rctx->rdataset); + if (rctx->sigrdataset != NULL) { + putrdataset(mctx, + &rctx->sigrdataset); + } + send_event = true; + } + goto done; + } + } else { + INSIST(event != NULL); + INSIST(event->fetch == rctx->fetch); + dns_resolver_destroyfetch(&rctx->fetch); + db = event->db; + node = event->node; + result = event->result; + vresult = event->vresult; + fname = dns_fixedname_name(&event->foundname); + INSIST(event->rdataset == rctx->rdataset); + INSIST(event->sigrdataset == rctx->sigrdataset); + } + + /* + * If we've been canceled, forget about the result. + */ + if (rctx->canceled) { + result = ISC_R_CANCELED; + } else { + /* + * Otherwise, get some resource for copying the + * result. + */ + dns_name_t *aname = dns_fixedname_name(&rctx->name); + + ansname = isc_mem_get(mctx, sizeof(*ansname)); + dns_name_init(ansname, NULL); + + dns_name_dup(aname, mctx, ansname); + } + + switch (result) { + case ISC_R_SUCCESS: + send_event = true; + /* + * This case is handled in the main line below. + */ + break; + case DNS_R_CNAME: + /* + * Add the CNAME to the answer list. + */ + trdataset = rctx->rdataset; + ISC_LIST_APPEND(ansname->list, rctx->rdataset, link); + rctx->rdataset = NULL; + if (rctx->sigrdataset != NULL) { + ISC_LIST_APPEND(ansname->list, + rctx->sigrdataset, link); + rctx->sigrdataset = NULL; + } + ISC_LIST_APPEND(rctx->namelist, ansname, link); + ansname = NULL; + + /* + * Copy the CNAME's target into the lookup's + * query name and start over. + */ + tresult = dns_rdataset_first(trdataset); + if (tresult != ISC_R_SUCCESS) { + goto done; + } + dns_rdataset_current(trdataset, &rdata); + tresult = dns_rdata_tostruct(&rdata, &cname, NULL); + dns_rdata_reset(&rdata); + if (tresult != ISC_R_SUCCESS) { + goto done; + } + dns_name_copynf(&cname.cname, name); + dns_rdata_freestruct(&cname); + want_restart = true; + goto done; + case DNS_R_DNAME: + /* + * Add the DNAME to the answer list. + */ + trdataset = rctx->rdataset; + ISC_LIST_APPEND(ansname->list, rctx->rdataset, link); + rctx->rdataset = NULL; + if (rctx->sigrdataset != NULL) { + ISC_LIST_APPEND(ansname->list, + rctx->sigrdataset, link); + rctx->sigrdataset = NULL; + } + ISC_LIST_APPEND(rctx->namelist, ansname, link); + ansname = NULL; + + namereln = dns_name_fullcompare(name, fname, &order, + &nlabels); + INSIST(namereln == dns_namereln_subdomain); + /* + * Get the target name of the DNAME. + */ + tresult = dns_rdataset_first(trdataset); + if (tresult != ISC_R_SUCCESS) { + result = tresult; + goto done; + } + dns_rdataset_current(trdataset, &rdata); + tresult = dns_rdata_tostruct(&rdata, &dname, NULL); + dns_rdata_reset(&rdata); + if (tresult != ISC_R_SUCCESS) { + result = tresult; + goto done; + } + /* + * Construct the new query name and start over. + */ + prefix = dns_fixedname_initname(&fixed); + dns_name_split(name, nlabels, prefix, NULL); + tresult = dns_name_concatenate(prefix, &dname.dname, + name, NULL); + dns_rdata_freestruct(&dname); + if (tresult == ISC_R_SUCCESS) { + want_restart = true; + } else { + result = tresult; + } + goto done; + case DNS_R_NCACHENXDOMAIN: + case DNS_R_NCACHENXRRSET: + ISC_LIST_APPEND(ansname->list, rctx->rdataset, link); + ISC_LIST_APPEND(rctx->namelist, ansname, link); + ansname = NULL; + rctx->rdataset = NULL; + /* What about sigrdataset? */ + if (rctx->sigrdataset != NULL) { + putrdataset(mctx, &rctx->sigrdataset); + } + send_event = true; + goto done; + default: + if (rctx->rdataset != NULL) { + putrdataset(mctx, &rctx->rdataset); + } + if (rctx->sigrdataset != NULL) { + putrdataset(mctx, &rctx->sigrdataset); + } + send_event = true; + goto done; + } + + if (rctx->type == dns_rdatatype_any) { + int n = 0; + dns_rdatasetiter_t *rdsiter = NULL; + + tresult = dns_db_allrdatasets(db, node, NULL, 0, 0, + &rdsiter); + if (tresult != ISC_R_SUCCESS) { + result = tresult; + goto done; + } + + tresult = dns_rdatasetiter_first(rdsiter); + while (tresult == ISC_R_SUCCESS) { + dns_rdatasetiter_current(rdsiter, + rctx->rdataset); + if (rctx->rdataset->type != 0) { + ISC_LIST_APPEND(ansname->list, + rctx->rdataset, link); + n++; + rctx->rdataset = NULL; + } else { + /* + * We're not interested in this + * rdataset. + */ + dns_rdataset_disassociate( + rctx->rdataset); + } + tresult = dns_rdatasetiter_next(rdsiter); + + if (tresult == ISC_R_SUCCESS && + rctx->rdataset == NULL) + { + tresult = getrdataset(mctx, + &rctx->rdataset); + if (tresult != ISC_R_SUCCESS) { + result = tresult; + POST(result); + break; + } + } + } + if (rctx->rdataset != NULL) { + putrdataset(mctx, &rctx->rdataset); + } + if (rctx->sigrdataset != NULL) { + putrdataset(mctx, &rctx->sigrdataset); + } + if (n == 0) { + /* + * We didn't match any rdatasets (which means + * something went wrong in this + * implementation). + */ + result = DNS_R_SERVFAIL; /* better code? */ + POST(result); + } else { + ISC_LIST_APPEND(rctx->namelist, ansname, link); + ansname = NULL; + } + dns_rdatasetiter_destroy(&rdsiter); + if (tresult != ISC_R_NOMORE) { + result = DNS_R_SERVFAIL; /* ditto */ + } else { + result = ISC_R_SUCCESS; + } + goto done; + } else { + /* + * This is the "normal" case -- an ordinary question + * to which we've got the answer. + */ + ISC_LIST_APPEND(ansname->list, rctx->rdataset, link); + rctx->rdataset = NULL; + if (rctx->sigrdataset != NULL) { + ISC_LIST_APPEND(ansname->list, + rctx->sigrdataset, link); + rctx->sigrdataset = NULL; + } + ISC_LIST_APPEND(rctx->namelist, ansname, link); + ansname = NULL; + } + + done: + /* + * Free temporary resources + */ + if (ansname != NULL) { + dns_rdataset_t *rdataset; + + while ((rdataset = ISC_LIST_HEAD(ansname->list)) != + NULL) + { + ISC_LIST_UNLINK(ansname->list, rdataset, link); + putrdataset(mctx, &rdataset); + } + dns_name_free(ansname, mctx); + isc_mem_put(mctx, ansname, sizeof(*ansname)); + } + + if (node != NULL) { + dns_db_detachnode(db, &node); + } + if (db != NULL) { + dns_db_detach(&db); + } + if (event != NULL) { + isc_event_free(ISC_EVENT_PTR(&event)); + } + + /* + * Limit the number of restarts. + */ + if (want_restart && rctx->restarts == MAX_RESTARTS) { + want_restart = false; + result = ISC_R_QUOTA; + send_event = true; + } + + /* + * Prepare further find with new resources + */ + if (want_restart) { + INSIST(rctx->rdataset == NULL && + rctx->sigrdataset == NULL); + + result = getrdataset(mctx, &rctx->rdataset); + if (result == ISC_R_SUCCESS && rctx->want_dnssec) { + result = getrdataset(mctx, &rctx->sigrdataset); + if (result != ISC_R_SUCCESS) { + putrdataset(mctx, &rctx->rdataset); + } + } + + if (result != ISC_R_SUCCESS) { + want_restart = false; + send_event = true; + } + } + } while (want_restart); + + if (send_event) { + isc_task_t *task; + + while ((name = ISC_LIST_HEAD(rctx->namelist)) != NULL) { + ISC_LIST_UNLINK(rctx->namelist, name, link); + ISC_LIST_APPEND(rctx->event->answerlist, name, link); + } + + rctx->event->result = result; + rctx->event->vresult = vresult; + task = rctx->event->ev_sender; + rctx->event->ev_sender = rctx; + isc_task_sendanddetach(&task, ISC_EVENT_PTR(&rctx->event)); + } + + UNLOCK(&rctx->lock); +} + +static void +suspend(isc_task_t *task, isc_event_t *event) { + isc_appctx_t *actx = event->ev_arg; + + UNUSED(task); + + isc_app_ctxsuspend(actx); + isc_event_free(&event); +} + +static void +resolve_done(isc_task_t *task, isc_event_t *event) { + resarg_t *resarg = event->ev_arg; + dns_clientresevent_t *rev = (dns_clientresevent_t *)event; + dns_name_t *name; + isc_result_t result; + + UNUSED(task); + + LOCK(&resarg->lock); + + resarg->result = rev->result; + resarg->vresult = rev->vresult; + while ((name = ISC_LIST_HEAD(rev->answerlist)) != NULL) { + ISC_LIST_UNLINK(rev->answerlist, name, link); + ISC_LIST_APPEND(*resarg->namelist, name, link); + } + + dns_client_destroyrestrans(&resarg->trans); + isc_event_free(&event); + + if (!resarg->canceled) { + UNLOCK(&resarg->lock); + + /* + * We may or may not be running. isc__appctx_onrun will + * fail if we are currently running otherwise we post a + * action to call isc_app_ctxsuspend when we do start + * running. + */ + result = isc_app_ctxonrun(resarg->actx, resarg->client->mctx, + task, suspend, resarg->actx); + if (result == ISC_R_ALREADYRUNNING) { + isc_app_ctxsuspend(resarg->actx); + } + } else { + /* + * We have already exited from the loop (due to some + * unexpected event). Just clean the arg up. + */ + UNLOCK(&resarg->lock); + isc_mutex_destroy(&resarg->lock); + isc_mem_put(resarg->client->mctx, resarg, sizeof(*resarg)); + } +} + +isc_result_t +dns_client_resolve(dns_client_t *client, const dns_name_t *name, + dns_rdataclass_t rdclass, dns_rdatatype_t type, + unsigned int options, dns_namelist_t *namelist) { + isc_result_t result; + resarg_t *resarg; + + REQUIRE(DNS_CLIENT_VALID(client)); + REQUIRE(client->actx != NULL); + REQUIRE(namelist != NULL && ISC_LIST_EMPTY(*namelist)); + + resarg = isc_mem_get(client->mctx, sizeof(*resarg)); + + *resarg = (resarg_t){ + .actx = client->actx, + .client = client, + .result = DNS_R_SERVFAIL, + .namelist = namelist, + }; + + isc_mutex_init(&resarg->lock); + + result = dns_client_startresolve(client, name, rdclass, type, options, + client->task, resolve_done, resarg, + &resarg->trans); + if (result != ISC_R_SUCCESS) { + isc_mutex_destroy(&resarg->lock); + isc_mem_put(client->mctx, resarg, sizeof(*resarg)); + return (result); + } + + /* + * Start internal event loop. It blocks until the entire process + * is completed. + */ + result = isc_app_ctxrun(client->actx); + + LOCK(&resarg->lock); + if (result == ISC_R_SUCCESS || result == ISC_R_SUSPEND) { + result = resarg->result; + } + if (result != ISC_R_SUCCESS && resarg->vresult != ISC_R_SUCCESS) { + /* + * If this lookup failed due to some error in DNSSEC + * validation, return the validation error code. + * XXX: or should we pass the validation result separately? + */ + result = resarg->vresult; + } + if (resarg->trans != NULL) { + /* + * Unusual termination (perhaps due to signal). We need some + * tricky cleanup process. + */ + resarg->canceled = true; + dns_client_cancelresolve(resarg->trans); + + UNLOCK(&resarg->lock); + + /* resarg will be freed in the event handler. */ + } else { + UNLOCK(&resarg->lock); + + isc_mutex_destroy(&resarg->lock); + isc_mem_put(client->mctx, resarg, sizeof(*resarg)); + } + + return (result); +} + +isc_result_t +dns_client_startresolve(dns_client_t *client, const dns_name_t *name, + dns_rdataclass_t rdclass, dns_rdatatype_t type, + unsigned int options, isc_task_t *task, + isc_taskaction_t action, void *arg, + dns_clientrestrans_t **transp) { + dns_view_t *view = NULL; + dns_clientresevent_t *event = NULL; + resctx_t *rctx = NULL; + isc_task_t *tclone = NULL; + isc_mem_t *mctx; + isc_result_t result; + dns_rdataset_t *rdataset, *sigrdataset; + bool want_dnssec, want_validation, want_cdflag, want_tcp; + + REQUIRE(DNS_CLIENT_VALID(client)); + REQUIRE(transp != NULL && *transp == NULL); + + LOCK(&client->lock); + result = dns_viewlist_find(&client->viewlist, DNS_CLIENTVIEW_NAME, + rdclass, &view); + UNLOCK(&client->lock); + if (result != ISC_R_SUCCESS) { + return (result); + } + + mctx = client->mctx; + rdataset = NULL; + sigrdataset = NULL; + want_dnssec = ((options & DNS_CLIENTRESOPT_NODNSSEC) == 0); + want_validation = ((options & DNS_CLIENTRESOPT_NOVALIDATE) == 0); + want_cdflag = ((options & DNS_CLIENTRESOPT_NOCDFLAG) == 0); + want_tcp = ((options & DNS_CLIENTRESOPT_TCP) != 0); + + /* + * Prepare some intermediate resources + */ + tclone = NULL; + isc_task_attach(task, &tclone); + event = (dns_clientresevent_t *)isc_event_allocate( + mctx, tclone, DNS_EVENT_CLIENTRESDONE, action, arg, + sizeof(*event)); + event->result = DNS_R_SERVFAIL; + ISC_LIST_INIT(event->answerlist); + + rctx = isc_mem_get(mctx, sizeof(*rctx)); + isc_mutex_init(&rctx->lock); + + result = getrdataset(mctx, &rdataset); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + rctx->rdataset = rdataset; + + if (want_dnssec) { + result = getrdataset(mctx, &sigrdataset); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + } + rctx->sigrdataset = sigrdataset; + + dns_fixedname_init(&rctx->name); + dns_name_copynf(name, dns_fixedname_name(&rctx->name)); + + rctx->client = client; + ISC_LINK_INIT(rctx, link); + rctx->canceled = false; + rctx->task = client->task; + rctx->type = type; + rctx->view = view; + rctx->restarts = 0; + rctx->fetch = NULL; + rctx->want_dnssec = want_dnssec; + rctx->want_validation = want_validation; + rctx->want_cdflag = want_cdflag; + rctx->want_tcp = want_tcp; + ISC_LIST_INIT(rctx->namelist); + rctx->event = event; + + rctx->magic = RCTX_MAGIC; + isc_refcount_increment(&client->references); + + LOCK(&client->lock); + ISC_LIST_APPEND(client->resctxs, rctx, link); + UNLOCK(&client->lock); + + *transp = (dns_clientrestrans_t *)rctx; + client_resfind(rctx, NULL); + + return (ISC_R_SUCCESS); + +cleanup: + if (rdataset != NULL) { + putrdataset(client->mctx, &rdataset); + } + if (sigrdataset != NULL) { + putrdataset(client->mctx, &sigrdataset); + } + isc_mutex_destroy(&rctx->lock); + isc_mem_put(mctx, rctx, sizeof(*rctx)); + isc_event_free(ISC_EVENT_PTR(&event)); + isc_task_detach(&tclone); + dns_view_detach(&view); + + return (result); +} + +void +dns_client_cancelresolve(dns_clientrestrans_t *trans) { + resctx_t *rctx; + + REQUIRE(trans != NULL); + rctx = (resctx_t *)trans; + REQUIRE(RCTX_VALID(rctx)); + + LOCK(&rctx->lock); + + if (!rctx->canceled) { + rctx->canceled = true; + if (rctx->fetch != NULL) { + dns_resolver_cancelfetch(rctx->fetch); + } + } + + UNLOCK(&rctx->lock); +} + +void +dns_client_freeresanswer(dns_client_t *client, dns_namelist_t *namelist) { + dns_name_t *name; + dns_rdataset_t *rdataset; + + REQUIRE(DNS_CLIENT_VALID(client)); + REQUIRE(namelist != NULL); + + while ((name = ISC_LIST_HEAD(*namelist)) != NULL) { + ISC_LIST_UNLINK(*namelist, name, link); + while ((rdataset = ISC_LIST_HEAD(name->list)) != NULL) { + ISC_LIST_UNLINK(name->list, rdataset, link); + putrdataset(client->mctx, &rdataset); + } + dns_name_free(name, client->mctx); + isc_mem_put(client->mctx, name, sizeof(*name)); + } +} + +void +dns_client_destroyrestrans(dns_clientrestrans_t **transp) { + resctx_t *rctx; + isc_mem_t *mctx; + dns_client_t *client; + + REQUIRE(transp != NULL); + rctx = (resctx_t *)*transp; + *transp = NULL; + REQUIRE(RCTX_VALID(rctx)); + REQUIRE(rctx->fetch == NULL); + REQUIRE(rctx->event == NULL); + client = rctx->client; + REQUIRE(DNS_CLIENT_VALID(client)); + + mctx = client->mctx; + dns_view_detach(&rctx->view); + + /* + * Wait for the lock in client_resfind to be released before + * destroying the lock. + */ + LOCK(&rctx->lock); + UNLOCK(&rctx->lock); + + LOCK(&client->lock); + + INSIST(ISC_LINK_LINKED(rctx, link)); + ISC_LIST_UNLINK(client->resctxs, rctx, link); + + UNLOCK(&client->lock); + + INSIST(ISC_LIST_EMPTY(rctx->namelist)); + + isc_mutex_destroy(&rctx->lock); + rctx->magic = 0; + + isc_mem_put(mctx, rctx, sizeof(*rctx)); + + dns_client_destroy(&client); +} + +isc_result_t +dns_client_addtrustedkey(dns_client_t *client, dns_rdataclass_t rdclass, + dns_rdatatype_t rdtype, const dns_name_t *keyname, + isc_buffer_t *databuf) { + isc_result_t result; + dns_view_t *view = NULL; + dns_keytable_t *secroots = NULL; + dns_name_t *name = NULL; + char rdatabuf[DST_KEY_MAXSIZE]; + unsigned char digest[ISC_MAX_MD_SIZE]; + dns_rdata_ds_t ds; + dns_decompress_t dctx; + dns_rdata_t rdata; + isc_buffer_t b; + + REQUIRE(DNS_CLIENT_VALID(client)); + + LOCK(&client->lock); + result = dns_viewlist_find(&client->viewlist, DNS_CLIENTVIEW_NAME, + rdclass, &view); + UNLOCK(&client->lock); + CHECK(result); + + CHECK(dns_view_getsecroots(view, &secroots)); + + DE_CONST(keyname, name); + + if (rdtype != dns_rdatatype_dnskey && rdtype != dns_rdatatype_ds) { + result = ISC_R_NOTIMPLEMENTED; + goto cleanup; + } + + isc_buffer_init(&b, rdatabuf, sizeof(rdatabuf)); + dns_decompress_init(&dctx, -1, DNS_DECOMPRESS_NONE); + dns_rdata_init(&rdata); + isc_buffer_setactive(databuf, isc_buffer_usedlength(databuf)); + CHECK(dns_rdata_fromwire(&rdata, rdclass, rdtype, databuf, &dctx, 0, + &b)); + dns_decompress_invalidate(&dctx); + + if (rdtype == dns_rdatatype_ds) { + CHECK(dns_rdata_tostruct(&rdata, &ds, NULL)); + } else { + CHECK(dns_ds_fromkeyrdata(name, &rdata, DNS_DSDIGEST_SHA256, + digest, &ds)); + } + + CHECK(dns_keytable_add(secroots, false, false, name, &ds)); + +cleanup: + if (view != NULL) { + dns_view_detach(&view); + } + if (secroots != NULL) { + dns_keytable_detach(&secroots); + } + return (result); +} |