PROTOCOLS-FUN . CAPTURE & FILTER

Capture & Filter

the wire, filteredBPF · tcpdump · Wireshark
rev 2026.08
tools tcpdump · tshark · Wireshark
rule capture drops, display hides
goal see only what matters

Two filter languages. Capture filter = BPF: runs in-kernel as packets arrive; rejected packets are gone. Display filter = Wireshark's own language: runs after capture over the buffer, hides rows without deleting. Same intent, different syntax -- udp port 37008 (BPF) vs udp.port == 37008 (display). Hover a field below for the byte, its BPF idiom, and its Wireshark field.

captureBPF, kernel, at capture timedisplayWireshark, post-hoc, on the buffer

Pick the wrong one and you either throw away evidence you needed or wait for a filter that never narrows the capture. The rule: capture to keep the haystack small, display to find the needle.

capture filter (BPF)display filter
whenas packets arrive, in the kernelafter capture, on stored packets
effectrejected packets are gone foreverhidden rows, still in the file
syntaxudp port 53udp.port == 53
powerfast, coarse, offset/mask mathper-field, dissector-aware, regex
used bytcpdump, tshark -f, Wireshark capture boxWireshark, tshark -Y
use it tokeep the capture small on a busy linkdrill into what you already caught
capture box vs display box. A display expression in a capture box (or vice versa) errors or matches nothing. tzsp is a Wireshark display dissector name; as a capture filter you'd write udp port 37008. When a filter "does nothing," check which language the box speaks.
on a busy link, capture narrowly. A full-rate capture on a 10G link fills disk and drops packets in seconds. Use a BPF capture filter to keep only the conversation you care about (a host, a port, a subnet), then use display filters to explore what you kept. You can never display-filter your way back to a packet the capture filter already dropped.
the pointevery sniffer speaks one or both of the same two languages

There is no "tcpdump syntax" versus "Wireshark syntax" - there is BPF (capture) and the display language, and each tool is a frontend to one or both. Learn the two languages once; the tools are just ergonomics. A capture filter written for tcpdump pastes straight into Wireshark's capture box or tshark -f; a display filter from Wireshark pastes straight into tshark -Y.

toolcapture (BPF)displaybest at
tcpdumpyes (filter arg)nofast CLI capture, headless boxes, writing pcap
tsharkyes (-f)yes (-Y)scripting, field extraction, stats, pipelines
Wiresharkyes (capture box)yes (main bar)interactive analysis, dissectors, follow-stream
termsharkyesyesWireshark's TUI - full display filters over SSH
ngrepyes + regexnogrep-style payload matching live on the wire
tcpflowyesnoreassembling and dumping TCP stream contents
not interchangeable. Capture and display filters don't cross over -- a display expression in a capture box matches nothing, and vice versa. Hence this guide is organized by the two languages, not by tool.
when tcpdump isn't enough: line-rate capture. At 10G+ and for lossless capture you drop below libpcap - AF_XDP, XDP/eBPF programs, or capture hardware. Those are for volume, not interactive troubleshooting; tcpdump and tshark (classic BPF via libpcap) stay the right tools at the console.
grammarqualifier + combinatormanpcap-filter(7)

A BPF expression is qualifiers (what to match) joined by and / or / not. Qualifiers come in three kinds: type (host, net, port, portrange), dir (src, dst), and proto (ether, ip, ip6, arp, tcp, udp, icmp).

expressionmatches
host 192.0.2.1any packet to or from that address
src net 198.51.100.0/24sourced from that subnet
tcp port 443TCP, either direction, port 443
portrange 6000-6100any port in the range
udp and not port 53UDP except DNS
icmp or icmp6either ICMP
vlan and host 192.0.2.1inside an 802.1Q tag (resets offsets)
tcpdump, the flags that matter
flagdoes
-i eth0interface (-i any = all)
-n / -nndon't resolve names / names and ports (do this - see gotchas)
-w f.pcap / -r f.pcapwrite raw / read back a capture
-c 100stop after 100 packets
-s Nsnaplen: keep only the first N bytes. Modern default is already full (262144), so -s0 is a legacy habit - use -s only to truncate
-A / -Xprint ASCII payload / hex + ASCII
-eshow the link-layer (MAC) header
-C 100 -W 10 -G 300ring buffer: 100MB files, keep 10, rotate every 300s
--printdecode to the screen AND write -w at the same time
-Uimmediate/unbuffered - flush each packet (needed when piping live)
--nanonanosecond timestamps (writes a different pcap magic; some old readers choke)
$ tcpdump -ni eth0 -w susp.pcap # full packets by default; add --print to watch live too > 'host 192.0.2.1 and tcp port 443'
syntaxproto[offset:size] & maskthe Rosettabyte / BPF / display

