Commit 00e2ed640b for openssl.org
commit 00e2ed640baa4c8a8f234e8fef9d7688a6cdc002
Author: Mounir IDRASSI <mounir.idrassi@idrix.fr>
Date: Sat Sep 26 10:19:47 2026 +0900
BIO: make Windows datagram socket timeouts retryable
BIO_dgram_recv_timedout already expects WSAETIMEDOUT on Windows,
but dgram_read only saves errors accepted by BIO_dgram_non_fatal_error.
The missing case leaves the read retry flag clear and prevents the
timeout query from reporting the error.
Since ebb188f0, a fatal record read without an alert also puts the
connection into the error state, preventing DTLS from handling its
retransmission timer after a socket timeout.
Accept WSAETIMEDOUT as retryable, matching the existing EAGAIN case.
Datagram writes use the same classifier, so send timeouts become
retryable too. Add a socket test for timeout reporting and successful
reads afterwards.
Fixes #32979
Assisted-by: Codex:gpt-6-astra
Reviewed-by: Tomas Mraz <tomas@openssl.foundation>
Reviewed-by: Matt Caswell <matt@openssl.foundation>
Merge-date: Thu Oct 1 08:38:25 2026
Merged-from: https://github.com/openssl/openssl/pull/32999
diff --git a/crypto/bio/bss_dgram.c b/crypto/bio/bss_dgram.c
index 1d98239511..0ef973c195 100644
--- a/crypto/bio/bss_dgram.c
+++ b/crypto/bio/bss_dgram.c
@@ -2806,6 +2806,9 @@ int BIO_dgram_non_fatal_error(int err)
{
switch (err) {
#if defined(OPENSSL_SYS_WINDOWS)
+#if defined(WSAETIMEDOUT)
+ case WSAETIMEDOUT:
+#endif
#if defined(WSAEWOULDBLOCK)
case WSAEWOULDBLOCK:
#endif
diff --git a/test/bio_dgram_test.c b/test/bio_dgram_test.c
index fdbc3d340f..0494a1cee9 100644
--- a/test/bio_dgram_test.c
+++ b/test/bio_dgram_test.c
@@ -17,6 +17,56 @@
#if !defined(OPENSSL_NO_DGRAM) && !defined(OPENSSL_NO_SOCK)
+#if defined(SO_RCVTIMEO)
+static int test_bio_dgram_recv_timeout(void)
+{
+ int testresult = 0, fd = -1;
+ BIO *bio = NULL;
+ BIO_ADDR *addr = NULL;
+ struct in_addr ina;
+ struct timeval timeout = { 0, 100000 };
+ union BIO_sock_info_u info;
+ char buf;
+
+ ina.s_addr = htonl(INADDR_LOOPBACK);
+ if (!TEST_ptr(addr = BIO_ADDR_new())
+ || !TEST_true(BIO_ADDR_rawmake(addr, AF_INET, &ina, sizeof(ina), 0))
+ || !TEST_int_ge(fd = BIO_socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP, 0), 0)
+ || !TEST_int_gt(BIO_bind(fd, addr, 0), 0)
+ || !TEST_ptr(bio = BIO_new_dgram(fd, BIO_NOCLOSE))
+ || !TEST_int_eq(BIO_ctrl(bio, BIO_CTRL_DGRAM_SET_RECV_TIMEOUT,
+ 0, &timeout),
+ 0))
+ goto err;
+
+ /* A receive timeout must leave the BIO ready to retry. */
+ if (!TEST_int_eq(BIO_read(bio, &buf, sizeof(buf)), -1)
+ || !TEST_true(BIO_should_retry(bio))
+ || !TEST_true(BIO_should_read(bio))
+ || !TEST_int_eq(BIO_dgram_recv_timedout(bio), 1)
+ || !TEST_int_eq(BIO_dgram_recv_timedout(bio), 0))
+ goto err;
+
+ /* The same socket must still be usable after the timeout. */
+ info.addr = addr;
+ if (!TEST_int_gt(BIO_sock_info(fd, BIO_SOCK_INFO_ADDRESS, &info), 0)
+ || !TEST_int_gt(BIO_dgram_set_peer(bio, addr), 0)
+ || !TEST_int_eq(BIO_write(bio, "x", 1), 1)
+ || !TEST_int_eq(BIO_read(bio, &buf, sizeof(buf)), 1)
+ || !TEST_int_eq(buf, 'x')
+ || !TEST_false(BIO_should_retry(bio)))
+ goto err;
+
+ testresult = 1;
+err:
+ BIO_free(bio);
+ BIO_ADDR_free(addr);
+ if (fd >= 0)
+ BIO_closesocket(fd);
+ return testresult;
+}
+#endif
+
static int compare_addr(const BIO_ADDR *a, const BIO_ADDR *b)
{
struct in_addr xa, xb;
@@ -1013,6 +1063,9 @@ int setup_tests(void)
}
#if !defined(OPENSSL_NO_DGRAM) && !defined(OPENSSL_NO_SOCK)
+#if defined(SO_RCVTIMEO)
+ ADD_TEST(test_bio_dgram_recv_timeout);
+#endif
ADD_ALL_TESTS(test_bio_dgram, OSSL_NELEM(bio_dgram_cases));
#if !defined(OPENSSL_NO_CHACHA)
ADD_ALL_TESTS(test_bio_dgram_pair, 3);