Why was TCP window scaling needed, and how does it work?
Show the answer
Say this first
The window field is 16 bits, so without help the receive window tops out at 65,535 bytes. At 100 ms RTT that caps one flow at about 5.2 Mbit/s whatever the link speed. The window scale option (RFC 7323) is sent in the SYN and SYN-ACK with a shift count from 0 to 14; after the handshake each side multiplies the window field by 2 to the power of the peer's shift, up to about 1 GiB. Both sides must offer it, or neither side scales.
Go deeper
Example
A Linux host with default buffers sends wscale 7 in its SYN. Later its segments show win 502. The real
window is 502 x 128 = 64,256 bytes. Each side picks its own shift from its maximum receive buffer, so the
two values can differ.
Rules worth saying
- Only in SYN and SYN-ACK. The factor cannot change during the connection.
- If the client sends it and the SYN-ACK does not, scaling is off in both directions.
- The maximum shift is 14, so the largest window is 65,535 x 16,384, about 1 GiB.
Where it goes wrong
- A capture that starts mid-connection. Wireshark cannot know the factor and shows the raw field;
it labels the scaling factor
-1 (unknown). Capture from the SYN. - A middlebox that strips or rewrites the option. The connection works but every flow is capped at 64 KB per RTT. Captures on both sides of the device show the option disappearing.
- An application that sets SO_RCVBUF. On Linux that fixes the buffer and turns off receive autotuning for that socket, which can leave the window far smaller than the path needs.
They will ask next
Why can the window scale only be agreed in the handshake?
Both sides must interpret every later window field the same way. Changing the shift mid-stream could not be synchronised safely, so RFC 7323 fixes it at connection setup.
What sets the receive buffer on Linux?
Autotuning between the three values of
net.ipv4.tcp_rmem(minimum, default, maximum), unless the application sets SO_RCVBUF, in which casenet.core.rmem_maxcaps the request. The send side usesnet.ipv4.tcp_wmemandnet.core.wmem_maxthe same way.
Traps
- Reading
win 502in tcpdump as 502 bytes. tcpdump prints the raw field; multiply by the agreed scale.