s2n_quic_platform/features/
gso.rs

1// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2// SPDX-License-Identifier: Apache-2.0
3
4use super::c_int;
5use core::{
6    convert::{TryFrom, TryInto},
7    fmt,
8    fmt::{Display, Formatter},
9    num::NonZeroUsize,
10};
11
12#[derive(Clone, Copy, Debug)]
13pub struct MaxSegments(NonZeroUsize);
14
15impl MaxSegments {
16    pub const MAX: Self = gso_impl::MAX_SEGMENTS;
17    pub const DEFAULT: Self = gso_impl::DEFAULT_SEGMENTS;
18}
19
20impl Default for MaxSegments {
21    #[inline]
22    fn default() -> Self {
23        MaxSegments::DEFAULT
24    }
25}
26
27impl TryFrom<usize> for MaxSegments {
28    type Error = MaxSegmentsError;
29
30    fn try_from(value: usize) -> Result<Self, Self::Error> {
31        if !(1..=Self::MAX.into()).contains(&value) {
32            return Err(MaxSegmentsError);
33        }
34
35        Ok(MaxSegments(value.try_into().expect(
36            "Value must be greater than zero according to the check above",
37        )))
38    }
39}
40
41#[derive(Debug)]
42pub struct MaxSegmentsError;
43
44impl Display for MaxSegmentsError {
45    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
46        write!(
47            f,
48            "MaxSegments must be a non-zero value less than or equal to {}",
49            MaxSegments::MAX.0.get()
50        )
51    }
52}
53
54impl From<MaxSegments> for usize {
55    #[inline]
56    fn from(value: MaxSegments) -> Self {
57        value.0.get()
58    }
59}
60
61#[cfg(s2n_quic_platform_gso)]
62mod gso_enabled {
63    use super::*;
64    use libc::{SOL_UDP, UDP_SEGMENT};
65    use std::sync::{
66        atomic::{AtomicUsize, Ordering},
67        Arc,
68    };
69
70    pub const LEVEL: Option<c_int> = Some(SOL_UDP as _);
71    pub const TYPE: Option<c_int> = Some(UDP_SEGMENT as _);
72    pub const CMSG_SPACE: usize = crate::message::cmsg::size_of_cmsg::<super::Cmsg>();
73
74    #[inline]
75    pub const fn is_match(level: c_int, ty: c_int) -> bool {
76        level == SOL_UDP as c_int && ty == UDP_SEGMENT as c_int
77    }
78
79    // This value represents the Maximum value MaxSegments can be set to, i.e. a Max of a Max. The
80    // value comes from the Linux kernel:
81    //
82    // https://github.com/torvalds/linux/blob/e9f1cbc0c4114880090c7a578117d3b9cf184ad4/tools/testing/selftests/net/udpgso.c#L37
83    // ```
84    // #define UDP_MAX_SEGMENTS	(1 << 6UL)
85    // ```
86    pub const MAX_SEGMENTS: MaxSegments = MaxSegments(NonZeroUsize::new(1 << 6).unwrap());
87
88    // The packet pacer enforces a burst limit of 10 packets, so generally there is no benefit to
89    // exceeding that value for GSO segments. However, in low RTT/high bandwidth networks the pacing
90    // interval may drop below the timer granularity, resulting in `MAX_BURST_PACKETS` being
91    // exceeded. In such networks, setting a MaxSegments size higher than the default may have a
92    // positive effect on efficiency.
93    //= https://www.rfc-editor.org/rfc/rfc9002#section-7.7
94    //# Senders SHOULD limit bursts to the initial congestion window
95    pub const DEFAULT_SEGMENTS: MaxSegments = MaxSegments(
96        NonZeroUsize::new(s2n_quic_core::recovery::MAX_BURST_PACKETS as usize).unwrap(),
97    );
98
99    #[derive(Clone, Debug)]
100    pub struct Gso(Arc<AtomicUsize>);
101
102    impl Default for Gso {
103        fn default() -> Self {
104            MaxSegments::DEFAULT.into()
105        }
106    }
107
108    impl Gso {
109        #[inline]
110        pub fn max_segments(&self) -> usize {
111            self.0.load(Ordering::Relaxed)
112        }
113
114        #[inline]
115        pub fn disable(&self) {
116            self.0.store(1, Ordering::Relaxed);
117        }
118
119        #[inline]
120        pub fn handle_socket_error(&self, error: &std::io::Error) -> Option<usize> {
121            let raw_error = error.raw_os_error()?;
122            s2n_quic_core::ensure!(raw_error == libc::EIO, None);
123            let prev = self.0.swap(1, Ordering::Relaxed);
124            Some(prev)
125        }
126    }
127
128    impl From<MaxSegments> for Gso {
129        #[inline]
130        fn from(segments: MaxSegments) -> Self {
131            Self(Arc::new(AtomicUsize::new(segments.0.into())))
132        }
133    }
134}
135
136#[cfg(any(not(s2n_quic_platform_gso), test))]
137mod gso_disabled {
138    #![cfg_attr(test, allow(dead_code))]
139
140    use super::*;
141
142    pub const LEVEL: Option<c_int> = None;
143    pub const TYPE: Option<c_int> = None;
144    pub const CMSG_SPACE: usize = 0;
145
146    #[inline]
147    pub const fn is_match(level: c_int, ty: c_int) -> bool {
148        let _ = level;
149        let _ = ty;
150        false
151    }
152
153    pub const MAX_SEGMENTS: MaxSegments = MaxSegments(NonZeroUsize::new(1).unwrap());
154    pub const DEFAULT_SEGMENTS: MaxSegments = MAX_SEGMENTS;
155
156    #[derive(Clone, Default, Debug)]
157    pub struct Gso(());
158
159    impl Gso {
160        #[inline]
161        pub fn max_segments(&self) -> usize {
162            1
163        }
164
165        #[inline]
166        #[allow(dead_code)] // this may or may not be used on certain platforms
167        pub fn disable(&self) {
168            // it's already disabled
169        }
170
171        #[inline(always)]
172        pub fn handle_socket_error(&self, error: &std::io::Error) -> Option<usize> {
173            let _ = error;
174            None
175        }
176    }
177
178    impl From<MaxSegments> for Gso {
179        #[inline]
180        fn from(_segments: MaxSegments) -> Self {
181            Self(())
182        }
183    }
184}
185
186mod gso_impl {
187    #[cfg(not(s2n_quic_platform_gso))]
188    pub use super::gso_disabled::*;
189    #[cfg(s2n_quic_platform_gso)]
190    pub use super::gso_enabled::*;
191}
192
193pub use gso_impl::*;
194pub type Cmsg = u16;
195
196pub const IS_SUPPORTED: bool = cfg!(s2n_quic_platform_gso);