1080 lines
26 KiB
C
1080 lines
26 KiB
C
/*
|
|
* 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/async.h>
|
|
#include <isc/buffer.h>
|
|
#include <isc/counter.h>
|
|
#include <isc/loop.h>
|
|
#include <isc/md.h>
|
|
#include <isc/mem.h>
|
|
#include <isc/mutex.h>
|
|
#include <isc/netmgr.h>
|
|
#include <isc/portset.h>
|
|
#include <isc/refcount.h>
|
|
#include <isc/result.h>
|
|
#include <isc/safe.h>
|
|
#include <isc/sockaddr.h>
|
|
#include <isc/util.h>
|
|
|
|
#include <dns/adb.h>
|
|
#include <dns/client.h>
|
|
#include <dns/db.h>
|
|
#include <dns/dispatch.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/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 CHECK(r) \
|
|
do { \
|
|
result = (r); \
|
|
if (result != ISC_R_SUCCESS) \
|
|
goto cleanup; \
|
|
} while (0)
|
|
|
|
/*%
|
|
* DNS client object
|
|
*/
|
|
struct dns_client {
|
|
unsigned int magic;
|
|
unsigned int attributes;
|
|
isc_mem_t *mctx;
|
|
isc_loop_t *loop;
|
|
isc_nm_t *nm;
|
|
dns_dispatchmgr_t *dispatchmgr;
|
|
dns_dispatch_t *dispatchv4;
|
|
dns_dispatch_t *dispatchv6;
|
|
|
|
unsigned int find_timeout;
|
|
unsigned int find_udpretries;
|
|
uint8_t max_restarts;
|
|
uint8_t max_queries;
|
|
|
|
isc_refcount_t references;
|
|
|
|
dns_view_t *view;
|
|
ISC_LIST(struct resctx) resctxs;
|
|
};
|
|
|
|
#define DEF_FIND_TIMEOUT 5
|
|
#define DEF_FIND_UDPRETRIES 3
|
|
#define DEF_MAX_RESTARTS 11
|
|
#define DEF_MAX_QUERIES 200
|
|
|
|
/*%
|
|
* Internal state for a single name resolution procedure
|
|
*/
|
|
typedef struct resctx {
|
|
unsigned int magic;
|
|
dns_client_t *client;
|
|
bool want_dnssec;
|
|
bool want_validation;
|
|
bool want_cdflag;
|
|
bool want_tcp;
|
|
|
|
ISC_LINK(struct resctx) link;
|
|
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;
|
|
isc_counter_t *qc;
|
|
dns_clientresume_t *rev;
|
|
dns_rdataset_t *rdataset;
|
|
dns_rdataset_t *sigrdataset;
|
|
} resctx_t;
|
|
|
|
/*%
|
|
* Argument of an internal event for synchronous name resolution.
|
|
*/
|
|
typedef struct resarg {
|
|
isc_mem_t *mctx;
|
|
dns_client_t *client;
|
|
const dns_name_t *name;
|
|
|
|
isc_result_t result;
|
|
isc_result_t vresult;
|
|
dns_namelist_t *namelist;
|
|
dns_clientrestrans_t *trans;
|
|
dns_client_resolve_cb resolve_cb;
|
|
} resarg_t;
|
|
|
|
static void
|
|
client_resfind(resctx_t *rctx, dns_fetchresponse_t *event);
|
|
static void
|
|
destroyrestrans(dns_clientrestrans_t **transp);
|
|
|
|
/*
|
|
* 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,
|
|
dns_dispatch_t **dispp, const isc_sockaddr_t *localaddr) {
|
|
dns_dispatch_t *disp = NULL;
|
|
isc_result_t result;
|
|
isc_sockaddr_t anyaddr;
|
|
|
|
if (localaddr == NULL) {
|
|
isc_sockaddr_anyofpf(&anyaddr, family);
|
|
localaddr = &anyaddr;
|
|
}
|
|
|
|
result = dns_dispatch_createudp(dispatchmgr, localaddr, &disp);
|
|
if (result == ISC_R_SUCCESS) {
|
|
*dispp = disp;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static isc_result_t
|
|
createview(isc_mem_t *mctx, dns_rdataclass_t rdclass, isc_nm_t *nm,
|
|
isc_tlsctx_cache_t *tlsctx_client_cache, isc_loopmgr_t *loopmgr,
|
|
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;
|
|
|
|
result = dns_view_create(mctx, loopmgr, dispatchmgr, rdclass,
|
|
DNS_CLIENTVIEW_NAME, &view);
|
|
if (result != ISC_R_SUCCESS) {
|
|
return result;
|
|
}
|
|
|
|
/* Initialize view security roots */
|
|
dns_view_initsecroots(view);
|
|
|
|
CHECK(dns_view_createresolver(view, nm, 0, tlsctx_client_cache,
|
|
dispatchv4, dispatchv6));
|
|
CHECK(dns_db_create(mctx, CACHEDB_DEFAULT, dns_rootname,
|
|
dns_dbtype_cache, rdclass, 0, NULL,
|
|
&view->cachedb));
|
|
|
|
*viewp = view;
|
|
return ISC_R_SUCCESS;
|
|
|
|
cleanup:
|
|
dns_view_detach(&view);
|
|
return result;
|
|
}
|
|
|
|
isc_result_t
|
|
dns_client_create(isc_mem_t *mctx, isc_loopmgr_t *loopmgr, isc_nm_t *nm,
|
|
unsigned int options, isc_tlsctx_cache_t *tlsctx_client_cache,
|
|
dns_client_t **clientp, const isc_sockaddr_t *localaddr4,
|
|
const isc_sockaddr_t *localaddr6) {
|
|
isc_result_t result;
|
|
dns_client_t *client = NULL;
|
|
dns_dispatch_t *dispatchv4 = NULL;
|
|
dns_dispatch_t *dispatchv6 = NULL;
|
|
dns_view_t *view = NULL;
|
|
|
|
REQUIRE(mctx != NULL);
|
|
REQUIRE(nm != NULL);
|
|
REQUIRE(tlsctx_client_cache != NULL);
|
|
REQUIRE(clientp != NULL && *clientp == NULL);
|
|
|
|
UNUSED(options);
|
|
|
|
client = isc_mem_get(mctx, sizeof(*client));
|
|
*client = (dns_client_t){
|
|
.loop = isc_loop_get(loopmgr, 0),
|
|
.nm = nm,
|
|
.max_restarts = DEF_MAX_RESTARTS,
|
|
.max_queries = DEF_MAX_QUERIES,
|
|
};
|
|
|
|
result = dns_dispatchmgr_create(mctx, loopmgr, nm,
|
|
&client->dispatchmgr);
|
|
if (result != ISC_R_SUCCESS) {
|
|
goto cleanup_client;
|
|
}
|
|
(void)setsourceports(mctx, client->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, client->dispatchmgr,
|
|
&dispatchv4, localaddr4);
|
|
if (result == ISC_R_SUCCESS) {
|
|
client->dispatchv4 = dispatchv4;
|
|
}
|
|
}
|
|
|
|
client->dispatchv6 = NULL;
|
|
if (localaddr6 != NULL || localaddr4 == NULL) {
|
|
result = getudpdispatch(AF_INET6, client->dispatchmgr,
|
|
&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, nm, tlsctx_client_cache,
|
|
isc_loop_getloopmgr(client->loop),
|
|
client->dispatchmgr, dispatchv4, dispatchv6, &view);
|
|
if (result != ISC_R_SUCCESS) {
|
|
goto cleanup_references;
|
|
}
|
|
|
|
client->view = view;
|
|
|
|
dns_view_freeze(view); /* too early? */
|
|
|
|
ISC_LIST_INIT(client->resctxs);
|
|
|
|
isc_mem_attach(mctx, &client->mctx);
|
|
|
|
client->find_timeout = DEF_FIND_TIMEOUT;
|
|
client->find_udpretries = DEF_FIND_UDPRETRIES;
|
|
|
|
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_detach(&client->dispatchmgr);
|
|
cleanup_client:
|
|
isc_mem_put(mctx, client, sizeof(*client));
|
|
|
|
return result;
|
|
}
|
|
|
|
static void
|
|
destroyclient(dns_client_t *client) {
|
|
isc_refcount_destroy(&client->references);
|
|
|
|
dns_view_detach(&client->view);
|
|
|
|
if (client->dispatchv4 != NULL) {
|
|
dns_dispatch_detach(&client->dispatchv4);
|
|
}
|
|
if (client->dispatchv6 != NULL) {
|
|
dns_dispatch_detach(&client->dispatchv6);
|
|
}
|
|
|
|
dns_dispatchmgr_detach(&client->dispatchmgr);
|
|
|
|
client->magic = 0;
|
|
|
|
isc_mem_putanddetach(&client->mctx, client, sizeof(*client));
|
|
}
|
|
|
|
void
|
|
dns_client_detach(dns_client_t **clientp) {
|
|
dns_client_t *client = NULL;
|
|
|
|
REQUIRE(clientp != NULL);
|
|
REQUIRE(DNS_CLIENT_VALID(*clientp));
|
|
|
|
client = *clientp;
|
|
*clientp = NULL;
|
|
|
|
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;
|
|
|
|
REQUIRE(DNS_CLIENT_VALID(client));
|
|
REQUIRE(addrs != NULL);
|
|
REQUIRE(rdclass == dns_rdataclass_in);
|
|
|
|
if (name_space == NULL) {
|
|
name_space = dns_rootname;
|
|
}
|
|
|
|
result = dns_fwdtable_add(client->view->fwdtable, name_space, addrs,
|
|
dns_fwdpolicy_only);
|
|
|
|
return result;
|
|
}
|
|
|
|
void
|
|
dns_client_setmaxrestarts(dns_client_t *client, uint8_t max_restarts) {
|
|
REQUIRE(DNS_CLIENT_VALID(client));
|
|
REQUIRE(max_restarts > 0);
|
|
|
|
client->max_restarts = max_restarts;
|
|
}
|
|
|
|
void
|
|
dns_client_setmaxqueries(dns_client_t *client, uint8_t max_queries) {
|
|
REQUIRE(DNS_CLIENT_VALID(client));
|
|
REQUIRE(max_queries > 0);
|
|
|
|
client->max_queries = max_queries;
|
|
}
|
|
|
|
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(void *arg) {
|
|
dns_fetchresponse_t *resp = (dns_fetchresponse_t *)arg;
|
|
resctx_t *rctx = resp->arg;
|
|
|
|
REQUIRE(RCTX_VALID(rctx));
|
|
|
|
client_resfind(rctx, resp);
|
|
}
|
|
|
|
static isc_result_t
|
|
start_fetch(resctx_t *rctx) {
|
|
isc_result_t result;
|
|
int fopts = 0;
|
|
|
|
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->qc,
|
|
rctx->client->loop, fetch_done, rctx, NULL, 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_fetchresponse_t *resp) {
|
|
isc_mem_t *mctx = NULL;
|
|
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 = NULL, *prefix = NULL;
|
|
dns_fixedname_t foundname, fixed;
|
|
dns_rdataset_t *trdataset = NULL;
|
|
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));
|
|
|
|
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 (resp == NULL) {
|
|
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(resp != NULL);
|
|
INSIST(resp->fetch == rctx->fetch);
|
|
dns_resolver_destroyfetch(&rctx->fetch);
|
|
db = resp->db;
|
|
node = resp->node;
|
|
result = resp->result;
|
|
vresult = resp->vresult;
|
|
fname = resp->foundname;
|
|
INSIST(resp->rdataset == rctx->rdataset);
|
|
INSIST(resp->sigrdataset == rctx->sigrdataset);
|
|
dns_resolver_freefresp(&resp);
|
|
}
|
|
|
|
/*
|
|
* 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_copy(&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);
|
|
}
|
|
|
|
/*
|
|
* Limit the number of restarts.
|
|
*/
|
|
if (want_restart &&
|
|
rctx->restarts == rctx->client->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) {
|
|
while ((name = ISC_LIST_HEAD(rctx->namelist)) != NULL) {
|
|
ISC_LIST_UNLINK(rctx->namelist, name, link);
|
|
ISC_LIST_APPEND(rctx->rev->answerlist, name, link);
|
|
}
|
|
|
|
rctx->rev->result = result;
|
|
rctx->rev->vresult = vresult;
|
|
isc_async_run(rctx->client->loop, rctx->rev->cb, rctx->rev);
|
|
}
|
|
}
|
|
|
|
static void
|
|
resolve_done(void *arg) {
|
|
dns_clientresume_t *rev = (dns_clientresume_t *)arg;
|
|
resarg_t *resarg = rev->arg;
|
|
dns_name_t *name = NULL;
|
|
isc_result_t result;
|
|
|
|
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);
|
|
}
|
|
|
|
isc_mem_put(resarg->mctx, rev, sizeof(*rev));
|
|
destroyrestrans(&resarg->trans);
|
|
|
|
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;
|
|
}
|
|
|
|
resarg->resolve_cb(resarg->client, resarg->name, resarg->namelist,
|
|
result);
|
|
|
|
dns_client_detach(&resarg->client);
|
|
|
|
isc_mem_putanddetach(&resarg->mctx, resarg, sizeof(*resarg));
|
|
}
|
|
|
|
static isc_result_t
|
|
startresolve(dns_client_t *client, const dns_name_t *name,
|
|
dns_rdataclass_t rdclass, dns_rdatatype_t type,
|
|
unsigned int options, isc_job_cb cb, void *arg,
|
|
dns_clientrestrans_t **transp) {
|
|
dns_clientresume_t *rev = NULL;
|
|
resctx_t *rctx = NULL;
|
|
isc_mem_t *mctx = NULL;
|
|
isc_result_t result;
|
|
dns_rdataset_t *rdataset = NULL, *sigrdataset = NULL;
|
|
bool want_dnssec, want_validation, want_cdflag, want_tcp;
|
|
|
|
REQUIRE(DNS_CLIENT_VALID(client));
|
|
REQUIRE(transp != NULL && *transp == NULL);
|
|
REQUIRE(rdclass == dns_rdataclass_in);
|
|
|
|
mctx = client->mctx;
|
|
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
|
|
*/
|
|
rev = isc_mem_get(mctx, sizeof(*rev));
|
|
*rev = (dns_clientresume_t){
|
|
.result = DNS_R_SERVFAIL,
|
|
.answerlist = ISC_LIST_INITIALIZER,
|
|
.cb = cb,
|
|
.arg = arg,
|
|
};
|
|
|
|
rctx = isc_mem_get(mctx, sizeof(*rctx));
|
|
*rctx = (resctx_t){
|
|
.client = client,
|
|
.rev = rev,
|
|
.type = type,
|
|
.want_dnssec = want_dnssec,
|
|
.want_validation = want_validation,
|
|
.want_cdflag = want_cdflag,
|
|
.want_tcp = want_tcp,
|
|
.namelist = ISC_LIST_INITIALIZER,
|
|
.link = ISC_LINK_INITIALIZER,
|
|
};
|
|
|
|
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_copy(name, dns_fixedname_name(&rctx->name));
|
|
|
|
dns_view_attach(client->view, &rctx->view);
|
|
|
|
rctx->magic = RCTX_MAGIC;
|
|
isc_refcount_increment(&client->references);
|
|
|
|
result = isc_counter_create(mctx, client->max_queries, &rctx->qc);
|
|
if (result != ISC_R_SUCCESS) {
|
|
goto cleanup;
|
|
}
|
|
|
|
ISC_LIST_APPEND(client->resctxs, rctx, link);
|
|
|
|
*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);
|
|
}
|
|
if (rctx->qc != NULL) {
|
|
isc_counter_detach(&rctx->qc);
|
|
}
|
|
isc_mem_put(mctx, rctx, sizeof(*rctx));
|
|
isc_mem_put(mctx, rev, sizeof(*rev));
|
|
|
|
return result;
|
|
}
|
|
|
|
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,
|
|
dns_client_resolve_cb resolve_cb) {
|
|
isc_result_t result;
|
|
resarg_t *resarg = NULL;
|
|
|
|
REQUIRE(DNS_CLIENT_VALID(client));
|
|
REQUIRE(namelist != NULL && ISC_LIST_EMPTY(*namelist));
|
|
REQUIRE(rdclass == dns_rdataclass_in);
|
|
|
|
resarg = isc_mem_get(client->mctx, sizeof(*resarg));
|
|
|
|
*resarg = (resarg_t){
|
|
.client = client,
|
|
.name = name,
|
|
.result = DNS_R_SERVFAIL,
|
|
.namelist = namelist,
|
|
.resolve_cb = resolve_cb,
|
|
};
|
|
|
|
isc_mem_attach(client->mctx, &resarg->mctx);
|
|
|
|
result = startresolve(client, name, rdclass, type, options,
|
|
resolve_done, resarg, &resarg->trans);
|
|
if (result != ISC_R_SUCCESS) {
|
|
isc_mem_put(client->mctx, resarg, sizeof(*resarg));
|
|
return result;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
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));
|
|
}
|
|
}
|
|
|
|
/*%
|
|
* Destroy name resolution transaction state identified by '*transp'.
|
|
*
|
|
* The caller must have received the CLIENTRESDONE event (either because the
|
|
* resolution completed or because cancelresolve() was called).
|
|
*/
|
|
static void
|
|
destroyrestrans(dns_clientrestrans_t **transp) {
|
|
resctx_t *rctx = NULL;
|
|
isc_mem_t *mctx = NULL;
|
|
dns_client_t *client = NULL;
|
|
|
|
REQUIRE(transp != NULL);
|
|
|
|
rctx = (resctx_t *)*transp;
|
|
*transp = NULL;
|
|
|
|
REQUIRE(RCTX_VALID(rctx));
|
|
REQUIRE(rctx->fetch == NULL);
|
|
|
|
client = rctx->client;
|
|
|
|
REQUIRE(DNS_CLIENT_VALID(client));
|
|
|
|
mctx = client->mctx;
|
|
dns_view_detach(&rctx->view);
|
|
|
|
INSIST(ISC_LINK_LINKED(rctx, link));
|
|
ISC_LIST_UNLINK(client->resctxs, rctx, link);
|
|
|
|
INSIST(ISC_LIST_EMPTY(rctx->namelist));
|
|
|
|
rctx->magic = 0;
|
|
|
|
if (rctx->qc != NULL) {
|
|
isc_counter_detach(&rctx->qc);
|
|
}
|
|
isc_mem_put(mctx, rctx, sizeof(*rctx));
|
|
}
|
|
|
|
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) {
|
|
REQUIRE(DNS_CLIENT_VALID(client));
|
|
REQUIRE(rdclass == dns_rdataclass_in);
|
|
|
|
return dns_view_addtrustedkey(client->view, rdtype, keyname, databuf);
|
|
}
|