Files
bind9/lib/dns/dispatch.c
T
Brian Wellington 317a9d3589 pullup:
330.   [func]          New function isc_log_wouldlog().

(note: #330 is out of place in CHANGES, because it's new in rc4, but if
it was in the place that 330 is supposed to go, no one would see it.  So,
leave it at the top)
2000-08-22 01:45:28 +00:00

2168 lines
50 KiB
C

/*
* 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.
*/
/* $Id: dispatch.c,v 1.57.2.1 2000/08/22 01:45:14 bwelling Exp $ */
#include <config.h>
#include <stdlib.h>
#include <isc/entropy.h>
#include <isc/lfsr.h>
#include <isc/mem.h>
#include <isc/mutex.h>
#include <isc/print.h>
#include <isc/string.h>
#include <isc/task.h>
#include <isc/util.h>
#include <dns/dispatch.h>
#include <dns/events.h>
#include <dns/log.h>
#include <dns/message.h>
#include <dns/tcpmsg.h>
#include <dns/types.h>
struct dns_dispatchmgr {
/* Unlocked. */
unsigned int magic;
isc_mem_t *mctx;
/* Locked by "lock". */
isc_mutex_t lock;
unsigned int state;
ISC_LIST(dns_dispatch_t) list;
/* Locked internally. */
isc_mutex_t pool_lock;
isc_mempool_t *epool; /* memory pool for events */
isc_mempool_t *rpool; /* memory pool request/reply */
isc_mempool_t *dpool; /* dispatch allocations */
isc_entropy_t *entropy; /* entropy source */
};
#define MGR_SHUTTINGDOWN 0x00000001U
#define MGR_IS_SHUTTINGDOWN(l) (((l)->state & MGR_SHUTTINGDOWN) != 0)
#define IS_PRIVATE(d) (((d)->attributes & DNS_DISPATCHATTR_PRIVATE) != 0)
struct dns_dispentry {
unsigned int magic;
dns_dispatch_t *disp;
isc_uint32_t id;
unsigned int bucket;
isc_sockaddr_t host;
isc_task_t *task;
isc_taskaction_t action;
void *arg;
isc_boolean_t item_out;
ISC_LIST(dns_dispatchevent_t) items;
ISC_LINK(dns_dispentry_t) link;
};
#define INVALID_BUCKET (0xffffdead)
typedef ISC_LIST(dns_dispentry_t) dns_displist_t;
struct dns_dispatch {
/* Unlocked. */
unsigned int magic; /* magic */
dns_dispatchmgr_t *mgr; /* dispatch manager */
isc_task_t *task; /* internal task */
isc_socket_t *socket; /* isc socket attached to */
isc_sockaddr_t local; /* local address */
unsigned int buffersize; /* size of each buffer */
unsigned int maxrequests; /* max requests */
unsigned int maxbuffers; /* max buffers */
/* Locked by mgr->lock. */
ISC_LINK(dns_dispatch_t) link;
/* Locked by "lock". */
isc_mutex_t lock; /* locks all below */
isc_sockettype_t socktype;
unsigned int attributes;
unsigned int refcount; /* number of users */
isc_mempool_t *bpool; /* memory pool for buffers */
dns_dispatchevent_t *failsafe_ev; /* failsafe cancel event */
unsigned int recvs; /* recv() calls outstanding */
unsigned int recvs_wanted; /* recv() calls wanted */
unsigned int shutting_down : 1,
shutdown_out : 1,
connected : 1,
tcpmsg_valid : 1;
isc_result_t shutdown_why;
unsigned int requests; /* how many requests we have */
unsigned int buffers; /* allocated buffers */
ISC_LIST(dns_dispentry_t) rq_handlers; /* request handler list */
ISC_LIST(dns_dispatchevent_t) rq_events; /* holder for rq events */
dns_tcpmsg_t tcpmsg; /* for tcp streams */
isc_lfsr_t qid_lfsr1; /* state generator info */
isc_lfsr_t qid_lfsr2; /* state generator info */
unsigned int qid_nbuckets; /* hash table size */
unsigned int qid_increment; /* id increment on collision */
dns_displist_t *qid_table; /* the table itself */
};
#define REQUEST_MAGIC ISC_MAGIC('D', 'r', 'q', 's')
#define VALID_REQUEST(e) ISC_MAGIC_VALID((e), REQUEST_MAGIC)
#define RESPONSE_MAGIC ISC_MAGIC('D', 'r', 's', 'p')
#define VALID_RESPONSE(e) ISC_MAGIC_VALID((e), RESPONSE_MAGIC)
#define DISPATCH_MAGIC ISC_MAGIC('D', 'i', 's', 'p')
#define VALID_DISPATCH(e) ISC_MAGIC_VALID((e), DISPATCH_MAGIC)
#define DNS_DISPATCHMGR_MAGIC ISC_MAGIC('D', 'M', 'g', 'r')
#define VALID_DISPATCHMGR(e) ISC_MAGIC_VALID((e), DNS_DISPATCHMGR_MAGIC)
/*
* Statics.
*/
static dns_dispentry_t *bucket_search(dns_dispatch_t *, isc_sockaddr_t *,
dns_messageid_t, unsigned int);
static isc_boolean_t destroy_disp_ok(dns_dispatch_t *);
static void destroy_disp(dns_dispatch_t **);
static void udp_recv(isc_task_t *, isc_event_t *);
static void tcp_recv(isc_task_t *, isc_event_t *);
static inline void startrecv(dns_dispatch_t *);
static isc_uint32_t dns_randomid(dns_dispatch_t *);
static isc_uint32_t dns_hash(dns_dispatch_t *, isc_sockaddr_t *, isc_uint32_t);
static void free_buffer(dns_dispatch_t *disp, void *buf, unsigned int len);
static void *allocate_udp_buffer(dns_dispatch_t *disp);
static inline void free_event(dns_dispatch_t *disp, dns_dispatchevent_t *ev);
static inline dns_dispatchevent_t *allocate_event(dns_dispatch_t *disp);
static void do_next_request(dns_dispatch_t *disp, dns_dispentry_t *resp);
static void do_next_response(dns_dispatch_t *disp, dns_dispentry_t *resp);
static void do_cancel(dns_dispatch_t *disp, dns_dispentry_t *resp);
static dns_dispentry_t *linear_first(dns_dispatch_t *disp);
static dns_dispentry_t *linear_next(dns_dispatch_t *disp,
dns_dispentry_t *resp);
static void dispatch_free(dns_dispatch_t **dispp);
static isc_result_t dispatch_createudp(dns_dispatchmgr_t *mgr,
isc_socketmgr_t *sockmgr,
isc_taskmgr_t *taskmgr,
isc_sockaddr_t *localaddr,
unsigned int buffersize,
unsigned int maxbuffers,
unsigned int maxrequests,
unsigned int buckets,
unsigned int increment,
unsigned int attributes,
dns_dispatch_t **dispp);
static isc_boolean_t destroy_mgr_ok(dns_dispatchmgr_t *mgr);
static void destroy_mgr(dns_dispatchmgr_t **mgrp);
#define LVL(x) ISC_LOG_DEBUG(x)
static void
mgr_log(dns_dispatchmgr_t *mgr, int level, const char *fmt, ...) {
char msgbuf[2048];
va_list ap;
if (! isc_log_wouldlog(dns_lctx, level))
return;
va_start(ap, fmt);
vsnprintf(msgbuf, sizeof(msgbuf), fmt, ap);
va_end(ap);
isc_log_write(dns_lctx,
DNS_LOGCATEGORY_DISPATCH, DNS_LOGMODULE_DISPATCH,
level, "dispatchmgr %p: %s", mgr, msgbuf);
}
static void
dispatch_log(dns_dispatch_t *disp, int level, const char *fmt, ...) {
char msgbuf[2048];
va_list ap;
if (! isc_log_wouldlog(dns_lctx, level))
return;
va_start(ap, fmt);
vsnprintf(msgbuf, sizeof(msgbuf), fmt, ap);
va_end(ap);
isc_log_write(dns_lctx,
DNS_LOGCATEGORY_DISPATCH, DNS_LOGMODULE_DISPATCH,
level, "dispatch %p: %s", disp, msgbuf);
}
static void
request_log(dns_dispatch_t *disp, dns_dispentry_t *resp,
int level, const char *fmt, ...)
{
char msgbuf[2048];
char peerbuf[256];
va_list ap;
if (! isc_log_wouldlog(dns_lctx, level))
return;
va_start(ap, fmt);
vsnprintf(msgbuf, sizeof(msgbuf), fmt, ap);
va_end(ap);
if (VALID_RESPONSE(resp)) {
isc_sockaddr_format(&resp->host, peerbuf, sizeof peerbuf);
isc_log_write(dns_lctx, DNS_LOGCATEGORY_DISPATCH,
DNS_LOGMODULE_DISPATCH, level,
"dispatch %p request %p %s: %s", disp, resp,
peerbuf, msgbuf);
} else if (VALID_REQUEST(resp)) {
isc_log_write(dns_lctx, DNS_LOGCATEGORY_DISPATCH,
DNS_LOGMODULE_DISPATCH, level,
"dispatch %p response %p: %s", disp, resp,
msgbuf);
} else {
isc_log_write(dns_lctx, DNS_LOGCATEGORY_DISPATCH,
DNS_LOGMODULE_DISPATCH, level,
"dispatch %p req/resp %p: %s", disp, resp,
msgbuf);
}
}
static void
reseed_lfsr(isc_lfsr_t *lfsr, void *arg)
{
dns_dispatch_t *disp = (dns_dispatch_t *)arg;
dns_dispatchmgr_t *mgr = disp->mgr;
isc_result_t result;
isc_uint32_t val;
if (mgr->entropy != NULL) {
result = isc_entropy_getdata(mgr->entropy, &val, sizeof val,
NULL, 0);
INSIST(result == ISC_R_SUCCESS);
lfsr->count = (val & 0x1f) + 32;
lfsr->state = val;
return;
}
lfsr->count = (random() & 0x1f) + 32; /* From 32 to 63 states */
lfsr->state = random();
}
/*
* Return an unpredictable message ID.
*/
static isc_uint32_t
dns_randomid(dns_dispatch_t *disp) {
isc_uint32_t id;
id = isc_lfsr_generate32(&disp->qid_lfsr1, &disp->qid_lfsr2);
return (id & 0x0000ffffU);
}
/*
* Return a hash of the destination and message id.
*/
static isc_uint32_t
dns_hash(dns_dispatch_t *disp, isc_sockaddr_t *dest, isc_uint32_t id) {
unsigned int ret;
ret = isc_sockaddr_hash(dest, ISC_TRUE);
ret ^= (id & 0x0000ffff); /* important to mask off garbage bits */
ret %= disp->qid_nbuckets;
INSIST(ret < disp->qid_nbuckets);
return (ret);
}
static dns_dispentry_t *
linear_first(dns_dispatch_t *disp) {
dns_dispentry_t *ret;
unsigned int bucket;
bucket = 0;
while (bucket < disp->qid_nbuckets) {
ret = ISC_LIST_HEAD(disp->qid_table[bucket]);
if (ret != NULL)
return (ret);
bucket++;
}
return (NULL);
}
static dns_dispentry_t *
linear_next(dns_dispatch_t *disp, dns_dispentry_t *resp) {
dns_dispentry_t *ret;
unsigned int bucket;
ret = ISC_LIST_NEXT(resp, link);
if (ret != NULL)
return (ret);
bucket = resp->bucket;
while (bucket < disp->qid_nbuckets) {
ret = ISC_LIST_HEAD(disp->qid_table[bucket]);
if (ret != NULL)
return (ret);
bucket++;
}
return (NULL);
}
/*
* The dispatch must be locked.
*/
static isc_boolean_t
destroy_disp_ok(dns_dispatch_t *disp)
{
if (disp->refcount != 0)
return (ISC_FALSE);
if (disp->recvs > 0)
return (ISC_FALSE);
if (disp->shutting_down == 0)
return (ISC_FALSE);
return (ISC_TRUE);
}
/*
* Called when refcount reaches 0 (and safe to destroy).
*
* The dispatcher must not be locked.
* The manager must be locked.
*/
static void
destroy_disp(dns_dispatch_t **dispp) {
dns_dispatchmgr_t *mgr;
dns_dispatch_t *disp;
disp = *dispp;
*dispp = NULL;
mgr = disp->mgr;
ISC_LIST_UNLINK(mgr->list, disp, link);
dispatch_log(disp, LVL(90),
"shutting down; detaching from sock %p, task %p",
disp->socket, disp->task);
isc_socket_detach(&disp->socket);
isc_task_detach(&disp->task);
dispatch_free(&disp);
}
static dns_dispentry_t *
bucket_search(dns_dispatch_t *disp, isc_sockaddr_t *dest, dns_messageid_t id,
unsigned int bucket)
{
dns_dispentry_t *res;
REQUIRE(bucket < disp->qid_nbuckets);
res = ISC_LIST_HEAD(disp->qid_table[bucket]);
while (res != NULL) {
if ((res->id == id) && isc_sockaddr_equal(dest, &res->host))
return (res);
res = ISC_LIST_NEXT(res, link);
}
return (NULL);
}
static void
free_buffer(dns_dispatch_t *disp, void *buf, unsigned int len) {
INSIST(buf != NULL && len != 0);
INSIST(disp->buffers > 0);
disp->buffers--;
switch (disp->socktype) {
case isc_sockettype_tcp:
isc_mem_put(disp->mgr->mctx, buf, len);
break;
case isc_sockettype_udp:
INSIST(len == disp->buffersize);
isc_mempool_put(disp->bpool, buf);
break;
default:
INSIST(0);
break;
}
}
static void *
allocate_udp_buffer(dns_dispatch_t *disp) {
void *temp;
temp = isc_mempool_get(disp->bpool);
if (temp != NULL)
disp->buffers++;
return (temp);
}
static inline void
free_event(dns_dispatch_t *disp, dns_dispatchevent_t *ev) {
if (disp->failsafe_ev == ev) {
INSIST(disp->shutdown_out == 1);
disp->shutdown_out = 0;
return;
}
isc_mempool_put(disp->mgr->epool, ev);
}
static inline dns_dispatchevent_t *
allocate_event(dns_dispatch_t *disp) {
dns_dispatchevent_t *ev;
ev = isc_mempool_get(disp->mgr->epool);
return (ev);
}
/*
* General flow:
*
* If I/O result == CANCELED, free the buffer and notify everyone as
* the various queues drain.
*
* If I/O is error (not canceled and not success) log it, free the buffer,
* and restart.
*
* If query:
* if no listeners: free the buffer, restart.
* if listener: allocate event, fill in details.
* If cannot allocate, free buffer, restart.
* if rq event queue is not empty, queue. else, send.
* restart.
*
* If response:
* Allocate event, fill in details.
* If cannot allocate, free buffer, restart.
* find target. If not found, free buffer, restart.
* if event queue is not empty, queue. else, send.
* restart.
*/
static void
udp_recv(isc_task_t *task, isc_event_t *ev_in) {
isc_socketevent_t *ev = (isc_socketevent_t *)ev_in;
dns_dispatch_t *disp = ev_in->ev_arg;
dns_messageid_t id;
isc_result_t dres;
isc_buffer_t source;
unsigned int flags;
dns_dispentry_t *resp;
dns_dispatchevent_t *rev;
unsigned int bucket;
isc_boolean_t killit;
isc_boolean_t queue_request;
isc_boolean_t queue_response;
dns_dispatchmgr_t *mgr;
UNUSED(task);
LOCK(&disp->lock);
mgr = disp->mgr;
dispatch_log(disp, LVL(90),
"got packet: requests %d, buffers %d, recvs %d",
disp->requests, disp->buffers, disp->recvs);
INSIST(disp->recvs > 0);
disp->recvs--;
if (disp->shutting_down) {
/*
* This dispatcher is shutting down.
*/
free_buffer(disp, ev->region.base, ev->region.length);
isc_event_free(&ev_in);
ev = NULL;
killit = destroy_disp_ok(disp);
UNLOCK(&disp->lock);
if (killit) {
LOCK(&mgr->lock);
destroy_disp(&disp);
killit = destroy_mgr_ok(mgr);
UNLOCK(&mgr->lock);
if (killit)
destroy_mgr(&mgr);
}
return;
}
if (ev->result != ISC_R_SUCCESS) {
free_buffer(disp, ev->region.base, ev->region.length);
/*
* If the recv() was canceled pass the word on.
*/
if (ev->result == ISC_R_CANCELED) {
UNLOCK(&disp->lock);
isc_event_free(&ev_in);
return;
}
dispatch_log(disp, LVL(10),
"odd socket result in udp_recv(): %s\n",
ev->result);
/*
* otherwise, on strange error, log it and restart.
* XXXMLG
*/
goto restart;
}
/*
* Peek into the buffer to see what we can see.
*/
isc_buffer_init(&source, ev->region.base, ev->region.length);
isc_buffer_add(&source, ev->n);
dres = dns_message_peekheader(&source, &id, &flags);
if (dres != ISC_R_SUCCESS) {
free_buffer(disp, ev->region.base, ev->region.length);
dispatch_log(disp, LVL(10), "got garbage packet");
goto restart;
}
dispatch_log(disp, LVL(92),
"got valid DNS message header, /QR %c, id %u",
((flags & DNS_MESSAGEFLAG_QR) ? '1' : '0'), id);
/*
* Look at flags. If query, check to see if we have someone handling
* them. If response, look to see where it goes.
*/
queue_request = ISC_FALSE;
queue_response = ISC_FALSE;
if ((flags & DNS_MESSAGEFLAG_QR) == 0) {
resp = ISC_LIST_HEAD(disp->rq_handlers);
while (resp != NULL) {
if (resp->item_out == ISC_FALSE)
break;
resp = ISC_LIST_NEXT(resp, link);
}
if (resp == NULL)
queue_request = ISC_TRUE;
rev = allocate_event(disp);
if (rev == NULL) {
free_buffer(disp, ev->region.base, ev->region.length);
goto restart;
}
/* query */
} else {
/* response */
bucket = dns_hash(disp, &ev->address, id);
resp = bucket_search(disp, &ev->address, id, bucket);
dispatch_log(disp, LVL(90),
"search for response in bucket %d: %s",
bucket, (resp == NULL ? "NOT FOUND" : "FOUND"));
if (resp == NULL) {
free_buffer(disp, ev->region.base, ev->region.length);
goto restart;
}
queue_response = resp->item_out;
rev = allocate_event(disp);
if (rev == NULL) {
free_buffer(disp, ev->region.base, ev->region.length);
goto restart;
}
}
/*
* At this point, rev contains the event we want to fill in, and
* resp contains the information on the place to send it to.
* Send the event off.
*/
isc_buffer_init(&rev->buffer, ev->region.base, ev->region.length);
isc_buffer_add(&rev->buffer, ev->n);
rev->result = ISC_R_SUCCESS;
rev->id = id;
rev->addr = ev->address;
rev->pktinfo = ev->pktinfo;
rev->attributes = ev->attributes;
if (queue_request) {
ISC_LIST_APPEND(disp->rq_events, rev, ev_link);
} else if (queue_response) {
ISC_LIST_APPEND(resp->items, rev, ev_link);
} else {
ISC_EVENT_INIT(rev, sizeof(*rev), 0, NULL,
DNS_EVENT_DISPATCH,
resp->action, resp->arg, resp, NULL, NULL);
request_log(disp, resp, LVL(90),
"[a] Sent event %p buffer %p len %d to task %p",
rev, rev->buffer.base, rev->buffer.length,
resp->task);
resp->item_out = ISC_TRUE;
isc_task_send(resp->task, (isc_event_t **)&rev);
}
/*
* Restart recv() to get the next packet.
*/
restart:
startrecv(disp);
UNLOCK(&disp->lock);
isc_event_free(&ev_in);
}
/*
* General flow:
*
* If I/O result == CANCELED, free the buffer and notify everyone as
* the various queues drain.
*
* If I/O is error (not canceled and not success) log it, free the buffer,
* and restart.
*
* If query:
* if no listeners: free the buffer, restart.
* if listener: allocate event, fill in details.
* If cannot allocate, free buffer, restart.
* if rq event queue is not empty, queue. else, send.
* restart.
*
* If response:
* Allocate event, fill in details.
* If cannot allocate, free buffer, restart.
* find target. If not found, free buffer, restart.
* if event queue is not empty, queue. else, send.
* restart.
*/
static void
tcp_recv(isc_task_t *task, isc_event_t *ev_in) {
dns_dispatch_t *disp = ev_in->ev_arg;
dns_dispatchmgr_t *mgr;
dns_tcpmsg_t *tcpmsg = &disp->tcpmsg;
dns_messageid_t id;
isc_result_t dres;
unsigned int flags;
dns_dispentry_t *resp;
dns_dispatchevent_t *rev;
unsigned int bucket;
isc_boolean_t killit;
isc_boolean_t queue_request;
isc_boolean_t queue_response;
UNUSED(task);
REQUIRE(VALID_DISPATCH(disp));
mgr = disp->mgr;
dispatch_log(disp, LVL(90),
"got TCP packet: requests %d, buffers %d, recvs %d",
disp->requests, disp->buffers, disp->recvs);
LOCK(&disp->lock);
INSIST(disp->recvs > 0);
disp->recvs--;
if (disp->refcount == 0) {
/*
* This dispatcher is shutting down. Force cancelation.
*/
tcpmsg->result = ISC_R_CANCELED;
}
switch (tcpmsg->result) {
case ISC_R_SUCCESS:
break;
case ISC_R_EOF:
dispatch_log(disp, LVL(90), "shutting down on EOF");
disp->shutdown_why = ISC_R_EOF;
disp->shutting_down = 1;
do_cancel(disp, NULL);
/* FALLTHROUGH */
case ISC_R_CANCELED:
/*
* The event is statically allocated in the tcpmsg
* structure, and destroy_disp() frees the tcpmsg, so we must
* free the event *before* calling destroy_disp().
*/
isc_event_free(&ev_in);
disp->shutting_down = 1;
/*
* If the recv() was canceled pass the word on.
*/
killit = destroy_disp_ok(disp);
UNLOCK(&disp->lock);
if (killit) {
LOCK(&mgr->lock);
destroy_disp(&disp);
killit = destroy_mgr_ok(mgr);
UNLOCK(&mgr->lock);
if (killit)
destroy_mgr(&mgr);
}
return;
default:
/*
* otherwise, on strange error, log it and restart.
* XXXMLG
*/
goto restart;
}
dispatch_log(disp, LVL(90), "result %d, length == %d, addr = %p",
tcpmsg->result,
tcpmsg->buffer.length, tcpmsg->buffer.base);
/*
* Peek into the buffer to see what we can see.
*/
dres = dns_message_peekheader(&tcpmsg->buffer, &id, &flags);
if (dres != ISC_R_SUCCESS) {
dispatch_log(disp, LVL(10), "got garbage packet");
goto restart;
}
dispatch_log(disp, LVL(92),
"got valid DNS message header, /QR %c, id %u",
((flags & DNS_MESSAGEFLAG_QR) ? '1' : '0'), id);
/*
* Allocate an event to send to the query or response client, and
* allocate a new buffer for our use.
*/
/*
* Look at flags. If query, check to see if we have someone handling
* them. If response, look to see where it goes.
*/
queue_request = ISC_FALSE;
queue_response = ISC_FALSE;
if ((flags & DNS_MESSAGEFLAG_QR) == 0) {
resp = ISC_LIST_HEAD(disp->rq_handlers);
while (resp != NULL) {
if (resp->item_out == ISC_FALSE)
break;
resp = ISC_LIST_NEXT(resp, link);
}
if (resp == NULL)
queue_request = ISC_TRUE;
rev = allocate_event(disp);
if (rev == NULL)
goto restart;
/* query */
} else {
/* response */
bucket = dns_hash(disp, &tcpmsg->address, id);
resp = bucket_search(disp, &tcpmsg->address, id, bucket);
dispatch_log(disp, LVL(90),
"search for response in bucket %d: %s",
bucket, (resp == NULL ? "NOT FOUND" : "FOUND"));
if (resp == NULL)
goto restart;
queue_response = resp->item_out;
rev = allocate_event(disp);
if (rev == NULL)
goto restart;
}
/*
* At this point, rev contains the event we want to fill in, and
* resp contains the information on the place to send it to.
* Send the event off.
*/
dns_tcpmsg_keepbuffer(tcpmsg, &rev->buffer);
disp->buffers++;
rev->result = ISC_R_SUCCESS;
rev->id = id;
rev->addr = tcpmsg->address;
if (queue_request) {
ISC_LIST_APPEND(disp->rq_events, rev, ev_link);
} else if (queue_response) {
ISC_LIST_APPEND(resp->items, rev, ev_link);
} else {
ISC_EVENT_INIT(rev, sizeof(*rev), 0, NULL, DNS_EVENT_DISPATCH,
resp->action, resp->arg, resp, NULL, NULL);
request_log(disp, resp, LVL(90),
"[b] Sent event %p buffer %p len %d to task %p",
rev, rev->buffer.base, rev->buffer.length,
resp->task);
resp->item_out = ISC_TRUE;
isc_task_send(resp->task, (isc_event_t **)&rev);
}
/*
* Restart recv() to get the next packet.
*/
restart:
startrecv(disp);
UNLOCK(&disp->lock);
isc_event_free(&ev_in);
}
/*
* disp must be locked.
*/
static void
startrecv(dns_dispatch_t *disp) {
isc_result_t res;
isc_region_t region;
unsigned int wanted;
if (disp->shutting_down == 1)
return;
wanted = ISC_MIN(disp->recvs_wanted, disp->requests + 2);
if (wanted == 0)
return;
if (disp->recvs >= wanted)
return;
if (disp->buffers >= disp->maxbuffers)
return;
while (disp->recvs < wanted) {
switch (disp->socktype) {
/*
* UDP reads are always maximal.
*/
case isc_sockettype_udp:
region.length = disp->buffersize;
region.base = allocate_udp_buffer(disp);
if (region.base == NULL)
return;
res = isc_socket_recv(disp->socket, &region, 1,
disp->task, udp_recv, disp);
if (res != ISC_R_SUCCESS) {
disp->shutdown_why = res;
disp->shutting_down = 1;
do_cancel(disp, NULL);
return;
}
disp->recvs++;
break;
case isc_sockettype_tcp:
res = dns_tcpmsg_readmessage(&disp->tcpmsg,
disp->task, tcp_recv,
disp);
if (res != ISC_R_SUCCESS) {
disp->shutdown_why = res;
disp->shutting_down = 1;
do_cancel(disp, NULL);
return;
}
disp->recvs++;
break;
}
}
}
/*
* Mgr must be locked when calling this function.
*/
static isc_boolean_t
destroy_mgr_ok(dns_dispatchmgr_t *mgr) {
mgr_log(mgr, LVL(90),
"destroy_mgr_ok: shuttingdown=%d, listnonempty=%d, "
"epool=%d, rpool=%d, dpool=%d",
MGR_IS_SHUTTINGDOWN(mgr), !ISC_LIST_EMPTY(mgr->list),
isc_mempool_getallocated(mgr->epool),
isc_mempool_getallocated(mgr->rpool),
isc_mempool_getallocated(mgr->dpool));
if (!MGR_IS_SHUTTINGDOWN(mgr))
return (ISC_FALSE);
if (!ISC_LIST_EMPTY(mgr->list))
return (ISC_FALSE);
if (isc_mempool_getallocated(mgr->epool) != 0)
return (ISC_FALSE);
if (isc_mempool_getallocated(mgr->rpool) != 0)
return (ISC_FALSE);
if (isc_mempool_getallocated(mgr->dpool) != 0)
return (ISC_FALSE);
return (ISC_TRUE);
}
/*
* Mgr must be unlocked when calling this function.
*/
static void
destroy_mgr(dns_dispatchmgr_t **mgrp) {
isc_mem_t *mctx;
dns_dispatchmgr_t *mgr;
mgr = *mgrp;
*mgrp = NULL;
mctx = mgr->mctx;
mgr->magic = 0;
mgr->mctx = 0;
isc_mutex_destroy(&mgr->lock);
mgr->state = 0;
isc_mempool_destroy(&mgr->epool);
isc_mempool_destroy(&mgr->rpool);
isc_mempool_destroy(&mgr->dpool);
isc_mutex_destroy(&mgr->pool_lock);
if (mgr->entropy != NULL)
isc_entropy_detach(&mgr->entropy);
isc_mem_put(mctx, mgr, sizeof(dns_dispatchmgr_t));
isc_mem_detach(&mctx);
}
static isc_result_t
create_socket(isc_socketmgr_t *mgr, isc_sockaddr_t *local,
isc_socket_t **sockp)
{
isc_socket_t *sock;
isc_result_t result;
sock = NULL;
result = isc_socket_create(mgr, isc_sockaddr_pf(local),
isc_sockettype_udp, &sock);
if (result != ISC_R_SUCCESS)
return (result);
result = isc_socket_bind(sock, local);
if (result != ISC_R_SUCCESS) {
isc_socket_detach(&sock);
return (result);
}
*sockp = sock;
return (ISC_R_SUCCESS);
}
/*
* Publics.
*/
isc_result_t
dns_dispatchmgr_create(isc_mem_t *mctx, isc_entropy_t *entropy,
dns_dispatchmgr_t **mgrp)
{
dns_dispatchmgr_t *mgr;
isc_result_t result;
REQUIRE(mctx != NULL);
REQUIRE(mgrp != NULL && *mgrp == NULL);
mgr = isc_mem_get(mctx, sizeof(dns_dispatchmgr_t));
if (mgr == NULL)
return (ISC_R_NOMEMORY);
mgr->mctx = NULL;
isc_mem_attach(mctx, &mgr->mctx);
result = isc_mutex_init(&mgr->lock);
if (result != ISC_R_SUCCESS)
goto deallocate;
result = isc_mutex_init(&mgr->pool_lock);
if (result != ISC_R_SUCCESS)
goto kill_lock;
mgr->epool = NULL;
if (isc_mempool_create(mgr->mctx, sizeof(dns_dispatchevent_t),
&mgr->epool) != ISC_R_SUCCESS) {
result = ISC_R_NOMEMORY;
goto kill_pool_lock;
}
mgr->rpool = NULL;
if (isc_mempool_create(mgr->mctx, sizeof(dns_dispentry_t),
&mgr->rpool) != ISC_R_SUCCESS) {
result = ISC_R_NOMEMORY;
goto kill_epool;
}
mgr->dpool = NULL;
if (isc_mempool_create(mgr->mctx, sizeof(dns_dispatch_t),
&mgr->dpool) != ISC_R_SUCCESS) {
result = ISC_R_NOMEMORY;
goto kill_rpool;
}
isc_mempool_setname(mgr->epool, "dispmgr_epool");
isc_mempool_setfreemax(mgr->epool, 1024);
isc_mempool_associatelock(mgr->epool, &mgr->pool_lock);
isc_mempool_setname(mgr->rpool, "dispmgr_rpool");
isc_mempool_setfreemax(mgr->rpool, 1024);
isc_mempool_associatelock(mgr->rpool, &mgr->pool_lock);
isc_mempool_setname(mgr->dpool, "dispmgr_dpool");
isc_mempool_setfreemax(mgr->dpool, 1024);
isc_mempool_associatelock(mgr->dpool, &mgr->pool_lock);
mgr->magic = DNS_DISPATCHMGR_MAGIC;
mgr->state = 0;
ISC_LIST_INIT(mgr->list);
mgr->entropy = NULL;
if (entropy != NULL)
isc_entropy_attach(entropy, &mgr->entropy);
*mgrp = mgr;
return (ISC_R_SUCCESS);
#if 0
kill_dpool:
isc_mempool_destroy(&mgr->dpool);
#endif
kill_rpool:
isc_mempool_destroy(&mgr->rpool);
kill_epool:
isc_mempool_destroy(&mgr->epool);
kill_pool_lock:
isc_mutex_destroy(&mgr->pool_lock);
kill_lock:
isc_mutex_destroy(&mgr->lock);
deallocate:
isc_mem_put(mctx, mgr, sizeof(dns_dispatchmgr_t));
isc_mem_detach(&mgr->mctx);
return (result);
}
void
dns_dispatchmgr_destroy(dns_dispatchmgr_t **mgrp) {
dns_dispatchmgr_t *mgr;
isc_boolean_t killit;
REQUIRE(mgrp != NULL);
REQUIRE(VALID_DISPATCHMGR(*mgrp));
mgr = *mgrp;
*mgrp = NULL;
LOCK(&mgr->lock);
mgr->state |= MGR_SHUTTINGDOWN;
killit = destroy_mgr_ok(mgr);
UNLOCK(&mgr->lock);
mgr_log(mgr, LVL(90), "destroy: killit=%d", killit);
if (killit)
destroy_mgr(&mgr);
}
#define ATTRMATCH(_a1, _a2, _mask) (((_a1) & (_mask)) == ((_a2) & (_mask)))
static isc_boolean_t
local_addr_match(dns_dispatch_t *disp, isc_sockaddr_t *addr) {
in_port_t port;
if (addr == NULL)
return (ISC_TRUE);
port = isc_sockaddr_getport(addr);
if (port == 0)
return (isc_sockaddr_eqaddr(&disp->local, addr));
else
return (isc_sockaddr_equal(&disp->local, addr));
}
/*
* Requires mgr be locked.
*
* No dispatcher can be locked by this thread when calling this function.
*
*
* NOTE:
* If a matching dispatcher is found, it is locked after this function
* returns, and must be unlocked by the caller.
*/
static isc_result_t
dispatch_find(dns_dispatchmgr_t *mgr, isc_sockaddr_t *local,
unsigned int attributes, unsigned int mask,
dns_dispatch_t **dispp)
{
dns_dispatch_t *disp;
isc_result_t result;
/*
* Make certain that we will not match a private dispatch.
*/
attributes &= ~DNS_DISPATCHATTR_PRIVATE;
mask |= DNS_DISPATCHATTR_PRIVATE;
disp = ISC_LIST_HEAD(mgr->list);
while (disp != NULL) {
LOCK(&disp->lock);
if ((disp->shutting_down == 0)
&& ATTRMATCH(disp->attributes, attributes, mask)
&& local_addr_match(disp, local))
break;
UNLOCK(&disp->lock);
disp = ISC_LIST_NEXT(disp, link);
}
if (disp == NULL) {
result = ISC_R_NOTFOUND;
goto out;
}
*dispp = disp;
result = ISC_R_SUCCESS;
out:
return (result);
}
/*
* Allocate and set important limits.
*/
static isc_result_t
dispatch_allocate(dns_dispatchmgr_t *mgr, unsigned int buffersize,
unsigned int maxbuffers, unsigned int maxrequests,
unsigned int buckets, unsigned int increment,
dns_dispatch_t **dispp)
{
unsigned int i;
dns_dispatch_t *disp;
isc_result_t res;
REQUIRE(VALID_DISPATCHMGR(mgr));
REQUIRE(buffersize >= 512 && buffersize < (64 * 1024));
REQUIRE(maxbuffers > 0);
REQUIRE(buckets < 2097169); /* next prime > 65536 * 32 */
REQUIRE(increment > buckets);
REQUIRE(dispp != NULL && *dispp == NULL);
/*
* Set up the dispatcher, mostly. Don't bother setting some of
* the options that are controlled by tcp vs. udp, etc.
*/
disp = isc_mempool_get(mgr->dpool);
if (disp == NULL)
return (ISC_R_NOMEMORY);
disp->magic = 0;
disp->mgr = mgr;
disp->buffersize = buffersize;
disp->maxrequests = maxrequests;
disp->maxbuffers = maxbuffers;
disp->attributes = 0;
ISC_LINK_INIT(disp, link);
disp->refcount = 1;
disp->recvs = 0;
memset(&disp->local, 0, sizeof disp->local);
disp->shutting_down = 0;
disp->shutdown_out = 0;
disp->connected = 0;
disp->tcpmsg_valid = 0;
disp->shutdown_why = ISC_R_UNEXPECTED;
disp->requests = 0;
disp->buffers = 0;
ISC_LIST_INIT(disp->rq_handlers);
ISC_LIST_INIT(disp->rq_events);
disp->qid_table = isc_mem_get(mgr->mctx,
buckets * sizeof(dns_displist_t));
if (disp->qid_table == NULL) {
res = ISC_R_NOMEMORY;
goto deallocate;
}
for (i = 0 ; i < buckets ; i++)
ISC_LIST_INIT(disp->qid_table[i]);
disp->qid_nbuckets = buckets;
disp->qid_increment = increment;
if (isc_mutex_init(&disp->lock) != ISC_R_SUCCESS) {
res = ISC_R_UNEXPECTED;
UNEXPECTED_ERROR(__FILE__, __LINE__, "isc_mutex_init failed");
goto deallocate_qidtable;
}
disp->bpool = NULL;
if (isc_mempool_create(mgr->mctx, buffersize,
&disp->bpool) != ISC_R_SUCCESS) {
res = ISC_R_NOMEMORY;
goto kill_lock;
}
isc_mempool_setmaxalloc(disp->bpool, maxbuffers);
isc_mempool_setname(disp->bpool, "disp_bpool");
/*
* Keep some number of items around. This should be a config
* option. For now, keep 8, but later keep at least two even
* if the caller wants less. This allows us to ensure certain
* things, like an event can be "freed" and the next allocation
* will always succeed.
*
* Note that if limits are placed on anything here, we use one
* event internally, so the actual limit should be "wanted + 1."
*
* XXXMLG
*/
isc_mempool_setfreemax(disp->bpool, 8);
disp->failsafe_ev = allocate_event(disp);
if (disp->failsafe_ev == NULL) {
res = ISC_R_NOMEMORY;
goto kill_bpool;
}
/*
* Initialize to a 32-bit LFSR. Both of these are from Applied
* Cryptography.
*
* lfsr1:
* x^32 + x^7 + x^5 + x^3 + x^2 + x + 1
*
* lfsr2:
* x^32 + x^7 + x^6 + x^2 + 1
*/
isc_lfsr_init(&disp->qid_lfsr1, 0, 32, 0x80000057U,
0, reseed_lfsr, disp);
isc_lfsr_init(&disp->qid_lfsr2, 0, 32, 0x800000c2U,
0, reseed_lfsr, disp);
disp->magic = DISPATCH_MAGIC;
*dispp = disp;
return (ISC_R_SUCCESS);
/*
* error returns
*/
kill_bpool:
isc_mempool_destroy(&disp->bpool);
kill_lock:
isc_mutex_destroy(&disp->lock);
deallocate_qidtable:
isc_mem_put(mgr->mctx, disp->qid_table,
disp->qid_nbuckets * sizeof(dns_displist_t));
deallocate:
isc_mempool_put(mgr->dpool, disp);
return (res);
}
/*
* MUST be unlocked, and not used by anthing.
*/
static void
dispatch_free(dns_dispatch_t **dispp)
{
dns_dispatch_t *disp;
dns_dispatchmgr_t *mgr;
dns_dispatchevent_t *ev;
REQUIRE(VALID_DISPATCH(*dispp));
disp = *dispp;
*dispp = NULL;
mgr = disp->mgr;
REQUIRE(VALID_DISPATCHMGR(mgr));
if (disp->tcpmsg_valid) {
dns_tcpmsg_invalidate(&disp->tcpmsg);
disp->tcpmsg_valid = 0;
}
/*
* Final cleanup of packets on the request list.
*/
ev = ISC_LIST_HEAD(disp->rq_events);
while (ev != NULL) {
ISC_LIST_UNLINK(disp->rq_events, ev, ev_link);
free_buffer(disp, ev->buffer.base, ev->buffer.length);
free_event(disp, ev);
ev = ISC_LIST_HEAD(disp->rq_events);
}
INSIST(disp->buffers == 0);
INSIST(disp->requests == 0);
INSIST(disp->recvs == 0);
isc_mempool_put(mgr->epool, disp->failsafe_ev);
disp->failsafe_ev = NULL;
isc_mempool_destroy(&disp->bpool);
isc_mutex_destroy(&disp->lock);
isc_mem_put(mgr->mctx, disp->qid_table,
disp->qid_nbuckets * sizeof(dns_displist_t));
disp->mgr = NULL;
disp->magic = 0;
isc_mempool_put(mgr->dpool, disp);
}
isc_result_t
dns_dispatch_createtcp(dns_dispatchmgr_t *mgr, isc_socket_t *sock,
isc_taskmgr_t *taskmgr, unsigned int buffersize,
unsigned int maxbuffers, unsigned int maxrequests,
unsigned int buckets, unsigned int increment,
unsigned int attributes, dns_dispatch_t **dispp)
{
isc_result_t result;
dns_dispatch_t *disp;
REQUIRE(VALID_DISPATCHMGR(mgr));
REQUIRE(isc_socket_gettype(sock) == isc_sockettype_tcp);
REQUIRE((attributes & DNS_DISPATCHATTR_TCP) != 0);
attributes |= DNS_DISPATCHATTR_PRIVATE; /* XXXMLG */
LOCK(&mgr->lock);
/*
* dispatch_allocate() checks mgr, buffersize, maxbuffers,
* buckets, and increment for us.
*/
disp = NULL;
result = dispatch_allocate(mgr, buffersize, maxbuffers, maxrequests,
buckets, increment, &disp);
if (result != ISC_R_SUCCESS) {
UNLOCK(&mgr->lock);
return (result);
}
disp->socktype = isc_sockettype_tcp;
disp->socket = NULL;
isc_socket_attach(sock, &disp->socket);
disp->recvs_wanted = 1;
disp->task = NULL;
result = isc_task_create(taskmgr, 0, &disp->task);
if (result != ISC_R_SUCCESS)
goto kill_socket;
isc_task_setname(disp->task, "tcpdispatch", disp);
dns_tcpmsg_init(mgr->mctx, disp->socket, &disp->tcpmsg);
disp->tcpmsg_valid = 1;
attributes &= ~DNS_DISPATCHATTR_UDP;
attributes |= DNS_DISPATCHATTR_TCP;
disp->attributes = attributes;
/*
* Append it to the dispatcher list.
*/
ISC_LIST_APPEND(mgr->list, disp, link);
UNLOCK(&mgr->lock);
mgr_log(mgr, LVL(90), "created TCP dispatcher %p", disp);
dispatch_log(disp, LVL(90), "created task %p", disp->task);
*dispp = disp;
return (ISC_R_SUCCESS);
/*
* Error returns.
*/
kill_socket:
isc_socket_detach(&disp->socket);
dispatch_free(&disp);
UNLOCK(&mgr->lock);
return (result);
}
isc_result_t
dns_dispatch_getudp(dns_dispatchmgr_t *mgr, isc_socketmgr_t *sockmgr,
isc_taskmgr_t *taskmgr, isc_sockaddr_t *localaddr,
unsigned int buffersize,
unsigned int maxbuffers, unsigned int maxrequests,
unsigned int buckets, unsigned int increment,
unsigned int attributes, unsigned int mask,
dns_dispatch_t **dispp)
{
isc_result_t result;
dns_dispatch_t *disp;
REQUIRE(VALID_DISPATCHMGR(mgr));
REQUIRE(sockmgr != NULL);
REQUIRE(localaddr != NULL);
REQUIRE(taskmgr != NULL);
REQUIRE(buffersize >= 512 && buffersize < (64 * 1024));
REQUIRE(maxbuffers > 0);
REQUIRE(buckets < 2097169); /* next prime > 65536 * 32 */
REQUIRE(increment > buckets);
REQUIRE(dispp != NULL && *dispp == NULL);
REQUIRE((attributes & DNS_DISPATCHATTR_TCP) == 0);
LOCK(&mgr->lock);
/*
* First, see if we have a dispatcher that matches.
*/
disp = NULL;
result = dispatch_find(mgr, localaddr, attributes, mask, &disp);
if (result == ISC_R_SUCCESS) {
disp->refcount++;
if (disp->maxbuffers < maxbuffers) {
isc_mempool_setmaxalloc(disp->bpool, maxbuffers);
disp->maxbuffers = maxbuffers;
}
if (disp->maxrequests < maxrequests)
disp->maxrequests = maxrequests;
UNLOCK(&disp->lock);
UNLOCK(&mgr->lock);
*dispp = disp;
return (ISC_R_SUCCESS);
}
/*
* Nope, create one.
*/
result = dispatch_createudp(mgr, sockmgr, taskmgr, localaddr,
buffersize, maxbuffers, maxrequests,
buckets, increment, attributes, &disp);
if (result != ISC_R_SUCCESS) {
UNLOCK(&mgr->lock);
return (result);
}
UNLOCK(&mgr->lock);
*dispp = disp;
return (ISC_R_SUCCESS);
}
/*
* mgr should be locked.
*/
static isc_result_t
dispatch_createudp(dns_dispatchmgr_t *mgr, isc_socketmgr_t *sockmgr,
isc_taskmgr_t *taskmgr,
isc_sockaddr_t *localaddr, unsigned int buffersize,
unsigned int maxbuffers, unsigned int maxrequests,
unsigned int buckets, unsigned int increment,
unsigned int attributes,
dns_dispatch_t **dispp)
{
isc_result_t result;
dns_dispatch_t *disp;
isc_socket_t *sock;
/*
* dispatch_allocate() checks mgr, buffersize, maxbuffers,
* buckets, and increment for us.
*/
disp = NULL;
result = dispatch_allocate(mgr, buffersize, maxbuffers, maxrequests,
buckets, increment, &disp);
if (result != ISC_R_SUCCESS)
return (result);
result = create_socket(sockmgr, localaddr, &sock);
if (result != ISC_R_SUCCESS)
goto deallocate_dispatch;
disp->local = *localaddr;
disp->socket = sock;
disp->socktype = isc_sockettype_udp;
disp->recvs_wanted = 4; /* XXXMLG config option */
disp->task = NULL;
result = isc_task_create(taskmgr, 0, &disp->task);
if (result != ISC_R_SUCCESS)
goto kill_socket;
isc_task_setname(disp->task, "udpdispatch", disp);
attributes &= ~DNS_DISPATCHATTR_TCP;
attributes |= DNS_DISPATCHATTR_UDP;
disp->attributes = attributes;
/*
* Append it to the dispatcher list.
*/
ISC_LIST_APPEND(mgr->list, disp, link);
mgr_log(mgr, LVL(90), "created UDP dispatcher %p", disp);
dispatch_log(disp, LVL(90), "created task %p", disp->task);
dispatch_log(disp, LVL(90), "created socket %p", disp->socket);
*dispp = disp;
return (ISC_R_SUCCESS);
/*
* Error returns.
*/
kill_socket:
isc_socket_detach(&disp->socket);
deallocate_dispatch:
dispatch_free(&disp);
return (result);
}
void
dns_dispatch_attach(dns_dispatch_t *disp, dns_dispatch_t **dispp) {
REQUIRE(VALID_DISPATCH(disp));
REQUIRE(dispp != NULL && *dispp == NULL);
LOCK(&disp->lock);
disp->refcount++;
UNLOCK(&disp->lock);
*dispp = disp;
}
/*
* It is important to lock the manager while we are deleting the dispatch,
* since dns_dispatch_getudp will call dispatch_find, which returns to
* the caller a dispatch but does not attach to it until later. _getudp
* locks the manager, however, so locking it here will keep us from attaching
* to a dispatcher that is in the process of going away.
*/
void
dns_dispatch_detach(dns_dispatch_t **dispp) {
dns_dispatch_t *disp;
isc_boolean_t killit;
dns_dispatchmgr_t *mgr;
REQUIRE(dispp != NULL && VALID_DISPATCH(*dispp));
disp = *dispp;
*dispp = NULL;
mgr = disp->mgr;
LOCK(&disp->lock);
INSIST(disp->refcount > 0);
disp->refcount--;
killit = ISC_FALSE;
if (disp->refcount == 0) {
if (disp->recvs > 0)
isc_socket_cancel(disp->socket, NULL,
ISC_SOCKCANCEL_RECV);
disp->shutting_down = 1;
}
dispatch_log(disp, LVL(90), "detach: refcount %d", disp->refcount);
killit = destroy_disp_ok(disp);
UNLOCK(&disp->lock);
if (killit) {
LOCK(&mgr->lock);
destroy_disp(&disp);
killit = destroy_mgr_ok(mgr);
UNLOCK(&mgr->lock);
if (killit)
destroy_mgr(&mgr);
}
}
isc_result_t
dns_dispatch_addresponse(dns_dispatch_t *disp, isc_sockaddr_t *dest,
isc_task_t *task, isc_taskaction_t action, void *arg,
dns_messageid_t *idp, dns_dispentry_t **resp)
{
dns_dispentry_t *res;
unsigned int bucket;
dns_messageid_t id;
int i;
isc_boolean_t ok;
REQUIRE(VALID_DISPATCH(disp));
REQUIRE(task != NULL);
REQUIRE(dest != NULL);
REQUIRE(resp != NULL && *resp == NULL);
REQUIRE(idp != NULL);
LOCK(&disp->lock);
if (disp->shutting_down == 1) {
UNLOCK(&disp->lock);
return (ISC_R_SHUTTINGDOWN);
}
if (disp->requests >= disp->maxrequests) {
UNLOCK(&disp->lock);
return (ISC_R_QUOTA);
}
/*
* Try somewhat hard to find an unique ID.
*/
id = dns_randomid(disp);
bucket = dns_hash(disp, dest, id);
ok = ISC_FALSE;
for (i = 0 ; i < 64 ; i++) {
if (bucket_search(disp, dest, id, bucket) == NULL) {
ok = ISC_TRUE;
break;
}
id += disp->qid_increment;
id &= 0x0000ffff;
bucket = dns_hash(disp, dest, id);
}
if (!ok) {
UNLOCK(&disp->lock);
return (ISC_R_NOMORE);
}
res = isc_mempool_get(disp->mgr->rpool);
if (res == NULL) {
UNLOCK(&disp->lock);
return (ISC_R_NOMEMORY);
}
disp->refcount++;
disp->requests++;
res->task = NULL;
isc_task_attach(task, &res->task);
res->disp = disp;
res->id = id;
res->bucket = bucket;
res->host = *dest;
res->action = action;
res->arg = arg;
res->item_out = ISC_FALSE;
ISC_LIST_INIT(res->items);
ISC_LINK_INIT(res, link);
res->magic = RESPONSE_MAGIC;
ISC_LIST_APPEND(disp->qid_table[bucket], res, link);
request_log(disp, res, LVL(90),
"attached to task %p", res->task);
if (((disp->attributes & DNS_DISPATCHATTR_UDP) != 0) ||
((disp->attributes & DNS_DISPATCHATTR_CONNECTED) != 0))
startrecv(disp);
UNLOCK(&disp->lock);
*idp = id;
*resp = res;
return (ISC_R_SUCCESS);
}
void
dns_dispatch_starttcp(dns_dispatch_t *disp) {
REQUIRE(VALID_DISPATCH(disp));
dispatch_log(disp, LVL(90), "starttcp %p", disp->task);
LOCK(&disp->lock);
disp->attributes |= DNS_DISPATCHATTR_CONNECTED;
startrecv(disp);
UNLOCK(&disp->lock);
}
void
dns_dispatch_removeresponse(dns_dispentry_t **resp,
dns_dispatchevent_t **sockevent)
{
dns_dispatchmgr_t *mgr;
dns_dispatch_t *disp;
dns_dispentry_t *res;
dns_dispatchevent_t *ev;
unsigned int bucket;
isc_boolean_t killit;
unsigned int n;
isc_eventlist_t events;
REQUIRE(resp != NULL);
REQUIRE(VALID_RESPONSE(*resp));
res = *resp;
*resp = NULL;
disp = res->disp;
REQUIRE(VALID_DISPATCH(disp));
mgr = disp->mgr;
REQUIRE(VALID_DISPATCHMGR(mgr));
if (sockevent != NULL) {
REQUIRE(*sockevent != NULL);
ev = *sockevent;
*sockevent = NULL;
} else {
ev = NULL;
}
LOCK(&disp->lock);
INSIST(disp->requests > 0);
disp->requests--;
INSIST(disp->refcount > 0);
disp->refcount--;
killit = ISC_FALSE;
if (disp->refcount == 0) {
if (disp->recvs > 0)
isc_socket_cancel(disp->socket, NULL,
ISC_SOCKCANCEL_RECV);
disp->shutting_down = 1;
}
bucket = res->bucket;
ISC_LIST_UNLINK(disp->qid_table[bucket], res, link);
if (ev == NULL && res->item_out) {
/*
* We've posted our event, but the caller hasn't gotten it
* yet. Take it back.
*/
ISC_LIST_INIT(events);
n = isc_task_unsend(res->task, res, DNS_EVENT_DISPATCH,
NULL, &events);
/*
* We had better have gotten it back.
*/
INSIST(n == 1);
ev = (dns_dispatchevent_t *)ISC_LIST_HEAD(events);
}
if (ev != NULL) {
REQUIRE(res->item_out == ISC_TRUE);
res->item_out = ISC_FALSE;
if (ev->buffer.base != NULL)
free_buffer(disp, ev->buffer.base, ev->buffer.length);
free_event(disp, ev);
}
request_log(disp, res, LVL(90), "detaching from task %p", res->task);
isc_task_detach(&res->task);
/*
* Free any buffered requests as well
*/
ev = ISC_LIST_HEAD(res->items);
while (ev != NULL) {
ISC_LIST_UNLINK(res->items, ev, ev_link);
if (ev->buffer.base != NULL)
free_buffer(disp, ev->buffer.base, ev->buffer.length);
free_event(disp, ev);
ev = ISC_LIST_HEAD(res->items);
}
res->magic = 0;
isc_mempool_put(disp->mgr->rpool, res);
if (disp->shutting_down == 1)
do_cancel(disp, NULL);
else
startrecv(disp);
killit = destroy_disp_ok(disp);
UNLOCK(&disp->lock);
if (killit) {
destroy_disp(&disp);
killit = destroy_mgr_ok(mgr);
UNLOCK(&mgr->lock);
if (killit)
destroy_mgr(&mgr);
}
}
isc_result_t
dns_dispatch_addrequest(dns_dispatch_t *disp,
isc_task_t *task, isc_taskaction_t action, void *arg,
dns_dispentry_t **resp)
{
dns_dispentry_t *res;
REQUIRE(VALID_DISPATCH(disp));
REQUIRE(task != NULL);
REQUIRE(resp != NULL && *resp == NULL);
LOCK(&disp->lock);
if (disp->shutting_down == 1) {
UNLOCK(&disp->lock);
return (ISC_R_SHUTTINGDOWN);
}
if (disp->requests >= disp->maxrequests) {
UNLOCK(&disp->lock);
return (ISC_R_QUOTA);
}
res = isc_mempool_get(disp->mgr->rpool);
if (res == NULL) {
UNLOCK(&disp->lock);
return (ISC_R_NOMEMORY);
}
disp->refcount++;
disp->requests++;
res->task = NULL;
isc_task_attach(task, &res->task);
res->magic = REQUEST_MAGIC;
res->disp = disp;
res->bucket = INVALID_BUCKET;
res->action = action;
res->arg = arg;
res->item_out = ISC_FALSE;
ISC_LIST_INIT(res->items);
ISC_LINK_INIT(res, link);
ISC_LIST_APPEND(disp->rq_handlers, res, link);
request_log(disp, res, LVL(90), "attaching task %p", res->task);
/*
* If there are queries waiting to be processed, give this critter
* one of them.
*/
do_next_request(disp, res);
startrecv(disp);
UNLOCK(&disp->lock);
*resp = res;
return (ISC_R_SUCCESS);
}
void
dns_dispatch_removerequest(dns_dispentry_t **resp,
dns_dispatchevent_t **sockevent)
{
dns_dispatchmgr_t *mgr;
dns_dispatch_t *disp;
dns_dispentry_t *res;
dns_dispatchevent_t *ev;
isc_boolean_t killit;
unsigned int n;
isc_eventlist_t events;
REQUIRE(resp != NULL);
REQUIRE(VALID_REQUEST(*resp));
res = *resp;
*resp = NULL;
disp = res->disp;
REQUIRE(VALID_DISPATCH(disp));
mgr = disp->mgr;
REQUIRE(VALID_DISPATCHMGR(mgr));
if (sockevent != NULL) {
REQUIRE(*sockevent != NULL);
ev = *sockevent;
*sockevent = NULL;
} else {
ev = NULL;
}
LOCK(&disp->lock);
INSIST(disp->requests > 0);
disp->requests--;
INSIST(disp->refcount > 0);
disp->refcount--;
killit = ISC_FALSE;
if (disp->refcount == 0) {
if (disp->recvs > 0)
isc_socket_cancel(disp->socket, NULL,
ISC_SOCKCANCEL_RECV);
disp->shutting_down = 1;
}
ISC_LIST_UNLINK(disp->rq_handlers, res, link);
if (ev == NULL && res->item_out) {
/*
* We've posted our event, but the caller hasn't gotten it
* yet. Take it back.
*/
ISC_LIST_INIT(events);
n = isc_task_unsend(res->task, res, DNS_EVENT_DISPATCH,
NULL, &events);
/*
* We had better have gotten it back.
*/
INSIST(n == 1);
ev = (dns_dispatchevent_t *)ISC_LIST_HEAD(events);
}
if (ev != NULL) {
REQUIRE(res->item_out == ISC_TRUE);
res->item_out = ISC_FALSE;
if (ev->buffer.base != NULL)
free_buffer(disp, ev->buffer.base, ev->buffer.length);
free_event(disp, ev);
}
request_log(disp, res, LVL(90), "detaching from task %p", res->task);
isc_task_detach(&res->task);
res->magic = 0;
isc_mempool_put(disp->mgr->rpool, res);
if (disp->shutting_down == 1)
do_cancel(disp, NULL);
else
startrecv(disp);
killit = destroy_disp_ok(disp);
UNLOCK(&disp->lock);
if (killit) {
destroy_disp(&disp);
killit = destroy_mgr_ok(mgr);
UNLOCK(&mgr->lock);
if (killit)
destroy_mgr(&mgr);
}
}
void
dns_dispatch_freeevent(dns_dispatch_t *disp, dns_dispentry_t *resp,
dns_dispatchevent_t **sockevent)
{
dns_dispatchevent_t *ev;
isc_boolean_t response;
REQUIRE(VALID_DISPATCH(disp));
REQUIRE(sockevent != NULL && *sockevent != NULL);
ev = *sockevent;
*sockevent = NULL;
response = ISC_FALSE;
if (VALID_RESPONSE(resp)) {
response = ISC_TRUE;
} else {
REQUIRE(VALID_RESPONSE(resp) || VALID_REQUEST(resp));
}
LOCK(&disp->lock);
REQUIRE(ev != disp->failsafe_ev);
REQUIRE(resp->item_out == ISC_TRUE);
REQUIRE(ev->result == ISC_R_SUCCESS);
resp->item_out = ISC_FALSE;
if (ev->buffer.base != NULL)
free_buffer(disp, ev->buffer.base, ev->buffer.length);
free_event(disp, ev);
if (response)
do_next_response(disp, resp);
else
do_next_request(disp, resp);
if (disp->shutting_down == 0)
startrecv(disp);
UNLOCK(&disp->lock);
}
static void
do_next_response(dns_dispatch_t *disp, dns_dispentry_t *resp) {
dns_dispatchevent_t *ev;
INSIST(resp->item_out == ISC_FALSE);
ev = ISC_LIST_HEAD(resp->items);
if (ev == NULL) {
if (disp->shutting_down == 1)
do_cancel(disp, NULL);
return;
}
ISC_LIST_UNLINK(disp->rq_events, ev, ev_link);
ISC_EVENT_INIT(ev, sizeof(*ev), 0, NULL, DNS_EVENT_DISPATCH,
resp->action, resp->arg, resp, NULL, NULL);
resp->item_out = ISC_TRUE;
request_log(disp, resp, LVL(90),
"[c] Sent event %p buffer %p len %d to task %p",
ev, ev->buffer.base, ev->buffer.length,
resp->task);
isc_task_send(resp->task, (isc_event_t **)&ev);
}
static void
do_next_request(dns_dispatch_t *disp, dns_dispentry_t *resp) {
dns_dispatchevent_t *ev;
INSIST(resp->item_out == ISC_FALSE);
ev = ISC_LIST_HEAD(disp->rq_events);
if (ev == NULL) {
if (disp->shutting_down == 1)
do_cancel(disp, NULL);
return;
}
ISC_LIST_UNLINK(disp->rq_events, ev, ev_link);
ISC_EVENT_INIT(ev, sizeof(*ev), 0, NULL, DNS_EVENT_DISPATCH,
resp->action, resp->arg, resp, NULL, NULL);
resp->item_out = ISC_TRUE;
request_log(disp, resp, LVL(90),
"[d] Sent event %p buffer %p len %d to task %p",
ev, ev->buffer.base, ev->buffer.length, resp->task);
isc_task_send(resp->task, (isc_event_t **)&ev);
}
static void
do_cancel(dns_dispatch_t *disp, dns_dispentry_t *resp) {
dns_dispatchevent_t *ev;
if (disp->shutdown_out == 1)
return;
/*
* If no target given, find the first request handler. If
* there are packets waiting for any handler, however, don't
* kill them.
*/
if (resp == NULL) {
if (ISC_LIST_EMPTY(disp->rq_events)) {
resp = ISC_LIST_HEAD(disp->rq_handlers);
while (resp != NULL) {
if (resp->item_out == ISC_FALSE)
break;
resp = ISC_LIST_NEXT(resp, link);
}
}
}
/*
* Search for the first response handler without packets outstanding.
*/
if (resp == NULL) {
resp = linear_first(disp);
if (resp == NULL) /* no first item? */
return;
do {
if (resp->item_out == ISC_FALSE)
break;
resp = linear_next(disp, resp);
} while (resp != NULL);
}
/*
* No one to send the cancel event to, so nothing to do.
*/
if (resp == NULL)
return;
/*
* Send the shutdown failsafe event to this resp.
*/
ev = disp->failsafe_ev;
ISC_EVENT_INIT(ev, sizeof (*ev), 0, NULL, DNS_EVENT_DISPATCH,
resp->action, resp->arg, resp, NULL, NULL);
ev->result = disp->shutdown_why;
ev->buffer.base = NULL;
ev->buffer.length = 0;
disp->shutdown_out = 1;
request_log(disp, resp, LVL(10),
"cancel: failsafe event %p -> task %p",
ev, resp->task);
resp->item_out = ISC_TRUE;
isc_task_send(resp->task, (isc_event_t **)&ev);
}
isc_socket_t *
dns_dispatch_getsocket(dns_dispatch_t *disp) {
REQUIRE(VALID_DISPATCH(disp));
return (disp->socket);
}
void
dns_dispatch_cancel(dns_dispatch_t *disp) {
REQUIRE(VALID_DISPATCH(disp));
LOCK(&disp->lock);
if (disp->shutting_down == 1) {
UNLOCK(&disp->lock);
return;
}
disp->shutdown_why = ISC_R_CANCELED;
disp->shutting_down = 1;
do_cancel(disp, NULL);
UNLOCK(&disp->lock);
return;
}
void
dns_dispatch_changeattributes(dns_dispatch_t *disp,
unsigned int attributes, unsigned int mask)
{
LOCK(&disp->lock);
disp->attributes &= ~mask;
disp->attributes |= (attributes & mask);
UNLOCK(&disp->lock);
}
#if 0
void
dns_dispatchmgr_dump(dns_dispatchmgr_t *mgr) {
dns_dispatch_t *disp;
char foo[1024];
disp = ISC_LIST_HEAD(mgr->list);
while (disp != NULL) {
isc_sockaddr_format(&disp->local, foo, sizeof foo);
printf("\tdispatch %p, addr %s\n", disp, foo);
disp = ISC_LIST_NEXT(disp, link);
}
}
#endif