#![cfg_attr(all(coverage_nightly, test), feature(coverage_attribute))] #![expect(
clippy::missing_errors_doc,
reason = "Functions simply delegate to tokio and quinn-udp."
)]
use std::{
array,
io::{self, IoSliceMut},
iter,
net::SocketAddr,
slice::{self, ChunksMut},
};
use log::{Level, log_enabled}; use neqo_common::{Datagram, Tos, datagram, qdebug, qtrace}; use quinn_udp::{EcnCodepoint, RecvMeta, Transmit, UdpSocketState}; #[cfg(windows)] use windows::Win32::Networking::WinSock;
/// Receive buffer size /// /// Fits a maximum size UDP datagram, or, on platforms with segmentation /// offloading, multiple smaller datagrams. const RECV_BUF_SIZE: usize = u16::MAX as usize;
/// The number of buffers to pass to the OS on [`Socket::recv`]. /// /// Platforms without segmentation offloading, i.e. platforms not able to read /// multiple datagrams into a single buffer, can benefit from using multiple /// buffers instead. /// /// Platforms with segmentation offloading have not shown performance /// improvements when additionally using multiple buffers. /// /// - Linux/Android: use segmentation offloading via GRO /// - Windows: use segmentation offloading via URO (caveat see <https://github.com/quinn-rs/quinn/issues/2041>) /// - Apple: no segmentation offloading available, use multiple buffers #[cfg(not(apple))] const NUM_BUFS: usize = 1; #[cfg(apple)] // Value approximated based on neqo-bin "Download" benchmark only. const NUM_BUFS: usize = 16;
/// A UDP receive buffer. pubstruct RecvBuf(Vec<Vec<u8>>);
match state.try_send(socket, &transmit) {
Ok(()) => {}
Err(e) if is_emsgsize(&e) => {
qdebug!( "Failed to send datagram of size {} bytes, in {} segments, each {} bytes, from {} to {}. PMTUD probe? Ignoring error: {e}",
d.data().len(),
d.num_datagrams(),
d.datagram_size().get(),
d.source(),
d.destination()
); return Ok(());
}
Err(e) if is_enobufs(&e) => { // The send queue is momentarily full. Don't map to WouldBlock: the // socket IS writable, so edge-triggered epoll/kqueue won't re-signal // and the send loop would hang. Drop the packet; QUIC will retransmit.
qdebug!("Interface send queue full (ENOBUFS), dropping packet: {e}"); return Ok(());
}
e @ Err(_) => return e,
}
qtrace!( "sent {} bytes, in {} segments, each {} bytes, from {} to {} ",
d.data().len(),
d.num_datagrams(),
d.datagram_size().get(),
d.source(),
d.destination(),
);
#[cfg(windows)] fn is_emsgsize(e: &io::Error) -> bool { // WSAEINVAL is returned when the Windows USO (UDP Segmentation Offload) // segment size exceeds the supported limit.
matches!(e.raw_os_error(), Some(c) if c == WinSock::WSAEMSGSIZE.0 || c == WinSock::WSAEINVAL.0)
}
let n = state.recv((&socket).into(), &mut iovs, &mut metas)?;
if log_enabled!(Level::Trace) { for meta in metas.iter().take(n) {
qtrace!( "received {} bytes, in {} segments, each {} bytes, from {} to {local_address}",
meta.len, if meta.stride == 0 { 0
} else {
meta.len.div_ceil(meta.stride)
},
meta.stride,
meta.addr,
);
}
}
pubstruct DatagramIter<'a> { /// The current buffer, containing zero or more datagrams, each sharing the /// same [`RecvMeta`].
current_buffer: Option<(RecvMeta, ChunksMut<'a, u8>)>, /// Remaining buffers, each containing zero or more datagrams, one /// [`RecvMeta`] per buffer.
remaining_buffers:
iter::Take<iter::Zip<array::IntoIter<RecvMeta, NUM_BUFS>, slice::IterMut<'a, Vec<u8>>>>, /// The local address of the UDP socket used to receive the datagrams.
local_address: SocketAddr,
}
impl<'a> Iterator for DatagramIter<'a> { type Item = Datagram<&'a mut [u8]>;
fn next(&mutself) -> Option<Self::Item> { loop { // Return the next datagram in the current buffer, if any. iflet Some((meta, d)) = self
.current_buffer
.as_mut()
.and_then(|(meta, ds)| ds.next().map(|d| (meta, d)))
{ return Some(Datagram::from_slice(
meta.addr, self.local_address,
meta.ecn.map(|n| Tos::from(n as u8)).unwrap_or_default(),
d,
));
}
// There are no more datagrams in the current buffer. Try promoting // one of the remaining buffers, if any, to be the current buffer. let Some((meta, buf)) = self.remaining_buffers.next() else { // Handled all buffers. No more datagrams. Iterator is empty. return None;
};
// Ignore empty datagrams. if meta.len == 0 || meta.stride == 0 {
qdebug!( "ignoring empty datagram from {} to {} len {} stride {}",
meta.addr, self.local_address,
meta.len,
meta.stride
); continue;
}
// Got another buffer. Let's chunk it into datagrams and return the // first datagram in the next loop iteration. self.current_buffer = Some((meta, buf[0..meta.len].chunks_mut(meta.stride)));
}
}
}
/// A wrapper around a UDP socket, sending and receiving [`Datagram`]s. pubstruct Socket<S> {
state: UdpSocketState,
inner: S,
}
impl<S: SocketRef> Socket<S> { /// Create a new [`Socket`] given a raw file descriptor managed externally. pubfn new(socket: S) -> Result<Self, io::Error> { let state = UdpSocketState::new((&socket).into())?;
Ok(Self {
state,
inner: socket,
})
}
/// Enable the Apple fast UDP datapath (`sendmsg_x`/`recvmsg_x`) for this /// socket. /// /// # Safety /// /// `sendmsg_x` and `recvmsg_x` are private Apple APIs. Quinn-udp resolves /// them at runtime via `dlsym` and falls back to standard `sendmsg`/`recvmsg` /// if they are unavailable, so this will not crash on unsupported OS versions. /// The `unsafe` contract is inherited from [`quinn_udp::UdpSocketState::set_apple_fast_path`]. #[cfg(apple)] pubunsafefn enable_apple_fast_path(&self) { // SAFETY: Caller ensures the APIs are available on this OS version. unsafe { self.state.set_apple_fast_path() }
}
/// Send a [`datagram::Batch`] on the given [`Socket`]. pubfn send(&self, d: &datagram::Batch) -> io::Result<()> {
send_inner(&self.state, (&self.inner).into(), d)
}
/// Returns the maximum number of GSO segments supported by this socket. pubfn max_gso_segments(&self) -> usize { self.state.max_gso_segments()
}
/// Receive a batch of [`Datagram`]s on the given [`Socket`], each /// set with the provided local address. pubfn recv<'a>(
&self,
local_address: SocketAddr,
recv_buf: &'a mut RecvBuf,
) -> Result<DatagramIter<'a>, io::Error> {
recv_inner(local_address, &self.state, &self.inner, recv_buf)
}
/// Whether transmitted datagrams might get fragmented by the IP layer /// /// Returns `false` on targets which employ e.g. the `IPV6_DONTFRAG` socket option. pubfn may_fragment(&self) -> bool { self.state.may_fragment()
}
}
#[cfg(test)] #[cfg_attr(coverage_nightly, coverage(off))] mod tests { #![allow(
clippy::allow_attributes,
clippy::unwrap_in_result,
reason = "OK in tests."
)] use std::{env, num::NonZeroUsize};
use neqo_common::{Dscp, Ecn};
usesuper::*;
fn socket() -> Result<Socket<std::net::UdpSocket>, io::Error> { let socket = Socket::new(std::net::UdpSocket::bind("127.0.0.1:0")?)?; // Reverse non-blocking flag set by `UdpSocketState` to make the test non-racy.
socket.inner.set_nonblocking(false)?;
Ok(socket)
}
#[test] fn handle_empty_datagram() -> Result<(), io::Error> { // quinn-udp doesn't support sending emtpy datagrams across all // platforms. Use `std` socket instead. See also // <https://github.com/quinn-rs/quinn/pull/2123>. let sender = std::net::UdpSocket::bind("127.0.0.1:0")?; let receiver = socket()?; let receiver_addr: SocketAddr = "127.0.0.1:0".parse().unwrap();
// Assert that the ECN is correct. // On Android API level <= 25 the IPv4 `IP_TOS` control message is // not supported and thus ECN bits can not be received. // On NetBSD and OpenBSD, this also fails, but the cause has not been looked into. if cfg!(target_os = "android")
&& env::var("API_LEVEL")
.ok()
.and_then(|v| v.parse::<u32>().ok())
.expect("API_LEVEL environment variable to be set on Android")
<= 25
|| cfg!(any(target_os = "netbsd", target_os = "openbsd"))
{
assert_eq!(
Ecn::default(),
Ecn::from(received_datagrams.next().unwrap().tos())
);
} else {
assert_eq!(
Ecn::from(datagram.tos()),
Ecn::from(received_datagrams.next().unwrap().tos())
);
}
Ok(())
}
let sender = socket()?; let receiver = socket()?; let receiver_addr: SocketAddr = "127.0.0.1:0".parse().unwrap();
let max_gso_segments = sender.max_gso_segments(); let msg = vec![0xAB; SEGMENT_SIZE * max_gso_segments]; let batch = datagram::Batch::new(
sender.inner.local_addr()?,
receiver.inner.local_addr()?,
Tos::from((Dscp::Le, Ecn::Ect0)),
NonZeroUsize::new(SEGMENT_SIZE).expect("SEGMENT_SIZE cannot be zero"),
msg,
);
sender.send(&batch)?;
// Allow for one GSO sendmsg to result in multiple GRO recvmmsg. letmut num_received = 0; letmut recv_buf = RecvBuf::default(); while num_received < max_gso_segments {
receiver
.recv(receiver_addr, &mut recv_buf)
.expect("receive to succeed")
.for_each(|d| {
assert_eq!(
SEGMENT_SIZE,
d.len(), "Expect received datagrams to have same length as sent datagrams"
);
num_received += 1;
});
}
Ok(())
}
#[test] fn send_ignore_emsgsize() -> Result<(), io::Error> { let sender = socket()?; // Use non-blocking socket to test for `WouldBlock` error. let receiver = Socket::new(std::net::UdpSocket::bind("127.0.0.1:0")?)?; let receiver_addr: SocketAddr = "127.0.0.1:0".parse().unwrap();
// Send oversized batch and expect `EMSGSIZE` error to be ignored. // // Use two segments to ensure quinn-udp's `effective_segment_size()` returns `Some`, // which sets `UDP_SEND_MSG_SIZE` on Windows. With a single segment, // `effective_segment_size()` returns `None`, and Windows silently truncates // the oversized datagram instead of returning `EMSGSIZE`. let segment_size = u16::MAX as usize + 1; let oversized_batch = datagram::Batch::new(
sender.inner.local_addr()?,
receiver.inner.local_addr()?,
Tos::from((Dscp::Le, Ecn::Ect1)),
NonZeroUsize::new(segment_size).unwrap(),
vec![0; segment_size * 2],
);
sender.send(&oversized_batch)?;
letmut recv_buf = RecvBuf::default(); match receiver.recv(receiver_addr, &mut recv_buf) {
Ok(_) => panic!("Expected an error, but received datagrams"),
Err(e) => assert_eq!(e.kind(), io::ErrorKind::WouldBlock),
}
// Now send a normal datagram to ensure that the socket is still usable. let normal_datagram = Datagram::new(
sender.inner.local_addr()?,
receiver.inner.local_addr()?,
Tos::from((Dscp::Le, Ecn::Ect1)),
b"Hello World!".to_vec(),
)
.into();
sender.send(&normal_datagram)?;
#[test] #[cfg(apple)] fn apple_fast_path() -> Result<(), io::Error> { let socket = socket()?; // SAFETY: Tests run on Apple OS versions that support sendmsg_x/recvmsg_x. unsafe {
socket.enable_apple_fast_path();
}
assert!(socket.max_gso_segments() > 1);
Ok(())
}
#[test] fn may_fragment_returns_bool() -> Result<(), io::Error> { let s = socket()?; // On platforms that set DONTFRAG (Linux, macOS), this should be false. // On other platforms it may be true. Either way it must not panic. let frag = s.may_fragment(); // On Linux and macOS, fragmentation is disabled via socket options. #[cfg(apple)]
assert!(!frag, "may_fragment should be false on this platform"); #[cfg(target_os = "linux")]
assert!(!frag, "may_fragment should be false on Linux"); #[cfg(not(any(apple, target_os = "linux")))] let _: bool = frag;
Ok(())
}
#[test] fn max_gso_segments_is_consistent() -> Result<(), io::Error> { let s = socket()?; let a = s.max_gso_segments(); let b = s.max_gso_segments();
assert_eq!(a, b, "max_gso_segments should be deterministic");
assert!(a >= 1);
Ok(())
}
}
Messung V0.5 in Prozent
¤ Dauer der Verarbeitung: 0.15 Sekunden
(vorverarbeitet am 2026-08-27)
¤
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.