From 6422682fa546d9ef2dd0201080021d83c51817a7 Mon Sep 17 00:00:00 2001 From: Jamil Bou Kheir Date: Mon, 29 Dec 2025 10:47:24 -0600 Subject: [PATCH] fix(windows): use WSASendMsg for GSO detection instead of setsockopt The previous GSO detection used setsockopt() to test if UDP_SEND_MSG_SIZE was supported. However, this can succeed even when the actual WSASendMsg() call with the control message fails (e.g., due to network adapter/driver limitations). This changes the detection to actually try sending a test packet with the GSO control message, which more accurately reflects whether GSO will work at runtime. Fixes WSAEINVAL (error 10022) on some Windows configurations when sending UDP packets with the segment size control message. --- quinn-udp/src/windows.rs | 72 ++++++++++++++++++++++++++++++++-------- 1 file changed, 59 insertions(+), 13 deletions(-) diff --git a/quinn-udp/src/windows.rs b/quinn-udp/src/windows.rs index ae1fb5033a..35ebb6ba49 100644 --- a/quinn-udp/src/windows.rs +++ b/quinn-udp/src/windows.rs @@ -8,7 +8,7 @@ use std::{ time::Instant, }; -use libc::{c_int, c_uint}; +use libc::c_int; use windows_sys::Win32::Networking::WinSock; use crate::{ @@ -495,21 +495,67 @@ static WSARECVMSG_PTR: LazyLock = LazyLock::new(|| { }); static MAX_GSO_SEGMENTS: LazyLock = LazyLock::new(|| { - let socket = match std::net::UdpSocket::bind("[::]:0") - .or_else(|_| std::net::UdpSocket::bind((Ipv4Addr::LOCALHOST, 0))) - { + let socket = match std::net::UdpSocket::bind((Ipv4Addr::LOCALHOST, 0)) { Ok(socket) => socket, Err(_) => return 1, }; - const GSO_SIZE: c_uint = 1500; - match set_socket_option( - &socket, - WinSock::IPPROTO_UDP, - WinSock::UDP_SEND_MSG_SIZE, - GSO_SIZE, - ) { - // Empirically found on Windows 11 x64 - Ok(()) => 512, + + socket.set_nonblocking(true).ok(); + + // Test GSO support by actually trying to send with the UDP_SEND_MSG_SIZE control message. + // Using setsockopt() alone is not sufficient as it may succeed even when WSASendMsg() + // with the control message fails (e.g., due to network adapter/driver limitations). + match send_test_packet(&socket) { + Ok(()) => 512, // Empirically found on Windows 11 x64 Err(_) => 1, } }); + +/// Send a test packet with GSO control message to verify WSASendMsg works with UDP_SEND_MSG_SIZE. +fn send_test_packet(socket: &std::net::UdpSocket) -> io::Result<()> { + let mut ctrl_buf = cmsg::Aligned([0u8; CMSG_LEN]); + // Send to localhost discard port - packet will be dropped but we can test the syscall + let dst = socket2::SockAddr::from(std::net::SocketAddr::from((Ipv4Addr::LOCALHOST, 9))); + let payload = [0u8; 1]; + + let mut data = WinSock::WSABUF { + buf: payload.as_ptr() as *mut _, + len: payload.len() as _, + }; + + let ctrl = WinSock::WSABUF { + buf: ctrl_buf.0.as_mut_ptr(), + len: ctrl_buf.0.len() as _, + }; + + let mut wsa_msg = WinSock::WSAMSG { + name: dst.as_ptr() as *mut _, + namelen: dst.len(), + lpBuffers: &mut data, + Control: ctrl, + dwBufferCount: 1, + dwFlags: 0, + }; + + let mut encoder = unsafe { cmsg::Encoder::new(&mut wsa_msg) }; + // Add GSO control message - this is what we're testing + encoder.push(WinSock::IPPROTO_UDP, WinSock::UDP_SEND_MSG_SIZE, 1u32); + encoder.finish(); + + let mut len = 0; + let rc = unsafe { + WinSock::WSASendMsg( + socket.as_raw_socket() as usize, + &wsa_msg, + 0, + &mut len, + ptr::null_mut(), + None, + ) + }; + + match rc { + 0 => Ok(()), + _ => Err(io::Error::last_os_error()), + } +}