tcp/
window_scaling.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
/// An object to encode the window scaling logic. It asserts that no operations have been taken
/// out-of-order.
#[derive(Copy, Clone, Debug)]
pub(crate) struct WindowScaling {
    sent_syn: bool,
    received_syn: bool,
    recv_window_scale_shift: Option<u8>,
    send_window_scale_shift: Option<u8>,
    disabled: bool,
}

impl WindowScaling {
    /// Maximum value of the "Window Scale" TCP option as specified by RFC 7323.
    const MAX_SCALE_SHIFT: u8 = 14;

    pub fn new() -> Self {
        Self {
            sent_syn: false,
            received_syn: false,
            recv_window_scale_shift: None,
            send_window_scale_shift: None,
            disabled: false,
        }
    }

    /// Given a maximum window size, this returns the window scale shift that should be used. As the
    /// window size must be less than 2^30, the returned shift will be limited to 14. This does not
    /// guarantee that `window >> shift` fits within a `u16`.
    pub fn scale_shift_for_max_window(window: u32) -> u8 {
        // the maximum possible window allowed by tcp
        let max_window_size = (u16::MAX as u32) << Self::MAX_SCALE_SHIFT;

        // calculation based on linux 6.5 `tcp_select_initial_window`:
        // https://elixir.bootlin.com/linux/v6.5/source/net/ipv4/tcp_output.c#L206

        let window = std::cmp::min(window, max_window_size);

        if window == 0 {
            return 0;
        }

        let shift = window
            .ilog2()
            .saturating_sub(Self::MAX_SCALE_SHIFT as u32 + 1);
        let shift = std::cmp::min(shift, Self::MAX_SCALE_SHIFT as u32);

        shift.try_into().unwrap()
    }

    /// Disable window scaling. This will ensure that a window scale is not sent in a SYN packet.
    pub fn disable(&mut self) {
        // tried to disable window scaling after sending the SYN
        assert!(!self.sent_syn);
        self.disabled = true;
    }

    /// A SYN packet was sent with the given window scale.
    pub fn sent_syn(&mut self, window_scale: Option<u8>) {
        // why did we send the SYN twice?
        assert!(!self.sent_syn);

        // if it was disabled, we shouldn't have sent a window scale in the SYN
        if self.disabled {
            assert!(window_scale.is_none());
        }

        if self.received_syn && self.send_window_scale_shift.is_none() {
            // RFC 7323 1.3.:
            // > Furthermore, the Window Scale option will be sent in a <SYN,ACK> segment only if
            // > the corresponding option was received in the initial <SYN> segment.
            assert!(window_scale.is_none());
        }

        if let Some(window_scale) = window_scale {
            // RFC 7323 2.3.:
            // > Since the max window is 2^S (where S is the scaling shift count) times at most 2^16
            // > - 1 (the maximum unscaled window), the maximum window is guaranteed to be < 2^30 if
            // > S <= 14.  Thus, the shift count MUST be limited to 14 (which allows windows of 2^30
            // > = 1 GiB).
            assert!(window_scale <= Self::MAX_SCALE_SHIFT);
        }

        self.sent_syn = true;
        self.recv_window_scale_shift = window_scale;
    }

    /// A SYN packet was received with the given window scale.
    pub fn received_syn(&mut self, mut window_scale: Option<u8>) {
        // why did we receive the SYN twice?
        assert!(!self.received_syn);

        if let Some(ref mut window_scale) = window_scale {
            // RFC 7323 2.3.:
            // > If a Window Scale option is received with a shift.cnt value larger than 14, the TCP
            // > SHOULD log the error but MUST use 14 instead of the specified value.
            if *window_scale > Self::MAX_SCALE_SHIFT {
                *window_scale = Self::MAX_SCALE_SHIFT;
            }
        }

        self.received_syn = true;
        self.send_window_scale_shift = window_scale;
    }

