Skip to content
Merged
Changes from 18 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
126 changes: 124 additions & 2 deletions quinn-udp/src/unix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -797,18 +797,30 @@ pub(crate) const BATCH_SIZE: usize = 1;
#[cfg(any(target_os = "linux", target_os = "android"))]
mod gso {
use super::*;
use std::{ffi::CStr, mem, str::FromStr, sync::OnceLock};

#[cfg(not(target_os = "android"))]
const UDP_SEGMENT: libc::c_int = libc::UDP_SEGMENT;
#[cfg(target_os = "android")]
// TODO: Add this to libc
const UDP_SEGMENT: libc::c_int = 103;

/// Checks whether GSO support is available by setting the UDP_SEGMENT
/// option on a socket
// Support for UDP GSO has been added to linux kernel in version 4.18
// https://github.com/torvalds/linux/commit/cb586c63e3fc5b227c51fd8c4cb40b34d3750645
const SUPPORTED_SINCE: KernelVersion = KernelVersion {
version: 4,
major_revision: 18,
};

/// Checks whether GSO support is available by checking the kernel version followed by setting
/// the UDP_SEGMENT option on a socket
pub(crate) fn max_gso_segments() -> usize {
const GSO_SIZE: libc::c_int = 1500;

if !SUPPORTED_BY_CURRENT_KERNEL.get_or_init(supported_by_current_kernel) {
return 1;
}

let socket = match std::net::UdpSocket::bind("[::]:0")
.or_else(|_| std::net::UdpSocket::bind((Ipv4Addr::LOCALHOST, 0)))
{
Expand All @@ -833,6 +845,116 @@ mod gso {
pub(crate) fn set_segment_size(encoder: &mut cmsg::Encoder<libc::msghdr>, segment_size: u16) {
encoder.push(libc::SOL_UDP, UDP_SEGMENT, segment_size);
}

// Avoid calling `supported_by_current_kernel` for each socket by using `OnceLock`.
static SUPPORTED_BY_CURRENT_KERNEL: OnceLock<bool> = OnceLock::new();

fn supported_by_current_kernel() -> bool {
let kernel_version_string = match kernel_version_string() {
Ok(kernel_version_string) => kernel_version_string,
Err(_errno) => {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do these need _ prefixes?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When the log macros are configured out (no features active), they'd otherwise show up as unused variables.

crate::log::warn!("GSO disabled: uname returned {_errno}");
return false;
}
};

let Some(kernel_version) = KernelVersion::from_str(&kernel_version_string) else {
crate::log::warn!(
"GSO disabled: failed to parse kernel version ({kernel_version_string:?})"
Comment thread
inetic marked this conversation as resolved.
Outdated
);
return false;
};

if kernel_version < SUPPORTED_SINCE {
crate::log::info!("GSO disabled: kernel too old ({kernel_version_string}); need 4.18+",);
return false;
}

true
}

fn kernel_version_string() -> Result<String, libc::c_int> {
let mut n = unsafe { mem::zeroed() };
let r = unsafe { libc::uname(&mut n) };
if r != 0 {
return Err(r);
}
Comment on lines +878 to +881

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I understand libc's uname correctly, the return value is either 0 or -1. On -1 the actual error code can be retrieved via errno:

On success, zero is returned. On error, -1 is returned, and errno
is set to indicate the error.

https://man7.org/linux/man-pages/man2/uname.2.html

Would it make sense to retrieve the actual error code here?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok(unsafe {
CStr::from_ptr(n.release[..].as_ptr())
.to_string_lossy()
.into_owned()
})
}

// https://www.linfo.org/kernel_version_numbering.html
#[derive(Eq, PartialEq, Ord, PartialOrd, Debug)]
struct KernelVersion {
version: u8,
major_revision: u8,
}

impl KernelVersion {
fn from_str(release: &str) -> Option<Self> {
let mut split = release
.split_once('-')
.map(|pair| pair.0)
.unwrap_or(release)
.split('.');
Comment on lines +898 to +902

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

... then this can be release.split_once('-')?.0.split('.').

@inetic inetic May 22, 2025

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The .unwrap_or(release) part is there to avoid failing on strings which don't have "-", like "4.18" for example. I don't know whether such release strings exist in real life, but might be good to keep it just in case?

On the other hand if we go with a "let's not worry about what we don't know" approach, then we could get rid of the .split_once('-') part all together because the formats that I've seen so far were always "X.Y.Z-...", for which let mut split = release.split('.'); would suffice.


let version = u8::from_str(split.next()?).ok()?;
let major_revision = u8::from_str(split.next()?).ok()?;

Some(Self {
version,
major_revision,
})
}
}

#[cfg(test)]
mod test {
use super::*;

#[test]
fn parse_current_kernel_version_release_string() {
let release = kernel_version_string().unwrap();
KernelVersion::from_str(&release).unwrap();
}

#[test]
fn parse_kernel_version_release_string() {
// These are made up for the test
assert_eq!(
KernelVersion::from_str("4.14"),
Some(KernelVersion {
version: 4,
major_revision: 14
})
);
assert_eq!(
KernelVersion::from_str("4.18"),
Some(KernelVersion {
version: 4,
major_revision: 18
})
);
// These were seen in the wild
assert_eq!(
KernelVersion::from_str("4.14.186-27095505"),
Some(KernelVersion {
version: 4,
major_revision: 14
})
);
assert_eq!(
KernelVersion::from_str("6.8.0-59-generic"),
Some(KernelVersion {
version: 6,
major_revision: 8
})
);
}
}
}

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