Most people who study networking learn TCP from diagrams and textbooks. But there is a real difference between reading about the three-way handshake and actually watching it happen — byte by byte — in a live packet capture.
I spent a session doing exactly that: loading a real .pcap trace into Wireshark, expanding TCP headers, filtering traffic, reading sequence numbers, and tracing the full lifecycle of a TCP connection — from the first SYN all the way to the RST teardown.
This article walks through everything I observed. If you have Wireshark installed (it ships with Kali Linux), you can follow along with any .pcap file and see the same patterns yourself.
What Is a PCAP File and How Do You Open One?
A .pcap (packet capture) file is a recorded snapshot of network traffic. Wireshark can open these files and let you inspect every packet as if the traffic was live.
To open one:
wireshark trace-tcp.pcap
Or from the GUI: File → Open → select your .pcap file
Once loaded, you will see a three-panel view:
- Top panel — packet list (number, time, source, destination, protocol, info)
- Middle panel — protocol layers of the selected packet (expandable)
- Bottom panel — raw hex bytes
The Trace I Analyzed
The capture contained 1172 packets of a real HTTP download over TCP. Two IP addresses were involved:
| Role | IP Address |
|---|---|
| Client (my machine) | 192.168.1.122 |
| Remote web server | 64.238.147.113 |
The client fetched a PDF file from a web server on port 80. The trace covers the full TCP session: connection setup, data transfer, and teardown.
Step 1: Inspecting a TCP Segment's Header Fields
I selected a long packet from the middle of the trace (one of the 1434-byte data segments) and expanded the Transmission Control Protocol section in the middle panel.
Here is what each field tells you:
| Field | What it means |
|---|---|
| Source Port | Port on the sender's side (port 80 = web server) |
| Destination Port | Port on the receiver's side |
| Sequence Number | Position in the byte stream of the first payload byte |
| Acknowledgement Number | Next expected byte from the other direction |
| Header Length | Size of the TCP header in bytes |
| Flags | Control bits: SYN, ACK, FIN, RST, PSH, URG |
| Window Size | How much buffer space is available at the receiver |
| Checksum | Error detection value |
| Options | Negotiated parameters like MSS, timestamps, SACK |
| Payload | The actual data being transferred |
The Urgent Pointer field exists in the header but was zeroed out in every packet I saw — it is rarely used in modern TCP connections.
Step 2: Understanding the TCP Segment Structure
The TCP segment layout looks like this:
| Source Port (2B) | Dest Port (2B) |
| Sequence Number (4B) |
| Acknowledgement Number (4B) |
| Header Len + Flags (2B) | Window (2B) |
| Checksum (2B) | Urgent Ptr (2B) |
| Options (variable, if any) |
| Payload (N bytes) |
A few things to note from Wireshark:
- Header Length and Flags are packed together into 2 bytes — Wireshark separates and labels them for you
- Options were present in most packets and were always a multiple of 4 bytes in length
- Payload was absent on pure ACK segments (the ACK-only packets are typically only 66 bytes)
Step 3: The Three-Way Handshake
To filter only SYN packets:
tcp.flags.syn == 1
Here is what I observed in the first three packets:
| Packet | Direction | Flags | Seq | Ack |
|---|---|---|---|---|
| 1 (t=0.000000) | Client → Server | SYN | 0 | — |
| 2 (t=0.088010) | Server → Client | SYN, ACK | 0 | 1 |
| 3 (t=0.088080) | Client → Server | ACK | 1 | 1 |
Then immediately after packet 3, the client sent the HTTP GET request (packet 4, t=0.088579).
Key observations:
- The initial SYN carries no ACK number — it is the first message, so there is nothing to acknowledge yet
- Wireshark shows relative sequence numbers (starting from 0), not the actual 32-bit values negotiated at the start
- The ACK number is always the received Seq number + 1
- The RTT (Round Trip Time) between SYN and SYN-ACK was 88 ms in this trace
- The third packet (ACK) and the HTTP GET (data) were sent in separate packets even though they could theoretically be combined
Step 4: TCP Connection Options
The SYN packets also negotiate TCP options. I expanded the Options section and found:
| Option | Purpose |
|---|---|
| Maximum Segment Size (MSS) | Tells the other side the largest segment it can receive |
| Window Scale | Scales up the window size field beyond 65535 bytes |
| SACK Permitted | Enables selective acknowledgements for better loss recovery |
| Timestamps | Used to estimate RTT on each packet |
| NOP / End of Option List | Padding — used to align options to 4-byte boundaries |
Both sides advertised these options in their SYN. MSS was only sent on the SYN (it does not change), while Timestamps were included on every data packet to keep a fresh RTT estimate.
Step 5: FIN Teardown
To find the teardown, I scrolled to the end of the trace. In this particular capture, the connection was torn down with a RST (Reset) rather than a graceful FIN exchange.
| Packet | Direction | Flags | Seq | Ack |
|---|---|---|---|---|
| Last | Client → Server | RST | 146 | 1056827 |
Points worth noting:
- RST teardown is abrupt — no acknowledgement is needed from the other side
- A FIN-based teardown (when it happens) is symmetric, similar to the handshake: each side sends a FIN and ACKs the other's FIN
- In a FIN exchange, the FIN flag occupies one sequence number (just like SYN does), so the ACK is always FIN Seq + 1
Step 6: Analyzing the Data Transfer with IO Graphs
To visualize throughput over time:
Statistics → IO Graph
Configuration I used:
- X-Axis: Tick interval = 0.1 sec, Pixels per tick = 10
- Y-Axis: Unit = Bits/Tick
- Graph 1 filter:
tcp.srcport == 80(download traffic) - Graph 2 filter:
tcp.dstport == 80(upload/ACK traffic)
What I saw:
- Download started slow and ramped up exponentially — this is TCP slow start in action
- Once running at full speed, throughput settled around 2.5 Mbps (250 packets/sec)
- Upload was a thin, steady line of ACK traffic: about 120 packets/sec, ~60,000 bits/sec
- The download finished cleanly with no visible retransmission events
The data packets were 1434 bytes each, of which 1368 bytes were TCP payload. That means ~95% of the download was actual content — very efficient.
Step 7: Delayed ACKs and Flow Control
Looking at the middle section of the trace, I noticed the client was not ACKing every single data packet. Instead, one ACK was sent for roughly every two received data segments. This is Delayed ACK — a standard TCP optimization that cuts the number of ACK packets in half.
I also observed the Window Size field on every segment. Throughout the download, the window stayed well above zero. If it ever hits zero, TCP enters a flow control stall — the sender is blocked until the receiver empties its buffer and advertises a non-zero window.
During the download, since the client was only receiving:
- The sequence numbers of incoming server packets kept increasing
- The ACK numbers in the client's outgoing packets increased to match
- The sequence number of client's outgoing packets stayed flat (no new data being sent)
How to Verify Your Own Trace
If you open any .pcap of HTTP traffic, you can verify these same patterns:
# Filter three-way handshake
tcp.flags.syn == 1
# Filter only download traffic (from web server)
tcp.srcport == 80
# Filter only upload/ACK traffic (to web server)
tcp.dstport == 80
# Find RST teardown
tcp.flags.reset == 1
# Find FIN teardown
tcp.flags.fin == 1
Use Statistics → IO Graph with Bits/Tick on Y-axis to observe slow start and steady-state throughput.
My Full Observation Table
Here is a summary of all observation points I identified in the trace:
| Obs. | Packet No. | Time | Description |
|---|---|---|---|
| a | 1 | 0.000000 | Client sends SYN — initiates TCP connection |
| b | 2 | 0.088010 | Server replies with SYN-ACK |
| c | 3 | 0.088080 | Client sends ACK — handshake complete |
| d | 4 | 0.088579 | Client sends HTTP GET request |
| e | 5 | 0.177819 | Server ACKs the HTTP GET |
| f | 6 | 0.178321 | Server begins sending HTTP response (PSH+ACK) |
| g | 7 | 0.178388 | Client ACKs first data segment |
| h | 8 | 0.189114 | Server sends another TCP data segment |
| i | 9 | 0.266705 | Server continues streaming data segments |
| j | 10 | 0.266787 | Client sends delayed ACK for previous segments |
What I Learned
Doing this kind of hands-on analysis changed the way I think about TCP. A few things that stood out:
Relative vs absolute sequence numbers — Wireshark shows you 0-based numbers by default. The real sequence numbers are large random 32-bit values chosen at connection time. Always check whether you are looking at relative or absolute numbers.
RTT from packet timestamps — You can measure the RTT directly from the trace without any extra tools. The gap between SYN (packet 1) and SYN-ACK (packet 2) was exactly 88ms, which matched the FIN exchange RTT too.
Slow start is visible — The IO Graph ramp from 0 to 2.5 Mbps is not gradual — it has the characteristic exponential curve of TCP slow start. This is not a network artifact, it is the protocol deliberately probing for available bandwidth.
Efficiency is high — 95% of the download bytes were actual payload. The overhead from headers and ACK packets was small.
RST vs FIN — Graceful FIN shutdown is symmetric and takes at least one round trip. RST is a hard cut that requires no acknowledgement. Both are normal and you will see both in real traffic.
Common Mistakes to Avoid
| Mistake | What Actually Happens |
|---|---|
| Confusing sequence and ACK numbers | Seq = my data position; ACK = what I expect from you next |
| Assuming Wireshark shows real Seq numbers | It shows relative numbers by default — enable absolute in preferences if needed |
| Using TCP Stream Graph for receiver-side traces | Those graphs assume you captured at the sender — use IO Graph instead |
| Ignoring the Window field | A zero window stalls the connection entirely |
| Expecting one ACK per packet | Delayed ACK means typically one ACK per two data packets |
| Treating RST and FIN as the same | RST is abrupt (no handshake); FIN is graceful (symmetric exchange) |
Conclusion
Wireshark turns abstract protocol concepts into something you can see and measure. The three-way handshake, slow start, delayed ACKs, flow control, and connection teardown — they are all there in the packets if you know what to look for.
If you have never done a trace analysis like this, I would encourage you to download any .pcap from a public dataset and try the filters from this article. The patterns are consistent across real traffic.
The next time someone mentions TCP, you will not just remember the diagram — you will remember what the bytes actually looked like.




Top comments (1)
Some comments may only be visible to logged-in visitors. Sign in to view all comments.