resolver.c revision 1a1a2ad3e82589d25d813d92936eb0403c68d62a
/*
* Copyright (C) 1999, 2000 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/keytable.h>
#include <dns/dispatch.h>
#include <dns/resolver.h>
#include <dns/rdatalist.h>
#include <dns/rdataset.h>
#include <dns/rdatatype.h>
#include <dns/validator.h>
#define DNS_RESOLVER_TRACE
#ifdef DNS_RESOLVER_TRACE
ISC_LOG_DEBUG(3), \
"res %p: %s", res, (m))
ISC_LOG_DEBUG(3), \
"res %p: %s", (r), (m))
ISC_LOG_DEBUG(3), \
"fctx %p: %s", fctx, (m))
ISC_LOG_DEBUG(3), \
"fetch %p (fctx %p): %s", \
ISC_LOG_DEBUG(3), \
"resquery %p (fctx %p): %s", \
#else
#define RTRACE(m)
#define RRTRACE(r, m)
#define FCTXTRACE(m)
#define FTRACE(m)
#define QTRACE(m)
#endif
/*
* Maximum EDNS0 input packet size.
*/
typedef struct fetchctx fetchctx_t;
typedef struct query {
/* Locked by task event serialization. */
unsigned int magic;
fetchctx_t * fctx;
unsigned int options;
unsigned int attributes;
unsigned char data[512];
} resquery_t;
#define RESQUERY_ATTR_CONNECTING 0x01
#define RESQUERY_ATTR_CANCELED 0x02
#define RESQUERY_CONNECTING(q) (((q)->attributes & \
RESQUERY_ATTR_CONNECTING) != 0)
#define RESQUERY_CANCELED(q) (((q)->attributes & \
RESQUERY_ATTR_CANCELED) != 0)
typedef enum {
fetchstate_init = 0, /* Start event has not run yet. */
fetchstate_done /* FETCHDONE events posted. */
} fetchstate;
struct fetchctx {
/* Not locked. */
unsigned int magic;
unsigned int options;
unsigned int bucketnum;
/* Locked by appropriate bucket lock. */
unsigned int references;
/* Locked by task event serialization. */
unsigned int attributes;
isc_timer_t * timer;
/*
* # of events we're waiting for.
*/
unsigned int pending;
unsigned int validating;
unsigned int restarts;
};
#define FCTX_ATTR_HAVEANSWER 0x01
#define FCTX_ATTR_GLUING 0x02
#define FCTX_ATTR_ADDRWAIT 0x04
#define FCTX_ATTR_SHUTTINGDOWN 0x08
#define FCTX_ATTR_WANTCACHE 0x10
#define FCTX_ATTR_WANTNCACHE 0x20
#define FCTX_ATTR_NEEDEDNS0 0x40
0)
0)
0)
!= 0)
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;
unsigned int options;
unsigned int nbuckets;
/* Locked by lock. */
unsigned int references;
unsigned int activebuckets;
};
/*
* Private addrinfo flags. These must not conflict with DNS_FETCHOPT_NOEDNS0,
* which we also use as an addrinfo flag.
*/
#define FCTX_ADDRINFO_MARK 0x0001
#define FCTX_ADDRINFO_FORWARDER 0x1000
== 0)
#define ISFORWARDER(a) (((a)->flags & \
FCTX_ADDRINFO_FORWARDER) != 0)
static inline isc_result_t
/*
* Start the lifetime timer for fctx.
*/
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 isc_result_t
/*
* Start the idle timer for fctx. The lifetime timer continues
* to be in effect.
*/
ISC_FALSE));
}
/*
* Stopping the idle timer is equivalent to calling fctx_starttimer(), but
* we use fctx_stopidletimer for readability in the code below.
*/
#define fctx_stopidletimer fctx_starttimer
static inline void
}
static void
{
unsigned int rtt;
unsigned int factor;
FCTXTRACE("cancelquery");
/*
* Should we update the RTT?
*/
/*
* We have both the start and finish times for this
* packet, so we can compute a real RTT.
*/
} else {
/*
* We don't have an RTT for this query. Maybe the
* packet was lost, or maybe this server is very
* slow. We don't know. Increase the RTT.
*/
if (rtt > 10000000)
rtt = 10000000;
/*
* Replace the current RTT with our value.
*/
}
factor);
}
deventp);
}
if (RESQUERY_CONNECTING(query)) {
/*
* Cancel the connect.
*/
}
if (!RESQUERY_CONNECTING(query)) {
/*
* It's safe to destroy the query now.
*/
}
}
static void
FCTXTRACE("cancelqueries");
query = next_query) {
}
}
static void
}
}
static void
}
}
static inline void
FCTXTRACE("stopeverything");
}
static inline void
/*
* Caller must be holding the appropriate bucket lock.
*/
FCTXTRACE("sendevents");
event = next_event) {
if (!HAVE_ANSWER(fctx))
}
}
static void
FCTXTRACE("done");
if (result == ISC_R_SUCCESS)
else
}
static void
QTRACE("senddone");
/*
* 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!
*/
}
static inline isc_result_t
if (result != ISC_R_SUCCESS)
return (result);
if (result != ISC_R_SUCCESS)
return (result);
if (result != ISC_R_SUCCESS)
return (result);
/*
* Set Maximum UDP buffer size.
*/
/*
* Set EXTENDED-RCODE, VERSION, and Z to 0.
*/
/*
* No ENDS options.
*/
}
static inline void
unsigned int seconds;
/*
* We retry every 2 seconds the first two times through the address
* list, and then we do exponential back-off.
*/
seconds = 2;
else
/*
* Double the round-trip time and convert to seconds.
*/
rtt /= 500000;
/*
* Always wait for at least the doubled round-trip time.
*/
/*
* But don't ever wait for more than 30 seconds.
*/
if (seconds > 30)
seconds = 30;
}
static isc_result_t
unsigned int options)
{
FCTXTRACE("query");
if (result != ISC_R_SUCCESS)
return (result);
goto stop_idle_timer;
}
query->attributes = 0;
/*
* Note that the caller MUST guarantee that 'addrinfo' will remain
* valid until this query is canceled.
*/
if (result != ISC_R_SUCCESS)
goto cleanup_query;
/*
* 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.
*/
&socket);
if (result != ISC_R_SUCCESS)
goto cleanup_query;
/*
* Regardless of whether dns_dispatch_create() succeeded or
* not, we don't need our reference to the socket anymore.
*/
if (result != ISC_R_SUCCESS)
goto cleanup_dispatch;
} else {
case PF_INET:
break;
case PF_INET6:
break;
default:
goto cleanup_dispatch;
}
/*
* 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.
*/
}
/*
* Connect to the remote server.
*
* XXXRTH Should we attach to the socket?
*/
if (result != ISC_R_SUCCESS)
goto cleanup_dispatch;
QTRACE("connecting via TCP");
} else {
if (result != ISC_R_SUCCESS)
goto cleanup_dispatch;
}
return (ISC_R_SUCCESS);
return (result);
}
static isc_result_t
isc_region_t r;
QTRACE("send");
/*
* Reserve space for the TCP message length.
*/
} else {
}
if (result != ISC_R_SUCCESS)
goto cleanup_temps;
if (result != ISC_R_SUCCESS)
goto cleanup_temps;
/*
* Get a query id from the dispatch.
*/
task,
if (result != ISC_R_SUCCESS)
goto cleanup_temps;
/*
* Set up question.
*/
/*
* Set RD if the client has requested that we do a recursive query,
* or if we're sending to a forwarder.
*/
/*
* We don't have to set opcode because it defaults to query.
*/
/*
* Convert the question to wire format.
*/
if (result != ISC_R_SUCCESS)
goto cleanup_message;
DNS_SECTION_QUESTION, 0);
if (result != ISC_R_SUCCESS)
goto cleanup_message;
/*
* Use EDNS0, unless the caller doesn't want it, or we know that
* the remote server doesn't like it.
*/
if (result != ISC_R_SUCCESS) {
/*
* We couldn't add the OPT, but we'll press on.
* We're not using EDNS0, so set the NOEDNS0
* bit.
*/
}
} else {
/*
* We know this server doesn't like EDNS0, so we
* won't use it. Set the NOEDNS0 bit since we're
* not using EDNS0.
*/
}
}
/*
* If we need EDNS0 to do this query and aren't using it, we lose.
*/
goto cleanup_message;
}
/*
* If we're using EDNS, set AD and CD so we'll get DNSSEC data.
*/
/*
* Add TSIG record tailored to the current recipient.
*/
if (result == ISC_R_SUCCESS &&
{
if (result == ISC_R_NOTFOUND)
goto cleanup_message;
}
if (result != ISC_R_SUCCESS)
goto cleanup_message;
if (result != ISC_R_SUCCESS)
goto cleanup_message;
}
/*
* If using TCP, write the length of the message at the beginning
* of the buffer.
*/
}
/*
* We're now done with the query message.
*/
/*
* Send the query!
*/
isc_buffer_used(buffer, &r);
if (result != ISC_R_SUCCESS)
goto cleanup_message;
QTRACE("sent");
return (ISC_R_SUCCESS);
/*
* Stop the dispatcher from listening.
*/
NULL);
return (result);
}
static void
QTRACE("connected");
/*
* XXXRTH
*
* Currently we don't wait for the connect event before retrying
* a query. This means that if we get really behind, we may end
* up doing extra work!
*/
if (RESQUERY_CANCELED(query)) {
/*
* This query was canceled while the connect() was in
* progress.
*/
} else {
/*
* We are connected. Send the query.
*/
if (result != ISC_R_SUCCESS) {
}
} else
}
}
static void
unsigned int bucketnum;
FCTXTRACE("finddone");
/*
* The fetch is waiting for a name to be found.
*/
/*
* We've got nothing else to wait for and don't
* know the answer. There's nothing to do but
* fail the fctx.
*/
}
fctx->validating == 0) {
/*
* Note that we had to wait until we had the lock before
* looking at fctx->references.
*/
if (fctx->references == 0)
}
if (want_try)
else if (want_done)
else if (bucket_empty)
}
static inline isc_boolean_t
return (ISC_TRUE);
}
return (ISC_FALSE);
}
static inline isc_boolean_t
/*
* Mark all known bad servers, so we don't try to talk to them
* again.
*/
/*
* Mark any bad nameservers.
*/
else
}
}
/*
* Mark any bad forwarders.
*/
else
}
return (all_bad);
}
static void
/*
* We already know this server is bad.
*/
return;
}
FCTXTRACE("add_bad");
return;
}
static void
/*
* Lame N^2 bubble sort.
*/
}
}
}
static void
/*
* Lame N^2 bubble sort.
*/
}
}
}
}
static isc_result_t
isc_region_t r;
unsigned int stdoptions, options;
FCTXTRACE("getaddresses");
/*
* Don't pound on remote servers. (Failsafe!)
*/
FCTXTRACE("too many restarts");
return (DNS_R_SERVFAIL);
}
stdoptions = 0; /* Keep compiler happy. */
/*
* Forwarders.
*/
/*
* If this fctx has forwarders, use them; otherwise the use
* resolver's forwarders (if any).
*/
if (result == ISC_R_SUCCESS) {
}
}
/*
* If the forwarding policy is "only", we don't need the addresses
* of the nameservers.
*/
goto out;
/*
* Normal nameservers.
*/
while (result == ISC_R_SUCCESS) {
/*
* Extract the name from the NS record.
*/
dns_rdata_toregion(&rdata, &r);
dns_name_fromregion(&name, &r);
/*
* If this name is a subdomain of the query domain, tell
* the ADB to start looking at "." if it doesn't know the
* address. This keeps us from getting stuck if the
* nameserver is beneath the zone cut and we don't know its
* address (e.g. because the A record has expired).
* By restarting from ".", we ensure that any missing glue
* will be reestablished.
*
* A further optimization would be to get the ADB to start
* looking at the most enclosing zone cut above fctx->domain.
* We don't expect this situation to happen very frequently,
* so we've chosen the simple solution.
*/
/*
* See what we know about this address.
*/
&find);
if (result != ISC_R_SUCCESS) {
if (result == DNS_R_ALIAS) {
/*
*/
}
/*
* We have at least some of the addresses for the
* name.
*/
} else {
/*
* We don't know any of the addresses for this
* name.
*/
/*
* We're looking for them and will get an
* event about it later.
*/
} else {
/*
* And ADB isn't going to send us any events
* either. This find loses.
*/
!= 0) {
/*
* The ADB pruned lame servers for
* this name. Remember that in case
* we get desperate later on.
*/
}
}
}
}
if (result != ISC_R_NOMORE)
return (result);
out:
/*
* Mark all known bad servers.
*/
/*
* How are we doing?
*/
if (all_bad) {
/*
* We've got no addresses.
*/
/*
* We're fetching the addresses, but don't have any
* yet. Tell the caller to wait for an answer.
*/
result = DNS_R_WAIT;
} else if (pruned) {
/*
* Some addresses were removed by lame pruning.
* Turn pruning off and try again.
*/
FCTXTRACE("restarting with returnlame");
goto restart;
} else {
/*
* We've lost completely. We don't know any
* addresses, and the ADB has told us it can't get
* them.
*/
FCTXTRACE("no addresses");
}
} else {
/*
* We've found some addresses. We might still be looking
* for more addresses.
*/
/*
* XXXRTH We could sort the forwaddrs here if the caller
* wants to use the forwaddrs in "best order" as
* opposed to "fixed order".
*/
}
return (result);
}
static inline dns_adbaddrinfo_t *
/*
* Return the next untried address, if any.
*/
/*
* Find the first unmarked forwarder (if any).
*/
return (addrinfo);
}
}
/*
* No forwarders. Move to the next find.
*/
else {
}
/*
* Find the first unmarked addrinfo.
*/
break;
}
}
break;
}
return (addrinfo);
}
static void
FCTXTRACE("try");
/*
* We have no more addresses. Start over.
*/
if (result == DNS_R_WAIT) {
/*
* Sleep waiting for addresses.
*/
FCTXTRACE("addrwait");
return;
} else if (result != ISC_R_SUCCESS) {
/*
* Something bad happened.
*/
return;
}
/*
* fctx_getaddresses() returned success, so at least one
* of the find lists should be nonempty.
*/
}
if (result != ISC_R_SUCCESS)
}
static isc_boolean_t
unsigned int bucketnum;
/*
* Caller must be holding the bucket lock.
*/
FCTXTRACE("destroy");
/*
* Free bad.
*/
}
return (ISC_TRUE);
return (ISC_FALSE);
}
/*
* Fetch event handlers.
*/
static void
FCTXTRACE("timeout");
} else {
/*
* We could cancel the running queries here, or we could let
* them keep going. Right now we choose the latter...
*/
/*
* Our timer has triggered. Reestablish the fctx lifetime
* timer.
*/
/*
* Keep trying.
*/
}
}
static void
/*
* Start the shutdown process for fctx, if it isn't already underway.
*/
FCTXTRACE("shutdown");
/*
* The caller must be holding the appropriate bucket lock.
*/
if (fctx->want_shutdown)
return;
/*
* 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);
}
}
static void
unsigned int bucketnum;
FCTXTRACE("doshutdown");
/*
* An fctx that is shutting down is no longer in ADDRWAIT mode.
*/
}
fctx->validating == 0)
if (bucket_empty)
}
static void
unsigned int bucketnum;
FCTXTRACE("start");
if (fctx->want_shutdown) {
/*
* We haven't started this fctx yet, and we've been requested
* to shut it down.
*/
/*
* Since we haven't started, we INSIST that we have no
* pending ADB finds and no pending validations.
*/
if (fctx->references == 0) {
/*
* It's now safe to destroy this fctx.
*/
}
} 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);
}
/*
* Make sure that we can store the sigrdataset in the
* first event if it is needed by any of the events.
*/
else
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;
/*
* The caller didn't supply a query domain and
* nameservers, and we're not in forward-only mode,
* so find the best nameservers to use.
*/
dns_fixedname_name(&qdomain), 0,
0, ISC_TRUE,
&fctx->nameservers,
NULL);
if (result != ISC_R_SUCCESS)
goto cleanup_name;
if (result != ISC_R_SUCCESS) {
goto cleanup_name;
}
} else {
/*
* We're in forward-only mode. Set the query domain
* to ".".
*/
if (result != ISC_R_SUCCESS)
goto cleanup_name;
}
} else {
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;
fctx->validating = 0;
if (dns_name_requiresedns(name))
else
fctx->attributes = 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;
}
/*
* Default retry interval initialization. We set the interval now
* mostly so it won't be uninitialized. It will be set to the
* correct value before a query is issued.
*/
/*
* 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);
}
/*
* 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 void
/*
* Set up any other events to have the same data as the first
* event.
*
* Caller must be holding the appropriate lock.
*/
return;
if (result != ISC_R_SUCCESS)
else
event->sigrdataset);
}
}
static void
/*
* If shutting down, ignore the results. Check to see if we're
* done waiting for validator completions and ADB pending events; if
* so, destroy the fctx.
*/
if (SHUTTINGDOWN(fctx)) {
FCTXTRACE("XXX validator shutdown code is still incomplete");
goto cleanup;
}
/*
* We're not shutting down.
*
* XXX This code assumes that the result of a validation is always
* "the answer". Is it?
*/
FCTXTRACE("received validation completion event");
FCTXTRACE("validation failed");
goto cleanup;
}
FCTXTRACE("negative validation result handling "
"is not yet implemented");
goto cleanup;
}
FCTXTRACE("validation OK");
}
/*
* The data was already cached as pending data.
* Re-cache it as secure and bind the cached
* rdatasets to the first event on the fetch
* event list.
*/
if (result != ISC_R_SUCCESS)
goto cleanup;
if (result != ISC_R_SUCCESS &&
goto cleanup;
vevent->sigrdataset, 0,
if (result != ISC_R_SUCCESS &&
goto cleanup;
}
fctx->validating--;
}
static inline isc_result_t
unsigned int options;
/*
* The appropriate bucket lock must be held.
*/
/*
* Is DNSSEC validation required for this name?
*/
if (result != ISC_R_SUCCESS)
return (result);
if (need_validation) {
/*
* XXXRTH
*
* If some of the rdatasets associated with this name
* don't have signatures, it could be that data we got is in
* an unsecured subzone of a secure domain.
*
* In this case we need to see if we can find a key chain
* starting at the most-enclosing security root that proves
* that this name is not secure.
*/
}
asigrdataset = NULL;
!need_validation) {
if (result != ISC_R_SUCCESS)
return (result);
}
}
}
/*
* Find or create the cache node.
*/
if (result != ISC_R_SUCCESS)
return (result);
/*
* Cache or validate each cacheable rdataset.
*/
continue;
/*
* If this rrset is in a secure domain, do DNSSEC validation
* for it, unless it is glue.
*/
/*
* SIGs are validated as part of validating the
* type they cover.
*/
continue;
/*
* Find the SIG for this rdataset, if we have it.
*/
sigrdataset != NULL;
break;
}
if (sigrdataset == NULL) {
/*
* The peer is broken.
*/
FCTXTRACE("DNSSEC response "
"missing SIG");
break;
} else {
/*
* Ignore non-answer rdatasets that
* are missing signatures.
*/
continue;
}
}
/*
* Cache this rdataset/sigrdataset pair as
* pending data.
*/
#ifdef notyet
#endif
if (result != ISC_R_SUCCESS &&
break;
sigrdataset, 0, NULL);
if (result != ISC_R_SUCCESS &&
break;
/*
* XXXRTH We should probably do this
* after we've cached everything as
* pending, otherwise we might not
* take advantage of a key which we
* have learned.
*/
name,
0,
task,
fctx,
if (result == ISC_R_SUCCESS)
fctx->validating++;
/* XXX what should we do in the failure case? */
}
/*
* It's OK to cache this rdataset now.
*/
else
else {
}
}
/*
* If the trust level is 'dns_trust_glue'
* then we are adding data from a referral
* we got while executing the search algorithm.
* New referral data always takes precedence
* over the existing cache contents.
*/
} else
options = 0;
/*
* Now we can add the rdataset.
*/
if (result == DNS_R_UNCHANGED) {
/*
* The answer in the cache is better
* than the answer we found, and is
* a negative cache entry, so we
* must set eresult appropriately.
*/
eresult =
else
eresult =
}
} else if (result != ISC_R_SUCCESS)
break;
}
}
}
}
return (result);
}
static inline isc_result_t
FCTXTRACE("cache_message");
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 isc_result_t
FCTXTRACE("ncache_message");
/*
* Is DNSSEC validation required for this name?
*/
if (result != ISC_R_SUCCESS)
return (result);
if (need_validation) {
/*
* XXXRTH
*
* If some of the rdatasets associated with this name
* don't have signatures, it could be that data we got is in
* an unsecured subzone of a secure domain.
*
* In this case we need to see if we can find a key chain
* starting at the most-enclosing security root that proves
* that this name is not secure.
*/
return (ISC_R_NOTIMPLEMENTED);
}
if (!HAVE_ANSWER(fctx)) {
if (result != ISC_R_SUCCESS)
goto unlock;
}
} else
&node);
if (result != ISC_R_SUCCESS)
goto unlock;
if (result == DNS_R_UNCHANGED) {
/*
* The data in the cache is better than the negative cache
* entry we're trying to add.
*/
/*
* The cache data is also a negative cache
* entry.
*/
else
} else {
/*
* Either we don't care about the nature of the
* cache rdataset (because no fetch is interested
* in the outcome), or the cache rdataset is not
* a negative cache entry. Whichever case it is,
* we can return success. In the latter case,
* 'eresult' is already set correctly.
*
*/
}
} else if (result == ISC_R_SUCCESS) {
if (covers == dns_rdatatype_any)
else
} else
goto unlock;
if (!HAVE_ANSWER(fctx)) {
}
}
return (result);
}
static inline void
{
if (gluing)
else
if (external)
}
static isc_result_t
else
NULL);
if (result == ISC_R_SUCCESS) {
if (type == dns_rdatatype_a) {
else
if (rtype == dns_rdatatype_a ||
rtype == dns_rdatatype_aaaa ||
gluing);
/*
* 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)
gluing);
}
}
/*
* XXXRTH Some other stuff still needs to be marked.
* See query.c.
*/
}
return (ISC_R_SUCCESS);
}
static inline isc_result_t
isc_region_t r;
if (result != ISC_R_SUCCESS)
return (result);
dns_rdata_toregion(&rdata, &r);
dns_name_fromregion(tname, &r);
return (ISC_R_SUCCESS);
}
static inline isc_result_t
{
isc_region_t r;
int order;
/*
* Get the target name of the DNAME.
*/
if (result != ISC_R_SUCCESS)
return (result);
dns_rdata_toregion(&rdata, &r);
dns_name_fromregion(&tname, &r);
/*
* Get the prefix of qname.
*/
&nbits);
if (namereln != dns_namereln_subdomain)
return (DNS_R_FORMERR);
if (result != ISC_R_SUCCESS)
return (result);
}
static isc_result_t
FCTXTRACE("noanswer_response");
/*
* Setup qname.
*/
/*
* We have a normal, non-chained negative response or
* referral.
*/
else
} else {
/*
* We're being invoked by answer_response() after it has
*/
/*
* If the current qname is not a subdomain of the query
* domain, there's no point in looking at the authority
* section without doing DNSSEC validation.
*
* Until we do that validation, we'll just return success
* in this case.
*/
return (ISC_R_SUCCESS);
}
/*
* We have to figure out if this is a negative response, or a
* referral.
*/
/*
* Sometimes we can tell if its a negative response by looking at
* the message header.
*/
/*
* Process the authority section.
*/
ns_rdataset = NULL;
if (type == dns_rdatatype_sig)
if (type == dns_rdatatype_ns) {
/*
* NS or SIG NS.
*
* Only one set of NS RRs is allowed.
*/
return (DNS_R_FORMERR);
name->attributes |=
rdataset->attributes |=
} else if (type == dns_rdatatype_soa ||
type == dns_rdatatype_nxt) {
/*
* SOA, SIG SOA, NXT, or SIG NXT.
*
* Only one SOA is allowed.
*/
{
return (DNS_R_FORMERR);
}
name->attributes |=
rdataset->attributes |=
if (aa)
else
/*
* No additional data needs to be
* marked.
*/
}
}
}
if (result == ISC_R_NOMORE)
break;
else if (result != ISC_R_SUCCESS)
return (result);
}
/*
* Did we find anything?
*/
/*
* Nope.
*/
/*
* and haven't found else anything useful here, but
* no error has occurred since we have an answer.
*/
return (ISC_R_SUCCESS);
} else {
/*
* The responder is insane.
*/
return (DNS_R_FORMERR);
}
}
/*
* If we found both NS and SOA, they should be the same name.
*/
return (DNS_R_FORMERR);
/*
* Do we have a referral? (We only want to follow a referral if
* we're not following a chain.)
*/
/*
* If ns_name is equal to fctx->domain, we're not making
* progress. We return DNS_R_FORMERR so that we'll keep
* keep trying other servers.
*/
return (DNS_R_FORMERR);
/*
* Mark any additional data related to this rdataset.
* It's important that we do this before we change the
* query domain.
*/
fctx);
/*
* Set the current query domain to the referral name.
*
* XXXRTH We should check if we're in forward-only mode, and
* if so we should bail out.
*/
if (result != ISC_R_SUCCESS)
return (result);
return (DNS_R_DELEGATION);
}
/*
* Since we're not doing a referral, we don't want to cache any
* NS RRs we may have found.
*/
if (negative_response)
return (ISC_R_SUCCESS);
}
static isc_result_t
unsigned int aflag;
FCTXTRACE("answer_response");
/*
* Examine the answer section, marking those rdatasets which are
* part of the answer and should be cached.
*/
else
aflag = 0;
type == dns_rdatatype_any) {
/*
* We've found an ordinary answer.
*/
/*
* We've found a signature that
* covers the type we're looking for.
*/
/*
* We're looking for something else,
* but we found a CNAME.
*
* Getting a CNAME response for some
* query types is an error.
*/
if (type == dns_rdatatype_sig ||
type == dns_rdatatype_key ||
return (DNS_R_FORMERR);
&tname);
if (result != ISC_R_SUCCESS)
return (result);
/*
* We're looking for something else,
* but we found a SIG CNAME.
*/
}
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).
*/
if (aflag ==
else
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);
/*
* CNAME chaining.
*/
if (want_chaining) {
rdataset->attributes |=
}
}
/*
* We could add an "else" clause here and
* log that we're ignoring this rdataset.
*/
}
} else {
/*
* Look for a DNAME (or its SIG). Anything else is
* ignored.
*/
aflag = 0;
/*
* We're looking for something else,
* but we found a DNAME.
*
* If we're not chaining, then the
* DNAME should not be external.
*/
return (DNS_R_FORMERR);
&dname);
if (result == ISC_R_NOSPACE) {
/*
* We can't construct the
* DNAME target. Do not
* try to continue.
*/
} else if (result != ISC_R_SUCCESS)
return (result);
/*
* We've found a signature that
* covers the DNAME.
*/
}
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.
*/
name->attributes |=
if (aa)
} else if (external) {
rdataset->attributes |=
}
/*
* DNAME chaining.
*/
if (want_chaining) {
rdataset->attributes |=
&dname);
}
}
}
}
}
if (result != ISC_R_NOMORE)
return (result);
/*
* We should have found an answer.
*/
if (!have_answer)
return (DNS_R_FORMERR);
/*
* This response is now potentially cacheable.
*/
/*
* Did chaining end before we got the final answer?
*/
if (want_chaining) {
/*
* Yes. This may be a negative reply, so hand off
* authority section processing to the noanswer code.
* If it isn't a noanswer response, no harm will be
* done.
*/
}
/*
* We didn't end with an incomplete chain, so the rcode should be
* "no error".
*/
return (DNS_R_FORMERR);
/*
* 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.
*/
name->attributes |=
rdataset->attributes |=
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);
}
static void
unsigned int options;
QTRACE("response");
covers = 0;
/*
* XXXRTH We should really get the current time just once. We
* need a routine to convert from an isc_time_t to an
* isc_stdtime_t.
*/
if (result != ISC_R_SUCCESS)
goto done;
/*
* Did the dispatcher have a problem?
*/
/*
* The problem might be that they
* don't understand EDNS0. Turn it
* off and try again.
*/
/*
* Remember that they don't like EDNS0.
*/
} else {
/*
* There's no hope for this query.
*/
}
goto done;
}
/* BEW - clean this up at some point */
if (result != ISC_R_SUCCESS) {
switch (result) {
case ISC_R_UNEXPECTEDEND:
if (!message->question_ok ||
(options & DNS_FETCHOPT_TCP) != 0) {
/*
* Either the message ended prematurely,
* sent over TCP. In all of these cases,
* something is wrong with the remote
* server and we don't want to retry using
* TCP.
*/
== 0) {
/*
* The problem might be that they
* don't understand EDNS0. Turn it
* off and try again.
*/
/*
* Remember that they don't like EDNS0.
*/
} else {
}
goto done;
}
/*
* We defer retrying via TCP for a bit so we can
* check out this message further.
*/
break;
case DNS_R_FORMERR:
/*
* The problem might be that they
* don't understand EDNS0. Turn it
* off and try again.
*/
/*
* Remember that they don't like EDNS0.
*/
} else {
}
goto done;
case DNS_R_MOREDATA:
goto done;
default:
/*
* Something bad has happened.
*/
goto done;
}
}
/*
* If the message is signed, check the signature. If not, this
* returns success anyway.
*/
if (result != ISC_R_SUCCESS)
goto done;
{
goto done;
}
/*
* The dispatcher should ensure we only get responses with QR set.
*/
/*
* 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).
*/
/*
* Deal with truncated responses by retrying using TCP.
*/
if (truncated) {
if ((options & DNS_FETCHOPT_TCP) != 0) {
} else {
}
goto done;
}
/*
* Is it a query response?
*/
/* XXXRTH Log */
goto done;
}
/*
* Is the remote server broken, or does it dislike us?
*/
/*
* It's very likely they don't like EDNS0.
*
* XXXRTH We should check if the question
* we're asking requires EDNS0, and
* if so, we should bail out.
*/
/*
* Remember that they don't like EDNS0.
*/
/*
* This forwarder doesn't understand us,
* but other forwarders might. Keep trying.
*/
} else {
/*
* The server doesn't understand us. Since
* all servers for a zone need similar
* capabilities, we assume that we will get
* FORMERR from all servers, and thus we
* cannot make any more progress with this
* fetch.
*/
}
/*
* DNAME mapping failed because the new name
* was too long. There's no chance of success
* for this fetch.
*/
} else {
/*
* XXXRTH log.
*/
}
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 (result == DNS_R_DELEGATION) {
/*
* We don't have the answer, but we know a better
* place to look.
*/
} else if (result != ISC_R_SUCCESS) {
/*
* Something has gone wrong.
*/
if (result == DNS_R_FORMERR)
goto done;
}
} else {
/*
* The server is insane.
*/
/* XXXRTH Log */
goto done;
}
/*
* Cache the cacheable parts of the message. This may also cause
* work to be queued to the DNSSEC validator.
*/
if (result != ISC_R_SUCCESS)
goto done;
}
/*
* Ncache the negatively cacheable parts of the message. This may
* also cause work to be queued to the DNSSEC validator.
*/
if (WANTNCACHE(fctx)) {
else
/*
* Cache any negative cache entries in the message.
*/
}
done:
/*
* Remember the query's addrinfo, in case we need to mark the
* server as broken.
*/
/*
* Cancel the query.
*
* XXXRTH Don't cancel the query if waiting for validation?
*/
if (keep_trying) {
if (result == DNS_R_FORMERR)
if (broken_server) {
/*
* Add 500 units of "badness" to this server.
*/
-500);
/*
* Add this server to the list of bad servers for
* this fctx.
*/
}
if (get_nameservers) {
if (result != ISC_R_SUCCESS) {
return;
}
&fctx->nameservers,
NULL);
if (result != ISC_R_SUCCESS) {
FCTXTRACE("couldn't find a zonecut");
return;
}
/*
* The best nameservers are now above our
* QDOMAIN.
*/
FCTXTRACE("nameservers now above QDOMAIN");
return;
}
if (result != ISC_R_SUCCESS) {
return;
}
}
/*
* Try again.
*/
} else if (resend) {
/*
* Resend (probably with changed options).
*/
FCTXTRACE("resend");
if (result != ISC_R_SUCCESS)
/*
* All has gone well so far, but we are waiting for the
* DNSSEC validator to validate the answer.
*/
if (result != ISC_R_SUCCESS)
} else {
/*
* We're done.
*/
}
}
/***
*** Resolver Methods
***/
static void
}
}
static void
unsigned int i;
RTRACE("destroy");
}
}
static void
/*
* Caller must be holding the resolver lock.
*/
event = next_event) {
}
}
static void
RTRACE("empty_bucket");
res->activebuckets--;
if (res->activebuckets == 0)
}
unsigned int options,
{
unsigned int i, buckets_created = 0;
char name[16];
/*
* Create a resolver.
*/
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.
*/
if (dispatchv4 != NULL) {
} else if (isc_net_probeipv4() == ISC_R_SUCCESS) {
/*
* Create an IPv4 UDP socket and a dispatcher for it.
*/
&res->udpsocketv4);
if (result != ISC_R_SUCCESS)
goto cleanup_buckets;
&res->dispatchv4);
if (result != ISC_R_SUCCESS)
goto cleanup_udpsocketv4;
}
/*
* IPv6 Dispatcher.
*/
if (dispatchv6 != NULL) {
} else if (isc_net_probeipv6() == ISC_R_SUCCESS) {
/*
* Create an IPv6 UDP socket and a dispatcher for it.
*/
&res->udpsocketv6);
if (result != ISC_R_SUCCESS)
goto cleanup_dispatchv4;
&res->dispatchv6);
if (result != ISC_R_SUCCESS)
goto cleanup_udpsocketv6;
}
/*
* Forwarding.
*/
if (result != ISC_R_SUCCESS)
goto cleanup_dispatchv6;
return (ISC_R_SUCCESS);
for (i = 0; i < buckets_created; i++) {
}
return (result);
}
{
/*
* Set the default forwarders to be used by the resolver.
*/
return (ISC_R_NOMEMORY);
}
/* XXXRTH Create and use isc_sockaddr_copy(). */
}
return (ISC_R_SUCCESS);
}
/*
* Set the default forwarding policy to be used by the resolver.
*/
return (ISC_R_SUCCESS);
}
static void
}
void
RTRACE("dns_resolver_prime");
/*
* Forwarding-only resolvers don't need to be
* primed.
*/
return;
}
if (want_priming) {
/*
* To avoid any possible recursive locking problems, we
* start the priming fetch like any other fetch, and holding
* no resolver locks. No one else will try to start it
* because we're the ones who set res->priming to true.
* Any other callers of dns_resolver_prime() while we're
* running will see that res->priming is already true and
* do nothing.
*/
RTRACE("priming");
return;
}
if (result == ISC_R_SUCCESS)
else
}
}
void
/*
* Freeze resolver.
*/
}
void
source->references++;
}
void
{
/*
* We're already shutdown. Send the event.
*/
} else {
}
}
void
unsigned int i;
RTRACE("shutdown");
RTRACE("exiting");
res->activebuckets--;
}
}
if (res->activebuckets == 0)
}
}
void
RTRACE("detach");
res->references--;
if (res->references == 0) {
}
if (need_destroy)
}
static inline isc_boolean_t
unsigned int options)
{
return (ISC_FALSE);
}
static inline void
isc_buffer_t b;
char text[1024];
isc_region_t r;
/*
* XXXRTH Allow this to be turned on and off...
*/
return;
isc_buffer_available(&b, &r);
if (r.length < 1)
return;
*r.base = ' ';
isc_buffer_add(&b, 1);
return;
isc_buffer_used(&b, &r);
/* XXXRTH Give them their own category? */
}
{
unsigned int bucketnum;
/* XXXRTH Check for meta type */
} else
/*
* 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("cancelfetch");
event = next_event) {
break;
}
}
}
}
}
void
unsigned int bucketnum;
FTRACE("destroyfetch");
/*
* Sanity check: the caller should have gotten its event before
* trying to destroy the fetch.
*/
event = next_event) {
}
}
fctx->references--;
if (fctx->references == 0) {
/*
* No one cares about the result of this fetch anymore.
*/
SHUTTINGDOWN(fctx)) {
/*
* This fctx is already shutdown; we were just
* waiting for the last reference to go away.
*/
} else {
/*
* Initiate shutdown.
*/
}
}
if (bucket_empty)
}
return (resolver->dispatchv4);
}
return (resolver->dispatchv6);
}
}