aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMounir IDRASSI <[email protected]>2026-10-14 11:37:37 +0200
committerTomas Mraz <[email protected]>2026-10-23 14:40:34 +0200
commit8c8c85cdc4470140c6b918d85ab45a6a0ad9547a (patch)
tree6c1506480262b5794702afef42e4456722ab037b
parentDocument DTLS 1.3 handshake ACK known issue (diff)
downloadopenssl-8c8c85cdc4470140c6b918d85ab45a6a0ad9547a.tar.gz
openssl-8c8c85cdc4470140c6b918d85ab45a6a0ad9547a.zip
test: make RADIX QUIC objects nonblocking by default
RADIX drives both endpoints and advances its simulated clock from one interpreter. Accepted server connections inherit blocking mode from their listeners, even though the underlying UDP sockets are nonblocking. Extra handshake retries advance fake time and can inflate the initial RTT estimates. In the one-way key update loops the server's estimate can stay high while later samples reduce the client's PTO. The server can then retain its previous receive keys for longer than the client waits before its next update. Packets starting that update are then discarded, as permitted by RFC 9001 section 6.5. A blocking server read prevents the interpreter from driving client retransmission or advancing the clock, so this recoverable discard becomes a hang. The interpreter's 60-second deadline cannot interrupt the call, leaving the process watchdog to abort. External UDP loss is not required to reproduce the failure. Set clients and listeners to nonblocking mode at construction, including listeners created from a domain and the custom listener constructors. Accepted connections and their streams inherit this setting. The client setting makes the construction policy consistent before OP_CONNECT_WAIT, which also sets its mode. Retain the domain blocking capability needed by SSL_poll and document how tests can explicitly enable blocking I/O. Add a regression using existing opcodes to delay an ACK, increase the server's PTO and require recovery across successive client key updates. Cover both listener creation paths. Check listener and client modes before connection establishment and the accepted server's mode, so a recurrence fails promptly instead of waiting for the process watchdog. Fixes #33206. Assisted-by: Codex:gpt-6-astra Reviewed-by: Tomas Mraz <[email protected]> Reviewed-by: Nikola Pajkovsky <[email protected]> Merge-date: Fri Oct 23 12:40:35 2026 Merged-from: https://github.com/openssl/openssl/pull/33213
-rw-r--r--test/radix/README.md15
-rw-r--r--test/radix/quic_ops.c18
-rw-r--r--test/radix/quic_tests.c72
3 files changed, 102 insertions, 3 deletions
diff --git a/test/radix/README.md b/test/radix/README.md
index f79a4d42300..a1bf133a605 100644
--- a/test/radix/README.md
+++ b/test/radix/README.md
@@ -95,6 +95,21 @@ The RADIX test features the following improvements relative to the
- An explicit `OP_END` is no longer needed.
+Blocking operations
+-------------------
+
+QUIC clients and listeners are explicitly nonblocking by default. Accepted
+connections and their streams inherit this setting. The interpreter drives
+active endpoints and advances the simulated clock between operations and on
+retries, so an operation must return to allow its peer to make progress.
+
+Tests which require blocking operations must enable them explicitly with
+`SSL_set_blocking_mode` and provide independent progress for the peer and the
+simulated clock while the call waits. `OP_CONNECT_WAIT` always sets the client
+to nonblocking mode, so enable blocking I/O after that operation. The domain
+flags still enable blocking support, which is also needed by tests of
+`SSL_poll` with a nonzero timeout.
+
Architecture
------------
diff --git a/test/radix/quic_ops.c b/test/radix/quic_ops.c
index c16509ea89c..4ed639830a2 100644
--- a/test/radix/quic_ops.c
+++ b/test/radix/quic_ops.c
@@ -242,6 +242,12 @@ DEF_FUNC(hf_new_ssl)
&& !TEST_true(ssl_attach_bio_dgram(ssl, 0, NULL)))
goto err;
+ /* The interpreter must remain free to tick peers and advance the clock. */
+ if (!is_domain && !TEST_true(SSL_set_blocking_mode(ssl, 0))) {
+ SSL_free(ssl);
+ goto err;
+ }
+
if (!TEST_true(ossl_quic_set_override_now_cb(ssl, get_time, NULL))) {
SSL_free(ssl);
goto err;
@@ -275,8 +281,11 @@ DEF_FUNC(hf_new_ssl_listener_from)
if (!TEST_ptr(listener = SSL_new_listener_from(domain, flags)))
goto err;
- if (!TEST_true(ssl_attach_bio_dgram(listener, 0, NULL)))
+ if (!TEST_true(ssl_attach_bio_dgram(listener, 0, NULL))
+ || !TEST_true(SSL_set_blocking_mode(listener, 0))) {
+ SSL_free(listener);
goto err;
+ }
if (!TEST_true(RADIX_PROCESS_set_ssl(RP(), listener_name, listener))) {
SSL_free(listener);
@@ -633,8 +642,13 @@ DEF_FUNC(hf_write)
REQUIRE_SSL(ssl);
r = SSL_write_ex(ssl, buf, buf_len, &bytes_written);
+ if (!check_consistent_want(ssl, r))
+ goto err;
+
+ if (r == 0 && is_want(ssl, r))
+ F_SPIN_AGAIN();
+
if (!TEST_true(r)
- || !check_consistent_want(ssl, r)
|| !TEST_size_t_eq(bytes_written, buf_len))
goto err;
diff --git a/test/radix/quic_tests.c b/test/radix/quic_tests.c
index 830aac1b038..e0b673a1df0 100644
--- a/test/radix/quic_tests.c
+++ b/test/radix/quic_tests.c
@@ -1438,7 +1438,8 @@ DEF_FUNC(new_listener)
if (!TEST_ptr(listener = SSL_new_listener(ctx, 0)))
goto err;
- if (!TEST_true(ssl_attach_bio_dgram(listener, 0, NULL))) {
+ if (!TEST_true(ssl_attach_bio_dgram(listener, 0, NULL))
+ || !TEST_true(SSL_set_blocking_mode(listener, 0))) {
SSL_free(listener);
goto err;
}
@@ -3932,6 +3933,7 @@ DEF_FUNC(new_listener_54)
if (!TEST_ptr(listener = SSL_new_listener(ctx, 0))
|| !TEST_true(ssl_attach_bio_dgram(listener, 0, NULL))
+ || !TEST_true(SSL_set_blocking_mode(listener, 0))
|| !TEST_true(RADIX_PROCESS_set_ssl(RP(), name, listener))) {
SSL_free(listener);
goto err;
@@ -6044,6 +6046,73 @@ DEF_SCRIPT(new_connid, "verify remote peer does not send excessive amount of NEW
OP_EXPECT_CONN_CLOSE_INFO(C, OSSL_QUIC_ERR_CONNECTION_ID_LIMIT_ERROR, 0, 0);
}
+DEF_FUNC(check_blocking_mode)
+{
+ int ok = 0;
+ SSL *ssl;
+ uint64_t blocking;
+
+ REQUIRE_SSL(ssl);
+ F_POP(blocking);
+
+ ok = TEST_int_eq(SSL_get_blocking_mode(ssl), (int)blocking);
+err:
+ return ok;
+}
+
+#define OP_CHECK_BLOCKING_MODE(name, blocking) \
+ (OP_SELECT_SSL(0, name), \
+ OP_PUSH_U64(blocking), \
+ OP_FUNC(check_blocking_mode))
+
+/*
+ * A packet which starts a key update while the peer still retains its old keys
+ * may be discarded (RFC 9001 s. 6.5), requiring retransmission. Delay an
+ * ACK to give the server a much larger PTO than the client, which makes the
+ * client's next key update arrive too early. The read waiting for that data
+ * must let the interpreter drive the client and advance the clock.
+ */
+DEF_SCRIPT(key_update_retained_keys,
+ "Key update recovery when the peer still retains old keys")
+{
+ size_t i, j;
+
+ for (i = 0; i < 2; ++i) {
+ if (i == 0) {
+ OP_NEW_SSL_L_LISTEN(L);
+ } else {
+ OP_CLEAR();
+ OP_NEW_SSL_D(Ds);
+ OP_NEW_SSL_L_FROM_LISTEN(Ds, L, 0);
+ }
+
+ OP_CHECK_BLOCKING_MODE(L, 0);
+ OP_NEW_SSL_C(C);
+ /* Check construction before OP_CONNECT_WAIT sets the mode again. */
+ OP_CHECK_BLOCKING_MODE(C, 0);
+ OP_SET_PEER_ADDR_FROM(C, L);
+ OP_CONNECT_WAIT(C);
+ OP_ACCEPT_CONN_WAIT(L, S, 0);
+ OP_CHECK_BLOCKING_MODE(S, 0);
+
+ OP_WRITE(C, "apple", 5);
+ OP_READ_EXPECT(S, "apple", 5);
+
+ OP_WRITE(S, "x", 1);
+ OP_SKIP_TIME(400);
+ OP_READ_EXPECT(C, "x", 1);
+
+ OP_OVERRIDE_KEY_UPDATE(C, 1);
+ for (j = 0; j < 200; ++j) {
+ OP_WRITE(C, "apple", 5);
+ OP_READ_EXPECT(S, "apple", 5);
+ OP_SKIP_TIME(8);
+ }
+
+ OP_CHECK_KEY_UPDATE_GE(C, 2);
+ }
+}
+
/*
* List of Test Scripts
* ============================================================================
@@ -6166,4 +6235,5 @@ static SCRIPT_INFO *const scripts[] = {
USE(script_106),
USE(new_connid),
USE(forbidden_frame_types),
+ USE(key_update_retained_keys),
};