diff --git a/CHANGES b/CHANGES index b22b621027..568a42e0ed 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,7 @@ +2417. [bug] Connecting UDP sockets for outgoing queries could + unexpectedly fail with an 'address already in use' + error. [RT #18411] + 2416. [func] Log file descriptors that cause exceeding the internal maximum. [RT #18460] diff --git a/lib/dns/dispatch.c b/lib/dns/dispatch.c index 1767c5523d..9166a924a4 100644 --- a/lib/dns/dispatch.c +++ b/lib/dns/dispatch.c @@ -15,7 +15,7 @@ * PERFORMANCE OF THIS SOFTWARE. */ -/* $Id: dispatch.c,v 1.150 2008/08/05 19:18:02 jinmei Exp $ */ +/* $Id: dispatch.c,v 1.151 2008/08/15 17:29:52 jinmei Exp $ */ /*! \file */ @@ -49,6 +49,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; @@ -65,7 +68,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 { @@ -127,15 +130,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; @@ -144,7 +144,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; }; /*% @@ -172,9 +171,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) @@ -261,9 +264,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 **); @@ -677,6 +679,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. @@ -684,8 +710,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; @@ -694,7 +719,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; @@ -727,6 +752,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; } @@ -741,11 +767,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) @@ -754,8 +778,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 { /* @@ -777,6 +804,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. @@ -790,6 +818,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); @@ -804,6 +839,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. @@ -818,6 +854,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 { @@ -834,23 +877,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); } @@ -1116,8 +1157,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")); @@ -1374,8 +1415,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")); @@ -2127,7 +2167,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; @@ -2149,11 +2189,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)); @@ -2163,8 +2203,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, @@ -2175,8 +2215,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; @@ -2199,8 +2239,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); @@ -2739,7 +2779,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; @@ -2813,14 +2852,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; } @@ -2831,8 +2869,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; } @@ -2864,7 +2901,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; @@ -2874,11 +2910,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), @@ -2890,10 +2923,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) @@ -3008,8 +3037,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) {