Filters
Chapter 2's capture was quiet because a filter kept it that way. Remove the filter and the same loopback, driven by the same three small programs, looks like this:
$ tcpdump -i lo -nn
(second terminal: ping -c 2 127.0.0.1; curl -s http://127.0.0.1:45080/; printf "packets please\n" | nc -u -w 1 127.0.0.1 45053)
...
21:45:39.264559 IP 127.0.0.1 > 127.0.0.1: ICMP echo request, id 16554, seq 1, length 64
21:45:39.264574 IP 127.0.0.1 > 127.0.0.1: ICMP echo reply, id 16554, seq 1, length 64
21:45:39.567190 IP 127.0.0.1 > 127.0.0.1: ICMP echo request, id 16554, seq 2, length 64
21:45:39.567206 IP 127.0.0.1 > 127.0.0.1: ICMP echo reply, id 16554, seq 2, length 64
21:45:39.592398 IP 127.0.0.1.41696 > 127.0.0.1.45080: Flags [S], seq 364417795, win 65495, ...
... (the twelve-packet web conversation of chapter 2) ...
21:45:39.598667 IP 127.0.0.1.60935 > 127.0.0.1.45053: UDP, length 15
21:45:39.598831 IP 127.0.0.1.45053 > 127.0.0.1.60935: UDP, length 21
18 packets captured
36 packets received by filter
0 packets dropped by kernel
Eighteen packets: 4 ICMP, 12 TCP, 2 UDP, all interleaved by time and nothing else. On a real interface the unfiltered view is that, multiplied by everything the machine is doing. Filters are how you ask for the one conversation you came for.
The language, briefly
Everything after the options is a filter
expression in the BPF language
(the pcap-filter manual page is its
full grammar). tcpdump compiles it,
hands it to the kernel, and the kernel
applies it to every packet — which is
why the footer's "received by filter"
counter is the kernel's count of
matches, and why filtering costs almost
nothing even on a busy link.
The building blocks:
| Primitive | Matches |
|---|---|
host 127.0.0.1 |
Packets to or from that address |
src host … / dst host … |
Only as source / only as destination |
net 127.0.0.0/8 |
To or from that network |
port 45080 |
TCP or UDP, to or from that port |
src port … / dst port … |
Directional ports |
portrange 45000-45100 |
A span of ports |
tcp, udp, icmp |
That protocol (ICMP has no ports — note its lines carry none) |
ip, ip6 |
That address family |
Combine with and, or, not (or
&&, ||, !), group with
parentheses, and quote the whole
expression so the shell doesn't eat the
interesting characters. Two grammar
notes that bite everyone once:
notbinds to what follows it, andandbinds tighter thanor. When in doubt, parentheses — they are cheap, and a silently-wrong grouping produces a silently-wrong capture.tcp port 45080is the common shape: a protocol primitive narrowed by a port primitive.port 45080alone would also match a UDP conversation on that port, if one existed.
A filter that doesn't compile is refused before capture starts — a mercy, and loud about it:
$ tcpdump -i lo -nn "tpc port 45080"
tcpdump: can't parse filter expression: syntax error
(Filter errors are the honest kind: you find them in the terminal, not in an empty capture three days later.)
Same burst, three filters
The lab burst above, re-run under
filters, shrinks exactly as the language
promises. Only the pings survive
icmp — the web conversation and the
UDP pair were still sent; the filter
just declined to show them:
$ tcpdump -i lo -nn -c 4 icmp
21:45:55.587364 IP 127.0.0.1 > 127.0.0.1: ICMP echo request, id 16589, seq 1, length 64
21:45:55.587375 IP 127.0.0.1 > 127.0.0.1: ICMP echo reply, id 16589, seq 1, length 64
21:45:55.895147 IP 127.0.0.1 > 127.0.0.1: ICMP echo request, id 16589, seq 2, length 64
21:45:55.895166 IP 127.0.0.1 > 127.0.0.1: ICMP echo reply, id 16589, seq 2, length 64
4 packets captured
or admits the UDP conversation too —
six lines, the same four ICMP plus the
pair:
$ tcpdump -i lo -nn -c 6 "icmp or udp port 45053"
...
21:45:57.925281 IP 127.0.0.1.46928 > 127.0.0.1.45053: UDP, length 15
21:45:57.925404 IP 127.0.0.1.45053 > 127.0.0.1.46928: UDP, length 21
6 packets captured
One honest power tool
The language can test bytes inside the packet, not only addresses. The classic use is "show me only connection attempts": a packet with the SYN flag set —
$ tcpdump -i lo -nn -c 2 "tcp[tcpflags] & tcp-syn != 0"
21:45:59.624019 IP 127.0.0.1.41564 > 127.0.0.1.45080: Flags [S], seq 1544696929, win 65495, options [mss 65495,sackOK,TS val 3609135553 ecr 0,nop,wscale 10], length 0
21:45:59.624038 IP 127.0.0.1.45080 > 127.0.0.1.41564: Flags [S.], seq 3512323252, ack 1544696930, win 65483, options [mss 65495,sackOK,TS val 1728529377 ecr 3609135553,nop,wscale 10], length 0
2 packets captured
tcp[tcpflags] names the flags byte of
the TCP header; & tcp-syn != 0 keeps
packets where the SYN bit is lit — the
client's [S] and the server's [S.],
and nothing else of the twelve. This is
as deep as the byte-surgery goes in
this guide: one expression, fully
explained, genuinely useful (chapter 6
builds a recipe on it). The grammar
allows far more; the pcap-filter
manual page is the honest place to learn
it, with a live capture open beside
you.
Check: predict, then run. Under
"tcp port 45080 and tcp[tcpflags] &
tcp-syn != 0", how many lines does the
lab's curl conversation produce? (Two:
the same opening pair — the port
primitive and the flag test both have
to pass.) If you got four, your filter
has an or where this one has an
and.