Skip to content

Commit 81f9cd9

Browse files
authored
udp: disable GSO for old Linux
1 parent 8537a18 commit 81f9cd9

File tree

1 file changed

+124
-2
lines changed

1 file changed

+124
-2
lines changed

quinn-udp/src/unix.rs

Lines changed: 124 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -797,18 +797,30 @@ pub(crate) const BATCH_SIZE: usize = 1;
797797
#[cfg(any(target_os = "linux", target_os = "android"))]
798798
mod gso {
799799
use super::*;
800+
use std::{ffi::CStr, mem, str::FromStr, sync::OnceLock};
800801

801802
#[cfg(not(target_os = "android"))]
802803
const UDP_SEGMENT: libc::c_int = libc::UDP_SEGMENT;
803804
#[cfg(target_os = "android")]
804805
// TODO: Add this to libc
805806
const UDP_SEGMENT: libc::c_int = 103;
806807

807-
/// Checks whether GSO support is available by setting the UDP_SEGMENT
808-
/// option on a socket
808+
// Support for UDP GSO has been added to linux kernel in version 4.18
809+
// https://github.com/torvalds/linux/commit/cb586c63e3fc5b227c51fd8c4cb40b34d3750645
810+
const SUPPORTED_SINCE: KernelVersion = KernelVersion {
811+
version: 4,
812+
major_revision: 18,
813+
};
814+
815+
/// Checks whether GSO support is available by checking the kernel version followed by setting
816+
/// the UDP_SEGMENT option on a socket
809817
pub(crate) fn max_gso_segments() -> usize {
810818
const GSO_SIZE: libc::c_int = 1500;
811819

820+
if !SUPPORTED_BY_CURRENT_KERNEL.get_or_init(supported_by_current_kernel) {
821+
return 1;
822+
}
823+
812824
let socket = match std::net::UdpSocket::bind("[::]:0")
813825
.or_else(|_| std::net::UdpSocket::bind((Ipv4Addr::LOCALHOST, 0)))
814826
{
@@ -833,6 +845,116 @@ mod gso {
833845
pub(crate) fn set_segment_size(encoder: &mut cmsg::Encoder<libc::msghdr>, segment_size: u16) {
834846
encoder.push(libc::SOL_UDP, UDP_SEGMENT, segment_size);
835847
}
848+
849+
// Avoid calling `supported_by_current_kernel` for each socket by using `OnceLock`.
850+
static SUPPORTED_BY_CURRENT_KERNEL: OnceLock<bool> = OnceLock::new();
851+
852+
fn supported_by_current_kernel() -> bool {
853+
let kernel_version_string = match kernel_version_string() {
854+
Ok(kernel_version_string) => kernel_version_string,
855+
Err(_e) => {
856+
crate::log::warn!("GSO disabled: uname returned {_e}");
857+
return false;
858+
}
859+
};
860+
861+
let Some(kernel_version) = KernelVersion::from_str(&kernel_version_string) else {
862+
crate::log::warn!(
863+
"GSO disabled: failed to parse kernel version ({kernel_version_string})"
864+
);
865+
return false;
866+
};
867+
868+
if kernel_version < SUPPORTED_SINCE {
869+
crate::log::info!("GSO disabled: kernel too old ({kernel_version_string}); need 4.18+",);
870+
return false;
871+
}
872+
873+
true
874+
}
875+
876+
fn kernel_version_string() -> io::Result<String> {
877+
let mut n = unsafe { mem::zeroed() };
878+
let r = unsafe { libc::uname(&mut n) };
879+
if r != 0 {
880+
return Err(io::Error::last_os_error());
881+
}
882+
Ok(unsafe {
883+
CStr::from_ptr(n.release[..].as_ptr())
884+
.to_string_lossy()
885+
.into_owned()
886+
})
887+
}
888+
889+
// https://www.linfo.org/kernel_version_numbering.html
890+
#[derive(Eq, PartialEq, Ord, PartialOrd, Debug)]
891+
struct KernelVersion {
892+
version: u8,
893+
major_revision: u8,
894+
}
895+
896+
impl KernelVersion {
897+
fn from_str(release: &str) -> Option<Self> {
898+
let mut split = release
899+
.split_once('-')
900+
.map(|pair| pair.0)
901+
.unwrap_or(release)
902+
.split('.');
903+
904+
let version = u8::from_str(split.next()?).ok()?;
905+
let major_revision = u8::from_str(split.next()?).ok()?;
906+
907+
Some(Self {
908+
version,
909+
major_revision,
910+
})
911+
}
912+
}
913+
914+
#[cfg(test)]
915+
mod test {
916+
use super::*;
917+
918+
#[test]
919+
fn parse_current_kernel_version_release_string() {
920+
let release = kernel_version_string().unwrap();
921+
KernelVersion::from_str(&release).unwrap();
922+
}
923+
924+
#[test]
925+
fn parse_kernel_version_release_string() {
926+
// These are made up for the test
927+
assert_eq!(
928+
KernelVersion::from_str("4.14"),
929+
Some(KernelVersion {
930+
version: 4,
931+
major_revision: 14
932+
})
933+
);
934+
assert_eq!(
935+
KernelVersion::from_str("4.18"),
936+
Some(KernelVersion {
937+
version: 4,
938+
major_revision: 18
939+
})
940+
);
941+
// These were seen in the wild
942+
assert_eq!(
943+
KernelVersion::from_str("4.14.186-27095505"),
944+
Some(KernelVersion {
945+
version: 4,
946+
major_revision: 14
947+
})
948+
);
949+
assert_eq!(
950+
KernelVersion::from_str("6.8.0-59-generic"),
951+
Some(KernelVersion {
952+
version: 6,
953+
major_revision: 8
954+
})
955+
);
956+
}
957+
}
836958
}
837959

838960
// On Apple platforms using the `sendmsg_x` call, UDP datagram segmentation is not

0 commit comments

Comments
 (0)