Analyst Field Guide · Reading a Packet Capture
A packet capture is the closest thing you get to a recording of what actually crossed the network. Unlike a user's account of a problem, the packets are the traffic — but only the traffic that passed your capture point, and only the parts that weren't encrypted. This guide is how to open a capture, cut it down to the conversation you care about, read what the protocols are telling you, and stay honest about what a capture can and can't prove.
A capture is ground truth with an asterisk. The bytes in the file really did cross the wire — that's what makes a capture the strongest evidence at this layer, stronger than any log summary or user report (see the Eliciting Information guide). The asterisk is that a capture only ever shows what passed one point, during one window, unencrypted. Reading captures well is two skills at once: pulling the signal out of the noise, and never forgetting the edges of what the file can actually tell you.
A capture file (.pcap, or the more modern .pcapng) is a timestamped record of frames as seen at one observation point. It's produced by tcpdump or dumpcap on the command line, by Wireshark's GUI, or by tshark (Wireshark's CLI, sharing the same dissection engine). The single most important thing to establish before reading a byte is where it was captured — because that determines what could possibly be in it.
So the capture is evidence, but bounded evidence — the same discipline the Eliciting guide applies to a witness account applies here to a file. Establish the vantage point, the time window, and the filters that were in force at capture time, and only then start reading.
Wireshark has two completely different filter systems, and confusing them is the most common beginner mistake. They use different syntax and act at different times.
| Capture filter (BPF) | Display filter (Wireshark) |
|---|---|
| Applied before/during capture — decides what gets written to disk | Applied after capture — hides packets from view without deleting them |
BPF syntax, same as tcpdump: tcp port 80, host 10.0.0.5, net 192.168.1.0/24 | Field syntax: tcp.port == 80, ip.addr == 10.0.0.5 |
| Less expressive; runs in-kernel; lossy — what you don't capture is gone | Very expressive, protocol-field-aware; non-destructive; change it anytime |
You can always tighten a display filter later, but you can never recover a packet a capture filter threw away. On a quiet link, capture everything and filter in analysis. Use a capture filter only when volume forces it — a busy uplink, a long-running capture, limited disk — and even then keep it as loose as you can afford. One useful exception when capturing remotely: exclude your own management session (not port 22) so it doesn't drown the trace. Wireshark's filter bar shades valid display filters green and invalid ones red as you type.
The same split exists on the command line: tcpdump -w out.pcap 'tcp port 443' uses a BPF capture filter; tshark -r out.pcap -Y 'http.request' applies a display filter to an existing file.
Every packet is the encapsulation from the Networking guide, frozen and readable. Wireshark's detail pane shows it as a tree — outermost layer at the top, application data at the bottom — and each layer gives you fields to filter and pivot on.
This is also the fastest way to learn filters: rather than memorizing field names, click the value you care about in the detail pane, right-click, and let Wireshark build the display filter for you.
A capture is thousands of interleaved packets from many conversations. What defines a single conversation is the 5-tuple: source IP, source port, destination IP, destination port, and protocol. Wireshark numbers each TCP conversation with a tcp.stream index so you can isolate and reassemble it.
For a bird's-eye view before you drill in, Statistics → Protocol Hierarchy shows what protocols make up the file, and Conversations and Endpoints show who talked and how much — often the quickest way to spot the one host or flow that doesn't belong.
Most of what you'll read is TCP, and its control packets are a fast diagnostic. A healthy connection opens with the three-way handshake, exchanges data, and closes in order; the ways it fails each have a distinct signature.
tcp.analysis.retransmission.Two cautions on Wireshark's TCP analysis. Its retransmission and "out-of-order" markers are inferences from sequence numbers seen at your capture point — a mid-path capture can misjudge them. And a lone RST is normal at the end of many connections; it's an early or unexpected RST that's the signal.
A capture's power comes with hard limits, and reading captures responsibly means knowing them cold. Overstating what a file proves is the analyst's version of the false confidence this collection keeps warning about.
Encryption. For TLS (HTTPS, and most modern traffic) you see the handshake, the SNI/server name, the certificate, sizes, and timing — but not the plaintext, unless you have the session keys and load them into Wireshark. "I captured it" is not "I can read it." Vantage. You only ever saw one point; traffic on other paths, or intra-switch traffic never mirrored to you, simply isn't there. Loss. A SPAN port under load, a small snaplen truncating payloads, or a full buffer means packets are missing from the file — and a missing packet looks exactly like a packet that was never sent. Rewriting. NAT and proxies (see the HTTP and VPN guides) change addresses along the path, so the IPs you see may not be the real endpoints. The load-bearing rule: absence of a packet is not proof the event didn't happen — it may only mean it didn't cross your capture point. A capture tells you what you did see; be precise about the rest.
None of this makes captures weak evidence — it makes them bounded evidence, which is different. Stated with its limits, a capture is often the most reliable artifact you have; stated as if it were omniscient, it will mislead you.
The "unless you have the session keys" escape hatch above has a standard mechanism. Most TLS clients — browsers, curl, many apps built on common TLS libraries — will, when the SSLKEYLOGFILE environment variable points at a writable file, log each connection's session secrets there. Point Wireshark at that file (its TLS preference for the key-log filename) and it decrypts exactly those sessions. The decisive limitation: this only works when you control the client and set the variable on the machine generating the traffic before the handshake. You cannot retroactively decrypt someone else's captured TLS — modern key exchange (ECDHE) gives forward secrecy, so even the server's private key won't recover past sessions without the logged secrets. That makes SSLKEYLOGFILE a tool for debugging your own or lab traffic, not for reading arbitrary traffic off the wire (the other route — a TLS-terminating inspection proxy — is a deliberate interception with its own consent and trust implications). It changes what you can read; it does not change whose traffic you're entitled to read.
Analysis is noise reduction. Start broad, confirm one thing, then narrow — the same funnel the Eliciting guide uses for questions and the First-Principles guide uses for problems. A small set of display filters covers most of the work.
| Goal | Display filter |
|---|---|
| Everything to/from one host | ip.addr == 10.0.0.5 (source or destination; use ip.src / ip.dst to pin direction) |
| One service | tcp.port == 443 · or a protocol name alone: dns, tls, http |
| Connection attempts / failures | tcp.flags.syn == 1 and tcp.flags.ack == 0 · resets: tcp.flags.reset == 1 |
| Loss & performance | tcp.analysis.retransmission |
| What was requested | http.request · dns.qry.name == "example.com" · failed lookups: dns.flags.rcode != 0 |
| Which encrypted sites | tls.handshake.type == 1 (Client Hello) — reveals the SNI even though the payload is encrypted |
| Exclude your own noise | not (arp or dns) · negate an address correctly as !(ip.addr == 10.0.0.5), not ip.addr != … |
First, ip.addr != x does not mean "not involving x" — because ip.addr matches both source and destination, the correct exclusion is !(ip.addr == x). Second, capture-filter syntax (tcp port 80) pasted into the display-filter bar just errors — they're different languages (§2). Beyond filters, Statistics → Protocol Hierarchy, Conversations, and File → Export Objects → HTTP (which pulls files out of reassembled HTTP) are the high-leverage menus.
With the mechanics in hand, the skill is recognizing shapes. A few common patterns and what they look like in a capture:
| Pattern | What it looks like |
|---|---|
| Normal web fetch | DNS query + response for the name, then a TCP handshake to the resolved IP on 443, then a TLS Client Hello carrying that hostname as SNI, then encrypted data. |
| Connection refused | SYN immediately answered by RST — the port isn't open. Distinct from a SYN with no reply at all (filtered/down). |
| Port scan | One source sending many SYNs to many ports or hosts, most getting RST or nothing back, few completing a handshake. |
| Plaintext credentials | Readable payload on an unencrypted protocol (HTTP, FTP, Telnet) — the very thing TLS exists to prevent. Follow the stream to confirm, and treat as an exposure. |
| Beaconing | A host making small, regular, repeating connections to one external endpoint at a near-fixed interval — a pattern worth investigating, though backup agents and telemetry look similar, so corroborate before concluding (§9). |
| DNS anomalies | Unusually long or high-volume queries, or many NXDOMAIN (dns.flags.rcode == 3) — can indicate tunneling or algorithmically-generated domains, but has benign causes too. |
Notice the hedges. Each shape is a hypothesis, not a verdict — beaconing can be a backup client, odd DNS can be a misconfigured app. The capture tells you the shape; confirming the cause is the reasoning work of the Critical Thinking and Root Cause Analysis guides, against additional evidence.
If a capture might support an incident finding, a dispute, or anything with consequences, treat the file as evidence from the moment it exists — the discipline is the Incident Response guide's, applied to a .pcap.
A capture answers "what crossed this point, in this window, in clear?" — precisely and reliably. It does not answer "what happened everywhere," "what was in the encrypted bytes," or "why." Keep your written findings inside what the file actually supports, flag what you're inferring, and say where corroboration is still needed. That precision is what makes packet evidence trusted.
| Belief | Reality |
|---|---|
| "If it's not in the capture, it didn't happen" | It may just not have crossed your capture point, or was dropped/truncated. Absence is not proof (§6). |
| "I captured the traffic, so I can read it" | TLS payloads are encrypted. Without the session keys you get the handshake, SNI, cert, and timing — not the content. |
| "A capture filter and a display filter are interchangeable" | Different syntax, different timing. tcp port 80 (BPF, at capture) vs tcp.port == 80 (display, after). One is lossy; the other isn't. |
| "The source IP in the packet is the real sender" | NAT, proxies, and CDNs rewrite addresses; a mid-path capture shows translated IPs. And source IPs can be spoofed. |
| "Wireshark says retransmission, so there was loss" | That's an inference from sequence numbers at your vantage point; a mid-path capture can misjudge it. Corroborate. |
| "Beaconing in the capture means malware" | Regular callbacks also describe backup agents, update checkers, and telemetry. It's a lead to investigate, not a verdict (§8). |
Establish the capture point, time window, and any capture filter. The file shows only what passed that point, in that window, unencrypted. Hash it if it's evidence; analyze a copy.
Capture filter = BPF, before capture, lossy: tcp port 443, host 10.0.0.5. Display filter = after, non-destructive: tcp.port == 443, ip.addr == 10.0.0.5. Capture broad, filter narrow.
ip.addr == x · tcp.port == n · dns / tls / http.request · dns.qry.name == "…" · tls.handshake.type == 1 (SNI) · tcp.flags.reset == 1 · tcp.analysis.retransmission. Exclude an IP with !(ip.addr == x).
SYN → SYN/ACK → ACK = open. SYN, no reply = down/filtered. SYN then RST = refused. Repeated SYNs / retransmits = loss. Follow → TCP Stream to reassemble; Statistics → Conversations for who-talked-to-whom.
Encrypted = opaque without keys. Absence ≠ proof. Source IPs may be NAT'd or spoofed. Wireshark's analysis flags are inferences. Every shape is a hypothesis — corroborate with logs before concluding.
Tool syntax here (Wireshark display filters such as ip.addr, tcp.port, tcp.flags.syn, tcp.flags.reset, tcp.analysis.retransmission, dns.qry.name, tls.handshake.type, and BPF capture filters such as host, port, net, tcp[tcpflags]) reflects long-stable Wireshark/tcpdump syntax, but field names and behavior can change between versions — verify against the current Wireshark display-filter reference and pcap-filter / tcpdump man pages before relying on an exact expression. Protocol behavior described (the TCP three-way handshake, RST semantics, the 5-tuple, TLS exposing SNI but not payload) is stable. No capture is complete or self-interpreting: a file bounds its own conclusions to what crossed one point, in one window, in clear text; absence of a packet is not proof of absence of an event, source addresses may be translated or spoofed, and Wireshark's retransmission/out-of-order markers are inferences from the capture vantage. Companion to Networking ("Follow the Packet" — the encapsulation you're now reading), HTTP & the Application Layer (the payloads above TCP), VPN & IPsec and Wi-Fi (why so much is encrypted, and capturing the air), Incident Response and the forthcoming Logging & Evidence (captures as correlated, preserved evidence), and Eliciting Information and Critical Thinking (a capture is bounded observation, and each traffic shape is a hypothesis to test).