Commit e4d3898697 for openssl.org

commit e4d3898697cf553f45989db145d8a57842082fc6
Author: Matt Caswell <matt@openssl.foundation>
Date:   Wed Aug 12 10:18:58 2026 +0100

    DTLS 1.3 Support blocking reads on listener connections

    A connection created by a DTLS listener has no BIO of its own. The
    listener owns the socket and demultiplexes datagrams into a per
    connection queue, so there is nothing for a read to block in, and every
    read on an empty queue reported WANT_READ however the connection was
    configured. SSL_set_blocking_mode() therefore had no effect on reads.

    Wait in the record layer instead, where a blocking BIO would have. When
    rlayer_dtls_get_urxe_packet() finds the queue empty and the connection is
    in blocking mode, ossl_dtls_conn_wait_for_datagram() waits for one to
    arrive. The state machine above is untouched, so SSL_read(), SSL_peek(),
    SSL_do_handshake() and SSL_accept() all become blocking together, which
    is what an application moving from a plain DTLS object expects.

    The wait loops, because the socket is shared: a wakeup may be for a
    datagram belonging to a different connection, in which case our queue is
    still empty and there is nothing to return. It also handles events after
    each wakeup, since while a thread is in here nothing else services the
    connection, and the retransmission timer has to keep running - the poll
    translation folds the event timeout into the deadline so the wait ends in
    time for it.

    Existing tests drive both ends of a connection from a single thread,
    which cannot work if the server side blocks, so create_dtls_listener() in
    dtlsssllistenertest.c now turns blocking mode off for its callers, with
    create_dtls_listener_unconfigured() for the one test which has to observe
    the default. test_dtls_multithread opts out for the same reason.

    The new test_dtls_blocking_read has the client stay silent for a while
    after its handshake, so the server thread is already parked in
    SSL_read_ex() when the write finally comes. Its second iteration
    handshakes in non-blocking mode and switches to blocking only for the
    read, which pins the read path on its own: in the first iteration the
    handshake is what fails without this change, and the read is never
    reached.

    Assisted-by: Claude Code:claude-opus-5
    Reviewed-by: Ryan Hooper <ryanh@openssl.foundation>
    Reviewed-by: Jakub Zelenka <jakub.zelenka@openssl.foundation>
    Merge-date: Mon Aug 17 08:29:57 2026
    Merged-from: https://github.com/openssl/openssl/pull/32324

diff --git a/ssl/d1_lib.c b/ssl/d1_lib.c
index 92ac3751e4..79e86a97ec 100644
--- a/ssl/d1_lib.c
+++ b/ssl/d1_lib.c
@@ -3131,6 +3131,70 @@ int ossl_dtls_get_blocking_mode(const SSL *s)
     return ossl_dtls_blocking(s);
 }

