diff options
| author | Matt Caswell <[email protected]> | 2026-10-19 14:21:04 +0200 |
|---|---|---|
| committer | Tomas Mraz <[email protected]> | 2026-10-22 16:52:20 +0200 |
| commit | 4af5800f0ce6c566bb64ae55b02a5a4dd2a6b8fe (patch) | |
| tree | 662ef41c47a517e8525e3ada6ca906da03a0e01d | |
| parent | DTLS listener: free a pending connection that fails after HVR (diff) | |
| download | openssl-4af5800f0ce6c566bb64ae55b02a5a4dd2a6b8fe.tar.gz openssl-4af5800f0ce6c566bb64ae55b02a5a4dd2a6b8fe.zip | |
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 <[email protected]>
Reviewed-by: Saša Nedvědický <[email protected]>
Reviewed-by: Ryan Hooper <[email protected]>
Reviewed-by: Mounir Idrassi <[email protected]>
Merge-date: Thu Oct 22 14:38:07 2026
Merged-from: https://github.com/openssl/openssl/pull/33265
| -rw-r--r-- | ssl/d1_lib.c | 91 | ||||
| -rw-r--r-- | ssl/rio/poll_immediate.c | 35 | ||||
| -rw-r--r-- | ssl/ssl_local.h | 16 | ||||
| -rw-r--r-- | test/dtlsssllistenertest.c | 741 |
4 files changed, 867 insertions, 16 deletions
diff --git a/ssl/d1_lib.c b/ssl/d1_lib.c index 09c961742e9..8f0cf1c6eb0 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); @@ -2425,6 +2460,28 @@ static int dtls_listener_drive_pending(DTLS_LISTENER *dl) } /* + * 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. * * Uses the demux pump/callback architecture for efficient packet handling: @@ -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 4ba67a438d7..7b50fe5a916 100644 --- a/ssl/rio/poll_immediate.c +++ b/ssl/rio/poll_immediate.c @@ -233,6 +233,15 @@ static int poll_translate_ssl_dtls_listener(SSL *ssl, } /* + * 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 * incoming connection and data pending on the listener itself, so there is @@ -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 283065640cb..274b3cd5210 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 @@ -2431,6 +2440,12 @@ typedef struct dtls_listener_st { 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 f6d98bf9419..9b327f1fc63 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); |
