2417. [bug] Connecting UDP sockets for outgoing queries could

unexpectedly fail with an 'address already in use'
			error. [RT #18411]
This commit is contained in:
Tatuya JINMEI 神明達哉 2008-08-15 17:32:55 +00:00
parent c69b5b3eb4
commit 20cd6fd589

View file

@ -15,7 +15,7 @@
* PERFORMANCE OF THIS SOFTWARE.
*/
/* $Id: dispatch.c,v 1.116.18.33 2008/08/05 19:19:58 jinmei Exp $ */
/* $Id: dispatch.c,v 1.116.18.34 2008/08/15 17:32:55 jinmei Exp $ */
/*! \file */
@ -48,6 +48,9 @@
typedef ISC_LIST(dns_dispentry_t) dns_displist_t;
typedef struct dispsocket dispsocket_t;
typedef ISC_LIST(dispsocket_t) dispsocketlist_t;
/* ARC4 Random generator state */
typedef struct arc4ctx {
isc_uint8_t i;
@ -64,7 +67,7 @@ typedef struct dns_qid {
unsigned int qid_increment; /*%< id increment on collision */
isc_mutex_t lock;
dns_displist_t *qid_table; /*%< the table itself */
dns_displist_t *addr_table; /*%< address/port table */
dispsocketlist_t *sock_table; /*%< socket table */
} dns_qid_t;
struct dns_dispatchmgr {
@ -125,15 +128,12 @@ struct dns_dispatchmgr {
#define IS_PRIVATE(d) (((d)->attributes & DNS_DISPATCHATTR_PRIVATE) != 0)
typedef struct dispsocket dispsocket_t;
struct dns_dispentry {
unsigned int magic;
dns_dispatch_t *disp;
dns_messageid_t id;
in_port_t port;
unsigned int bucket;
unsigned int abucket;
isc_sockaddr_t host;
isc_task_t *task;
isc_taskaction_t action;
@ -142,7 +142,6 @@ struct dns_dispentry {
dispsocket_t *dispsocket;
ISC_LIST(dns_dispatchevent_t) items;
ISC_LINK(dns_dispentry_t) link;
ISC_LINK(dns_dispentry_t) alink;
};
/*%
@ -170,9 +169,13 @@ struct dispsocket {
unsigned int magic;
isc_socket_t *socket;
dns_dispatch_t *disp;
isc_sockaddr_t host;
in_port_t localport;
dns_dispentry_t *resp;
isc_task_t *task;
ISC_LINK(dispsocket_t) link;
unsigned int bucket;
ISC_LINK(dispsocket_t) blink;
};
#define INVALID_BUCKET (0xffffdead)
@ -259,9 +262,8 @@ struct dns_dispatch {
/*
* Statics.
*/
static dns_dispentry_t *bucket_search(dns_qid_t *, dns_displist_t *,
isc_sockaddr_t *, dns_messageid_t,
in_port_t, unsigned int, isc_boolean_t);
static dns_dispentry_t *entry_search(dns_qid_t *, isc_sockaddr_t *,
dns_messageid_t, in_port_t, unsigned int);
static isc_boolean_t destroy_disp_ok(dns_dispatch_t *);
static void destroy_disp(isc_task_t *task, isc_event_t *event);
static void destroy_dispsocket(dns_dispatch_t *, dispsocket_t **);
@ -675,6 +677,30 @@ destroy_disp(isc_task_t *task, isc_event_t *event) {
destroy_mgr(&mgr);
}
/*%
* Find a dispsocket for socket address 'dest', and port number 'port'.
* Return NULL if no such entry exists.
*/
static dispsocket_t *
socket_search(dns_qid_t *qid, isc_sockaddr_t *dest, in_port_t port,
unsigned int bucket)
{
dispsocket_t *dispsock;
REQUIRE(bucket < qid->qid_nbuckets);
dispsock = ISC_LIST_HEAD(qid->sock_table[bucket]);
while (dispsock != NULL) {
if (isc_sockaddr_equal(dest, &dispsock->host) &&
dispsock->localport == port)
return (dispsock);
dispsock = ISC_LIST_NEXT(dispsock, blink);
}
return (NULL);
}
/*%
* Make a new socket for a single dispatch with a random port number.
* The caller must hold the disp->lock and qid->lock.
@ -682,8 +708,7 @@ destroy_disp(isc_task_t *task, isc_event_t *event) {
static isc_result_t
get_dispsocket(dns_dispatch_t *disp, isc_sockaddr_t *dest,
isc_socketmgr_t *sockmgr, dns_qid_t *qid,
dispsocket_t **dispsockp, unsigned int *abucketp,
in_port_t *portp)
dispsocket_t **dispsockp, in_port_t *portp)
{
int i;
isc_uint32_t r;
@ -692,7 +717,7 @@ get_dispsocket(dns_dispatch_t *disp, isc_sockaddr_t *dest,
isc_result_t result = ISC_R_FAILURE;
in_port_t port;
isc_sockaddr_t localaddr;
unsigned int abucket = 0;
unsigned int bucket = 0;
dispsocket_t *dispsock;
unsigned int nports;
in_port_t *ports;
@ -725,6 +750,7 @@ get_dispsocket(dns_dispatch_t *disp, isc_sockaddr_t *dest,
dispsock->task = NULL;
isc_task_attach(disp->task[r % disp->ntasks], &dispsock->task);
ISC_LINK_INIT(dispsock, link);
ISC_LINK_INIT(dispsock, blink);
dispsock->magic = DISPSOCK_MAGIC;
}
@ -739,11 +765,9 @@ get_dispsocket(dns_dispatch_t *disp, isc_sockaddr_t *dest,
nports)];
isc_sockaddr_setport(&localaddr, port);
abucket = dns_hash(qid, dest, 0, port);
if (bucket_search(qid, qid->addr_table, dest, 0, port, abucket,
ISC_TRUE) != NULL) {
bucket = dns_hash(qid, dest, 0, port);
if (socket_search(qid, dest, port, bucket) != NULL)
continue;
}
result = open_socket(sockmgr, &localaddr, 0, &sock);
if (result == ISC_R_SUCCESS || result != ISC_R_ADDRINUSE)
@ -752,8 +776,11 @@ get_dispsocket(dns_dispatch_t *disp, isc_sockaddr_t *dest,
if (result == ISC_R_SUCCESS) {
dispsock->socket = sock;
dispsock->host = *dest;
dispsock->localport = port;
dispsock->bucket = bucket;
ISC_LIST_APPEND(qid->sock_table[bucket], dispsock, blink);
*dispsockp = dispsock;
*abucketp = abucket;
*portp = port;
} else {
/*
@ -775,6 +802,7 @@ get_dispsocket(dns_dispatch_t *disp, isc_sockaddr_t *dest,
static void
destroy_dispsocket(dns_dispatch_t *disp, dispsocket_t **dispsockp) {
dispsocket_t *dispsock;
dns_qid_t *qid;
/*
* The dispatch must be locked.
@ -788,6 +816,13 @@ destroy_dispsocket(dns_dispatch_t *disp, dispsocket_t **dispsockp) {
dispsock->magic = 0;
if (dispsock->socket != NULL)
isc_socket_detach(&dispsock->socket);
if (ISC_LINK_LINKED(dispsock, blink)) {
qid = DNS_QID(disp);
LOCK(&qid->lock);
ISC_LIST_UNLINK(qid->sock_table[dispsock->bucket], dispsock,
blink);
UNLOCK(&qid->lock);
}
if (dispsock->task != NULL)
isc_task_detach(&dispsock->task);
isc_mempool_put(disp->mgr->spool, dispsock);
@ -802,6 +837,7 @@ destroy_dispsocket(dns_dispatch_t *disp, dispsocket_t **dispsockp) {
static void
deactivate_dispsocket(dns_dispatch_t *disp, dispsocket_t *dispsock) {
isc_result_t result;
dns_qid_t *qid;
/*
* The dispatch must be locked.
@ -816,6 +852,13 @@ deactivate_dispsocket(dns_dispatch_t *disp, dispsocket_t *dispsock) {
destroy_dispsocket(disp, &dispsock);
else {
result = isc_socket_close(dispsock->socket);
qid = DNS_QID(disp);
LOCK(&qid->lock);
ISC_LIST_UNLINK(qid->sock_table[dispsock->bucket], dispsock,
blink);
UNLOCK(&qid->lock);
if (result == ISC_R_SUCCESS)
ISC_LIST_APPEND(disp->inactivesockets, dispsock, link);
else {
@ -832,23 +875,21 @@ deactivate_dispsocket(dns_dispatch_t *disp, dispsocket_t *dispsock) {
/*
* Find an entry for query ID 'id', socket address 'dest', and port number
* 'port' in 'table'.
* 'port'.
* Return NULL if no such entry exists.
*/
static dns_dispentry_t *
bucket_search(dns_qid_t *qid, dns_displist_t *table, isc_sockaddr_t *dest,
dns_messageid_t id, in_port_t port, unsigned int bucket,
isc_boolean_t ignoreid)
entry_search(dns_qid_t *qid, isc_sockaddr_t *dest, dns_messageid_t id,
in_port_t port, unsigned int bucket)
{
dns_dispentry_t *res;
REQUIRE(bucket < qid->qid_nbuckets);
res = ISC_LIST_HEAD(table[bucket]);
res = ISC_LIST_HEAD(qid->qid_table[bucket]);
while (res != NULL) {
if ((ignoreid || res->id == id) &&
isc_sockaddr_equal(dest, &res->host) &&
if (res->id == id && isc_sockaddr_equal(dest, &res->host) &&
res->port == port) {
return (res);
}
@ -1114,8 +1155,8 @@ udp_recv(isc_event_t *ev_in, dns_dispatch_t *disp, dispsocket_t *dispsock) {
bucket = dns_hash(qid, &ev->address, id, disp->localport);
LOCK(&qid->lock);
qidlocked = ISC_TRUE;
resp = bucket_search(qid, qid->qid_table, &ev->address, id,
disp->localport, bucket, ISC_FALSE);
resp = entry_search(qid, &ev->address, id, disp->localport,
bucket);
dispatch_log(disp, LVL(90),
"search for response in bucket %d: %s",
bucket, (resp == NULL ? "not found" : "found"));
@ -1368,8 +1409,7 @@ tcp_recv(isc_task_t *task, isc_event_t *ev_in) {
*/
bucket = dns_hash(qid, &tcpmsg->address, id, disp->localport);
LOCK(&qid->lock);
resp = bucket_search(qid, qid->qid_table, &tcpmsg->address, id,
disp->localport, bucket, ISC_FALSE);
resp = entry_search(qid, &tcpmsg->address, id, disp->localport, bucket);
dispatch_log(disp, LVL(90),
"search for response in bucket %d: %s",
bucket, (resp == NULL ? "not found" : "found"));
@ -2107,7 +2147,7 @@ dispatch_find(dns_dispatchmgr_t *mgr, isc_sockaddr_t *local,
static isc_result_t
qid_allocate(dns_dispatchmgr_t *mgr, unsigned int buckets,
unsigned int increment, dns_qid_t **qidp,
isc_boolean_t needaddrtable)
isc_boolean_t needsocktable)
{
dns_qid_t *qid;
unsigned int i;
@ -2129,11 +2169,11 @@ qid_allocate(dns_dispatchmgr_t *mgr, unsigned int buckets,
return (ISC_R_NOMEMORY);
}
qid->addr_table = NULL;
if (needaddrtable) {
qid->addr_table = isc_mem_get(mgr->mctx,
buckets * sizeof(dns_displist_t));
if (qid->addr_table == NULL) {
qid->sock_table = NULL;
if (needsocktable) {
qid->sock_table = isc_mem_get(mgr->mctx, buckets *
sizeof(dispsocketlist_t));
if (qid->sock_table == NULL) {
isc_mem_put(mgr->mctx, qid, sizeof(*qid));
isc_mem_put(mgr->mctx, qid->qid_table,
buckets * sizeof(dns_displist_t));
@ -2143,8 +2183,8 @@ qid_allocate(dns_dispatchmgr_t *mgr, unsigned int buckets,
result = isc_mutex_init(&qid->lock);
if (result != ISC_R_SUCCESS) {
if (qid->addr_table != NULL) {
isc_mem_put(mgr->mctx, qid->addr_table,
if (qid->sock_table != NULL) {
isc_mem_put(mgr->mctx, qid->sock_table,
buckets * sizeof(dns_displist_t));
}
isc_mem_put(mgr->mctx, qid->qid_table,
@ -2155,8 +2195,8 @@ qid_allocate(dns_dispatchmgr_t *mgr, unsigned int buckets,
for (i = 0; i < buckets; i++) {
ISC_LIST_INIT(qid->qid_table[i]);
if (qid->addr_table != NULL)
ISC_LIST_INIT(qid->addr_table[i]);
if (qid->sock_table != NULL)
ISC_LIST_INIT(qid->sock_table[i]);
}
qid->qid_nbuckets = buckets;
@ -2179,8 +2219,8 @@ qid_destroy(isc_mem_t *mctx, dns_qid_t **qidp) {
qid->magic = 0;
isc_mem_put(mctx, qid->qid_table,
qid->qid_nbuckets * sizeof(dns_displist_t));
if (qid->addr_table != NULL) {
isc_mem_put(mctx, qid->addr_table,
if (qid->sock_table != NULL) {
isc_mem_put(mctx, qid->sock_table,
qid->qid_nbuckets * sizeof(dns_displist_t));
}
DESTROYLOCK(&qid->lock);
@ -2720,7 +2760,6 @@ dns_dispatch_addresponse2(dns_dispatch_t *disp, isc_sockaddr_t *dest,
{
dns_dispentry_t *res;
unsigned int bucket;
unsigned int abucket;
in_port_t localport = 0;
dns_messageid_t id;
int i;
@ -2794,14 +2833,13 @@ dns_dispatch_addresponse2(dns_dispatch_t *disp, isc_sockaddr_t *dest,
* Get a separate UDP socket with a random port number.
*/
result = get_dispsocket(disp, dest, sockmgr, qid, &dispsocket,
&abucket, &localport);
&localport);
if (result != ISC_R_SUCCESS) {
UNLOCK(&qid->lock);
UNLOCK(&disp->lock);
return (result);
}
} else {
abucket = 0; /* meaningless, but set explicitly */
localport = disp->localport;
}
@ -2812,8 +2850,7 @@ dns_dispatch_addresponse2(dns_dispatch_t *disp, isc_sockaddr_t *dest,
bucket = dns_hash(qid, dest, id, localport);
ok = ISC_FALSE;
for (i = 0; i < 64; i++) {
if (bucket_search(qid, qid->qid_table, dest, id, localport,
bucket, ISC_FALSE) == NULL) {
if (entry_search(qid, dest, id, localport, bucket) == NULL) {
ok = ISC_TRUE;
break;
}
@ -2845,7 +2882,6 @@ dns_dispatch_addresponse2(dns_dispatch_t *disp, isc_sockaddr_t *dest,
res->id = id;
res->port = localport;
res->bucket = bucket;
res->abucket = abucket;
res->host = *dest;
res->action = action;
res->arg = arg;
@ -2855,11 +2891,8 @@ dns_dispatch_addresponse2(dns_dispatch_t *disp, isc_sockaddr_t *dest,
res->item_out = ISC_FALSE;
ISC_LIST_INIT(res->items);
ISC_LINK_INIT(res, link);
ISC_LINK_INIT(res, alink);
res->magic = RESPONSE_MAGIC;
ISC_LIST_APPEND(qid->qid_table[bucket], res, link);
if (dispsocket != NULL)
ISC_LIST_APPEND(qid->addr_table[abucket], res, alink);
UNLOCK(&qid->lock);
request_log(disp, res, LVL(90),
@ -2871,10 +2904,6 @@ dns_dispatch_addresponse2(dns_dispatch_t *disp, isc_sockaddr_t *dest,
if (result != ISC_R_SUCCESS) {
LOCK(&qid->lock);
ISC_LIST_UNLINK(qid->qid_table[bucket], res, link);
if (ISC_LINK_LINKED(res, alink)) {
ISC_LIST_UNLINK(qid->addr_table[abucket], res,
alink);
}
UNLOCK(&qid->lock);
if (dispsocket != NULL)
@ -2989,8 +3018,6 @@ dns_dispatch_removeresponse(dns_dispentry_t **resp,
LOCK(&qid->lock);
ISC_LIST_UNLINK(qid->qid_table[bucket], res, link);
if (ISC_LINK_LINKED(res, alink))
ISC_LIST_UNLINK(qid->addr_table[res->abucket], res, alink);
UNLOCK(&qid->lock);
if (ev == NULL && res->item_out) {