skip to content

A tcpdump capture of a fast bulk transfer shows `win 502` on every segment; why is that not a 502-byte window?

level: middleimportance: nice to knowfreq 15%

answer

  1. the header field, not bytes
  2. look back at the handshake
  3. wscale in each side's SYN
  4. a shift count, not a multiplier

basics

~20 s

tcpdump prints the 16-bit window field exactly as sent and never applies window scaling. The scale comes from the wscale option in each side's SYN; with wscale 7, win 502 means 502 × 128 = 64,256 bytes.

solid answer

~40 s

The `win` value on a tcpdump line is the raw 16-bit window field from the TCP header; tcpdump's printer does not apply the negotiated scale. To turn it into bytes, go back to the handshake and read the options list: `options [mss 1460,sackOK,TS val 3196613497 ecr 0,nop,wscale 7]` means that side shifts its advertised window left by 7 bits, so its later `win 502` is 502 × 2⁷ = 64,256 bytes. Each direction uses the shift count that side sent in its own SYN or SYN-ACK, and scaling is in effect only when both of them carried the option. If the capture missed the handshake, the factor is simply not in the file, and neither you nor tcpdump can recover bytes from the printed value alone.

go deeper

for a junior

Recall that tcpdump prints the window field raw and that the wscale option on the handshake lines holds the factor needed to read it.

for a middle

Explain the conversion: the sender's own shift count from its SYN or SYN-ACK, applied as a power of two, and only when both sides offered it.

for a senior

Refuse to diagnose a receiver bottleneck from raw win values until the handshake has been captured and the scale confirmed for that direction.

for a principal

Push capture runbooks to start before the connection opens when throughput is in question, because a capture without the handshake cannot answer it.

## The window field as tcpdump prints it A TCP receiver advertises how much more data it is willing to accept in the **window** field of every segment it sends. tcpdump prints that field as `win N`. Its TCP printer reads the 16-bit value from the header and prints it unchanged; there is no code in it that applies the negotiated scale factor. A fast transfer therefore often looks like this: ``` IP 192.0.2.10.51514 > 198.51.100.20.443: Flags [S], seq 1584207323, win 64240, options [mss 1460,sackOK,TS val 3196613497 ecr 0,nop,wscale 7], length 0 IP 198.51.100.20.443 > 192.0.2.10.51514: Flags [S.], seq 2836418390, ack 1584207324, win 65160, options [mss 1460,sackOK,TS val 1033404826 ecr 3196613497,nop,wscale 7], length 0 IP 192.0.2.10.51514 > 198.51.100.20.443: Flags [.], ack 1, win 502, options [nop,nop,TS val 3196613511 ecr 1033404826], length 0 ``` The 502 on the third line is not a tiny window. It is a scaled value that tcpdump shows unscaled. ## Reading the options list Options appear as `options [...]`, comma-separated, only when the header has any. The ones you meet on almost every handshake: | Printed as | What tcpdump is showing | |---|---| | `mss 1460` | the largest segment this side wants to receive | | `sackOK` | this side can use selective acknowledgments | | `TS val X ecr Y` | timestamp value, and the echo of the peer's last one | | `nop` | a one-byte no-operation option used as padding | | `wscale 7` | the window shift count this side will use | | `sack 1 {a:b}` | a selective acknowledgment block on a later segment | Why each option exists and how it is negotiated is TCP header material; here the point is to find the numbers you need on the printed line. ## Turning win into bytes 1. Find the SYN and SYN-ACK for the connection in the capture. 2. Check that **both** carry `wscale`. If either does not, scaling is not in effect and `win` is already the byte count. 3. Take the shift count that the **sender of the segment you are reading** announced in its own SYN or SYN-ACK. 4. Multiply `win` by 2 raised to that shift: `win 502` with `wscale 7` is 502 × 128 = **64,256 bytes**. Some traps sit in those steps: - **Two directions, two factors.** The client's `wscale` applies to windows the client advertises, the server's to the server's. Using the peer's factor gives a wrong answer that looks plausible. - **It is a shift, not a multiplier.** `wscale 7` means × 128, not × 7. - **SYN windows are not scaled.** The `win 64240` on the SYN line is a plain byte count; the scale starts after the handshake. - **Mid-connection captures.** If the capture started after the handshake, the shift count was never captured. The `win` values are still correct header values; you just cannot convert them from this file. ## The same connection, two factors In the fragment above both sides announced `wscale 7`, so every later `win` value is multiplied by 128 whichever direction it travels. Suppose instead the server's SYN-ACK had carried `wscale 9`. Then the server's later `win 506` would mean 506 × 512 = **259,072 bytes**, while the client's `win 502` would still mean 64,256 bytes. Reading the factor from the wrong handshake line produces numbers that are off by a factor of four in either direction — and nothing on the data lines warns you, because tcpdump prints the same kind of small raw value for both sides. ## Why this matters in an incident A window that stays near zero, or one that never grows, can explain a slow transfer — and a raw `win 502` read as 502 bytes produces exactly that wrong story. Before claiming the receiver is the bottleneck, confirm the scale from the handshake, or capture again from before the connection opens. Why a small window caps throughput is flow-control material owned elsewhere; reading the number correctly is the part that belongs to the tcpdump line. One more check is worth a glance: `options` printed with `[bad opt]` means tcpdump met an option whose length makes no sense and stopped decoding the rest, and a line ending in `[|tcp]` was cut by the snapshot length, so its options may be incomplete.

  • The SYN offers wscale 7 but the SYN-ACK in the capture carries no wscale option; how do you read the later win values?
    Scaling takes effect only when both handshake segments carry the option, so neither side scales. The printed `win` is then the real byte count, capped by the 16-bit field at 65,535 bytes. Why that cap limits a long, fast path is a flow-control question; the reading is simply that no conversion applies.
  • Can you work out the real window from a tcpdump capture that started after the handshake?
    Not from the capture alone. The shift count appears only in the SYN and SYN-ACK, so without them you have the raw field and nothing to scale it by. Capture again from before the connection opens, or take the factor from the endpoint's own view of the connection.

saying these in an interview costs you the question

  • win 502 means the receiver will accept only 502 more bytes.
  • tcpdump applies the negotiated scale factor before printing win.
  • One wscale value applies to both directions of the connection.
  • wscale 7 means multiply the window by seven.
  • The window on the SYN line is already scaled.