Commit 4a7d9705f3 for openssl.org

commit 4a7d9705f30842b402058324a6947938fe3486ec
Author: Alexandr Nedvedicky <sashan@openssl.org>
Date:   Mon Jan 5 15:52:47 2026 +0100

    QUIC listener may fail with SSL_POLL_EVENT_EL on windows.

    recvfrom() may return WSAECONNRESET when the destination port
    used in a previous sendto() call is no longer available. This
    causes QUIC PORT to swich from running state. This behavior
    is not desired for QUIC protocol. The trick is to disable
    SIO_UDP_CONNRESET flag on UDP port used by QUIC.

    The issue was kindly reported and root caused by goforit22123-netizen@

    Fixes: #29530

    Reviewed-by: Eugene Syromiatnikov <esyr@openssl.org>
    Reviewed-by: Neil Horman <nhorman@openssl.org>
    Reviewed-by: Norbert Pocs <norbertp@openssl.org>
    MergeDate: Mon Jan 12 10:27:25 2026
    (Merged from https://github.com/openssl/openssl/pull/29538)

diff --git a/doc/man7/openssl-quic.pod b/doc/man7/openssl-quic.pod
index 75ca85d7ed..9d5cf9e079 100644
--- a/doc/man7/openssl-quic.pod
+++ b/doc/man7/openssl-quic.pod
@@ -899,6 +899,20 @@ that a call to L<SSL_handle_events(3)> is performed after the specified timeout

 =back

+=head1 WINDOWS APPLICATION NOTES
+
+QUIC protocol uses UDP sockets. The recvfrom() function on Windows may fail
+with C<WSAECONNRESET> error causing OpenSSL QUIC stack to enter permanent
+error, which prevents further communication over QUIC protocol. Applications
+should disable SIO_UDP_CONNRESET and SIO_UDP_NETRESET error notification
+on UDP sockets they pass to OpenSSL QUIC stack. More details can be found here:
+https://learn.microsoft.com/en-us/windows/win32/winsock/winsock-ioctls#sio_udp_connreset-opcode-setting-i-t3
+
+OpenSSL attempts to always disable SIO_UDP_CONNRESET and SIO_UDP_NETRESET
+on UDP sockets it receives from application, but no error is reported back
+if the respective C<WSAIoctl()> calls fail. Robust application should set those
+options itself so it can handle error notifications from C<WSAIoctl()> properly.
+
 =head1 SEE ALSO

 L<SSL_handle_events(3)>, L<SSL_get_event_timeout(3)>,
diff --git a/ssl/quic/quic_reactor.c b/ssl/quic/quic_reactor.c
index f54fbcdd96..a754f285bb 100644
--- a/ssl/quic/quic_reactor.c
+++ b/ssl/quic/quic_reactor.c
@@ -11,6 +11,12 @@
 #include "internal/thread_arch.h"
 #include <assert.h>

+#if defined(OPENSSL_SYS_WINDOWS)
+#include <winsock2.h>
+#include <mstcpip.h>
+#include <mswsock.h>
+#endif
+
 /*
  * Core I/O Reactor Framework
  * ==========================
@@ -69,6 +75,28 @@ void ossl_quic_reactor_cleanup(QUIC_REACTOR *rtor)
     }
 }

+#if defined(OPENSSL_SYS_WINDOWS)
+/*
+ * On Windows recvfrom() may return WSAECONNRESET when destination port
+ * used in preceding call to sendto() is no longer reachable. The reset
+ * error received from UDP socket takes the whole port down. This behavior
+ * must be suppressed for QUIC protocol so QUIC applications may rely on
+ * QUIC protocol itself to detect network failures.
+ */
+static void rtor_configure_winsock(BIO_POLL_DESCRIPTOR *bpd)
+{
+    BOOL bNewBehavior = FALSE;
+    DWORD dwBytesReturned = 0;
+
+    if (bpd->type == BIO_POLL_DESCRIPTOR_TYPE_SOCK_FD) {
+        WSAIoctl(bpd->value.fd, SIO_UDP_CONNRESET, &bNewBehavior,
+            sizeof(bNewBehavior), NULL, 0, &dwBytesReturned, NULL, NULL);
+        WSAIoctl(bpd->value.fd, SIO_UDP_NETRESET, &bNewBehavior,
+            sizeof(bNewBehavior), NULL, 0, &dwBytesReturned, NULL, NULL);
+    }
+}
+#endif
+
 void ossl_quic_reactor_set_poll_r(QUIC_REACTOR *rtor, const BIO_POLL_DESCRIPTOR *r)
 {
     if (r == NULL)
@@ -76,6 +104,10 @@ void ossl_quic_reactor_set_poll_r(QUIC_REACTOR *rtor, const BIO_POLL_DESCRIPTOR
     else
         rtor->poll_r = *r;

+#if defined(OPENSSL_SYS_WINDOWS)
+    rtor_configure_winsock(&rtor->poll_r);
+#endif
+
     rtor->can_poll_r
         = ossl_quic_reactor_can_support_poll_descriptor(rtor, &rtor->poll_r);
 }
@@ -87,6 +119,10 @@ void ossl_quic_reactor_set_poll_w(QUIC_REACTOR *rtor, const BIO_POLL_DESCRIPTOR
     else
         rtor->poll_w = *w;

+#if defined(OPENSSL_SYS_WINDOWS)
+    rtor_configure_winsock(&rtor->poll_w);
+#endif
+
     rtor->can_poll_w
         = ossl_quic_reactor_can_support_poll_descriptor(rtor, &rtor->poll_w);
 }