tcp/
window_scaling.rs

1/// An object to encode the window scaling logic. It asserts that no operations have been taken
2/// out-of-order.
3#[derive(Copy, Clone, Debug)]
4pub(crate) struct WindowScaling {
5    sent_syn: bool,
6    received_syn: bool,
7    recv_window_scale_shift: Option<u8>,
8    send_window_scale_shift: Option<u8>,
9    disabled: bool,
10}
11
12impl WindowScaling {
13    /// Maximum value of the "Window Scale" TCP option as specified by RFC 7323.
14    const MAX_SCALE_SHIFT: u8 = 14;
15
16    pub fn new() -> Self {
17        Self {
18            sent_syn: false,
19            received_syn: false,
20            recv_window_scale_shift: None,
21            send_window_scale_shift: None,
22            disabled: false,
23        }
24    }
25
26    /// Given a maximum window size, this returns the window scale shift that should be used. As the
27    /// window size must be less than 2^30, the returned shift will be limited to 14. This does not
28    /// guarantee that `window >> shift` fits within a `u16`.
29    pub fn scale_shift_for_max_window(window: u32) -> u8 {
30        // the maximum possible window allowed by tcp
31        let max_window_size = (u16::MAX as u32) << Self::MAX_SCALE_SHIFT;
32
33        // calculation based on linux 6.5 `tcp_select_initial_window`:
34        // https://elixir.bootlin.com/linux/v6.5/source/net/ipv4/tcp_output.c#L206
35
36        let window = std::cmp::min(window, max_window_size);
37
38        if window == 0 {
39            return 0;
40        }
41
42        let shift = window
43            .ilog2()
44            .saturating_sub(Self::MAX_SCALE_SHIFT as u32 + 1);
45        let shift = std::cmp::min(shift, Self::MAX_SCALE_SHIFT as u32);
46
47        shift.try_into().unwrap()
48    }
49
50    /// Disable window scaling. This will ensure that a window scale is not sent in a SYN packet.
51    pub fn disable(&mut self) {
52        // tried to disable window scaling after sending the SYN
53        assert!(!self.sent_syn);
54        self.disabled = true;
55    }
56
57    /// A SYN packet was sent with the given window scale.
58    pub fn sent_syn(&mut self, window_scale: Option<u8>) {
59        // why did we send the SYN twice?
60        assert!(!self.sent_syn);
61
62        // if it was disabled, we shouldn't have sent a window scale in the SYN
63        if self.disabled {
64            assert!(window_scale.is_none());
65        }
66
67        if self.received_syn && self.send_window_scale_shift.is_none() {
68            // RFC 7323 1.3.:
69            // > Furthermore, the Window Scale option will be sent in a <SYN,ACK> segment only if
70            // > the corresponding option was received in the initial <SYN> segment.
71            assert!(window_scale.is_none());
72        }
73
74        if let Some(window_scale) = window_scale {
75            // RFC 7323 2.3.:
76            // > Since the max window is 2^S (where S is the scaling shift count) times at most 2^16
77            // > - 1 (the maximum unscaled window), the maximum window is guaranteed to be < 2^30 if
78            // > S <= 14.  Thus, the shift count MUST be limited to 14 (which allows windows of 2^30
79            // > = 1 GiB).
80            assert!(window_scale <= Self::MAX_SCALE_SHIFT);
81        }
82
83        self.sent_syn = true;
84        self.recv_window_scale_shift = window_scale;
85    }
86
87    /// A SYN packet was received with the given window scale.
88    pub fn received_syn(&mut self, mut window_scale: Option<u8>) {
89        // why did we receive the SYN twice?
90        assert!(!self.received_syn);
91
92        if let Some(ref mut window_scale) = window_scale {
93            // RFC 7323 2.3.:
94            // > If a Window Scale option is received with a shift.cnt value larger than 14, the TCP
95            // > SHOULD log the error but MUST use 14 instead of the specified value.
96            if *window_scale > Self::MAX_SCALE_SHIFT {
97                *window_scale = Self::MAX_SCALE_SHIFT;
98            }
99        }
100
101        self.received_syn = true;
102        self.send_window_scale_shift = window_scale;
103    }
104
105    /// Checks if it's valid for an outward SYN packet to contain a window scale option.
106    pub fn can_send_window_scale(&self) -> bool {
107        // can't send if window scaling has been disabled, or if we've already received a SYN packet
108        // that did not have window scaling enabled
109        !(self.disabled || (self.received_syn && self.send_window_scale_shift.is_none()))
110    }
111
112    /// Has window scaling been configured? This does *not* mean that it's enabled. If this returns
113    /// true, then `recv_window_scale_shift()` and `send_window_scale_shift()` should not panic.
114    pub fn is_configured(&self) -> bool {
115        self.disabled || (self.sent_syn && self.received_syn)
116    }
117
118    fn recv_shift(&self) -> u8 {
119        if self.disabled {
120            return 0;
121        }
122
123        if self.send_window_scale_shift.is_some() && self.recv_window_scale_shift.is_some() {
124            self.recv_window_scale_shift.unwrap()
125        } else {
126            0
127        }
128    }
129
130    fn send_shift(&self) -> u8 {
131        if self.disabled {
132            return 0;
133        }
134
135        if self.send_window_scale_shift.is_some() && self.recv_window_scale_shift.is_some() {
136            self.send_window_scale_shift.unwrap()
137        } else {
138            0
139        }
140    }
141
142    /// The right bit-shift to apply to the receive buffer's window size when sending a packet.
143    pub fn recv_window_scale_shift(&self) -> u8 {
144        // we shouldn't be trying to get the receive window scale before we've fully configured
145        // window scaling
146        assert!(self.is_configured());
147
148        self.recv_shift()
149    }
150
151    /// The left bit-shift to apply to the send buffer's window size when receiving a packet.
152    pub fn send_window_scale_shift(&self) -> u8 {
153        // we shouldn't be trying to get the send window scale before we've fully configured window
154        // scaling
155        assert!(self.is_configured());
156
157        self.send_shift()
158    }
159
160    pub fn recv_window_max(&self) -> u32 {
161        (u16::MAX as u32) << self.recv_shift()
162    }
163}
164
165#[cfg(test)]
166mod tests {
167    use super::*;
168
169    #[test]
170    fn test_scale_shift_for_max_window() {
171        // the maximum possible window allowed by tcp
172        const MAX_WINDOW_SIZE: u32 = (u16::MAX as u32) << 14;
173
174        fn test(window: u32, expected: u8) {
175            let rv = WindowScaling::scale_shift_for_max_window(window);
176
177            assert_eq!(rv, expected);
178
179            // Check that applying the window scale bit-shift does result in a value that fits
180            // within a u16. We can't check this if the provided window size is larger than
181            // `MAX_WINDOW_SIZE`, since we use a max bit shift value of 14.
182            if window <= MAX_WINDOW_SIZE {
183                assert!(window >> rv <= u16::MAX as u32);
184            }
185        }
186
187        const U16_MAX: u32 = u16::MAX as u32;
188
189        test(0, 0);
190        test(1, 0);
191
192        test(U16_MAX, 0);
193        test(U16_MAX + 1, 1);
194
195        test(2 * U16_MAX, 1);
196        test(2 * U16_MAX + 1, 1);
197        test(2 * (U16_MAX + 1), 2);
198
199        test(4 * U16_MAX, 2);
200        test(4 * U16_MAX + 1, 2);
201        test(4 * U16_MAX + 2, 2);
202        test(4 * (U16_MAX + 1), 3);
203
204        test(MAX_WINDOW_SIZE / 2, 13);
205        test(MAX_WINDOW_SIZE / 2 + 1, 13);
206        test(MAX_WINDOW_SIZE / 2 + 14, 13);
207        test(MAX_WINDOW_SIZE / 2 + 2_u32.pow(13) - 1, 13);
208        test(MAX_WINDOW_SIZE / 2 + 2_u32.pow(13), 14);
209
210        test(MAX_WINDOW_SIZE - 1, 14);
211        test(MAX_WINDOW_SIZE, 14);
212        test(MAX_WINDOW_SIZE + 1, 14);
213        test(MAX_WINDOW_SIZE + 2_u32.pow(14), 14);
214        test(MAX_WINDOW_SIZE + 2_u32.pow(14) + 1, 14);
215
216        test(u32::MAX, 14);
217    }
218}