    /// Checks if it's valid for an outward SYN packet to contain a window scale option.
    pub fn can_send_window_scale(&self) -> bool {
        // can't send if window scaling has been disabled, or if we've already received a SYN packet
        // that did not have window scaling enabled
        !(self.disabled || (self.received_syn && self.send_window_scale_shift.is_none()))
    }

    /// Has window scaling been configured? This does *not* mean that it's enabled. If this returns
    /// true, then `recv_window_scale_shift()` and `send_window_scale_shift()` should not panic.
    pub fn is_configured(&self) -> bool {
        self.disabled || (self.sent_syn && self.received_syn)
    }

    fn recv_shift(&self) -> u8 {
        if self.disabled {
            return 0;
        }

        if self.send_window_scale_shift.is_some() && self.recv_window_scale_shift.is_some() {
            self.recv_window_scale_shift.unwrap()
        } else {
            0
        }
    }

    fn send_shift(&self) -> u8 {
        if self.disabled {
            return 0;
        }

        if self.send_window_scale_shift.is_some() && self.recv_window_scale_shift.is_some() {
            self.send_window_scale_shift.unwrap()
        } else {
            0
        }
    }

    /// The right bit-shift to apply to the receive buffer's window size when sending a packet.
    pub fn recv_window_scale_shift(&self) -> u8 {
        // we shouldn't be trying to get the receive window scale before we've fully configured
        // window scaling
        assert!(self.is_configured());

        self.recv_shift()
    }

    /// The left bit-shift to apply to the send buffer's window size when receiving a packet.
    pub fn send_window_scale_shift(&self) -> u8 {
        // we shouldn't be trying to get the send window scale before we've fully configured window
        // scaling
        assert!(self.is_configured());

        self.send_shift()
    }

    pub fn recv_window_max(&self) -> u32 {
        (u16::MAX as u32) << self.recv_shift()
    }
}

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

    #[test]
    fn test_scale_shift_for_max_window() {
        // the maximum possible window allowed by tcp
        const MAX_WINDOW_SIZE: u32 = (u16::MAX as u32) << 14;

        fn test(window: u32, expected: u8) {
            let rv = WindowScaling::scale_shift_for_max_window(window);

            assert_eq!(rv, expected);

            // Check that applying the window scale bit-shift does result in a value that fits
            // within a u16. We can't check this if the provided window size is larger than
            // `MAX_WINDOW_SIZE`, since we use a max bit shift value of 14.
            if window <= MAX_WINDOW_SIZE {
                assert!(window >> rv <= u16::MAX as u32);
            }
        }

        const U16_MAX: u32 = u16::MAX as u32;

        test(0, 0);
        test(1, 0);

        test(U16_MAX, 0);
        test(U16_MAX + 1, 1);

        test(2 * U16_MAX, 1);
        test(2 * U16_MAX + 1, 1);
        test(2 * (U16_MAX + 1), 2);

        test(4 * U16_MAX, 2);
        test(4 * U16_MAX + 1, 2);
        test(4 * U16_MAX + 2, 2);
        test(4 * (U16_MAX + 1), 3);

        test(MAX_WINDOW_SIZE / 2, 13);
        test(MAX_WINDOW_SIZE / 2 + 1, 13);
        test(MAX_WINDOW_SIZE / 2 + 14, 13);
        test(MAX_WINDOW_SIZE / 2 + 2_u32.pow(13) - 1, 13);
        test(MAX_WINDOW_SIZE / 2 + 2_u32.pow(13), 14);

        test(MAX_WINDOW_SIZE - 1, 14);
        test(MAX_WINDOW_SIZE, 14);
        test(MAX_WINDOW_SIZE + 1, 14);
        test(MAX_WINDOW_SIZE + 2_u32.pow(14), 14);
        test(MAX_WINDOW_SIZE + 2_u32.pow(14) + 1, 14);

        test(u32::MAX, 14);
    }
}