client.c revision cec9fddbafee587975900475c677fbb48aea862f
/*
* Copyright (C) 2009-2016 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/.
*/
#include <config.h>
#include <stddef.h>
#include <isc/sockaddr.h>
#include <dns/dispatch.h>
#include <dns/keytable.h>
#include <dns/rdatalist.h>
#include <dns/rdataset.h>
#include <dns/rdatatype.h>
#include <dns/rdatasetiter.h>
#include <dns/rdatastruct.h>
#include <dns/resolver.h>
#define MAX_RESTARTS 16
#ifdef TUNE_LARGE
#define RESOLVER_NTASKS 523
#else
#define RESOLVER_NTASKS 31
#endif /* TUNE_LARGE */
/*%
* DNS client object
*/
struct dns_client {
/* Unlocked */
unsigned int magic;
unsigned int attributes;
unsigned int update_timeout;
unsigned int update_udptimeout;
unsigned int update_udpretries;
unsigned int find_timeout;
unsigned int find_udpretries;
/* Locked */
unsigned int references;
};
/*%
*/
#define DEF_UPDATE_TIMEOUT 300
#define MIN_UPDATE_TIMEOUT 30
#define DEF_UPDATE_UDPTIMEOUT 3
#define DEF_UPDATE_UDPRETRIES 3
#define DEF_FIND_TIMEOUT 5
#define DEF_FIND_UDPRETRIES 3
#define DNS_CLIENTATTR_OWNCTX 0x01
#define DNS_CLIENTVIEW_NAME "dnsclient"
/*%
* Internal state for a single name resolution procedure
*/
typedef struct resctx {
/* Unlocked */
unsigned int magic;
/* Locked */
unsigned int restarts;
} resctx_t;
/*%
* Argument of an internal event for synchronous name resolution.
*/
typedef struct resarg {
/* Unlocked */
/* Locked */
} resarg_t;
/*%
* Internal state for a single DNS request
*/
typedef struct reqctx {
/* Unlocked */
unsigned int magic;
unsigned int parseoptions;
/* Locked */
} reqctx_t;
/*%
* Argument of an internal event for synchronous DNS request.
*/
typedef struct reqarg {
/* Unlocked */
/* Locked */
} reqarg_t;
/*%
* Argument of an internal event for synchronous name resolution.
*/
typedef struct updatearg {
/* Unlocked */
/* Locked */
} updatearg_t;
/*%
* Internal state for a single dynamic update procedure
*/
typedef struct updatectx {
/* Unlocked */
unsigned int magic;
/* Locked */
/* Task Locked */
unsigned int nservers;
} updatectx_t;
static isc_result_t
{
attrs = 0;
switch (family) {
case AF_INET:
break;
case AF_INET6:
break;
default:
INSIST(0);
}
attrmask = 0;
}
buffersize = 4096;
maxrequests = 32768;
if (result == ISC_R_SUCCESS)
return (result);
}
static isc_result_t
dns_view_t **viewp)
{
const char *dbtype;
if (result != ISC_R_SUCCESS)
return (result);
/* Initialize view security roots */
if (result != ISC_R_SUCCESS) {
return (result);
}
if (result != ISC_R_SUCCESS) {
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";
if (result != ISC_R_SUCCESS) {
return (result);
}
return (ISC_R_SUCCESS);
}
#if 0
/* XXXMPA add debug logging support */
unsigned int logdebuglevel = 0;
#endif
if (result != ISC_R_SUCCESS)
return (result);
if (result != ISC_R_SUCCESS)
goto cleanup;
if (result != ISC_R_SUCCESS)
goto cleanup;
if (result != ISC_R_SUCCESS)
goto cleanup;
if (result != ISC_R_SUCCESS)
goto cleanup;
if (result != ISC_R_SUCCESS)
goto cleanup;
#if 0
if (result != ISC_R_SUCCESS)
goto cleanup;
if (result != ISC_R_SUCCESS)
goto cleanup;
#endif
if (result != ISC_R_SUCCESS)
goto cleanup;
/* client has its own reference to mctx, so we can detach it here */
return (ISC_R_SUCCESS);
return (result);
}
{
return (result);
}
{
return (ISC_R_NOMEMORY);
if (result != ISC_R_SUCCESS) {
return (result);
}
if (result != ISC_R_SUCCESS)
goto cleanup;
if (result != ISC_R_SUCCESS)
goto cleanup;
/*
* If only one address family is specified, use it.
* If neither family is specified, or if both are, use both.
*/
&dispatchv4, localaddr4);
if (result == ISC_R_SUCCESS)
}
&dispatchv6, localaddr6);
if (result == ISC_R_SUCCESS)
}
/* We need at least one of the dispatchers */
goto cleanup;
}
/* Create the default view for class IN */
if (result != ISC_R_SUCCESS)
goto cleanup;
client->attributes = 0;
return (ISC_R_SUCCESS);
if (dispatchv4 != NULL)
if (dispatchv6 != NULL)
if (dispatchmgr != NULL)
return (result);
}
static void
}
/*
* If the client has created its own running environments,
* destroy them.
*/
}
}
void
client->references--;
}
if (destroyok)
}
{
if (result != ISC_R_SUCCESS) {
return (result);
}
return (result);
}
{
if (result != ISC_R_SUCCESS) {
return (result);
}
return (result);
}
const char *dlvname)
{
isc_buffer_t b;
if (result != ISC_R_SUCCESS)
goto cleanup;
else {
if (result != ISC_R_SUCCESS)
goto cleanup;
}
return (result);
}
static isc_result_t
return (ISC_R_NOMEMORY);
return (ISC_R_SUCCESS);
}
static void
}
static void
}
static inline isc_result_t
int fopts = 0;
/*
* The caller must be holding the rctx's lock.
*/
if (!rctx->want_cdflag)
if (!rctx->want_validation)
return (result);
}
static isc_result_t
{
else
rctx->sigrdataset);
return (result);
}
static void
unsigned int nlabels;
int order;
do {
if (result == ISC_R_NOTFOUND) {
/*
* We don't know anything about the name.
* Launch a fetch.
*/
}
dns_db_detach(&db);
if (result != ISC_R_SUCCESS) {
&rctx->sigrdataset);
}
goto done;
}
} else {
}
/*
* If we've been canceled, forget about the result.
*/
else {
/*
* Otherwise, get some resource for copying the
* result.
*/
else {
if (tresult != ISC_R_SUCCESS)
sizeof(*ansname));
}
if (tresult != ISC_R_SUCCESS)
}
switch (result) {
case ISC_R_SUCCESS:
/*
* This case is handled in the main line below.
*/
break;
case DNS_R_CNAME:
/*
* Add the CNAME to the answer list.
*/
}
/*
* Copy the CNAME's target into the lookup's
* query name and start over.
*/
if (tresult != ISC_R_SUCCESS)
goto done;
if (tresult != ISC_R_SUCCESS)
goto done;
if (tresult == ISC_R_SUCCESS)
else
goto done;
case DNS_R_DNAME:
/*
* Add the DNAME to the answer list.
*/
}
&nlabels);
/*
* Get the target name of the DNAME.
*/
if (tresult != ISC_R_SUCCESS) {
goto done;
}
if (tresult != ISC_R_SUCCESS) {
goto done;
}
/*
* Construct the new query name and start over.
*/
if (tresult == ISC_R_SUCCESS)
else
goto done;
case DNS_R_NCACHENXDOMAIN:
case DNS_R_NCACHENXRRSET:
/* What about sigrdataset? */
goto done;
default:
goto done;
}
int n = 0;
&rdsiter);
if (tresult != ISC_R_SUCCESS) {
goto done;
}
while (tresult == ISC_R_SUCCESS) {
link);
n++;
} else {
/*
* We're not interested in this
* rdataset.
*/
}
if (tresult == ISC_R_SUCCESS &&
if (tresult != ISC_R_SUCCESS) {
break;
}
}
}
if (n == 0) {
/*
* We didn't match any rdatasets (which means
* something went wrong in this
* implementation).
*/
} else {
}
if (tresult != ISC_R_NOMORE)
else
goto done;
} else {
/*
* This is the "normal" case -- an ordinary question
* to which we've got the answer.
*/
}
}
done:
/*
* Free temporary resources
*/
!= NULL) {
}
}
dns_db_detach(&db);
/*
* Limit the number of restarts.
*/
}
/*
* Prepare further find with new resources
*/
if (want_restart) {
if (result != ISC_R_SUCCESS) {
}
}
if (result != ISC_R_SUCCESS) {
}
}
} while (want_restart);
if (send_event) {
}
}
}
static void
}
static void
}
/*
* 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.
*/
if (result == ISC_R_ALREADYRUNNING)
} else {
/*
* We have already exited from the loop (due to some
* unexpected event). Just clean the arg up.
*/
}
}
{
(options & DNS_CLIENTRESOPT_ALLOWRUN) == 0) {
/*
* If the client is run under application's control, we need
* to create a new running (sub)environment for this
* particular resolution.
*/
return (ISC_R_NOTIMPLEMENTED); /* XXXTBD */
} else
return (ISC_R_NOMEMORY);
if (result != ISC_R_SUCCESS) {
return (result);
}
if (result != ISC_R_SUCCESS) {
return (result);
}
/*
* Start internal event loop. It blocks until the entire process
* is completed.
*/
/*
* 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?
*/
}
/*
* Unusual termination (perhaps due to signal). We need some
* tricky cleanup process.
*/
/* resarg will be freed in the event handler. */
} else {
}
return (result);
}
{
if (result != ISC_R_SUCCESS)
return (result);
sigrdataset = NULL;
/*
* Prepare some intermediate resources
*/
event = (dns_clientresevent_t *)
goto cleanup;
}
else {
if (result != ISC_R_SUCCESS) {
}
}
if (result != ISC_R_SUCCESS)
goto cleanup;
if (result != ISC_R_SUCCESS)
goto cleanup;
if (want_dnssec) {
if (result != ISC_R_SUCCESS)
goto cleanup;
}
if (result != ISC_R_SUCCESS)
goto cleanup;
return (ISC_R_SUCCESS);
if (sigrdataset != NULL)
}
return (result);
}
void
}
}
void
}
}
}
void
/*
* Wait for the lock in client_resfind to be released before
* destroying the lock.
*/
if (need_destroyclient)
}
{
if (result != ISC_R_SUCCESS)
goto cleanup;
if (result != ISC_R_SUCCESS)
goto cleanup;
&dstkey);
if (result != ISC_R_SUCCESS)
goto cleanup;
return (result);
}
/*%
* Simple request routines
*/
static void
if (eresult == ISC_R_SUCCESS) {
ctx->parseoptions);
}
else
}
static void
/* Exit from the internal event loop */
} else {
/*
* We have already exited from the loop (due to some
* unexpected event). Just clean the arg up.
*/
}
}
unsigned int options, unsigned int parseoptions,
unsigned int udptimeout, unsigned int udpretries)
{
(options & DNS_CLIENTREQOPT_ALLOWRUN) == 0) {
/*
* If the client is run under application's control, we need
* to create a new running (sub)environment for this
* particular resolution.
*/
return (ISC_R_NOTIMPLEMENTED); /* XXXTBD */
} else
return (ISC_R_NOMEMORY);
if (result != ISC_R_SUCCESS) {
return (result);
}
if (result != ISC_R_SUCCESS) {
return (result);
}
/*
* Start internal event loop. It blocks until the entire process
* is completed.
*/
/*
* Unusual termination (perhaps due to signal). We need some
* tricky cleanup process.
*/
/* reqarg will be freed in the event handler. */
} else {
}
return (result);
}
unsigned int options, unsigned int parseoptions,
unsigned int udptimeout, unsigned int udpretries,
{
unsigned int reqoptions;
if (tsectype != dns_tsectype_tsig)
return (ISC_R_NOTIMPLEMENTED); /* XXX */
}
if (result != ISC_R_SUCCESS)
return (result);
reqoptions = 0;
if ((options & DNS_CLIENTREQOPT_TCP) != 0)
event = (dns_clientreqevent_t *)
goto cleanup;
}
else {
if (result != ISC_R_SUCCESS) {
}
}
if (result != ISC_R_SUCCESS)
goto cleanup;
if (result == ISC_R_SUCCESS) {
return (ISC_R_SUCCESS);
}
}
return (result);
}
void
}
}
void
}
if (need_destroyclient)
}
/*%
* Dynamic update routines
*/
static isc_result_t
/* XXX: isn't there a similar function? */
switch (rcode) {
case dns_rcode_formerr:
return (DNS_R_FORMERR);
case dns_rcode_servfail:
return (DNS_R_SERVFAIL);
case dns_rcode_nxdomain:
return (DNS_R_NXDOMAIN);
case dns_rcode_notimp:
return (DNS_R_NOTIMP);
case dns_rcode_refused:
return (DNS_R_REFUSED);
case dns_rcode_yxdomain:
return (DNS_R_YXDOMAIN);
case dns_rcode_yxrrset:
return (DNS_R_YXRRSET);
case dns_rcode_nxrrset:
return (DNS_R_NXRRSET);
case dns_rcode_notauth:
return (DNS_R_NOTAUTH);
case dns_rcode_notzone:
return (DNS_R_NOTZONE);
case dns_rcode_badvers:
return (DNS_R_BADVERS);
}
return (ISC_R_FAILURE);
}
static void
else
}
static void
unsigned int timeout, reqoptions;
if (result != ISC_R_SUCCESS)
goto out;
&answer);
if (result != ISC_R_SUCCESS)
goto out;
out:
/*
* Moving on to the next server shouldn't change the result
* for NXDOMAIN, YXDOMAIN, NXRRSET and YXRRSET as they
* indicate a prerequisite failure. REFUSED should also
* be consistent across all servers but often isn't as that
* is policy rather that zone content driven (slaves that
* aren't willing to forward should return NOTIMPL). NOTZONE
* indicates that we stuffed up the request construction so
* don't retry.
*/
{
if (timeout < MIN_UPDATE_TIMEOUT)
reqoptions = 0;
NULL,
if (result == ISC_R_SUCCESS) {
/* XXX: should we keep the 'done' state here? */
return;
}
} else
}
static isc_result_t
unsigned int timeout, reqoptions;
if (result != ISC_R_SUCCESS)
return (result);
if (result != ISC_R_SUCCESS) {
return (result);
}
if (result != ISC_R_SUCCESS)
return (result);
}
if (timeout < MIN_UPDATE_TIMEOUT)
reqoptions = 0;
if (result == ISC_R_SUCCESS &&
}
return (result);
}
static void
int family;
} else {
}
if (result != ISC_R_SUCCESS)
goto done;
continue;
continue;
result == ISC_R_SUCCESS;
sizeof(*sa));
/*
* If we fail to get a sockaddr,
we simply move forward with the
* addresses we've got so far.
*/
goto done;
}
switch (family) {
case AF_INET:
NULL);
53);
break;
case AF_INET6:
NULL);
53);
break;
}
}
}
}
done:
if (completed) {
else {
if (result == ISC_R_SUCCESS)
}
}
}
static isc_result_t
unsigned int resoptions;
if (result != ISC_R_SUCCESS)
return (result);
if (result != ISC_R_SUCCESS)
return (result);
if (result != ISC_R_SUCCESS)
goto out;
}
else {
/*
* Get addresses of the primary server. We don't use the ADB
* feature so that we could avoid caching data.
*/
resoptions = 0;
if (result == ISC_R_SUCCESS) {
&primary,
}
}
out:
return (result);
}
static void
int pass = 0;
unsigned int nlabels, reqoptions;
if (eresult != ISC_R_SUCCESS) {
goto out;
}
if (result != ISC_R_SUCCESS)
goto out;
if (result == DNS_R_TSIGERRORSET) {
/* Retry SOA request without TSIG */
reqoptions = 0;
&newrequest);
if (result == ISC_R_SUCCESS) {
return;
}
goto out;
}
goto out;
}
if (pass == 0)
else if (pass == 1)
else {
goto out;
}
if (result != ISC_R_SUCCESS) {
pass++;
goto lookforsoa;
}
while (result == ISC_R_SUCCESS) {
&soaset);
if (result == ISC_R_SUCCESS)
break;
if (section == DNS_SECTION_ANSWER) {
&tset) == ISC_R_SUCCESS
||
&tset) == ISC_R_SUCCESS
)
{
break;
}
}
}
pass++;
goto lookforsoa;
}
if (seencname) {
goto out;
}
out:
if (droplabel) {
if (nlabels == 1)
else {
&tname);
reqoptions = 0;
20,
}
}
}
if (result != ISC_R_SUCCESS)
}
static isc_result_t
unsigned int reqoptions;
&soaquery);
if (result != ISC_R_SUCCESS)
return (result);
}
if (result != ISC_R_SUCCESS)
goto fail;
if (result != ISC_R_SUCCESS)
goto fail;
reqoptions = 0;
if (result == ISC_R_SUCCESS) {
return (ISC_R_SUCCESS);
}
fail:
}
return (result);
}
static void
unsigned int nlabels, resoptions;
if (result != ISC_R_SUCCESS &&
result != DNS_R_NCACHENXDOMAIN &&
result != DNS_R_NCACHENXRRSET) {
/* XXX: what about DNSSEC failure? */
goto out;
}
if (dns_rdataset_isassociated(rdataset) &&
break;
}
}
/* Drop one label and retry resolution. */
if (nlabels == 1) {
goto out;
}
&tname);
resoptions = 0;
} else
out:
if (result != ISC_R_SUCCESS)
}
static isc_result_t
{
isc_region_t r;
if (result != ISC_R_SUCCESS)
return (result);
if (result != ISC_R_SUCCESS)
goto fail;
if (result != ISC_R_SUCCESS)
goto fail;
if (result != ISC_R_SUCCESS)
goto fail;
while (result == ISC_R_SUCCESS) {
if (result != ISC_R_SUCCESS)
goto fail;
dns_rdata_toregion(&rdata, &r);
r.length);
if (result != ISC_R_SUCCESS)
goto fail;
}
newrdataset = NULL;
if (result != ISC_R_SUCCESS)
goto fail;
}
return (ISC_R_SUCCESS);
fail:
return (result);
}
static void
/* Exit from the internal event loop */
} else {
/*
* We have already exited from the loop (due to some
* unexpected event). Just clean the arg up.
*/
}
}
{
(options & DNS_CLIENTUPDOPT_ALLOWRUN) == 0) {
/*
* If the client is run under application's control, we need
* to create a new running (sub)environment for this
* particular update.
*/
return (ISC_R_NOTIMPLEMENTED); /* XXXTBD */
} else
return (ISC_R_NOMEMORY);
if (result != ISC_R_SUCCESS) {
return (result);
}
if (result != ISC_R_SUCCESS) {
return (result);
}
/*
* Start internal event loop. It blocks until the entire process
* is completed.
*/
/*
* Unusual termination (perhaps due to signal). We need some
* tricky cleanup process.
*/
/* uarg will be freed in the event handler. */
} else {
}
return (result);
}
static void
unsigned int resoptions;
if (result != ISC_R_SUCCESS)
goto fail;
if (result != ISC_R_SUCCESS)
goto fail;
} else {
resoptions = 0;
if (result != ISC_R_SUCCESS)
goto fail;
}
fail:
if (result != ISC_R_SUCCESS)
}
{
if (tsectype != dns_tsectype_tsig)
return (ISC_R_NOTIMPLEMENTED); /* XXX */
}
if (result != ISC_R_SUCCESS)
return (result);
/*
* Create a context and prepare some resources.
*/
return (ISC_R_NOMEMORY);
}
uctx, sizeof(*runinevent));
if (runinevent == NULL) {
return (ISC_R_NOMEMORY);
}
if (result != ISC_R_SUCCESS) {
return (ISC_R_NOMEMORY);
}
goto fail;
}
goto fail;
}
}
/* Make update message */
if (result != ISC_R_SUCCESS)
goto fail;
if (prerequisites != NULL) {
if (result != ISC_R_SUCCESS)
goto fail;
}
}
&newname);
if (result != ISC_R_SUCCESS)
goto fail;
}
if (result == ISC_R_NOMORE) {
}
if (result != ISC_R_SUCCESS)
goto fail;
return (ISC_R_SUCCESS);
fail:
if (runinevent != NULL)
}
}
return (result);
}
void
}
}
void
}
if (need_destroyclient)
}
}
typedef struct {
unsigned char data[FLEXIBLE_ARRAY_MEMBER];
{
if (op == updateop_add)
op == updateop_exist);
}
return (ISC_R_NOMEMORY);
(unsigned int)
(size -
isc_region_t r;
dns_rdata_toregion(rdata, &r);
}
switch (op) {
case updateop_add:
break;
case updateop_delete:
ttl = 0;
} else
break;
case updateop_notexist:
break;
case updateop_exist:
ttl = 0;
}
case updateop_none:
break;
default:
INSIST(0);
}
}
}
else
return (ISC_R_SUCCESS);
}
void
continue;
}
}
}
}