HackBlue

Tools · Wireshark · 13 / 30

Zero windows

A zero window is the receiver telling the sender to stop. The stall is on the side that advertised zero.

12 min read · practice only with authorization

The receiver is the one who says zero

Every TCP frame advertises a window, the number of bytes the sender of that frame is willing to receive. When the value is zero, the peer must stop sending application data. tcp.analysis.zero_window marks the frame that advertised zero. The host in the source column of that frame is the receiver that is full, not the sender who has been paused.

tcp.window_size is the scaled window Wireshark calculated. tcp.window_size_value is the raw 16-bit field. A raw value of 20 with a scale of 128 is a window of 2560 bytes, not a tiny one. Read tcp.window_size when the handshake was in the file and the scale is known. Read the raw field when you are checking what the frame literally contained.

Snippet
tcp.analysis.zero_window && ip.src == 10.8.0.20

Probes and the window update

While the window stays zero, the sender may transmit a zero-window probe, marked tcp.analysis.zero_window_probe. The receiver answers with tcp.analysis.zero_window_probe_ack and a still-zero window, or with a window update. tcp.analysis.window_update marks an ACK that opens the window again without carrying data. The time from the first zero to that update is how long the sender was blocked.

A full window, tcp.analysis.window_full, is the related warning that the sender has filled the window the receiver offered. The receiver has not necessarily advertised zero yet. Both flags point at a receiver that is not reading as fast as the sender is writing. The application on the receiving host is the usual place to look, in a lab where you can look: a paused client, a full disk, a process that stopped calling read.

Worked case: the lab server stopped reading

During a lab upload to 10.8.0.20, the transfer stalls for eight seconds. You filter tcp.analysis.zero_window && ip.src == 10.8.0.20 and find the server advertising zero at frame 2200. The client’s next frames are probes, not new payload. Frame 2310 is a window update from the server, and the client resumes. Set a time reference on 2200 and the update reads as 8.1 seconds later.

The client’s window never went to zero. Blaming the client’s uplink is the wrong direction. The server said it could take no more bytes, then changed its mind. On a lab host you administer, that lines up with the receiving process. On a capture from production, you hand the stream index, the eight seconds, and the source of the zero to the people who own that process. You do not need to guess the bug from the packet alone.

A missing SYN muddies the number

If the capture starts after the handshake, Wireshark may not know the scale factor. A window that looks absurdly small or absurdly large may be a math problem in the dissector. Check whether the SYN is in the file before you quote tcp.window_size. The raw field remains quotable either way.

Keepalives are easy to confuse with probes if you only read the length. tcp.analysis.keep_alive is a probe on an idle connection. A zero-window probe happens while the peer has advertised zero. Read the flag, not just the one-byte length.

Diagram

From a stall to the host that advertised zero

  1. Zero

    Find tcp.analysis.zero_window. The source of that frame is the host that is full.

    Zeros from the server
    tcp.analysis.zero_window && ip.src == 10.8.0.20
  2. Probe

    The peer’s zero-window probes keep the conversation alive without sending the bulk data.

  3. Update

    A window update is the frame that lets the sender continue. Time it from the first zero.

  4. Scale

    Quote tcp.window_size only if the handshake was captured. Otherwise quote the raw value.

Check yourself

1. Frame 2200 is marked tcp.analysis.zero_window and its source is 10.8.0.20. Who has stopped accepting data?

2. The raw window field is 20 and the SYN negotiated a scale of 128. Which number is the window in bytes?

3. How long was the sender blocked?