resolver.c revision eba3ad47e72ecc9682fe3596c820919311ed2c80
/*
* Copyright (C) 1999 Internet Software Consortium.
*
* Permission to use, copy, modify, and distribute this software for any
* 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 INTERNET SOFTWARE CONSORTIUM DISCLAIMS
* ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE
* CONSORTIUM 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.
*/
#include <config.h>
#include <isc/assertions.h>
#include <dns/dispatch.h>
#include <dns/resolver.h>
#include <dns/rdataset.h>
#define DNS_RESOLVER_TRACE
#ifdef DNS_RESOLVER_TRACE
#else
#define RTRACE(m)
#define RRTRACE(r, m)
#define FCTXTRACE(m)
#define FTRACE(m)
#define QTRACE(m)
#endif
typedef struct fetchctx fetchctx_t;
typedef struct query {
/* Not locked. */
unsigned int magic;
fetchctx_t * fctx;
/* Locked by fctx lock. */
unsigned char data[512];
} resquery_t;
typedef enum {
fetchstate_init = 0,
} fetchstate;
struct fetchctx {
/* Not locked. */
unsigned int magic;
unsigned int options;
isc_task_t * task;
unsigned int bucketnum;
/* Locked by lock. */
unsigned int references;
/* Only changable by event actions running in the context's task */
isc_timer_t * timer;
};
struct dns_fetch {
unsigned int magic;
void * private;
};
typedef struct fctxbucket {
isc_task_t * task;
} fctxbucket_t;
struct dns_resolver {
/* Unlocked */
unsigned int magic;
dns_view_t * view;
/* Locked by lock. */
unsigned int references;
unsigned int nbuckets;
unsigned int activebuckets;
};
/*
* Internal fetch routines. Caller must be holding the proper lock.
*/
static inline isc_result_t
ISC_FALSE));
}
static inline void
/*
* We don't return a result if resetting the timer to inactive fails
* since there's nothing to be done about it. Resetting to inactive
* should never fail anyway, since the code as currently written
* cannot fail in that case.
*/
if (result != ISC_R_SUCCESS) {
"isc_timer_reset(): %s",
}
}
static inline void
FCTXTRACE("cancelquery");
/*
* XXXRTH I don't think that dns_dispatch_removeresponse() will
* reclaim events posted to this task. What do we do
* about this? Doing what we're doing now is bad, because
* we're destroying the query while there may be outstanding
* references to it.
*/
deventp);
}
static void
FCTXTRACE("cancelqueries");
query = next_query) {
}
}
static void
FCTXTRACE("done");
/* XXXRTH Free our scaffolding addresses. */
{
address = next_address) {
}
}
event = next_event) {
if (result != ISC_R_SUCCESS)
}
/*
* XXXRTH check for finished state.
*/
}
static void
/*
* XXXRTH
*
* Currently we don't wait for the senddone event before retrying
* a query. This means that if we get really behind, we may end
* up doing extra work!
*/
(void)task;
QTRACE("senddone");
}
static isc_result_t
isc_region_t r;
FCTXTRACE("sendquery");
if (result != ISC_R_SUCCESS)
return (result);
if (result != ISC_R_SUCCESS)
goto cleanup_temps;
if (result != ISC_R_SUCCESS)
goto cleanup_temps;
return (ISC_R_NOMEMORY);
/*
* If this is a TCP query, then we need to make a socket and
* a dispatch for it here. Otherwise we use the resolver's
* shared dispatch. We do not attach to the resolver's shared
* dispatch if we use it, so the resolver MUST ensure that no
* fetches are running before changing the shared dispatch.
*/
/* XXXRTH */
goto cleanup_query;
} else {
switch (isc_sockaddr_pf(address)) {
case AF_INET:
break;
case AF_INET6:
break;
default:
goto cleanup_query;
}
/*
* We should always have a valid dispatcher here. If we
* don't support a protocol family, then its dispatcher
* will be NULL, but we shouldn't be finding addresses for
* protocol types we don't support, so the dispatcher
* we found should never be NULL.
*/
}
/*
* Get a query id from the dispatch.
*/
task,
if (result != ISC_R_SUCCESS)
goto cleanup_query;
/*
* Set up question.
*/
/*
* We don't have to set opcode because it defaults to query.
*/
/*
* recipient.
*/
/*
* Convert the question to wire format.
*/
if (result != ISC_R_SUCCESS)
goto cleanup_message;
DNS_SECTION_QUESTION, 0, 0);
if (result != ISC_R_SUCCESS)
goto cleanup_message;
DNS_SECTION_ADDITIONAL, 0, 0);
if (result != ISC_R_SUCCESS)
goto cleanup_message;
if (result != ISC_R_SUCCESS)
goto cleanup_message;
}
/*
* We're now done with the query message.
*/
/*
* Send the query!
*/
&r, task, query_senddone,
if (result != ISC_R_SUCCESS)
goto cleanup_message;
/*
* Finally, we've got everything going!
*/
QTRACE("sent");
return (ISC_R_SUCCESS);
/*
* Stop the dispatcher from listening.
*/
NULL);
/*
* XXXRTH will need to cleanup a nonshared dispatch and TCP socket
* here.
*/
return (result);
}
static isc_result_t
isc_region_t r;
FCTXTRACE("getaddresses");
/*
* XXXRTH We don't try to handle forwarding yet.
*/
/*
* XXXRTH This code is a temporary hack until we have working
* address code.
*/
while (result == ISC_R_SUCCESS) {
dns_rdata_toregion(&rdata, &r);
dns_name_fromregion(&name, &r);
if (result == ISC_R_SUCCESS ||
result == DNS_R_GLUE ||
result == DNS_R_HINT) {
while (result == ISC_R_SUCCESS) {
sizeof *address);
break;
}
link);
}
}
}
if (result == DNS_R_NOMORE)
return (result);
}
static void
/*
* Caller must be holding the fetch's lock.
*/
FCTXTRACE("try");
if (result != ISC_R_SUCCESS) {
return;
}
}
/*
* XXXRTH No addresses are available...
*/
INSIST(0);
}
/*
* XXXRTH This is the place where a try strategy routine would
* be called to send one or more queries. Instead, we
* just send a single query.
*/
if (result != ISC_R_SUCCESS)
}
static isc_boolean_t
unsigned int bucketnum;
/*
* Caller must be holding the bucket lock.
*/
FCTXTRACE("destroy");
}
return (ISC_TRUE);
return (ISC_FALSE);
}
/*
* Fetch event handlers.
*/
static void
(void)task; /* Keep compiler quiet. */
FCTXTRACE("timeout");
} else {
/*
* We could cancel the running queries here, or we could let
* them keep going. Right now we choose the latter...
*/
}
}
static void
unsigned int bucketnum;
(void)task; /* Keep compiler quiet. */
FCTXTRACE("shutdown");
if (fctx->references == 0)
} else
if (need_done)
else if (bucket_empty)
}
static void
unsigned int bucketnum;
(void)task; /* Keep compiler quiet. */
FCTXTRACE("start");
/*
* We haven't started this fctx yet, and we've been requested
* to shut it down.
*
* The events list should be empty, so we INSIST on it.
*/
} else {
/*
* Normal fctx startup.
*/
/*
* Reset the control event for later use in shutting down
* the fctx.
*/
}
if (!done) {
/*
* All is well. Start working on the fetch.
*/
} else if (bucket_empty)
}
/*
* Fetch Creation, Joining, and Cancelation.
*/
static inline isc_result_t
{
FCTXTRACE("join");
/*
* We store the task we're going to send this event to in the
* sender field. We'll make the fetch the sender when we actually
* send the event.
*/
event = (dns_fetchevent_t *)
return (ISC_R_NOMEMORY);
fctx->references++;
return (ISC_R_SUCCESS);
}
static isc_result_t
{
/*
* Caller must be holding the lock for bucket number 'bucketnum'.
*/
return (ISC_R_NOMEMORY);
FCTXTRACE("create");
if (result != ISC_R_SUCCESS)
goto cleanup_fetch;
if (result != ISC_R_SUCCESS)
goto cleanup_name;
}
/*
* Note! We do not attach to the task. We are relying on the
* resolver to ensure that this task doesn't go away while we are
* using it.
*/
fctx->references = 0;
if (result != ISC_R_SUCCESS)
goto cleanup_domain;
if (result != ISC_R_SUCCESS)
goto cleanup_qmessage;
/*
* Compute an expiration time for the entire fetch.
*/
if (iresult != ISC_R_SUCCESS) {
"isc_time_nowplusinterval: %s",
goto cleanup_rmessage;
}
/*
* XXX Retry interval initialization. Should be setup by the
* transmission strategy routine (when we have one).
*/
/*
* Create an inactive timer. It will be made active when the fetch
* is actually started.
*/
if (iresult != ISC_R_SUCCESS) {
"isc_timer_create: %s",
goto cleanup_rmessage;
}
return (ISC_R_SUCCESS);
}
return (result);
}
/*
* XXXRTH Cleanup
*/
#ifdef obsolete
static void
FCTXTRACE("cancel");
if (iresult != ISC_R_SUCCESS)
"isc_timer_reset(): %s",
}
}
#endif
/*
* Handle Responses
*/
static inline isc_result_t
/*
* Caller must be holding the fctx lock.
*/
/*
* XXXRTH Currently we support only one question.
*/
return (DNS_R_FORMERR);
if (result != ISC_R_SUCCESS)
return (result);
return (DNS_R_FORMERR);
return (ISC_R_SUCCESS);
}
static inline isc_result_t
void *data;
/*
* The appropriate bucket lock must be held.
*/
/*
* Is DNSSEC validation required for this name?
*/
/*
* This name is at or below one of the view's security roots,
* so DNSSEC validation is required.
*/
} else if (result != ISC_R_NOTFOUND) {
/*
* Something bad happened.
*/
return (result);
}
asigrdataset = NULL;
if (result != ISC_R_SUCCESS)
return (result);
}
}
}
/*
* Find or create the cache node.
*/
&node);
if (result != ISC_R_SUCCESS)
return (result);
/*
* Cache or validate each cacheable rdataset.
*/
continue;
if (need_validation) {
INSIST(0);
/*
* It's OK to cache this rdataset now.
*/
else
if (result != ISC_R_SUCCESS)
break;
}
}
if (result == ISC_R_SUCCESS) {
}
/*
* XXXRTH clone rdatasets to other events.
*/
} else
return (result);
}
static inline isc_result_t
if (result != ISC_R_SUCCESS)
return (result);
for (section = DNS_SECTION_ANSWER;
section++) {
while (result == ISC_R_SUCCESS) {
&name);
if (result != ISC_R_SUCCESS)
break;
}
}
if (result != ISC_R_NOMORE)
break;
}
if (result == ISC_R_NOMORE)
return (result);
}
static inline void
{
if (external)
}
static isc_result_t
NULL);
if (result == ISC_R_SUCCESS) {
if (type == dns_rdatatype_a) {
else
if (rtype == dns_rdatatype_a ||
rtype == dns_rdatatype_aaaa ||
/*
* XXXRTH Need to do a controlled recursion
* on the A6 prefix names to mark
* any additional data related to them.
*
* Ick.
*/
}
} else {
&rdataset);
if (result == ISC_R_SUCCESS) {
/*
* Do we have its SIG too?
*/
if (result == ISC_R_SUCCESS)
}
}
/*
* XXXRTH Some other stuff still needs to be marked.
* See query.c.
*/
}
return (ISC_R_SUCCESS);
}
static inline isc_result_t
unsigned int aflag;
/*
* Examine the answer section, marking those rdatasets which are
* part of the answer and should be cached.
*/
else
/*
* XXXRTH CNAME check.
*/
#if 0
if (cname) {
}
#endif
aflag = 0;
/*
* We've found an ordinary answer.
*/
/*
* We've found a signature that
* covers the type we're looking for.
*/
}
if (found) {
/*
* We've found an answer to our
* question.
*/
name->attributes |=
rdataset->attributes |=
if (!chaining) {
/*
* This data is "the" answer
* to our question only if
* we're not chaining (i.e.
* if we haven't followed
* a CNAME or DNAME).
*/
name->attributes |=
if (aa)
} else if (external) {
/*
* This data is outside of
* our query domain, and
* may only be cached if it
* comes from a secure zone
* and validates.
*/
rdataset->attributes |=
}
/*
* Mark any additional data related
* to this rdataset.
*/
(void)dns_rdataset_additionaldata(
fctx);
/*
* A6 special cases...
*/
}
}
/*
* We could add an "else" clause here and
* log that we're ignoring this rdataset.
*/
}
} else {
/*
* Either this is a DNAME or we've got junk.
*/
}
}
if (result != ISC_R_NOMORE)
return (result);
/*
* Examine the authority section (if there is one).
*
* We expect there to be only one owner name for all the rdatasets
* in this section, and we expect that it is not external.
*/
if (!external) {
/*
* We expect to find NS or SIG NS rdatasets, and
* nothing else.
*/
rdataset->attributes |=
if (aa)
else
/*
* Mark any additional data related
* to this rdataset.
*/
(void)dns_rdataset_additionaldata(
fctx);
}
}
/*
* Since we've found a non-external name in the
* authority section, we should stop looking, even
* if we didn't find any NS or SIG NS.
*/
}
}
if (result != ISC_R_NOMORE)
return (result);
return (ISC_R_SUCCESS);
}
#if 0
static inline isc_result_t
unsigned int aflag;
/*
* We have to figure out if this is a negative response, or a
* referral. We start by examining the rcode.
*/
else
return (DNS_R_FORMERR);
&name);
/*
* The only valid records are CNAME, DNAME, and
* their corresponding sigs.
*/
/*
* XXXRTH CNAME check.
*/
} else {
/*
* XXXRTH DNAME check.
*/
}
}
if (result != ISC_R_NOMORE)
return (result);
}
/*
* XXXRTH Authority section.
*/
return (ISC_R_SUCCESS);
}
#endif
static void
(void)task;
QTRACE("response");
if (result != ISC_R_SUCCESS) {
switch (result) {
case DNS_R_FORMERR:
case DNS_R_UNEXPECTEDEND:
break;
case DNS_R_MOREDATA:
break;
}
goto done;
}
/*
* INSIST() that the message comes from the place we sent it to,
* since the dispatch code should ensure this.
*
* INSIST() that the message id is correct (this should also be
* ensured by the dispatch code).
*/
/*
* Is it a query response?
*/
/* XXXRTH Log */
goto done;
}
/*
* Is the remote server broken, or does it dislike us?
*/
/*
* XXXRTH If we want to catch a FORMERR caused by an EDNS0
* OPT RR, this is the place to do it.
*/
goto done;
}
/*
* Is the question the same as the one we asked?
*/
if (result != ISC_R_SUCCESS) {
/* XXXRTH Log */
if (result == DNS_R_FORMERR) {
}
goto done;
}
/*
* Did we get any answers?
*/
/*
* We've got answers.
*/
if (result != ISC_R_SUCCESS) {
if (result == DNS_R_FORMERR)
goto done;
}
/*
* NXDOMAIN, NXRDATASET, or referral.
*/
#if 0
#else
#endif
if (result != ISC_R_SUCCESS) {
if (result == DNS_R_FORMERR)
goto done;
}
} else {
/*
* The server is insane.
*/
/* XXXRTH Log */
goto done;
}
done:
/*
* XXXRTH Record round-trip statistics here.
*/
if (keep_trying) {
/*
* XXXRTH We will mark the sender as bad here instead
* of doing the printf().
*/
if (broken_server)
printf("broken sender\n");
&devent);
/*
* Keep trying.
*/
} else {
/*
* All is well, or we got an error fatal to the fetch.
* In either case, we're done.
*/
}
}
/***
*** Resolver Methods
***/
static void
unsigned int i;
RTRACE("destroy");
}
}
static void
RTRACE("empty_bucket");
res->activebuckets--;
if (res->activebuckets == 0)
if (need_destroy)
}
{
unsigned int i, buckets_created = 0;
return (ISC_R_NOMEMORY);
RTRACE("create");
ntasks * sizeof (fctxbucket_t));
goto cleanup_res;
}
for (i = 0; i < ntasks; i++) {
if (result != ISC_R_SUCCESS)
goto cleanup_buckets;
if (result != ISC_R_SUCCESS) {
goto cleanup_buckets;
}
}
/*
* IPv4 Dispatcher.
*/
} else if (isc_net_probeipv4() == ISC_R_SUCCESS) {
/*
* Create an IPv4 UDP socket and a dispatcher for it.
*/
&res->udpsocket4);
if (result != ISC_R_SUCCESS)
goto cleanup_buckets;
/*
* XXXRTH Temporarily bind() to 5353 to make things
* easier for Bob's firewalls.
*/
if (result != ISC_R_SUCCESS)
goto cleanup_buckets;
if (result != ISC_R_SUCCESS)
goto cleanup_udpsocket4;
}
/*
* IPv6 Dispatcher.
*/
if (isc_net_probeipv6() == ISC_R_SUCCESS) {
/*
* Create an IPv6 UDP socket and a dispatcher for it.
*/
&res->udpsocket6);
if (result != ISC_R_SUCCESS)
goto cleanup_dispatch4;
if (result != ISC_R_SUCCESS)
goto cleanup_udpsocket6;
}
if (result != ISC_R_SUCCESS)
goto cleanup_dispatch6;
return (ISC_R_SUCCESS);
for (i = 0; i < buckets_created; i++) {
}
return (result);
}
void
source->references++;
}
void
unsigned int i;
RTRACE("detach");
res->references--;
if (res->references == 0) {
RTRACE("exiting");
/*
* XXXRTH Post shutdown events?
*/
res->activebuckets--;
}
}
if (res->activebuckets == 0)
}
if (need_destroy)
}
static inline isc_boolean_t
unsigned int options)
{
return (ISC_FALSE);
}
/*
* XXXRTH This routine takes an unconscionable number of arguments!
*
* Maybe caller should allocate an event and pass that in? Something must
* be done!
*/
{
unsigned int bucketnum;
(void)forwarders;
RTRACE("createfetch");
/* XXXRTH */
if ((options & DNS_FETCHOPT_TCP) != 0)
return (DNS_R_NOTIMPLEMENTED);
/*
* XXXRTH use a mempool?
*/
return (ISC_R_NOMEMORY);
goto unlock;
}
if ((options & DNS_FETCHOPT_UNSHARED) == 0) {
break;
}
}
if (result != ISC_R_SUCCESS)
goto unlock;
}
if (new_fctx) {
if (result == ISC_R_SUCCESS) {
/*
* Launch this fctx.
*/
} else {
/*
* We don't care about the result of fctx_destroy()
* since we know we're not exiting.
*/
(void)fctx_destroy(fctx);
}
}
if (result == ISC_R_SUCCESS) {
FTRACE("created");
} else
return (result);
}
void
FTRACE("destroyfetch");
event = next_event) {
link);
FTRACE("found");
break;
}
}
}
}
fctx->references--;
if (fctx->references == 0) {
/*
* No one cares about the result of this fetch anymore.
* Shut it down.
*/
/*
* Unless we're still initializing (in which case the
* control event is still outstanding), we need to post
* the control event to tell the fetch we want it to
* exit.
*/
&cevent);
}
}
}