References¶
- https://danielmiessler.com/study/tcpdump
- https://gist.github.com/tuxfight3r/9ac030cb0d707bb446c7
- http://www.commandlinefu.com/commands/using/tcpdump
man pcap-filter— the canonical BPF filter syntax reference
1. What tcpdump actually does¶
tcpdump is a thin CLI wrapper around libpcap, which talks to the kernel's packet capture facility (AF_PACKET on Linux, BPF device on BSD/macOS). Every filter expression you write gets compiled down to BPF (Berkeley Packet Filter) bytecode and pushed into the kernel, so filtering happens before packets are copied to userspace — this is why filtering is cheap and sniffing an interface at line rate with a tight filter is fine, but tcpdump -i eth0 with no filter on a busy link is not.
Two things worth internalizing early:
- Capture ≠ display. -v/-vv/-vvv and -X/-A control how much detail is printed, not how much is captured. Use -w to capture everything to disk regardless of verbosity.
- Filter expression ≠ display filter. Unlike Wireshark, tcpdump's capture filter (BPF) cannot do things like "show me only retransmitted TCP segments" — that requires post-processing (usually with tshark or Wireshark itself on the resulting pcap).
2. Basic invocation & permissions¶
tcpdump -D # list available interfaces
tcpdump -i eth0 # capture on a specific interface
tcpdump -i any # capture on all interfaces (Linux "cooked" capture, adds a pseudo-header)
tcpdump needs CAP_NET_RAW (and CAP_NET_ADMIN for some interface modes). Rather than running as root:
sudo setcap cap_net_raw,cap_net_admin+eip $(which tcpdump)
then any user in the right group can capture without sudo.
3. Core flags reference¶
| Flag | Meaning |
|---|---|
-i <iface> |
interface to sniff (any for all) |
-n |
don't resolve hostnames |
-nn |
don't resolve hostnames or port names (show raw port numbers) |
-v, -vv, -vvv |
increasing verbosity of decoded output |
-e |
print link-layer (Ethernet/MAC) header |
-X |
print packet payload in hex and ASCII |
-XX |
same as -X but includes the link-layer header in the hex dump |
-A |
print payload in ASCII only (good for quick text-protocol inspection) |
-c <n> |
stop after capturing n packets |
-s <n> |
snap length — bytes captured per packet (-s0 / -s 262144 = full packet, default is often already full on modern tcpdump) |
-w <file> |
write raw packets to a pcap file instead of decoding to stdout |
-r <file> |
read packets back from a pcap file |
-G <secs> |
rotate the output file every N seconds (used with -w) |
-W <n> |
limit the number of rotated files (ring buffer) before overwriting |
-C <size> |
rotate the output file once it reaches <size> (in MB) |
-z <cmd> |
run <cmd> <savefile> each time a -G/-C rotation completes (e.g. compress or ship the file) |
-tttt |
print full, human-readable date+time on every line (default timestamps are relative and easy to misread) |
-l |
line-buffer stdout — needed if you're piping tcpdump into grep/awk and want to see output live |
-K |
don't verify checksums (useful when capturing on the sending host, where checksum offload makes checksums look "wrong") |
-p |
don't put the interface into promiscuous mode |
-U |
write each packet to the savefile immediately, not just when the buffer fills |
4. Filter expression language (BPF)¶
Filters are built from primitives combined with and/or/not (or &&/||/!):
- Type:
host,net,port,portrange - Direction:
src,dst(default is both) - Protocol:
ip,ip6,tcp,udp,icmp,icmp6,arp,vlan
tcpdump 'src host 10.0.0.5 and dst port 443'
tcpdump 'net 192.168.0.0/24'
tcpdump 'tcp portrange 8000-8010'
tcpdump 'vlan and host 10.0.0.5' # match inside a VLAN-tagged frame
Advanced BPF: matching on raw bytes/flags¶
You can index directly into the packet with proto[offset:size]. The most common real-world use is matching TCP flags without a helper keyword:
# SYN packets only (start of a connection)
tcpdump 'tcp[13] & 2 != 0'
# SYN-ACK
tcpdump 'tcp[13] & 18 == 18'
# RST packets (connection resets/refusals — great for finding blocked ports)
tcpdump 'tcp[13] & 4 != 0'
# FIN
tcpdump 'tcp[13] & 1 != 0'
(byte 13 of the TCP header is the flags byte; the bit values are the same as the flag bits: FIN=1, SYN=2, RST=4, PUSH=8, ACK=16, URG=32).
There are also shorthand keywords in modern tcpdump: tcp-syn, tcp-ack, tcp-fin, tcp-rst, tcp-push, e.g. tcpdump 'tcp[tcpflags] == tcp-syn'.
5. Common filter recipes¶
Sniff by destination / source IP¶
tcpdump -i any -vvv 'dst 45.63.18.98' or 'dst 108.61.191.230' # traffic TO these IPs
tcpdump -i any -vvv 'src 45.63.18.98' or 'src 108.61.191.230' # traffic FROM these IPs
Note: when combining or across quoted primitives like this, each side is evaluated independently — for combined AND/OR logic wrap the whole thing in one filter string: tcpdump -i any 'dst 45.63.18.98 or dst 108.61.191.230'.
IPv6 traffic only¶
tcpdump -vvvv -ttt -i any icmp6
-ttt prints a delta timestamp (time since the previous printed packet) — handy for spotting retransmission/timeout patterns. Compare against -tttt (absolute wall-clock time) when you need to correlate with logs elsewhere.
Sniff by host — to pcap¶
tcpdump -i eth0 -vvv ip host 10.25.20.52 -n -c 5000 -w 10.25.20.52_dump.pcap
tcpdump -i eth0 -vvv ip host 10.25.20.52 -n -w 10.25.20.52_dump.pcap
tcpdump -i eth0 -vvv dst host 10.25.20.52 or src host 10.25.20.52 -n > 10.25.20.52_dump.txt
Read it back:
tcpdump -r 10.25.20.52_dump.pcap
tcpdump -r 10.25.20.52_dump.pcap -nn -X # re-apply verbosity/hex on replay
tcpdump -r 10.25.20.52_dump.pcap 'tcp port 443' # you can even re-filter on replay
host matches both source and destination — dst host X or src host X above is equivalent to just host X, spelled out.
Sniff by host — stdout¶
tcpdump -i eth0 dst host 208.67.222.222 or dst host 208.67.220.220 or src host 208.67.222.222 or src host 208.67.220.220 -n
Same idea, simplifiable to: tcpdump -i eth0 -n host 208.67.222.222 or host 208.67.220.220.
Sniff specific ports¶
tcpdump -vvv -i eth0 port 22
Ignoring things¶
tcpdump -i any -vvv port not 22 # ignore a specific port
tcpdump -i any -vvv not arp # ignore ARP noise
tcpdump -i any -vvv port not 22 and not arp # ignore ssh and arp
tcpdump -i any -vvv not port 22 and not host metadata.google.internal
This pattern — excluding your own management traffic (SSH, cloud metadata endpoint, DNS) — is the standard way to declutter a capture down to "the thing I actually care about."
Test if a flow is encrypted¶
flow = connection from src ip/port to dst ip/port and the data in the middle
tcpdump -X port 995
If -X's ASCII column shows recognizable plaintext (headers, credentials, protocol banners) the flow is cleartext; high-entropy/garbled output on a port that should be TLS (e.g. 995 = POP3S) confirms it's actually encrypted. This is a fast way to validate that a "should be TLS" service actually negotiated TLS rather than falling back to plaintext.
Sniff VLAN IDs¶
tcpdump -i eth0 -vvv -nn -e
-e prints the link-level header on each line — needed to see 802.1Q VLAN tags and MAC addresses. With -e -vvv on a trunk port you'll see vlan <id> inline in the decode.
Misc — top 10 talkers¶
tcpdump -tnn -c 2000 -i eth0 | awk -F "." '{print $1"."$2"."$3"."$4}' | sort | uniq -c | sort -nr | awk '$1 > 10'
Misc — IPs communicating on an interface¶
tcpdump -i wlan0 -n ip | awk '{ print gensub(/(.*)\..*/,"\\1","g",$3), $4, gensub(/(.*)\..*/,"\\1","g",$5) }' | awk -F " > " '{print $1"\n"$2}'
Record a day's traffic in 15-minute chunks¶
tcpdump -G 900 -w '%Y-%m-%d_%H:%M:%S.pcap' -W 96
-G 900 rotates every 900s (15 min); -W 96 caps it at 96 files = 24 hours, then wraps around (ring buffer) so disk usage is bounded. Add -z gzip to compress each file the moment it's rotated:
tcpdump -G 900 -W 96 -z gzip -w '/var/log/pcaps/%Y-%m-%d_%H:%M:%S.pcap'
Namespace / DHCP capture examples¶
ip netns exec qdhcp-f6e9be13-26d8-4277-82bd-e6aa903d7abe tcpdump -vnes0 -i tap13d3e279-0c port 67 or port 68 -w /tmp/director_dhcp.dump
tcpdump -vnes0 -i any port 67 or port 68 -w /tmp/contoller_dhcp.dump
-e (link header) + -s0 (full snaplen) + -n is the standard combo for DHCP debugging, since you often need MAC addresses (-e) and the full DHCP options list (-s0) to diagnose lease/relay issues. ip netns exec <ns> tcpdump ... is the general pattern for sniffing inside a network namespace (OpenStack/Neutron here, but identical technique for any netns — including container netns, see §7).
6. Advanced techniques¶
Remote live capture piped straight into Wireshark¶
No need to scp a pcap back — stream it live over SSH into a local GUI:
ssh user@remote-host "tcpdump -i eth0 -U -s0 -w -" | wireshark -k -i -
-U flushes each packet immediately so there's no buffering delay over the pipe.
Capturing inside a Kubernetes pod (no tcpdump in the image)¶
Most production container images don't ship tcpdump. Options, cheapest first:
# 1. Ephemeral debug container sharing the target pod's network namespace (k8s 1.23+)
kubectl debug -it <pod> --image=nicolaka/netshoot --target=<container> -- tcpdump -i any -w /tmp/cap.pcap
# 2. If you have node access: find the pod's network namespace and enter it directly
crictl inspect <container-id> | grep netns # or: docker inspect --format '{{.NetworkSettings.SandboxKey}}' <id>
nsenter --net=<netns-path> tcpdump -i eth0
# 3. Sidecar pattern: add a tcpdump sidecar container with shareProcessNamespace/hostNetwork as appropriate
nicolaka/netshoot is the de-facto standard "networking swiss army knife" debug image (tcpdump, dig, curl, iproute2, tshark, etc. all preinstalled) — worth keeping pinned in your homelab tooling.
Snaplen matters more than people think¶
Older tcpdump defaulted to -s 96 or -s 68, silently truncating packets — which corrupts the checksum/length fields you see and breaks reassembly in Wireshark. Modern tcpdump defaults to full capture, but always pin it explicitly in scripts/automation: -s0 (unlimited) or -s 262144.
Reducing overhead on high-throughput links¶
- Always pair
-nnwith production captures — DNS/service-name resolution is a blocking syscall per packet and will cause drops on busy interfaces. - Increase the kernel capture buffer with
-B <KB>if you seepackets dropped by kernelin the summary stats (printed on exit or viaSIGINFO/Ctrl+T on BSD, or-vcounters on Linux). - Prefer writing straight to a fast local disk with
-w, then filtering/decoding offline with-r— decoding to stdout while capturing competes with capture for CPU.
Extracting just TLS SNI (which host a client is connecting to, even without decrypting)¶
tcpdump -i any -nn -A 'tcp port 443 and (tcp[((tcp[12:1] & 0xf0) >> 2):1] = 0x16)' -w - | \
tshark -r - -Y "tls.handshake.extensions_server_name" -T fields -e tls.handshake.extensions_server_name
This is the standard technique for identifying which domains a host is talking to over TLS purely from the ClientHello, without breaking encryption — useful for egress auditing.
Legal/ethical note¶
Only capture traffic on networks/hosts you own or are explicitly authorized to monitor. Passive sniffing of others' traffic (even "just to test") crosses into wiretapping in most jurisdictions.
7. Quick cheat sheet¶
tcpdump -D # list interfaces
tcpdump -i eth0 -nn # basic, no name resolution
tcpdump -i any -nn host 1.2.3.4 # by host
tcpdump -i eth0 -nn port 443 # by port
tcpdump -i eth0 -nn 'tcp[tcpflags] == tcp-syn' # new connections only
tcpdump -i eth0 -w cap.pcap -s0 -c 10000 # capture to disk, full packets, bounded count
tcpdump -r cap.pcap -nn -X # replay with hex+ascii
tcpdump -i eth0 -G 900 -W 96 -z gzip -w '/var/log/pcap/%F_%T.pcap' # rotating, compressed, bounded