Skip to content

Commit

Permalink
Fallback to not using ECN if IP_TOS is not supported
Browse files Browse the repository at this point in the history
On Linux <3.13 sendmsg/sendmmsg system calls
return EINVAL without sending anything if they encounter
an `IP_TOS` cmsg. To ensure we can still send
on such systems, remember if we got an EINVAL error
and switch to "fallback mode" in which we do not try
to transmit ECN at all and only use well-supported features.
  • Loading branch information
link2xt committed Mar 16, 2023
1 parent d31597f commit 3cf3e31
Show file tree
Hide file tree
Showing 2 changed files with 33 additions and 3 deletions.
21 changes: 20 additions & 1 deletion quinn-udp/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use std::os::unix::io::AsRawFd;
use std::os::windows::io::AsRawSocket;
use std::{
net::{IpAddr, Ipv6Addr, SocketAddr},
sync::atomic::{AtomicUsize, Ordering},
sync::atomic::{AtomicBool, AtomicUsize, Ordering},
time::{Duration, Instant},
};

Expand Down Expand Up @@ -37,6 +37,13 @@ pub const BATCH_SIZE: usize = imp::BATCH_SIZE;
pub struct UdpState {
max_gso_segments: AtomicUsize,
gro_segments: usize,

/// True if we have received EINVAL error from `sendmsg` or `sendmmsg` 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,
}

impl UdpState {
Expand All @@ -62,6 +69,18 @@ impl UdpState {
pub fn gro_segments(&self) -> usize {
self.gro_segments
}

/// Returns true if we previously got an EINVAL error from `sendmsg` or `sendmmsg` syscall.
#[inline]
pub fn sendmsg_einval(&self) -> bool {
self.sendmsg_einval.load(Ordering::Relaxed)
}

/// Sets the flag indicating we got EINVAL error from `sendmsg` or `sendmmsg` syscall.
#[inline]
pub fn set_sendmsg_einval(&self) {
self.sendmsg_einval.store(true, Ordering::Relaxed)
}
}

impl Default for UdpState {
Expand Down
15 changes: 13 additions & 2 deletions quinn-udp/src/unix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use std::{
mem::{self, MaybeUninit},
net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6},
os::unix::io::AsRawFd,
sync::atomic::AtomicUsize,
sync::atomic::{AtomicBool, AtomicUsize},
time::Instant,
};

Expand Down Expand Up @@ -189,6 +189,7 @@ fn send(
&mut iovecs[i],
&mut cmsgs[i],
encode_src_ip,
state.sendmsg_einval(),
);
}
let num_transmits = transmits.len().min(BATCH_SIZE);
Expand Down Expand Up @@ -221,6 +222,12 @@ fn send(
}
}

if e.raw_os_error() == Some(libc::EINVAL) {
// Some arguments to `sendmsg` are not supported.
// Switch to fallback mode.
state.set_sendmsg_einval();
}

// Other errors are ignored, since they will ususally be handled
// by higher level retransmits and timeouts.
// - PermissionDenied errors have been observed due to iptable rules.
Expand Down Expand Up @@ -459,6 +466,7 @@ pub fn udp_state() -> UdpState {
UdpState {
max_gso_segments: AtomicUsize::new(gso::max_gso_segments()),
gro_segments: gro::gro_segments(),
sendmsg_einval: AtomicBool::new(false),
}
}

Expand All @@ -472,6 +480,7 @@ fn prepare_msg(
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();
Expand All @@ -493,7 +502,9 @@ fn prepare_msg(
let mut encoder = unsafe { cmsg::Encoder::new(hdr) };
let ecn = transmit.ecn.map_or(0, |x| x as libc::c_int);
if transmit.destination.is_ipv4() {
encoder.push(libc::IPPROTO_IP, libc::IP_TOS, ecn as IpTosTy);
if !sendmsg_einval {
encoder.push(libc::IPPROTO_IP, libc::IP_TOS, ecn as IpTosTy);
}
} else {
encoder.push(libc::IPPROTO_IPV6, libc::IPV6_TCLASS, ecn);
}
Expand Down

0 comments on commit 3cf3e31

Please sign in to comment.