Wireshark Cheatsheet

Capture Filters (BPF)

Use this Wireshark reference while you build software engineering projects, review code, or refresh the syntax you reach for most.

Two Different Languages

Wireshark has two filter syntaxes and they share nothing. Mixing them up is the single most common Wireshark mistake.

Capture filterDisplay filter
LanguageBPF, same as tcpdumpWireshark display filter
WhereCapture options (Ctrl+K), -fFilter bar, -Y / -R
AppliedIn the kernel, before writingAfter dissection, on the saved packets
UndoableNo, excluded packets are gone foreverYes, change it freely
Host syntaxhost 10.0.0.1ip.addr == 10.0.0.1
Port syntaxport 443tcp.port == 443
Knows protocolsOnly what the byte offsets sayEvery dissector, 3000+ protocols
tshark -f "tcp port 443"       # capture filter, BPF
tshark -Y "tcp.port == 443"    # display filter

BPF has no idea what HTTP or DNS is beyond a port number, so port 53 is as close to "capture DNS" as a capture filter gets. Anything protocol-aware belongs in a display filter.

Primitives

A BPF expression is built from primitives, each of which is a direction, a type, and a value.

PrimitiveMatches
host 10.0.0.1Either direction, IPv4 or IPv6
src host 10.0.0.1 / dst host …One direction only
net 10.0.0.0/8, net 10.0.0.0 mask 255.0.0.0Subnet
port 443TCP or UDP, either direction
src port 53 / dst port 53Directional port
portrange 8000-8100Port range
ether host 00:1a:2b:3c:4d:5eMAC address
ether broadcast, ether multicastLayer 2 destination class
ip, ip6, arp, rarpNetwork-layer protocol
tcp, udp, icmp, icmp6, sctpTransport
vlan, vlan 100802.1Q, shifts all later offsets by 4 bytes
mpls, pppoed, pppoesEncapsulations, also shift offsets
less 128, greater 1400Frame length
proto \ip, ip proto 47Explicit protocol number, 47 is GRE

Combine with and / &&, or / ||, not / !. Parentheses need escaping or quoting in a shell.

tshark -f "host 10.0.0.5 and not port 22"
tshark -f "tcp and (port 80 or port 443) and not net 10.0.0.0/8"

not host x and not host y is the correct way to exclude two hosts. not (host x or host y) is equivalent. not host x or not host y is always true and excludes nothing.

Byte Offsets and TCP Flags

proto[offset:size] reads raw bytes. Size defaults to 1 and may be 1, 2, or 4. This is how BPF reaches things it has no keyword for. A byte read is only a filter once it is compared, so ip[9] is a value and ip[9] == 6 is the filter.

ExpressionReads
ip[0] & 0x0fIHL, header length in 32-bit words
ip[1] >> 2DSCP, the upper 6 bits of the TOS byte
ip[8]TTL
ip[9]Protocol number, 6 TCP, 17 UDP, 1 ICMP
tcp[13]The flags byte
tcp[tcpflags]Same byte, by name
tcp[0:2]Source port
icmp[0]ICMP type
udp[8:4]First four payload bytes of UDP

The flags byte, bit by bit:

FlagBit valuetcp[13] maskNamed constant
FIN10x01tcp-fin
SYN20x02tcp-syn
RST40x04tcp-rst
PSH80x08tcp-push
ACK160x10tcp-ack
URG320x20tcp-urg
ECE640x40tcp-ece
CWR1280x80tcp-cwr
# SYN set, regardless of other flags
tshark -f "tcp[tcpflags] & tcp-syn != 0"
tshark -f "tcp[13] & 2 != 0"

# SYN only, so connection attempts and not the SYN/ACK reply
tshark -f "tcp[tcpflags] & (tcp-syn|tcp-ack) == tcp-syn"

# Resets
tshark -f "tcp[tcpflags] & tcp-rst != 0"

# Any packet carrying TCP payload, the standard "data only" idiom
tshark -f "tcp[((tcp[12:1] & 0xf0) >> 2)] != 0"

The last one deserves the explanation. tcp[12] holds the data offset in its high nibble, in 32-bit words. & 0xf0 isolates it, >> 2 divides by 4 to shift the nibble down and multiplies by 4 to convert words to bytes, which cancel into a single shift. The result is the TCP header length in bytes, so tcp[that_offset] is the first payload byte. Comparing it to zero is a proxy for "there is at least one payload byte".

Recipes

# DNS only
-f "port 53"
-f "udp port 53 or tcp port 53"

# TLS handshakes only, content type 22 at the start of the record
-f "tcp port 443 and (tcp[((tcp[12:1] & 0xf0) >> 2)] = 0x16)"

# HTTP requests with a payload (GET, POST, HEAD by their first bytes)
-f "tcp port 80 and (((ip[2:2] - ((ip[0]&0xf)<<2)) - ((tcp[12]&0xf0)>>2)) != 0)"
-f "tcp[20:4] = 0x47455420"     # "GET " right after a 20-byte TCP header

# One host pair, both directions
-f "host 10.0.0.5 and host 93.184.216.34"

# Exclude your own SSH session so the capture does not feed itself
-f "not (tcp port 22 and host 203.0.113.9)"
-f "not port 22"

# Broadcast and multicast noise only, or everything but
-f "ether broadcast or ether multicast"
-f "not ether broadcast and not ether multicast"

# DHCP, ARP, ICMP
-f "udp port 67 or udp port 68"
-f "arp"
-f "icmp or icmp6"

# VLAN-tagged traffic on VLAN 100
-f "vlan 100"

# Small packets only, useful for pure ACK storms
-f "less 100"

vlan is positional. Everything after it is offset by the 4-byte tag, so vlan and tcp port 80 works while tcp port 80 and vlan does not match tagged HTTP. For a link that carries both tagged and untagged frames, write the filter twice: -f "(tcp port 80) or (vlan and tcp port 80)".

Compile and Debug a Filter

BPF errors are terse. Compile the expression and read the bytecode when a filter silently matches nothing.

dumpcap -d -f "tcp[tcpflags] & tcp-syn != 0" -i en0
tcpdump -d "tcp port 443"        # same, without an interface capture
tcpdump -dd "tcp port 443"       # C array form

Checklist when a capture filter looks broken:

- Kernel-level filter, so a typo is a hard error and the capture never starts. Read the status bar, not the empty packet list. - Names resolve at compile time. host www.example.com expands to every A and AAAA record at that instant and will not follow DNS changes. - Snaplen interacts with byte offsets. -s 96 plus a filter reading deep into the payload works on capture but truncates what you can inspect later. - IPv6 has no fragment-friendly byte-offset support in BPF, so ip6[…] idioms break on extension headers. Filter on ip6 and tcp port 443 and finish the job with a display filter. - Offsets assume Ethernet. On any (Linux cooked capture) or a loopback link type, the link-layer header size differs and hand-written offsets shift.