refactor(quinn-udp): split fast&slow send/recv paths

Separate the fast path (`msghdr_x`-based) and slow path (`msghdr`-based)
implementations to prepare for runtime dispatch between them.

Broken out of #2463 as suggested by @djc.
This commit is contained in:
Lars Eggert
2025-12-09 15:18:25 +02:00
committed by Benjamin Saunders
parent 7ea60c10ff
commit 2964782b4f
2 changed files with 95 additions and 6 deletions
+1
View File
@@ -161,6 +161,7 @@ impl Transmit<'_> {
/// This case is actually quite common when splitting up a prepared GSO batch
/// again after GSO has been disabled because the last datagram in a GSO
/// batch is allowed to be smaller than the segment size.
#[cfg_attr(apple_fast, allow(dead_code))] // Used by prepare_msg, which is unused when apple_fast
fn effective_segment_size(&self) -> Option<usize> {
match self.segment_size? {
size if size >= self.contents.len() => None,
+94 -6
View File
@@ -397,7 +397,7 @@ fn send(state: &UdpSocketState, io: SockRef<'_>, transmit: &Transmit<'_>) -> io:
.enumerate()
.take(BATCH_SIZE)
{
prepare_msg(
prepare_msg_x(
&Transmit {
destination: transmit.destination,
ecn: transmit.ecn,
@@ -433,6 +433,12 @@ fn send(state: &UdpSocketState, io: SockRef<'_>, transmit: &Transmit<'_>) -> io:
#[cfg(any(target_os = "openbsd", target_os = "netbsd", apple_slow))]
fn send(state: &UdpSocketState, io: SockRef<'_>, transmit: &Transmit<'_>) -> io::Result<()> {
send_single(state, io, transmit)
}
#[cfg(any(target_os = "openbsd", target_os = "netbsd", apple))]
#[cfg_attr(apple_fast, allow(dead_code))] // Unused when apple_fast is enabled
fn send_single(state: &UdpSocketState, io: SockRef<'_>, transmit: &Transmit<'_>) -> io::Result<()> {
let mut hdr: libc::msghdr = unsafe { mem::zeroed() };
let mut iov: libc::iovec = unsafe { mem::zeroed() };
let mut ctrl = cmsg::Aligned([0u8; CMSG_LEN]);
@@ -523,7 +529,7 @@ fn recv(io: SockRef<'_>, bufs: &mut [IoSliceMut<'_>], meta: &mut [RecvMeta]) ->
let mut hdrs = unsafe { mem::zeroed::<[msghdr_x; BATCH_SIZE]>() };
let max_msg_count = bufs.len().min(BATCH_SIZE);
for i in 0..max_msg_count {
prepare_recv(&mut bufs[i], &mut names[i], &mut ctrls[i], &mut hdrs[i]);
prepare_recv_x(&mut bufs[i], &mut names[i], &mut ctrls[i], &mut hdrs[i]);
}
let msg_count = loop {
let n = unsafe { recvmsg_x(io.as_raw_fd(), hdrs.as_mut_ptr(), max_msg_count as _, 0) };
@@ -553,6 +559,22 @@ fn recv(io: SockRef<'_>, bufs: &mut [IoSliceMut<'_>], meta: &mut [RecvMeta]) ->
apple_slow
))]
fn recv(io: SockRef<'_>, bufs: &mut [IoSliceMut<'_>], meta: &mut [RecvMeta]) -> io::Result<usize> {
recv_single(io, bufs, meta)
}
#[cfg(any(
target_os = "openbsd",
target_os = "netbsd",
target_os = "dragonfly",
solarish,
apple
))]
#[cfg_attr(apple_fast, allow(dead_code))] // Unused when apple_fast is enabled
fn recv_single(
io: SockRef<'_>,
bufs: &mut [IoSliceMut<'_>],
meta: &mut [RecvMeta],
) -> io::Result<usize> {
let mut name = MaybeUninit::<libc::sockaddr_storage>::uninit();
let mut ctrl = cmsg::Aligned(MaybeUninit::<[u8; CMSG_LEN]>::uninit());
let mut hdr = unsafe { mem::zeroed::<libc::msghdr>() };
@@ -581,11 +603,11 @@ fn recv(io: SockRef<'_>, bufs: &mut [IoSliceMut<'_>], meta: &mut [RecvMeta]) ->
const CMSG_LEN: usize = 88;
#[cfg_attr(apple_fast, allow(dead_code))] // Unused when apple_fast is enabled
fn prepare_msg(
transmit: &Transmit<'_>,
dst_addr: &socket2::SockAddr,
#[cfg(not(apple_fast))] hdr: &mut libc::msghdr,
#[cfg(apple_fast)] hdr: &mut msghdr_x,
hdr: &mut libc::msghdr,
iov: &mut libc::iovec,
ctrl: &mut cmsg::Aligned<[u8; CMSG_LEN]>,
#[allow(unused_variables)] // only used on FreeBSD & macOS
@@ -625,6 +647,10 @@ fn prepare_msg(
encoder.push(libc::IPPROTO_IPV6, libc::IPV6_TCLASS, ecn);
}
// On apple_fast, prepare_msg is only compiled for send_single (fallback path), while the main
// send path uses prepare_msg_x with msghdr_x. gso::set_segment_size has a different signature
// when apple_fast is enabled, and it's a no-op on non-Linux platforms anyway.
#[cfg(not(apple_fast))]
if let Some(segment_size) = transmit.effective_segment_size() {
gso::set_segment_size(&mut encoder, segment_size as u16);
}
@@ -668,7 +694,67 @@ fn prepare_msg(
encoder.finish();
}
#[cfg(not(apple_fast))]
/// Prepares an `msghdr_x` for use with `sendmsg_x`.
#[cfg(apple_fast)]
fn prepare_msg_x(
transmit: &Transmit<'_>,
dst_addr: &socket2::SockAddr,
hdr: &mut msghdr_x,
iov: &mut libc::iovec,
ctrl: &mut cmsg::Aligned<[u8; CMSG_LEN]>,
#[allow(unused_variables)] encode_src_ip: bool,
sendmsg_einval: bool,
) {
iov.iov_base = transmit.contents.as_ptr() as *const _ as *mut _;
iov.iov_len = transmit.contents.len();
let name = dst_addr.as_ptr() as *mut libc::c_void;
let namelen = dst_addr.len();
hdr.msg_name = name as *mut _;
hdr.msg_namelen = namelen;
hdr.msg_iov = iov;
hdr.msg_iovlen = 1;
hdr.msg_control = ctrl.0.as_mut_ptr() as _;
hdr.msg_controllen = CMSG_LEN as _;
let mut encoder = unsafe { cmsg::Encoder::new(hdr) };
let ecn = transmit.ecn.map_or(0, |x| x as libc::c_int);
let is_ipv4 = transmit.destination.is_ipv4()
|| matches!(transmit.destination.ip(), IpAddr::V6(addr) if addr.to_ipv4_mapped().is_some());
if is_ipv4 {
if !sendmsg_einval {
encoder.push(libc::IPPROTO_IP, libc::IP_TOS, ecn as IpTosTy);
}
} else {
encoder.push(libc::IPPROTO_IPV6, libc::IPV6_TCLASS, ecn);
}
if let Some(ip) = &transmit.src_ip {
match ip {
IpAddr::V4(v4) => {
if encode_src_ip {
let addr = libc::in_addr {
s_addr: u32::from_ne_bytes(v4.octets()),
};
encoder.push(libc::IPPROTO_IP, libc::IP_RECVDSTADDR, addr);
}
}
IpAddr::V6(v6) => {
let pktinfo = libc::in6_pktinfo {
ipi6_ifindex: 0,
ipi6_addr: libc::in6_addr {
s6_addr: v6.octets(),
},
};
encoder.push(libc::IPPROTO_IPV6, libc::IPV6_PKTINFO, pktinfo);
}
}
}
encoder.finish();
}
#[cfg_attr(apple_fast, allow(dead_code))] // Unused when apple_fast is enabled
fn prepare_recv(
buf: &mut IoSliceMut<'_>,
name: &mut MaybeUninit<libc::sockaddr_storage>,
@@ -684,8 +770,9 @@ fn prepare_recv(
hdr.msg_flags = 0;
}
/// Prepares an `msghdr_x` for receiving with `recvmsg_x`.
#[cfg(apple_fast)]
fn prepare_recv(
fn prepare_recv_x(
buf: &mut IoSliceMut<'_>,
name: &mut MaybeUninit<libc::sockaddr_storage>,
ctrl: &mut cmsg::Aligned<[u8; CMSG_LEN]>,
@@ -1006,6 +1093,7 @@ mod gso {
}
}
#[cfg_attr(apple_fast, allow(dead_code))] // Unused when apple_fast is enabled
pub(super) fn set_segment_size(
#[cfg(not(apple_fast))] _encoder: &mut cmsg::Encoder<'_, libc::msghdr>,
#[cfg(apple_fast)] _encoder: &mut cmsg::Encoder<'_, msghdr_x>,