diff options
Diffstat (limited to '')
-rw-r--r-- | lib/dns/lookup.c | 491 |
1 files changed, 491 insertions, 0 deletions
diff --git a/lib/dns/lookup.c b/lib/dns/lookup.c new file mode 100644 index 0000000..acd9c34 --- /dev/null +++ b/lib/dns/lookup.c @@ -0,0 +1,491 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * 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 http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + + +/*! \file */ + +#include <config.h> + +#include <stdbool.h> + +#include <isc/mem.h> +#include <isc/netaddr.h> +#include <isc/string.h> /* Required for HP/UX (and others?) */ +#include <isc/task.h> +#include <isc/util.h> + +#include <dns/db.h> +#include <dns/events.h> +#include <dns/lookup.h> +#include <dns/rdata.h> +#include <dns/rdataset.h> +#include <dns/rdatastruct.h> +#include <dns/resolver.h> +#include <dns/result.h> +#include <dns/view.h> + +struct dns_lookup { + /* Unlocked. */ + unsigned int magic; + isc_mem_t * mctx; + isc_mutex_t lock; + dns_rdatatype_t type; + dns_fixedname_t name; + /* Locked by lock. */ + unsigned int options; + isc_task_t * task; + dns_view_t * view; + dns_lookupevent_t * event; + dns_fetch_t * fetch; + unsigned int restarts; + bool canceled; + dns_rdataset_t rdataset; + dns_rdataset_t sigrdataset; +}; + +#define LOOKUP_MAGIC ISC_MAGIC('l', 'o', 'o', 'k') +#define VALID_LOOKUP(l) ISC_MAGIC_VALID((l), LOOKUP_MAGIC) + +#define MAX_RESTARTS 16 + +static void lookup_find(dns_lookup_t *lookup, dns_fetchevent_t *event); + +static void +fetch_done(isc_task_t *task, isc_event_t *event) { + dns_lookup_t *lookup = event->ev_arg; + dns_fetchevent_t *fevent; + + UNUSED(task); + REQUIRE(event->ev_type == DNS_EVENT_FETCHDONE); + REQUIRE(VALID_LOOKUP(lookup)); + REQUIRE(lookup->task == task); + fevent = (dns_fetchevent_t *)event; + REQUIRE(fevent->fetch == lookup->fetch); + + lookup_find(lookup, fevent); +} + +static inline isc_result_t +start_fetch(dns_lookup_t *lookup) { + isc_result_t result; + + /* + * The caller must be holding the lookup's lock. + */ + + REQUIRE(lookup->fetch == NULL); + + result = dns_resolver_createfetch(lookup->view->resolver, + dns_fixedname_name(&lookup->name), + lookup->type, + NULL, NULL, NULL, 0, + lookup->task, fetch_done, lookup, + &lookup->rdataset, + &lookup->sigrdataset, + &lookup->fetch); + + return (result); +} + +static isc_result_t +build_event(dns_lookup_t *lookup) { + dns_name_t *name = NULL; + dns_rdataset_t *rdataset = NULL; + dns_rdataset_t *sigrdataset = NULL; + isc_result_t result; + + name = isc_mem_get(lookup->mctx, sizeof(dns_name_t)); + if (name == NULL) { + result = ISC_R_NOMEMORY; + goto fail; + } + dns_name_init(name, NULL); + result = dns_name_dup(dns_fixedname_name(&lookup->name), + lookup->mctx, name); + if (result != ISC_R_SUCCESS) + goto fail; + + if (dns_rdataset_isassociated(&lookup->rdataset)) { + rdataset = isc_mem_get(lookup->mctx, sizeof(dns_rdataset_t)); + if (rdataset == NULL) { + result = ISC_R_NOMEMORY; + goto fail; + } + dns_rdataset_init(rdataset); + dns_rdataset_clone(&lookup->rdataset, rdataset); + } + + if (dns_rdataset_isassociated(&lookup->sigrdataset)) { + sigrdataset = isc_mem_get(lookup->mctx, + sizeof(dns_rdataset_t)); + if (sigrdataset == NULL) { + result = ISC_R_NOMEMORY; + goto fail; + } + dns_rdataset_init(sigrdataset); + dns_rdataset_clone(&lookup->sigrdataset, sigrdataset); + } + + lookup->event->name = name; + lookup->event->rdataset = rdataset; + lookup->event->sigrdataset = sigrdataset; + + return (ISC_R_SUCCESS); + + fail: + if (name != NULL) { + if (dns_name_dynamic(name)) + dns_name_free(name, lookup->mctx); + isc_mem_put(lookup->mctx, name, sizeof(dns_name_t)); + } + if (rdataset != NULL) { + if (dns_rdataset_isassociated(rdataset)) + dns_rdataset_disassociate(rdataset); + isc_mem_put(lookup->mctx, rdataset, sizeof(dns_rdataset_t)); + } + return (result); +} + +static isc_result_t +view_find(dns_lookup_t *lookup, dns_name_t *foundname) { + isc_result_t result; + dns_name_t *name = dns_fixedname_name(&lookup->name); + dns_rdatatype_t type; + + if (lookup->type == dns_rdatatype_rrsig) + type = dns_rdatatype_any; + else + type = lookup->type; + + result = dns_view_find(lookup->view, name, type, 0, 0, false, + &lookup->event->db, &lookup->event->node, + foundname, &lookup->rdataset, + &lookup->sigrdataset); + return (result); +} + +static void +lookup_find(dns_lookup_t *lookup, dns_fetchevent_t *event) { + isc_result_t result; + bool want_restart; + bool send_event; + dns_name_t *name, *fname, *prefix; + dns_fixedname_t foundname, fixed; + 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(VALID_LOOKUP(lookup)); + + LOCK(&lookup->lock); + + result = ISC_R_SUCCESS; + name = dns_fixedname_name(&lookup->name); + + do { + lookup->restarts++; + want_restart = false; + send_event = true; + + if (event == NULL && !lookup->canceled) { + fname = dns_fixedname_initname(&foundname); + INSIST(!dns_rdataset_isassociated(&lookup->rdataset)); + INSIST(!dns_rdataset_isassociated + (&lookup->sigrdataset)); + /* + * If we have restarted then clear the old node. */ + if (lookup->event->node != NULL) { + INSIST(lookup->event->db != NULL); + dns_db_detachnode(lookup->event->db, + &lookup->event->node); + } + if (lookup->event->db != NULL) + dns_db_detach(&lookup->event->db); + result = view_find(lookup, fname); + if (result == ISC_R_NOTFOUND) { + /* + * We don't know anything about the name. + * Launch a fetch. + */ + if (lookup->event->node != NULL) { + INSIST(lookup->event->db != NULL); + dns_db_detachnode(lookup->event->db, + &lookup->event->node); + } + if (lookup->event->db != NULL) + dns_db_detach(&lookup->event->db); + result = start_fetch(lookup); + if (result == ISC_R_SUCCESS) + send_event = false; + goto done; + } + } else if (event != NULL) { + result = event->result; + fname = dns_fixedname_name(&event->foundname); + dns_resolver_destroyfetch(&lookup->fetch); + INSIST(event->rdataset == &lookup->rdataset); + INSIST(event->sigrdataset == &lookup->sigrdataset); + } else + fname = NULL; /* Silence compiler warning. */ + + /* + * If we've been canceled, forget about the result. + */ + if (lookup->canceled) + result = ISC_R_CANCELED; + + switch (result) { + case ISC_R_SUCCESS: + result = build_event(lookup); + if (event == NULL) + break; + if (event->db != NULL) + dns_db_attach(event->db, &lookup->event->db); + if (event->node != NULL) + dns_db_attachnode(lookup->event->db, + event->node, + &lookup->event->node); + break; + case DNS_R_CNAME: + /* + * Copy the CNAME's target into the lookup's + * query name and start over. + */ + result = dns_rdataset_first(&lookup->rdataset); + if (result != ISC_R_SUCCESS) + break; + dns_rdataset_current(&lookup->rdataset, &rdata); + result = dns_rdata_tostruct(&rdata, &cname, NULL); + dns_rdata_reset(&rdata); + if (result != ISC_R_SUCCESS) + break; + result = dns_name_copy(&cname.cname, name, NULL); + dns_rdata_freestruct(&cname); + if (result == ISC_R_SUCCESS) { + want_restart = true; + send_event = false; + } + break; + case DNS_R_DNAME: + namereln = dns_name_fullcompare(name, fname, &order, + &nlabels); + INSIST(namereln == dns_namereln_subdomain); + /* + * Get the target name of the DNAME. + */ + result = dns_rdataset_first(&lookup->rdataset); + if (result != ISC_R_SUCCESS) + break; + dns_rdataset_current(&lookup->rdataset, &rdata); + result = dns_rdata_tostruct(&rdata, &dname, NULL); + dns_rdata_reset(&rdata); + if (result != ISC_R_SUCCESS) + break; + /* + * Construct the new query name and start over. + */ + prefix = dns_fixedname_initname(&fixed); + dns_name_split(name, nlabels, prefix, NULL); + result = dns_name_concatenate(prefix, &dname.dname, + name, NULL); + dns_rdata_freestruct(&dname); + if (result == ISC_R_SUCCESS) { + want_restart = true; + send_event = false; + } + break; + default: + send_event = true; + } + + if (dns_rdataset_isassociated(&lookup->rdataset)) + dns_rdataset_disassociate(&lookup->rdataset); + if (dns_rdataset_isassociated(&lookup->sigrdataset)) + dns_rdataset_disassociate(&lookup->sigrdataset); + + done: + if (event != NULL) { + if (event->node != NULL) + dns_db_detachnode(event->db, &event->node); + if (event->db != NULL) + dns_db_detach(&event->db); + isc_event_free(ISC_EVENT_PTR(&event)); + } + + /* + * Limit the number of restarts. + */ + if (want_restart && lookup->restarts == MAX_RESTARTS) { + want_restart = false; + result = ISC_R_QUOTA; + send_event = true; + } + + } while (want_restart); + + if (send_event) { + lookup->event->result = result; + lookup->event->ev_sender = lookup; + isc_task_sendanddetach(&lookup->task, + (isc_event_t **)&lookup->event); + dns_view_detach(&lookup->view); + } + + UNLOCK(&lookup->lock); +} + +static void +levent_destroy(isc_event_t *event) { + dns_lookupevent_t *levent; + isc_mem_t *mctx; + + REQUIRE(event->ev_type == DNS_EVENT_LOOKUPDONE); + mctx = event->ev_destroy_arg; + levent = (dns_lookupevent_t *)event; + + if (levent->name != NULL) { + if (dns_name_dynamic(levent->name)) + dns_name_free(levent->name, mctx); + isc_mem_put(mctx, levent->name, sizeof(dns_name_t)); + } + if (levent->rdataset != NULL) { + dns_rdataset_disassociate(levent->rdataset); + isc_mem_put(mctx, levent->rdataset, sizeof(dns_rdataset_t)); + } + if (levent->sigrdataset != NULL) { + dns_rdataset_disassociate(levent->sigrdataset); + isc_mem_put(mctx, levent->sigrdataset, sizeof(dns_rdataset_t)); + } + if (levent->node != NULL) + dns_db_detachnode(levent->db, &levent->node); + if (levent->db != NULL) + dns_db_detach(&levent->db); + isc_mem_put(mctx, event, event->ev_size); +} + +isc_result_t +dns_lookup_create(isc_mem_t *mctx, dns_name_t *name, dns_rdatatype_t type, + dns_view_t *view, unsigned int options, isc_task_t *task, + isc_taskaction_t action, void *arg, dns_lookup_t **lookupp) +{ + isc_result_t result; + dns_lookup_t *lookup; + isc_event_t *ievent; + + lookup = isc_mem_get(mctx, sizeof(*lookup)); + if (lookup == NULL) + return (ISC_R_NOMEMORY); + lookup->mctx = NULL; + isc_mem_attach(mctx, &lookup->mctx); + lookup->options = options; + + ievent = isc_event_allocate(mctx, lookup, DNS_EVENT_LOOKUPDONE, + action, arg, sizeof(*lookup->event)); + if (ievent == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup_lookup; + } + lookup->event = (dns_lookupevent_t *)ievent; + lookup->event->ev_destroy = levent_destroy; + lookup->event->ev_destroy_arg = mctx; + lookup->event->result = ISC_R_FAILURE; + lookup->event->name = NULL; + lookup->event->rdataset = NULL; + lookup->event->sigrdataset = NULL; + lookup->event->db = NULL; + lookup->event->node = NULL; + + lookup->task = NULL; + isc_task_attach(task, &lookup->task); + + result = isc_mutex_init(&lookup->lock); + if (result != ISC_R_SUCCESS) + goto cleanup_event; + + dns_fixedname_init(&lookup->name); + + result = dns_name_copy(name, dns_fixedname_name(&lookup->name), NULL); + if (result != ISC_R_SUCCESS) + goto cleanup_lock; + + lookup->type = type; + lookup->view = NULL; + dns_view_attach(view, &lookup->view); + lookup->fetch = NULL; + lookup->restarts = 0; + lookup->canceled = false; + dns_rdataset_init(&lookup->rdataset); + dns_rdataset_init(&lookup->sigrdataset); + lookup->magic = LOOKUP_MAGIC; + + *lookupp = lookup; + + lookup_find(lookup, NULL); + + return (ISC_R_SUCCESS); + + cleanup_lock: + DESTROYLOCK(&lookup->lock); + + cleanup_event: + ievent = (isc_event_t *)lookup->event; + isc_event_free(&ievent); + lookup->event = NULL; + + isc_task_detach(&lookup->task); + + cleanup_lookup: + isc_mem_putanddetach(&mctx, lookup, sizeof(*lookup)); + + return (result); +} + +void +dns_lookup_cancel(dns_lookup_t *lookup) { + REQUIRE(VALID_LOOKUP(lookup)); + + LOCK(&lookup->lock); + + if (!lookup->canceled) { + lookup->canceled = true; + if (lookup->fetch != NULL) { + INSIST(lookup->view != NULL); + dns_resolver_cancelfetch(lookup->fetch); + } + } + + UNLOCK(&lookup->lock); +} + +void +dns_lookup_destroy(dns_lookup_t **lookupp) { + dns_lookup_t *lookup; + + REQUIRE(lookupp != NULL); + lookup = *lookupp; + REQUIRE(VALID_LOOKUP(lookup)); + REQUIRE(lookup->event == NULL); + REQUIRE(lookup->task == NULL); + REQUIRE(lookup->view == NULL); + if (dns_rdataset_isassociated(&lookup->rdataset)) + dns_rdataset_disassociate(&lookup->rdataset); + if (dns_rdataset_isassociated(&lookup->sigrdataset)) + dns_rdataset_disassociate(&lookup->sigrdataset); + + DESTROYLOCK(&lookup->lock); + lookup->magic = 0; + isc_mem_putanddetach(&lookup->mctx, lookup, sizeof(*lookup)); + + *lookupp = NULL; +} |