noq_udp/
linux.rs

1use std::{io, os::fd::AsRawFd};
2
3use crate::{cmsg, imp::set_socket_option};
4
5pub(super) mod gso {
6    use super::*;
7    use std::{ffi::CStr, mem, str::FromStr, sync::OnceLock};
8
9    // Support for UDP GSO has been added to linux kernel in version 4.18
10    // https://github.com/torvalds/linux/commit/cb586c63e3fc5b227c51fd8c4cb40b34d3750645
11    const SUPPORTED_SINCE: KernelVersion = KernelVersion {
12        version: 4,
13        major_revision: 18,
14    };
15
16    /// Checks whether GSO support is available.
17    ///
18    /// Checks the kernel version followed by setting the UDP_SEGMENT option on a socket.
19    pub(crate) fn max_gso_segments(socket: &impl AsRawFd) -> usize {
20        const GSO_SIZE: libc::c_int = 1500;
21
22        if !SUPPORTED_BY_CURRENT_KERNEL.get_or_init(supported_by_current_kernel) {
23            return 1;
24        }
25
26        // As defined in linux/udp.h
27        // #define UDP_MAX_SEGMENTS        (1 << 6UL)
28        match set_socket_option(socket, libc::SOL_UDP, libc::UDP_SEGMENT, GSO_SIZE) {
29            Ok(()) => {
30                // Disable GSO again globally to ensure we can selectively enable it via cmsg.
31                // See:
32                // - https://github.com/quinn-rs/quinn/issues/2575
33                // - https://man7.org/linux/man-pages/man7/udp.7.html
34                let _ = set_socket_option(socket, libc::SOL_UDP, libc::UDP_SEGMENT, 0);
35
36                64
37            }
38            Err(_e) => {
39                crate::log::debug!(
40                    "failed to set `UDP_SEGMENT` socket option ({_e}); setting `max_gso_segments = 1`"
41                );
42
43                1
44            }
45        }
46    }
47
48    pub(crate) fn set_segment_size(
49        encoder: &mut cmsg::Encoder<'_, libc::msghdr>,
50        segment_size: u16,
51    ) {
52        encoder.push(libc::SOL_UDP, libc::UDP_SEGMENT, segment_size);
53    }
54
55    // Avoid calling `supported_by_current_kernel` for each socket by using `OnceLock`.
56    static SUPPORTED_BY_CURRENT_KERNEL: OnceLock<bool> = OnceLock::new();
57
58    fn supported_by_current_kernel() -> bool {
59        let kernel_version_string = match kernel_version_string() {
60            Ok(kernel_version_string) => kernel_version_string,
61            Err(_e) => {
62                crate::log::warn!("GSO disabled: uname returned {_e}");
63                return false;
64            }
65        };
66
67        let Some(kernel_version) = KernelVersion::from_str(&kernel_version_string) else {
68            crate::log::warn!(
69                "GSO disabled: failed to parse kernel version ({kernel_version_string})"
70            );
71            return false;
72        };
73
74        if kernel_version < SUPPORTED_SINCE {
75            crate::log::info!("GSO disabled: kernel too old ({kernel_version_string}); need 4.18+",);
76            return false;
77        }
78
79        true
80    }
81
82    fn kernel_version_string() -> io::Result<String> {
83        let mut n = unsafe { mem::zeroed() };
84        let r = unsafe { libc::uname(&mut n) };
85        if r != 0 {
86            return Err(io::Error::last_os_error());
87        }
88        Ok(unsafe {
89            CStr::from_ptr(n.release[..].as_ptr())
90                .to_string_lossy()
91                .into_owned()
92        })
93    }
94
95    // https://www.linfo.org/kernel_version_numbering.html
96    #[derive(Eq, PartialEq, Ord, PartialOrd, Debug)]
97    struct KernelVersion {
98        version: u8,
99        major_revision: u8,
100    }
101
102    impl KernelVersion {
103        fn from_str(release: &str) -> Option<Self> {
104            let mut split = release
105                .split_once('-')
106                .map(|pair| pair.0)
107                .unwrap_or(release)
108                .split('.');
109
110            let version = u8::from_str(split.next()?).ok()?;
111            let major_revision = u8::from_str(split.next()?).ok()?;
112
113            Some(Self {
114                version,
115                major_revision,
116            })
117        }
118    }
119
120    #[cfg(test)]
121    mod test {
122        use super::*;
123
124        #[test]
125        fn parse_current_kernel_version_release_string() {
126            let release = kernel_version_string().unwrap();
127            KernelVersion::from_str(&release).unwrap();
128        }
129
130        #[test]
131        fn parse_kernel_version_release_string() {
132            // These are made up for the test
133            assert_eq!(
134                KernelVersion::from_str("4.14"),
135                Some(KernelVersion {
136                    version: 4,
137                    major_revision: 14
138                })
139            );
140            assert_eq!(
141                KernelVersion::from_str("4.18"),
142                Some(KernelVersion {
143                    version: 4,
144                    major_revision: 18
145                })
146            );
147            // These were seen in the wild
148            assert_eq!(
149                KernelVersion::from_str("4.14.186-27095505"),
150                Some(KernelVersion {
151                    version: 4,
152                    major_revision: 14
153                })
154            );
155            assert_eq!(
156                KernelVersion::from_str("6.8.0-59-generic"),
157                Some(KernelVersion {
158                    version: 6,
159                    major_revision: 8
160                })
161            );
162        }
163    }
164}