When the named qualifiers aren't enough, BPF lets you index raw bytes: proto[offset:size] reads size bytes (1, 2, or 4) at offset relative to that protocol's header, and you mask/compare them. This is a real 54-byte Ethernet+IPv4+TCP SYN frame - hover any field for its bytes, the BPF idiom that matches it, and the equivalent Wireshark field.

the classic offset idioms
BPFmatches
tcp[13] & 2 != 0SYN bit set (the flags byte, masked)
tcp[13] == 2SYN and nothing else - a bare connection attempt / SYN flood
tcp[13] & 0x12 == 0x12SYN+ACK (the reply half of a handshake)
ip[6] & 0x20 != 0More Fragments set - a fragmented packet
ip[9] == 6protocol is TCP (same as just tcp)
icmp[0] == 8ICMP Echo Request (ping)
greater 512packet longer than 512 bytes (amp-answer sized)
tcp[tcpflags] & (tcp-syn|tcp-ack) == tcp-synsame as tcp[13]==2 using readable flag constants (tcp-syn, tcp-fin, tcp-rst, tcp-ack...)
tcp[((tcp[12]&0xf0)>>2):4] = 0x47455420"GET " at the real payload start - computes header length so it survives TCP options
offsets are protocol-relative, and BPF does the arithmetic. tcp[13] is byte 13 of the TCP header - in this frame that lands at absolute byte 47, because BPF adds the 14-byte Ethernet and 20-byte IP headers for you. Change the link layer (VLAN, tunnels) and those base offsets shift, which is exactly why the vlan keyword exists.
the same trick at the firewall: match & drop

The offset-and-mask idea isn't only for observing traffic - the firewall uses it to drop it. iptables' u32 match reaches into header bytes exactly like BPF; nftables (its modern successor) does it with readable keywords or raw payload expressions. Same mental model, three contexts: capture → legacy drop → modern drop.

contextmatch a bare SYN (proto = TCP, flags byte = 0x02)
BPF (capture)tcp[13] == 2
iptables u32 (legacy)-m u32 --u32 "6&0xFF=0x6 && 4&0x1FFF=0 && 0>>22&0x3C@13&0xFF=0x02"
nftables (native)tcp flags & (syn|ack) == syn
nftables (raw payload)meta l4proto tcp @th,13,8 0x02
decoding 0>>22&0x3C@, u32's infamous incantation. It computes the IP header length so the match can jump past IP options to the transport header: read the first 4 bytes, >>22 shifts the IHL nibble into place, &0x3C masks it to IHL×4 (header length in bytes), and @ sets that as the new base offset. Then @13&0xFF=0x02 reads TCP byte 13 (the flags) and tests for SYN; the 4&0x1FFF=0 guard first excludes fragments, which have no usable transport offset.
on a modern box, reach for nftables. iptables is now the nft-backed compatibility CLI on most distros, and u32 survives only through the xt_u32 shim - there is no native u32 in nftables. Write new rules with nftables' readable tcp flags / ip protocol matching, dropping to raw @th,offset,len payload only when a field has no keyword. Add counter drop and a filter becomes a mitigation - the bridge to the attack & mitigation page.
formproto.field OP valuepowerdissector-aware, regex, per-field

Display filters address dissected fields by name and compare them. Operators: == != > < >= <=, plus contains (substring), matches (regex), and set membership in {..}. Combine with && || !.

