Skip to content

Commit

Permalink
udp: simplify socket state initialization
Browse files Browse the repository at this point in the history
  • Loading branch information
djc committed Aug 11, 2023
1 parent 20612b6 commit 560af2e
Show file tree
Hide file tree
Showing 5 changed files with 94 additions and 116 deletions.
7 changes: 2 additions & 5 deletions quinn-udp/src/fallback.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,14 @@ pub struct UdpSocketState {
}

impl UdpSocketState {
pub fn new() -> Self {
pub fn new(socket: UdpSocketRef<'_>) -> io::Result<Self> {
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<'_>,
Expand Down
183 changes: 85 additions & 98 deletions quinn-udp/src/unix.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -43,18 +42,95 @@ pub struct UdpSocketState {
}

impl UdpSocketState {
pub fn new() -> Self {
pub fn new(sock: UdpSockRef<'_>) -> io::Result<Self> {
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::<libc::in6_pktinfo>() as _) as usize };
}

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
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<usize, io::Error> {
Expand Down Expand Up @@ -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::<libc::in6_pktinfo>() as _) as usize };
}

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
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
Expand Down
14 changes: 5 additions & 9 deletions quinn-udp/src/windows.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Self> {
socket.0.set_nonblocking(true)?;
let addr = socket.0.local_addr()?;
let is_ipv6 = addr.as_socket_ipv6().is_some();
Expand Down Expand Up @@ -77,7 +70,10 @@ impl UdpSocketState {
}
}

Ok(())
let now = Instant::now();
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<usize, io::Error> {
Expand Down
3 changes: 1 addition & 2 deletions quinn/src/runtime/async_std.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,9 @@ impl Runtime for AsyncStdRuntime {
}

fn wrap_udp_socket(&self, sock: std::net::UdpSocket) -> io::Result<Arc<dyn AsyncUdpSocket>> {
udp::UdpSocketState::configure((&sock).into())?;
Ok(Arc::new(UdpSocket {
inner: udp::UdpSocketState::new((&sock).into())?,
io: Async::new(sock)?,
inner: udp::UdpSocketState::new(),
}))
}
}
Expand Down
3 changes: 1 addition & 2 deletions quinn/src/runtime/tokio.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,9 @@ impl Runtime for TokioRuntime {
}

fn wrap_udp_socket(&self, sock: std::net::UdpSocket) -> io::Result<Arc<dyn AsyncUdpSocket>> {
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(),
}))
}
}
Expand Down

0 comments on commit 560af2e

Please sign in to comment.