]> git.ipfire.org Git - thirdparty/bind9.git/commitdiff
dispatch: Clean up connect and recv callbacks
authorEvan Hunt <each@isc.org>
Wed, 4 Aug 2021 20:14:11 +0000 (13:14 -0700)
committerEvan Hunt <each@isc.org>
Sat, 2 Oct 2021 18:39:56 +0000 (11:39 -0700)
- disp_connected() has been split into two functions,
  udp_connected() (which takes 'resp' as an argument) and
  tcp_connected() (which takes 'disp', and calls the connect callbacks
  for all pending resps).

- In dns_dispatch_connect(), if a connection is already open, we need to
  detach the dispentry immediately because we won't be running
  tcp_connected().

- dns_disptach_cancel() also now calls the connect callbacks for pending
  TCP responses, and the response callbacks for open TCP connections
  waiting on read.

- If udp_connected() runs after dns_dispatch_cancel() has been called,
  ensure that the caller's connect callback is run.

- If a UDP connection fails with EADDRINUSE, we try again up to five
  times with a different local port number before giving up.

- If a TCP connection is canceled while still pending connection, the
  connect timeout may still fire. we attach the dispatch before
  connecting to ensure that it won't be detached too soon in this case.

- The dispentry is no longer removed from the pending list when
  deactivating, so that the connect callback can still be run if
  dns_dispatch_removeresponse() was run while the connecting was
  pending.

- Rewrote dns_dispatch_gettcp() to avoid a data race.

- startrecv() and dispatch_getnext() can be called with a NULL resp when
  using TCP.

- Refactored udp_recv() and tcp_recv() and added result logging.

- EOF is now treated the same as CANCELED in response callbacks.

- ISC_R_SHUTTINGDOWN is sent to the reponse callbacks for all resps if
  tcp_recv() is triggered by a netmgr shutdown.  (response callbacks
  are *not* sent by udp_recv() in this case.)

lib/dns/dispatch.c
lib/dns/request.c
lib/dns/resolver.c
lib/dns/tests/Makefile.am
lib/dns/tests/dispatch_test.c
lib/dns/tests/dnstest.c
lib/dns/tests/dnstest.h
lib/dns/tests/resolver_test.c
lib/dns/tests/zonemgr_test.c
lib/ns/tests/nstest.c

