@@ -24,6 +24,15 @@ use crate::{
2424#[ derive( Debug ) ]
2525pub struct UdpSocketState {
2626 last_send_error : Mutex < Instant > ,
27+
28+ /// Whether the underlying Winsock provider supports IPv4 ECN socket options/control messages.
29+ ///
30+ /// Some environments (notably Wine/Proton) don't implement IP_RECVECN/IP_ECN.
31+ /// ECN is best-effort: when unsupported we continue without it.
32+ ecn_v4_supported : bool ,
33+
34+ /// Whether the underlying Winsock provider supports IPv6 ECN socket options/control messages.
35+ ecn_v6_supported : bool ,
2736}
2837
2938impl UdpSocketState {
@@ -67,6 +76,20 @@ impl UdpSocketState {
6776 ) ) ;
6877 }
6978
79+ // ECN is best-effort on Windows: if the Winsock provider doesn't support these options
80+ // (common under Wine/Proton), we disable ECN and keep working.
81+ let is_ecn_unsupported = |e : & io:: Error | {
82+ matches ! (
83+ e. raw_os_error( ) ,
84+ Some ( code)
85+ if code == WinSock :: WSAENOPROTOOPT as i32
86+ || code == WinSock :: WSAEOPNOTSUPP as i32
87+ )
88+ } ;
89+
90+ let mut ecn_v4_supported = true ;
91+ let mut ecn_v6_supported = true ;
92+
7093 if is_ipv4 {
7194 set_socket_option (
7295 & * socket. 0 ,
@@ -81,12 +104,20 @@ impl UdpSocketState {
81104 WinSock :: IP_PKTINFO ,
82105 OPTION_ON ,
83106 ) ?;
84- set_socket_option (
107+
108+ if let Err ( e) = set_socket_option (
85109 & * socket. 0 ,
86110 WinSock :: IPPROTO_IP ,
87111 WinSock :: IP_RECVECN ,
88112 OPTION_ON ,
89- ) ?;
113+ ) {
114+ if is_ecn_unsupported ( & e) {
115+ ecn_v4_supported = false ;
116+ debug ! ( "quinn-udp: ECN disabled for IPv4 (IP_RECVECN unsupported): {e}" ) ;
117+ } else {
118+ return Err ( e) ;
119+ }
120+ }
90121 }
91122
92123 if is_ipv6 {
@@ -104,17 +135,26 @@ impl UdpSocketState {
104135 OPTION_ON ,
105136 ) ?;
106137
107- set_socket_option (
138+ if let Err ( e ) = set_socket_option (
108139 & * socket. 0 ,
109140 WinSock :: IPPROTO_IPV6 ,
110141 WinSock :: IPV6_RECVECN ,
111142 OPTION_ON ,
112- ) ?;
143+ ) {
144+ if is_ecn_unsupported ( & e) {
145+ ecn_v6_supported = false ;
146+ debug ! ( "quinn-udp: ECN disabled for IPv6 (IPV6_RECVECN unsupported): {e}" ) ;
147+ } else {
148+ return Err ( e) ;
149+ }
150+ }
113151 }
114152
115153 let now = Instant :: now ( ) ;
116154 Ok ( Self {
117155 last_send_error : Mutex :: new ( now. checked_sub ( 2 * IO_ERROR_LOG_INTERVAL ) . unwrap_or ( now) ) ,
156+ ecn_v4_supported,
157+ ecn_v6_supported,
118158 } )
119159 }
120160
@@ -152,7 +192,12 @@ impl UdpSocketState {
152192 /// If you would like to handle these errors yourself, use [`UdpSocketState::try_send`]
153193 /// instead.
154194 pub fn send ( & self , socket : UdpSockRef < ' _ > , transmit : & Transmit < ' _ > ) -> io:: Result < ( ) > {
155- match send ( socket, transmit) {
195+ match send (
196+ socket,
197+ transmit,
198+ self . ecn_v4_supported ,
199+ self . ecn_v6_supported ,
200+ ) {
156201 Ok ( ( ) ) => Ok ( ( ) ) ,
157202 Err ( e) if e. kind ( ) == io:: ErrorKind :: WouldBlock => Err ( e) ,
158203 Err ( e) => {
@@ -165,7 +210,12 @@ impl UdpSocketState {
165210
166211 /// Sends a [`Transmit`] on the given socket without any additional error handling.
167212 pub fn try_send ( & self , socket : UdpSockRef < ' _ > , transmit : & Transmit < ' _ > ) -> io:: Result < ( ) > {
168- send ( socket, transmit)
213+ send (
214+ socket,
215+ transmit,
216+ self . ecn_v4_supported ,
217+ self . ecn_v6_supported ,
218+ )
169219 }
170220
171221 pub fn recv (
@@ -325,7 +375,12 @@ impl UdpSocketState {
325375 }
326376}
327377
328- fn send ( socket : UdpSockRef < ' _ > , transmit : & Transmit < ' _ > ) -> io:: Result < ( ) > {
378+ fn send (
379+ socket : UdpSockRef < ' _ > ,
380+ transmit : & Transmit < ' _ > ,
381+ ecn_v4_supported : bool ,
382+ ecn_v6_supported : bool ,
383+ ) -> io:: Result < ( ) > {
329384 // we cannot use [`socket2::sendmsg()`] and [`socket2::MsgHdr`] as we do not have access
330385 // to the inner field which holds the WSAMSG
331386 let mut ctrl_buf = cmsg:: Aligned ( [ 0 ; CMSG_LEN ] ) ;
@@ -379,15 +434,18 @@ fn send(socket: UdpSockRef<'_>, transmit: &Transmit<'_>) -> io::Result<()> {
379434 }
380435 }
381436
382- // ECN is a C integer https://learn.microsoft.com/en-us/windows/win32/winsock/winsock-ecn
383- let ecn = transmit. ecn . map_or ( 0 , |x| x as c_int ) ;
384437 // True for IPv4 or IPv4-Mapped IPv6
385438 let is_ipv4 = transmit. destination . is_ipv4 ( )
386439 || matches ! ( transmit. destination. ip( ) , IpAddr :: V6 ( addr) if addr. to_ipv4_mapped( ) . is_some( ) ) ;
387- if is_ipv4 {
388- encoder. push ( WinSock :: IPPROTO_IP , WinSock :: IP_ECN , ecn) ;
389- } else {
390- encoder. push ( WinSock :: IPPROTO_IPV6 , WinSock :: IPV6_ECN , ecn) ;
440+
441+ if ( is_ipv4 && ecn_v4_supported) || ( !is_ipv4 && ecn_v6_supported) {
442+ // ECN is a C integer https://learn.microsoft.com/en-us/windows/win32/winsock/winsock-ecn
443+ let ecn = transmit. ecn . map_or ( 0 , |x| x as c_int ) ;
444+ if is_ipv4 {
445+ encoder. push ( WinSock :: IPPROTO_IP , WinSock :: IP_ECN , ecn) ;
446+ } else {
447+ encoder. push ( WinSock :: IPPROTO_IPV6 , WinSock :: IPV6_ECN , ecn) ;
448+ }
391449 }
392450
393451 // Segment size is a u32 https://learn.microsoft.com/en-us/windows/win32/api/ws2tcpip/nf-ws2tcpip-wsasetudpsendmessagesize
0 commit comments