]> git.ipfire.org Git - thirdparty/bind9.git/commitdiff
2417. [bug] Connecting UDP sockets for outgoing queries could
authorTatuya JINMEI 神明達哉 <jinmei@isc.org>
Fri, 15 Aug 2008 17:32:55 +0000 (17:32 +0000)
committerTatuya JINMEI 神明達哉 <jinmei@isc.org>
Fri, 15 Aug 2008 17:32:55 +0000 (17:32 +0000)
unexpectedly fail with an 'address already in use'
error. [RT #18411]

lib/dns/dispatch.c

index 802f545d8b35c448f5bdc970d1d37c63c070d229..c1d1cce550e338d8d0e5c38e2dfdacbcd7145556 100644 (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) {