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}