skip to content

What does Wireshark's tcp.stream field number, and why can one client and server port pair end up with two different stream indexes?

level: middleimportance: should knowfreq 17%

answer

  1. a counter, not a port tuple
  2. numbered from 0 per file
  3. a fresh SYN on the same ports
  4. port numbers reused
  5. renumbered when the file changes

basics

~20 s

tcp.stream numbers each TCP conversation in a file from 0, in order of first appearance. A new SYN reusing the same addresses and ports, with a new initial sequence number or after a FIN or RST, gets a new index.

solid answer

~50 s

`tcp.stream` is the **Stream index**, a counter Wireshark assigns while dissecting: the first TCP conversation in the file is 0, the next new one 1, and so on. It is not stored in the capture, so it is recomputed every time a file is opened, and a subset you save or a file you merge gets new numbers. A conversation is normally one address-and-port pair, but when a SYN arrives on a pair Wireshark already knows, with a different initial sequence number or after that conversation saw a FIN or RST, Wireshark starts a new conversation with a new index and marks the packet `[TCP Port numbers reused]`. That is why `tcp.stream eq N` isolates one connection where a port filter can lump several retries together. Other protocols keep their own counters: in Wireshark 4.6, Follow UDP Stream filters on `udp.stream` and Follow TLS Stream on `tls.stream`.

go deeper

for a junior

Recall that tcp.stream is a conversation number starting at 0 and that Follow TCP Stream filters on it.

for a middle

Explain how Wireshark decides a SYN starts a new conversation on a reused port pair, and why the index changes when the file changes.

for a senior

Show that you hand over evidence with endpoints and timestamps rather than bare stream numbers, and that you separate retries on a recycled source port before comparing them.

for a principal

Consider how an analysis team names connections in tickets and reports so findings survive re-cut, merged and rotated captures.

## What the index is `tcp.stream`, labelled **Stream index** in the packet details, is a number Wireshark gives every TCP **conversation** it finds in a capture. It is the handle the rest of the tool uses: **Follow TCP Stream** applies `tcp.stream eq N`, the Follow dialog's Stream selector steps through the same numbers, and `tshark -z follow,tcp,...,N` takes it as an argument. It identifies a connection far more precisely than an address or a port does, and it is cheap to filter on. Because it is a single integer, it is also a convenient key when you export fields with `tshark` and group packets per connection in a script or spreadsheet. ## How Wireshark assigns it 1. The first packet of an address-and-port pair Wireshark has not seen creates a conversation and takes the next free number, starting at **0**. 2. Later packets of the same pair join that conversation and carry the same index. 3. A SYN on a known pair whose initial sequence number differs from the one the conversation started with, or a SYN arriving after that conversation saw a **FIN** or **RST**, creates a **new** conversation with a new index; the packet is marked `[TCP Port numbers reused]`. 4. A SYN that repeats the original initial sequence number on a conversation with no FIN or RST is treated as a repeat of the same handshake and keeps the old index. 5. A SYN-ACK is handled the same way when the capture is missing the SYN of the new connection. ## Why one port pair can become two streams Clients that open many short connections, NAT gateways and proxies recycle source ports, so a capture covering hours can hold several connections with an identical pair of addresses and ports. A filter such as `tcp.port == 50412` lumps them into one apparent session, with a handshake in the middle that makes no sense. The stream index keeps them apart. In the corrupted-upload case this matters directly: a client that retries an upload from the same source port produces two attempts, two indexes, and two sets of bytes you can compare. ## Why the number is not portable - It is **computed during dissection**, not written into pcap or pcapng, and the counter restarts at 0 whenever a file is opened. - Saving only stream 42 with **Export Specified Packets** produces a file in which that connection is stream **0**. - Merging files, or opening one file of a ring buffer on its own, renumbers every connection. - A ticket that says "look at stream 7" is therefore only meaningful together with the exact file; quoting the endpoints and a timestamp as well survives any re-cut of the capture. ## Other stream counters Wireshark keeps a separate counter or identifier for each protocol it can follow, and in Wireshark 4.6 the Follow dialogs apply these filters: | Follow menu entry | Filter applied | What the numbers mean | |---|---|---| | TCP Stream | `tcp.stream eq N` | TCP conversation in this file | | UDP Stream | `udp.stream eq N` | UDP conversation in this file | | TLS Stream | `tls.stream eq N` | TLS session counter, numbered separately from TCP | | HTTP/2 Stream | `tcp.stream eq N and http2.streamid eq M` | TCP connection plus the HTTP/2 stream identifier | | QUIC Stream | `quic.connection.number eq N and quic.stream.stream_id eq M` | QUIC connection plus the QUIC stream ID | Because the TLS counter only advances for sessions that carry TLS, `tls.stream` 0 can sit on `tcp.stream` 3; never assume the two numbers match. For HTTP/2 and QUIC the dialog adds a **Substream** selector, since many streams share one connection; why those protocols multiplex is a protocol subject of its own. ## Working with the index - List conversations with their index: `tshark -r capture.pcapng -T fields -e tcp.stream -e ip.src -e tcp.srcport -e ip.dst -e tcp.dstport -Y 'tcp.flags.syn == 1 && tcp.flags.ack == 0'`. - Open **Statistics > Conversations**, select a TCP row and press **Follow Stream...** to jump straight to that connection. - Add `tcp.stream` as a packet-list column when a capture interleaves many connections; the eye picks up a change of number faster than a change of port. - Compare two attempts on a recycled port by opening Follow TCP Stream on one and moving the dialog's Stream selector to the other.

  • How do you follow one HTTP/2 request rather than the whole TCP connection that carries it?
    Use **Analyze > Follow > HTTP/2 Stream**. Its Stream selector picks the TCP connection and its Substream selector picks the HTTP/2 stream identifier, so the filter becomes `tcp.stream eq N and http2.streamid eq M`. Over TLS the frames must be decrypted first. QUIC works the same way with the connection number and the QUIC stream ID.
  • A colleague says the attack is in stream 7, but stream 7 in your copy is a DNS-over-TCP lookup. What went wrong?
    The index is not stored in the file; it is recomputed per file in order of first appearance. If either of you filtered, re-saved, merged or opened a different ring-buffer file, the numbering differs. Ask for the endpoints, ports and a timestamp, or for the exact file, and find the connection from those.

saying these in an interview costs you the question

  • tcp.stream is a value stored in each packet of the pcapng file.
  • Two connections with the same addresses and ports always share one tcp.stream index.
  • Filtering on the client's source port isolates a connection just as well as tcp.stream does.
  • Follow TLS Stream filters on the same number as the connection's tcp.stream.
  • Wireshark numbers TCP streams from 1 in every capture file.