From 4f25f501ef4d009af9d3bef44d322c09c327b2df Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Fri, 11 Aug 2023 14:04:47 +0200 Subject: [PATCH] udp: simplify socket state initialization --- quinn-udp/src/fallback.rs | 7 +- quinn-udp/src/unix.rs | 183 +++++++++++++++------------------ quinn-udp/src/windows.rs | 20 +--- quinn/src/runtime/async_std.rs | 3 +- quinn/src/runtime/tokio.rs | 3 +- 5 files changed, 94 insertions(+), 122 deletions(-) diff --git a/quinn-udp/src/fallback.rs b/quinn-udp/src/fallback.rs index b3d600b15..ea1ccf585 100644 --- a/quinn-udp/src/fallback.rs +++ b/quinn-udp/src/fallback.rs @@ -18,17 +18,14 @@ pub struct UdpSocketState { } impl UdpSocketState { - pub fn new() -> Self { + pub fn new(socket: UdpSocketRef<'_>) -> io::Result { + socket.0.set_nonblocking(true)?; let now = Instant::now(); Self { last_send_error: Mutex::new(now.checked_sub(2 * IO_ERROR_LOG_INTERVAL).unwrap_or(now)), } } - pub fn configure(socket: UdpSockRef<'_>) -> io::Result<()> { - socket.0.set_nonblocking(true) - } - pub fn send( &self, socket: UdpSockRef<'_>, diff --git a/quinn-udp/src/unix.rs b/quinn-udp/src/unix.rs index 953da964a..92548dd17 100644 --- a/quinn-udp/src/unix.rs +++ b/quinn-udp/src/unix.rs @@ -1,8 +1,7 @@ #[cfg(not(any(target_os = "macos", target_os = "ios")))] use std::ptr; use std::{ - io, - io::IoSliceMut, + io::{self, IoSliceMut}, mem::{self, MaybeUninit}, net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6}, os::unix::io::AsRawFd, @@ -43,18 +42,95 @@ pub struct UdpSocketState { } impl UdpSocketState { - pub fn new() -> Self { + pub fn new(sock: UdpSockRef<'_>) -> io::Result { + let io = sock.0; + let mut cmsg_platform_space = 0; + if cfg!(target_os = "linux") || cfg!(target_os = "freebsd") || cfg!(target_os = "macos") { + cmsg_platform_space += + unsafe { libc::CMSG_SPACE(mem::size_of::() as _) as usize }; + } + + assert!( + CMSG_LEN + >= unsafe { libc::CMSG_SPACE(mem::size_of::() as _) as usize } + + cmsg_platform_space + ); + assert!( + mem::align_of::() <= mem::align_of::>(), + "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 + if is_ipv4 || !io.only_v6()? { + if let Err(err) = set_socket_option(&*io, libc::IPPROTO_IP, libc::IP_RECVTOS, OPTION_ON) + { + tracing::debug!("Ignoring error setting IP_RECVTOS on socket: {err:?}",); + } + } + + #[cfg(target_os = "linux")] + { + // 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_socket_option( + &*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_socket_option( + &*io, + libc::IPPROTO_IPV6, + libc::IPV6_MTU_DISCOVER, + libc::IP_PMTUDISC_PROBE, + )?; + } + } + #[cfg(any(target_os = "freebsd", target_os = "macos", target_os = "ios"))] + { + if is_ipv4 { + set_socket_option(&*io, libc::IPPROTO_IP, libc::IP_DONTFRAG, OPTION_ON)?; + } + } + #[cfg(any(target_os = "freebsd", target_os = "macos"))] + // IP_RECVDSTADDR == IP_SENDSRCADDR on FreeBSD + // macOS uses only IP_RECVDSTADDR, no IP_SENDSRCADDR on macOS + // 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_socket_option(&*io, libc::IPPROTO_IPV6, libc::IPV6_DONTFRAG, OPTION_ON)?; + } + let now = Instant::now(); - Self { + Ok(Self { last_send_error: Mutex::new(now.checked_sub(2 * IO_ERROR_LOG_INTERVAL).unwrap_or(now)), max_gso_segments: AtomicUsize::new(gso::max_gso_segments()), gro_segments: gro::gro_segments(), sendmsg_einval: AtomicBool::new(false), - } - } - - pub fn configure(sock: UdpSockRef<'_>) -> io::Result<()> { - init(sock.0) + }) } pub fn send(&self, socket: UdpSockRef<'_>, transmits: &[Transmit]) -> Result { @@ -101,95 +177,6 @@ impl UdpSocketState { } } -impl Default for UdpSocketState { - fn default() -> Self { - Self::new() - } -} - -fn init(io: SockRef<'_>) -> io::Result<()> { - let mut cmsg_platform_space = 0; - if cfg!(target_os = "linux") || cfg!(target_os = "freebsd") || cfg!(target_os = "macos") { - cmsg_platform_space += - unsafe { libc::CMSG_SPACE(mem::size_of::() as _) as usize }; - } - - assert!( - CMSG_LEN - >= unsafe { libc::CMSG_SPACE(mem::size_of::() as _) as usize } - + cmsg_platform_space - ); - assert!( - mem::align_of::() <= mem::align_of::>(), - "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 - if is_ipv4 || !io.only_v6()? { - if let Err(err) = set_socket_option(&*io, libc::IPPROTO_IP, libc::IP_RECVTOS, OPTION_ON) { - tracing::debug!("Ignoring error setting IP_RECVTOS on socket: {err:?}",); - } - } - - #[cfg(target_os = "linux")] - { - // 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_socket_option( - &*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_socket_option( - &*io, - libc::IPPROTO_IPV6, - libc::IPV6_MTU_DISCOVER, - libc::IP_PMTUDISC_PROBE, - )?; - } - } - #[cfg(any(target_os = "freebsd", target_os = "macos", target_os = "ios"))] - { - if is_ipv4 { - set_socket_option(&*io, libc::IPPROTO_IP, libc::IP_DONTFRAG, OPTION_ON)?; - } - } - #[cfg(any(target_os = "freebsd", target_os = "macos"))] - // IP_RECVDSTADDR == IP_SENDSRCADDR on FreeBSD - // macOS uses only IP_RECVDSTADDR, no IP_SENDSRCADDR on macOS - // 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_socket_option(&*io, libc::IPPROTO_IPV6, libc::IPV6_DONTFRAG, OPTION_ON)?; - } - - Ok(()) -} - #[cfg(not(any(target_os = "macos", target_os = "ios")))] fn send( #[allow(unused_variables)] // only used on Linux diff --git a/quinn-udp/src/windows.rs b/quinn-udp/src/windows.rs index 69036a4b7..5e2dfeb8b 100644 --- a/quinn-udp/src/windows.rs +++ b/quinn-udp/src/windows.rs @@ -17,14 +17,7 @@ pub struct UdpSocketState { } impl UdpSocketState { - pub fn new() -> Self { - let now = Instant::now(); - Self { - last_send_error: Mutex::new(now.checked_sub(2 * IO_ERROR_LOG_INTERVAL).unwrap_or(now)), - } - } - - pub fn configure(socket: UdpSockRef<'_>) -> io::Result<()> { + pub fn new(socket: UdpSockRef<'_>) -> io::Result { socket.0.set_nonblocking(true)?; let addr = socket.0.local_addr()?; let is_ipv6 = addr.as_socket_ipv6().is_some(); @@ -77,7 +70,10 @@ impl UdpSocketState { } } - Ok(()) + let now = Instant::now(); + Ok(Self { + last_send_error: Mutex::new(now.checked_sub(2 * IO_ERROR_LOG_INTERVAL).unwrap_or(now)), + }) } pub fn send(&self, socket: UdpSockRef<'_>, transmits: &[Transmit]) -> Result { @@ -153,12 +149,6 @@ impl UdpSocketState { } } -impl Default for UdpSocketState { - fn default() -> Self { - Self::new() - } -} - pub(crate) const BATCH_SIZE: usize = 1; #[inline] diff --git a/quinn/src/runtime/async_std.rs b/quinn/src/runtime/async_std.rs index e418cc6a0..5ac90c1b9 100644 --- a/quinn/src/runtime/async_std.rs +++ b/quinn/src/runtime/async_std.rs @@ -25,10 +25,9 @@ impl Runtime for AsyncStdRuntime { } fn wrap_udp_socket(&self, sock: std::net::UdpSocket) -> io::Result> { - udp::UdpSocketState::configure((&sock).into())?; Ok(Arc::new(UdpSocket { + inner: udp::UdpSocketState::new((&sock).into())?, io: Async::new(sock)?, - inner: udp::UdpSocketState::new(), })) } } diff --git a/quinn/src/runtime/tokio.rs b/quinn/src/runtime/tokio.rs index 0f6f57c0c..b31bff795 100644 --- a/quinn/src/runtime/tokio.rs +++ b/quinn/src/runtime/tokio.rs @@ -28,10 +28,9 @@ impl Runtime for TokioRuntime { } fn wrap_udp_socket(&self, sock: std::net::UdpSocket) -> io::Result> { - udp::UdpSocketState::configure((&sock).into())?; Ok(Arc::new(UdpSocket { + inner: udp::UdpSocketState::new((&sock).into())?, io: tokio::net::UdpSocket::from_std(sock)?, - inner: udp::UdpSocketState::new(), })) } }