tcpdump Cheatsheet

BPF Filter Syntax

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

Grammar: qualifier, direction, type

A BPF (pcap-filter) primitive is built from three kinds of qualifier plus an ID. Omit a qualifier and pcap fills in a default.

[dir] [proto] type id
Qualifier classValuesDefault
typehost, net, port, portrangehost
dirsrc, dst, src or dst, src and dst, ra, tasrc or dst
protoether, fddi, tr, wlan, ip, ip6, arp, rarp, tcp, udpall of them

So these are equivalent:

tcpdump host 10.0.0.5
tcpdump 'src or dst host 10.0.0.5'

And a bare number is a host, not a port. tcpdump 80 looks for the host 0.0.0.80. Always write port 80.

Operators

OperatorAliasesMeaning
and&&Both
or||Either
not!Negate, binds tighter than and/or
( )—Grouping, quote or escape in a shell
= == != < > <= >=—Numeric comparison on byte expressions
+ - * / % & | ^ << >>—Arithmetic and bitwise on byte expressions

Precedence traps:

# WRONG: not applies only to the first primitive
tcpdump not host 10.0.0.1 and port 22

# RIGHT: group what you are negating
tcpdump 'not (host 10.0.0.1 and port 22)'

Always single-quote the whole expression. (, ), |, !, and \ are all shell metacharacters, and \tcp in ip proto \tcp needs the backslash to reach pcap intact.

Filters vs Wireshark display filters

These are two different languages. tcpdump takes capture filters (BPF, kernel-side, decides what gets recorded). Wireshark's main filter bar takes display filters (userspace, decides what gets shown). They share almost no syntax.

Intenttcpdump (capture / BPF)Wireshark (display)
Either direction, one hosthost 10.0.0.5ip.addr == 10.0.0.5
Source hostsrc host 10.0.0.5ip.src == 10.0.0.5
Subnetnet 10.0.0.0/8ip.addr == 10.0.0.0/8
TCP porttcp port 443tcp.port == 443
Destination portdst port 443tcp.dstport == 443
Port rangeportrange 8000-8100tcp.port in {8000 .. 8100}
Only TCPtcptcp
SYN settcp[tcpflags] & tcp-syn != 0tcp.flags.syn == 1
SYN and not ACKtcp[tcpflags] & (tcp-syn|tcp-ack) == tcp-syntcp.flags.syn == 1 && tcp.flags.ack == 0
RST seentcp[tcpflags] & tcp-rst != 0tcp.flags.reset == 1
Has payloadtcp[13] & 8 != 0 (PSH, approximate)tcp.len > 0
DNSport 53dns
HTTPtcp port 80http
TLStcp port 443tls
MAC addressether host 00:11:22:33:44:55eth.addr == 00:11:22:33:44:55
VLANvlan 100vlan.id == 100
Frame sizegreater 1000frame.len > 1000
ICMP echo replyicmp[icmptype] == icmp-echoreplyicmp.type == 0
Payload contains text(no operator, use byte math)frame contains "password"
Retransmissions(impossible, needs state)tcp.analysis.retransmission
Not this hostnot host 10.0.0.5!(ip.addr == 10.0.0.5)

Two asymmetries to internalize:

- BPF is stateless and per-packet. It cannot express "retransmission", "response time", or "this stream". Display filters can, because Wireshark has already dissected and tracked the conversation. - ip.addr != x in Wireshark is a trap, because it is true whenever either address field differs. Write !(ip.addr == x). tcpdump's not host x has no such problem.

Wireshark accepts BPF in the capture options dialog (Capture ▸ Options ▸ Capture filter), and tshark -f is BPF while tshark -Y is a display filter. So both languages exist in both tools, in different slots.

Testing a filter without capturing

-d compiles the expression, prints the BPF program, and exits. It is the fastest syntax check there is, and it needs no packets.

# Syntax check against a file, no privileges needed
tcpdump -r capture.pcap -d 'tcp[tcpflags] & tcp-syn != 0'

# Without -r or -i, name the link type so it can compile
tcpdump -y EN10MB -d 'host 10.0.0.1 and tcp port 443'
FlagOutput
-dHuman-readable BPF assembly
-ddC array, to paste into a program
-dddDecimal, preceded by an instruction count

If it prints instructions, the filter parses. If it prints syntax error, fix it before you waste a capture window.

Filter files

Long filters belong in a file, which also sidesteps shell quoting.

cat > /tmp/f.bpf <<'EOF'
tcp port 443 and
not (host 203.0.113.9 and port 22)
EOF

sudo tcpdump -i eth0 -nn -F /tmp/f.bpf

-F file replaces any expression on the command line rather than combining with it, so do not pass both. Comments in the file start with #, and newlines act as whitespace.

Performance notes

The kernel runs the filter, so a tight filter is the single biggest win when you are capturing on a busy link.

- Put the cheapest, most selective primitive first. host x and tcp port 443 beats the reverse on a link where that host is rare. - port 443 expands to a check on TCP, UDP, SCTP and both IP versions, plus a fragment-offset test. tcp port 443 is meaningfully cheaper. - Name resolution in a filter happens once at compile time, so host example.com is fine, but it may expand to several addresses. - vlan is positional, not a predicate. vlan and tcp port 80 shifts all later offsets by 4 bytes, so tcp port 80 and vlan does not work. Put vlan first, and for tagged or untagged traffic write 'tcp port 80 or (vlan and tcp port 80)'.