client.c revision c1aef54e14bb92518b1c062ba8c0292a7cb949cb
/*
* Copyright (C) 2009-2011 Internet Systems Consortium, Inc. ("ISC")
*
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
/* $Id: client.c,v 1.14 2011/03/12 04:59:47 tbox Exp $ */
#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
/*%
* 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);
}
timermgr, 0, dispatchmgr,
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 (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 (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 (ISC_R_NOMEMORY);
if (result != ISC_R_SUCCESS) {
return (result);
}
if (result != ISC_R_SUCCESS)
goto cleanup;
if (result != ISC_R_SUCCESS)
goto cleanup;
/* TODO: whether to use dispatch v4 or v6 should be configurable */
if (result == ISC_R_SUCCESS)
if (result == ISC_R_SUCCESS)
/* We need at least one of the dispatchers */
goto cleanup;
}
/* Create the default view for class IN */
&view);
if (result != ISC_R_SUCCESS)
goto cleanup;
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);
}
static isc_result_t
return (ISC_R_NOMEMORY);
return (ISC_R_SUCCESS);
}
static void
}
static void
}
static inline isc_result_t
/*
* The caller must be holding the rctx's lock.
*/
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
}
/* 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_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
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,
{
if (tsectype != dns_tsectype_tsig)
return (ISC_R_NOTIMPLEMENTED); /* XXX */
}
if (result != ISC_R_SUCCESS)
return (result);
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;
if (result != ISC_R_SUCCESS)
goto out;
&answer);
if (result != ISC_R_SUCCESS)
goto out;
out:
if (timeout < MIN_UPDATE_TIMEOUT)
NULL,
uctx->currentserver, 0,
if (result == ISC_R_SUCCESS) {
/* XXX: should we keep the 'done' state here? */
return;
}
} else
}
static isc_result_t
unsigned int timeout;
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)
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
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.
*/
if (result == ISC_R_SUCCESS) {
&primary,
}
}
out:
return (result);
}
static void
int pass = 0;
unsigned int nlabels;
if (eresult != ISC_R_SUCCESS) {
goto out;
}
if (result != ISC_R_SUCCESS)
goto out;
if (result == DNS_R_TSIGERRORSET) {
/* Retry SOA request without TSIG */
NULL,
&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);
uctx->currentserver, 0,
20,
}
}
}
if (result != ISC_R_SUCCESS)
}
static isc_result_t
&soaquery);
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) {
return (ISC_R_SUCCESS);
}
fail:
}
return (result);
}
static void
unsigned int nlabels;
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);
} 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_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.
*/
/*
* Unusual termination (perhaps due to signal). We need some
* tricky cleanup process.
*/
/* uarg will be freed in the event handler. */
} else {
}
return (result);
}
{
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);
}
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;
if (result != ISC_R_SUCCESS)
goto fail;
if (result != ISC_R_SUCCESS)
goto fail;
} else {
if (result != ISC_R_SUCCESS)
goto fail;
}
return (ISC_R_SUCCESS);
fail:
}
}
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);
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;
}
}
}
}