index 341ac8630812b0afa634629739262c2499890c0f..888ab1f315f1e7299afd4c0f50b6fae446bc85f9 100644 (file)
@@ -60,7 +60,6 @@ struct dns_dispatchmgr {
        unsigned int state;
        ISC_LIST(dns_dispatch_t) list;
 
-       /* locked by buffer_lock */
        dns_qid_t *qid;
 
        in_port_t *v4ports;    /*%< available ports for IPv4 */
@@ -79,6 +78,7 @@ struct dns_dispentry {
        isc_nmhandle_t *handle; /*%< netmgr handle for UDP connection */
        unsigned int bucket;
        unsigned int timeout;
+       unsigned int retries;
        isc_sockaddr_t local;
        isc_sockaddr_t peer;
        in_port_t port;
@@ -91,6 +91,7 @@ struct dns_dispentry {
        ISC_LINK(dns_dispentry_t) link;
        ISC_LINK(dns_dispentry_t) alink;
        ISC_LINK(dns_dispentry_t) plink;
+       ISC_LINK(dns_dispentry_t) rlink;
 };
 
 /*%
@@ -125,8 +126,8 @@ struct dns_dispatch {
        isc_refcount_t references;
        unsigned int shutdown_out : 1;
 
-       ISC_LIST(dns_dispentry_t) pending;
-       ISC_LIST(dns_dispentry_t) active;
+       dns_displist_t pending;
+       dns_displist_t active;
        unsigned int nsockets;
 
        unsigned int requests;   /*%< how many requests we have */
@@ -301,6 +302,10 @@ setup_socket(dns_dispatch_t *disp, dns_dispentry_t *resp,
        in_port_t *ports = NULL;
        in_port_t port;
 
+       if (resp->retries++ > 5) {
+               return (ISC_R_FAILURE);
+       }
+
        if (isc_sockaddr_pf(&disp->local) == AF_INET) {
                nports = mgr->nv4ports;
                ports = mgr->v4ports;
@@ -336,10 +341,6 @@ deactivate_dispentry(dns_dispatch_t *disp, dns_dispentry_t *resp) {
                ISC_LIST_UNLINK(disp->active, resp, alink);
        }
 
-       if (ISC_LINK_LINKED(resp, plink)) {
-               ISC_LIST_UNLINK(disp->pending, resp, plink);
-       }
-
        if (resp->handle != NULL) {
                INSIST(disp->socktype == isc_socktype_udp);
 
@@ -392,6 +393,13 @@ dispentry_destroy(dns_dispentry_t *resp) {
 
        resp->magic = 0;
 
+       if (ISC_LINK_LINKED(resp, plink)) {
+               ISC_LIST_UNLINK(disp->pending, resp, plink);
+       }
+
+       INSIST(!ISC_LINK_LINKED(resp, alink));
+       INSIST(!ISC_LINK_LINKED(resp, rlink));
+
        if (resp->handle != NULL) {
                isc_nmhandle_detach(&resp->handle);
        }
@@ -433,11 +441,6 @@ dispentry_detach(dns_dispentry_t **respp) {
  *     if event queue is not empty, queue.  else, send.
  *     restart.
  */
-
-/* FIXME: If we read invalid packet, we never receive next that could be valid
- * and we also don't notify the read callback
- */
-
 static void
 udp_recv(isc_nmhandle_t *handle, isc_result_t eresult, isc_region_t *region,
         void *arg) {
@@ -459,38 +462,32 @@ udp_recv(isc_nmhandle_t *handle, isc_result_t eresult, isc_region_t *region,
 
        LOCK(&disp->lock);
 
-       if (isc_log_wouldlog(dns_lctx, LVL(90))) {
-               dispatch_log(disp, LVL(90), "got UDP packet: requests %d",
-                            disp->requests);
+       dispatch_log(disp, LVL(90), "UDP response %p:%s:requests %d", resp,
+                    isc_result_totext(eresult), disp->requests);
+
+       /*
+        * The resp may have been deactivated by shutdown; if
+        * so, we can skip the response callback.
+        */
+       if (ISC_LINK_LINKED(resp, alink)) {
+               response = resp->response;
        }
 
-       if (eresult == ISC_R_CANCELED) {
+       if (eresult != ISC_R_SUCCESS) {
                /*
-                * This dispatcher is shutting down.
+                * This is most likely a network error on a connected
+                * socket, a timeout, or the query has been canceled.
+                * It makes no sense to check the address or parse the
+                * packet, but we can return the error to the caller.
                 */
-               goto sendevent;
-       }
-
-       if (!ISC_LINK_LINKED(resp, alink)) {
-               goto unlock;
+               goto done;
        }
 
-       id = resp->id;
+       INSIST(ISC_LINK_LINKED(resp, alink));
 
        peer = isc_nmhandle_peeraddr(handle);
        isc_netaddr_fromsockaddr(&netaddr, &peer);
 
-       if (eresult != ISC_R_SUCCESS) {
-               /*
-                * This is most likely a network error on a connected
-                * socket, or a timeout on a timer that has not been
-                * reset. It makes no sense to check the address or
-                * parse the packet, but it will help to return the
-                * error to the caller.
-                */
-               goto sendevent;
-       }
-
        /*
         * If this is from a blackholed address, drop it.
         */
@@ -506,18 +503,19 @@ udp_recv(isc_nmhandle_t *handle, isc_result_t eresult, isc_region_t *region,
                        dispatch_log(disp, LVL(10), "blackholed packet from %s",
                                     netaddrstr);
                }
-               goto unlock;
+               goto next;
        }
 
        /*
         * Peek into the buffer to see what we can see.
         */
+       id = resp->id;
        isc_buffer_init(&source, region->base, region->length);
        isc_buffer_add(&source, region->length);
        dres = dns_message_peekheader(&source, &id, &flags);
        if (dres != ISC_R_SUCCESS) {
                dispatch_log(disp, LVL(10), "got garbage packet");
-               goto unlock;
+               goto next;
        }
 
        dispatch_log(disp, LVL(92),
@@ -525,12 +523,10 @@ udp_recv(isc_nmhandle_t *handle, isc_result_t eresult, isc_region_t *region,
                     (((flags & DNS_MESSAGEFLAG_QR) != 0) ? '1' : '0'), id);
 
        /*
-        * Look at flags.  If query, drop it. If response,
-        * look to see where it goes.
+        * Look at the message flags.  If it's a query, ignore it.
         */
        if ((flags & DNS_MESSAGEFLAG_QR) == 0) {
-               /* query */
-               goto unlock;
+               goto next;
        }
 
        /*
@@ -539,19 +535,23 @@ udp_recv(isc_nmhandle_t *handle, isc_result_t eresult, isc_region_t *region,
        if (resp->id != id || !isc_sockaddr_equal(&peer, &resp->peer)) {
                dispatch_log(disp, LVL(90), "response doesn't match");
                inc_stats(disp->mgr, dns_resstatscounter_mismatch);
-               goto unlock;
+               goto next;
        }
 
-sendevent:
        /*
-        * 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.
+        * We have the right resp, so call the caller back.
         */
+       goto done;
 
-       response = resp->response;
+next:
+       /*
+        * This is the wrong response. Don't call the caller back
+        * but keep listening.
+        */
+       response = NULL;
+       dispatch_getnext(disp, resp, resp->timeout);
 
-unlock:
+done:
        UNLOCK(&disp->lock);
 
        if (response != NULL) {
@@ -580,7 +580,7 @@ static void
 tcp_recv(isc_nmhandle_t *handle, isc_result_t eresult, isc_region_t *region,
         void *arg) {
        dns_dispatch_t *disp = (dns_dispatch_t *)arg;
-       dns_dispentry_t *resp = NULL;
+       dns_dispentry_t *resp = NULL, *next = NULL;
        dns_messageid_t id;
        isc_result_t dres;
        unsigned int flags;
@@ -590,6 +590,7 @@ tcp_recv(isc_nmhandle_t *handle, isc_result_t eresult, isc_region_t *region,
        char buf[ISC_SOCKADDR_FORMATSIZE];
        isc_buffer_t source;
        isc_sockaddr_t peer;
+       dns_displist_t resps;
 
        REQUIRE(VALID_DISPATCH(disp));
 
@@ -597,35 +598,49 @@ tcp_recv(isc_nmhandle_t *handle, isc_result_t eresult, isc_region_t *region,
 
        LOCK(&disp->lock);
 
-       dispatch_log(disp, LVL(90), "got TCP packet: requests %d, buffers %d",
-                    disp->requests, disp->tcpbuffers);
+       dispatch_log(disp, LVL(90), "TCP read:%s:requests %d, buffers %d",
+                    isc_result_totext(eresult), disp->requests,
+                    disp->tcpbuffers);
 
        peer = isc_nmhandle_peeraddr(handle);
+       ISC_LIST_INIT(resps);
 
        switch (eresult) {
        case ISC_R_SUCCESS:
                /* got our answer */
                break;
-       case ISC_R_CANCELED:
-               dispatch_log(disp, LVL(90), "shutting down on cancel");
-               goto unlock;
 
+       case ISC_R_SHUTTINGDOWN:
+       case ISC_R_CANCELED:
        case ISC_R_EOF:
-               dispatch_log(disp, LVL(90), "shutting down on EOF");
-               goto unlock;
+               dispatch_log(disp, LVL(90), "shutting down: %s",
+                            isc_result_totext(eresult));
+               /*
+                * If there are any active responses, shut them all down.
+                */
+               for (resp = ISC_LIST_HEAD(disp->active); resp != NULL;
+                    resp = next) {
+                       next = ISC_LIST_NEXT(resp, alink);
+                       dispentry_attach(resp, &(dns_dispentry_t *){ NULL });
+                       ISC_LIST_UNLINK(disp->active, resp, alink);
+                       ISC_LIST_APPEND(resps, resp, rlink);
+               }
+               goto done;
 
        case ISC_R_TIMEDOUT:
                /*
-                * Time out the first active response for which
-                * no event has already been sent.
+                * Time out the oldest response in the active queue,
+                * and move it to the end. (We don't remove it from the
+                * active queue immediately, though, because the callback
+                * might decide to keep waiting and leave it active.)
                 */
                resp = ISC_LIST_HEAD(disp->active);
-               INSIST(resp != NULL);
-
-               ISC_LIST_UNLINK(disp->active, resp, alink);
-               ISC_LIST_APPEND(disp->active, resp, alink);
-
-               goto unlock;
+               if (resp != NULL) {
+                       dispentry_attach(resp, &(dns_dispentry_t *){ NULL });
+                       ISC_LIST_UNLINK(disp->active, resp, alink);
+                       ISC_LIST_APPEND(disp->active, resp, alink);
+               }
+               goto done;
 
        default:
                if (eresult == ISC_R_CONNECTIONRESET) {
@@ -639,11 +654,11 @@ tcp_recv(isc_nmhandle_t *handle, isc_result_t eresult, isc_region_t *region,
                             "shutting down due to TCP "
                             "receive error: %s: %s",
                             buf, isc_result_totext(eresult));
-               goto unlock;
+               goto done;
        }
 
-       dispatch_log(disp, LVL(90), "result %d, length == %d, addr = %p",
-                    eresult, region->length, region->base);
+       dispatch_log(disp, LVL(90), "success, length == %d, addr = %p",
+                    region->length, region->base);
 
        /*
         * Peek into the buffer to see what we can see.
@@ -661,13 +676,8 @@ tcp_recv(isc_nmhandle_t *handle, isc_result_t eresult, isc_region_t *region,
                     (((flags & DNS_MESSAGEFLAG_QR) != 0) ? '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, drop it. If response,
-        * look to see where it goes.
+        * Look at the message flags.  If it's a query, ignore it
+        * and keep reading.
         */
        if ((flags & DNS_MESSAGEFLAG_QR) == 0) {
                /*
@@ -677,24 +687,37 @@ tcp_recv(isc_nmhandle_t *handle, isc_result_t eresult, isc_region_t *region,
        }
 
        /*
-        * We have a response; find the associated dispentry.
+        * We have a valid response; find the associated dispentry object
+        * and call the caller back.
         */
        bucket = dns_hash(qid, &peer, id, disp->localport);
        LOCK(&qid->lock);
        resp = entry_search(qid, &peer, id, disp->localport, bucket);
+       if (resp != NULL) {
+               dispentry_attach(resp, &(dns_dispentry_t *){ NULL });
+       }
        dispatch_log(disp, LVL(90), "search for response in bucket %d: %s",
                     bucket, (resp == NULL ? "not found" : "found"));
        UNLOCK(&qid->lock);
 
 next:
-       /* Restart the reading from the TCP socket */
-       dispatch_getnext(disp, resp, -1);
+       dispatch_getnext(disp, NULL, -1);
 
-unlock:
+done:
        UNLOCK(&disp->lock);
 
        if (resp != NULL) {
+               /* We got a matching response, or timed out */
                resp->response(eresult, region, resp->arg);
+               dispentry_detach(&resp);
+       } else {
+               /* We're being shut down; cancel all outstanding resps */
+               for (resp = ISC_LIST_HEAD(resps); resp != NULL; resp = next) {
+                       next = ISC_LIST_NEXT(resp, rlink);
+                       ISC_LIST_UNLINK(resps, resp, rlink);
+                       resp->response(ISC_R_SHUTTINGDOWN, region, resp->arg);
+                       dispentry_detach(&resp);
+               }
        }
 
        dns_dispatch_detach(&disp);
@@ -1021,11 +1044,8 @@ dns_dispatch_createtcp(dns_dispatchmgr_t *mgr, const isc_sockaddr_t *localaddr,
        ISC_LIST_APPEND(mgr->list, disp, link);
        UNLOCK(&mgr->lock);
 
-       if (isc_log_wouldlog(dns_lctx, 90)) {
-               mgr_log(mgr, LVL(90),
-                       "dns_dispatch_createtcp: created TCP dispatch %p",
-                       disp);
-       }
+       mgr_log(mgr, LVL(90), "dns_dispatch_createtcp: created TCP dispatch %p",
+               disp);
        *dispp = disp;
 
        return (ISC_R_SUCCESS);
@@ -1035,19 +1055,18 @@ isc_result_t
 dns_dispatch_gettcp(dns_dispatchmgr_t *mgr, const isc_sockaddr_t *destaddr,
                    const isc_sockaddr_t *localaddr, bool *connected,
                    dns_dispatch_t **dispp) {
-       dns_dispatch_t *disp = NULL;
+       dns_dispatch_t *disp_connected = NULL;
+       dns_dispatch_t *disp_fallback = NULL;
+       isc_result_t result = ISC_R_NOTFOUND;
 
        REQUIRE(VALID_DISPATCHMGR(mgr));
        REQUIRE(destaddr != NULL);
        REQUIRE(connected != NULL);
        REQUIRE(dispp != NULL && *dispp == NULL);
 
-       /* First pass, look for connected TCP dispatches */
-       *connected = true;
-
        LOCK(&mgr->lock);
-again:
-       for (disp = ISC_LIST_HEAD(mgr->list); disp != NULL && *dispp == NULL;
+
+       for (dns_dispatch_t *disp = ISC_LIST_HEAD(mgr->list); disp != NULL;
             disp = ISC_LIST_NEXT(disp, link))
        {
                isc_sockaddr_t sockname;
@@ -1063,56 +1082,57 @@ again:
                        peeraddr = disp->peer;
                }
 
-               if (*connected == true &&
-                   atomic_load(&disp->state) != DNS_DISPATCHSTATE_CONNECTED)
-               {
-                       goto unlock;
-               }
-
-               /* We don't reuse UDP sockets */
-               if (disp->socktype != isc_socktype_tcp) {
-                       goto unlock;
-               }
-
-               /* Different destination address */
-               if (!isc_sockaddr_equal(destaddr, &peeraddr)) {
-                       goto unlock;
-               }
-
-               /* Different local addr */
-               if (localaddr != NULL) {
-                       /* FIXME: This is weird as sockname == disp-local */
-                       if (!isc_sockaddr_eqaddr(localaddr, &disp->local) ||
-                           !isc_sockaddr_eqaddr(localaddr, &sockname))
-                       {
-                               goto unlock;
-                       }
-               }
-
                /*
                 * The conditions match:
                 * 1. socktype is TCP
                 * 2. destination address is same
                 * 3. local address is either NULL or same
                 */
-               dns_dispatch_attach(disp, dispp);
-       unlock:
+               if (disp->socktype == isc_socktype_tcp &&
+                   isc_sockaddr_equal(destaddr, &peeraddr) &&
+                   (localaddr == NULL ||
+                    isc_sockaddr_eqaddr(localaddr, &sockname)))
+               {
+                       if (atomic_load(&disp->state) ==
+                           DNS_DISPATCHSTATE_CONNECTED) {
+                               /* We found connected dispatch */
+                               disp_connected = disp;
+                               UNLOCK(&disp->lock);
+                               break;
+                       }
+
+                       /* We found "a" dispatch, store it for later */
+                       if (disp_fallback == NULL) {
+                               disp_fallback = disp;
+                       }
+
+                       UNLOCK(&disp->lock);
+                       continue;
+               }
+
                UNLOCK(&disp->lock);
        }
 
-       if (*dispp != NULL) {
-               UNLOCK(&mgr->lock);
-               return (ISC_R_SUCCESS);
-       }
+       if (disp_connected != NULL) {
+               /* We found connected dispatch */
+               INSIST(disp_connected->handle != NULL);
 
-       if (*connected) {
-               /* Second pass, look also for not-yet-connected dispatch */
+               *connected = true;
+               dns_dispatch_attach(disp_connected, dispp);
+
+               result = ISC_R_SUCCESS;
+       } else if (disp_fallback != NULL) {
+               /* We found matching dispatch */
                *connected = false;
-               goto again;
+
+               dns_dispatch_attach(disp_fallback, dispp);
+
+               result = ISC_R_SUCCESS;
        }
 
        UNLOCK(&mgr->lock);
-       return (ISC_R_NOTFOUND);
+
+       return (result);
 }
 
 isc_result_t
@@ -1305,6 +1325,7 @@ dns_dispatch_addresponse(dns_dispatch_t *disp, unsigned int options,
        ISC_LINK_INIT(res, link);
        ISC_LINK_INIT(res, alink);
        ISC_LINK_INIT(res, plink);
+       ISC_LINK_INIT(res, rlink);
 
        if (disp->socktype == isc_socktype_udp) {
                isc_result_t result = setup_socket(disp, res, dest, &localport);
@@ -1390,21 +1411,20 @@ dispatch_getnext(dns_dispatch_t *disp, dns_dispentry_t *resp, int32_t timeout) {
        switch (disp->socktype) {
        case isc_socktype_udp:
                dispentry_attach(resp, &(dns_dispentry_t *){ NULL });
-
                if (timeout > 0) {
                        isc_nmhandle_settimeout(resp->handle, timeout);
                }
                isc_nm_read(resp->handle, udp_recv, resp);
-
                break;
+
        case isc_socktype_tcp:
                dns_dispatch_attach(disp, &(dns_dispatch_t *){ NULL });
-
                if (timeout > 0) {
                        isc_nmhandle_settimeout(disp->handle, timeout);
                }
                isc_nm_read(disp->handle, tcp_recv, disp);
                break;
+
        default:
                INSIST(0);
                ISC_UNREACHABLE();
@@ -1418,14 +1438,12 @@ dns_dispatch_getnext(dns_dispentry_t *resp) {
        REQUIRE(VALID_RESPONSE(resp));
 
        disp = resp->disp;
+
        REQUIRE(VALID_DISPATCH(disp));
 
        LOCK(&disp->lock);
-
        dispatch_getnext(disp, resp, resp->timeout);
-
        UNLOCK(&disp->lock);
-
        return (ISC_R_SUCCESS);
 }
 
@@ -1485,12 +1503,15 @@ startrecv(isc_nmhandle_t *handle, dns_dispatch_t *disp, dns_dispentry_t *resp) {
                break;
 
        case isc_socktype_tcp:
-               REQUIRE(resp != NULL && resp->handle == NULL);
-               REQUIRE(disp != NULL && disp->handle == NULL);
+               REQUIRE(disp != NULL);
+               LOCK(&disp->lock);
+               REQUIRE(disp->handle == NULL);
 
                isc_nmhandle_attach(handle, &disp->handle);
                dns_dispatch_attach(disp, &(dns_dispatch_t *){ NULL });
                isc_nm_read(disp->handle, tcp_recv, disp);
+               UNLOCK(&disp->lock);
+
                break;
 
        default:
@@ -1499,51 +1520,88 @@ startrecv(isc_nmhandle_t *handle, dns_dispatch_t *disp, dns_dispentry_t *resp) {
        }
 }
 
-/*
- * FIXME: Split into tcp_connected() and udp_connected()
- */
-
 static void
-disp_connected(isc_nmhandle_t *handle, isc_result_t eresult, void *arg) {
-       dns_dispentry_t *resp = (dns_dispentry_t *)arg;
-       dns_dispentry_t *pending = NULL, *next = NULL;
-       dns_dispatch_t *disp = resp->disp;
+tcp_connected(isc_nmhandle_t *handle, isc_result_t eresult, void *arg) {
+       dns_dispatch_t *disp = (dns_dispatch_t *)arg;
+       dns_dispentry_t *resp = NULL, *next = NULL;
+       dns_displist_t resps;
+
+       dispatch_log(disp, LVL(90), "TCP connected (%p): %s", disp,
+                    isc_result_totext(eresult));
+
+       ISC_LIST_INIT(resps);
+
+       if (MGR_IS_SHUTTINGDOWN(disp->mgr)) {
+               eresult = ISC_R_SHUTTINGDOWN;
+       }
 
        if (eresult == ISC_R_SUCCESS) {
-               if (resp->canceled) {
-                       dispentry_detach(&resp);
-                       return;
-               }
+               REQUIRE(atomic_compare_exchange_strong(
+                       &disp->state,
+                       &(uint_fast32_t){ DNS_DISPATCHSTATE_CONNECTING },
+                       DNS_DISPATCHSTATE_CONNECTED));
+               startrecv(handle, disp, NULL);
+       }
 
-               if (disp->socktype == isc_socktype_tcp) {
-                       REQUIRE(atomic_compare_exchange_strong(
-                               &disp->state,
-                               &(uint_fast32_t){ DNS_DISPATCHSTATE_CONNECTING },
-                               DNS_DISPATCHSTATE_CONNECTED));
-               }
+       /*
+        * If there are pending responses, call the connect
+        * callbacks for all of them.
+        */
+       LOCK(&disp->lock);
+       for (resp = ISC_LIST_HEAD(disp->pending); resp != NULL; resp = next) {
+               next = ISC_LIST_NEXT(resp, plink);
+               ISC_LIST_UNLINK(disp->pending, resp, plink);
+               ISC_LIST_APPEND(resps, resp, plink);
+       }
+       UNLOCK(&disp->lock);
 
-               startrecv(handle, disp, resp);
+       for (resp = ISC_LIST_HEAD(resps); resp != NULL; resp = next) {
+               next = ISC_LIST_NEXT(resp, plink);
+               ISC_LIST_UNLINK(resps, resp, plink);
+
+               if (resp->connected != NULL) {
+                       resp->connected(eresult, NULL, resp->arg);
+               }
+               dispentry_detach(&resp);
        }
 
+       dns_dispatch_detach(&disp);
+}
+
+static void
+udp_connected(isc_nmhandle_t *handle, isc_result_t eresult, void *arg) {
+       dns_dispentry_t *resp = (dns_dispentry_t *)arg;
+       dns_dispatch_t *disp = resp->disp;
+
+       dispatch_log(disp, LVL(90), "UDP connected (%p): %s", resp,
+                    isc_result_totext(eresult));
+
        if (MGR_IS_SHUTTINGDOWN(disp->mgr)) {
                eresult = ISC_R_SHUTTINGDOWN;
        }
 
-       if (resp->connected != NULL) {
-               resp->connected(eresult, NULL, resp->arg);
+       if (eresult == ISC_R_SUCCESS && resp->canceled) {
+               eresult = ISC_R_CANCELED;
+       } else if (eresult == ISC_R_SUCCESS) {
+               startrecv(handle, disp, resp);
+       } else if (eresult == ISC_R_ADDRINUSE) {
+               in_port_t localport = 0;
+               isc_result_t result;
+
+               /* probably a port collision; try a different one */
+               disp->nsockets--;
+               result = setup_socket(disp, resp, &resp->peer, &localport);
+               if (result == ISC_R_SUCCESS) {
+                       dns_dispatch_connect(resp);
+                       goto detach;
+               }
        }
 
-       for (pending = ISC_LIST_HEAD(disp->pending); pending != NULL;
-            pending = next) {
-               next = ISC_LIST_NEXT(pending, plink);
-               ISC_LIST_UNLINK(disp->pending, pending, plink);
-
-               if (pending->connected != NULL) {
-                       pending->connected(eresult, NULL, pending->arg);
-               }
-               dispentry_detach(&pending);
+       if (resp->connected != NULL) {
+               resp->connected(eresult, NULL, resp->arg);
        }
 
+detach:
        dispentry_detach(&resp);
 }
 
@@ -1556,46 +1614,58 @@ dns_dispatch_connect(dns_dispentry_t *resp) {
 
        disp = resp->disp;
 
-       /* This will be detached in disp_connected() */
+       /* This will be detached once we've connected. */
        dispentry_attach(resp, &(dns_dispentry_t *){ NULL });
 
        switch (disp->socktype) {
        case isc_socktype_tcp:
                /*
-                * Check whether the dispatch was already connecting.
-                * If so, add resp to the pending responses.
+                * Check whether the dispatch is already connecting
+                * or connected.
                 */
                atomic_compare_exchange_strong(&disp->state,
                                               (uint_fast32_t *)&state,
                                               DNS_DISPATCHSTATE_CONNECTING);
-
                switch (state) {
                case DNS_DISPATCHSTATE_NONE:
                        /* First connection, continue with connecting */
-                       INSIST(disp->handle == NULL);
+                       LOCK(&disp->lock);
+                       INSIST(ISC_LIST_EMPTY(disp->pending));
+                       ISC_LIST_APPEND(disp->pending, resp, plink);
+                       UNLOCK(&disp->lock);
+                       dns_dispatch_attach(disp, &(dns_dispatch_t *){ NULL });
                        isc_nm_tcpdnsconnect(disp->mgr->nm, &disp->local,
-                                            &disp->peer, disp_connected, resp,
+                                            &disp->peer, tcp_connected, disp,
                                             resp->timeout, 0);
                        break;
+
                case DNS_DISPATCHSTATE_CONNECTING:
+                       /* Connection pending; add resp to the list */
+                       LOCK(&disp->lock);
                        ISC_LIST_APPEND(disp->pending, resp, plink);
-                       return (ISC_R_SUCCESS);
+                       UNLOCK(&disp->lock);
+                       break;
+
                case DNS_DISPATCHSTATE_CONNECTED:
-                       /* We are already connected, call the connected cb */
+                       /* We are already connected; call the connected cb */
                        if (resp->connected != NULL) {
                                resp->connected(ISC_R_SUCCESS, NULL, resp->arg);
                        }
-                       return (ISC_R_SUCCESS);
+                       dispentry_detach(&resp);
+                       break;
+
                default:
                        INSIST(0);
                        ISC_UNREACHABLE();
                }
 
                break;
+
        case isc_socktype_udp:
                isc_nm_udpconnect(disp->mgr->nm, &resp->local, &resp->peer,
-                                 disp_connected, resp, resp->timeout, 0);
+                                 udp_connected, resp, resp->timeout, 0);
                break;
+
        default:
                return (ISC_R_NOTIMPLEMENTED);
        }
@@ -1621,6 +1691,7 @@ send_done(isc_nmhandle_t *handle, isc_result_t result, void *cbarg) {
 void
 dns_dispatch_resume(dns_dispentry_t *resp, uint16_t timeout) {
        dns_dispatch_t *disp = NULL;
+
        REQUIRE(VALID_RESPONSE(resp));
 
        disp = resp->disp;
@@ -1668,22 +1739,29 @@ dns_dispatch_cancel(dns_dispentry_t *resp) {
 
        resp->canceled = true;
 
-       /* UDP case. */
+       /* Connected UDP */
        if (resp->handle != NULL) {
                isc_nm_cancelread(resp->handle);
                return;
        }
 
-       /*
-        * TCP case. We only want to cancel if this is the last resp
-        * listening on this TCP connection.
-        */
+       /* TCP pending connection. */
        if (ISC_LINK_LINKED(resp, plink)) {
                ISC_LIST_UNLINK(resp->disp->pending, resp, plink);
                if (resp->connected != NULL) {
                        resp->connected(ISC_R_CANCELED, NULL, resp->arg);
+                       dispentry_detach(&resp);
                }
-       } else if (ISC_LINK_LINKED(resp, alink)) {
+               return;
+       }
+
+       /*
+        * Connected TCP, or unconnected UDP.
+        *
+        * If TCP, we don't want to cancel the dispatch
+        * unless this is the last resp waiting.
+        */
+       if (ISC_LINK_LINKED(resp, alink)) {
                ISC_LIST_UNLINK(resp->disp->active, resp, alink);
                if (ISC_LIST_EMPTY(resp->disp->active) &&
                    resp->disp->handle != NULL) {
index b2b569ddf1912454b8fe56af907278b7e19b9079..9214225e04c2e33c59822fb7777214fb2d261bcc 100644 (file)
@@ -590,15 +590,15 @@ again:
                req_send(request);
                req_detach(&rclone);
        } else {
-               result = dns_dispatch_connect(request->dispentry);
-               if (result != ISC_R_SUCCESS) {
-                       goto unlink;
-               }
                request->flags |= DNS_REQUEST_F_CONNECTING;
-
                if (tcp) {
                        request->flags |= DNS_REQUEST_F_TCP;
                }
+
+               result = dns_dispatch_connect(request->dispentry);
+               if (result != ISC_R_SUCCESS) {
+                       goto unlink;
+               }
        }
 
        req_log(ISC_LOG_DEBUG(3), "dns_request_createraw: request %p", request);
@@ -763,14 +763,15 @@ use_tcp:
                req_send(request);
                req_detach(&rclone);
        } else {
-               result = dns_dispatch_connect(request->dispentry);
-               if (result != ISC_R_SUCCESS) {
-                       goto unlink;
-               }
                request->flags |= DNS_REQUEST_F_CONNECTING;
                if (tcp) {
                        request->flags |= DNS_REQUEST_F_TCP;
                }
+
+               result = dns_dispatch_connect(request->dispentry);
+               if (result != ISC_R_SUCCESS) {
+                       goto unlink;
+               }
        }
 
        req_log(ISC_LOG_DEBUG(3), "dns_request_createvia: request %p", request);
@@ -908,7 +909,8 @@ send_if_done(dns_request_t *request, isc_result_t result) {
 void
 request_cancel(dns_request_t *request) {
        if (!DNS_REQUEST_CANCELED(request)) {
-               req_log(ISC_LOG_DEBUG(3), "do_cancel: request %p", request);
+               req_log(ISC_LOG_DEBUG(3), "request_cancel: request %p",
+                       request);
 
                request->flags |= DNS_REQUEST_F_CANCELED;
                request->flags &= ~DNS_REQUEST_F_CONNECTING;
@@ -1006,9 +1008,6 @@ dns_request_destroy(dns_request_t **requestp) {
        req_detach(&request);
 }
 
-/***
- *** Private: request.
- ***/
 static void
 req_connected(isc_result_t eresult, isc_region_t *region, void *arg) {
        dns_request_t *request = (dns_request_t *)arg;
@@ -1018,13 +1017,9 @@ req_connected(isc_result_t eresult, isc_region_t *region, void *arg) {
        req_log(ISC_LOG_DEBUG(3), "req_connected: request %p: %s", request,
                isc_result_totext(eresult));
 
-       if (eresult == ISC_R_CANCELED) {
-               req_detach(&request);
-               return;
-       }
-
        REQUIRE(VALID_REQUEST(request));
-       REQUIRE(DNS_REQUEST_CONNECTING(request));
+       REQUIRE(DNS_REQUEST_CONNECTING(request) ||
+               DNS_REQUEST_CANCELED(request));
 
        LOCK(&request->requestmgr->locks[request->hash]);
        request->flags &= ~DNS_REQUEST_F_CONNECTING;
@@ -1078,13 +1073,14 @@ static void
 req_response(isc_result_t result, isc_region_t *region, void *arg) {
        dns_request_t *request = (dns_request_t *)arg;
 
-       if (result == ISC_R_CANCELED) {
+       req_log(ISC_LOG_DEBUG(3), "req_response: request %p: %s", request,
+               dns_result_totext(result));
+
+       if (result == ISC_R_CANCELED || result == ISC_R_EOF) {
                return;
        }
 
        if (result == ISC_R_TIMEDOUT) {
-               req_log(ISC_LOG_DEBUG(3), "req_timeout: request %p", request);
-
                LOCK(&request->requestmgr->locks[request->hash]);
                if (--request->udpcount != 0) {
                        dns_dispatch_resume(request->dispentry,
@@ -1102,9 +1098,6 @@ req_response(isc_result_t result, isc_region_t *region, void *arg) {
 
        REQUIRE(VALID_REQUEST(request));
 
-       req_log(ISC_LOG_DEBUG(3), "req_response: request %p: %s", request,
-               dns_result_totext(result));
-
        LOCK(&request->requestmgr->locks[request->hash]);
 
        if (result != ISC_R_SUCCESS) {
@@ -1119,11 +1112,14 @@ req_response(isc_result_t result, isc_region_t *region, void *arg) {
        if (result != ISC_R_SUCCESS) {
                isc_buffer_free(&request->answer);
        }
+
 done:
        /*
         * Cleanup.
         */
-       dns_dispatch_removeresponse(&request->dispentry);
+       if (request->dispentry != NULL) {
+               dns_dispatch_removeresponse(&request->dispentry);
+       }
        request_cancel(request);
 
        /*
index 1643b3c0d6f31a919d82a4176590bf917555984c..ba9b9f01b5166774683a9f347924fc73ab6d8e7c 100644 (file)
@@ -1388,8 +1388,8 @@ fctx_cancelquery(resquery_t *query, isc_time_t *finish, bool no_response,
        }
 
        /*
-        * Check for any outstanding dispatch responses.  If they exist,
-        * cancel them and let their callbacks finish the cleanup.
+        * Check for any outstanding dispatch responses and if they
+        * exist, cancel them.
         */
        if (query->dispentry != NULL) {
                dns_dispatch_cancel(query->dispentry);
@@ -1756,6 +1756,7 @@ resquery_senddone(isc_result_t eresult, isc_region_t *region, void *arg) {
 
        switch (eresult) {
        case ISC_R_SUCCESS:
+       case ISC_R_SHUTTINGDOWN:
                goto detach;
 
        case ISC_R_HOSTUNREACH:
@@ -1777,8 +1778,10 @@ resquery_senddone(isc_result_t eresult, isc_region_t *region, void *arg) {
                FCTX_ATTR_CLR(fctx, FCTX_ATTR_ADDRWAIT);
                fctx_try(fctx, true, false);
                break;
+
        case ISC_R_CANCELED:
                break;
+
        default:
                FCTXTRACE3("query canceled in resquery_senddone() "
                           "due to unexpected result; responding",
@@ -2822,6 +2825,13 @@ resquery_connected(isc_result_t eresult, isc_region_t *region, void *arg) {
                }
                goto detach;
 
+       case ISC_R_SHUTTINGDOWN:
+               FCTXTRACE3("shutdown in resquery_connected(): no response",
+                          eresult);
+               fctx_cancelquery(query, NULL, true, false);
+               fctx_done(fctx, eresult, __LINE__);
+               break;
+
        case ISC_R_NETUNREACH:
        case ISC_R_HOSTUNREACH:
        case ISC_R_CONNREFUSED:
@@ -7193,7 +7203,7 @@ resquery_response(isc_result_t eresult, isc_region_t *region, void *arg) {
        fetchctx_t *fctx = NULL;
        respctx_t rctx;
 
-       if (eresult == ISC_R_CANCELED) {
+       if (eresult == ISC_R_CANCELED || eresult == ISC_R_EOF) {
                return;
        }
 
@@ -7697,7 +7707,8 @@ rctx_dispfail(respctx_t *rctx) {
                if (rctx->result == ISC_R_HOSTUNREACH ||
                    rctx->result == ISC_R_NETUNREACH ||
                    rctx->result == ISC_R_CONNREFUSED ||
-                   rctx->result == ISC_R_CANCELED)
+                   rctx->result == ISC_R_CANCELED ||
+                   rctx->result == ISC_R_SHUTTINGDOWN)
                {
                        rctx->broken_server = rctx->result;
                        rctx->broken_type = badns_unreachable;
@@ -10827,8 +10838,7 @@ dns_resolver_disable_algorithm(dns_resolver_t *resolver, const dns_name_t *name,
                                memmove(tmp, algorithms, *algorithms);
                        }
                        tmp[len - 1] |= mask;
-                       /* 'tmp[0]' should contain the length of 'tmp'.
-                        */
+                       /* tmp[0] should contain the length of 'tmp'. */
                        *tmp = len;
                        node->data = tmp;
                        /* Free the older bitfield. */
index bc7250600f3561d9c9cd97204c6a6dc5e0adf543..a90f97c79eaf626f568b740336f0932261a913af 100644 (file)
@@ -3,6 +3,7 @@ include $(top_srcdir)/Makefile.top
 AM_CPPFLAGS +=                         \
        $(LIBISC_CFLAGS)                \
        $(LIBDNS_CFLAGS)                \
+       $(LIBUV_CFLAGS)                 \
        $(KRB5_CFLAGS)                  \
        -DSRCDIR=\"$(abs_srcdir)\"      \
        -DBUILDDIR=\"$(abs_builddir)\"
@@ -10,6 +11,7 @@ AM_CPPFLAGS +=                                \
 LDADD +=                               \
        libdnstest.la                   \
        $(LIBISC_LIBS)                  \
+       $(LIBUV_LIBS)                   \
        $(LIBDNS_LIBS)
 
 check_LTLIBRARIES = libdnstest.la
index b8d083b687990b781caa79bf9ccd1bc93918cde3..8b64c82ce023542d1a4dbf4f5803ef8a974e0c0f 100644 (file)
@@ -25,7 +25,6 @@
 #define UNIT_TESTING
 #include <cmocka.h>
 
-#include <isc/app.h>
 #include <isc/buffer.h>
 #include <isc/managers.h>
 #include <isc/refcount.h>
 
 #include "dnstest.h"
 
+uv_sem_t sem;
+
 /* Timeouts in miliseconds */
-#define T_INIT      120 * 1000
-#define T_IDLE      120 * 1000
-#define T_KEEPALIVE  120 * 1000
-#define T_ADVERTISED 120 * 1000
-#define T_CONNECT    30 * 1000
+#define T_SERVER_INIT      5000
+#define T_SERVER_IDLE      5000
+#define T_SERVER_KEEPALIVE  5000
+#define T_SERVER_ADVERTISED 5000
+
+#define T_CLIENT_INIT      2000
+#define T_CLIENT_IDLE      2000
+#define T_CLIENT_KEEPALIVE  2000
+#define T_CLIENT_ADVERTISED 2000
+
+#define T_CLIENT_CONNECT 1000
 
 dns_dispatchmgr_t *dispatchmgr = NULL;
 dns_dispatchset_t *dset = NULL;
 isc_nm_t *connect_nm = NULL;
-static isc_sockaddr_t server_addr;
-static isc_sockaddr_t connect_addr;
+static isc_sockaddr_t udp_server_addr;
+static isc_sockaddr_t udp_connect_addr;
+static isc_sockaddr_t tcp_server_addr;
+static isc_sockaddr_t tcp_connect_addr;
+
+const struct in6_addr in6addr_blackhole = { { { 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+                                               0, 0, 0, 0, 1 } } };
 
 static int
 setup_ephemeral_port(isc_sockaddr_t *addr, sa_family_t family) {
@@ -96,21 +108,35 @@ setup_ephemeral_port(isc_sockaddr_t *addr, sa_family_t family) {
        return (fd);
 }
 
+static void
+reset_testdata(void);
+
 static int
 _setup(void **state) {
        isc_result_t result;
        uv_os_sock_t sock = -1;
+       int r;
 
        UNUSED(state);
 
        result = dns_test_begin(NULL, true);
        assert_int_equal(result, ISC_R_SUCCESS);
 
-       connect_addr = (isc_sockaddr_t){ .length = 0 };
-       isc_sockaddr_fromin6(&connect_addr, &in6addr_loopback, 0);
+       udp_connect_addr = (isc_sockaddr_t){ .length = 0 };
+       isc_sockaddr_fromin6(&udp_connect_addr, &in6addr_loopback, 0);
+
+       tcp_connect_addr = (isc_sockaddr_t){ .length = 0 };
+       isc_sockaddr_fromin6(&tcp_connect_addr, &in6addr_loopback, 0);
 
-       server_addr = (isc_sockaddr_t){ .length = 0 };
-       sock = setup_ephemeral_port(&server_addr, SOCK_DGRAM);
+       udp_server_addr = (isc_sockaddr_t){ .length = 0 };
+       sock = setup_ephemeral_port(&udp_server_addr, SOCK_DGRAM);
+       if (sock < 0) {
+               return (-1);
+       }
+       close(sock);
+
+       tcp_server_addr = (isc_sockaddr_t){ .length = 0 };
+       sock = setup_ephemeral_port(&tcp_server_addr, SOCK_STREAM);
        if (sock < 0) {
                return (-1);
        }
@@ -120,9 +146,20 @@ _setup(void **state) {
        isc_managers_create(dt_mctx, ncpus, 0, 0, &connect_nm, NULL, NULL,
                            NULL);
 
-       isc_nm_settimeouts(netmgr, T_INIT, T_IDLE, T_KEEPALIVE, T_ADVERTISED);
-       isc_nm_settimeouts(connect_nm, T_INIT, T_IDLE, T_KEEPALIVE,
-                          T_ADVERTISED);
+       isc_nm_settimeouts(netmgr, T_SERVER_INIT, T_SERVER_IDLE,
+                          T_SERVER_KEEPALIVE, T_SERVER_ADVERTISED);
+
+       /*
+        * Use shorter client-side timeouts, to ensure that clients
+        * time out before the server.
+        */
+       isc_nm_settimeouts(connect_nm, T_CLIENT_INIT, T_CLIENT_IDLE,
+                          T_CLIENT_KEEPALIVE, T_CLIENT_ADVERTISED);
+
+       r = uv_sem_init(&sem, 0);
+       assert_int_equal(r, 0);
+
+       reset_testdata();
 
        return (0);
 }
@@ -131,6 +168,8 @@ static int
 _teardown(void **state) {
        UNUSED(state);
 
+       uv_sem_destroy(&sem);
+
        isc_managers_destroy(&connect_nm, NULL, NULL, NULL);
        assert_null(connect_nm);
 
@@ -151,12 +190,12 @@ make_dispatchset(unsigned int ndisps) {
        }
 
        isc_sockaddr_any(&any);
-       result = dns_dispatch_createudp(dispatchmgr, taskmgr, &any, 0, &disp);
+       result = dns_dispatch_createudp(dispatchmgr, &any, &disp);
        if (result != ISC_R_SUCCESS) {
                return (result);
        }
 
-       result = dns_dispatchset_create(dt_mctx, taskmgr, disp, &dset, ndisps);
+       result = dns_dispatchset_create(dt_mctx, disp, &dset, ndisps);
        dns_dispatch_detach(&disp);
 
        return (result);
@@ -231,20 +270,29 @@ dispatchset_get(void **state) {
 }
 
 struct {
-       isc_nmhandle_t *handle;
        atomic_uint_fast32_t responses;
+       atomic_uint_fast32_t result;
 } testdata;
 
 static dns_dispatch_t *dispatch = NULL;
 static dns_dispentry_t *dispentry = NULL;
 static atomic_bool first = ATOMIC_VAR_INIT(true);
 
+static void
+reset_testdata(void) {
+       atomic_init(&testdata.responses, 0);
+       atomic_init(&testdata.result, ISC_R_UNSET);
+}
+
 static void
 server_senddone(isc_nmhandle_t *handle, isc_result_t eresult, void *cbarg) {
        UNUSED(handle);
        UNUSED(eresult);
        UNUSED(cbarg);
 
+       fprintf(stderr, "%s(..., %s, ...)\n", __func__,
+               isc_result_totext(eresult));
+
        return;
 }
 
@@ -281,88 +329,368 @@ nameserver(isc_nmhandle_t *handle, isc_result_t eresult, isc_region_t *region,
        isc_nm_send(handle, &response, server_senddone, NULL);
 }
 
+static isc_result_t
+accept_cb(isc_nmhandle_t *handle, isc_result_t eresult, void *cbarg) {
+       UNUSED(handle);
+       UNUSED(cbarg);
+
+       return (eresult);
+}
+
 static void
-response(isc_task_t *task, isc_event_t *event) {
-       dns_dispatchevent_t *devent = (dns_dispatchevent_t *)event;
-       bool exp_true = true;
+noop_nameserver(isc_nmhandle_t *handle, isc_result_t eresult,
+               isc_region_t *region, void *cbarg) {
+       UNUSED(handle);
+       UNUSED(eresult);
+       UNUSED(region);
+       UNUSED(cbarg);
+}
 
-       UNUSED(task);
+static void
+response_getnext(isc_result_t result, isc_region_t *region, void *arg) {
+       UNUSED(region);
+       UNUSED(arg);
 
        atomic_fetch_add_relaxed(&testdata.responses, 1);
-       if (atomic_compare_exchange_strong(&first, &exp_true, false)) {
-               isc_result_t result = dns_dispatch_getnext(dispentry, &devent);
+
+       if (atomic_compare_exchange_strong(&first, &(bool){ true }, false)) {
+               result = dns_dispatch_getnext(dispentry);
                assert_int_equal(result, ISC_R_SUCCESS);
        } else {
-               dns_dispatch_removeresponse(&dispentry, &devent);
-               isc_nmhandle_detach(&testdata.handle);
-               isc_app_shutdown();
+               uv_sem_post(&sem);
        }
 }
 
 static void
-connected(isc_nmhandle_t *handle, isc_result_t eresult, void *cbarg) {
+response(isc_result_t eresult, isc_region_t *region, void *arg) {
+       UNUSED(region);
+       UNUSED(arg);
+
+       fprintf(stderr, "%s(..., %s, ...)\n", __func__,
+               isc_result_totext(eresult));
+
+       switch (eresult) {
+       case ISC_R_EOF:
+       case ISC_R_CANCELED:
+       case ISC_R_SHUTTINGDOWN:
+               break;
+       default:
+               atomic_fetch_add_relaxed(&testdata.responses, 1);
+               atomic_store_relaxed(&testdata.result, eresult);
+       }
+
+       uv_sem_post(&sem);
+}
+
+static void
+response_timeout(isc_result_t eresult, isc_region_t *region, void *arg) {
+       UNUSED(region);
+       UNUSED(arg);
+
+       fprintf(stderr, "%s(..., %s, ...)\n", __func__,
+               isc_result_totext(eresult));
+
+       atomic_store_relaxed(&testdata.result, eresult);
+
+       uv_sem_post(&sem);
+}
+
+static void
+connected(isc_result_t eresult, isc_region_t *region, void *cbarg) {
        isc_region_t *r = (isc_region_t *)cbarg;
 
        UNUSED(eresult);
+       UNUSED(region);
+
+       fprintf(stderr, "%s(..., %s, ...)\n", __func__,
+               isc_result_totext(eresult));
 
-       isc_nmhandle_attach(handle, &testdata.handle);
        dns_dispatch_send(dispentry, r, -1);
 }
 
 static void
-client_senddone(isc_nmhandle_t *handle, isc_result_t eresult, void *cbarg) {
-       UNUSED(handle);
+client_senddone(isc_result_t eresult, isc_region_t *region, void *cbarg) {
        UNUSED(eresult);
+       UNUSED(region);
        UNUSED(cbarg);
 
+       fprintf(stderr, "%s(..., %s, ...)\n", __func__,
+               isc_result_totext(eresult));
+
        return;
 }
 
 static void
-startit(isc_task_t *task, isc_event_t *event) {
-       UNUSED(task);
+timeout_connected(isc_result_t eresult, isc_region_t *region, void *cbarg) {
+       UNUSED(region);
+       UNUSED(cbarg);
+
+       fprintf(stderr, "%s(..., %s, ...)\n", __func__,
+               isc_result_totext(eresult));
+
+       atomic_store_relaxed(&testdata.result, eresult);
+
+       uv_sem_post(&sem);
+}
+
+static void
+dispatch_timeout_tcp_connect(void **state) {
+       isc_result_t result;
+       isc_region_t region;
+       unsigned char rbuf[12] = { 0 };
+       unsigned char message[12] = { 0 };
+       uint16_t id;
+
+       UNUSED(state);
+
+       tcp_connect_addr = (isc_sockaddr_t){ .length = 0 };
+       isc_sockaddr_fromin6(&tcp_connect_addr, &in6addr_blackhole, 0);
+
+       result = dns_dispatchmgr_create(dt_mctx, connect_nm, &dispatchmgr);
+       assert_int_equal(result, ISC_R_SUCCESS);
+
+       result = dns_dispatch_createtcp(dispatchmgr, &tcp_connect_addr,
+                                       &tcp_server_addr, -1, &dispatch);
+       assert_int_equal(result, ISC_R_SUCCESS);
+
+       region.base = rbuf;
+       region.length = sizeof(rbuf);
+
+       result = dns_dispatch_addresponse(dispatch, 0, T_CLIENT_CONNECT,
+                                         &tcp_server_addr, timeout_connected,
+                                         client_senddone, response, &region,
+                                         &id, &dispentry);
+       assert_int_equal(result, ISC_R_SUCCESS);
+
+       memset(message, 0, sizeof(message));
+       message[0] = (id >> 8) & 0xff;
+       message[1] = id & 0xff;
+
+       region.base = message;
+       region.length = sizeof(message);
+
        dns_dispatch_connect(dispentry);
-       isc_event_free(&event);
+
+       uv_sem_wait(&sem);
+
+       dns_dispatch_removeresponse(&dispentry);
+
+       dns_dispatch_detach(&dispatch);
+       dns_dispatchmgr_detach(&dispatchmgr);
+
+       /* Skip if the IPv6 is not available or not blackholed */
+
+       result = atomic_load_acquire(&testdata.result);
+       if (result == ISC_R_ADDRNOTAVAIL || result == ISC_R_CONNREFUSED) {
+               skip();
+               return;
+       }
+
+       assert_int_equal(result, ISC_R_TIMEDOUT);
 }
 
-/* test dispatch getnext */
 static void
-dispatch_getnext(void **state) {
+dispatch_timeout_tcp_response(void **state __attribute__((unused))) {
        isc_result_t result;
        isc_region_t region;
+       unsigned char rbuf[12] = { 0 };
+       unsigned char message[12] = { 0 };
+       uint16_t id;
        isc_nmsocket_t *sock = NULL;
-       isc_task_t *task = NULL;
-       unsigned char message[12];
-       unsigned char rbuf[12];
+
+       UNUSED(state);
+
+       tcp_connect_addr = (isc_sockaddr_t){ .length = 0 };
+       isc_sockaddr_fromin6(&tcp_connect_addr, &in6addr_loopback, 0);
+
+       result = dns_dispatchmgr_create(dt_mctx, connect_nm, &dispatchmgr);
+       assert_int_equal(result, ISC_R_SUCCESS);
+
+       result = dns_dispatch_createtcp(dispatchmgr, &tcp_connect_addr,
+                                       &tcp_server_addr, -1, &dispatch);
+       assert_int_equal(result, ISC_R_SUCCESS);
+
+       result = isc_nm_listentcpdns(netmgr, &tcp_server_addr, noop_nameserver,
+                                    NULL, accept_cb, NULL, 0, 0, NULL, &sock);
+       assert_int_equal(result, ISC_R_SUCCESS);
+
+       region.base = rbuf;
+       region.length = sizeof(rbuf);
+
+       result = dns_dispatch_addresponse(
+               dispatch, 0, T_CLIENT_CONNECT, &tcp_server_addr, connected,
+               client_senddone, response_timeout, &region, &id, &dispentry);
+       assert_int_equal(result, ISC_R_SUCCESS);
+
+       memset(message, 0, sizeof(message));
+       message[0] = (id >> 8) & 0xff;
+       message[1] = id & 0xff;
+
+       region.base = message;
+       region.length = sizeof(message);
+
+       dns_dispatch_connect(dispentry);
+
+       uv_sem_wait(&sem);
+
+       assert_int_equal(atomic_load_acquire(&testdata.result), ISC_R_TIMEDOUT);
+
+       isc_nm_stoplistening(sock);
+       isc_nmsocket_close(&sock);
+       assert_null(sock);
+
+       dns_dispatch_removeresponse(&dispentry);
+
+       dns_dispatch_detach(&dispatch);
+       dns_dispatchmgr_detach(&dispatchmgr);
+}
+
+static void
+dispatch_tcp_response(void **state __attribute__((unused))) {
+       isc_result_t result;
+       isc_region_t region;
+       unsigned char rbuf[12] = { 0 };
+       unsigned char message[12] = { 0 };
        uint16_t id;
+       isc_nmsocket_t *sock = NULL;
 
        UNUSED(state);
 
-       testdata.handle = NULL;
-       atomic_init(&testdata.responses, 0);
+       tcp_connect_addr = (isc_sockaddr_t){ .length = 0 };
+       isc_sockaddr_fromin6(&tcp_connect_addr, &in6addr_loopback, 0);
+
+       result = dns_dispatchmgr_create(dt_mctx, connect_nm, &dispatchmgr);
+       assert_int_equal(result, ISC_R_SUCCESS);
+
+       result = dns_dispatch_createtcp(dispatchmgr, &tcp_connect_addr,
+                                       &tcp_server_addr, -1, &dispatch);
+       assert_int_equal(result, ISC_R_SUCCESS);
+
+       result = isc_nm_listentcpdns(netmgr, &tcp_server_addr, nameserver, NULL,
+                                    accept_cb, NULL, 0, 0, NULL, &sock);
+       assert_int_equal(result, ISC_R_SUCCESS);
+
+       region.base = rbuf;
+       region.length = sizeof(rbuf);
+
+       result = dns_dispatch_addresponse(
+               dispatch, 0, T_CLIENT_CONNECT, &tcp_server_addr, connected,
+               client_senddone, response, &region, &id, &dispentry);
+       assert_int_equal(result, ISC_R_SUCCESS);
+
+       memset(message, 0, sizeof(message));
+       message[0] = (id >> 8) & 0xff;
+       message[1] = id & 0xff;
+
+       region.base = message;
+       region.length = sizeof(message);
+
+       dns_dispatch_connect(dispentry);
+
+       uv_sem_wait(&sem);
+
+       assert_in_range(atomic_load_acquire(&testdata.responses), 1, 2);
+       assert_int_equal(atomic_load_acquire(&testdata.result), ISC_R_SUCCESS);
+
+       /* Cleanup */
+
+       isc_nm_stoplistening(sock);
+       isc_nmsocket_close(&sock);
+       assert_null(sock);
+
+       dns_dispatch_removeresponse(&dispentry);
+
+       dns_dispatch_detach(&dispatch);
+       dns_dispatchmgr_detach(&dispatchmgr);
+}
+
+static void
+dispatch_timeout_udp_response(void **state __attribute__((unused))) {
+       isc_result_t result;
+       isc_region_t region;
+       unsigned char rbuf[12] = { 0 };
+       unsigned char message[12] = { 0 };
+       uint16_t id;
+       isc_nmsocket_t *sock = NULL;
+
+       UNUSED(state);
+
+       udp_connect_addr = (isc_sockaddr_t){ .length = 0 };
+       isc_sockaddr_fromin6(&udp_connect_addr, &in6addr_loopback, 0);
 
-       result = isc_task_create(taskmgr, 0, &task);
+       result = dns_dispatchmgr_create(dt_mctx, connect_nm, &dispatchmgr);
        assert_int_equal(result, ISC_R_SUCCESS);
 
+       result = dns_dispatch_createudp(dispatchmgr, &tcp_connect_addr,
+                                       &dispatch);
+       assert_int_equal(result, ISC_R_SUCCESS);
+
+       result = isc_nm_listenudp(netmgr, &udp_server_addr, noop_nameserver,
+                                 NULL, 0, &sock);
+       assert_int_equal(result, ISC_R_SUCCESS);
+
+       region.base = rbuf;
+       region.length = sizeof(rbuf);
+
+       result = dns_dispatch_addresponse(
+               dispatch, 0, T_CLIENT_CONNECT, &udp_server_addr, connected,
+               client_senddone, response_timeout, &region, &id, &dispentry);
+       assert_int_equal(result, ISC_R_SUCCESS);
+
+       memset(message, 0, sizeof(message));
+       message[0] = (id >> 8) & 0xff;
+       message[1] = id & 0xff;
+
+       region.base = message;
+       region.length = sizeof(message);
+
+       dns_dispatch_connect(dispentry);
+
+       uv_sem_wait(&sem);
+
+       assert_int_equal(atomic_load_acquire(&testdata.result), ISC_R_TIMEDOUT);
+
+       isc_nm_stoplistening(sock);
+       isc_nmsocket_close(&sock);
+       assert_null(sock);
+
+       dns_dispatch_removeresponse(&dispentry);
+
+       dns_dispatch_detach(&dispatch);
+       dns_dispatchmgr_detach(&dispatchmgr);
+}
+
+/* test dispatch getnext */
+static void
+dispatch_getnext(void **state) {
+       isc_result_t result;
+       isc_region_t region;
+       isc_nmsocket_t *sock = NULL;
+       unsigned char message[12] = { 0 };
+       unsigned char rbuf[12] = { 0 };
+       uint16_t id;
+
+       UNUSED(state);
+
        result = dns_dispatchmgr_create(dt_mctx, connect_nm, &dispatchmgr);
        assert_int_equal(result, ISC_R_SUCCESS);
 
-       result = dns_dispatch_createudp(dispatchmgr, taskmgr, &connect_addr, 0,
+       result = dns_dispatch_createudp(dispatchmgr, &udp_connect_addr,
                                        &dispatch);
        assert_int_equal(result, ISC_R_SUCCESS);
 
        /*
         * Create a local udp nameserver on the loopback.
         */
-       result = isc_nm_listenudp(netmgr, &server_addr, nameserver, NULL, 0,
+       result = isc_nm_listenudp(netmgr, &udp_server_addr, nameserver, NULL, 0,
                                  &sock);
        assert_int_equal(result, ISC_R_SUCCESS);
 
        region.base = rbuf;
        region.length = sizeof(rbuf);
        result = dns_dispatch_addresponse(
-               dispatch, 0, 10000, &server_addr, task, connected,
-               client_senddone, response, NULL, &region, &id, &dispentry);
+               dispatch, 0, T_CLIENT_CONNECT, &udp_server_addr, connected,
+               client_senddone, response_getnext, &region, &id, &dispentry);
        assert_int_equal(result, ISC_R_SUCCESS);
 
        memset(message, 0, sizeof(message));
@@ -372,26 +700,18 @@ dispatch_getnext(void **state) {
        region.base = message;
        region.length = sizeof(message);
 
-       result = isc_app_onrun(dt_mctx, task, startit, NULL);
-       assert_int_equal(result, ISC_R_SUCCESS);
+       dns_dispatch_connect(dispentry);
 
-       result = isc_app_run();
-       assert_int_equal(result, ISC_R_SUCCESS);
+       uv_sem_wait(&sem);
 
        assert_int_equal(atomic_load_acquire(&testdata.responses), 2);
 
+       /* Cleanup */
        isc_nm_stoplistening(sock);
        isc_nmsocket_close(&sock);
        assert_null(sock);
 
-       /*
-        * Shutdown nameserver.
-        */
-       isc_task_detach(&task);
-
-       /*
-        * Shutdown the dispatch.
-        */
+       dns_dispatch_removeresponse(&dispentry);
        dns_dispatch_detach(&dispatch);
        dns_dispatchmgr_detach(&dispatchmgr);
 }
@@ -399,6 +719,14 @@ dispatch_getnext(void **state) {
 int
 main(void) {
        const struct CMUnitTest tests[] = {
+               cmocka_unit_test_setup_teardown(dispatch_timeout_tcp_connect,
+                                               _setup, _teardown),
+               cmocka_unit_test_setup_teardown(dispatch_timeout_tcp_response,
+                                               _setup, _teardown),
+               cmocka_unit_test_setup_teardown(dispatch_tcp_response, _setup,
+                                               _teardown),
+               cmocka_unit_test_setup_teardown(dispatch_timeout_udp_response,
+                                               _setup, _teardown),
                cmocka_unit_test_setup_teardown(dispatchset_create, _setup,
                                                _teardown),
                cmocka_unit_test_setup_teardown(dispatchset_get, _setup,
index fac3f25725954d901088835a7567b50ee135c12b..990bc8ceb9f67e1362c74d2d3a9deee7c51d9f9b 100644 (file)
@@ -69,7 +69,6 @@ isc_nm_t *netmgr = NULL;
 isc_taskmgr_t *taskmgr = NULL;
 isc_task_t *maintask = NULL;
 isc_timermgr_t *timermgr = NULL;
-isc_socketmgr_t *socketmgr = NULL;
 dns_zonemgr_t *zonemgr = NULL;
 bool app_running = false;
 int ncpus;
@@ -100,8 +99,7 @@ cleanup_managers(void) {
 
        isc_managers_destroy(netmgr == NULL ? NULL : &netmgr,
                             taskmgr == NULL ? NULL : &taskmgr,
-                            timermgr == NULL ? NULL : &timermgr,
-                            socketmgr == NULL ? NULL : &socketmgr);
+                            timermgr == NULL ? NULL : &timermgr, NULL);
 
        if (app_running) {
                isc_app_finish();
@@ -114,7 +112,7 @@ create_managers(void) {
        ncpus = isc_os_ncpus();
 
        isc_managers_create(dt_mctx, ncpus, 0, 0, &netmgr, &taskmgr, &timermgr,
-                           &socketmgr);
+                           NULL);
        CHECK(isc_task_create(taskmgr, 0, &maintask));
        return (ISC_R_SUCCESS);
 
@@ -296,8 +294,7 @@ dns_test_setupzonemgr(void) {
        isc_result_t result;
        REQUIRE(zonemgr == NULL);
 
-       result = dns_zonemgr_create(dt_mctx, taskmgr, timermgr, socketmgr, NULL,
-                                   &zonemgr);
+       result = dns_zonemgr_create(dt_mctx, taskmgr, timermgr, NULL, &zonemgr);
        return (result);
 }
 
index a2c32cc1462c0198ed26351278d7aa9ba023a69b..adc88913c215c70b92d63ab160aa016280f9c065 100644 (file)
@@ -45,7 +45,6 @@ extern isc_log_t *lctx;
 extern isc_taskmgr_t *taskmgr;
 extern isc_task_t *maintask;
 extern isc_timermgr_t *timermgr;
-extern isc_socketmgr_t *socketmgr;
 extern isc_nm_t *netmgr;
 extern dns_zonemgr_t *zonemgr;
 extern bool app_running;
index 2c8b6f4e9a400c094ba093360171d41613b5ca7e..866498e35e85b06ed6da9ce418fa85c77a001ac4 100644 (file)
@@ -57,8 +57,7 @@ _setup(void **state) {
        assert_int_equal(result, ISC_R_SUCCESS);
 
        isc_sockaddr_any(&local);
-       result = dns_dispatch_createudp(dispatchmgr, taskmgr, &local, 0,
-                                       &dispatch);
+       result = dns_dispatch_createudp(dispatchmgr, &local, &dispatch);
        assert_int_equal(result, ISC_R_SUCCESS);
 
        return (0);
index ca7559e1817c0da63f8e8a2a2d0ff389a0017b59..694401e871ca1fd2ed00692703a0e4897037b7a3 100644 (file)
@@ -62,7 +62,7 @@ zonemgr_create(void **state) {
 
        UNUSED(state);
 
-       result = dns_zonemgr_create(dt_mctx, taskmgr, timermgr, socketmgr, NULL,
+       result = dns_zonemgr_create(dt_mctx, taskmgr, timermgr, NULL,
                                    &myzonemgr);
        assert_int_equal(result, ISC_R_SUCCESS);
 
@@ -80,7 +80,7 @@ zonemgr_managezone(void **state) {
 
        UNUSED(state);
 
-       result = dns_zonemgr_create(dt_mctx, taskmgr, timermgr, socketmgr, NULL,
+       result = dns_zonemgr_create(dt_mctx, taskmgr, timermgr, NULL,
                                    &myzonemgr);
        assert_int_equal(result, ISC_R_SUCCESS);
 
@@ -121,7 +121,7 @@ zonemgr_createzone(void **state) {
 
        UNUSED(state);
 
-       result = dns_zonemgr_create(dt_mctx, taskmgr, timermgr, socketmgr, NULL,
+       result = dns_zonemgr_create(dt_mctx, taskmgr, timermgr, NULL,
                                    &myzonemgr);
        assert_int_equal(result, ISC_R_SUCCESS);
 
@@ -160,7 +160,7 @@ zonemgr_unreachable(void **state) {
 
        TIME_NOW(&now);
 
-       result = dns_zonemgr_create(dt_mctx, taskmgr, timermgr, socketmgr, NULL,
+       result = dns_zonemgr_create(dt_mctx, taskmgr, timermgr, NULL,
                                    &myzonemgr);
        assert_int_equal(result, ISC_R_SUCCESS);
 
index 8f49e53047aaf1901f8bc6fd7652a1568210182a..1df32364e1da4c9db7e1fd5ce6e9cf05267ff573 100644 (file)
@@ -442,8 +442,7 @@ ns_test_setupzonemgr(void) {
        isc_result_t result;
        REQUIRE(zonemgr == NULL);
 
-       result = dns_zonemgr_create(mctx, taskmgr, timermgr, socketmgr, NULL,
-                                   &zonemgr);
+       result = dns_zonemgr_create(mctx, taskmgr, timermgr, NULL, &zonemgr);
        return (result);
 }