From 72ef0c0f166e774f05cb2bd59baf71cb62af6de8 Mon Sep 17 00:00:00 2001 From: Floris Bruynooghe Date: Mon, 27 May 2024 18:24:23 +0200 Subject: [PATCH] Support netbsd NetBSD does not support DONTFRAG or ECN on IPv4, but does for IPv6. It has recvmmsg. --- quinn-udp/src/cmsg/mod.rs | 4 +++ quinn-udp/src/cmsg/unix.rs | 5 +++ quinn-udp/src/lib.rs | 3 +- quinn-udp/src/unix.rs | 64 +++++++++++++++++++++++++++----------- quinn-udp/tests/tests.rs | 8 ++--- quinn/src/tests.rs | 1 + 6 files changed, 61 insertions(+), 24 deletions(-) diff --git a/quinn-udp/src/cmsg/mod.rs b/quinn-udp/src/cmsg/mod.rs index 271d2f860..531c3d25b 100644 --- a/quinn-udp/src/cmsg/mod.rs +++ b/quinn-udp/src/cmsg/mod.rs @@ -124,6 +124,10 @@ pub(crate) trait MsgHdr { fn cmsg_nxt_hdr(&self, cmsg: &Self::ControlMessage) -> *mut Self::ControlMessage; + /// Sets the number of control messages added to this `struct msghdr`. + /// + /// Note that this is a destructive operation and should only be done as a finalisation + /// step. fn set_control_len(&mut self, len: usize); fn control_len(&self) -> usize; diff --git a/quinn-udp/src/cmsg/unix.rs b/quinn-udp/src/cmsg/unix.rs index e77174c55..112bd5ebe 100644 --- a/quinn-udp/src/cmsg/unix.rs +++ b/quinn-udp/src/cmsg/unix.rs @@ -20,6 +20,11 @@ impl MsgHdr for libc::msghdr { fn set_control_len(&mut self, len: usize) { self.msg_controllen = len as _; + if len == 0 { + // netbsd is particular about this being a NULL pointer if there are no control + // messages. + self.msg_control = std::ptr::null_mut(); + } } fn control_len(&self) -> usize { diff --git a/quinn-udp/src/lib.rs b/quinn-udp/src/lib.rs index 2ae4eaf5f..a10b43f48 100644 --- a/quinn-udp/src/lib.rs +++ b/quinn-udp/src/lib.rs @@ -85,7 +85,8 @@ pub struct RecvMeta { pub ecn: Option, /// The destination IP address which was encoded in this datagram /// - /// Populated on platforms: Windows, Linux, Android, FreeBSD, OpenBSD, macOS, and iOS. + /// Populated on platforms: Windows, Linux, Android, FreeBSD, OpenBSD, NetBSD, macOS, + /// and iOS. pub dst_ip: Option, } diff --git a/quinn-udp/src/unix.rs b/quinn-udp/src/unix.rs index 2ca2005c3..a8d35ecca 100644 --- a/quinn-udp/src/unix.rs +++ b/quinn-udp/src/unix.rs @@ -21,14 +21,14 @@ use super::{ // Defined in netinet6/in6.h on OpenBSD, this is not yet exported by the libc crate // directly. See https://github.com/rust-lang/libc/issues/3704 for when we might be able to // rely on this from the libc crate. -#[cfg(target_os = "openbsd")] +#[cfg(any(target_os = "openbsd", target_os = "netbsd"))] const IPV6_DONTFRAG: libc::c_int = 62; -#[cfg(not(target_os = "openbsd"))] +#[cfg(not(any(target_os = "openbsd", target_os = "netbsd")))] const IPV6_DONTFRAG: libc::c_int = libc::IPV6_DONTFRAG; #[cfg(target_os = "freebsd")] type IpTosTy = libc::c_uchar; -#[cfg(not(target_os = "freebsd"))] +#[cfg(not(any(target_os = "freebsd", target_os = "netbsd")))] type IpTosTy = libc::c_int; /// Tokio-compatible UDP socket with some useful specializations. @@ -57,6 +57,7 @@ impl UdpSocketState { if cfg!(target_os = "linux") || cfg!(target_os = "freebsd") || cfg!(target_os = "openbsd") + || cfg!(target_os = "netbsd") || cfg!(target_os = "macos") || cfg!(target_os = "ios") || cfg!(target_os = "android") @@ -82,7 +83,7 @@ impl UdpSocketState { // 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(target_os = "openbsd"))] + #[cfg(not(any(target_os = "openbsd", target_os = "netbsd")))] if is_ipv4 || !io.only_v6()? { if let Err(err) = set_socket_option(&*io, libc::IPPROTO_IP, libc::IP_RECVTOS, OPTION_ON) { @@ -120,7 +121,7 @@ impl UdpSocketState { } #[cfg(any( target_os = "freebsd", - target_od = "openbsd", + target_os = "openbsd", target_os = "macos", target_os = "ios" ))] @@ -138,6 +139,7 @@ impl UdpSocketState { #[cfg(any( target_os = "freebsd", target_os = "openbsd", + target_os = "netbsd", target_os = "macos", target_os = "ios" ))] @@ -218,13 +220,23 @@ impl UdpSocketState { } /// Sets the flag indicating we got EINVAL error from `sendmsg` or `sendmmsg` syscall. - #[cfg(not(any(target_os = "macos", target_os = "ios", target_os = "openbsd")))] + #[cfg(not(any( + target_os = "macos", + target_os = "ios", + target_os = "openbsd", + target_os = "netbsd" + )))] fn set_sendmsg_einval(&self) { self.sendmsg_einval.store(true, Ordering::Relaxed) } } -#[cfg(not(any(target_os = "macos", target_os = "ios", target_os = "openbsd")))] +#[cfg(not(any( + target_os = "macos", + target_os = "ios", + target_os = "openbsd", + target_os = "netbsd" +)))] fn send( #[allow(unused_variables)] // only used on Linux state: &UdpSocketState, @@ -309,7 +321,12 @@ fn send( } } -#[cfg(any(target_os = "macos", target_os = "ios", target_os = "openbsd"))] +#[cfg(any( + target_os = "macos", + target_os = "ios", + target_os = "openbsd", + target_os = "netbsd" +))] fn send(state: &UdpSocketState, io: SockRef<'_>, transmit: &Transmit<'_>) -> io::Result<()> { let mut hdr: libc::msghdr = unsafe { mem::zeroed() }; let mut iov: libc::iovec = unsafe { mem::zeroed() }; @@ -321,8 +338,10 @@ fn send(state: &UdpSocketState, io: SockRef<'_>, transmit: &Transmit<'_>) -> io: &mut hdr, &mut iov, &mut ctrl, - // Only tested on macOS and iOS - cfg!(target_os = "macos") || cfg!(target_os = "ios") || cfg!(target_os = "openbsd"), + cfg!(target_os = "macos") + || cfg!(target_os = "ios") + || cfg!(target_os = "openbsd") + || cfg!(target_os = "netbsd"), state.sendmsg_einval(), ); let n = unsafe { libc::sendmsg(io.as_raw_fd(), &hdr, 0) }; @@ -426,7 +445,7 @@ unsafe fn recvmmsg_with_fallback( let flags = 0; let timeout = ptr::null_mut::(); - #[cfg(not(target_os = "freebsd"))] + #[cfg(not(any(target_os = "freebsd", target_os = "netbsd")))] { let ret = libc::syscall(libc::SYS_recvmmsg, sockfd, msgvec, vlen, flags, timeout) as libc::c_int; @@ -435,11 +454,13 @@ unsafe fn recvmmsg_with_fallback( } } - // libc on FreeBSD implements `recvmmsg` as a high-level abstraction over `recvmsg`, - // thus `SYS_recvmmsg` constant and direct system call do not exist - #[cfg(target_os = "freebsd")] + // libc on FreeBSD and NetBSD implement `recvmmsg` as a high-level abstraction over + // `recvmsg`, thus `SYS_recvmmsg` constant and direct system call do not exist + #[cfg(any(target_os = "freebsd", target_os = "netbsd"))] { - let ret = libc::recvmmsg(sockfd, msgvec, vlen as usize, flags, timeout) as libc::c_int; + #[cfg(target_os = "freebsd")] + let vlen = vlen as usize; + let ret = libc::recvmmsg(sockfd, msgvec, vlen, flags, timeout) as libc::c_int; if ret != -1 { return ret; } @@ -516,7 +537,10 @@ fn prepare_msg( || 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); + #[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); @@ -542,9 +566,10 @@ fn prepare_msg( } #[cfg(any( target_os = "freebsd", + target_os = "openbsd", + target_os = "netbsd", target_os = "macos", target_os = "ios", - target_os = "openbsd" ))] { if encode_src_ip { @@ -603,7 +628,7 @@ fn decode_recv( ecn_bits = cmsg::decode::(cmsg); }, // FreeBSD uses IP_RECVTOS here, and we can be liberal because cmsgs are opt-in. - #[cfg(not(target_os = "openbsd"))] + #[cfg(not(any(target_os = "openbsd", target_os = "netbsd")))] (libc::IPPROTO_IP, libc::IP_RECVTOS) => unsafe { ecn_bits = cmsg::decode::(cmsg); }, @@ -628,9 +653,10 @@ fn decode_recv( } #[cfg(any( target_os = "freebsd", + target_os = "openbsd", + target_os = "netbsd", target_os = "macos", target_os = "ios", - target_os = "openbsd" ))] (libc::IPPROTO_IP, libc::IP_RECVDSTADDR) => { let in_addr = unsafe { cmsg::decode::(cmsg) }; diff --git a/quinn-udp/tests/tests.rs b/quinn-udp/tests/tests.rs index fdeedbfc7..4c06c488f 100644 --- a/quinn-udp/tests/tests.rs +++ b/quinn-udp/tests/tests.rs @@ -1,4 +1,4 @@ -#[cfg(not(target_os = "openbsd"))] +#[cfg(not(any(target_os = "openbsd", target_os = "netbsd")))] use std::net::{SocketAddr, SocketAddrV4, SocketAddrV6}; use std::{ io::IoSliceMut, @@ -51,7 +51,7 @@ fn ecn_v6() { } #[test] -#[cfg(not(target_os = "openbsd"))] +#[cfg(not(any(target_os = "openbsd", target_os = "netbsd")))] fn ecn_v4() { let send = Socket::from(UdpSocket::bind("127.0.0.1:0").unwrap()); let recv = Socket::from(UdpSocket::bind("127.0.0.1:0").unwrap()); @@ -71,7 +71,7 @@ fn ecn_v4() { } #[test] -#[cfg(not(target_os = "openbsd"))] +#[cfg(not(any(target_os = "openbsd", target_os = "netbsd")))] fn ecn_v6_dualstack() { let recv = socket2::Socket::new( socket2::Domain::IPV6, @@ -114,7 +114,7 @@ fn ecn_v6_dualstack() { } #[test] -#[cfg(not(target_os = "openbsd"))] +#[cfg(not(any(target_os = "openbsd", target_os = "netbsd")))] fn ecn_v4_mapped_v6() { let send = socket2::Socket::new( socket2::Domain::IPV6, diff --git a/quinn/src/tests.rs b/quinn/src/tests.rs index 802509cbd..c9c7f768f 100755 --- a/quinn/src/tests.rs +++ b/quinn/src/tests.rs @@ -541,6 +541,7 @@ fn run_echo(args: EchoArgs) { if cfg!(target_os = "linux") || cfg!(target_os = "freebsd") || cfg!(target_os = "openbsd") + || cfg!(target_os = "netbsd") || cfg!(target_os = "macos") || cfg!(target_os = "windows") {