#[cfg(target_os = "freebsd")] type IpTosTy = libc::c_uchar; #[cfg(not(any(target_os = "freebsd", target_os = "netbsd")))] type IpTosTy = libc::c_int;
/// Tokio-compatible UDP socket with some useful specializations. /// /// Unlike a standard tokio UDP socket, this allows ECN bits to be read and written on some /// platforms. #[derive(Debug)] pubstruct UdpSocketState {
last_send_error: Mutex<Instant>,
max_gso_segments: AtomicUsize,
gro_segments: usize,
may_fragment: bool,
/// True if we have received EINVAL error from `sendmsg` system call at least once. /// /// If enabled, we assume that old kernel is used and switch to fallback mode. /// In particular, we do not use IP_TOS cmsg_type in this case, /// which is not supported on Linux <3.13 and results in not sending the UDP packet at all.
sendmsg_einval: AtomicBool,
/// Whether to use Apple's fast `sendmsg_x`/`recvmsg_x` APIs. /// /// These private APIs provide better performance but may not be available on all /// Apple OS versions. Callers must verify availability before enabling. #[cfg(apple_fast)]
apple_fast_path: AtomicBool,
}
assert!(
CMSG_LEN
>= unsafe { libc::CMSG_SPACE(mem::size_of::<libc::c_int>() as _) as usize }
+ cmsg_platform_space
);
assert!(
mem::align_of::<libc::cmsghdr>() <= mem::align_of::<cmsg::Aligned<[u8; 0]>>(), "control message buffers will be misaligned"
);
io.set_nonblocking(true)?;
let addr = io.local_addr()?; let is_ipv4 = addr.family() == libc::AF_INET as libc::sa_family_t;
// mac and ios do not support IP_RECVTOS on dual-stack sockets :( // older macos versions also don't have the flag and will error out if we don't ignore it #[cfg(not(any(
target_os = "openbsd",
target_os = "netbsd",
target_os = "dragonfly",
solarish
)))] if is_ipv4 || !io.only_v6()? { iflet Err(_err) =
set_socket_option(&*io, libc::IPPROTO_IP, libc::IP_RECVTOS, OPTION_ON)
{ crate::log::debug!("Ignoring error setting IP_RECVTOS on socket: {_err:?}");
}
}
letmut may_fragment = false; #[cfg(any(target_os = "linux", target_os = "android"))]
{ // opportunistically try to enable GRO. See gro::gro_segments(). let _ = set_socket_option(&*io, libc::SOL_UDP, libc::UDP_GRO, OPTION_ON);
// Forbid IPv4 fragmentation. Set even for IPv6 to account for IPv6 mapped IPv4 addresses. // Set `may_fragment` to `true` if this option is not supported on the platform.
may_fragment |= !set_socket_option_supported(
&*io,
libc::IPPROTO_IP,
libc::IP_MTU_DISCOVER,
libc::IP_PMTUDISC_PROBE,
)?;
if is_ipv4 {
set_socket_option(&*io, libc::IPPROTO_IP, libc::IP_PKTINFO, OPTION_ON)?;
} else { // Set `may_fragment` to `true` if this option is not supported on the platform.
may_fragment |= !set_socket_option_supported(
&*io,
libc::IPPROTO_IPV6,
libc::IPV6_MTU_DISCOVER,
libc::IPV6_PMTUDISC_PROBE,
)?;
}
} #[cfg(any(target_os = "freebsd", apple))]
{ if is_ipv4 { // Set `may_fragment` to `true` if this option is not supported on the platform.
may_fragment |= !set_socket_option_supported(
&*io,
libc::IPPROTO_IP,
libc::IP_DONTFRAG,
OPTION_ON,
)?;
}
} #[cfg(any(bsd, apple, solarish))] // IP_RECVDSTADDR == IP_SENDSRCADDR on FreeBSD // macOS uses only IP_RECVDSTADDR, no IP_SENDSRCADDR on macOS (the same on Solaris) // macOS also supports IP_PKTINFO
{ if is_ipv4 {
set_socket_option(&*io, libc::IPPROTO_IP, libc::IP_RECVDSTADDR, OPTION_ON)?;
}
}
// Options standardized in RFC 3542 if !is_ipv4 {
set_socket_option(&*io, libc::IPPROTO_IPV6, libc::IPV6_RECVPKTINFO, OPTION_ON)?;
set_socket_option(&*io, libc::IPPROTO_IPV6, libc::IPV6_RECVTCLASS, OPTION_ON)?; // Linux's IP_PMTUDISC_PROBE allows us to operate under interface MTU rather than the // kernel's path MTU guess, but actually disabling fragmentation requires this too. See // __ip6_append_data in ip6_output.c. // Set `may_fragment` to `true` if this option is not supported on the platform.
may_fragment |= !set_socket_option_supported(
&*io,
libc::IPPROTO_IPV6,
libc::IPV6_DONTFRAG,
OPTION_ON,
)?;
}
/// 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(self, socket.0, transmit) {
Ok(()) => Ok(()),
Err(e) if e.kind() == io::ErrorKind::WouldBlock => Err(e), // - EMSGSIZE is expected for MTU probes. Future work might be able to avoid // these by automatically clamping the MTUD upper bound to the interface MTU.
Err(e) if e.raw_os_error() == Some(libc::EMSGSIZE) => Ok(()),
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(self, socket.0, transmit)
}
/// 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 { self.gro_segments
}
/// 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()
}
/// Whether transmitted datagrams might get fragmented by the IP layer /// /// Returns `false` on targets which employ e.g. the `IPV6_DONTFRAG` socket option. #[inline] pubfn may_fragment(&self) -> bool { self.may_fragment
}
/// Returns true if we previously got an EINVAL error from `sendmsg` syscall. fn sendmsg_einval(&self) -> bool { self.sendmsg_einval.load(Ordering::Relaxed)
}
/// Sets the flag indicating we got EINVAL error from `sendmsg` syscall. #[cfg(not(any(apple, target_os = "openbsd", target_os = "netbsd")))] fn set_sendmsg_einval(&self) { self.sendmsg_einval.store(true, Ordering::Relaxed)
}
/// Enables Apple's fast UDP datapath using private `sendmsg_x`/`recvmsg_x` APIs. /// Once enabled, this also updates [`max_gso_segments`] to allow batched sends. /// /// # Safety /// /// These APIs may crash on unsupported OS versions, so callers must verify /// availability before enabling. /// /// [`max_gso_segments`]: Self::max_gso_segments #[cfg(apple_fast)] pubunsafefn set_apple_fast_path(&self) { self.apple_fast_path.store(true, Ordering::Relaxed); self.max_gso_segments.store(BATCH_SIZE, Ordering::Relaxed);
}
/// Returns whether Apple's fast UDP datapath is enabled for this socket. #[cfg(apple_fast)] pubfn is_apple_fast_path_enabled(&self) -> bool { self.apple_fast_path.load(Ordering::Relaxed)
}
}
#[cfg(not(any(apple, target_os = "openbsd", target_os = "netbsd")))] fn send( #[allow(unused_variables)] // only used on Linux
state: &UdpSocketState,
io: SockRef<'_>,
transmit: &Transmit<'_>,
) -> io::Result<()> { #[allow(unused_mut)] // only mutable on FreeBSD letmut encode_src_ip = true; #[cfg(target_os = "freebsd")]
{ let addr = io.local_addr()?; let is_ipv4 = addr.family() == libc::AF_INET as libc::sa_family_t; if is_ipv4 { iflet Some(socket) = addr.as_socket_ipv4() {
encode_src_ip = socket.ip() == &Ipv4Addr::UNSPECIFIED;
}
}
} letmut msg_hdr: libc::msghdr = unsafe { mem::zeroed() }; letmut iovec: libc::iovec = unsafe { mem::zeroed() }; letmut cmsgs = cmsg::Aligned([0u8; CMSG_LEN]); let dst_addr = socket2::SockAddr::from(transmit.destination);
prepare_msg(
transmit,
&dst_addr,
&mut msg_hdr,
&mut iovec,
&mut cmsgs,
encode_src_ip,
state.sendmsg_einval(),
);
loop { let n = unsafe { libc::sendmsg(io.as_raw_fd(), &msg_hdr, 0) };
if n >= 0 { return Ok(());
}
let e = io::Error::last_os_error(); match e.kind() { // Retry the transmission
io::ErrorKind::Interrupted => continue,
io::ErrorKind::WouldBlock => return Err(e),
_ => { // Some network adapters and drivers do not support GSO. Unfortunately, Linux // offers no easy way for us to detect this short of an EIO or sometimes EINVAL // when we try to actually send datagrams using it. #[cfg(any(target_os = "linux", target_os = "android"))] iflet Some(libc::EIO) | Some(libc::EINVAL) = e.raw_os_error() { // Prevent new transmits from being scheduled using GSO. Existing GSO transmits // may already be in the pipeline, so we need to tolerate additional failures. if state.max_gso_segments() > 1 { crate::log::info!( "`libc::sendmsg` failed with {e}; halting segmentation offload"
);
state
.max_gso_segments
.store(1, std::sync::atomic::Ordering::Relaxed);
}
}
// Some arguments to `sendmsg` are not supported. Switch to // fallback mode and retry if we haven't already. if e.raw_os_error() == Some(libc::EINVAL) && !state.sendmsg_einval() {
state.set_sendmsg_einval();
prepare_msg(
transmit,
&dst_addr,
&mut msg_hdr,
&mut iovec,
&mut cmsgs,
encode_src_ip,
state.sendmsg_einval(),
); continue;
}
let e = io::Error::last_os_error(); match e.kind() { // Retry receiving
io::ErrorKind::Interrupted => continue,
_ => return Err(e),
}
}; for i in0..(msg_count as usize) {
meta[i] = decode_recv(&names[i], &hdrs[i].msg_hdr, hdrs[i].msg_len as usize)?;
}
Ok(msg_count as usize)
}
/// 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> { letmut 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 // which control messages have been written to. To prevent reading // uninitialized memory, do not use `MaybeUninit` for `ctrls`, instead // initialize `ctrls` with `0`s. A control message of all `0`s is // automatically skipped by `libc::CMSG_NXTHDR`. letmut ctrls = [cmsg::Aligned([0u8; CMSG_LEN]); BATCH_SIZE]; letmut hdrs = unsafe { mem::zeroed::<[msghdr_x; BATCH_SIZE]>() }; let max_msg_count = bufs.len().min(BATCH_SIZE); for i in0..max_msg_count {
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) };
if n >= 0 { break n;
}
let e = io::Error::last_os_error(); match e.kind() { // Retry receiving
io::ErrorKind::Interrupted => continue,
_ => return Err(e),
}
}; for i in0..(msg_count as usize) {
meta[i] = decode_recv(&names[i], &hdrs[i], hdrs[i].msg_datalen as usize)?;
}
Ok(msg_count as usize)
}
#[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> { letmut name = MaybeUninit::<libc::sockaddr_storage>::uninit(); letmut ctrl = cmsg::Aligned(MaybeUninit::<[u8; CMSG_LEN]>::uninit()); letmut hdr = unsafe { mem::zeroed::<libc::msghdr>() };
prepare_recv(&mut bufs[0], &mut name, &an style='color:red'>mut ctrl, &mut hdr); let n = loop { let n = unsafe { libc::recvmsg(io.as_raw_fd(), &mut hdr, 0) };
if hdr.msg_flags & libc::MSG_TRUNC != 0 { continue;
}
if n >= 0 { break n;
}
let e = io::Error::last_os_error(); match e.kind() { // Retry receiving
io::ErrorKind::Interrupted => continue,
_ => return Err(e),
}
};
meta[0] = decode_recv(&name, &hdr, n as usize)?;
Ok(1)
}
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,
hdr: &mut libc::msghdr,
iov: &mut libc::iovec,
ctrl: &mut cmsg::Aligned<[u8; CMSG_LEN]>, #[allow(unused_variables)] // only used on FreeBSD & macOS
encode_src_ip: bool,
sendmsg_einval: bool,
) {
iov.iov_base = transmit.contents.as_ptr() as *const _ as *mut _;
iov.iov_len = transmit.contents.len();
// SAFETY: Casting the pointer to a mutable one is legal, // as sendmsg is guaranteed to not alter the mutable pointer // as per the POSIX spec. See the section on the sys/socket.h // header for details. The type is only mutable in the first // place because it is reused by recvmsg as well. 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 _; letmut encoder = unsafe { cmsg::Encoder::new(hdr) }; let ecn = transmit.ecn.map_or(0, |x| x as libc::c_int); // 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 { if !sendmsg_einval { #[cfg(not(target_os = "netbsd"))]
{
encoder.push(libc::IPPROTO_IP, libc::IP_TOS, ecn as IpTosTy);
}
}
} else {
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))] iflet Some(segment_size) = transmit.effective_segment_size() {
gso::set_segment_size(&mut encoder, segment_size as u16);
}
/// 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 _; letmut 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);
}
iflet 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>,
ctrl: &mut cmsg::Aligned<MaybeUninit<[u8; CMSG_LEN]>>,
hdr: &mut libc::msghdr,
) {
hdr.msg_name = name.as_mut_ptr() as _;
hdr.msg_namelen = mem::size_of::<libc::sockaddr_storage>() as _;
hdr.msg_iov = buf as *mut IoSliceMut<'_> as *mut libc::iovec;
hdr.msg_iovlen = 1;
hdr.msg_control = ctrl.0.as_mut_ptr() as _;
hdr.msg_controllen = CMSG_LEN as _;
hdr.msg_flags = 0;
}
/// Prepares an `msghdr_x` for receiving with `recvmsg_x`. #[cfg(apple_fast)] fn prepare_recv_x(
buf: &mut IoSliceMut<'_>,
name: &mut MaybeUninit<libc::sockaddr_storage>,
ctrl: &mut cmsg::Aligned<[u8; CMSG_LEN]>,
hdr: &mut msghdr_x,
) {
hdr.msg_name = name.as_mut_ptr() as _;
hdr.msg_namelen = mem::size_of::<libc::sockaddr_storage>() as _;
hdr.msg_iov = buf as *mut IoSliceMut<'_> as *mut libc::iovec;
hdr.msg_iovlen = 1;
hdr.msg_control = ctrl.0.as_mut_ptr() as _;
hdr.msg_controllen = CMSG_LEN as _;
hdr.msg_flags = 0;
hdr.msg_datalen = buf.len();
}
/// Metadata decoded from control messages struct ControlMetadata {
ecn_bits: u8,
dst_ip: Option<IpAddr>,
interface_index: Option<u32>,
stride: usize,
}
impl ControlMetadata { /// Decodes a control message and updates the metadata state fn decode(&mutself, cmsg: &libc::cmsghdr) { match (cmsg.cmsg_level, cmsg.cmsg_type) {
(libc::IPPROTO_IP, libc::IP_TOS) => unsafe { self.ecn_bits = cmsg::decode::<u8, libc::cmsghdr>(cmsg);
}, // FreeBSD uses IP_RECVTOS here, and we can be liberal because cmsgs are opt-in. #[cfg(not(any(
target_os = "openbsd",
target_os = "netbsd",
target_os = "dragonfly",
solarish
)))]
(libc::IPPROTO_IP, libc::IP_RECVTOS) => unsafe { self.ecn_bits = cmsg::decode::<u8, libc::cmsghdr>(cmsg);
},
(libc::IPPROTO_IPV6, libc::IPV6_TCLASS) => unsafe { // Temporary hack around broken macos ABI. Remove once upstream fixes it. // https://bugreport.apple.com/web/?problemID=48761855 #[allow(clippy::unnecessary_cast)] // cmsg.cmsg_len defined as size_t if cfg!(apple)
&& cmsg.cmsg_len as usize == libc::CMSG_LEN(mem::size_of::<u8>() as _) as usize
{ self.ecn_bits = cmsg::decode::<u8, libc::cmsghdr>(cmsg);
} else { self.ecn_bits = cmsg::decode::<libc::c_int, libc::cmsghdr>(cmsg) as u8;
}
}, #[cfg(any(target_os = "linux", target_os = "android"))]
(libc::IPPROTO_IP, libc::IP_PKTINFO) => { let pktinfo = unsafe { cmsg::decode::<libc::in_pktinfo, libc::cmsghdr>(cmsg) }; self.dst_ip = Some(IpAddr::V4(Ipv4Addr::from(
pktinfo.ipi_addr.s_addr.to_ne_bytes(),
))); self.interface_index = Some(pktinfo.ipi_ifindex as u32);
} #[cfg(any(bsd, apple))]
(libc::IPPROTO_IP, libc::IP_RECVDSTADDR) => { let in_addr = unsafe { cmsg::decode::<libc::in_addr, libc::cmsghdr>(cmsg) }; self.dst_ip = Some(IpAddr::V4(Ipv4Addr::from(in_addr.s_addr.to_ne_bytes())));
}
(libc::IPPROTO_IPV6, libc::IPV6_PKTINFO) => { let pktinfo = unsafe { cmsg::decode::<libc::in6_pktinfo, libc::cmsghdr>(cmsg) }; self.dst_ip = Some(IpAddr::V6(Ipv6Addr::from(pktinfo.ipi6_addr.s6_addr))); self.interface_index = Some(pktinfo.ipi6_ifindex as u32);
} #[cfg(any(target_os = "linux", target_os = "android"))]
(libc::SOL_UDP, libc::UDP_GRO) => unsafe { self.stride = cmsg::decode::<libc::c_int, libc::cmsghdr>(cmsg) as usize;
},
_ => {}
}
}
}
/// Decodes a `sockaddr_storage` into a `SocketAddr` fn decode_socket_addr(name: &libc::sockaddr_storage) -> io::Result<SocketAddr> { match libc::c_int::from(name.ss_family) {
libc::AF_INET => { // Safety: if the ss_family field is AF_INET then storage must be a sockaddr_in. let addr: &libc::sockaddr_in = unsafe { &*(name as *const _ as *const libc::sockaddr_in) };
Ok(SocketAddr::V4(SocketAddrV4::new(
Ipv4Addr::from(addr.sin_addr.s_addr.to_ne_bytes()),
u16::from_be(addr.sin_port),
)))
}
libc::AF_INET6 => { // Safety: if the ss_family field is AF_INET6 then storage must be a sockaddr_in6. let addr: &libc::sockaddr_in6 = unsafe { &*(name as *const _ as *const libc::sockaddr_in6) };
Ok(SocketAddr::V6(SocketAddrV6::new(
Ipv6Addr::from(addr.sin6_addr.s6_addr),
u16::from_be(addr.sin6_port),
addr.sin6_flowinfo,
addr.sin6_scope_id,
)))
}
f => Err(io::Error::other(format!( "expected AF_INET or AF_INET6, got {f}"
))),
}
}
/// Checks whether GSO support is available by checking the kernel version followed by setting /// the UDP_SEGMENT option on a socket pub(crate) fn max_gso_segments() -> usize { const GSO_SIZE: libc::c_int = 1500;
if !SUPPORTED_BY_CURRENT_KERNEL.get_or_init(supported_by_current_kernel) { return1;
}
// Avoid calling `supported_by_current_kernel` for each socket by using `OnceLock`. static SUPPORTED_BY_CURRENT_KERNEL: OnceLock<bool> = OnceLock::new();
fn supported_by_current_kernel() -> bool { let kernel_version_string = match kernel_version_string() {
Ok(kernel_version_string) => kernel_version_string,
Err(_e) => { crate::log::warn!("GSO disabled: uname returned {_e}"); returnfalse;
}
};
let Some(kernel_version) = KernelVersion::from_str(&kernel_version_string) else { crate::log::warn!( "GSO disabled: failed to parse kernel version ({kernel_version_string})"
); returnfalse;
};
if kernel_version < SUPPORTED_SINCE { crate::log::info!("GSO disabled: kernel too old ({kernel_version_string}); need 4.18+",); returnfalse;
}
true
}
fn kernel_version_string() -> io::Result<String> { letmut n = unsafe { mem::zeroed() }; let r = unsafe { libc::uname(&mut n) }; if r != 0 { return Err(io::Error::last_os_error());
}
Ok(unsafe {
CStr::from_ptr(n.release[..].as_ptr())
.to_string_lossy()
.into_owned()
})
}
let version = u8::from_str(split.next()?).ok()?; let major_revision = u8::from_str(split.next()?).ok()?;
Some(Self {
version,
major_revision,
})
}
}
#[cfg(test)] mod test { usesuper::*;
#[test] fn parse_current_kernel_version_release_string() { let release = kernel_version_string().unwrap();
KernelVersion::from_str(&release).unwrap();
}
#[test] fn parse_kernel_version_release_string() { // These are made up for the test
assert_eq!(
KernelVersion::from_str("4.14"),
Some(KernelVersion {
version: 4,
major_revision: 14
})
);
assert_eq!(
KernelVersion::from_str("4.18"),
Some(KernelVersion {
version: 4,
major_revision: 18
})
); // These were seen in the wild
assert_eq!(
KernelVersion::from_str("4.14.186-27095505"),
Some(KernelVersion {
version: 4,
major_revision: 14
})
);
assert_eq!(
KernelVersion::from_str("6.8.0-59-generic"),
Some(KernelVersion {
version: 6,
major_revision: 8
})
);
}
}
}
// On Apple platforms using the `sendmsg_x` call, UDP datagram segmentation is not // offloaded to the NIC or even the kernel, but instead done here in user space in // [`send`]) and then passed to the OS as individual `iovec`s (up to `BATCH_SIZE`). // The initial value is 1 (no batching); callers can enable batching via // `UdpSocketState::set_apple_fast_path()` which updates `max_gso_segments`. #[cfg(not(any(target_os = "linux", target_os = "android")))] mod gso { usesuper::*;
// As defined in net/ipv4/udp_offload.c // #define UDP_GRO_CNT_MAX 64 // // NOTE: this MUST be set to UDP_GRO_CNT_MAX to ensure that the receive buffer size // (get_max_udp_payload_size() * gro_segments()) is large enough to hold the largest GRO // list the kernel might potentially produce. See // https://github.com/quinn-rs/quinn/pull/1354. match set_socket_option(&socket, libc::SOL_UDP, libc::UDP_GRO, OPTION_ON) {
Ok(()) => 64,
Err(_) => 1,
}
}
}
/// Returns whether the given socket option is supported on the current platform /// /// Yields `Ok(true)` if the option was set successfully, `Ok(false)` if setting /// the option raised an `ENOPROTOOPT` or `EOPNOTSUPP` error, and `Err` for any other error. fn set_socket_option_supported(
socket: &impl AsRawFd,
level: libc::c_int,
name: libc::c_int,
value: libc::c_int,
) -> io::Result<bool> { match set_socket_option(socket, level, name, value) {
Ok(()) => Ok(true),
Err(err) if err.raw_os_error() == Some(libc::ENOPROTOOPT) => Ok(false),
Err(err) if err.raw_os_error() == Some(libc::EOPNOTSUPP) => Ok(false),
Err(err) => Err(err),
}
}
fn set_socket_option(
socket: &impl AsRawFd,
level: libc::c_int,
name: libc::c_int,
value: libc::c_int,
) -> io::Result<()> { let rc = unsafe {
libc::setsockopt(
socket.as_raw_fd(),
level,
name,
&value as *const _ as _,
mem::size_of_val(&value) as _,
)
};
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.