dispatch.c revision 42b48d11ca7b296324d7a8a98cdbf0070b0deb1d
/*
* Copyright (C) 2004 Internet Systems Consortium, Inc. ("ISC")
* Copyright (C) 1999-2003 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 ISC DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
/* $Id: dispatch.c,v 1.117 2004/04/15 01:58:24 marka Exp $ */
#include <config.h>
#include <stdlib.h>
#include <dns/dispatch.h>
#include <dns/portlist.h>
typedef struct dns_qid {
unsigned int magic;
unsigned int qid_nbuckets; /* hash table size */
unsigned int qid_increment; /* id increment on collision */
} dns_qid_t;
struct dns_dispatchmgr {
/* Unlocked. */
unsigned int magic;
/* Locked by "lock". */
unsigned int state;
/* locked by buffer lock */
unsigned int buffers; /* allocated buffers */
unsigned int buffersize; /* size of each buffer */
unsigned int maxbuffers; /* max buffers */
/* Locked internally. */
};
#define MGR_SHUTTINGDOWN 0x00000001U
struct dns_dispentry {
unsigned int magic;
unsigned int bucket;
void *arg;
};
#define INVALID_BUCKET (0xffffdead)
struct dns_dispatch {
/* Unlocked. */
unsigned int magic; /* magic */
unsigned int maxrequests; /* max requests */
/* Locked by mgr->lock. */
/* Locked by "lock". */
unsigned int attributes;
unsigned int refcount; /* number of users */
unsigned int shutting_down : 1,
shutdown_out : 1,
connected : 1,
tcpmsg_valid : 1,
unsigned int requests; /* how many requests we have */
unsigned int tcpbuffers; /* allocated buffers */
};
/*
* Statics.
*/
dns_messageid_t, unsigned int);
static inline void startrecv(dns_dispatch_t *);
unsigned int maxrequests,
unsigned int attributes,
dns_dispatch_t **dispp);
#define LVL(x) ISC_LOG_DEBUG(x)
static void
static void
char msgbuf[2048];
return;
}
static void
static void
char msgbuf[2048];
return;
}
static void
static void
{
char msgbuf[2048];
char peerbuf[256];
return;
if (VALID_RESPONSE(resp)) {
} else {
msgbuf);
}
}
static void
{
NULL, 0);
return;
}
}
/*
* Return an unpredictable message ID.
*/
static dns_messageid_t
}
/*
* Return a hash of the destination and message id.
*/
static isc_uint32_t
unsigned int ret;
return (ret);
}
/*
* Find the first entry in 'qid'. Returns NULL if there are no entries.
*/
static dns_dispentry_t *
unsigned int bucket;
bucket = 0;
return (ret);
bucket++;
}
return (NULL);
}
/*
* Find the next entry after 'resp' in 'qid'. Return NULL if there are
* no more entries.
*/
static dns_dispentry_t *
unsigned int bucket;
return (ret);
bucket++;
return (ret);
bucket++;
}
return (NULL);
}
/*
* The dispatch must be locked.
*/
static isc_boolean_t
{
return (ISC_FALSE);
if (disp->recv_pending != 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
"shutting down; detaching from sock %p, task %p",
if (killmgr)
destroy_mgr(&mgr);
}
/*
* Find an entry for query ID 'id' and socket address 'dest' in 'qid'.
* Return NULL if no such entry exists.
*/
static dns_dispentry_t *
unsigned int bucket)
{
return (res);
}
return (NULL);
}
static void
case isc_sockettype_tcp:
disp->tcpbuffers--;
break;
case isc_sockettype_udp:
break;
default:
INSIST(0);
break;
}
}
static void *
void *temp;
return (temp);
}
static inline void
disp->shutdown_out = 0;
return;
}
}
static inline dns_dispatchevent_t *
return (NULL);
return (ev);
}
/*
* General flow:
*
* If I/O result == CANCELED or error, free the buffer.
*
* If query, free the buffer, 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
unsigned int flags;
unsigned int bucket;
int match;
"got packet: requests %d, buffers %d, recvs %d",
/*
* Unless the receive event was imported from a listening
* interface, in which case the event type is
* DNS_EVENT_IMPORTRECVDONE, receive operation must be pending.
*/
disp->recv_pending = 0;
}
if (disp->shutting_down) {
/*
* This dispatcher is shutting down.
*/
if (killit)
return;
}
"odd socket result in udp_recv(): %s",
return;
}
/*
* If this is from a blackholed address, drop it.
*/
match > 0)
{
char netaddrstr[ISC_NETADDR_FORMATSIZE];
sizeof(netaddrstr));
"blackholed packet from %s",
}
goto restart;
}
/*
* Peek into the buffer to see what we can see.
*/
if (dres != ISC_R_SUCCESS) {
goto restart;
}
"got valid DNS message header, /QR %c, id %u",
/*
* Look at flags. If query, drop it. If response,
* look to see where it goes.
*/
if ((flags & DNS_MESSAGEFLAG_QR) == 0) {
/* query */
goto restart;
} else {
/* response */
"search for response in bucket %d: %s",
goto restart;
}
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.
*/
if (queue_response) {
} else {
"[a] Sent event %p buffer %p len %d to task %p",
}
/*
* Restart recv() to get the next packet.
*/
}
/*
* General flow:
*
* If I/O result == CANCELED, EOF, or error, notify everyone as the
* various queues drain.
*
* If query, restart.
*
* If response:
* Allocate event, fill in details.
* If cannot allocate, restart.
* find target. If not found, restart.
* if event queue is not empty, queue. else, send.
* restart.
*/
static void
unsigned int flags;
unsigned int bucket;
int level;
char buf[ISC_SOCKADDR_FORMATSIZE];
"got TCP packet: requests %d, buffers %d, recvs %d",
disp->recv_pending = 0;
/*
* This dispatcher is shutting down. Force cancelation.
*/
}
case ISC_R_CANCELED:
break;
case ISC_R_EOF:
break;
case ISC_R_CONNECTIONRESET:
goto logit;
default:
"receive error: %s: %s", buf,
break;
}
/*
* 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().
*/
/*
* If the recv() was canceled pass the word on.
*/
if (killit)
return;
}
/*
* Peek into the buffer to see what we can see.
*/
if (dres != ISC_R_SUCCESS) {
goto restart;
}
"got valid DNS message header, /QR %c, id %u",
/*
* Allocate an event to send to the query or response client, and
* allocate a new buffer for our use.
*/
/*
* Look at flags. If query, drop it. If response,
* look to see where it goes.
*/
if ((flags & DNS_MESSAGEFLAG_QR) == 0) {
/*
* Query.
*/
goto restart;
} else {
/*
* Response.
*/
"search for response in bucket %d: %s",
goto restart;
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.
*/
disp->tcpbuffers++;
if (queue_response) {
} else {
"[b] Sent event %p buffer %p len %d to task %p",
}
/*
* Restart recv() to get the next packet.
*/
}
/*
* disp must be locked.
*/
static void
return;
return;
if (disp->recv_pending != 0)
return;
return;
/*
* UDP reads are always maximal.
*/
case isc_sockettype_udp:
return;
if (res != ISC_R_SUCCESS) {
return;
}
break;
case isc_sockettype_tcp:
if (res != ISC_R_SUCCESS) {
return;
}
break;
}
}
/*
* Mgr must be locked when calling this function.
*/
static isc_boolean_t
"destroy_mgr_ok: shuttingdown=%d, listnonempty=%d, "
"epool=%d, rpool=%d, dpool=%d",
if (!MGR_IS_SHUTTINGDOWN(mgr))
return (ISC_FALSE);
return (ISC_FALSE);
return (ISC_FALSE);
return (ISC_FALSE);
return (ISC_FALSE);
return (ISC_TRUE);
}
/*
* Mgr must be unlocked when calling this function.
*/
static void
}
static isc_result_t
{
if (result != ISC_R_SUCCESS)
return (result);
#ifndef ISC_ALLOW_MAPPED
#endif
if (result != ISC_R_SUCCESS) {
return (result);
}
return (ISC_R_SUCCESS);
}
/*
* Publics.
*/
{
return (ISC_R_NOMEMORY);
if (result != ISC_R_SUCCESS)
goto deallocate;
if (result != ISC_R_SUCCESS)
goto kill_lock;
if (result != ISC_R_SUCCESS)
goto kill_buffer_lock;
goto kill_pool_lock;
}
goto kill_epool;
}
goto kill_rpool;
}
mgr->buffersize = 0;
mgr->maxbuffers = 0;
return (ISC_R_SUCCESS);
return (result);
}
void
}
}
void
{
}
}
static isc_result_t
unsigned int buffersize, unsigned int maxbuffers,
{
REQUIRE(maxbuffers > 0);
/*
* 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
*/
if (maxbuffers < 8)
maxbuffers = 8;
return (ISC_R_SUCCESS);
}
return (ISC_R_NOMEMORY);
}
if (result != ISC_R_SUCCESS)
goto cleanup;
return (ISC_R_SUCCESS);
return (ISC_R_NOMEMORY);
}
void
if (killit)
destroy_mgr(&mgr);
}
static isc_boolean_t
return (ISC_FALSE);
if (result != ISC_R_SUCCESS)
return (ISC_FALSE);
return (ISC_TRUE);
return (ISC_FALSE);
}
static isc_boolean_t
return (ISC_TRUE);
/*
* Don't match wildcard ports against newly blacklisted ports.
*/
isc_sockaddr_getport(addr) == 0 &&
return (ISC_FALSE);
/*
* Check if we match the binding <address,port>.
*/
return (ISC_TRUE);
if (isc_sockaddr_getport(addr) == 0)
return (ISC_FALSE);
/*
* Check if we match a bound wildcard port <address,port>.
*/
return (ISC_FALSE);
if (result != ISC_R_SUCCESS)
return (ISC_FALSE);
}
/*
* 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
unsigned int attributes, unsigned int mask,
{
/*
* Make certain that we will not match a private dispatch.
*/
if ((disp->shutting_down == 0)
break;
}
goto out;
}
out:
return (result);
}
static isc_result_t
{
unsigned int i;
return (ISC_R_NOMEMORY);
buckets * sizeof(dns_displist_t));
return (ISC_R_NOMEMORY);
}
buckets * sizeof(dns_displist_t));
return (ISC_R_UNEXPECTED);
}
for (i = 0; i < buckets; i++)
/*
* 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
*/
0, reseed_lfsr, mgr);
0, reseed_lfsr, mgr);
return (ISC_R_SUCCESS);
}
static void
}
/*
* Allocate and set important limits.
*/
static isc_result_t
{
/*
* Set up the dispatcher, mostly. Don't bother setting some of
* the options that are controlled by tcp vs. udp, etc.
*/
return (ISC_R_NOMEMORY);
disp->attributes = 0;
disp->recv_pending = 0;
disp->shutting_down = 0;
disp->shutdown_out = 0;
disp->tcpmsg_valid = 0;
disp->tcpbuffers = 0;
goto deallocate;
}
goto kill_lock;
}
return (ISC_R_SUCCESS);
/*
* error returns
*/
return (res);
}
/*
* MUST be unlocked, and not used by anthing.
*/
static void
{
if (disp->tcpmsg_valid) {
disp->tcpmsg_valid = 0;
}
}
unsigned int maxbuffers, unsigned int maxrequests,
{
/*
* dispatch_allocate() checks mgr for us.
* qid_allocate() checks buckets and increment for us.
*/
if (result != ISC_R_SUCCESS) {
return (result);
}
if (result != ISC_R_SUCCESS)
goto deallocate_dispatch;
if (result != ISC_R_SUCCESS)
goto kill_socket;
sizeof(isc_event_t));
goto kill_task;
/*
* Append it to the dispatcher list.
*/
return (ISC_R_SUCCESS);
/*
* Error returns.
*/
return (result);
}
unsigned int buffersize,
unsigned int maxbuffers, unsigned int maxrequests,
unsigned int attributes, unsigned int mask,
{
REQUIRE(maxbuffers > 0);
if (result != ISC_R_SUCCESS)
return (result);
/*
* First, see if we have a dispatcher that matches.
*/
if (result == ISC_R_SUCCESS) {
(attributes & DNS_DISPATCHATTR_NOLISTEN) != 0)
{
if (disp->recv_pending != 0)
}
return (ISC_R_SUCCESS);
}
/*
* Nope, create one.
*/
if (result != ISC_R_SUCCESS) {
return (result);
}
return (ISC_R_SUCCESS);
}
/*
* mgr should be locked.
*/
static isc_result_t
unsigned int maxrequests,
unsigned int attributes,
{
/*
* dispatch_allocate() checks mgr for us.
*/
if (result != ISC_R_SUCCESS)
return (result);
/*
* This assumes that the IP stack will *not* quickly reallocate
* the same port. If it does continually reallocate the same port
* then we need a mechanism to hold all the blacklisted sockets
* until we find a usable socket.
*/
if (result != ISC_R_SUCCESS)
goto deallocate_dispatch;
goto getsocket;
}
if (result != ISC_R_SUCCESS)
goto kill_socket;
sizeof(isc_event_t));
goto kill_task;
/*
* Append it to the dispatcher list.
*/
return (ISC_R_SUCCESS);
/*
* Error returns.
*/
return (result);
}
void
}
/*
* 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
if (disp->recv_pending > 0)
}
if (killit)
}
{
unsigned int bucket;
int i;
return (ISC_R_SHUTTINGDOWN);
}
return (ISC_R_QUOTA);
}
/*
* Try somewhat hard to find an unique ID.
*/
for (i = 0; i < 64; i++) {
break;
}
id &= 0x0000ffff;
}
if (!ok) {
return (ISC_R_NOMORE);
}
return (ISC_R_NOMEMORY);
}
return (ISC_R_SUCCESS);
}
void
}
void
{
unsigned int bucket;
unsigned int n;
} else {
}
if (disp->recv_pending > 0)
}
/*
* We've posted our event, but the caller hasn't gotten it
* yet. Take it back.
*/
/*
* We had better have gotten it back.
*/
INSIST(n == 1);
}
}
/*
* Free any buffered requests as well
*/
}
else
if (killit)
}
static void
return;
/*
* Search for the first response handler without packets outstanding.
*/
/* no first item? */
return;
}
do {
break;
}
/*
* No one to send the cancel event to, so nothing to do.
*/
return;
/*
* Send the shutdown failsafe event to this resp.
*/
"cancel: failsafe event %p -> task %p",
}
}
return (ISC_R_SUCCESS);
}
return (ISC_R_NOTIMPLEMENTED);
}
void
return;
}
return;
}
void
unsigned int attributes, unsigned int mask)
{
/* XXXMLG
* Should check for valid attributes here!
*/
if ((mask & DNS_DISPATCHATTR_NOLISTEN) != 0) {
(attributes & DNS_DISPATCHATTR_NOLISTEN) == 0) {
== 0 &&
(attributes & DNS_DISPATCHATTR_NOLISTEN) != 0) {
if (disp->recv_pending != 0)
}
}
}
void
void *buf;
newsevent = (isc_socketevent_t *)
disp, sizeof(isc_socketevent_t));
return;
return;
}
}
#if 0
void
char foo[1024];
}
}
#endif