Skip to content

Commit 11b34a7

Browse files
authored
Fallback to not using ECN if IP_TOS is not supported (#1516)
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.
1 parent d31597f commit 11b34a7

File tree

2 files changed

+38
-4
lines changed

2 files changed

+38
-4
lines changed

quinn-udp/src/lib.rs

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use std::os::unix::io::AsRawFd;
55
use std::os::windows::io::AsRawSocket;
66
use std::{
77
net::{IpAddr, Ipv6Addr, SocketAddr},
8-
sync::atomic::{AtomicUsize, Ordering},
8+
sync::atomic::{AtomicBool, AtomicUsize, Ordering},
99
time::{Duration, Instant},
1010
};
1111

@@ -37,6 +37,14 @@ pub const BATCH_SIZE: usize = imp::BATCH_SIZE;
3737
pub struct UdpState {
3838
max_gso_segments: AtomicUsize,
3939
gro_segments: usize,
40+
41+
/// True if we have received EINVAL error from `sendmsg` or `sendmmsg` system call at least once.
42+
///
43+
/// If enabled, we assume that old kernel is used and switch to fallback mode.
44+
/// In particular, we do not use IP_TOS cmsg_type in this case,
45+
/// which is not supported on Linux <3.13 and results in not sending the UDP packet at all.
46+
#[cfg(not(windows))]
47+
sendmsg_einval: AtomicBool,
4048
}
4149

4250
impl UdpState {
@@ -62,6 +70,20 @@ impl UdpState {
6270
pub fn gro_segments(&self) -> usize {
6371
self.gro_segments
6472
}
73+
74+
/// Returns true if we previously got an EINVAL error from `sendmsg` or `sendmmsg` syscall.
75+
#[inline]
76+
#[cfg(not(windows))]
77+
pub fn sendmsg_einval(&self) -> bool {
78+
self.sendmsg_einval.load(Ordering::Relaxed)
79+
}
80+
81+
/// Sets the flag indicating we got EINVAL error from `sendmsg` or `sendmmsg` syscall.
82+
#[inline]
83+
#[cfg(not(windows))]
84+
pub fn set_sendmsg_einval(&self) {
85+
self.sendmsg_einval.store(true, Ordering::Relaxed)
86+
}
6587
}
6688

6789
impl Default for UdpState {

quinn-udp/src/unix.rs

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use std::{
66
mem::{self, MaybeUninit},
77
net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6},
88
os::unix::io::AsRawFd,
9-
sync::atomic::AtomicUsize,
9+
sync::atomic::{AtomicBool, AtomicUsize},
1010
time::Instant,
1111
};
1212

@@ -189,6 +189,7 @@ fn send(
189189
&mut iovecs[i],
190190
&mut cmsgs[i],
191191
encode_src_ip,
192+
state.sendmsg_einval(),
192193
);
193194
}
194195
let num_transmits = transmits.len().min(BATCH_SIZE);
@@ -221,6 +222,12 @@ fn send(
221222
}
222223
}
223224

225+
if e.raw_os_error() == Some(libc::EINVAL) {
226+
// Some arguments to `sendmsg` are not supported.
227+
// Switch to fallback mode.
228+
state.set_sendmsg_einval();
229+
}
230+
224231
// Other errors are ignored, since they will ususally be handled
225232
// by higher level retransmits and timeouts.
226233
// - PermissionDenied errors have been observed due to iptable rules.
@@ -243,7 +250,7 @@ fn send(
243250

244251
#[cfg(any(target_os = "macos", target_os = "ios"))]
245252
fn send(
246-
_state: &UdpState,
253+
state: &UdpState,
247254
io: SockRef<'_>,
248255
last_send_error: &mut Instant,
249256
transmits: &[Transmit],
@@ -263,6 +270,7 @@ fn send(
263270
&mut ctrl,
264271
// Only tested on macOS
265272
cfg!(target_os = "macos"),
273+
state.sendmsg_einval(),
266274
);
267275
let n = unsafe { libc::sendmsg(io.as_raw_fd(), &hdr, 0) };
268276
if n == -1 {
@@ -459,6 +467,7 @@ pub fn udp_state() -> UdpState {
459467
UdpState {
460468
max_gso_segments: AtomicUsize::new(gso::max_gso_segments()),
461469
gro_segments: gro::gro_segments(),
470+
sendmsg_einval: AtomicBool::new(false),
462471
}
463472
}
464473

@@ -472,6 +481,7 @@ fn prepare_msg(
472481
ctrl: &mut cmsg::Aligned<[u8; CMSG_LEN]>,
473482
#[allow(unused_variables)] // only used on FreeBSD & macOS
474483
encode_src_ip: bool,
484+
sendmsg_einval: bool,
475485
) {
476486
iov.iov_base = transmit.contents.as_ptr() as *const _ as *mut _;
477487
iov.iov_len = transmit.contents.len();
@@ -493,7 +503,9 @@ fn prepare_msg(
493503
let mut encoder = unsafe { cmsg::Encoder::new(hdr) };
494504
let ecn = transmit.ecn.map_or(0, |x| x as libc::c_int);
495505
if transmit.destination.is_ipv4() {
496-
encoder.push(libc::IPPROTO_IP, libc::IP_TOS, ecn as IpTosTy);
506+
if !sendmsg_einval {
507+
encoder.push(libc::IPPROTO_IP, libc::IP_TOS, ecn as IpTosTy);
508+
}
497509
} else {
498510
encoder.push(libc::IPPROTO_IPV6, libc::IPV6_TCLASS, ecn);
499511
}

0 commit comments

Comments
 (0)