@@ -21,6 +21,8 @@ use std::{
2121use log:: { Level , log_enabled} ;
2222use neqo_common:: { Datagram , Tos , datagram, qdebug, qtrace} ;
2323use quinn_udp:: { EcnCodepoint , RecvMeta , Transmit , UdpSocketState } ;
24+ #[ cfg( windows) ]
25+ use windows:: Win32 :: Networking :: WinSock ;
2426
2527/// Receive buffer size
2628///
@@ -81,6 +83,12 @@ pub fn send_inner(
8183 ) ;
8284 return Ok ( ( ) ) ;
8385 }
86+ Err ( e) if is_enobufs ( & e) => {
87+ // On macOS/BSD, ENOBUFS means the NIC transmit queue is momentarily
88+ // full. The packet is already dropped by the kernel. Signal WouldBlock.
89+ qdebug ! ( "Interface send queue full (ENOBUFS), signaling WouldBlock: {e}" ) ;
90+ return Err ( io:: Error :: from ( io:: ErrorKind :: WouldBlock ) ) ;
91+ }
8492 e @ Err ( _) => return e,
8593 }
8694
@@ -96,27 +104,27 @@ pub fn send_inner(
96104 Ok ( ( ) )
97105}
98106
99- #[ expect(
100- clippy:: unnecessary_map_or,
101- reason = "Clippy ignores the #[cfg] attribute."
102- ) ]
103107fn is_emsgsize ( e : & io:: Error ) -> bool {
104- e. raw_os_error ( ) . map_or ( false , |e| {
108+ e. raw_os_error ( ) . is_some_and ( |e| {
105109 #[ cfg( unix) ]
106- {
107- e == libc:: EMSGSIZE
108- }
110+ return e == libc:: EMSGSIZE ;
109111 #[ cfg( windows) ]
110- {
111- e == windows:: Win32 :: Networking :: WinSock :: WSAEMSGSIZE . 0
112- // WSAEINVAL is returned when the Windows USO (UDP Segmentation Offload)
113- // segment size exceeds the supported limit.
114- || e == windows:: Win32 :: Networking :: WinSock :: WSAEINVAL . 0
115- }
112+ // WSAEINVAL is returned when the Windows USO (UDP Segmentation Offload)
113+ // segment size exceeds the supported limit.
114+ return e == WinSock :: WSAEMSGSIZE . 0 || e == WinSock :: WSAEINVAL . 0 ;
116115 #[ cfg( not( any( unix, windows) ) ) ]
117- {
118- false
119- }
116+ return false ;
117+ } )
118+ }
119+
120+ fn is_enobufs ( e : & io:: Error ) -> bool {
121+ e. raw_os_error ( ) . is_some_and ( |e| {
122+ #[ cfg( unix) ]
123+ return e == libc:: ENOBUFS ;
124+ #[ cfg( windows) ]
125+ return e == WinSock :: WSAENOBUFS . 0 ;
126+ #[ cfg( not( any( unix, windows) ) ) ]
127+ return false ;
120128 } )
121129}
122130
@@ -350,16 +358,18 @@ mod tests {
350358
351359 #[ test]
352360 #[ cfg( unix) ]
353- fn is_emsgsize_true_for_emsgsize ( ) {
354- let err = io:: Error :: from_raw_os_error ( libc:: EMSGSIZE ) ;
355- assert ! ( is_emsgsize( & err) ) ;
356- }
357-
358- #[ test]
359- #[ cfg( unix) ]
360- fn is_emsgsize_false_for_other_errors ( ) {
361- let err = io:: Error :: from_raw_os_error ( libc:: EAGAIN ) ;
362- assert ! ( !is_emsgsize( & err) ) ;
361+ fn is_emsgsize_and_is_enobufs_are_disjoint ( ) {
362+ let emsgsize = io:: Error :: from_raw_os_error ( libc:: EMSGSIZE ) ;
363+ assert ! ( is_emsgsize( & emsgsize) ) ;
364+ assert ! ( !is_enobufs( & emsgsize) ) ;
365+
366+ let enobufs = io:: Error :: from_raw_os_error ( libc:: ENOBUFS ) ;
367+ assert ! ( !is_emsgsize( & enobufs) ) ;
368+ assert ! ( is_enobufs( & enobufs) ) ;
369+
370+ let eagain = io:: Error :: from_raw_os_error ( libc:: EAGAIN ) ;
371+ assert ! ( !is_emsgsize( & eagain) ) ;
372+ assert ! ( !is_enobufs( & eagain) ) ;
363373 }
364374
365375 #[ test]
@@ -385,9 +395,10 @@ mod tests {
385395 }
386396
387397 #[ test]
388- fn is_emsgsize_false_for_non_os_error ( ) {
398+ fn is_emsgsize_and_is_enobufs_false_for_non_os_error ( ) {
389399 let err = io:: Error :: other ( "test error" ) ;
390400 assert ! ( !is_emsgsize( & err) ) ;
401+ assert ! ( !is_enobufs( & err) ) ;
391402 }
392403
393404 #[ test]
0 commit comments