+/*
+ * Wait until a datagram has been demultiplexed to this connection's receive
+ * queue, for a connection which is in blocking mode.
+ *
+ * This is what makes a blocking read on a listener based connection block. Such
+ * a connection has no BIO of its own to block in: it reads from a queue which
+ * the listener fills, so the wait has to happen here instead.
+ *
+ * A wakeup does not mean the datagram was ours - the listener's socket is
+ * shared, and another connection may be the one with data - so this loops until
+ * something actually lands in our queue. Events are handled after each wait
+ * because nothing else will do it while we are in here, and the retransmission
+ * timer needs servicing if it is what woke us.
+ *
+ * A datagram which is already waiting costs nothing: the wait pumps the
+ * listener's demux while translating the poll, and returns without sleeping if
+ * anything has been queued for us by then.
+ *
+ * Returns 1 if a datagram is now queued for this connection, or 0 if the wait
+ * could not be performed or the listener has failed.
+ */
+int ossl_dtls_conn_wait_for_datagram(SSL *s)
+{
+    SSL_CONNECTION *sc = SSL_CONNECTION_FROM_SSL_ONLY(s);
+    DTLS_LISTENER *dl;
+    int empty;
+
+    if (sc == NULL || sc->d1 == NULL || sc->d1->rx == NULL
+        || sc->d1->listener == NULL)
+        return 0;
+
+    dl = (DTLS_LISTENER *)sc->d1->listener;
+
+    for (;;) {
+        ossl_crypto_mutex_lock(dl->mutex);
+        if (dl->fatal) {
+            ossl_crypto_mutex_unlock(dl->mutex);
+            return 0;
+        }
+        ossl_crypto_mutex_unlock(dl->mutex);
+
+        /*
+         * An infinite deadline here is bounded by the connection's own event
+         * timeout, which the poll translation folds in, so this still wakes in
+         * time to retransmit.
+         */
+        if (!ossl_dtls_block_until_ready(s, SSL_POLL_EVENT_R,
+                ossl_time_infinite()))
+            return 0;
+
+        if (!SSL_handle_events(s))
+            return 0;
+
+        ossl_dgram_demux_pump(sc->d1->rx->demux);
+
+        ossl_crypto_mutex_lock(sc->d1->rx->mutex);
+        empty = ossl_list_urxe_is_empty(&sc->d1->rx->urxe_pending);
+        ossl_crypto_mutex_unlock(sc->d1->rx->mutex);
+
+        if (!empty)
+            return 1;
+    }
+}
+
 void ossl_dtls_listener_enter_blocking_section(SSL *s)
 {
     DTLS_LISTENER *dl;
diff --git a/ssl/record/rec_layer_s3.c b/ssl/record/rec_layer_s3.c
index 69596f8a59..81092cc2a2 100644
--- a/ssl/record/rec_layer_s3.c
+++ b/ssl/record/rec_layer_s3.c
@@ -1176,6 +1176,19 @@ static int rlayer_dtls_get_urxe_packet(void *cbarg, unsigned char **data,
         urxe = ossl_dtls_read_datagram(s->d1->rx);
     }

+    /*
+     * Still nothing. In blocking mode this is where the caller waits: the
+     * connection has no BIO of its own to block in, so returning here would
+     * report SSL_ERROR_WANT_READ instead of blocking. Waiting inside this
+     * callback keeps that out of the record layer and the state machine, which
+     * see only a read which took a while, exactly as a blocking BIO would give
+     * them.
+     */
+    if (urxe == NULL && s->d1->listener != NULL
+        && ossl_dtls_blocking(SSL_CONNECTION_GET_SSL(s))
+        && ossl_dtls_conn_wait_for_datagram(SSL_CONNECTION_GET_SSL(s)))
+        urxe = ossl_dtls_read_datagram(s->d1->rx);
+
     if (urxe == NULL)
         return 0;

diff --git a/ssl/ssl_local.h b/ssl/ssl_local.h
index cc059ca647..524317b4c4 100644
--- a/ssl/ssl_local.h
+++ b/ssl/ssl_local.h
@@ -3102,6 +3102,7 @@ int ossl_dtls_block_until_ready(SSL *ssl, uint64_t events, OSSL_TIME deadline);
 int ossl_dtls_blocking(const SSL *s);
 int ossl_dtls_set_blocking_mode(SSL *s, int blocking);
 int ossl_dtls_get_blocking_mode(const SSL *s);
+int ossl_dtls_conn_wait_for_datagram(SSL *s);
 int ossl_dtls_tick(DTLS_LISTENER *dl);

 /* DTLS Listener internal cookie callbacks */
diff --git a/test/dtls_multithread_test.c b/test/dtls_multithread_test.c
index 8d312a40cc..278c403ad5 100644
--- a/test/dtls_multithread_test.c
+++ b/test/dtls_multithread_test.c
@@ -355,6 +355,16 @@ static int test_dtls_multithread(void)
     if (!TEST_true(create_listener(sctx, &listener, &server_addr, &server_fd)))
         goto err;

+    /*
+     * do_handshake() below drives both ends from this thread, so the server
+     * side must not block: a blocking read would wait for a client which only
+     * this thread can advance. Connections accepted from the listener inherit
+     * this. The worker threads which follow use SSL_poll() and expect
+     * SSL_ERROR_WANT_READ, so they need it too.
+     */
+    if (!TEST_true(SSL_set_blocking_mode(listener, 0)))
+        goto err;
+
     for (i = 0; i < NUM_CLIENTS; i++) {
         if (!TEST_true(create_client(cctx, server_addr, &clients[i], &client_fds[i])))
             goto err;
@@ -503,6 +513,13 @@ static int test_dtls_blocking_accept(void)
     if (!TEST_true(create_listener(sctx, &listener, &server_addr, &server_fd)))
         goto err;

+    /*
+     * This test needs the blocking accept, so ask for it rather than relying
+     * on the default.
+     */
+    if (!TEST_true(SSL_set_blocking_mode(listener, 1)))
+        goto err;
+
     /*
      * Create the client's socket first, but do not connect with it yet. There
      * is no way to cancel a blocking SSL_accept_connection(), so a thread
@@ -562,6 +579,209 @@ err:
     return testresult;
 }

+/*
+ * How long the client waits, in milliseconds, after its handshake completes
+ * before sending anything. The server thread has to still be inside its
+ * blocking read when the write finally happens, or the test proves nothing.
+ *
+ * A machine slow enough to get there late does not make the test fail: the read
+ * then finds the datagram already queued and returns it, which is a pass with
+ * nothing demonstrated. Only the absence of blocking turns it into a failure.
+ */
+#define BLOCKING_READ_QUIET_MS 250
+
+/*
+ * Per-thread state for the blocking read
+ */
+struct read_thread_args {
+    SSL *listener;
+    SSL *conn; /* connection the accept returned */
+    CRYPTO_THREAD *thread;
+    char buf[256];
+    size_t readbytes;
+    int nonblocking_handshake; /* handshake without blocking mode */
+    int result; /* 1 = success, 0 = failure */
+};
+
+/*
+ * Helper: complete the handshake with the connection in non-blocking mode,
+ * driving the listener by hand, so that the blocking read which follows is the
+ * only thing relying on the emulation.
+ */
+static int nonblocking_handshake(struct read_thread_args *ta)
+{
+    int i, ret = -1, err;
+
+    if (!TEST_true(SSL_set_blocking_mode(ta->conn, 0)))
+        return 0;
+
+    for (i = 0; i < 200; i++) {
+        ret = SSL_accept(ta->conn);
+        if (ret == 1)
+            break;
+        err = SSL_get_error(ta->conn, ret);
+        if (err != SSL_ERROR_WANT_READ && err != SSL_ERROR_WANT_WRITE) {
+            TEST_error("SSL_accept failed (err %d)", err);
+            return 0;
+        }
+        /* Nothing else will service the listener while we are in here. */
+        if (!TEST_true(SSL_handle_events(ta->listener)))
+            return 0;
+        OSSL_sleep(10);
+    }
+
+    if (!TEST_int_eq(ret, 1))
+        return 0;
+
+    return TEST_true(SSL_set_blocking_mode(ta->conn, 1));
+}
+
+/*
+ * Thread function: accept a connection, complete its handshake and read from
+ * it, so that the thread never has to handle WANT_READ.
+ */
+static unsigned int blocking_read_thread(void *arg)
+{
+    struct read_thread_args *ta = (struct read_thread_args *)arg;
+
+    ta->conn = SSL_accept_connection(ta->listener, 0);
+    if (!TEST_ptr(ta->conn))
+        return 0;
+
+    /*
+     * A listener-created connection has no BIO of its own to block in - it
+     * reads from a queue the listener demultiplexes into - so without the
+     * emulation these calls return immediately with WANT_READ instead of
+     * waiting.
+     */
+    if (ta->nonblocking_handshake) {
+        if (!nonblocking_handshake(ta))
+            return 0;
+    } else if (!TEST_int_gt(SSL_accept(ta->conn), 0)) {
+        return 0;
+    }
+
+    if (!TEST_true(SSL_read_ex(ta->conn, ta->buf, sizeof(ta->buf) - 1,
+            &ta->readbytes)))
+        return 0;
+
+    ta->result = 1;
+    return 0;
+}
+
+/*
+ * Test that a blocking listener connection waits for a datagram rather than
+ * reporting WANT_READ.
+ *
+ * The client stays silent for BLOCKING_READ_QUIET_MS after its own handshake
+ * completes, so by the time it writes, the server thread is already parked in
+ * SSL_read_ex() with nothing to return. A non-blocking connection reports
+ * WANT_READ immediately, so the absence of the emulation shows up as a failed
+ * assertion rather than as a hang.
+ *
+ * idx 0 blocks in the handshake as well as in the read. idx 1 handshakes in
+ * non-blocking mode and only then switches the connection to blocking, which
+ * leaves the read as the sole assertion the emulation has to satisfy - without
+ * it, idx 0 fails at SSL_accept() and never reaches the read, so on its own it
+ * would not tell us the read path works.
+ *
+ * Only the client is driven from this thread: the accepting thread ticks the
+ * listener itself, which is what lets a blocked connection make progress at
+ * all.
+ */
+static int test_dtls_blocking_read(int idx)
+{
+    SSL_CTX *sctx = NULL, *cctx = NULL;
+    SSL *listener = NULL, *client = NULL;
+    struct read_thread_args read_args;
+    BIO_ADDR *server_addr = NULL;
+    int server_fd = -1, client_fd = -1;
+    int testresult = 0;
+    size_t written;
+    int i, ret = -1, err;
+
+    memset(&read_args, 0, sizeof(read_args));
+
+    if (!TEST_true(create_ssl_ctx_pair(NULL, DTLS_server_method(),
+            DTLS_client_method(), 0, 0, &sctx, &cctx, cert, privkey)))
+        goto err;
+
+    if (!TEST_true(create_listener(sctx, &listener, &server_addr, &server_fd)))
+        goto err;
+
+    /* Blocking is the default, but this test depends on it, so be explicit. */
+    if (!TEST_true(SSL_set_blocking_mode(listener, 1)))
+        goto err;
+
+    /*
+     * As in test_dtls_blocking_accept, create the client's socket before the
+     * thread which will park in the blocking accept, because nothing can
+     * release that thread except a connection arriving. Nothing is sent until
+     * SSL_connect() below, so the accept still waits.
+     */
+    if (!TEST_true(create_client(cctx, server_addr, &client, &client_fd)))
+        goto err;
+
+    read_args.listener = listener;
+    read_args.nonblocking_handshake = idx;
+    read_args.thread = ossl_crypto_thread_native_start(blocking_read_thread,
+        &read_args, 1);
+    if (!TEST_ptr(read_args.thread))
+        goto err;
+
+    /* Drive the client's handshake to completion. */
+    SSL_set_connect_state(client);
+    for (i = 0; i < 200; i++) {
+        ret = SSL_connect(client);
+        if (ret == 1)
+            break;
+        err = SSL_get_error(client, ret);
+        if (err != SSL_ERROR_WANT_READ && err != SSL_ERROR_WANT_WRITE) {
+            TEST_error("SSL_connect failed (err %d)", err);
+            goto err;
+        }
+        OSSL_sleep(10);
+    }
+    if (!TEST_int_eq(ret, 1))
+        goto err;
+
+    /* Let the server thread reach its read and find nothing there. */
+    OSSL_sleep(BLOCKING_READ_QUIET_MS);
+
+    if (!TEST_true(SSL_write_ex(client, CLIENT_TO_SERVER_MSG,
+            strlen(CLIENT_TO_SERVER_MSG), &written)))
+        goto err;
+
+    ossl_crypto_thread_native_join(read_args.thread, NULL);
+    ossl_crypto_thread_native_clean(read_args.thread);
+    read_args.thread = NULL;
+
+    if (!TEST_int_eq(read_args.result, 1))
+        goto err;
+
+    read_args.buf[read_args.readbytes] = '\0';
+    if (!TEST_str_eq(read_args.buf, CLIENT_TO_SERVER_MSG))
+        goto err;
+
+    testresult = 1;
+err:
+    if (read_args.thread != NULL) {
+        ossl_crypto_thread_native_join(read_args.thread, NULL);
+        ossl_crypto_thread_native_clean(read_args.thread);
+    }
+    SSL_free(read_args.conn);
+    SSL_free(client);
+    SSL_free(listener);
+    BIO_ADDR_free(server_addr);
+    if (server_fd >= 0)
+        BIO_closesocket(server_fd);
+    if (client_fd >= 0)
+        BIO_closesocket(client_fd);
+    SSL_CTX_free(sctx);
+    SSL_CTX_free(cctx);
+    return testresult;
+}
+
 int setup_tests(void)
 {
     if (!TEST_ptr(cert = test_get_argument(0))
@@ -570,5 +790,6 @@ int setup_tests(void)

     ADD_TEST(test_dtls_multithread);
     ADD_TEST(test_dtls_blocking_accept);
+    ADD_ALL_TESTS(test_dtls_blocking_read, 2);
     return 1;
 }
diff --git a/test/dtlsssllistenertest.c b/test/dtlsssllistenertest.c
index b93dc268cc..9bc395f359 100644
--- a/test/dtlsssllistenertest.c
+++ b/test/dtlsssllistenertest.c
@@ -115,7 +115,7 @@ static int dtls_read_with_retry(SSL *ssl, void *buf, size_t bufsize,
  * Returns 1 on success, 0 on failure.
  * On success, caller is responsible for cleanup using the returned pointers/fds.
  */
-static int create_dtls_listener(SSL_CTX *sctx, uint64_t listener_flags,
+static int create_dtls_listener_unconfigured(SSL_CTX *sctx, uint64_t listener_flags,
     SSL **listener, BIO_ADDR **server_addr, int *server_fd)
 {
     BIO *listener_bio = NULL;
@@ -183,6 +183,38 @@ err:
     return ret;
 }

+/*
+ * As create_dtls_listener_unconfigured(), but also opts out of blocking mode.
+ *
+ * These tests drive both ends of a handshake from a single thread, so the server
+ * side must not block: a blocking read would wait for a client which only this
+ * thread can advance. A listener is blocking by default, so opt out here rather
+ * than in each test, and note that connections accepted from it inherit this.
+ *
+ * A test which needs to observe the default, or to exercise blocking mode, should
+ * use create_dtls_listener_unconfigured() and configure what it needs.
+ */
+static int create_dtls_listener(SSL_CTX *sctx, uint64_t listener_flags,
+    SSL **listener, BIO_ADDR **server_addr, int *server_fd)
+{
+    if (!create_dtls_listener_unconfigured(sctx, listener_flags, listener,
+            server_addr, server_fd))
+        return 0;
+
+    if (!TEST_true(SSL_set_blocking_mode(*listener, 0))) {
+        SSL_free(*listener);
+        BIO_ADDR_free(*server_addr);
+        if (*server_fd >= 0)
+            BIO_closesocket(*server_fd);
+        *listener = NULL;
+        *server_addr = NULL;
+        *server_fd = -1;
+        return 0;
+    }
+
+    return 1;
+}
+
 /*
  * Helper to create a DTLS client connected to a server address.
  *
@@ -5421,8 +5453,15 @@ static int test_dtls_blocking_mode(void)
             privkey)))
         goto end;

-    /* A socket BIO supplies a poll descriptor, so blocking is available. */
-    if (!TEST_true(create_dtls_listener(sctx,
+    /*
+     * create_dtls_listener() turns blocking mode off on behalf of the single
+     * threaded tests, so it cannot be used here: this test has to see the mode
+     * a listener has when the application has never set it. Hence the
+     * unconfigured variant.
+     *
+     * A socket BIO supplies a poll descriptor, so blocking is available.
+     */
+    if (!TEST_true(create_dtls_listener_unconfigured(sctx,
             SSL_LISTENER_FLAG_REQUIRE_HVR | SSL_LISTENER_FLAG_REQUIRE_HRR
                 | SSL_LISTENER_FLAG_SINGLE_THREAD,
             &listener, &server_addr, &server_fd)))