Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
2 changes: 1 addition & 1 deletion quinn-udp/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ default = ["tracing", "tracing-log"]
# Configure `tracing` to log events via `log` if no `tracing` subscriber exists.
tracing-log = ["tracing/log"]
log = ["dep:log"]
# Use private Apple APIs to send multiple packets in a single syscall.
# Support private Apple APIs to send multiple packets in a single syscall.
fast-apple-datapath = []

[dependencies]
Expand Down
21 changes: 21 additions & 0 deletions quinn-udp/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,27 @@ impl EcnCodepoint {
}
}

/// Pre-sets Apple fast path availability from external code.
///
/// Must be called before any socket operations that would trigger the internal
/// probe. Returns the effective value after the set attempt: if already
/// initialized, returns the existing value; otherwise returns the newly set value.
///
/// # Example
///
/// ```c
/// // In C++ code:
/// extern "C" bool quinn_udp_set_apple_fast_path_available(bool available);
///
/// bool probeResult = PerformAppleFastDatapathProbe();
/// bool effective = quinn_udp_set_apple_fast_path_available(probeResult);
/// ```
#[cfg(apple_fast)]
#[no_mangle]
pub extern "C" fn quinn_udp_set_apple_fast_path_available(available: bool) -> bool {
imp::set_apple_fast_path_available(available)
}

#[cfg(test)]
mod tests {
use std::net::Ipv4Addr;
Expand Down
161 changes: 152 additions & 9 deletions quinn-udp/src/unix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,20 @@ fn send(

#[cfg(apple_fast)]
fn send(state: &UdpSocketState, io: SockRef<'_>, transmit: &Transmit<'_>) -> io::Result<()> {
if probe::is_fast_path_available() {
send_via_sendmsg_x(state, io, transmit)
} else {
send_single(state, io, transmit)
}
}

/// Send using the fast `sendmsg_x` API.
#[cfg(apple_fast)]
fn send_via_sendmsg_x(
state: &UdpSocketState,
io: SockRef<'_>,
transmit: &Transmit<'_>,
) -> io::Result<()> {
let mut hdrs = unsafe { mem::zeroed::<[msghdr_x; BATCH_SIZE]>() };
let mut iovs = unsafe { mem::zeroed::<[libc::iovec; BATCH_SIZE]>() };
let mut ctrls = [cmsg::Aligned([0u8; CMSG_LEN]); BATCH_SIZE];
Expand All @@ -397,7 +411,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,
Expand Down Expand Up @@ -433,6 +447,11 @@ 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))]
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]);
Expand Down Expand Up @@ -512,6 +531,20 @@ fn recv(io: SockRef<'_>, bufs: &mut [IoSliceMut<'_>], meta: &mut [RecvMeta]) ->

#[cfg(apple_fast)]
fn recv(io: SockRef<'_>, bufs: &mut [IoSliceMut<'_>], meta: &mut [RecvMeta]) -> io::Result<usize> {
if probe::is_fast_path_available() {
recv_via_recvmsg_x(io, bufs, meta)
} else {
recv_single(io, bufs, meta)
}
}

/// Receive using the fast `recvmsg_x` API.
#[cfg(apple_fast)]
fn recv_via_recvmsg_x(
io: SockRef<'_>,
bufs: &mut [IoSliceMut<'_>],
meta: &mut [RecvMeta],
) -> io::Result<usize> {
let mut names = [MaybeUninit::<libc::sockaddr_storage>::uninit(); BATCH_SIZE];
// MacOS 10.15 `recvmsg_x` does not override the `msghdr_x`
// `msg_controllen`. Thus, after the call to `recvmsg_x`, one does not know
Expand All @@ -523,7 +556,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) };
Expand Down Expand Up @@ -553,6 +586,21 @@ 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
))]
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>() };
Expand Down Expand Up @@ -584,8 +632,7 @@ const CMSG_LEN: usize = 88;
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
Expand Down Expand Up @@ -668,7 +715,66 @@ 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();
}

fn prepare_recv(
buf: &mut IoSliceMut<'_>,
name: &mut MaybeUninit<libc::sockaddr_storage>,
Expand All @@ -684,8 +790,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]>,
Expand Down Expand Up @@ -998,7 +1105,11 @@ mod gso {
pub(super) fn max_gso_segments() -> usize {
#[cfg(apple_fast)]
{
BATCH_SIZE
if probe::is_fast_path_available() {
BATCH_SIZE
} else {
1
}
}
#[cfg(not(apple_fast))]
{
Expand All @@ -1007,8 +1118,7 @@ mod gso {
}

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>,
_encoder: &mut cmsg::Encoder<'_, libc::msghdr>,
_segment_size: u16,
) {
}
Expand Down Expand Up @@ -1087,3 +1197,36 @@ mod gro {
1
}
}

/// Fast path availability for Apple's private `sendmsg_x`/`recvmsg_x` APIs.
#[cfg(apple_fast)]
mod probe {
use std::sync::OnceLock;

/// Fast path availability flag. Defaults to `false` (disabled).
static FAST_PATH_AVAILABLE: OnceLock<bool> = OnceLock::new();

/// Returns `true` if the fast path has been enabled.
pub(super) fn is_fast_path_available() -> bool {
*FAST_PATH_AVAILABLE.get_or_init(|| false)
}

/// Sets the fast path availability flag (can only be set once).
pub(super) fn set_fast_path_available(available: bool) -> bool {
let _ = FAST_PATH_AVAILABLE.set(available);
is_fast_path_available()
}
}

/// Sets whether Apple's fast UDP datapath should be used.
///
/// On Apple platforms, quinn-udp can use private `sendmsg_x`/`recvmsg_x` APIs
/// for better performance. These APIs may crash on unsupported OS versions,
/// so callers must verify availability before enabling.
///
/// This can only be set once; subsequent calls have no effect. Returns the
/// effective value after the call.
#[cfg(apple_fast)]
pub fn set_apple_fast_path_available(available: bool) -> bool {
probe::set_fast_path_available(available)
}
Loading