display filtershows
ip.addr == 192.0.2.1packets to or from that host
tcp.port == 443 && tcp.flags.syn == 1SYNs to/from 443
tcp.flags.syn==1 && tcp.flags.ack==0bare SYNs (flood signature)
tcp.analysis.retransmissionWireshark-flagged retransmits (loss/pressure)
http.request.method == "GET"HTTP GET requests
dns.qry.name contains "example"DNS queries for a name substring
tls.handshake.type == 1TLS ClientHello - the packet you fingerprint (JA4)
frame.len > 512large frames (amp answers)
ip.ttl < 32low TTL - possible spoofing / distant OS
tcp.stream eq 3one whole TCP conversation (pair with Follow → TCP Stream)
frame contains "password"raw byte substring anywhere in the frame
tcp.len > 0segments actually carrying payload (skip bare ACKs)
let Wireshark write the filter. Right-click any field in the packet detail tree and Apply as Filter - it builds the exact proto.field == value for you. Follow → TCP/HTTP Stream reassembles a whole conversation, and Statistics → Conversations / Endpoints ranks talkers by bytes - the fastest way to spot the one host drowning a link.
use caseinvestigating hostile trafficpairs withthe attack & mitigation page

Turning "something is wrong" into a specific offender. Vendor-neutral, documentation addresses. See attack & mitigation for what each pattern is.

isolate a suspect address
$ tcpdump -nni eth0 'host 203.0.113.5' # capture # Wireshark display: ip.addr == 203.0.113.5
catch a SYN flood / count the sources
$ tcpdump -nni eth0 'tcp[13] == 2' # bare SYNs only $ tshark -nr cap.pcap -Y 'tcp.flags.syn==1 && tcp.flags.ack==0' \ > -T fields -e ip.src | sort | uniq -c | sort -rn # top SYN sources
amplification-sized answers
$ tcpdump -nni eth0 'udp port 53 and greater 512' # big DNS replies
extract ClientHellos to fingerprint
$ tshark -nr cap.pcap -Y 'tls.handshake.type == 1' \ > -T fields -e ip.src -e tls.handshake.extensions_server_name
rank talkers, exclude your own session
$ tcpdump -nni eth0 'not port 22' # don't capture your SSH $ tshark -nr cap.pcap -qz conv,ip # IP conversation table
into a pipeline or SIEM
$ tshark -nr cap.pcap -T ek > events.ndjson # newline JSON (Elasticsearch bulk) $ tshark -nr cap.pcap -qz io,stat,1 # throughput per second $ tshark -nr cap.pcap -qz endpoints,ip # rank hosts by bytes
the themequiet ways captures lie
capture vs displaythe #1 trap - a display expression in a capture box matches nothing. Know which language the box speaks.
snaplen truncationmodern tcpdump captures full packets by default, but an explicit short -s (or an old tool) grabs only the first N bytes and payload/TLS analysis silently breaks. Check capinfos if a capture looks cut off.
the -n rulewithout -n, tools do reverse-DNS on every address - generating traffic, polluting your own capture, and stalling on a busy link.
capturing your captureif you SSH in and sniff, you'll capture your own SSH + the mirror stream. Exclude with not port 22 and the mirror port.
promiscuous vs monitorpromiscuous sees all wired frames on the segment; 802.11 needs monitor mode to see other stations' frames at all.
offsets shiftVLAN tags and tunnels (TZSP, GRE, VXLAN) move the base offsets BPF assumes - use the vlan keyword or filter on the outer header.
you can't un-dropthe capture filter is destructive. When unsure, capture broadly to a ring buffer and narrow with display filters afterward.
piped capture lagstcpdump buffers when its output isn't a terminal, so a live pipe looks frozen. Add -U (immediate mode) to flush each packet.
"retransmission" is a guesstcp.analysis.* flags are Wireshark's own sequence-number heuristics, not fields on the wire - treat them as strong hints, not truth.
nanosecond pcaps--nano writes a different pcap magic number - fine in modern tools, misread by some old ones.
Wireshark profilessave columns, coloring, and go-to filters per investigation type instead of re-tuning the GUI every time.
the toolkit around the capture. capinfos summarizes a pcap (count, duration, rate); editcap slices/trims it (time range, dedupe, re-snaplen); mergecap joins files; tshark -qz runs the same statistics as Wireshark's menus, headless - the right move for captures too big to open in the GUI.
authoritative references. Capture / BPF: pcap-filter(7) and tcpdump(1). Display filters: the Wireshark display filter reference (browsable per protocol). Firewall payload matching: nftables raw payload.