Skip to content
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 59 additions & 13 deletions quinn-udp/src/windows.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::{
Expand Down Expand Up @@ -495,21 +495,67 @@ static WSARECVMSG_PTR: LazyLock<WinSock::LPFN_WSARECVMSG> = LazyLock::new(|| {
});

static MAX_GSO_SEGMENTS: LazyLock<usize> = 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,
Comment on lines +498 to 500
Copy link

Copilot AI Dec 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The socket binding was changed from attempting IPv6 first with IPv4 fallback to only IPv4. This change could miss detecting IPv6-specific GSO support or fail on systems where IPv4 binding is unavailable. Consider restoring the original fallback pattern to maintain broader compatibility and more comprehensive GSO detection.

Suggested change
let socket = match std::net::UdpSocket::bind((Ipv4Addr::LOCALHOST, 0)) {
Ok(socket) => socket,
Err(_) => return 1,
let socket = match std::net::UdpSocket::bind((std::net::Ipv6Addr::LOCALHOST, 0)) {
Ok(socket) => socket,
Err(_) => match std::net::UdpSocket::bind((Ipv4Addr::LOCALHOST, 0)) {
Ok(socket) => socket,
Err(_) => return 1,
},

Copilot uses AI. Check for mistakes.
};
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();
Copy link

Copilot AI Dec 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Errors from set_nonblocking(true) are silently ignored with .ok(). If this call fails, the socket remains in blocking mode, which could cause the subsequent send_test_packet to block indefinitely if the discard service isn't responding. Consider propagating this error or at minimum handling it explicitly, e.g., by returning 1 (no GSO) if nonblocking mode cannot be enabled.

Suggested change
socket.set_nonblocking(true).ok();
if let Err(e) = socket.set_nonblocking(true) {
debug!(
"failed to set nonblocking mode for GSO detection socket: {}",
e
);
return 1;
}

Copilot uses AI. Check for mistakes.

// 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()),
Copy link

Copilot AI Dec 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When the socket is in nonblocking mode and the send operation cannot complete immediately, WSASendMsg returns a non-zero value with the error WSAEWOULDBLOCK (error code 10035). This error should not be interpreted as GSO being unsupported. Consider distinguishing between WSAEWOULDBLOCK (which should be treated as success for detection purposes) and actual GSO-related errors like WSAEINVAL.

Suggested change
_ => Err(io::Error::last_os_error()),
_ => {
let err = io::Error::last_os_error();
// When the socket is nonblocking, WSAEWOULDBLOCK means the send cannot
// complete immediately, not that GSO is unsupported. Treat this as
// success for detection purposes.
match err.raw_os_error() {
Some(code) if code == WinSock::WSAEWOULDBLOCK as i32 => Ok(()),
_ => Err(err),
}
}

Copilot uses AI. Check for mistakes.
}
}