Commit 4af5800f0c for openssl.org
commit 4af5800f0ce6c566bb64ae55b02a5a4dd2a6b8fe
Author: Matt Caswell <matt@openssl.foundation>
Date: Mon Oct 19 13:21:04 2026 +0100
DTLS listener: do not drive idle pending connections every tick
Every listener tick called SSL_accept() on every pending connection,
whether or not anything had arrived for it, and each such call pumped
the demux with another recvmmsg(). SSL_poll() ticks the listener once
per item, so 256 one-byte datagrams from distinct source ports, which
fill the pending table for 30 s, made every SSL_poll() over N
connections cost (N + 1) * 256 futile SSL_accept() calls.
Record whether the last drive left a pending connection waiting for
its peer, and skip it until a datagram is queued for it or its
retransmission timer expires.
Driving one connection pumps the demux, which can queue a datagram for
a connection the tick skipped or has already driven while emptying the
socket the caller then waits on. Drive again when that happens, up to
DTLS_LISTENER_MAX_DRIVE_PASSES times, and do not let a wait on the
listener or on its connections sleep while such input remains.
Discovered by Anthropic's OSS Scanner (finding ANT-2026-EFVK6J4Z).
Assisted-by: Claude:claude-opus-5-5
Reviewed-by: Tomas Mraz <tomas@openssl.foundation>
Reviewed-by: Saša NedvÄ›dický <sashan@openssl.org>
Reviewed-by: Ryan Hooper <ryanh@openssl.foundation>
Reviewed-by: Mounir Idrassi <mounir.idrassi@idrix.fr>
Merge-date: Thu Oct 22 14:38:07 2026
Merged-from: https://github.com/openssl/openssl/pull/33265
diff --git a/ssl/d1_lib.c b/ssl/d1_lib.c
index 09c961742e..8f0cf1c6eb 100644
--- a/ssl/d1_lib.c
+++ b/ssl/d1_lib.c
@@ -1439,6 +1439,7 @@ static void dtls_listener_packet_handler(DGRAM_URXE *urxe, void *arg)
DTLS_LISTENER *dl = arg;
SSL *conn_ssl = NULL;
SSL_CONNECTION *sc = NULL;
+ int pending = 0;
ossl_crypto_mutex_lock(dl->mutex);
@@ -1447,8 +1448,10 @@ static void dtls_listener_packet_handler(DGRAM_URXE *urxe, void *arg)
conn_ssl = ossl_dgram_conn_lookup_find(dl->established_conns, urxe);
/* Check pending connections */
- if (conn_ssl == NULL)
+ if (conn_ssl == NULL) {
conn_ssl = ossl_dgram_conn_lookup_find(dl->pending_conns, urxe);
+ pending = 1;
+ }
/* Create new pending connection if needed */
if (conn_ssl == NULL) {
@@ -1531,6 +1534,13 @@ static void dtls_listener_packet_handler(DGRAM_URXE *urxe, void *arg)
/* Inject packet into connection's URXE queue */
ossl_dtls_rx_inject_urxe(sc->listener_rx, urxe);
+ /*
+ * A connection being driven is rechecked by its driver in phase 3 of
+ * dtls_listener_drive_pass(). Anything else needs another pass.
+ */
+ if (pending && !sc->listener_being_driven)
+ dl->pending_input = 1;
+
/* Signal notifier if needed */
dtls_listener_signal_notifier(dl);
@@ -2204,6 +2214,16 @@ static void collect_pending_cb(SSL *ssl, const BIO_ADDR *peer, void *arg)
}
}
+ if (sc->listener_idle && !dtls1_is_timer_expired(sc)) {
+ int empty;
+
+ ossl_crypto_mutex_lock(sc->listener_rx->mutex);
+ empty = ossl_list_urxe_is_empty(&sc->listener_rx->urxe_pending);
+ ossl_crypto_mutex_unlock(sc->listener_rx->mutex);
+ if (empty)
+ return;
+ }
+
/*
* Up-ref and add to list first, then mark as being driven.
* The up-ref ensures the connection stays valid while we drive it
@@ -2254,6 +2274,8 @@ static void drive_single_connection(SSL *ssl, DTLS_LISTENER *dl,
sc->listener_force_nonblocking = 1;
ret = SSL_accept(ssl);
sc->listener_force_nonblocking = 0;
+ ssl_err = SSL_get_error(ssl, ret);
+ sc->listener_idle = ssl_err == SSL_ERROR_WANT_READ;
/*
* Always clear the stateless flag after SSL_accept() completes.
@@ -2272,9 +2294,6 @@ static void drive_single_connection(SSL *ssl, DTLS_LISTENER *dl,
if (dtls_listener_conn_needs_retry(ssl))
return;
- /* Check SSL error */
- ssl_err = SSL_get_error(ssl, ret);
-
if (ssl_err == SSL_ERROR_WANT_READ || ssl_err == SSL_ERROR_WANT_WRITE) {
/* Handshake in progress, needs more data - keep pending */
return;
@@ -2288,7 +2307,7 @@ static void drive_single_connection(SSL *ssl, DTLS_LISTENER *dl,
}
/*
- * dtls_listener_drive_pending - drive handshakes for all pending connections.
+ * dtls_listener_drive_pass - drive handshakes for all pending connections.
*
* Uses a three-phase approach to minimize lock contention:
* Phase 1: LOCK - collect connections to drive, mark as being_driven, up-ref
@@ -2298,12 +2317,15 @@ static void drive_single_connection(SSL *ssl, DTLS_LISTENER *dl,
* This approach allows SSL_accept() to call demux_pump() without deadlock,
* since the mutex is not held during phase 2.
*
+ * *again is set if a pending connection may have input left undriven.
+ *
* Returns:
* 1 At least one connection was moved to incoming_connections
* 0 No connections completed (all still pending or failed)
* -1 Fatal error
*/
-static int dtls_listener_drive_pending(DTLS_LISTENER *dl)
+static int dtls_listener_drive_pass(DTLS_LISTENER *dl, int last_pass,
+ int *again)
{
DRIVE_PENDING_CTX ctx;
SSL *ssl;
@@ -2330,6 +2352,7 @@ static int dtls_listener_drive_pending(DTLS_LISTENER *dl)
* and up-ref them so they stay valid after we release the mutex.
*/
ossl_crypto_mutex_lock(dl->mutex);
+ dl->pending_input = 0;
ossl_dgram_conn_lookup_foreach(dl->pending_conns, collect_pending_cb, &ctx);
ossl_crypto_mutex_unlock(dl->mutex);
@@ -2358,8 +2381,15 @@ static int dtls_listener_drive_pending(DTLS_LISTENER *dl)
ssl = sk_SSL_value(ctx.to_drive, i);
sc = SSL_CONNECTION_FROM_SSL_ONLY(ssl);
- if (sc != NULL)
+ if (sc != NULL) {
sc->listener_being_driven = 0;
+ if (sc->listener_idle && sc->listener_rx != NULL) {
+ ossl_crypto_mutex_lock(sc->listener_rx->mutex);
+ if (!ossl_list_urxe_is_empty(&sc->listener_rx->urxe_pending))
+ dl->pending_input = 1;
+ ossl_crypto_mutex_unlock(sc->listener_rx->mutex);
+ }
+ }
SSL_free(ssl); /* Release reference from phase 1 */
}
@@ -2415,6 +2445,11 @@ static int dtls_listener_drive_pending(DTLS_LISTENER *dl)
dtls_listener_connection_free(ssl);
}
+ *again = dl->pending_input;
+ /* Out of passes: make sure a blocked waiter ticks again. */
+ if (*again && last_pass)
+ dtls_listener_signal_notifier(dl);
+
ossl_crypto_mutex_unlock(dl->mutex);
sk_SSL_free(ctx.to_drive);
@@ -2424,6 +2459,28 @@ static int dtls_listener_drive_pending(DTLS_LISTENER *dl)
return result;
}
+/*
+ * Driving a connection pumps the demux, which can queue input for a pending
+ * connection that the pass did not collect or has already driven. Pass again
+ * while that happens, up to a bound.
+ */
+static int dtls_listener_drive_pending(DTLS_LISTENER *dl)
+{
+ int pass, ret, again = 0, result = 0;
+
+ for (pass = 0; pass < DTLS_LISTENER_MAX_DRIVE_PASSES; pass++) {
+ ret = dtls_listener_drive_pass(dl,
+ pass == DTLS_LISTENER_MAX_DRIVE_PASSES - 1, &again);
+ if (ret < 0)
+ return -1;
+ if (ret > 0)
+ result = 1;
+ if (!again)
+ break;
+ }
+ return result;
+}
+
/*
* ossl_dtls_tick - drive one iteration of the DTLS listener I/O loop.
*
@@ -2467,6 +2524,26 @@ int ossl_dtls_tick(DTLS_LISTENER *dl)
return dtls_listener_drive_pending(dl);
}
+/*
+ * Does a pending connection have input which the last tick left undriven?
+ * A waiter must then tick again rather than sleep. Not on a fatal listener:
+ * its ticks fail before driving, so pending_input may never be cleared.
+ */
+int ossl_dtls_listener_has_pending_input(SSL *s)
+{
+ DTLS_LISTENER *dl;
+ int ret;
+
+ if (!IS_DTLS_LISTENER(s))
+ return 0;
+
+ dl = (DTLS_LISTENER *)s;
+ ossl_crypto_mutex_lock(dl->mutex);
+ ret = dl->pending_input && !dl->fatal;
+ ossl_crypto_mutex_unlock(dl->mutex);
+ return ret;
+}
+
SSL *ossl_dtls_accept_connection(SSL *ssl, uint64_t flags)
{
DTLS_LISTENER *dl;
diff --git a/ssl/rio/poll_immediate.c b/ssl/rio/poll_immediate.c
index 4ba67a438d..7b50fe5a91 100644
--- a/ssl/rio/poll_immediate.c
+++ b/ssl/rio/poll_immediate.c
@@ -232,6 +232,15 @@ static int poll_translate_ssl_dtls_listener(SSL *ssl,
return 0;
}
+ /*
+ * Input already taken off the socket for a pending connection is not
+ * signalled by the socket. Tick again instead of sleeping.
+ */
+ if (ossl_dtls_listener_has_pending_input(ssl)) {
+ *abort_blocking = 1;
+ return 1;
+ }
+
/*
* Watch the socket for readability whatever was asked for. Every event a
* listener reports is ultimately driven by a datagram arriving, both an
@@ -268,7 +277,7 @@ static int poll_translate_ssl_dtls_listener(SSL *ssl,
&revents))
return 0;
- if (revents != 0) {
+ if (revents != 0 || ossl_dtls_listener_has_pending_input(ssl)) {
*abort_blocking = 1;
return 1;
}
@@ -310,8 +319,14 @@ static int poll_translate_ssl_dtls_conn(SSL *ssl,
/*
* Now check the URXE buffer. If data has been demuxed into
* this connection's receive queue, abort blocking immediately -
- * there is no need to wait on the socket FD.
+ * there is no need to wait on the socket FD. Likewise if the
+ * tick left input undriven on a pending connection: the socket
+ * will not signal it, so tick again instead of sleeping.
*/
+ if (ossl_dtls_listener_has_pending_input(sc->d1->listener)) {
+ *abort_blocking = 1;
+ return 1;
+ }
if (sc->d1->rx != NULL) {
ossl_crypto_mutex_lock(sc->d1->rx->mutex);
has_pending = !ossl_list_urxe_is_empty(&sc->d1->rx->urxe_pending);
@@ -343,17 +358,19 @@ static int poll_translate_ssl_dtls_conn(SSL *ssl,
* Only after the above call returns is it guaranteed that
* any readiness events will cause the notifier to become
* readable. Therefore, it is possible data was demuxed to
- * our URXE queue after our initial check above but before
- * we entered the blocking section. Re-check now.
+ * our URXE queue, or input left on a pending connection,
+ * after our initial check above but before we entered
+ * the blocking section. Re-check now.
*/
- if (sc->d1->rx != NULL) {
+ has_pending = ossl_dtls_listener_has_pending_input(sc->d1->listener);
+ if (!has_pending && sc->d1->rx != NULL) {
ossl_crypto_mutex_lock(sc->d1->rx->mutex);
has_pending = !ossl_list_urxe_is_empty(&sc->d1->rx->urxe_pending);
ossl_crypto_mutex_unlock(sc->d1->rx->mutex);
- if (has_pending) {
- *abort_blocking = 1;
- return 1;
- }
+ }
+ if (has_pending) {
+ *abort_blocking = 1;
+ return 1;
}
}
}
diff --git a/ssl/ssl_local.h b/ssl/ssl_local.h
index 283065640c..274b3cd521 100644
--- a/ssl/ssl_local.h
+++ b/ssl/ssl_local.h
@@ -1601,6 +1601,12 @@ struct ssl_connection_st {
* making any further progress, including the progress being waited for.
*/
unsigned int listener_force_nonblocking;
+ /*
+ * Set when the listener's last drive of this pending connection left it
+ * waiting for its peer, so it need not be driven again until a datagram
+ * is queued for it or its retransmission timer expires.
+ */
+ unsigned int listener_idle;
#endif
/* callback that allows applications to peek at protocol messages */
void (*msg_callback)(int write_p, int version, int content_type,
@@ -2349,6 +2355,9 @@ DEFINE_STACK_OF(SSL)
*/
#define DTLS_LISTENER_DEFAULT_MAX_PENDING_CONNS 256
+/* Most drive passes over the pending connections in one listener tick */
+#define DTLS_LISTENER_MAX_DRIVE_PASSES 4
+
/*
* Default maximum size in bytes of a datagram a DTLS listener will receive.
* A DTLS 1.3 ClientHello carrying large (e.g. post-quantum) key shares can
@@ -2430,6 +2439,12 @@ typedef struct dtls_listener_st {
/* Advanced when the last waiter clears the notifier. */
uint64_t notify_generation;
+ /*
+ * A pending connection may have queued input that no drive pass in
+ * progress will see. Protected by mutex.
+ */
+ unsigned int pending_input;
+
/*
* Time callback for customizable time source (primarily for testing).
* If NULL, ossl_time_now() is used.
@@ -3172,6 +3187,7 @@ int ossl_dtls_get_blocking_mode(const SSL *s);
int ossl_dtls_conn_wait_for_datagram(SSL *s);
int ossl_dtls_conn_wait_for_write(SSL *s);
int ossl_dtls_tick(DTLS_LISTENER *dl);
+int ossl_dtls_listener_has_pending_input(SSL *s);
/* DTLS Listener internal cookie callbacks */
int ossl_dtls_listener_gen_cookie_cb(SSL *ssl, unsigned char *cookie,
diff --git a/test/dtlsssllistenertest.c b/test/dtlsssllistenertest.c
index f6d98bf941..9b327f1fc6 100644
--- a/test/dtlsssllistenertest.c
+++ b/test/dtlsssllistenertest.c
@@ -7607,6 +7607,742 @@ end:
return testresult;
}
#endif /* OPENSSL_NO_DTLS1_2 */
+
+static int idle_pending_accept_calls;
+
+static void idle_pending_info_cb(const SSL *ssl, int where, int ret)
+{
+ if ((where & SSL_CB_ACCEPT_EXIT) == SSL_CB_ACCEPT_EXIT)
+ idle_pending_accept_calls++;
+}
+
+#define IDLE_PENDING_TICKS 20
+
+typedef struct {
+ int retry_sent; /* pending connections that have sent an HVR/HRR */
+ int queued; /* pending connections with a datagram still queued */
+} IDLE_PENDING_STATE;
+
+static void idle_pending_state_cb(SSL *ssl, const BIO_ADDR *peer, void *arg)
+{
+ IDLE_PENDING_STATE *st = arg;
+ SSL_CONNECTION *sc = SSL_CONNECTION_FROM_SSL_ONLY(ssl);
+
+ if (sc == NULL || sc->listener_rx == NULL) {
+ st->queued++;
+ return;
+ }
+ if (sc->hello_retry_request == SSL_HRR_PENDING
+ || sc->statem.hand_state == DTLS_ST_SW_HELLO_VERIFY_REQUEST)
+ st->retry_sent++;
+ if (!ossl_list_urxe_is_empty(&sc->listener_rx->urxe_pending))
+ st->queued++;
+}
+
+/*
+ * Have both pending connections consumed everything sent to them? The
+ * client's has sent its HVR/HRR, so has read its whole ClientHello, and
+ * the junk datagram has been read since its connection exists; neither
+ * has a datagram queued.
+ */
+static int idle_pending_settled(DTLS_LISTENER *dl)
+{
+ IDLE_PENDING_STATE st = { 0, 0 };
+
+ if (ossl_dgram_conn_lookup_num_items(dl->pending_conns) != 2)
+ return 0;
+ ossl_dgram_conn_lookup_foreach(dl->pending_conns, idle_pending_state_cb,
+ &st);
+ return st.retry_sent == 1 && st.queued == 0;
+}
+
+/*
+ * Regression test: a pending connection waiting for its peer must not be
+ * driven on every listener tick. One pending connection has received a
+ * junk datagram, another a ClientHello answered with an HVR/HRR. Ticking
+ * the listener with nothing new arriving must not call SSL_accept() on
+ * either; the client must then still complete its handshake.
+ */
+static int test_dtls_listener_idle_pending_not_driven_int(int version)
+{
+ SSL_CTX *sctx = NULL, *cctx = NULL;
+ SSL *listener = NULL, *clientssl = NULL, *serverssl = NULL;
+ SSL *junk = NULL;
+ BIO_ADDR *server_addr = NULL;
+ int server_fd = -1, client_fd = -1, junk_fd = -1;
+ DTLS_LISTENER *dl;
+ int testresult = 0, k;
+
+ idle_pending_accept_calls = 0;
+ if (!TEST_true(create_ssl_ctx_pair(NULL, DTLS_server_method(),
+ DTLS_client_method(), version, version,
+ &sctx, &cctx, cert, privkey)))
+ goto end;
+ SSL_CTX_set_info_callback(sctx, idle_pending_info_cb);
+
+ if (!TEST_true(create_udp_listener(sctx, SSL_LISTENER_FLAG_SINGLE_THREAD,
+ &listener, &server_addr, &server_fd)))
+ goto end;
+ dl = (DTLS_LISTENER *)listener;
+
+ if (!TEST_true(create_dtls_client_for_addr(cctx, server_addr,
+ &junk, &junk_fd))
+ || !TEST_true(create_dtls_client_for_addr(cctx, server_addr,
+ &clientssl, &client_fd)))
+ goto end;
+
+ /* A one-byte datagram, not a DTLS record. */
+ if (!TEST_int_eq(BIO_write(SSL_get_wbio(junk), "", 1), 1)
+ || !client_step(clientssl))
+ goto end;
+
+ /*
+ * Tick until both connections have consumed what was sent to them and
+ * a tick has driven neither. How many ticks that takes depends on how
+ * many datagrams each pump reads. Without the fix no tick is quiet, so
+ * this runs to its bound and the check below fails.
+ */
+ for (k = 0; k < 200; k++) {
+ int prev = idle_pending_accept_calls;
+
+ listener_tick(listener);
+ if (idle_pending_accept_calls == prev && idle_pending_settled(dl))
+ break;
+ OSSL_sleep(10);
+ }
+ if (!TEST_true(idle_pending_settled(dl))
+ || !TEST_int_gt(idle_pending_accept_calls, 0))
+ goto end;
+
+ idle_pending_accept_calls = 0;
+ for (k = 0; k < IDLE_PENDING_TICKS; k++)
+ listener_tick(listener);
+ if (!TEST_int_eq(idle_pending_accept_calls, 0)
+ || !TEST_size_t_eq(ossl_dgram_conn_lookup_num_items(dl->pending_conns),
+ 2))
+ goto end;
+
+ /* The client's next flight is still processed. */
+ if (!drive_client_to_promotion(listener, clientssl, 1))
+ goto end;
+ if (!TEST_ptr(serverssl = SSL_accept_connection(listener,
+ SSL_ACCEPT_CONNECTION_NO_BLOCK))
+ || !TEST_true(create_ssl_connection(serverssl, clientssl,
+ SSL_ERROR_NONE))
+ || !TEST_int_eq(SSL_version(serverssl), version))
+ goto end;
+
+ testresult = 1;
+end:
+ SSL_free(serverssl);
+ SSL_free(clientssl);
+ SSL_free(junk);
+ if (client_fd >= 0)
+ BIO_closesocket(client_fd);
+ if (junk_fd >= 0)
+ BIO_closesocket(junk_fd);
+ SSL_free(listener);
+ BIO_ADDR_free(server_addr);
+ if (server_fd >= 0)
+ BIO_closesocket(server_fd);
+ SSL_CTX_free(sctx);
+ SSL_CTX_free(cctx);
+ return testresult;
+}
+
+static int test_dtls_listener_idle_pending_not_driven(void)
+{
+ int ok = 1;
+
+#ifndef OPENSSL_NO_DTLS1_2
+ if (!test_dtls_listener_idle_pending_not_driven_int(DTLS1_2_VERSION))
+ ok = 0;
+#endif
+#ifndef OPENSSL_NO_DTLS1_3
+ if (!test_dtls_listener_idle_pending_not_driven_int(DTLS1_3_VERSION))
+ ok = 0;
+#endif
+ return ok;
+}
+
+/*
+ * Gives each recvmmsg() at most one datagram, as the recvmsg() fallback does,
+ * or for one call only, as many as *data says.
+ */
+static int one_dgram_recvmmsg(BIO *bio, BIO_MSG *msg, size_t stride,
+ size_t num_msg, uint64_t flags, size_t *msgs_processed)
+{
+ size_t *batch = BIO_get_data(bio);
+ BIO *next = BIO_next(bio);
+ size_t max = 1;
+
+ if (next == NULL)
+ return 0;
+ if (batch != NULL && *batch > 0)
+ max = *batch;
+ if (!BIO_recvmmsg(next, msg, stride, num_msg > max ? max : num_msg, flags,
+ msgs_processed))
+ return 0;
+ if (batch != NULL)
+ *batch = 0;
+ return 1;
+}
+
+#define BIO_TYPE_ONE_DGRAM_FILTER (0x84 | BIO_TYPE_FILTER)
+
+static BIO_METHOD *method_one_dgram = NULL;
+
+/* Note: Not thread safe! */
+static const BIO_METHOD *bio_f_one_dgram_filter(void)
+{
+ if (method_one_dgram == NULL) {
+ method_one_dgram = BIO_meth_new(BIO_TYPE_ONE_DGRAM_FILTER,
+ "One datagram per read filter");
+ if (method_one_dgram == NULL
+ || !BIO_meth_set_ctrl(method_one_dgram, failing_send_ctrl)
+ || !BIO_meth_set_recvmmsg(method_one_dgram, one_dgram_recvmmsg))
+ return NULL;
+ }
+ return method_one_dgram;
+}
+
+static unsigned int redrive_timer_cb(SSL *ssl, unsigned int us)
+{
+ return 3600U * 1000000U;
+}
+
+/* No retransmission timer may fire on the listener's side either */
+static int redrive_pending_cb(SSL_CTX *ctx, SSL *ssl, void *arg)
+{
+ DTLS_set_timer_cb(ssl, redrive_timer_cb);
+ return 1;
+}
+
+static void redrive_ch_cb(int write_p, int version, int content_type,
+ const void *buf, size_t len, SSL *ssl, void *arg)
+{
+ if (write_p && content_type == SSL3_RT_HANDSHAKE && len > 0
+ && *(const unsigned char *)buf == SSL3_MT_CLIENT_HELLO)
+ (*(int *)arg)++;
+}
+
+static void redrive_idle_cb(SSL *ssl, const BIO_ADDR *peer, void *arg)
+{
+ SSL_CONNECTION *sc = SSL_CONNECTION_FROM_SSL_ONLY(ssl);
+
+ if (sc != NULL && sc->listener_idle && sc->listener_rx != NULL
+ && ossl_list_urxe_is_empty(&sc->listener_rx->urxe_pending))
+ (*(size_t *)arg)++;
+}
+
+/* Are all |n| pending connections waiting for their peers? */
+static int redrive_all_idle(DTLS_LISTENER *dl, size_t n)
+{
+ size_t idle = 0;
+
+ if (ossl_dgram_conn_lookup_num_items(dl->pending_conns) != n)
+ return 0;
+ ossl_dgram_conn_lookup_foreach(dl->pending_conns, redrive_idle_cb, &idle);
+ return idle == n;
+}
+
+#define REDRIVE_MAX_CLIENTS (DTLS_LISTENER_MAX_DRIVE_PASSES + 2)
+#define REDRIVE_POLL_TIMEOUT_SEC 10
+
+typedef struct {
+ int version;
+ SSL_CTX *sctx, *cctx;
+ SSL *listener;
+ BIO_ADDR *server_addr;
+ int server_fd;
+ size_t batch; /* datagrams the listener's next read may take */
+ size_t nclients;
+ SSL *clients[REDRIVE_MAX_CLIENTS];
+ int fds[REDRIVE_MAX_CLIENTS];
+ int ch_count[REDRIVE_MAX_CLIENTS]; /* ClientHellos each has sent */
+} REDRIVE_FIXTURE;
+
+static void redrive_teardown(REDRIVE_FIXTURE *fx)
+{
+ size_t i;
+
+ for (i = 0; i < REDRIVE_MAX_CLIENTS; i++) {
+ SSL_free(fx->clients[i]);
+ if (fx->fds[i] >= 0)
+ BIO_closesocket(fx->fds[i]);
+ }
+ SSL_free(fx->listener);
+ BIO_ADDR_free(fx->server_addr);
+ if (fx->server_fd >= 0)
+ BIO_closesocket(fx->server_fd);
+ SSL_CTX_free(fx->sctx);
+ SSL_CTX_free(fx->cctx);
+ BIO_meth_free(method_one_dgram);
+ method_one_dgram = NULL;
+}
+
+/*
+ * A listener which reads one datagram at a time, with |nclients| pending
+ * connections which have all sent their HVR/HRR and are waiting for their
+ * peers.
+ */
+static int redrive_setup(REDRIVE_FIXTURE *fx, int version, uint64_t flags,
+ size_t nclients)
+{
+ BIO *sockbio, *filter;
+ size_t i;
+ int k;
+
+ memset(fx, 0, sizeof(*fx));
+ fx->version = version;
+ fx->server_fd = -1;
+ for (i = 0; i < REDRIVE_MAX_CLIENTS; i++)
+ fx->fds[i] = -1;
+ fx->nclients = nclients;
+
+ if (!TEST_true(create_ssl_ctx_pair(NULL, DTLS_server_method(),
+ DTLS_client_method(), version, version,
+ &fx->sctx, &fx->cctx, cert, privkey)))
+ return 0;
+ SSL_CTX_set_new_pending_conn_cb(fx->sctx, redrive_pending_cb, NULL);
+
+ if (!TEST_true(create_udp_listener(fx->sctx, flags, &fx->listener,
+ &fx->server_addr, &fx->server_fd)))
+ return 0;
+
+ if (!TEST_ptr(sockbio = SSL_get_rbio(fx->listener))
+ || !TEST_ptr(filter = BIO_new(bio_f_one_dgram_filter())))
+ return 0;
+ BIO_set_data(filter, &fx->batch);
+ BIO_set_init(filter, 1);
+ if (!TEST_true(BIO_up_ref(sockbio))) {
+ BIO_free(filter);
+ return 0;
+ }
+ BIO_push(filter, sockbio);
+ SSL_set0_rbio(fx->listener, filter);
+
+ for (i = 0; i < nclients; i++) {
+ if (!TEST_true(create_dtls_client_for_addr(fx->cctx, fx->server_addr,
+ &fx->clients[i], &fx->fds[i])))
+ return 0;
+ DTLS_set_timer_cb(fx->clients[i], redrive_timer_cb);
+ /* Each ClientHello is one datagram */
+ SSL_set_options(fx->clients[i], SSL_OP_NO_QUERY_MTU);
+ SSL_set_mtu(fx->clients[i], 1800);
+ SSL_set_msg_callback(fx->clients[i], redrive_ch_cb);
+ SSL_set_msg_callback_arg(fx->clients[i], &fx->ch_count[i]);
+ if (!client_step(fx->clients[i]))
+ return 0;
+ }
+
+ for (k = 0; k < 200; k++) {
+ if (redrive_all_idle((DTLS_LISTENER *)fx->listener, nclients))
+ return 1;
+ listener_tick(fx->listener);
+ OSSL_sleep(10);
+ }
+ return TEST_true(redrive_all_idle((DTLS_LISTENER *)fx->listener, nclients));
+}
+
+static int redrive_send_junk(REDRIVE_FIXTURE *fx, size_t i)
+{
+ return TEST_int_eq(BIO_write(SSL_get_wbio(fx->clients[i]), "", 1), 1);
+}
+
+/* Have client |i| send its second ClientHello */
+static int redrive_send_ch2(REDRIVE_FIXTURE *fx, size_t i)
+{
+ int k;
+
+ for (k = 0; k < 30 && fx->ch_count[i] < 2; k++) {
+ if (!client_step(fx->clients[i]))
+ return 0;
+ OSSL_sleep(10);
+ }
+ return TEST_int_eq(fx->ch_count[i], 2);
+}
+
+/* Accept the queued connection and complete client |i|'s handshake */
+static int redrive_finish(REDRIVE_FIXTURE *fx, size_t i)
+{
+ SSL *serverssl = NULL;
+ int ok;
+
+ ok = TEST_size_t_eq(SSL_get_accept_connection_queue_len(fx->listener), 1)
+ && TEST_ptr(serverssl = SSL_accept_connection(fx->listener,
+ SSL_ACCEPT_CONNECTION_NO_BLOCK))
+ && TEST_true(create_ssl_connection(serverssl, fx->clients[i],
+ SSL_ERROR_NONE))
+ && TEST_int_eq(SSL_version(serverssl), fx->version);
+ SSL_free(serverssl);
+ return ok;
+}
+
+static int redrive_params(int idx, int *version, uint64_t *flags)
+{
+ *version = (idx & 1) != 0 ? DTLS1_3_VERSION : DTLS1_2_VERSION;
+ *flags = (idx & 2) != 0 ? 0 : SSL_LISTENER_FLAG_SINGLE_THREAD;
+#ifdef OPENSSL_NO_DTLS1_3
+ if (*version == DTLS1_3_VERSION)
+ return TEST_skip("DTLS 1.3 disabled");
+#endif
+#ifdef OPENSSL_NO_DTLS1_2
+ if (*version == DTLS1_2_VERSION)
+ return TEST_skip("DTLS 1.2 disabled");
+#endif
+#ifndef OPENSSL_THREADS
+ if (*flags == 0)
+ return TEST_skip("no threads");
+#endif
+ return -1;
+}
+
+/*
+ * Regression test: input which driving one pending connection takes off the
+ * socket for another must be driven without waiting for the socket.
+ *
+ * Junk from |njunk| idle pending connections, then the remaining client's
+ * second ClientHello, are queued on the socket. Each drive reads the next
+ * datagram for the next connection; the last takes the ClientHello off the
+ * socket. With one junk connection a single tick must promote the client.
+ * With more than one tick's passes can follow, SSL_poll() must tick again
+ * rather than sleep on the empty socket.
+ *
+ * idx bits 0-1: see redrive_params(). bit 2: the client is the first socket,
+ * not the last, which varies the pending table order. bit 3: |njunk| is
+ * DTLS_LISTENER_MAX_DRIVE_PASSES, else 1.
+ */
+static int test_dtls_listener_redrive(int idx)
+{
+ REDRIVE_FIXTURE fx;
+ int version, ret, testresult = 0;
+ uint64_t flags;
+ size_t i, njunk = (idx & 8) != 0 ? DTLS_LISTENER_MAX_DRIVE_PASSES : 1;
+ size_t ci = (idx & 4) != 0 ? 0 : njunk;
+ SSL_POLL_ITEM item;
+ struct timeval timeout;
+ size_t result_count = 0;
+ OSSL_TIME start;
+
+ if ((ret = redrive_params(idx, &version, &flags)) >= 0)
+ return ret;
+
+ if (!redrive_setup(&fx, version, flags, njunk + 1))
+ goto end;
+
+ for (i = 0; i <= njunk; i++)
+ if (i != ci && !redrive_send_junk(&fx, i))
+ goto end;
+ if (!redrive_send_ch2(&fx, ci))
+ goto end;
+ OSSL_sleep(50);
+
+ if (njunk == 1) {
+ listener_tick(fx.listener);
+ } else {
+ item.desc.type = BIO_POLL_DESCRIPTOR_TYPE_SSL;
+ item.desc.value.ssl = fx.listener;
+ item.events = SSL_POLL_EVENT_IC;
+ item.revents = 0;
+ timeout.tv_sec = REDRIVE_POLL_TIMEOUT_SEC;
+ timeout.tv_usec = 0;
+ start = ossl_time_now();
+ if (!TEST_true(SSL_poll(&item, 1, sizeof(item), &timeout, 0,
+ &result_count))
+ || !TEST_uint64_t_lt(ossl_time2seconds(ossl_time_subtract(ossl_time_now(),
+ start)),
+ REDRIVE_POLL_TIMEOUT_SEC / 2)
+ || !TEST_size_t_eq(result_count, 1)
+ || !TEST_uint64_t_ne(item.revents & SSL_POLL_EVENT_IC, 0))
+ goto end;
+ }
+
+ if (!redrive_finish(&fx, ci))
+ goto end;
+
+ testresult = 1;
+end:
+ redrive_teardown(&fx);
+ return testresult;
+}
+
+typedef struct {
+ uint16_t ports[2];
+ size_t n;
+} REDRIVE_ORDER;
+
+static void redrive_order_cb(SSL *ssl, const BIO_ADDR *peer, void *arg)
+{
+ REDRIVE_ORDER *order = arg;
+
+ if (order->n < 2)
+ order->ports[order->n] = BIO_ADDR_rawport(peer);
+ order->n++;
+}
+
+static int redrive_client_port(REDRIVE_FIXTURE *fx, size_t i, uint16_t *port)
+{
+ BIO_ADDR *addr = BIO_ADDR_new();
+ union BIO_sock_info_u info;
+ int ok;
+
+ info.addr = addr;
+ ok = TEST_ptr(addr)
+ && TEST_true(BIO_sock_info(fx->fds[i], BIO_SOCK_INFO_ADDRESS, &info));
+ if (ok)
+ *port = BIO_ADDR_rawport(addr);
+ BIO_ADDR_free(addr);
+ return ok;
+}
+
+/*
+ * Regression test: input queued for a pending connection already driven in
+ * the same pass must be driven.
+ *
+ * Two idle pending connections, F and S, in the order a tick collects them.
+ * The socket holds junk from F, junk twice from S, then F's second
+ * ClientHello, and the tick's first read takes two datagrams. F is driven
+ * first and reads the next datagram, S's junk. S is driven next and takes
+ * F's ClientHello off the socket. One tick must promote F.
+ *
+ * idx: see redrive_params().
+ */
+static int test_dtls_listener_redrive_driven_earlier(int idx)
+{
+ REDRIVE_FIXTURE fx;
+ REDRIVE_ORDER order;
+ int version, ret, testresult = 0;
+ uint64_t flags;
+ uint16_t port0 = 0;
+ size_t f, s;
+
+ if ((ret = redrive_params(idx, &version, &flags)) >= 0)
+ return ret;
+
+ if (!redrive_setup(&fx, version, flags, 2))
+ goto end;
+
+ /* The pending table is unchanged until the tick, so it collects in this order */
+ memset(&order, 0, sizeof(order));
+ ossl_dgram_conn_lookup_foreach(((DTLS_LISTENER *)fx.listener)->pending_conns,
+ redrive_order_cb, &order);
+ if (!TEST_size_t_eq(order.n, 2)
+ || !redrive_client_port(&fx, 0, &port0))
+ goto end;
+ f = order.ports[0] == port0 ? 0 : 1;
+ s = 1 - f;
+
+ if (!redrive_send_junk(&fx, f)
+ || !redrive_send_junk(&fx, s)
+ || !redrive_send_junk(&fx, s)
+ || !redrive_send_ch2(&fx, f))
+ goto end;
+ OSSL_sleep(50);
+
+ fx.batch = 2;
+ listener_tick(fx.listener);
+ if (!redrive_finish(&fx, f))
+ goto end;
+
+ testresult = 1;
+end:
+ redrive_teardown(&fx);
+ return testresult;
+}
+
+typedef struct {
+ SSL *writer; /* established client */
+ int sent;
+} REDRIVE_CONN_POLL;
+
+/* On the pending connection's reply to its ClientHello, send app data */
+static void redrive_sh_cb(int write_p, int version, int content_type,
+ const void *buf, size_t len, SSL *ssl, void *arg)
+{
+ REDRIVE_CONN_POLL *cp = arg;
+
+ if (write_p && content_type == SSL3_RT_HANDSHAKE && len > 0
+ && *(const unsigned char *)buf == SSL3_MT_SERVER_HELLO && !cp->sent
+ && SSL_write(cp->writer, "x", 1) == 1)
+ cp->sent = 1;
+}
+
+static void redrive_set_sh_cb(SSL *ssl, const BIO_ADDR *peer, void *arg)
+{
+ SSL_set_msg_callback(ssl, redrive_sh_cb);
+ SSL_set_msg_callback_arg(ssl, arg);
+}
+
+/*
+ * Regression test: a wait on an accepted connection must not sleep while
+ * the listener's tick has left input on a pending connection.
+ *
+ * One client is accepted. The socket then holds junk from
+ * DTLS_LISTENER_MAX_DRIVE_PASSES idle pending connections and another
+ * client's second ClientHello, which the tick in SSL_poll()'s blocking
+ * translation takes off the socket with its last pass. SSL_POLL_FLAG_NO_
+ * HANDLE_EVENTS keeps the readout before it from ticking. When the
+ * ClientHello is driven the accepted client sends data, so SSL_poll() on
+ * the accepted connection returns then; otherwise it sleeps to its timeout.
+ *
+ * idx: see redrive_params().
+ */
+static int test_dtls_listener_conn_poll_pending_input(int idx)
+{
+ REDRIVE_FIXTURE fx;
+ REDRIVE_CONN_POLL cp;
+ int version, ret, testresult = 0, k;
+ uint64_t flags;
+ size_t i, njunk = DTLS_LISTENER_MAX_DRIVE_PASSES;
+ size_t ei = njunk + 1, ci = njunk;
+ SSL *serverssl = NULL;
+ SSL_POLL_ITEM item;
+ struct timeval timeout;
+ size_t result_count = 0;
+ OSSL_TIME start;
+ char buf[16];
+
+ if ((ret = redrive_params(idx, &version, &flags)) >= 0)
+ return ret;
+
+ if (!redrive_setup(&fx, version, flags, njunk + 2))
+ goto end;
+
+ /* Accept client ei */
+ if (!redrive_send_ch2(&fx, ei))
+ goto end;
+ for (k = 0; k < 200
+ && SSL_get_accept_connection_queue_len(fx.listener) == 0;
+ k++) {
+ listener_tick(fx.listener);
+ OSSL_sleep(10);
+ }
+ if (!TEST_ptr(serverssl = SSL_accept_connection(fx.listener,
+ SSL_ACCEPT_CONNECTION_NO_BLOCK))
+ || !TEST_true(create_ssl_connection(serverssl, fx.clients[ei],
+ SSL_ERROR_NONE)))
+ goto end;
+
+ /* Let any post-handshake messages settle */
+ for (k = 0; k < 20; k++) {
+ if (!TEST_int_le(SSL_read(fx.clients[ei], buf, sizeof(buf)), 0)
+ || !TEST_int_le(SSL_read(serverssl, buf, sizeof(buf)), 0))
+ goto end;
+ listener_tick(fx.listener);
+ OSSL_sleep(10);
+ }
+ ERR_clear_error();
+ if (!TEST_true(redrive_all_idle((DTLS_LISTENER *)fx.listener, njunk + 1)))
+ goto end;
+
+ memset(&cp, 0, sizeof(cp));
+ cp.writer = fx.clients[ei];
+ ossl_dgram_conn_lookup_foreach(((DTLS_LISTENER *)fx.listener)->pending_conns,
+ redrive_set_sh_cb, &cp);
+
+ for (i = 0; i < njunk; i++)
+ if (!redrive_send_junk(&fx, i))
+ goto end;
+ if (!redrive_send_ch2(&fx, ci))
+ goto end;
+ OSSL_sleep(50);
+
+ item.desc.type = BIO_POLL_DESCRIPTOR_TYPE_SSL;
+ item.desc.value.ssl = serverssl;
+ item.events = SSL_POLL_EVENT_R;
+ item.revents = 0;
+ timeout.tv_sec = REDRIVE_POLL_TIMEOUT_SEC;
+ timeout.tv_usec = 0;
+ start = ossl_time_now();
+ if (!TEST_true(SSL_poll(&item, 1, sizeof(item), &timeout,
+ SSL_POLL_FLAG_NO_HANDLE_EVENTS, &result_count))
+ || !TEST_uint64_t_lt(ossl_time2seconds(ossl_time_subtract(ossl_time_now(),
+ start)),
+ REDRIVE_POLL_TIMEOUT_SEC / 2)
+ || !TEST_true(cp.sent)
+ || !TEST_size_t_eq(result_count, 1)
+ || !TEST_uint64_t_ne(item.revents & SSL_POLL_EVENT_R, 0)
+ || !TEST_int_eq(SSL_read(serverssl, buf, sizeof(buf)), 1))
+ goto end;
+
+ if (!redrive_finish(&fx, ci))
+ goto end;
+
+ testresult = 1;
+end:
+ SSL_free(serverssl);
+ redrive_teardown(&fx);
+ return testresult;
+}
+
+/* Fails every receive permanently */
+static int failing_recv_recvmmsg(BIO *bio, BIO_MSG *msg, size_t stride,
+ size_t num_msg, uint64_t flags, size_t *msgs_processed)
+{
+ *msgs_processed = 0;
+ ERR_raise(ERR_LIB_BIO, BIO_R_UNSUPPORTED_METHOD);
+ return 0;
+}
+
+#define BIO_TYPE_FAILING_RECV_FILTER (0x85 | BIO_TYPE_FILTER)
+
+/*
+ * Regression test: a listener whose network BIO has failed permanently must
+ * not report pending input. Its ticks fail before driving anything, so a
+ * dl->pending_input set before the failure is never cleared.
+ */
+static int test_dtls_listener_fatal_no_pending_input(void)
+{
+ SSL_CTX *sctx = NULL;
+ SSL *listener = NULL;
+ BIO_ADDR *server_addr = NULL;
+ BIO_METHOD *meth = NULL;
+ BIO *sockbio, *filter = NULL;
+ DTLS_LISTENER *dl;
+ int server_fd = -1, testresult = 0;
+
+ if (!TEST_ptr(sctx = SSL_CTX_new(DTLS_server_method()))
+ || !TEST_true(create_udp_listener(sctx,
+ SSL_LISTENER_FLAG_SINGLE_THREAD, &listener, &server_addr,
+ &server_fd)))
+ goto end;
+ dl = (DTLS_LISTENER *)listener;
+
+ if (!TEST_ptr(meth = BIO_meth_new(BIO_TYPE_FAILING_RECV_FILTER,
+ "Failing datagram recv filter"))
+ || !TEST_true(BIO_meth_set_ctrl(meth, failing_send_ctrl))
+ || !TEST_true(BIO_meth_set_recvmmsg(meth, failing_recv_recvmmsg))
+ || !TEST_ptr(sockbio = SSL_get_rbio(listener))
+ || !TEST_ptr(filter = BIO_new(meth)))
+ goto end;
+ BIO_set_init(filter, 1);
+ if (!TEST_true(BIO_up_ref(sockbio)))
+ goto end;
+ BIO_push(filter, sockbio);
+ SSL_set0_rbio(listener, filter);
+ filter = NULL;
+
+ /* Input left undriven, then the BIO fails */
+ dl->pending_input = 1;
+ if (!TEST_int_eq(ossl_dtls_tick(dl), -1)
+ || !TEST_true(dl->fatal)
+ || !TEST_true(dl->pending_input)
+ || !TEST_false(ossl_dtls_listener_has_pending_input(listener)))
+ goto end;
+
+ testresult = 1;
+end:
+ ERR_clear_error();
+ BIO_free(filter);
+ SSL_free(listener);
+ BIO_meth_free(meth);
+ BIO_ADDR_free(server_addr);
+ if (server_fd >= 0)
+ BIO_closesocket(server_fd);
+ SSL_CTX_free(sctx);
+ return testresult;
+}
#endif /* !OPENSSL_NO_DTLS1_2 || !OPENSSL_NO_DTLS1_3 */
#ifndef OPENSSL_NO_DTLS1_3
@@ -7869,6 +8605,11 @@ int setup_tests(void)
ADD_TEST(test_dtls_listener_accept_queue_novalidate_capped);
/* Demux free-list bound regression test; runs every enabled version */
ADD_TEST(test_dtls_listener_flood_then_handshake);
+ ADD_TEST(test_dtls_listener_idle_pending_not_driven);
+ ADD_ALL_TESTS(test_dtls_listener_redrive, 16);
+ ADD_ALL_TESTS(test_dtls_listener_redrive_driven_earlier, 4);
+ ADD_ALL_TESTS(test_dtls_listener_conn_poll_pending_input, 4);
+ ADD_TEST(test_dtls_listener_fatal_no_pending_input);
#endif
#ifndef OPENSSL_NO_DTLS1_3
ADD_TEST(test_dtls13_listener_conn_read_timeout);