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 class | Values | Default |
|---|---|---|
| type | host, net, port, portrange | host |
| dir | src, dst, src or dst, src and dst, ra, ta | src or dst |
| proto | ether, fddi, tr, wlan, ip, ip6, arp, rarp, tcp, udp | all 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
| Operator | Aliases | Meaning |
|---|---|---|
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.
| Intent | tcpdump (capture / BPF) | Wireshark (display) |
|---|---|---|
| Either direction, one host | host 10.0.0.5 | ip.addr == 10.0.0.5 |
| Source host | src host 10.0.0.5 | ip.src == 10.0.0.5 |
| Subnet | net 10.0.0.0/8 | ip.addr == 10.0.0.0/8 |
| TCP port | tcp port 443 | tcp.port == 443 |
| Destination port | dst port 443 | tcp.dstport == 443 |
| Port range | portrange 8000-8100 | tcp.port in {8000 .. 8100} |
| Only TCP | tcp | tcp |
| SYN set | tcp[tcpflags] & tcp-syn != 0 | tcp.flags.syn == 1 |
| SYN and not ACK | tcp[tcpflags] & (tcp-syn|tcp-ack) == tcp-syn | tcp.flags.syn == 1 && tcp.flags.ack == 0 |
| RST seen | tcp[tcpflags] & tcp-rst != 0 | tcp.flags.reset == 1 |
| Has payload | tcp[13] & 8 != 0 (PSH, approximate) | tcp.len > 0 |
| DNS | port 53 | dns |
| HTTP | tcp port 80 | http |
| TLS | tcp port 443 | tls |
| MAC address | ether host 00:11:22:33:44:55 | eth.addr == 00:11:22:33:44:55 |
| VLAN | vlan 100 | vlan.id == 100 |
| Frame size | greater 1000 | frame.len > 1000 |
| ICMP echo reply | icmp[icmptype] == icmp-echoreply | icmp.type == 0 |
| Payload contains text | (no operator, use byte math) | frame contains "password" |
| Retransmissions | (impossible, needs state) | tcp.analysis.retransmission |
| Not this host | not 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'
| Flag | Output |
|---|---|
-d | Human-readable BPF assembly |
-dd | C array, to paste into a program |
-ddd | Decimal, 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)'.