#include <config.h>
#include <isc/task.h>
#include <isc/timer.h>
#include <isc/util.h>
#include <dns/acl.h>
#include <dns/adb.h>
#include <dns/db.h>
#include <dns/dispatch.h>
#include <dns/events.h>
#include <dns/forward.h>
#include <dns/keytable.h>
#include <dns/log.h>
#include <dns/message.h>
#include <dns/ncache.h>
#include <dns/peer.h>
#include <dns/rdata.h>
#include <dns/rdatalist.h>
#include <dns/rdataset.h>
#include <dns/rdatastruct.h>
#include <dns/rdatatype.h>
#include <dns/resolver.h>
#include <dns/result.h>
#include <dns/tsig.h>
#include <dns/validator.h>
#define DNS_RESOLVER_TRACE
#ifdef DNS_RESOLVER_TRACE
#define RTRACE(m) isc_log_write(dns_lctx, \
DNS_LOGCATEGORY_RESOLVER, \
DNS_LOGMODULE_RESOLVER, \
ISC_LOG_DEBUG(3), \
"res %p: %s", res, (m))
#define RRTRACE(r, m) isc_log_write(dns_lctx, \
DNS_LOGCATEGORY_RESOLVER, \
DNS_LOGMODULE_RESOLVER, \
ISC_LOG_DEBUG(3), \
"res %p: %s", (r), (m))
#define FCTXTRACE(m) isc_log_write(dns_lctx, \
DNS_LOGCATEGORY_RESOLVER, \
DNS_LOGMODULE_RESOLVER, \
ISC_LOG_DEBUG(3), \
"fctx %p: %s", fctx, (m))
#define FCTXTRACE2(m1, m2) \
isc_log_write(dns_lctx, \
DNS_LOGCATEGORY_RESOLVER, \
DNS_LOGMODULE_RESOLVER, \
ISC_LOG_DEBUG(3), \
"fctx %p: %s %s", fctx, (m1), (m2))
#define FTRACE(m) isc_log_write(dns_lctx, \
DNS_LOGCATEGORY_RESOLVER, \
DNS_LOGMODULE_RESOLVER, \
ISC_LOG_DEBUG(3), \
"fetch %p (fctx %p): %s", \
fetch, fetch->private, (m))
#define QTRACE(m) isc_log_write(dns_lctx, \
DNS_LOGCATEGORY_RESOLVER, \
DNS_LOGMODULE_RESOLVER, \
ISC_LOG_DEBUG(3), \
"resquery %p (fctx %p): %s", \
query, query->fctx, (m))
#else
#define RTRACE(m)
#define RRTRACE(r, m)
#define FCTXTRACE(m)
#define FTRACE(m)
#define QTRACE(m)
#endif
#define SEND_BUFFER_SIZE 2048
#define MAX_EDNS0_TIMEOUTS 3
typedef struct fetchctx fetchctx_t;
typedef struct query {
unsigned int magic;
fetchctx_t * fctx;
isc_mem_t * mctx;
dns_dispatchmgr_t * dispatchmgr;
dns_dispatch_t * dispatch;
dns_adbaddrinfo_t * addrinfo;
isc_socket_t * tcpsocket;
isc_time_t start;
dns_messageid_t id;
dns_dispentry_t * dispentry;
ISC_LINK(struct query) link;
isc_buffer_t buffer;
isc_buffer_t *tsig;
dns_tsigkey_t *tsigkey;
unsigned int options;
unsigned int attributes;
unsigned int sends;
unsigned int connects;
unsigned char data[512];
} resquery_t;
#define QUERY_MAGIC ISC_MAGIC('Q', '!', '!', '!')
#define VALID_QUERY(query) ISC_MAGIC_VALID(query, QUERY_MAGIC)
#define RESQUERY_ATTR_CANCELED 0x02
#define RESQUERY_CONNECTING(q) ((q)->connects > 0)
#define RESQUERY_CANCELED(q) (((q)->attributes & \
RESQUERY_ATTR_CANCELED) != 0)
#define RESQUERY_SENDING(q) ((q)->sends > 0)
typedef enum {
fetchstate_init = 0,
fetchstate_active,
fetchstate_done
} fetchstate;
struct fetchctx {
unsigned int magic;
dns_resolver_t * res;
dns_name_t name;
dns_rdatatype_t type;
unsigned int options;
unsigned int bucketnum;
fetchstate state;
isc_boolean_t want_shutdown;
isc_boolean_t cloned;
unsigned int references;
isc_event_t control_event;
ISC_LINK(struct fetchctx) link;
ISC_LIST(dns_fetchevent_t) events;
dns_name_t domain;
dns_rdataset_t nameservers;
unsigned int attributes;
isc_timer_t * timer;
isc_time_t expires;
isc_interval_t interval;
dns_message_t * qmessage;
dns_message_t * rmessage;
ISC_LIST(resquery_t) queries;
dns_adbfindlist_t finds;
dns_adbfind_t * find;
dns_adbaddrinfolist_t forwaddrs;
isc_sockaddrlist_t forwarders;
dns_fwdpolicy_t fwdpolicy;
isc_sockaddrlist_t bad;
ISC_LIST(dns_validator_t) validators;
dns_db_t * cache;
dns_adb_t * adb;
unsigned int pending;
unsigned int restarts;
unsigned int timeouts;
};
#define FCTX_MAGIC ISC_MAGIC('F', '!', '!', '!')
#define VALID_FCTX(fctx) ISC_MAGIC_VALID(fctx, FCTX_MAGIC)
#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
#define HAVE_ANSWER(f) (((f)->attributes & FCTX_ATTR_HAVEANSWER) != \
0)
#define GLUING(f) (((f)->attributes & FCTX_ATTR_GLUING) != \
0)
#define ADDRWAIT(f) (((f)->attributes & FCTX_ATTR_ADDRWAIT) != \
0)
#define SHUTTINGDOWN(f) (((f)->attributes & FCTX_ATTR_SHUTTINGDOWN) \
!= 0)
#define WANTCACHE(f) (((f)->attributes & FCTX_ATTR_WANTCACHE) != 0)
#define WANTNCACHE(f) (((f)->attributes & FCTX_ATTR_WANTNCACHE) != 0)
#define NEEDEDNS0(f) (((f)->attributes & FCTX_ATTR_NEEDEDNS0) != 0)
struct dns_fetch {
unsigned int magic;
fetchctx_t * private;
};
#define DNS_FETCH_MAGIC ISC_MAGIC('F', 't', 'c', 'h')
#define DNS_FETCH_VALID(fetch) ISC_MAGIC_VALID(fetch, DNS_FETCH_MAGIC)
typedef struct fctxbucket {
isc_task_t * task;
isc_mutex_t lock;
ISC_LIST(fetchctx_t) fctxs;
isc_boolean_t exiting;
} fctxbucket_t;
struct dns_resolver {
unsigned int magic;
isc_mem_t * mctx;
isc_mutex_t lock;
dns_rdataclass_t rdclass;
isc_socketmgr_t * socketmgr;
isc_timermgr_t * timermgr;
isc_taskmgr_t * taskmgr;
dns_view_t * view;
isc_boolean_t frozen;
unsigned int options;
dns_dispatchmgr_t * dispatchmgr;
dns_dispatch_t * dispatchv4;
dns_dispatch_t * dispatchv6;
unsigned int nbuckets;
fctxbucket_t * buckets;
isc_uint32_t lame_ttl;
unsigned int references;
isc_boolean_t exiting;
isc_eventlist_t whenshutdown;
unsigned int activebuckets;
isc_boolean_t priming;
dns_fetch_t * primefetch;
};
#define RES_MAGIC ISC_MAGIC('R', 'e', 's', '!')
#define VALID_RESOLVER(res) ISC_MAGIC_VALID(res, RES_MAGIC)
#define FCTX_ADDRINFO_MARK 0x0001
#define FCTX_ADDRINFO_FORWARDER 0x1000
#define UNMARKED(a) (((a)->flags & FCTX_ADDRINFO_MARK) \
== 0)
#define ISFORWARDER(a) (((a)->flags & \
FCTX_ADDRINFO_FORWARDER) != 0)
static void destroy(dns_resolver_t *res);
static void empty_bucket(dns_resolver_t *res);
static isc_result_t resquery_send(resquery_t *query);
static void resquery_response(isc_task_t *task, isc_event_t *event);
static void resquery_connected(isc_task_t *task, isc_event_t *event);
static void fctx_try(fetchctx_t *fctx);
static isc_boolean_t fctx_destroy(fetchctx_t *fctx);
static isc_result_t ncache_adderesult(dns_message_t *message,
dns_db_t *cache, dns_dbnode_t *node,
dns_rdatatype_t covers,
isc_stdtime_t now, dns_ttl_t maxttl,
dns_rdataset_t *ardataset,
isc_result_t *eresultp);
static inline isc_result_t
fctx_starttimer(fetchctx_t *fctx) {
return (isc_timer_reset(fctx->timer, isc_timertype_once,
&fctx->expires, NULL,
ISC_TRUE));
}
static inline void
fctx_stoptimer(fetchctx_t *fctx) {
isc_result_t result;
result = isc_timer_reset(fctx->timer, isc_timertype_inactive,
NULL, NULL, ISC_TRUE);
if (result != ISC_R_SUCCESS) {
UNEXPECTED_ERROR(__FILE__, __LINE__,
"isc_timer_reset(): %s",
isc_result_totext(result));
}
}
static inline isc_result_t
fctx_startidletimer(fetchctx_t *fctx) {
return (isc_timer_reset(fctx->timer, isc_timertype_once,
&fctx->expires, &fctx->interval,
ISC_FALSE));
}
#define fctx_stopidletimer fctx_starttimer
static inline void
resquery_destroy(resquery_t **queryp) {
resquery_t *query;
REQUIRE(queryp != NULL);
query = *queryp;
REQUIRE(!ISC_LINK_LINKED(query, link));
INSIST(query->tcpsocket == NULL);
query->magic = 0;
isc_mem_put(query->mctx, query, sizeof(*query));
*queryp = NULL;
}
static void
fctx_cancelquery(resquery_t **queryp, dns_dispatchevent_t **deventp,
isc_time_t *finish, isc_boolean_t no_response)
{
fetchctx_t *fctx;
resquery_t *query;
unsigned int rtt;
unsigned int factor;
query = *queryp;
fctx = query->fctx;
FCTXTRACE("cancelquery");
REQUIRE(!RESQUERY_CANCELED(query));
query->attributes |= RESQUERY_ATTR_CANCELED;
if (finish != NULL || no_response) {
if (finish != NULL) {
rtt = (unsigned int)isc_time_microdiff(finish,
&query->start);
factor = DNS_ADB_RTTADJDEFAULT;
} else {
INSIST(no_response);
rtt = query->addrinfo->srtt +
(100000 * fctx->restarts);
if (rtt > 10000000)
rtt = 10000000;
factor = DNS_ADB_RTTADJREPLACE;
}
dns_adb_adjustsrtt(fctx->adb, query->addrinfo, rtt, factor);
}
if (finish != NULL) {
dns_adbfind_t *find;
dns_adbaddrinfo_t *addrinfo;
factor = DNS_ADB_RTTADJAGE;
for (find = ISC_LIST_HEAD(fctx->finds);
find != NULL;
find = ISC_LIST_NEXT(find, publink))
for (addrinfo = ISC_LIST_HEAD(find->list);
addrinfo != NULL;
addrinfo = ISC_LIST_NEXT(addrinfo, publink))
if (UNMARKED(addrinfo))
dns_adb_adjustsrtt(fctx->adb, addrinfo,
0, factor);
}
if (query->dispentry != NULL)
dns_dispatch_removeresponse(&query->dispentry, deventp);
ISC_LIST_UNLINK(fctx->queries, query, link);
if (query->tsig != NULL)
isc_buffer_free(&query->tsig);
if (query->tsigkey != NULL)
dns_tsigkey_detach(&query->tsigkey);
if (RESQUERY_CONNECTING(query))
isc_socket_cancel(query->tcpsocket, NULL,
ISC_SOCKCANCEL_CONNECT);
else if (RESQUERY_SENDING(query))
isc_socket_cancel(dns_dispatch_getsocket(query->dispatch),
NULL, ISC_SOCKCANCEL_SEND);
if (query->dispatch != NULL)
dns_dispatch_detach(&query->dispatch);
if (! (RESQUERY_CONNECTING(query) || RESQUERY_SENDING(query)))
resquery_destroy(&query);
}
static void
fctx_cancelqueries(fetchctx_t *fctx, isc_boolean_t no_response) {
resquery_t *query, *next_query;
FCTXTRACE("cancelqueries");
for (query = ISC_LIST_HEAD(fctx->queries);
query != NULL;
query = next_query) {
next_query = ISC_LIST_NEXT(query, link);
fctx_cancelquery(&query, NULL, NULL, no_response);
}
}
static void
fctx_cleanupfinds(fetchctx_t *fctx) {
dns_adbfind_t *find, *next_find;
REQUIRE(ISC_LIST_EMPTY(fctx->queries));
for (find = ISC_LIST_HEAD(fctx->finds);
find != NULL;
find = next_find) {
next_find = ISC_LIST_NEXT(find, publink);
ISC_LIST_UNLINK(fctx->finds, find, publink);
dns_adb_destroyfind(&find);
}
fctx->find = NULL;
}
static void
fctx_cleanupforwaddrs(fetchctx_t *fctx) {
dns_adbaddrinfo_t *addr, *next_addr;
REQUIRE(ISC_LIST_EMPTY(fctx->queries));
for (addr = ISC_LIST_HEAD(fctx->forwaddrs);
addr != NULL;
addr = next_addr) {
next_addr = ISC_LIST_NEXT(addr, publink);
ISC_LIST_UNLINK(fctx->forwaddrs, addr, publink);
dns_adb_freeaddrinfo(fctx->adb, &addr);
}
}
static inline void
fctx_stopeverything(fetchctx_t *fctx, isc_boolean_t no_response) {
FCTXTRACE("stopeverything");
fctx_cancelqueries(fctx, no_response);
fctx_cleanupfinds(fctx);
fctx_cleanupforwaddrs(fctx);
fctx_stoptimer(fctx);
}
static inline void
fctx_sendevents(fetchctx_t *fctx, isc_result_t result) {
dns_fetchevent_t *event, *next_event;
isc_task_t *task;
REQUIRE(fctx->state == fetchstate_done);
FCTXTRACE("sendevents");
for (event = ISC_LIST_HEAD(fctx->events);
event != NULL;
event = next_event) {
next_event = ISC_LIST_NEXT(event, ev_link);
ISC_LIST_UNLINK(fctx->events, event, ev_link);
task = event->ev_sender;
event->ev_sender = fctx;
if (!HAVE_ANSWER(fctx))
event->result = result;
INSIST(result != ISC_R_SUCCESS ||
dns_rdataset_isassociated(event->rdataset) ||
fctx->type == dns_rdatatype_any ||
fctx->type == dns_rdatatype_sig);
isc_task_sendanddetach(&task, (isc_event_t **)&event);
}
}
static void
fctx_done(fetchctx_t *fctx, isc_result_t result) {
dns_resolver_t *res;
isc_boolean_t no_response;
FCTXTRACE("done");
res = fctx->res;
if (result == ISC_R_SUCCESS)
no_response = ISC_TRUE;
else
no_response = ISC_FALSE;
fctx_stopeverything(fctx, no_response);
LOCK(&res->buckets[fctx->bucketnum].lock);
fctx->state = fetchstate_done;
fctx->attributes &= ~FCTX_ATTR_ADDRWAIT;
fctx_sendevents(fctx, result);
UNLOCK(&res->buckets[fctx->bucketnum].lock);
}
static void
resquery_senddone(isc_task_t *task, isc_event_t *event) {
isc_socketevent_t *sevent = (isc_socketevent_t *)event;
resquery_t *query = event->ev_arg;
REQUIRE(event->ev_type == ISC_SOCKEVENT_SENDDONE);
QTRACE("senddone");
UNUSED(task);
INSIST(RESQUERY_SENDING(query));
query->sends--;
if (RESQUERY_CANCELED(query)) {
if (query->sends == 0) {
if (query->tcpsocket != NULL)
isc_socket_detach(&query->tcpsocket);
resquery_destroy(&query);
}
} else if (sevent->result != ISC_R_SUCCESS)
fctx_cancelquery(&query, NULL, NULL, ISC_FALSE);
isc_event_free(&event);
}
static inline isc_result_t
fctx_addopt(dns_message_t *message) {
dns_rdataset_t *rdataset;
dns_rdatalist_t *rdatalist;
dns_rdata_t *rdata;
isc_result_t result;
rdatalist = NULL;
result = dns_message_gettemprdatalist(message, &rdatalist);
if (result != ISC_R_SUCCESS)
return (result);
rdata = NULL;
result = dns_message_gettemprdata(message, &rdata);
if (result != ISC_R_SUCCESS)
return (result);
rdataset = NULL;
result = dns_message_gettemprdataset(message, &rdataset);
if (result != ISC_R_SUCCESS)
return (result);
dns_rdataset_init(rdataset);
rdatalist->type = dns_rdatatype_opt;
rdatalist->covers = 0;
rdatalist->rdclass = SEND_BUFFER_SIZE;
#ifdef ISC_RFC2535
rdatalist->ttl = DNS_MESSAGEEXTFLAG_DO;
#else
rdatalist->ttl = 0;
#endif
rdata->data = NULL;
rdata->length = 0;
rdata->rdclass = rdatalist->rdclass;
rdata->type = rdatalist->type;
rdata->flags = 0;
ISC_LIST_INIT(rdatalist->rdata);
ISC_LIST_APPEND(rdatalist->rdata, rdata, link);
dns_rdatalist_tordataset(rdatalist, rdataset);
return (dns_message_setopt(message, rdataset));
}
static inline void
fctx_setretryinterval(fetchctx_t *fctx, unsigned int rtt) {
unsigned int seconds;
if (fctx->restarts < 3)
seconds = 2;
else
seconds = (2 << (fctx->restarts - 1));
rtt /= 500000;
if (seconds < rtt)
seconds = rtt;
if (seconds > 30)
seconds = 30;
isc_interval_set(&fctx->interval, seconds, 0);
}
static isc_result_t
fctx_query(fetchctx_t *fctx, dns_adbaddrinfo_t *addrinfo,
unsigned int options)
{
dns_resolver_t *res;
isc_task_t *task;
isc_result_t result;
resquery_t *query;
FCTXTRACE("query");
res = fctx->res;
task = res->buckets[fctx->bucketnum].task;
fctx_setretryinterval(fctx, addrinfo->srtt);
result = fctx_startidletimer(fctx);
if (result != ISC_R_SUCCESS)
return (result);
dns_message_reset(fctx->rmessage, DNS_MESSAGE_INTENTPARSE);
query = isc_mem_get(res->mctx, sizeof *query);
if (query == NULL) {
result = ISC_R_NOMEMORY;
goto stop_idle_timer;
}
query->mctx = res->mctx;
query->options = options;
query->attributes = 0;
query->sends = 0;
query->connects = 0;
query->addrinfo = addrinfo;
result = isc_time_now(&query->start);
if (result != ISC_R_SUCCESS)
goto cleanup_query;
query->dispatchmgr = res->dispatchmgr;
query->dispatch = NULL;
query->tcpsocket = NULL;
if ((query->options & DNS_FETCHOPT_TCP) != 0) {
isc_sockaddr_t addr;
int pf;
pf = isc_sockaddr_pf(&addrinfo->sockaddr);
switch (pf) {
case PF_INET:
result = dns_dispatch_getlocaladdress(res->dispatchv4,
&addr);
break;
case PF_INET6:
result = dns_dispatch_getlocaladdress(res->dispatchv6,
&addr);
break;
default:
result = ISC_R_NOTIMPLEMENTED;
break;
}
if (result != ISC_R_SUCCESS)
goto cleanup_query;
isc_sockaddr_setport(&addr, 0);
result = isc_socket_create(res->socketmgr, pf,
isc_sockettype_tcp,
&query->tcpsocket);
if (result != ISC_R_SUCCESS)
goto cleanup_query;
result = isc_socket_bind(query->tcpsocket, &addr);
if (result != ISC_R_SUCCESS)
goto cleanup_socket;
} else {
switch (isc_sockaddr_pf(&addrinfo->sockaddr)) {
case PF_INET:
dns_dispatch_attach(res->dispatchv4, &query->dispatch);
break;
case PF_INET6:
dns_dispatch_attach(res->dispatchv6, &query->dispatch);
break;
default:
result = ISC_R_NOTIMPLEMENTED;
goto cleanup_query;
}
INSIST(query->dispatch != NULL);
}
query->dispentry = NULL;
query->fctx = fctx;
query->tsig = NULL;
query->tsigkey = NULL;
ISC_LINK_INIT(query, link);
query->magic = QUERY_MAGIC;
if ((query->options & DNS_FETCHOPT_TCP) != 0) {
result = isc_socket_connect(query->tcpsocket,
&addrinfo->sockaddr, task,
resquery_connected, query);
if (result != ISC_R_SUCCESS)
goto cleanup_socket;
query->connects++;
QTRACE("connecting via TCP");
} else {
result = resquery_send(query);
if (result != ISC_R_SUCCESS)
goto cleanup_dispatch;
}
ISC_LIST_APPEND(fctx->queries, query, link);
return (ISC_R_SUCCESS);
cleanup_socket:
isc_socket_detach(&query->tcpsocket);
cleanup_dispatch:
if (query->dispatch != NULL)
dns_dispatch_detach(&query->dispatch);
cleanup_query:
query->magic = 0;
isc_mem_put(res->mctx, query, sizeof *query);
stop_idle_timer:
fctx_stopidletimer(fctx);
return (result);
}
static isc_result_t
resquery_send(resquery_t *query) {
fetchctx_t *fctx;
isc_result_t result;
dns_name_t *qname = NULL;
dns_rdataset_t *qrdataset = NULL;
isc_region_t r;
dns_resolver_t *res;
isc_task_t *task;
isc_socket_t *socket;
isc_buffer_t tcpbuffer;
isc_sockaddr_t *address;
isc_buffer_t *buffer;
isc_netaddr_t ipaddr;
dns_tsigkey_t *tsigkey = NULL;
dns_peer_t *peer = NULL;
isc_boolean_t useedns;
dns_compress_t cctx;
isc_boolean_t cleanup_cctx = ISC_FALSE;
fctx = query->fctx;
QTRACE("send");
res = fctx->res;
task = res->buckets[fctx->bucketnum].task;
address = NULL;
if ((query->options & DNS_FETCHOPT_TCP) != 0) {
isc_buffer_init(&tcpbuffer, query->data, sizeof(query->data));
isc_buffer_init(&query->buffer, query->data + 2,
sizeof(query->data) - 2);
buffer = &tcpbuffer;
} else {
isc_buffer_init(&query->buffer, query->data,
sizeof(query->data));
buffer = &query->buffer;
}
result = dns_message_gettempname(fctx->qmessage, &qname);
if (result != ISC_R_SUCCESS)
goto cleanup_temps;
result = dns_message_gettemprdataset(fctx->qmessage, &qrdataset);
if (result != ISC_R_SUCCESS)
goto cleanup_temps;
result = dns_dispatch_addresponse(query->dispatch,
&query->addrinfo->sockaddr,
task,
resquery_response,
query,
&query->id,
&query->dispentry);
if (result != ISC_R_SUCCESS)
goto cleanup_temps;
fctx->qmessage->opcode = dns_opcode_query;
dns_name_init(qname, NULL);
dns_name_clone(&fctx->name, qname);
dns_rdataset_init(qrdataset);
dns_rdataset_makequestion(qrdataset, res->rdclass, fctx->type);
ISC_LIST_APPEND(qname->list, qrdataset, link);
dns_message_addname(fctx->qmessage, qname, DNS_SECTION_QUESTION);
qname = NULL;
qrdataset = NULL;
if ((query->options & DNS_FETCHOPT_RECURSIVE) != 0 ||
ISFORWARDER(query->addrinfo))
fctx->qmessage->flags |= DNS_MESSAGEFLAG_RD;
fctx->qmessage->id = query->id;
result = dns_compress_init(&cctx, -1, fctx->res->mctx);
if (result != ISC_R_SUCCESS)
goto cleanup_message;
cleanup_cctx = ISC_TRUE;
result = dns_message_renderbegin(fctx->qmessage, &cctx,
&query->buffer);
if (result != ISC_R_SUCCESS)
goto cleanup_message;
result = dns_message_rendersection(fctx->qmessage,
DNS_SECTION_QUESTION, 0);
if (result != ISC_R_SUCCESS)
goto cleanup_message;
peer = NULL;
isc_netaddr_fromsockaddr(&ipaddr, &query->addrinfo->sockaddr);
(void) dns_peerlist_peerbyaddr(fctx->res->view->peers, &ipaddr, &peer);
if ((query->addrinfo->flags & DNS_FETCHOPT_NOEDNS0) == 0 &&
peer != NULL &&
dns_peer_getsupportedns(peer, &useedns) == ISC_R_SUCCESS &&
!useedns)
{
query->options |= DNS_FETCHOPT_NOEDNS0;
dns_adb_changeflags(fctx->adb,
query->addrinfo,
DNS_FETCHOPT_NOEDNS0,
DNS_FETCHOPT_NOEDNS0);
}
if (fctx->timeouts >= MAX_EDNS0_TIMEOUTS &&
(query->options & DNS_FETCHOPT_NOEDNS0) == 0) {
query->options |= DNS_FETCHOPT_NOEDNS0;
FCTXTRACE("too many timeouts, disabling EDNS0");
}
if ((query->options & DNS_FETCHOPT_NOEDNS0) == 0) {
if ((query->addrinfo->flags & DNS_FETCHOPT_NOEDNS0) == 0) {
result = fctx_addopt(fctx->qmessage);
if (result != ISC_R_SUCCESS) {
query->options |= DNS_FETCHOPT_NOEDNS0;
}
} else {
query->options |= DNS_FETCHOPT_NOEDNS0;
}
}
if (NEEDEDNS0(fctx) && (query->options & DNS_FETCHOPT_NOEDNS0) != 0) {
result = DNS_R_SERVFAIL;
goto cleanup_message;
}
if ((query->options & DNS_FETCHOPT_NOEDNS0) == 0)
fctx->qmessage->flags |= DNS_MESSAGEFLAG_CD;
result = dns_view_getpeertsig(fctx->res->view, &ipaddr, &tsigkey);
if (result != ISC_R_SUCCESS && result != ISC_R_NOTFOUND)
goto cleanup_message;
if (tsigkey != NULL) {
dns_message_settsigkey(fctx->qmessage, tsigkey);
dns_tsigkey_detach(&tsigkey);
}
result = dns_message_rendersection(fctx->qmessage,
DNS_SECTION_ADDITIONAL, 0);
if (result != ISC_R_SUCCESS)
goto cleanup_message;
result = dns_message_renderend(fctx->qmessage);
if (result != ISC_R_SUCCESS)
goto cleanup_message;
dns_compress_invalidate(&cctx);
cleanup_cctx = ISC_FALSE;
if (dns_message_gettsigkey(fctx->qmessage) != NULL) {
dns_tsigkey_attach(dns_message_gettsigkey(fctx->qmessage),
&query->tsigkey);
result = dns_message_getquerytsig(fctx->qmessage,
fctx->res->mctx,
&query->tsig);
if (result != ISC_R_SUCCESS)
goto cleanup_message;
}
if ((query->options & DNS_FETCHOPT_TCP) != 0) {
isc_buffer_usedregion(&query->buffer, &r);
isc_buffer_putuint16(&tcpbuffer, (isc_uint16_t)r.length);
isc_buffer_add(&tcpbuffer, r.length);
}
dns_message_reset(fctx->qmessage, DNS_MESSAGE_INTENTRENDER);
socket = dns_dispatch_getsocket(query->dispatch);
if ((query->options & DNS_FETCHOPT_TCP) == 0)
address = &query->addrinfo->sockaddr;
isc_buffer_usedregion(buffer, &r);
result = isc_socket_sendto(socket, &r, task, resquery_senddone,
query, address, NULL);
if (result != ISC_R_SUCCESS)
goto cleanup_message;
query->sends++;
QTRACE("sent");
return (ISC_R_SUCCESS);
cleanup_message:
if (cleanup_cctx)
dns_compress_invalidate(&cctx);
dns_message_reset(fctx->qmessage, DNS_MESSAGE_INTENTRENDER);
dns_dispatch_removeresponse(&query->dispentry, NULL);
cleanup_temps:
if (qname != NULL)
dns_message_puttempname(fctx->qmessage, &qname);
if (qrdataset != NULL)
dns_message_puttemprdataset(fctx->qmessage, &qrdataset);
return (result);
}
static void
resquery_connected(isc_task_t *task, isc_event_t *event) {
isc_socketevent_t *sevent = (isc_socketevent_t *)event;
resquery_t *query = event->ev_arg;
isc_result_t result;
REQUIRE(event->ev_type == ISC_SOCKEVENT_CONNECT);
REQUIRE(VALID_QUERY(query));
QTRACE("connected");
UNUSED(task);
query->connects--;
if (RESQUERY_CANCELED(query)) {
isc_socket_detach(&query->tcpsocket);
resquery_destroy(&query);
} else {
if (sevent->result == ISC_R_SUCCESS) {
unsigned int attrs;
attrs = 0;
attrs |= DNS_DISPATCHATTR_TCP;
attrs |= DNS_DISPATCHATTR_PRIVATE;
attrs |= DNS_DISPATCHATTR_CONNECTED;
if (isc_sockaddr_pf(&query->addrinfo->sockaddr) ==
AF_INET)
attrs |= DNS_DISPATCHATTR_IPV4;
else
attrs |= DNS_DISPATCHATTR_IPV6;
attrs |= DNS_DISPATCHATTR_MAKEQUERY;
result = dns_dispatch_createtcp(query->dispatchmgr,
query->tcpsocket,
query->fctx->res->taskmgr,
4096, 2, 1, 1, 3, attrs,
&query->dispatch);
isc_socket_detach(&query->tcpsocket);
if (result == ISC_R_SUCCESS)
result = resquery_send(query);
if (result != ISC_R_SUCCESS) {
fetchctx_t *fctx = query->fctx;
fctx_cancelquery(&query, NULL, NULL,
ISC_FALSE);
fctx_done(fctx, result);
}
} else {
isc_socket_detach(&query->tcpsocket);
fctx_cancelquery(&query, NULL, NULL, ISC_FALSE);
}
}
isc_event_free(&event);
}
static void
fctx_finddone(isc_task_t *task, isc_event_t *event) {
fetchctx_t *fctx;
dns_adbfind_t *find;
dns_resolver_t *res;
isc_boolean_t want_try = ISC_FALSE;
isc_boolean_t want_done = ISC_FALSE;
isc_boolean_t bucket_empty = ISC_FALSE;
unsigned int bucketnum;
find = event->ev_sender;
fctx = event->ev_arg;
REQUIRE(VALID_FCTX(fctx));
res = fctx->res;
UNUSED(task);
FCTXTRACE("finddone");
INSIST(fctx->pending > 0);
fctx->pending--;
if (ADDRWAIT(fctx)) {
INSIST(!SHUTTINGDOWN(fctx));
fctx->attributes &= ~FCTX_ATTR_ADDRWAIT;
if (event->ev_type == DNS_EVENT_ADBMOREADDRESSES)
want_try = ISC_TRUE;
else if (fctx->pending == 0) {
want_done = ISC_TRUE;
}
} else if (SHUTTINGDOWN(fctx) && fctx->pending == 0 &&
ISC_LIST_EMPTY(fctx->validators)) {
bucketnum = fctx->bucketnum;
LOCK(&res->buckets[bucketnum].lock);
if (fctx->references == 0)
bucket_empty = fctx_destroy(fctx);
UNLOCK(&res->buckets[bucketnum].lock);
}
isc_event_free(&event);
dns_adb_destroyfind(&find);
if (want_try)
fctx_try(fctx);
else if (want_done)
fctx_done(fctx, ISC_R_FAILURE);
else if (bucket_empty)
empty_bucket(res);
}
static inline isc_boolean_t
bad_server(fetchctx_t *fctx, isc_sockaddr_t *address) {
isc_sockaddr_t *sa;
for (sa = ISC_LIST_HEAD(fctx->bad);
sa != NULL;
sa = ISC_LIST_NEXT(sa, link)) {
if (isc_sockaddr_equal(sa, address))
return (ISC_TRUE);
}
return (ISC_FALSE);
}
static inline isc_boolean_t
mark_bad(fetchctx_t *fctx) {
dns_adbfind_t *curr;
dns_adbaddrinfo_t *addrinfo;
isc_boolean_t all_bad = ISC_TRUE;
for (curr = ISC_LIST_HEAD(fctx->finds);
curr != NULL;
curr = ISC_LIST_NEXT(curr, publink)) {
for (addrinfo = ISC_LIST_HEAD(curr->list);
addrinfo != NULL;
addrinfo = ISC_LIST_NEXT(addrinfo, publink)) {
if (bad_server(fctx, &addrinfo->sockaddr))
addrinfo->flags |= FCTX_ADDRINFO_MARK;
else
all_bad = ISC_FALSE;
}
}
for (addrinfo = ISC_LIST_HEAD(fctx->forwaddrs);
addrinfo != NULL;
addrinfo = ISC_LIST_NEXT(addrinfo, publink)) {
if (bad_server(fctx, &addrinfo->sockaddr))
addrinfo->flags |= FCTX_ADDRINFO_MARK;
else
all_bad = ISC_FALSE;
}
return (all_bad);
}
static void
add_bad(fetchctx_t *fctx, isc_sockaddr_t *address) {
isc_sockaddr_t *sa;
if (bad_server(fctx, address)) {
return;
}
FCTXTRACE("add_bad");
sa = isc_mem_get(fctx->res->mctx, sizeof *sa);
if (sa == NULL)
return;
*sa = *address;
ISC_LIST_INITANDAPPEND(fctx->bad, sa, link);
}
static void
sort_adbfind(dns_adbfind_t *find) {
dns_adbaddrinfo_t *best, *curr;
dns_adbaddrinfolist_t sorted;
ISC_LIST_INIT(sorted);
while (!ISC_LIST_EMPTY(find->list)) {
best = ISC_LIST_HEAD(find->list);
curr = ISC_LIST_NEXT(best, publink);
while (curr != NULL) {
if (curr->srtt < best->srtt)
best = curr;
curr = ISC_LIST_NEXT(curr, publink);
}
ISC_LIST_UNLINK(find->list, best, publink);
ISC_LIST_APPEND(sorted, best, publink);
}
find->list = sorted;
}
static void
sort_finds(fetchctx_t *fctx) {
dns_adbfind_t *best, *curr;
dns_adbfindlist_t sorted;
dns_adbaddrinfo_t *addrinfo, *bestaddrinfo;
ISC_LIST_INIT(sorted);
while (!ISC_LIST_EMPTY(fctx->finds)) {
best = ISC_LIST_HEAD(fctx->finds);
bestaddrinfo = ISC_LIST_HEAD(best->list);
INSIST(bestaddrinfo != NULL);
curr = ISC_LIST_NEXT(best, publink);
while (curr != NULL) {
addrinfo = ISC_LIST_HEAD(curr->list);
INSIST(addrinfo != NULL);
if (addrinfo->srtt < bestaddrinfo->srtt) {
best = curr;
bestaddrinfo = addrinfo;
}
curr = ISC_LIST_NEXT(curr, publink);
}
ISC_LIST_UNLINK(fctx->finds, best, publink);
ISC_LIST_APPEND(sorted, best, publink);
}
fctx->finds = sorted;
}
static isc_result_t
fctx_getaddresses(fetchctx_t *fctx) {
dns_rdata_t rdata = DNS_RDATA_INIT;
isc_result_t result;
dns_resolver_t *res;
isc_stdtime_t now;
dns_adbfind_t *find;
unsigned int stdoptions, options;
isc_sockaddr_t *sa;
dns_adbaddrinfo_t *ai;
isc_boolean_t pruned, all_bad;
dns_rdata_ns_t ns;
FCTXTRACE("getaddresses");
fctx->restarts++;
if (fctx->restarts > 10) {
FCTXTRACE("too many restarts");
return (DNS_R_SERVFAIL);
}
res = fctx->res;
pruned = ISC_FALSE;
stdoptions = 0;
INSIST(ISC_LIST_EMPTY(fctx->forwaddrs));
sa = ISC_LIST_HEAD(fctx->forwarders);
if (sa == NULL) {
dns_forwarders_t *forwarders = NULL;
result = dns_fwdtable_find(fctx->res->view->fwdtable,
&fctx->name, &forwarders);
if (result == ISC_R_SUCCESS) {
sa = ISC_LIST_HEAD(forwarders->addrs);
fctx->fwdpolicy = forwarders->fwdpolicy;
}
}
while (sa != NULL) {
ai = NULL;
result = dns_adb_findaddrinfo(fctx->adb,
sa, &ai, 0);
if (result == ISC_R_SUCCESS) {
ai->flags |= FCTX_ADDRINFO_FORWARDER;
ISC_LIST_APPEND(fctx->forwaddrs, ai, publink);
}
sa = ISC_LIST_NEXT(sa, link);
}
if (fctx->fwdpolicy == dns_fwdpolicy_only)
goto out;
stdoptions = DNS_ADBFIND_WANTEVENT | DNS_ADBFIND_EMPTYEVENT;
if (fctx->restarts == 1) {
stdoptions |= DNS_ADBFIND_AVOIDFETCHES;
}
if (res->dispatchv4 != NULL)
stdoptions |= DNS_ADBFIND_INET;
if (res->dispatchv6 != NULL)
stdoptions |= DNS_ADBFIND_INET6;
isc_stdtime_get(&now);
restart:
INSIST(ISC_LIST_EMPTY(fctx->finds));
result = dns_rdataset_first(&fctx->nameservers);
while (result == ISC_R_SUCCESS) {
dns_rdataset_current(&fctx->nameservers, &rdata);
result = dns_rdata_tostruct(&rdata, &ns, NULL);
if (result != ISC_R_SUCCESS) {
dns_rdataset_next(&fctx->nameservers);
continue;
}
options = stdoptions;
if (dns_name_issubdomain(&ns.name, &fctx->domain))
options |= DNS_ADBFIND_STARTATROOT;
options |= DNS_ADBFIND_GLUEOK;
options |= DNS_ADBFIND_HINTOK;
find = NULL;
result = dns_adb_createfind(fctx->adb,
res->buckets[fctx->bucketnum].task,
fctx_finddone, fctx, &ns.name,
&fctx->domain, options, now, NULL,
res->view->dstport, &find);
if (result != ISC_R_SUCCESS) {
if (result == DNS_R_ALIAS) {
dns_adb_destroyfind(&find);
}
} else if (!ISC_LIST_EMPTY(find->list)) {
INSIST((find->options & DNS_ADBFIND_WANTEVENT) == 0);
sort_adbfind(find);
ISC_LIST_APPEND(fctx->finds, find, publink);
} else {
if ((find->options & DNS_ADBFIND_WANTEVENT) != 0) {
fctx->pending++;
} else {
if ((find->options & DNS_ADBFIND_LAMEPRUNED)
!= 0) {
pruned = ISC_TRUE;
}
dns_adb_destroyfind(&find);
}
}
dns_rdata_reset(&rdata);
dns_rdata_freestruct(&ns);
result = dns_rdataset_next(&fctx->nameservers);
}
if (result != ISC_R_NOMORE)
return (result);
out:
all_bad = mark_bad(fctx);
if (all_bad) {
if (fctx->pending > 0) {
result = DNS_R_WAIT;
} else if (pruned) {
FCTXTRACE("restarting with returnlame");
INSIST((stdoptions & DNS_ADBFIND_RETURNLAME) == 0);
stdoptions |= DNS_ADBFIND_RETURNLAME;
pruned = ISC_FALSE;
fctx_cleanupfinds(fctx);
goto restart;
} else {
FCTXTRACE("no addresses");
result = ISC_R_FAILURE;
}
} else {
sort_finds(fctx);
result = ISC_R_SUCCESS;
}
return (result);
}
static inline void
possibly_mark(fetchctx_t *fctx, dns_adbaddrinfo_t *addr)
{
isc_netaddr_t na;
char buf[ISC_NETADDR_FORMATSIZE];
isc_sockaddr_t *sa;
isc_boolean_t aborted = ISC_FALSE;
isc_boolean_t bogus;
dns_acl_t *blackhole;
isc_netaddr_t ipaddr;
dns_peer_t *peer = NULL;
dns_resolver_t *res;
const char *msg = NULL;
sa = &addr->sockaddr;
res = fctx->res;
isc_netaddr_fromsockaddr(&ipaddr, sa);
blackhole = dns_dispatchmgr_getblackhole(res->dispatchmgr);
(void) dns_peerlist_peerbyaddr(res->view->peers, &ipaddr, &peer);
if (blackhole != NULL) {
int match;
if (dns_acl_match(&ipaddr, NULL, blackhole,
&res->view->aclenv,
&match, NULL) == ISC_R_SUCCESS &&
match > 0)
aborted = ISC_TRUE;
}
if (peer != NULL &&
dns_peer_getbogus(peer, &bogus) == ISC_R_SUCCESS &&
bogus)
aborted = ISC_TRUE;
if (aborted) {
addr->flags |= FCTX_ADDRINFO_MARK;
msg = "ignoring blackholed / bogus server: ";
} else if (sa->type.sa.sa_family != AF_INET6) {
return;
} else if (IN6_IS_ADDR_V4MAPPED(&sa->type.sin6.sin6_addr)) {
addr->flags |= FCTX_ADDRINFO_MARK;
msg = "ignoring IPv6 mapped IPV4 address: ";
} else if (IN6_IS_ADDR_V4COMPAT(&sa->type.sin6.sin6_addr)) {
addr->flags |= FCTX_ADDRINFO_MARK;
msg = "ignoring IPv6 compatibility IPV4 address: ";
} else
return;
if (!isc_log_wouldlog(dns_lctx, ISC_LOG_DEBUG(3)))
return;
isc_netaddr_fromsockaddr(&na, sa);
isc_netaddr_format(&na, buf, sizeof buf);
FCTXTRACE2(msg, buf);
}
static inline dns_adbaddrinfo_t *
fctx_nextaddress(fetchctx_t *fctx) {
dns_adbfind_t *find;
dns_adbaddrinfo_t *addrinfo;
for (addrinfo = ISC_LIST_HEAD(fctx->forwaddrs);
addrinfo != NULL;
addrinfo = ISC_LIST_NEXT(addrinfo, publink)) {
possibly_mark(fctx, addrinfo);
if (UNMARKED(addrinfo)) {
addrinfo->flags |= FCTX_ADDRINFO_MARK;
fctx->find = NULL;
return (addrinfo);
}
}
find = fctx->find;
if (find == NULL)
find = ISC_LIST_HEAD(fctx->finds);
else {
find = ISC_LIST_NEXT(find, publink);
if (find == NULL)
find = ISC_LIST_HEAD(fctx->finds);
}
addrinfo = NULL;
while (find != fctx->find) {
for (addrinfo = ISC_LIST_HEAD(find->list);
addrinfo != NULL;
addrinfo = ISC_LIST_NEXT(addrinfo, publink)) {
possibly_mark(fctx, addrinfo);
if (UNMARKED(addrinfo)) {
addrinfo->flags |= FCTX_ADDRINFO_MARK;
break;
}
}
if (addrinfo != NULL)
break;
find = ISC_LIST_NEXT(find, publink);
if (find != fctx->find && find == NULL)
find = ISC_LIST_HEAD(fctx->finds);
}
fctx->find = find;
return (addrinfo);
}
static void
fctx_try(fetchctx_t *fctx) {
isc_result_t result;
dns_adbaddrinfo_t *addrinfo;
FCTXTRACE("try");
REQUIRE(!ADDRWAIT(fctx));
addrinfo = fctx_nextaddress(fctx);
if (addrinfo == NULL) {
fctx_cancelqueries(fctx, ISC_TRUE);
fctx_cleanupfinds(fctx);
fctx_cleanupforwaddrs(fctx);
result = fctx_getaddresses(fctx);
if (result == DNS_R_WAIT) {
FCTXTRACE("addrwait");
fctx->attributes |= FCTX_ATTR_ADDRWAIT;
return;
} else if (result != ISC_R_SUCCESS) {
fctx_done(fctx, result);
return;
}
addrinfo = fctx_nextaddress(fctx);
if (addrinfo == NULL) {
fctx_done(fctx, DNS_R_SERVFAIL);
return;
}
}
result = fctx_query(fctx, addrinfo, fctx->options);
if (result != ISC_R_SUCCESS)
fctx_done(fctx, result);
}
static isc_boolean_t
fctx_destroy(fetchctx_t *fctx) {
dns_resolver_t *res;
unsigned int bucketnum;
isc_sockaddr_t *sa, *next_sa;
REQUIRE(VALID_FCTX(fctx));
REQUIRE(fctx->state == fetchstate_done ||
fctx->state == fetchstate_init);
REQUIRE(ISC_LIST_EMPTY(fctx->events));
REQUIRE(ISC_LIST_EMPTY(fctx->queries));
REQUIRE(ISC_LIST_EMPTY(fctx->finds));
REQUIRE(fctx->pending == 0);
REQUIRE(ISC_LIST_EMPTY(fctx->validators));
REQUIRE(fctx->references == 0);
FCTXTRACE("destroy");
res = fctx->res;
bucketnum = fctx->bucketnum;
ISC_LIST_UNLINK(res->buckets[bucketnum].fctxs, fctx, link);
for (sa = ISC_LIST_HEAD(fctx->bad);
sa != NULL;
sa = next_sa) {
next_sa = ISC_LIST_NEXT(sa, link);
ISC_LIST_UNLINK(fctx->bad, sa, link);
isc_mem_put(res->mctx, sa, sizeof *sa);
}
isc_timer_detach(&fctx->timer);
dns_message_destroy(&fctx->rmessage);
dns_message_destroy(&fctx->qmessage);
if (dns_name_countlabels(&fctx->domain) > 0)
dns_name_free(&fctx->domain, res->mctx);
if (dns_rdataset_isassociated(&fctx->nameservers))
dns_rdataset_disassociate(&fctx->nameservers);
dns_name_free(&fctx->name, res->mctx);
dns_db_detach(&fctx->cache);
dns_adb_detach(&fctx->adb);
isc_mem_put(res->mctx, fctx, sizeof *fctx);
if (res->buckets[bucketnum].exiting &&
ISC_LIST_EMPTY(res->buckets[bucketnum].fctxs))
return (ISC_TRUE);
return (ISC_FALSE);
}
static void
fctx_timeout(isc_task_t *task, isc_event_t *event) {
fetchctx_t *fctx = event->ev_arg;
REQUIRE(VALID_FCTX(fctx));
UNUSED(task);
FCTXTRACE("timeout");
if (event->ev_type == ISC_TIMEREVENT_LIFE) {
fctx_done(fctx, ISC_R_TIMEDOUT);
} else {
fctx->timeouts++;
fctx->attributes &= ~FCTX_ATTR_ADDRWAIT;
fctx_starttimer(fctx);
fctx_try(fctx);
}
isc_event_free(&event);
}
static void
fctx_shutdown(fetchctx_t *fctx) {
isc_event_t *cevent;
FCTXTRACE("shutdown");
if (fctx->want_shutdown)
return;
fctx->want_shutdown = ISC_TRUE;
if (fctx->state != fetchstate_init) {
cevent = &fctx->control_event;
isc_task_send(fctx->res->buckets[fctx->bucketnum].task,
&cevent);
}
}
static void
fctx_doshutdown(isc_task_t *task, isc_event_t *event) {
fetchctx_t *fctx = event->ev_arg;
isc_boolean_t bucket_empty = ISC_FALSE;
dns_resolver_t *res;
unsigned int bucketnum;
dns_validator_t *validator;
REQUIRE(VALID_FCTX(fctx));
UNUSED(task);
res = fctx->res;
bucketnum = fctx->bucketnum;
FCTXTRACE("doshutdown");
fctx->attributes &= ~FCTX_ATTR_ADDRWAIT;
validator = ISC_LIST_HEAD(fctx->validators);
while (validator != NULL) {
dns_validator_cancel(validator);
validator = ISC_LIST_NEXT(validator, link);
}
fctx_stopeverything(fctx, ISC_FALSE);
LOCK(&res->buckets[bucketnum].lock);
fctx->attributes |= FCTX_ATTR_SHUTTINGDOWN;
INSIST(fctx->state == fetchstate_active ||
fctx->state == fetchstate_done);
INSIST(fctx->want_shutdown);
if (fctx->state != fetchstate_done) {
fctx->state = fetchstate_done;
fctx_sendevents(fctx, ISC_R_CANCELED);
}
if (fctx->references == 0 && fctx->pending == 0 &&
ISC_LIST_EMPTY(fctx->validators))
bucket_empty = fctx_destroy(fctx);
UNLOCK(&res->buckets[bucketnum].lock);
if (bucket_empty)
empty_bucket(res);
}
static void
fctx_start(isc_task_t *task, isc_event_t *event) {
fetchctx_t *fctx = event->ev_arg;
isc_boolean_t done = ISC_FALSE, bucket_empty = ISC_FALSE;
dns_resolver_t *res;
unsigned int bucketnum;
REQUIRE(VALID_FCTX(fctx));
UNUSED(task);
res = fctx->res;
bucketnum = fctx->bucketnum;
FCTXTRACE("start");
LOCK(&res->buckets[bucketnum].lock);
INSIST(fctx->state == fetchstate_init);
if (fctx->want_shutdown) {
fctx->attributes |= FCTX_ATTR_SHUTTINGDOWN;
fctx->state = fetchstate_done;
fctx_sendevents(fctx, ISC_R_CANCELED);
INSIST(fctx->pending == 0);
INSIST(ISC_LIST_EMPTY(fctx->validators));
if (fctx->references == 0) {
bucket_empty = fctx_destroy(fctx);
}
done = ISC_TRUE;
} else {
fctx->state = fetchstate_active;
ISC_EVENT_INIT(event, sizeof(*event), 0, NULL,
DNS_EVENT_FETCHCONTROL, fctx_doshutdown, fctx,
NULL, NULL, NULL);
}
UNLOCK(&res->buckets[bucketnum].lock);
if (!done) {
fctx_starttimer(fctx);
fctx_try(fctx);
} else if (bucket_empty)
empty_bucket(res);
}
static inline isc_result_t
fctx_join(fetchctx_t *fctx, isc_task_t *task, isc_taskaction_t action,
void *arg, dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset,
dns_fetch_t *fetch)
{
isc_task_t *clone;
dns_fetchevent_t *event;
FCTXTRACE("join");
clone = NULL;
isc_task_attach(task, &clone);
event = (dns_fetchevent_t *)
isc_event_allocate(fctx->res->mctx, clone,
DNS_EVENT_FETCHDONE,
action, arg, sizeof *event);
if (event == NULL) {
isc_task_detach(&clone);
return (ISC_R_NOMEMORY);
}
event->result = DNS_R_SERVFAIL;
event->qtype = fctx->type;
event->db = NULL;
event->node = NULL;
event->rdataset = rdataset;
event->sigrdataset = sigrdataset;
event->fetch = fetch;
dns_fixedname_init(&event->foundname);
if (event->sigrdataset != NULL)
ISC_LIST_PREPEND(fctx->events, event, ev_link);
else
ISC_LIST_APPEND(fctx->events, event, ev_link);
fctx->references++;
fetch->magic = DNS_FETCH_MAGIC;
fetch->private = fctx;
return (ISC_R_SUCCESS);
}
static isc_result_t
fctx_create(dns_resolver_t *res, dns_name_t *name, dns_rdatatype_t type,
dns_name_t *domain, dns_rdataset_t *nameservers,
unsigned int options, unsigned int bucketnum, fetchctx_t **fctxp)
{
fetchctx_t *fctx;
isc_result_t result = ISC_R_SUCCESS;
isc_result_t iresult;
isc_interval_t interval;
dns_fixedname_t qdomain;
unsigned int findoptions = 0;
REQUIRE(fctxp != NULL && *fctxp == NULL);
fctx = isc_mem_get(res->mctx, sizeof *fctx);
if (fctx == NULL)
return (ISC_R_NOMEMORY);
FCTXTRACE("create");
dns_name_init(&fctx->name, NULL);
result = dns_name_dup(name, res->mctx, &fctx->name);
if (result != ISC_R_SUCCESS)
goto cleanup_fetch;
dns_name_init(&fctx->domain, NULL);
dns_rdataset_init(&fctx->nameservers);
fctx->type = type;
fctx->options = options;
fctx->res = res;
fctx->references = 0;
fctx->bucketnum = bucketnum;
fctx->state = fetchstate_init;
fctx->want_shutdown = ISC_FALSE;
fctx->cloned = ISC_FALSE;
ISC_LIST_INIT(fctx->queries);
ISC_LIST_INIT(fctx->finds);
ISC_LIST_INIT(fctx->forwaddrs);
ISC_LIST_INIT(fctx->forwarders);
fctx->fwdpolicy = dns_fwdpolicy_none;
ISC_LIST_INIT(fctx->bad);
ISC_LIST_INIT(fctx->validators);
fctx->find = NULL;
fctx->pending = 0;
fctx->restarts = 0;
fctx->timeouts = 0;
if (dns_name_requiresedns(name))
fctx->attributes = FCTX_ATTR_NEEDEDNS0;
else
fctx->attributes = 0;
if (domain == NULL) {
dns_forwarders_t *forwarders = NULL;
result = dns_fwdtable_find(fctx->res->view->fwdtable,
&fctx->name, &forwarders);
if (result == ISC_R_SUCCESS)
fctx->fwdpolicy = forwarders->fwdpolicy;
if (fctx->fwdpolicy != dns_fwdpolicy_only) {
if (type == dns_rdatatype_key)
findoptions |= DNS_DBFIND_NOEXACT;
dns_fixedname_init(&qdomain);
result = dns_view_findzonecut(res->view, name,
dns_fixedname_name(&qdomain), 0,
findoptions, ISC_TRUE,
&fctx->nameservers,
NULL);
if (result != ISC_R_SUCCESS)
goto cleanup_name;
result = dns_name_dup(dns_fixedname_name(&qdomain),
res->mctx, &fctx->domain);
if (result != ISC_R_SUCCESS) {
dns_rdataset_disassociate(&fctx->nameservers);
goto cleanup_name;
}
} else {
result = dns_name_dup(dns_rootname, res->mctx,
&fctx->domain);
if (result != ISC_R_SUCCESS)
goto cleanup_name;
}
} else {
result = dns_name_dup(domain, res->mctx, &fctx->domain);
if (result != ISC_R_SUCCESS)
goto cleanup_name;
dns_rdataset_clone(nameservers, &fctx->nameservers);
}
INSIST(dns_name_issubdomain(&fctx->name, &fctx->domain));
fctx->qmessage = NULL;
result = dns_message_create(res->mctx, DNS_MESSAGE_INTENTRENDER,
&fctx->qmessage);
if (result != ISC_R_SUCCESS)
goto cleanup_domain;
fctx->rmessage = NULL;
result = dns_message_create(res->mctx, DNS_MESSAGE_INTENTPARSE,
&fctx->rmessage);
if (result != ISC_R_SUCCESS)
goto cleanup_qmessage;
isc_interval_set(&interval, 90, 0);
iresult = isc_time_nowplusinterval(&fctx->expires, &interval);
if (iresult != ISC_R_SUCCESS) {
UNEXPECTED_ERROR(__FILE__, __LINE__,
"isc_time_nowplusinterval: %s",
isc_result_totext(iresult));
result = ISC_R_UNEXPECTED;
goto cleanup_rmessage;
}
isc_interval_set(&fctx->interval, 2, 0);
fctx->timer = NULL;
iresult = isc_timer_create(res->timermgr, isc_timertype_inactive,
NULL, NULL,
res->buckets[bucketnum].task, fctx_timeout,
fctx, &fctx->timer);
if (iresult != ISC_R_SUCCESS) {
UNEXPECTED_ERROR(__FILE__, __LINE__,
"isc_timer_create: %s",
isc_result_totext(iresult));
result = ISC_R_UNEXPECTED;
goto cleanup_rmessage;
}
fctx->cache = NULL;
dns_db_attach(res->view->cachedb, &fctx->cache);
fctx->adb = NULL;
dns_adb_attach(res->view->adb, &fctx->adb);
ISC_LIST_INIT(fctx->events);
ISC_LINK_INIT(fctx, link);
fctx->magic = FCTX_MAGIC;
ISC_LIST_APPEND(res->buckets[bucketnum].fctxs, fctx, link);
*fctxp = fctx;
return (ISC_R_SUCCESS);
cleanup_rmessage:
dns_message_destroy(&fctx->rmessage);
cleanup_qmessage:
dns_message_destroy(&fctx->qmessage);
cleanup_domain:
if (dns_name_countlabels(&fctx->domain) > 0)
dns_name_free(&fctx->domain, res->mctx);
if (dns_rdataset_isassociated(&fctx->nameservers))
dns_rdataset_disassociate(&fctx->nameservers);
cleanup_name:
dns_name_free(&fctx->name, res->mctx);
cleanup_fetch:
isc_mem_put(res->mctx, fctx, sizeof *fctx);
return (result);
}
static inline isc_boolean_t
is_lame(fetchctx_t *fctx) {
dns_message_t *message = fctx->rmessage;
dns_name_t *name;
dns_rdataset_t *rdataset;
isc_result_t result;
if (message->rcode != dns_rcode_noerror &&
message->rcode != dns_rcode_nxdomain)
return (ISC_FALSE);
if (message->counts[DNS_SECTION_ANSWER] != 0)
return (ISC_FALSE);
if (message->counts[DNS_SECTION_AUTHORITY] == 0)
return (ISC_FALSE);
result = dns_message_firstname(message, DNS_SECTION_AUTHORITY);
while (result == ISC_R_SUCCESS) {
name = NULL;
dns_message_currentname(message, DNS_SECTION_AUTHORITY, &name);
for (rdataset = ISC_LIST_HEAD(name->list);
rdataset != NULL;
rdataset = ISC_LIST_NEXT(rdataset, link)) {
dns_namereln_t namereln;
int order;
unsigned int labels, bits;
if (rdataset->type != dns_rdatatype_ns)
continue;
namereln = dns_name_fullcompare(name, &fctx->domain,
&order, &labels, &bits);
if (namereln == dns_namereln_equal &&
(message->flags & DNS_MESSAGEFLAG_AA) != 0)
return (ISC_FALSE);
if (namereln == dns_namereln_subdomain)
return (ISC_FALSE);
return (ISC_TRUE);
}
result = dns_message_nextname(message, DNS_SECTION_AUTHORITY);
}
return (ISC_FALSE);
}
static inline void
log_lame(fetchctx_t *fctx, dns_adbaddrinfo_t *addrinfo) {
char namebuf[DNS_NAME_FORMATSIZE];
char domainbuf[DNS_NAME_FORMATSIZE];
char addrbuf[ISC_SOCKADDR_FORMATSIZE];
dns_name_format(&fctx->name, namebuf, sizeof(namebuf));
dns_name_format(&fctx->domain, domainbuf, sizeof(domainbuf));
isc_sockaddr_format(&addrinfo->sockaddr, addrbuf, sizeof(addrbuf));
isc_log_write(dns_lctx, DNS_LOGCATEGORY_LAME_SERVERS,
DNS_LOGMODULE_RESOLVER, ISC_LOG_INFO,
"lame server resolving '%s' (in '%s'?): %s",
namebuf, domainbuf, addrbuf);
}
static inline isc_result_t
same_question(fetchctx_t *fctx) {
isc_result_t result;
dns_message_t *message = fctx->rmessage;
dns_name_t *name;
dns_rdataset_t *rdataset;
if (message->counts[DNS_SECTION_QUESTION] != 1)
return (DNS_R_FORMERR);
result = dns_message_firstname(message, DNS_SECTION_QUESTION);
if (result != ISC_R_SUCCESS)
return (result);
name = NULL;
dns_message_currentname(message, DNS_SECTION_QUESTION, &name);
rdataset = ISC_LIST_HEAD(name->list);
INSIST(rdataset != NULL);
INSIST(ISC_LIST_NEXT(rdataset, link) == NULL);
if (fctx->type != rdataset->type ||
fctx->res->rdclass != rdataset->rdclass ||
!dns_name_equal(&fctx->name, name))
return (DNS_R_FORMERR);
return (ISC_R_SUCCESS);
}
static void
clone_results(fetchctx_t *fctx) {
dns_fetchevent_t *event, *hevent;
isc_result_t result;
dns_name_t *name, *hname;
fctx->cloned = ISC_TRUE;
hevent = ISC_LIST_HEAD(fctx->events);
if (hevent == NULL)
return;
hname = dns_fixedname_name(&hevent->foundname);
for (event = ISC_LIST_NEXT(hevent, ev_link);
event != NULL;
event = ISC_LIST_NEXT(event, ev_link)) {
name = dns_fixedname_name(&event->foundname);
result = dns_name_copy(hname, name, NULL);
if (result != ISC_R_SUCCESS)
event->result = result;
else
event->result = hevent->result;
dns_db_attach(hevent->db, &event->db);
dns_db_attachnode(hevent->db, hevent->node, &event->node);
INSIST(hevent->rdataset != NULL);
INSIST(event->rdataset != NULL);
if (dns_rdataset_isassociated(hevent->rdataset))
dns_rdataset_clone(hevent->rdataset, event->rdataset);
INSIST(! (hevent->sigrdataset == NULL &&
event->sigrdataset != NULL));
if (hevent->sigrdataset != NULL &&
dns_rdataset_isassociated(hevent->sigrdataset) &&
event->sigrdataset != NULL)
dns_rdataset_clone(hevent->sigrdataset,
event->sigrdataset);
}
}
#define CACHE(r) (((r)->attributes & DNS_RDATASETATTR_CACHE) != 0)
#define ANSWER(r) (((r)->attributes & DNS_RDATASETATTR_ANSWER) != 0)
#define ANSWERSIG(r) (((r)->attributes & DNS_RDATASETATTR_ANSWERSIG) != 0)
#define EXTERNAL(r) (((r)->attributes & DNS_RDATASETATTR_EXTERNAL) != 0)
#define CHAINING(r) (((r)->attributes & DNS_RDATASETATTR_CHAINING) != 0)
#define CHASE(r) (((r)->attributes & DNS_RDATASETATTR_CHASE) != 0)
static void
maybe_destroy(fetchctx_t *fctx) {
unsigned int bucketnum;
isc_boolean_t bucket_empty = ISC_FALSE;
dns_resolver_t *res = fctx->res;
REQUIRE(SHUTTINGDOWN(fctx));
if (fctx->pending != 0 || !ISC_LIST_EMPTY(fctx->validators))
return;
bucketnum = fctx->bucketnum;
LOCK(&res->buckets[bucketnum].lock);
if (fctx->references == 0)
bucket_empty = fctx_destroy(fctx);
UNLOCK(&res->buckets[bucketnum].lock);
if (bucket_empty)
empty_bucket(res);
}
static void
validated(isc_task_t *task, isc_event_t *event) {
isc_result_t result = ISC_R_SUCCESS;
isc_result_t eresult = ISC_R_SUCCESS;
isc_stdtime_t now;
fetchctx_t *fctx;
dns_validatorevent_t *vevent;
dns_fetchevent_t *hevent;
dns_rdataset_t *ardataset = NULL;
dns_rdataset_t *asigrdataset = NULL;
dns_dbnode_t *node = NULL;
isc_boolean_t negative;
isc_boolean_t chaining;
isc_boolean_t sentresponse;
isc_uint32_t ttl;
UNUSED(task);
REQUIRE(event->ev_type == DNS_EVENT_VALIDATORDONE);
fctx = event->ev_arg;
REQUIRE(VALID_FCTX(fctx));
REQUIRE(!ISC_LIST_EMPTY(fctx->validators));
vevent = (dns_validatorevent_t *)event;
FCTXTRACE("received validation completion event");
ISC_LIST_UNLINK(fctx->validators, vevent->validator, link);
dns_validator_destroy(&vevent->validator);
negative = ISC_TF(vevent->rdataset == NULL);
sentresponse = ISC_TF((fctx->options & DNS_FETCHOPT_NOVALIDATE) != 0);
if (SHUTTINGDOWN(fctx) && !sentresponse ) {
maybe_destroy(fctx);
goto cleanup_event;
}
if (vevent->result == ISC_R_SUCCESS &&
!negative &&
vevent->rdataset != NULL &&
CHAINING(vevent->rdataset))
{
if (vevent->rdataset->type == dns_rdatatype_cname)
eresult = DNS_R_CNAME;
else {
INSIST(vevent->rdataset->type == dns_rdatatype_dname);
eresult = DNS_R_DNAME;
}
chaining = ISC_TRUE;
} else
chaining = ISC_FALSE;
hevent = ISC_LIST_HEAD(fctx->events);
if (hevent != NULL) {
if (!negative && !chaining &&
(fctx->type == dns_rdatatype_any ||
fctx->type == dns_rdatatype_sig)) {
} else {
ardataset = hevent->rdataset;
asigrdataset = hevent->sigrdataset;
}
}
if (vevent->result != ISC_R_SUCCESS) {
FCTXTRACE("validation failed");
if (vevent->rdataset != NULL) {
result = dns_db_findnode(fctx->cache, vevent->name,
ISC_TRUE, &node);
if (result != ISC_R_SUCCESS)
goto noanswer_response;
(void)dns_db_deleterdataset(fctx->cache, node, NULL,
vevent->type, 0);
if (vevent->sigrdataset != NULL)
(void)dns_db_deleterdataset(fctx->cache,
node, NULL,
dns_rdatatype_sig,
vevent->type);
}
result = vevent->result;
goto noanswer_response;
}
isc_stdtime_get(&now);
if (negative) {
dns_rdatatype_t covers;
FCTXTRACE("nonexistence validation OK");
if (fctx->rmessage->rcode == dns_rcode_nxdomain)
covers = dns_rdatatype_any;
else
covers = fctx->type;
result = dns_db_findnode(fctx->cache, vevent->name, ISC_TRUE,
&node);
if (result != ISC_R_SUCCESS)
goto noanswer_response;
ttl = fctx->res->view->maxncachettl;
if (fctx->type == dns_rdatatype_soa &&
covers == dns_rdatatype_any)
ttl = 0;
result = ncache_adderesult(fctx->rmessage, fctx->cache, node,
covers, now, ttl,
ardataset, &eresult);
if (result != ISC_R_SUCCESS)
goto noanswer_response;
goto answer_response;
}
FCTXTRACE("validation OK");
result = dns_db_findnode(fctx->cache, vevent->name, ISC_TRUE, &node);
if (result != ISC_R_SUCCESS)
goto noanswer_response;
result = dns_db_addrdataset(fctx->cache, node, NULL, now,
vevent->rdataset, 0, ardataset);
if (result != ISC_R_SUCCESS &&
result != DNS_R_UNCHANGED)
goto noanswer_response;
if (vevent->sigrdataset != NULL) {
result = dns_db_addrdataset(fctx->cache, node, NULL, now,
vevent->sigrdataset, 0,
asigrdataset);
if (result != ISC_R_SUCCESS &&
result != DNS_R_UNCHANGED)
goto noanswer_response;
}
if (sentresponse) {
if (SHUTTINGDOWN(fctx))
maybe_destroy(fctx);
goto cleanup_event;
}
if (!ISC_LIST_EMPTY(fctx->validators)) {
INSIST(!negative);
INSIST(fctx->type == dns_rdatatype_any ||
fctx->type == dns_rdatatype_sig);
goto cleanup_event;
}
result = ISC_R_SUCCESS;
answer_response:
fctx->attributes |= FCTX_ATTR_HAVEANSWER;
if (hevent != NULL) {
hevent->result = eresult;
dns_name_copy(vevent->name,
dns_fixedname_name(&hevent->foundname), NULL);
dns_db_attach(fctx->cache, &hevent->db);
hevent->node = node;
node = NULL;
clone_results(fctx);
}
noanswer_response:
if (node != NULL)
dns_db_detachnode(fctx->cache, &node);
fctx_done(fctx, result);
cleanup_event:
isc_event_free(&event);
}
static inline isc_result_t
cache_name(fetchctx_t *fctx, dns_name_t *name, isc_stdtime_t now) {
dns_rdataset_t *rdataset, *sigrdataset;
dns_rdataset_t *addedrdataset, *ardataset, *asigrdataset;
dns_rdataset_t *valrdataset = NULL, *valsigrdataset = NULL;
dns_dbnode_t *node, **anodep;
dns_db_t **adbp;
dns_name_t *aname;
dns_resolver_t *res;
isc_boolean_t need_validation, secure_domain, have_answer;
isc_result_t result, eresult;
dns_fetchevent_t *event;
unsigned int options;
isc_task_t *task;
dns_validator_t *validator;
res = fctx->res;
need_validation = ISC_FALSE;
secure_domain = ISC_FALSE;
have_answer = ISC_FALSE;
eresult = ISC_R_SUCCESS;
task = res->buckets[fctx->bucketnum].task;
result = dns_keytable_issecuredomain(res->view->secroots, name,
&secure_domain);
if (result != ISC_R_SUCCESS)
return (result);
if ((fctx->options & DNS_FETCHOPT_NOVALIDATE) != 0)
need_validation = ISC_FALSE;
else
need_validation = secure_domain;
adbp = NULL;
aname = NULL;
anodep = NULL;
ardataset = NULL;
asigrdataset = NULL;
event = NULL;
if ((name->attributes & DNS_NAMEATTR_ANSWER) != 0 &&
!need_validation) {
have_answer = ISC_TRUE;
event = ISC_LIST_HEAD(fctx->events);
if (event != NULL) {
adbp = &event->db;
aname = dns_fixedname_name(&event->foundname);
result = dns_name_copy(name, aname, NULL);
if (result != ISC_R_SUCCESS)
return (result);
anodep = &event->node;
if ((fctx->type != dns_rdatatype_any &&
fctx->type != dns_rdatatype_sig) ||
(name->attributes & DNS_NAMEATTR_CHAINING) != 0) {
ardataset = event->rdataset;
asigrdataset = event->sigrdataset;
}
}
}
node = NULL;
result = dns_db_findnode(fctx->cache, name, ISC_TRUE, &node);
if (result != ISC_R_SUCCESS)
return (result);
for (rdataset = ISC_LIST_HEAD(name->list);
rdataset != NULL;
rdataset = ISC_LIST_NEXT(rdataset, link)) {
if (!CACHE(rdataset))
continue;
if (rdataset->ttl > res->view->maxcachettl)
rdataset->ttl = res->view->maxcachettl;
if (secure_domain && rdataset->trust != dns_trust_glue) {
if (rdataset->type == dns_rdatatype_sig)
continue;
for (sigrdataset = ISC_LIST_HEAD(name->list);
sigrdataset != NULL;
sigrdataset = ISC_LIST_NEXT(sigrdataset, link)) {
if (sigrdataset->type == dns_rdatatype_sig &&
sigrdataset->covers == rdataset->type)
break;
}
if (sigrdataset == NULL) {
if (!ANSWER(rdataset) && need_validation) {
continue;
}
}
if (sigrdataset != NULL) {
rdataset->ttl = ISC_MIN(rdataset->ttl,
sigrdataset->ttl);
sigrdataset->ttl = rdataset->ttl;
}
rdataset->trust = dns_trust_pending;
if (sigrdataset != NULL)
sigrdataset->trust = dns_trust_pending;
if (!need_validation)
addedrdataset = ardataset;
else
addedrdataset = NULL;
result = dns_db_addrdataset(fctx->cache, node, NULL,
now, rdataset, 0,
addedrdataset);
if (result == DNS_R_UNCHANGED)
result = ISC_R_SUCCESS;
if (result != ISC_R_SUCCESS)
break;
if (sigrdataset != NULL) {
if (!need_validation)
addedrdataset = asigrdataset;
else
addedrdataset = NULL;
result = dns_db_addrdataset(fctx->cache,
node, NULL, now,
sigrdataset, 0,
addedrdataset);
if (result == DNS_R_UNCHANGED)
result = ISC_R_SUCCESS;
if (result != ISC_R_SUCCESS)
break;
} else if (!ANSWER(rdataset))
continue;
if (ANSWER(rdataset) && need_validation) {
if (fctx->type != dns_rdatatype_any &&
fctx->type != dns_rdatatype_sig) {
INSIST(valrdataset == NULL &&
valsigrdataset == NULL);
valrdataset = rdataset;
valsigrdataset = sigrdataset;
} else {
validator = NULL;
result = dns_validator_create(
res->view,
name,
rdataset->type,
rdataset,
sigrdataset,
fctx->rmessage,
0,
task,
validated,
fctx,
&validator);
if (result == ISC_R_SUCCESS)
ISC_LIST_APPEND(
fctx->validators,
validator, link);
}
}
} else if (!EXTERNAL(rdataset)) {
if (ANSWER(rdataset))
addedrdataset = ardataset;
else if (ANSWERSIG(rdataset))
addedrdataset = asigrdataset;
else
addedrdataset = NULL;
if (CHAINING(rdataset)) {
if (rdataset->type == dns_rdatatype_cname)
eresult = DNS_R_CNAME;
else {
INSIST(rdataset->type ==
dns_rdatatype_dname);
eresult = DNS_R_DNAME;
}
}
if (rdataset->trust == dns_trust_glue) {
options = DNS_DBADD_FORCE;
} else
options = 0;
result = dns_db_addrdataset(fctx->cache,
node, NULL, now,
rdataset,
options,
addedrdataset);
if (result == DNS_R_UNCHANGED) {
if (ANSWER(rdataset) &&
ardataset != NULL &&
ardataset->type == 0) {
if (ardataset->covers ==
dns_rdatatype_any)
eresult =
DNS_R_NCACHENXDOMAIN;
else
eresult =
DNS_R_NCACHENXRRSET;
}
result = ISC_R_SUCCESS;
} else if (result != ISC_R_SUCCESS)
break;
}
}
if (valrdataset != NULL) {
validator = NULL;
result = dns_validator_create(res->view,
name,
fctx->type,
valrdataset,
valsigrdataset,
fctx->rmessage,
0,
task,
validated,
fctx,
&validator);
if (result == ISC_R_SUCCESS)
ISC_LIST_APPEND(fctx->validators, validator, link);
}
if (result == ISC_R_SUCCESS && have_answer) {
fctx->attributes |= FCTX_ATTR_HAVEANSWER;
if (event != NULL) {
event->result = eresult;
dns_db_attach(fctx->cache, adbp);
*anodep = node;
node = NULL;
clone_results(fctx);
}
}
if (node != NULL)
dns_db_detachnode(fctx->cache, &node);
return (result);
}
static inline isc_result_t
cache_message(fetchctx_t *fctx, isc_stdtime_t now) {
isc_result_t result;
dns_section_t section;
dns_name_t *name;
FCTXTRACE("cache_message");
fctx->attributes &= ~FCTX_ATTR_WANTCACHE;
LOCK(&fctx->res->buckets[fctx->bucketnum].lock);
for (section = DNS_SECTION_ANSWER;
section <= DNS_SECTION_ADDITIONAL;
section++) {
result = dns_message_firstname(fctx->rmessage, section);
while (result == ISC_R_SUCCESS) {
name = NULL;
dns_message_currentname(fctx->rmessage, section,
&name);
if ((name->attributes & DNS_NAMEATTR_CACHE) != 0) {
result = cache_name(fctx, name, now);
if (result != ISC_R_SUCCESS)
break;
}
result = dns_message_nextname(fctx->rmessage, section);
}
if (result != ISC_R_NOMORE)
break;
}
if (result == ISC_R_NOMORE)
result = ISC_R_SUCCESS;
UNLOCK(&fctx->res->buckets[fctx->bucketnum].lock);
return (result);
}
static isc_result_t
ncache_adderesult(dns_message_t *message, dns_db_t *cache, dns_dbnode_t *node,
dns_rdatatype_t covers, isc_stdtime_t now, dns_ttl_t maxttl,
dns_rdataset_t *ardataset,
isc_result_t *eresultp)
{
isc_result_t result;
result = dns_ncache_add(message, cache, node, covers, now,
maxttl, ardataset);
if (result == DNS_R_UNCHANGED) {
if (ardataset != NULL && ardataset->type == 0) {
if (ardataset->covers == dns_rdatatype_any)
*eresultp = DNS_R_NCACHENXDOMAIN;
else
*eresultp = DNS_R_NCACHENXRRSET;
result = ISC_R_SUCCESS;
} else {
*eresultp = ISC_R_SUCCESS;
result = ISC_R_SUCCESS;
}
} else if (result == ISC_R_SUCCESS) {
if (covers == dns_rdatatype_any)
*eresultp = DNS_R_NCACHENXDOMAIN;
else
*eresultp = DNS_R_NCACHENXRRSET;
}
return (result);
}
static inline isc_result_t
ncache_message(fetchctx_t *fctx, dns_rdatatype_t covers, isc_stdtime_t now) {
isc_result_t result, eresult;
dns_name_t *name;
dns_resolver_t *res;
dns_db_t **adbp;
dns_dbnode_t *node, **anodep;
dns_rdataset_t *ardataset;
isc_boolean_t need_validation, secure_domain;
dns_name_t *aname;
dns_fetchevent_t *event;
isc_uint32_t ttl;
FCTXTRACE("ncache_message");
fctx->attributes &= ~FCTX_ATTR_WANTNCACHE;
res = fctx->res;
need_validation = ISC_FALSE;
secure_domain = ISC_FALSE;
eresult = ISC_R_SUCCESS;
name = &fctx->name;
node = NULL;
result = dns_keytable_issecuredomain(res->view->secroots, name,
&secure_domain);
if (result != ISC_R_SUCCESS)
return (result);
if ((fctx->options & DNS_FETCHOPT_NOVALIDATE) != 0)
need_validation = ISC_FALSE;
else
need_validation = secure_domain;
if (secure_domain) {
dns_rdataset_t *trdataset;
dns_name_t *tname;
result = dns_message_firstname(fctx->rmessage,
DNS_SECTION_AUTHORITY);
while (result == ISC_R_SUCCESS) {
tname = NULL;
dns_message_currentname(fctx->rmessage,
DNS_SECTION_AUTHORITY,
&tname);
for (trdataset = ISC_LIST_HEAD(tname->list);
trdataset != NULL;
trdataset = ISC_LIST_NEXT(trdataset, link))
trdataset->trust = dns_trust_pending;
result = dns_message_nextname(fctx->rmessage,
DNS_SECTION_AUTHORITY);
}
if (result != ISC_R_NOMORE)
return (result);
}
if (need_validation) {
dns_validator_t *validator = NULL;
isc_task_t *task = res->buckets[fctx->bucketnum].task;
result = dns_validator_create(res->view, name, fctx->type,
NULL, NULL,
fctx->rmessage, 0, task,
validated, fctx,
&validator);
if (result != ISC_R_SUCCESS)
return (result);
ISC_LIST_APPEND(fctx->validators, validator, link);
return (ISC_R_SUCCESS);
}
LOCK(&res->buckets[fctx->bucketnum].lock);
adbp = NULL;
aname = NULL;
anodep = NULL;
ardataset = NULL;
if (!HAVE_ANSWER(fctx)) {
event = ISC_LIST_HEAD(fctx->events);
if (event != NULL) {
adbp = &event->db;
aname = dns_fixedname_name(&event->foundname);
result = dns_name_copy(name, aname, NULL);
if (result != ISC_R_SUCCESS)
goto unlock;
anodep = &event->node;
ardataset = event->rdataset;
}
} else
event = NULL;
result = dns_db_findnode(fctx->cache, name, ISC_TRUE, &node);
if (result != ISC_R_SUCCESS)
goto unlock;
ttl = fctx->res->view->maxncachettl;
if (fctx->type == dns_rdatatype_soa &&
covers == dns_rdatatype_any)
ttl = 0;
result = ncache_adderesult(fctx->rmessage, fctx->cache, node,
covers, now, ttl, ardataset, &eresult);
if (result != ISC_R_SUCCESS)
goto unlock;
if (!HAVE_ANSWER(fctx)) {
fctx->attributes |= FCTX_ATTR_HAVEANSWER;
if (event != NULL) {
event->result = eresult;
dns_db_attach(fctx->cache, adbp);
*anodep = node;
node = NULL;
clone_results(fctx);
}
}
unlock:
UNLOCK(&res->buckets[fctx->bucketnum].lock);
if (node != NULL)
dns_db_detachnode(fctx->cache, &node);
return (result);
}
static inline void
mark_related(dns_name_t *name, dns_rdataset_t *rdataset,
isc_boolean_t external, isc_boolean_t gluing)
{
name->attributes |= DNS_NAMEATTR_CACHE;
if (gluing) {
rdataset->trust = dns_trust_glue;
if (rdataset->ttl == 0)
rdataset->ttl = 1;
} else
rdataset->trust = dns_trust_additional;
if (!CACHE(rdataset)) {
name->attributes |= DNS_NAMEATTR_CHASE;
rdataset->attributes |= DNS_RDATASETATTR_CHASE;
}
rdataset->attributes |= DNS_RDATASETATTR_CACHE;
if (external)
rdataset->attributes |= DNS_RDATASETATTR_EXTERNAL;
}
static isc_result_t
check_related(void *arg, dns_name_t *addname, dns_rdatatype_t type) {
fetchctx_t *fctx = arg;
isc_result_t result;
dns_name_t *name;
dns_rdataset_t *rdataset;
isc_boolean_t external;
dns_rdatatype_t rtype;
isc_boolean_t gluing;
REQUIRE(VALID_FCTX(fctx));
if (GLUING(fctx))
gluing = ISC_TRUE;
else
gluing = ISC_FALSE;
name = NULL;
result = dns_message_findname(fctx->rmessage, DNS_SECTION_ADDITIONAL,
addname, dns_rdatatype_any, 0, &name,
NULL);
if (result == ISC_R_SUCCESS) {
external = ISC_TF(!dns_name_issubdomain(name, &fctx->domain));
for (rdataset = ISC_LIST_HEAD(name->list);
rdataset != NULL;
rdataset = ISC_LIST_NEXT(rdataset, link)) {
if (rdataset->type == dns_rdatatype_sig)
rtype = rdataset->covers;
else
rtype = rdataset->type;
if ((type == dns_rdatatype_a &&
(rtype == dns_rdatatype_a ||
rtype == dns_rdatatype_aaaa ||
rtype == dns_rdatatype_a6)) ||
type == rtype)
mark_related(name, rdataset, external,
gluing);
}
}
return (ISC_R_SUCCESS);
}
static void
chase_additional(fetchctx_t *fctx) {
isc_boolean_t rescan;
dns_section_t section = DNS_SECTION_ADDITIONAL;
isc_result_t result;
again:
rescan = ISC_FALSE;
for (result = dns_message_firstname(fctx->rmessage, section);
result == ISC_R_SUCCESS;
result = dns_message_nextname(fctx->rmessage, section)) {
dns_name_t *name = NULL;
dns_rdataset_t *rdataset;
dns_message_currentname(fctx->rmessage, DNS_SECTION_ADDITIONAL,
&name);
if ((name->attributes & DNS_NAMEATTR_CHASE) == 0)
continue;
name->attributes &= ~DNS_NAMEATTR_CHASE;
for (rdataset = ISC_LIST_HEAD(name->list);
rdataset != NULL;
rdataset = ISC_LIST_NEXT(rdataset, link)) {
if (CHASE(rdataset)) {
rdataset->attributes &= ~DNS_RDATASETATTR_CHASE;
(void)dns_rdataset_additionaldata(rdataset,
check_related,
fctx);
rescan = ISC_TRUE;
}
}
}
if (rescan)
goto again;
}
static inline isc_result_t
cname_target(dns_rdataset_t *rdataset, dns_name_t *tname) {
isc_result_t result;
dns_rdata_t rdata = DNS_RDATA_INIT;
dns_rdata_cname_t cname;
result = dns_rdataset_first(rdataset);
if (result != ISC_R_SUCCESS)
return (result);
dns_rdataset_current(rdataset, &rdata);
result = dns_rdata_tostruct(&rdata, &cname, NULL);
if (result != ISC_R_SUCCESS)
return (result);
dns_name_init(tname, NULL);
dns_name_clone(&cname.cname, tname);
dns_rdata_freestruct(&cname);
return (ISC_R_SUCCESS);
}
static inline isc_result_t
dname_target(dns_rdataset_t *rdataset, dns_name_t *qname, dns_name_t *oname,
dns_fixedname_t *fixeddname)
{
isc_result_t result;
dns_rdata_t rdata = DNS_RDATA_INIT;
unsigned int nlabels, nbits;
int order;
dns_namereln_t namereln;
dns_rdata_dname_t dname;
dns_fixedname_t prefix;
result = dns_rdataset_first(rdataset);
if (result != ISC_R_SUCCESS)
return (result);
dns_rdataset_current(rdataset, &rdata);
result = dns_rdata_tostruct(&rdata, &dname, NULL);
if (result != ISC_R_SUCCESS)
return (result);
namereln = dns_name_fullcompare(qname, oname, &order, &nlabels,
&nbits);
if (namereln != dns_namereln_subdomain) {
dns_rdata_freestruct(&dname);
return (DNS_R_FORMERR);
}
dns_fixedname_init(&prefix);
result = dns_name_split(qname, nlabels, nbits,
dns_fixedname_name(&prefix), NULL);
if (result != ISC_R_SUCCESS) {
dns_rdata_freestruct(&dname);
return (result);
}
dns_fixedname_init(fixeddname);
result = dns_name_concatenate(dns_fixedname_name(&prefix),
&dname.dname,
dns_fixedname_name(fixeddname), NULL);
dns_rdata_freestruct(&dname);
return (result);
}
static isc_result_t
noanswer_response(fetchctx_t *fctx, dns_name_t *oqname) {
isc_result_t result;
dns_message_t *message;
dns_name_t *name, *qname, *ns_name, *soa_name;
dns_rdataset_t *rdataset, *ns_rdataset;
isc_boolean_t done, aa, negative_response;
dns_rdatatype_t type;
FCTXTRACE("noanswer_response");
message = fctx->rmessage;
if (oqname == NULL) {
if ((message->flags & DNS_MESSAGEFLAG_AA) != 0)
aa = ISC_TRUE;
else
aa = ISC_FALSE;
qname = &fctx->name;
} else {
qname = oqname;
aa = ISC_FALSE;
if (!dns_name_issubdomain(qname, &fctx->domain))
return (ISC_R_SUCCESS);
}
negative_response = ISC_FALSE;
if (message->rcode == dns_rcode_nxdomain ||
(message->counts[DNS_SECTION_ANSWER] == 0 &&
message->counts[DNS_SECTION_AUTHORITY] == 0))
negative_response = ISC_TRUE;
done = ISC_FALSE;
ns_name = NULL;
ns_rdataset = NULL;
soa_name = NULL;
result = dns_message_firstname(message, DNS_SECTION_AUTHORITY);
while (!done && result == ISC_R_SUCCESS) {
name = NULL;
dns_message_currentname(message, DNS_SECTION_AUTHORITY, &name);
if (dns_name_issubdomain(name, &fctx->domain)) {
for (rdataset = ISC_LIST_HEAD(name->list);
rdataset != NULL;
rdataset = ISC_LIST_NEXT(rdataset, link)) {
type = rdataset->type;
if (type == dns_rdatatype_sig)
type = rdataset->covers;
if (type == dns_rdatatype_ns) {
if (rdataset->type ==
dns_rdatatype_ns) {
if (ns_name != NULL &&
name != ns_name)
return (DNS_R_FORMERR);
ns_name = name;
}
name->attributes |=
DNS_NAMEATTR_CACHE;
rdataset->attributes |=
DNS_RDATASETATTR_CACHE;
rdataset->trust = dns_trust_glue;
ns_rdataset = rdataset;
}
}
for (rdataset = ISC_LIST_HEAD(name->list);
rdataset != NULL;
rdataset = ISC_LIST_NEXT(rdataset, link)) {
type = rdataset->type;
if (type == dns_rdatatype_sig)
type = rdataset->covers;
if (type == dns_rdatatype_soa ||
type == dns_rdatatype_nxt) {
if (rdataset->type ==
dns_rdatatype_soa) {
if (soa_name != NULL &&
name != soa_name)
return (DNS_R_FORMERR);
soa_name = name;
}
if (ns_name == NULL) {
negative_response = ISC_TRUE;
name->attributes |=
DNS_NAMEATTR_NCACHE;
rdataset->attributes |=
DNS_RDATASETATTR_NCACHE;
} else {
name->attributes |=
DNS_NAMEATTR_CACHE;
rdataset->attributes |=
DNS_RDATASETATTR_CACHE;
}
if (aa)
rdataset->trust =
dns_trust_authauthority;
else
rdataset->trust =
dns_trust_additional;
}
}
}
result = dns_message_nextname(message, DNS_SECTION_AUTHORITY);
if (result == ISC_R_NOMORE)
break;
else if (result != ISC_R_SUCCESS)
return (result);
}
if (!negative_response && ns_name == NULL) {
if (oqname != NULL) {
return (ISC_R_SUCCESS);
} else {
return (DNS_R_FORMERR);
}
}
if (ns_name != NULL && soa_name != NULL && ns_name != soa_name)
return (DNS_R_FORMERR);
if (!negative_response && ns_name != NULL && oqname == NULL) {
if (dns_name_equal(ns_name, &fctx->domain))
return (DNS_R_FORMERR);
if (! dns_name_issubdomain(&fctx->name, ns_name)) {
FCTXTRACE("referral to non-parent");
return (DNS_R_FORMERR);
}
INSIST(ns_rdataset != NULL);
fctx->attributes |= FCTX_ATTR_GLUING;
(void)dns_rdataset_additionaldata(ns_rdataset, check_related,
fctx);
fctx->attributes &= ~FCTX_ATTR_GLUING;
if (ns_rdataset->ttl == 0)
ns_rdataset->ttl = 1;
INSIST(dns_name_countlabels(&fctx->domain) > 0);
dns_name_free(&fctx->domain, fctx->res->mctx);
if (dns_rdataset_isassociated(&fctx->nameservers))
dns_rdataset_disassociate(&fctx->nameservers);
dns_name_init(&fctx->domain, NULL);
result = dns_name_dup(ns_name, fctx->res->mctx, &fctx->domain);
if (result != ISC_R_SUCCESS)
return (result);
fctx->attributes |= FCTX_ATTR_WANTCACHE;
return (DNS_R_DELEGATION);
}
if (ns_name != NULL)
ns_name->attributes &= ~DNS_NAMEATTR_CACHE;
if (negative_response && oqname == NULL)
fctx->attributes |= FCTX_ATTR_WANTNCACHE;
return (ISC_R_SUCCESS);
}
static isc_result_t
answer_response(fetchctx_t *fctx) {
isc_result_t result;
dns_message_t *message;
dns_name_t *name, *qname, tname;
dns_rdataset_t *rdataset;
isc_boolean_t done, external, chaining, aa, found, want_chaining;
isc_boolean_t have_answer, found_cname, found_type, wanted_chaining;
unsigned int aflag;
dns_rdatatype_t type;
dns_fixedname_t dname, fqname;
FCTXTRACE("answer_response");
message = fctx->rmessage;
done = ISC_FALSE;
found_cname = ISC_FALSE;
found_type = ISC_FALSE;
chaining = ISC_FALSE;
have_answer = ISC_FALSE;
want_chaining = ISC_FALSE;
if ((message->flags & DNS_MESSAGEFLAG_AA) != 0)
aa = ISC_TRUE;
else
aa = ISC_FALSE;
qname = &fctx->name;
type = fctx->type;
result = dns_message_firstname(message, DNS_SECTION_ANSWER);
while (!done && result == ISC_R_SUCCESS) {
name = NULL;
dns_message_currentname(message, DNS_SECTION_ANSWER, &name);
external = ISC_TF(!dns_name_issubdomain(name, &fctx->domain));
if (dns_name_equal(name, qname)) {
wanted_chaining = ISC_FALSE;
for (rdataset = ISC_LIST_HEAD(name->list);
rdataset != NULL;
rdataset = ISC_LIST_NEXT(rdataset, link)) {
found = ISC_FALSE;
want_chaining = ISC_FALSE;
aflag = 0;
if (rdataset->type == type && !found_cname) {
found = ISC_TRUE;
found_type = ISC_TRUE;
done = ISC_TRUE;
aflag = DNS_RDATASETATTR_ANSWER;
} else if (type == dns_rdatatype_any) {
found = ISC_TRUE;
aflag = DNS_RDATASETATTR_ANSWER;
} else if (rdataset->type == dns_rdatatype_sig
&& rdataset->covers == type
&& !found_cname) {
found = ISC_TRUE;
found_type = ISC_TRUE;
aflag = DNS_RDATASETATTR_ANSWERSIG;
} else if (rdataset->type ==
dns_rdatatype_cname
&& !found_type) {
if (type == dns_rdatatype_sig ||
type == dns_rdatatype_key ||
type == dns_rdatatype_nxt)
return (DNS_R_FORMERR);
found = ISC_TRUE;
found_cname = ISC_TRUE;
want_chaining = ISC_TRUE;
aflag = DNS_RDATASETATTR_ANSWER;
result = cname_target(rdataset,
&tname);
if (result != ISC_R_SUCCESS)
return (result);
} else if (rdataset->type == dns_rdatatype_sig
&& rdataset->covers ==
dns_rdatatype_cname
&& !found_type) {
found = ISC_TRUE;
found_cname = ISC_TRUE;
aflag = DNS_RDATASETATTR_ANSWERSIG;
}
if (found) {
name->attributes |=
DNS_NAMEATTR_CACHE;
rdataset->attributes |=
DNS_RDATASETATTR_CACHE;
rdataset->trust = dns_trust_answer;
if (!chaining) {
INSIST(!external);
if (aflag ==
DNS_RDATASETATTR_ANSWER)
have_answer = ISC_TRUE;
name->attributes |=
DNS_NAMEATTR_ANSWER;
rdataset->attributes |= aflag;
if (aa)
rdataset->trust =
dns_trust_authanswer;
} else if (external) {
rdataset->attributes |=
DNS_RDATASETATTR_EXTERNAL;
}
(void)dns_rdataset_additionaldata(
rdataset,
check_related,
fctx);
if (want_chaining) {
wanted_chaining = ISC_TRUE;
name->attributes |=
DNS_NAMEATTR_CHAINING;
rdataset->attributes |=
DNS_RDATASETATTR_CHAINING;
qname = &tname;
}
}
if (wanted_chaining)
chaining = ISC_TRUE;
}
} else {
wanted_chaining = ISC_FALSE;
for (rdataset = ISC_LIST_HEAD(name->list);
rdataset != NULL;
rdataset = ISC_LIST_NEXT(rdataset, link)) {
isc_boolean_t found_dname = ISC_FALSE;
found = ISC_FALSE;
aflag = 0;
if (rdataset->type == dns_rdatatype_dname) {
if (!chaining && external)
return (DNS_R_FORMERR);
found = ISC_TRUE;
want_chaining = ISC_TRUE;
aflag = DNS_RDATASETATTR_ANSWER;
result = dname_target(rdataset,
qname, name,
&dname);
if (result == ISC_R_NOSPACE) {
want_chaining = ISC_FALSE;
} else if (result != ISC_R_SUCCESS)
return (result);
else
found_dname = ISC_TRUE;
} else if (rdataset->type == dns_rdatatype_sig
&& rdataset->covers ==
dns_rdatatype_dname) {
found = ISC_TRUE;
aflag = DNS_RDATASETATTR_ANSWERSIG;
}
if (found) {
name->attributes |=
DNS_NAMEATTR_CACHE;
rdataset->attributes |=
DNS_RDATASETATTR_CACHE;
rdataset->trust = dns_trust_answer;
if (!chaining) {
INSIST(!external);
if (aflag ==
DNS_RDATASETATTR_ANSWER)
have_answer = ISC_TRUE;
name->attributes |=
DNS_NAMEATTR_ANSWER;
rdataset->attributes |= aflag;
if (aa)
rdataset->trust =
dns_trust_authanswer;
} else if (external) {
rdataset->attributes |=
DNS_RDATASETATTR_EXTERNAL;
}
if (found_dname) {
dns_fixedname_init(&fqname);
result = dns_name_copy(
dns_fixedname_name(&dname),
dns_fixedname_name(&fqname),
NULL);
if (result != ISC_R_SUCCESS)
return (result);
wanted_chaining = ISC_TRUE;
name->attributes |=
DNS_NAMEATTR_CHAINING;
rdataset->attributes |=
DNS_RDATASETATTR_CHAINING;
qname = dns_fixedname_name(
&fqname);
}
}
}
if (wanted_chaining)
chaining = ISC_TRUE;
}
result = dns_message_nextname(message, DNS_SECTION_ANSWER);
}
if (result == ISC_R_NOMORE)
result = ISC_R_SUCCESS;
if (result != ISC_R_SUCCESS)
return (result);
if (!have_answer)
return (DNS_R_FORMERR);
fctx->attributes |= FCTX_ATTR_WANTCACHE;
if (want_chaining) {
return (noanswer_response(fctx, qname));
}
if (message->rcode != dns_rcode_noerror)
return (DNS_R_FORMERR);
done = ISC_FALSE;
result = dns_message_firstname(message, DNS_SECTION_AUTHORITY);
while (!done && result == ISC_R_SUCCESS) {
name = NULL;
dns_message_currentname(message, DNS_SECTION_AUTHORITY, &name);
external = ISC_TF(!dns_name_issubdomain(name, &fctx->domain));
if (!external) {
for (rdataset = ISC_LIST_HEAD(name->list);
rdataset != NULL;
rdataset = ISC_LIST_NEXT(rdataset, link)) {
if (rdataset->type == dns_rdatatype_ns ||
(rdataset->type == dns_rdatatype_sig &&
rdataset->covers == dns_rdatatype_ns)) {
name->attributes |=
DNS_NAMEATTR_CACHE;
rdataset->attributes |=
DNS_RDATASETATTR_CACHE;
if (aa && !chaining)
rdataset->trust =
dns_trust_authauthority;
else
rdataset->trust =
dns_trust_additional;
(void)dns_rdataset_additionaldata(
rdataset,
check_related,
fctx);
}
}
done = ISC_TRUE;
}
result = dns_message_nextname(message, DNS_SECTION_AUTHORITY);
}
if (result == ISC_R_NOMORE)
result = ISC_R_SUCCESS;
return (result);
}
static void
resquery_response(isc_task_t *task, isc_event_t *event) {
isc_result_t result;
resquery_t *query = event->ev_arg;
dns_dispatchevent_t *devent = (dns_dispatchevent_t *)event;
isc_boolean_t keep_trying, broken_server, get_nameservers, resend;
isc_boolean_t truncated;
dns_message_t *message;
fetchctx_t *fctx;
dns_name_t *fname;
dns_fixedname_t foundname;
isc_stdtime_t now;
isc_time_t tnow, *finish;
dns_adbaddrinfo_t *addrinfo;
unsigned int options;
REQUIRE(VALID_QUERY(query));
fctx = query->fctx;
options = query->options;
REQUIRE(VALID_FCTX(fctx));
REQUIRE(event->ev_type == DNS_EVENT_DISPATCH);
UNUSED(task);
QTRACE("response");
(void)isc_timer_touch(fctx->timer);
keep_trying = ISC_FALSE;
broken_server = ISC_FALSE;
get_nameservers = ISC_FALSE;
resend = ISC_FALSE;
truncated = ISC_FALSE;
finish = NULL;
if (fctx->res->exiting) {
result = ISC_R_SHUTTINGDOWN;
goto done;
}
fctx->timeouts = 0;
result = isc_time_now(&tnow);
if (result != ISC_R_SUCCESS)
goto done;
finish = &tnow;
isc_stdtime_get(&now);
if (devent->result != ISC_R_SUCCESS) {
if (devent->result == ISC_R_EOF &&
(query->options & DNS_FETCHOPT_NOEDNS0) == 0) {
options |= DNS_FETCHOPT_NOEDNS0;
resend = ISC_TRUE;
dns_adb_changeflags(fctx->adb,
query->addrinfo,
DNS_FETCHOPT_NOEDNS0,
DNS_FETCHOPT_NOEDNS0);
} else {
keep_trying = ISC_TRUE;
}
goto done;
}
message = fctx->rmessage;
if (query->tsig != NULL) {
result = dns_message_setquerytsig(message, query->tsig);
if (result != ISC_R_SUCCESS)
goto done;
}
if (query->tsigkey) {
result = dns_message_settsigkey(message, query->tsigkey);
if (result != ISC_R_SUCCESS)
goto done;
}
result = dns_message_parse(message, &devent->buffer, 0);
if (result != ISC_R_SUCCESS) {
switch (result) {
case ISC_R_UNEXPECTEDEND:
if (!message->question_ok ||
(message->flags & DNS_MESSAGEFLAG_TC) == 0 ||
(options & DNS_FETCHOPT_TCP) != 0) {
if ((query->options & DNS_FETCHOPT_NOEDNS0)
== 0) {
options |= DNS_FETCHOPT_NOEDNS0;
resend = ISC_TRUE;
dns_adb_changeflags(
fctx->adb,
query->addrinfo,
DNS_FETCHOPT_NOEDNS0,
DNS_FETCHOPT_NOEDNS0);
} else {
broken_server = ISC_TRUE;
keep_trying = ISC_TRUE;
}
goto done;
}
truncated = ISC_TRUE;
break;
case DNS_R_FORMERR:
if ((query->options & DNS_FETCHOPT_NOEDNS0) == 0) {
options |= DNS_FETCHOPT_NOEDNS0;
resend = ISC_TRUE;
dns_adb_changeflags(fctx->adb,
query->addrinfo,
DNS_FETCHOPT_NOEDNS0,
DNS_FETCHOPT_NOEDNS0);
} else {
broken_server = ISC_TRUE;
keep_trying = ISC_TRUE;
}
goto done;
default:
goto done;
}
}
result = dns_message_checksig(message, fctx->res->view);
if (result != ISC_R_SUCCESS)
goto done;
INSIST((message->flags & DNS_MESSAGEFLAG_QR) != 0);
if ((message->flags & DNS_MESSAGEFLAG_TC) != 0)
truncated = ISC_TRUE;
if (truncated) {
if ((options & DNS_FETCHOPT_TCP) != 0) {
broken_server = ISC_TRUE;
keep_trying = ISC_TRUE;
} else {
options |= DNS_FETCHOPT_TCP;
resend = ISC_TRUE;
}
goto done;
}
if (message->opcode != dns_opcode_query) {
broken_server = ISC_TRUE;
keep_trying = ISC_TRUE;
goto done;
}
if (message->rcode != dns_rcode_noerror &&
message->rcode != dns_rcode_nxdomain) {
if ((message->rcode == dns_rcode_formerr ||
message->rcode == dns_rcode_notimp ||
message->rcode == dns_rcode_servfail) &&
(query->options & DNS_FETCHOPT_NOEDNS0) == 0) {
options |= DNS_FETCHOPT_NOEDNS0;
resend = ISC_TRUE;
if (message->rcode != dns_rcode_servfail)
dns_adb_changeflags(fctx->adb, query->addrinfo,
DNS_FETCHOPT_NOEDNS0,
DNS_FETCHOPT_NOEDNS0);
} else if (message->rcode == dns_rcode_formerr) {
if (ISFORWARDER(query->addrinfo)) {
broken_server = ISC_TRUE;
keep_trying = ISC_TRUE;
} else {
result = DNS_R_FORMERR;
}
} else if (message->rcode == dns_rcode_yxdomain) {
result = DNS_R_YXDOMAIN;
} else {
broken_server = ISC_TRUE;
keep_trying = ISC_TRUE;
}
goto done;
}
result = same_question(fctx);
if (result != ISC_R_SUCCESS) {
if (result == DNS_R_FORMERR)
keep_trying = ISC_TRUE;
goto done;
}
if (fctx->res->lame_ttl != 0 && !ISFORWARDER(query->addrinfo) &&
is_lame(fctx)) {
log_lame(fctx, query->addrinfo);
dns_adb_marklame(fctx->adb, query->addrinfo,
&fctx->domain, now + fctx->res->lame_ttl);
broken_server = ISC_TRUE;
keep_trying = ISC_TRUE;
goto done;
}
if (message->counts[DNS_SECTION_ANSWER] > 0 &&
(message->rcode == dns_rcode_noerror ||
message->rcode == dns_rcode_nxdomain)) {
result = answer_response(fctx);
if (result != ISC_R_SUCCESS) {
if (result == DNS_R_FORMERR)
keep_trying = ISC_TRUE;
goto done;
}
} else if (message->counts[DNS_SECTION_AUTHORITY] > 0 ||
message->rcode == dns_rcode_noerror ||
message->rcode == dns_rcode_nxdomain) {
result = noanswer_response(fctx, NULL);
if (result == DNS_R_DELEGATION) {
get_nameservers = ISC_TRUE;
keep_trying = ISC_TRUE;
fctx->restarts = 0;
result = ISC_R_SUCCESS;
} else if (result != ISC_R_SUCCESS) {
if (result == DNS_R_FORMERR)
keep_trying = ISC_TRUE;
goto done;
}
} else {
broken_server = ISC_TRUE;
keep_trying = ISC_TRUE;
goto done;
}
chase_additional(fctx);
if (WANTCACHE(fctx)) {
result = cache_message(fctx, now);
if (result != ISC_R_SUCCESS)
goto done;
}
if (WANTNCACHE(fctx)) {
dns_rdatatype_t covers;
if (message->rcode == dns_rcode_nxdomain)
covers = dns_rdatatype_any;
else
covers = fctx->type;
result = ncache_message(fctx, covers, now);
}
done:
addrinfo = query->addrinfo;
fctx_cancelquery(&query, &devent, finish, ISC_FALSE);
if (keep_trying) {
if (result == DNS_R_FORMERR)
broken_server = ISC_TRUE;
if (broken_server) {
add_bad(fctx, &addrinfo->sockaddr);
}
if (get_nameservers) {
dns_fixedname_init(&foundname);
fname = dns_fixedname_name(&foundname);
if (result != ISC_R_SUCCESS) {
fctx_done(fctx, DNS_R_SERVFAIL);
return;
}
result = dns_view_findzonecut(fctx->res->view,
&fctx->domain,
fname,
now, 0, ISC_TRUE,
&fctx->nameservers,
NULL);
if (result != ISC_R_SUCCESS) {
FCTXTRACE("couldn't find a zonecut");
fctx_done(fctx, DNS_R_SERVFAIL);
return;
}
if (!dns_name_issubdomain(fname, &fctx->domain)) {
FCTXTRACE("nameservers now above QDOMAIN");
fctx_done(fctx, DNS_R_SERVFAIL);
return;
}
dns_name_free(&fctx->domain, fctx->res->mctx);
dns_name_init(&fctx->domain, NULL);
result = dns_name_dup(fname, fctx->res->mctx,
&fctx->domain);
if (result != ISC_R_SUCCESS) {
fctx_done(fctx, DNS_R_SERVFAIL);
return;
}
fctx_cancelqueries(fctx, ISC_TRUE);
fctx_cleanupfinds(fctx);
fctx_cleanupforwaddrs(fctx);
}
fctx_try(fctx);
} else if (resend) {
FCTXTRACE("resend");
result = fctx_query(fctx, addrinfo, options);
if (result != ISC_R_SUCCESS)
fctx_done(fctx, result);
} else if (result == ISC_R_SUCCESS && !HAVE_ANSWER(fctx)) {
FCTXTRACE("wait for validator");
fctx_cancelqueries(fctx, ISC_TRUE);
result = fctx_stopidletimer(fctx);
if (result != ISC_R_SUCCESS)
fctx_done(fctx, result);
} else {
fctx_done(fctx, result);
}
}
static void
destroy(dns_resolver_t *res) {
unsigned int i;
REQUIRE(res->references == 0);
REQUIRE(!res->priming);
REQUIRE(res->primefetch == NULL);
RTRACE("destroy");
DESTROYLOCK(&res->lock);
for (i = 0; i < res->nbuckets; i++) {
INSIST(ISC_LIST_EMPTY(res->buckets[i].fctxs));
isc_task_shutdown(res->buckets[i].task);
isc_task_detach(&res->buckets[i].task);
DESTROYLOCK(&res->buckets[i].lock);
}
isc_mem_put(res->mctx, res->buckets,
res->nbuckets * sizeof (fctxbucket_t));
if (res->dispatchv4 != NULL)
dns_dispatch_detach(&res->dispatchv4);
if (res->dispatchv6 != NULL)
dns_dispatch_detach(&res->dispatchv6);
res->magic = 0;
isc_mem_put(res->mctx, res, sizeof *res);
}
static void
send_shutdown_events(dns_resolver_t *res) {
isc_event_t *event, *next_event;
isc_task_t *etask;
for (event = ISC_LIST_HEAD(res->whenshutdown);
event != NULL;
event = next_event) {
next_event = ISC_LIST_NEXT(event, ev_link);
ISC_LIST_UNLINK(res->whenshutdown, event, ev_link);
etask = event->ev_sender;
event->ev_sender = res;
isc_task_sendanddetach(&etask, &event);
}
}
static void
empty_bucket(dns_resolver_t *res) {
RTRACE("empty_bucket");
LOCK(&res->lock);
INSIST(res->activebuckets > 0);
res->activebuckets--;
if (res->activebuckets == 0)
send_shutdown_events(res);
UNLOCK(&res->lock);
}
isc_result_t
dns_resolver_create(dns_view_t *view,
isc_taskmgr_t *taskmgr, unsigned int ntasks,
isc_socketmgr_t *socketmgr,
isc_timermgr_t *timermgr,
unsigned int options,
dns_dispatchmgr_t *dispatchmgr,
dns_dispatch_t *dispatchv4,
dns_dispatch_t *dispatchv6,
dns_resolver_t **resp)
{
dns_resolver_t *res;
isc_result_t result = ISC_R_SUCCESS;
unsigned int i, buckets_created = 0;
char name[16];
REQUIRE(DNS_VIEW_VALID(view));
REQUIRE(ntasks > 0);
REQUIRE(resp != NULL && *resp == NULL);
REQUIRE(dispatchmgr != NULL);
REQUIRE(dispatchv4 != NULL || dispatchv6 != NULL);
res = isc_mem_get(view->mctx, sizeof *res);
if (res == NULL)
return (ISC_R_NOMEMORY);
RTRACE("create");
res->mctx = view->mctx;
res->rdclass = view->rdclass;
res->socketmgr = socketmgr;
res->timermgr = timermgr;
res->taskmgr = taskmgr;
res->dispatchmgr = dispatchmgr;
res->view = view;
res->options = options;
res->lame_ttl = 0;
res->nbuckets = ntasks;
res->activebuckets = ntasks;
res->buckets = isc_mem_get(view->mctx,
ntasks * sizeof (fctxbucket_t));
if (res->buckets == NULL) {
result = ISC_R_NOMEMORY;
goto cleanup_res;
}
for (i = 0; i < ntasks; i++) {
result = isc_mutex_init(&res->buckets[i].lock);
if (result != ISC_R_SUCCESS)
goto cleanup_buckets;
res->buckets[i].task = NULL;
result = isc_task_create(taskmgr, 0, &res->buckets[i].task);
if (result != ISC_R_SUCCESS) {
DESTROYLOCK(&res->buckets[i].lock);
goto cleanup_buckets;
}
sprintf(name, "res%u", i);
isc_task_setname(res->buckets[i].task, name, res);
ISC_LIST_INIT(res->buckets[i].fctxs);
res->buckets[i].exiting = ISC_FALSE;
buckets_created++;
}
res->dispatchv4 = NULL;
if (dispatchv4 != NULL)
dns_dispatch_attach(dispatchv4, &res->dispatchv4);
res->dispatchv6 = NULL;
if (dispatchv6 != NULL)
dns_dispatch_attach(dispatchv6, &res->dispatchv6);
res->references = 1;
res->exiting = ISC_FALSE;
res->frozen = ISC_FALSE;
ISC_LIST_INIT(res->whenshutdown);
res->priming = ISC_FALSE;
res->primefetch = NULL;
result = isc_mutex_init(&res->lock);
if (result != ISC_R_SUCCESS)
goto cleanup_dispatches;
res->magic = RES_MAGIC;
*resp = res;
return (ISC_R_SUCCESS);
cleanup_dispatches:
if (res->dispatchv6 != NULL)
dns_dispatch_detach(&res->dispatchv6);
if (res->dispatchv4 != NULL)
dns_dispatch_detach(&res->dispatchv4);
cleanup_buckets:
for (i = 0; i < buckets_created; i++) {
DESTROYLOCK(&res->buckets[i].lock);
isc_task_shutdown(res->buckets[i].task);
isc_task_detach(&res->buckets[i].task);
}
isc_mem_put(view->mctx, res->buckets,
res->nbuckets * sizeof (fctxbucket_t));
cleanup_res:
isc_mem_put(view->mctx, res, sizeof *res);
return (result);
}
static void
prime_done(isc_task_t *task, isc_event_t *event) {
dns_resolver_t *res;
dns_fetchevent_t *fevent;
dns_fetch_t *fetch;
REQUIRE(event->ev_type == DNS_EVENT_FETCHDONE);
fevent = (dns_fetchevent_t *)event;
res = event->ev_arg;
REQUIRE(VALID_RESOLVER(res));
UNUSED(task);
LOCK(&res->lock);
INSIST(res->priming);
res->priming = ISC_FALSE;
fetch = res->primefetch;
res->primefetch = NULL;
UNLOCK(&res->lock);
if (fevent->node != NULL)
dns_db_detachnode(fevent->db, &fevent->node);
if (fevent->db != NULL)
dns_db_detach(&fevent->db);
if (dns_rdataset_isassociated(fevent->rdataset))
dns_rdataset_disassociate(fevent->rdataset);
INSIST(fevent->sigrdataset == NULL);
isc_mem_put(res->mctx, fevent->rdataset, sizeof *fevent->rdataset);
isc_event_free(&event);
dns_resolver_destroyfetch(&fetch);
}
void
dns_resolver_prime(dns_resolver_t *res) {
isc_boolean_t want_priming = ISC_FALSE;
dns_fetch_t *fetch;
dns_rdataset_t *rdataset;
isc_result_t result;
REQUIRE(VALID_RESOLVER(res));
REQUIRE(res->frozen);
RTRACE("dns_resolver_prime");
LOCK(&res->lock);
if (!res->exiting && !res->priming) {
INSIST(res->primefetch == NULL);
res->priming = ISC_TRUE;
want_priming = ISC_TRUE;
}
UNLOCK(&res->lock);
if (want_priming) {
RTRACE("priming");
rdataset = isc_mem_get(res->mctx, sizeof *rdataset);
if (rdataset == NULL) {
LOCK(&res->lock);
INSIST(res->priming);
INSIST(res->primefetch == NULL);
res->priming = ISC_FALSE;
UNLOCK(&res->lock);
return;
}
dns_rdataset_init(rdataset);
fetch = NULL;
result = dns_resolver_createfetch(res, dns_rootname,
dns_rdatatype_ns,
NULL, NULL, NULL, 0,
res->buckets[0].task,
prime_done,
res, rdataset, NULL, &fetch);
LOCK(&res->lock);
INSIST(res->priming);
INSIST(res->primefetch == NULL);
if (result == ISC_R_SUCCESS)
res->primefetch = fetch;
else
res->priming = ISC_FALSE;
UNLOCK(&res->lock);
}
}
void
dns_resolver_freeze(dns_resolver_t *res) {
REQUIRE(VALID_RESOLVER(res));
REQUIRE(!res->frozen);
res->frozen = ISC_TRUE;
}
void
dns_resolver_attach(dns_resolver_t *source, dns_resolver_t **targetp) {
REQUIRE(VALID_RESOLVER(source));
REQUIRE(targetp != NULL && *targetp == NULL);
RRTRACE(source, "attach");
LOCK(&source->lock);
REQUIRE(!source->exiting);
INSIST(source->references > 0);
source->references++;
INSIST(source->references != 0);
UNLOCK(&source->lock);
*targetp = source;
}
void
dns_resolver_whenshutdown(dns_resolver_t *res, isc_task_t *task,
isc_event_t **eventp)
{
isc_task_t *clone;
isc_event_t *event;
REQUIRE(VALID_RESOLVER(res));
REQUIRE(eventp != NULL);
event = *eventp;
*eventp = NULL;
LOCK(&res->lock);
if (res->exiting && res->activebuckets == 0) {
event->ev_sender = res;
isc_task_send(task, &event);
} else {
clone = NULL;
isc_task_attach(task, &clone);
event->ev_sender = clone;
ISC_LIST_APPEND(res->whenshutdown, event, ev_link);
}
UNLOCK(&res->lock);
}
void
dns_resolver_shutdown(dns_resolver_t *res) {
unsigned int i;
fetchctx_t *fctx;
isc_socket_t *sock;
REQUIRE(VALID_RESOLVER(res));
RTRACE("shutdown");
LOCK(&res->lock);
if (!res->exiting) {
RTRACE("exiting");
res->exiting = ISC_TRUE;
for (i = 0; i < res->nbuckets; i++) {
LOCK(&res->buckets[i].lock);
for (fctx = ISC_LIST_HEAD(res->buckets[i].fctxs);
fctx != NULL;
fctx = ISC_LIST_NEXT(fctx, link))
fctx_shutdown(fctx);
if (res->dispatchv4 != NULL) {
sock = dns_dispatch_getsocket(res->dispatchv4);
isc_socket_cancel(sock, res->buckets[i].task,
ISC_SOCKCANCEL_ALL);
}
if (res->dispatchv6 != NULL) {
sock = dns_dispatch_getsocket(res->dispatchv6);
isc_socket_cancel(sock, res->buckets[i].task,
ISC_SOCKCANCEL_ALL);
}
res->buckets[i].exiting = ISC_TRUE;
if (ISC_LIST_EMPTY(res->buckets[i].fctxs)) {
INSIST(res->activebuckets > 0);
res->activebuckets--;
}
UNLOCK(&res->buckets[i].lock);
}
if (res->activebuckets == 0)
send_shutdown_events(res);
}
UNLOCK(&res->lock);
}
void
dns_resolver_detach(dns_resolver_t **resp) {
dns_resolver_t *res;
isc_boolean_t need_destroy = ISC_FALSE;
REQUIRE(resp != NULL);
res = *resp;
REQUIRE(VALID_RESOLVER(res));
RTRACE("detach");
LOCK(&res->lock);
INSIST(res->references > 0);
res->references--;
if (res->references == 0) {
INSIST(res->exiting && res->activebuckets == 0);
need_destroy = ISC_TRUE;
}
UNLOCK(&res->lock);
if (need_destroy)
destroy(res);
*resp = NULL;
}
static inline isc_boolean_t
fctx_match(fetchctx_t *fctx, dns_name_t *name, dns_rdatatype_t type,
unsigned int options)
{
if (fctx->type != type || fctx->options != options)
return (ISC_FALSE);
return (dns_name_equal(&fctx->name, name));
}
static inline void
log_fetch(dns_name_t *name, dns_rdatatype_t type) {
char namebuf[DNS_NAME_FORMATSIZE];
char typebuf[DNS_RDATATYPE_FORMATSIZE];
int level = ISC_LOG_DEBUG(1);
if (! isc_log_wouldlog(dns_lctx, level))
return;
dns_name_format(name, namebuf, sizeof(namebuf));
dns_rdatatype_format(type, typebuf, sizeof(typebuf));
isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER,
DNS_LOGMODULE_RESOLVER, level,
"createfetch: %s %s", namebuf, typebuf);
}
isc_result_t
dns_resolver_createfetch(dns_resolver_t *res, dns_name_t *name,
dns_rdatatype_t type,
dns_name_t *domain, dns_rdataset_t *nameservers,
dns_forwarders_t *forwarders,
unsigned int options, isc_task_t *task,
isc_taskaction_t action, void *arg,
dns_rdataset_t *rdataset,
dns_rdataset_t *sigrdataset,
dns_fetch_t **fetchp)
{
dns_fetch_t *fetch;
fetchctx_t *fctx = NULL;
isc_result_t result;
unsigned int bucketnum;
isc_boolean_t new_fctx = ISC_FALSE;
isc_event_t *event;
UNUSED(forwarders);
REQUIRE(VALID_RESOLVER(res));
REQUIRE(res->frozen);
if (domain != NULL) {
REQUIRE(DNS_RDATASET_VALID(nameservers));
REQUIRE(nameservers->type == dns_rdatatype_ns);
} else
REQUIRE(nameservers == NULL);
REQUIRE(forwarders == NULL);
REQUIRE(!dns_rdataset_isassociated(rdataset));
REQUIRE(sigrdataset == NULL ||
!dns_rdataset_isassociated(sigrdataset));
REQUIRE(fetchp != NULL && *fetchp == NULL);
log_fetch(name, type);
fetch = isc_mem_get(res->mctx, sizeof *fetch);
if (fetch == NULL)
return (ISC_R_NOMEMORY);
bucketnum = dns_name_hash(name, ISC_FALSE) % res->nbuckets;
LOCK(&res->buckets[bucketnum].lock);
if (res->buckets[bucketnum].exiting) {
result = ISC_R_SHUTTINGDOWN;
goto unlock;
}
if ((options & DNS_FETCHOPT_UNSHARED) == 0) {
for (fctx = ISC_LIST_HEAD(res->buckets[bucketnum].fctxs);
fctx != NULL;
fctx = ISC_LIST_NEXT(fctx, link)) {
if (fctx_match(fctx, name, type, options))
break;
}
}
if (fctx == NULL ||
fctx->state == fetchstate_done ||
fctx->cloned ||
ISC_LIST_EMPTY(fctx->events)) {
fctx = NULL;
result = fctx_create(res, name, type, domain, nameservers,
options, bucketnum, &fctx);
if (result != ISC_R_SUCCESS)
goto unlock;
new_fctx = ISC_TRUE;
}
result = fctx_join(fctx, task, action, arg,
rdataset, sigrdataset, fetch);
if (new_fctx) {
if (result == ISC_R_SUCCESS) {
event = &fctx->control_event;
ISC_EVENT_INIT(event, sizeof(*event), 0, NULL,
DNS_EVENT_FETCHCONTROL,
fctx_start, fctx, NULL,
NULL, NULL);
isc_task_send(res->buckets[bucketnum].task, &event);
} else {
(void)fctx_destroy(fctx);
}
}
unlock:
UNLOCK(&res->buckets[bucketnum].lock);
if (result == ISC_R_SUCCESS) {
FTRACE("created");
*fetchp = fetch;
} else
isc_mem_put(res->mctx, fetch, sizeof *fetch);
return (result);
}
void
dns_resolver_cancelfetch(dns_fetch_t *fetch) {
fetchctx_t *fctx;
dns_resolver_t *res;
dns_fetchevent_t *event, *next_event;
isc_task_t *etask;
REQUIRE(DNS_FETCH_VALID(fetch));
fctx = fetch->private;
REQUIRE(VALID_FCTX(fctx));
res = fctx->res;
FTRACE("cancelfetch");
LOCK(&res->buckets[fctx->bucketnum].lock);
event = NULL;
if (fctx->state != fetchstate_done) {
for (event = ISC_LIST_HEAD(fctx->events);
event != NULL;
event = next_event) {
next_event = ISC_LIST_NEXT(event, ev_link);
if (event->fetch == fetch) {
ISC_LIST_UNLINK(fctx->events, event, ev_link);
break;
}
}
}
if (event != NULL) {
etask = event->ev_sender;
event->ev_sender = fctx;
event->result = ISC_R_CANCELED;
isc_task_sendanddetach(&etask, (isc_event_t **)&event);
}
UNLOCK(&res->buckets[fctx->bucketnum].lock);
}
void
dns_resolver_destroyfetch(dns_fetch_t **fetchp) {
dns_fetch_t *fetch;
dns_resolver_t *res;
dns_fetchevent_t *event, *next_event;
fetchctx_t *fctx;
unsigned int bucketnum;
isc_boolean_t bucket_empty = ISC_FALSE;
REQUIRE(fetchp != NULL);
fetch = *fetchp;
REQUIRE(DNS_FETCH_VALID(fetch));
fctx = fetch->private;
REQUIRE(VALID_FCTX(fctx));
res = fctx->res;
FTRACE("destroyfetch");
bucketnum = fctx->bucketnum;
LOCK(&res->buckets[bucketnum].lock);
event = NULL;
if (fctx->state != fetchstate_done) {
for (event = ISC_LIST_HEAD(fctx->events);
event != NULL;
event = next_event) {
next_event = ISC_LIST_NEXT(event, ev_link);
RUNTIME_CHECK(event->fetch != fetch);
}
}
INSIST(fctx->references > 0);
fctx->references--;
if (fctx->references == 0) {
if (fctx->pending == 0 && ISC_LIST_EMPTY(fctx->validators) &&
SHUTTINGDOWN(fctx)) {
bucket_empty = fctx_destroy(fctx);
} else {
fctx_shutdown(fctx);
}
}
UNLOCK(&res->buckets[bucketnum].lock);
isc_mem_put(res->mctx, fetch, sizeof *fetch);
*fetchp = NULL;
if (bucket_empty)
empty_bucket(res);
}
dns_dispatchmgr_t *
dns_resolver_dispatchmgr(dns_resolver_t *resolver) {
REQUIRE(VALID_RESOLVER(resolver));
return (resolver->dispatchmgr);
}
dns_dispatch_t *
dns_resolver_dispatchv4(dns_resolver_t *resolver) {
REQUIRE(VALID_RESOLVER(resolver));
return (resolver->dispatchv4);
}
dns_dispatch_t *
dns_resolver_dispatchv6(dns_resolver_t *resolver) {
REQUIRE(VALID_RESOLVER(resolver));
return (resolver->dispatchv6);
}
isc_socketmgr_t *
dns_resolver_socketmgr(dns_resolver_t *resolver) {
REQUIRE(VALID_RESOLVER(resolver));
return (resolver->socketmgr);
}
isc_taskmgr_t *
dns_resolver_taskmgr(dns_resolver_t *resolver) {
REQUIRE(VALID_RESOLVER(resolver));
return (resolver->taskmgr);
}
isc_uint32_t
dns_resolver_getlamettl(dns_resolver_t *resolver) {
REQUIRE(VALID_RESOLVER(resolver));
return (resolver->lame_ttl);
}
void
dns_resolver_setlamettl(dns_resolver_t *resolver, isc_uint32_t lame_ttl) {
REQUIRE(VALID_RESOLVER(resolver));
resolver->lame_ttl = lame_ttl;
}