/// QUIC-friendly UDP socket for Windows /// /// Unlike a standard Windows UDP socket, this allows ECN bits to be read and written. #[derive(Debug)] pubstruct UdpSocketState {
last_send_error: Mutex<Instant>,
max_gso_segments: AtomicUsize,
/// Whether the underlying Winsock provider supports IPv4 ECN socket options/control messages. /// /// Some environments (notably Wine/Proton) don't implement IP_RECVECN/IP_ECN. /// ECN is best-effort: when unsupported we continue without it.
ecn_v4_supported: bool,
socket.0.set_nonblocking(true)?; let addr = socket.0.local_addr()?; let is_ipv6 = addr.as_socket_ipv6().is_some(); let v6only = unsafe { letmut result: u32 = 0; letmut len = mem::size_of_val(&result) as i32; let rc = WinSock::getsockopt(
socket.0.as_raw_socket() as _,
WinSock::IPPROTO_IPV6,
WinSock::IPV6_V6ONLY as _,
&mut result as *mut _ as _,
&mut len,
); if rc == -1 { return Err(io::Error::last_os_error());
}
result != 0
}; let is_ipv4 = addr.as_socket_ipv4().is_some() || !v6only;
// We don't support old versions of Windows that do not enable access to `WSARecvMsg()` if WSARECVMSG_PTR.is_none() { return Err(io::Error::new(
io::ErrorKind::Unsupported, "network stack does not support WSARecvMsg function",
));
}
// ECN is best-effort on Windows: if the Winsock provider doesn't support these options // (common under Wine/Proton), we disable ECN and keep working. let is_ecn_unsupported = |e: &io::Error| {
matches!(
e.raw_os_error(),
Some(code) if code == WinSock::WSAENOPROTOOPT as i32
|| code == WinSock::WSAEOPNOTSUPP as i32
)
};
/// Sends a [`Transmit`] on the given socket. /// /// This function will only ever return errors of kind [`io::ErrorKind::WouldBlock`]. /// All other errors will be logged and converted to `Ok`. /// /// UDP transmission errors are considered non-fatal because higher-level protocols must /// employ retransmits and timeouts anyway in order to deal with UDP's unreliable nature. /// Thus, logging is most likely the only thing you can do with these errors. /// /// If you would like to handle these errors yourself, use [`UdpSocketState::try_send`] /// instead. pubfn send(&self, socket: UdpSockRef<'_>, transmit: &Transmit<'_>) -> io::Result<()> { match send(
socket,
transmit, self.ecn_v4_supported, self.ecn_v6_supported,
) {
Ok(()) => Ok(()),
Err(e) if e.kind() == io::ErrorKind::WouldBlock => Err(e),
Err(e) => {
log_sendmsg_error(&self.last_send_error, e, transmit);
Ok(())
}
}
}
/// Sends a [`Transmit`] on the given socket without any additional error handling. pubfn try_send(&self, socket: UdpSockRef<'_>, transmit: &Transmit<'_>) -> io::Result<()> {
send(
socket,
transmit, self.ecn_v4_supported, self.ecn_v6_supported,
)
}
pubfn recv(
&self,
socket: UdpSockRef<'_>,
bufs: &mut [IoSliceMut<'_>],
meta: &mut [RecvMeta],
) -> io::Result<usize> { let wsa_recvmsg_ptr = WSARECVMSG_PTR.expect("valid function pointer for WSARecvMsg");
// we cannot use [`socket2::MsgHdrMut`] as we do not have access to inner field which holds the WSAMSG letmut ctrl_buf = cmsg::Aligned([0; CMSG_LEN]); letmut source: WinSock::SOCKADDR_INET = unsafe { mem::zeroed() }; letmut data = WinSock::WSABUF {
buf: bufs[0].as_mut_ptr(),
len: bufs[0].len() as _,
};
let ctrl = WinSock::WSABUF {
buf: ctrl_buf.0.as_mut_ptr(),
len: ctrl_buf.0.len() as _,
};
letmut wsa_msg = WinSock::WSAMSG {
name: &mut source as *mut _ as *mut _,
namelen: mem::size_of_val(&source) as _,
lpBuffers: &mut data,
Control: ctrl,
dwBufferCount: 1,
dwFlags: 0,
};
letmut len = 0; unsafe { let rc = (wsa_recvmsg_ptr)(
socket.0.as_raw_socket() as usize,
&mut wsa_msg,
&mut len,
ptr::null_mut(),
None,
); if rc == -1 { return Err(io::Error::last_os_error());
}
}
let addr = unsafe { let (_, addr) = socket2::SockAddr::try_init(|addr_storage, len| {
*len = mem::size_of_val(&source) as _;
ptr::copy_nonoverlapping(&source, addr_storage as _, 1);
Ok(())
})?;
addr.as_socket()
};
let cmsg_iter = unsafe { cmsg::Iter::new(&wsa_msg) }; for cmsg in cmsg_iter { const UDP_COALESCED_INFO: i32 = WinSock::UDP_COALESCED_INFO as i32; // [header (len)][data][padding(len + sizeof(data))] -> [header][data][padding] match (cmsg.cmsg_level, cmsg.cmsg_type) {
(WinSock::IPPROTO_IP, WinSock::IP_PKTINFO) => { let pktinfo = unsafe { cmsg::decode::<WinSock::IN_PKTINFO, WinSock::CMSGHDR>(cmsg) }; // Addr is stored in big endian format let ip4 = Ipv4Addr::from(u32::from_be(unsafe { pktinfo.ipi_addr.S_un.S_addr }));
dst_ip = Some(ip4.into());
interface_index = Some(pktinfo.ipi_ifindex);
}
(WinSock::IPPROTO_IPV6, WinSock::IPV6_PKTINFO) => { let pktinfo = unsafe { cmsg::decode::<WinSock::IN6_PKTINFO, WinSock::CMSGHDR>(cmsg) }; // Addr is stored in big endian format
dst_ip = Some(IpAddr::from(unsafe { pktinfo.ipi6_addr.u.Byte }));
interface_index = Some(pktinfo.ipi6_ifindex);
}
(WinSock::IPPROTO_IP, WinSock::IP_ECN) => { // ECN is a C integer https://learn.microsoft.com/en-us/windows/win32/winsock/winsock-ecn
ecn_bits = unsafe { cmsg::decode::<c_int, WinSock::CMSGHDR>(cmsg) };
}
(WinSock::IPPROTO_IPV6, WinSock::IPV6_ECN) => { // ECN is a C integer https://learn.microsoft.com/en-us/windows/win32/winsock/winsock-ecn
ecn_bits = unsafe { cmsg::decode::<c_int, WinSock::CMSGHDR>(cmsg) };
}
(WinSock::IPPROTO_UDP, UDP_COALESCED_INFO) => { // Has type u32 (aka DWORD) per // https://learn.microsoft.com/en-us/windows/win32/winsock/ipproto-udp-socket-options
stride = unsafe { cmsg::decode::<u32, WinSock::CMSGHDR>(cmsg) };
}
_ => {}
}
}
meta[0] = RecvMeta {
len: len as usize,
stride: stride as usize,
addr: addr.unwrap(),
ecn: EcnCodepoint::from_bits(ecn_bits as u8),
dst_ip,
interface_index,
};
Ok(1)
}
/// The maximum amount of segments which can be transmitted if a platform /// supports Generic Send Offload (GSO). /// /// This is 1 if the platform doesn't support GSO. Subject to change if errors are detected /// while using GSO. #[inline] pubfn max_gso_segments(&self) -> usize { self.max_gso_segments.load(Ordering::Relaxed)
}
/// The number of segments to read when GRO is enabled. Used as a factor to /// compute the receive buffer size. /// /// Returns 1 if the platform doesn't support GRO. #[inline] pubfn gro_segments(&self) -> usize { // Arbitrary reasonable value inspired by Linux and msquic 64
}
/// Resize the send buffer of `socket` to `bytes` #[inline] pubfn set_send_buffer_size(&self, socket: UdpSockRef<'_>, bytes: usize) -> io::Result<()> {
socket.0.set_send_buffer_size(bytes)
}
/// Resize the receive buffer of `socket` to `bytes` #[inline] pubfn set_recv_buffer_size(&self, socket: UdpSockRef<'_>, bytes: usize) -> io::Result<()> {
socket.0.set_recv_buffer_size(bytes)
}
/// Get the size of the `socket` send buffer #[inline] pubfn send_buffer_size(&self, socket: UdpSockRef<'_>) -> io::Result<usize> {
socket.0.send_buffer_size()
}
/// Get the size of the `socket` receive buffer #[inline] pubfn recv_buffer_size(&self, socket: UdpSockRef<'_>) -> io::Result<usize> {
socket.0.recv_buffer_size()
}
fn send(
socket: UdpSockRef<'_>,
transmit: &Transmit<'_>,
ecn_v4_supported: bool,
ecn_v6_supported: bool,
) -> io::Result<()> { // we cannot use [`socket2::sendmsg()`] and [`socket2::MsgHdr`] as we do not have access // to the inner field which holds the WSAMSG letmut ctrl_buf = cmsg::Aligned([0; CMSG_LEN]); let daddr = socket2::SockAddr::from(transmit.destination);
letmut data = WinSock::WSABUF {
buf: transmit.contents.as_ptr() as *mut _,
len: transmit.contents.len() as _,
};
let ctrl = WinSock::WSABUF {
buf: ctrl_buf.0.as_mut_ptr(),
len: ctrl_buf.0.len() as _,
};
// Add control messages (ECN and PKTINFO) letmut encoder = unsafe { cmsg::Encoder::new(&mut wsa_msg) };
iflet Some(ip) = transmit.src_ip { let ip = std::net::SocketAddr::new(ip, 0); let ip = socket2::SockAddr::from(ip); match ip.family() {
WinSock::AF_INET => { let src_ip = unsafe { ptr::read(ip.as_ptr() as *const WinSock::SOCKADDR_IN) }; let pktinfo = WinSock::IN_PKTINFO {
ipi_addr: src_ip.sin_addr,
ipi_ifindex: 0,
};
encoder.push(WinSock::IPPROTO_IP, WinSock::IP_PKTINFO, pktinfo);
}
WinSock::AF_INET6 => { let src_ip = unsafe { ptr::read(ip.as_ptr() as *const WinSock::SOCKADDR_IN6) }; let pktinfo = WinSock::IN6_PKTINFO {
ipi6_addr: src_ip.sin6_addr,
ipi6_ifindex: unsafe { src_ip.Anonymous.sin6_scope_id },
};
encoder.push(WinSock::IPPROTO_IPV6, WinSock::IPV6_PKTINFO, pktinfo);
}
_ => { return Err(io::Error::from(io::ErrorKind::InvalidInput));
}
}
}
// True for IPv4 or IPv4-Mapped IPv6 let is_ipv4 = transmit.destination.is_ipv4()
|| matches!(transmit.destination.ip(), IpAddr::V6(addr) if addr.to_ipv4_mapped().is_some());
if (is_ipv4 && ecn_v4_supported) || (!is_ipv4 && ecn_v6_supported) { // ECN is a C integer https://learn.microsoft.com/en-us/windows/win32/winsock/winsock-ecn let ecn = transmit.ecn.map_or(0, |x| x as c_int); if is_ipv4 {
encoder.push(WinSock::IPPROTO_IP, WinSock::IP_ECN, ecn);
} else {
encoder.push(WinSock::IPPROTO_IPV6, WinSock::IPV6_ECN, ecn);
}
}
pub(crate) const BATCH_SIZE: usize = 1; // Enough to store max(IP_PKTINFO + IP_ECN, IPV6_PKTINFO + IPV6_ECN) + max(UDP_SEND_MSG_SIZE, UDP_COALESCED_INFO) bytes (header + data) and some extra margin const CMSG_LEN: usize = 128; const OPTION_ON: u32 = 1;
static WSARECVMSG_PTR: LazyLock<WinSock::LPFN_WSARECVMSG> = LazyLock::new(|| { let s = unsafe { WinSock::socket(WinSock::AF_INET as _, WinSock::SOCK_DGRAM as _, 0) }; if s == WinSock::INVALID_SOCKET {
debug!( "ignoring WSARecvMsg function pointer due to socket creation error: {}",
io::Error::last_os_error()
); return None;
}
// Safety: Option handles the NULL pointer with a None value let rc = unsafe {
WinSock::WSAIoctl(
s as _,
WinSock::SIO_GET_EXTENSION_FUNCTION_POINTER,
&guid as *const _ as *const _,
mem::size_of_val(&guid) as u32,
&mut wsa_recvmsg_ptr as *mut _ as *mut _,
mem::size_of_val(&wsa_recvmsg_ptr) as u32,
&mut len,
ptr::null_mut(),
None,
)
};
if rc == -1 {
debug!( "ignoring WSARecvMsg function pointer due to ioctl error: {}",
io::Error::last_os_error()
);
} elseif len as usize != mem::size_of::<WinSock::LPFN_WSARECVMSG>() {
debug!("ignoring WSARecvMsg function pointer due to pointer size mismatch");
wsa_recvmsg_ptr = None;
}
unsafe {
WinSock::closesocket(s);
}
wsa_recvmsg_ptr
});
fn max_gso_segments(socket: &impl AsRawSocket) -> usize { 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,
Err(_) => 1,
}
}
Messung V0.5 in Prozent
¤ Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.0.6Bemerkung:
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.