Wire-Level Packet Analysis with tcpdump & Berkeley Packet Filters (BPF)
Created: 2026-11-08 · Last updated: 2026-11-08 · Nejat Hakan
Executive Summary: When system logs, reverse proxy error pages, and firewall metrics yield conflicting diagnoses, packets on the physical wire never lie. Built upon the Linux kernel's
AF_PACKETsocket family and thelibpcaplibrary,tcpdumpis the premier tool for real-time packet inspection. By evaluating Berkeley Packet Filters (BPF) directly inside kernel memory, it filters gigabit streams with minimal CPU overhead, diagnoses TCP handshake anomalies, and records forensic PCAP traces for offline Wireshark analysis.
-
In-Kernel BPF Engine
Understand how kernel-level BPF bytecode discards unwanted frames before expensive memory copies to user-space occur.
-
TCP Flag Diagnostics
Distinguish between firewall drops, closed port resets (
RST), and connection stalls by evaluatingSYN,ACK,FIN, andRSTbits. -
Advanced BPF Syntax
Construct byte-offset bitmasks to isolate TCP window scaling issues, ICMP fragmentation alerts, and specific HTTP request methods.
-
Circular PCAP Buffers
Safely capture sporadic production bugs without exhausting server storage using size-limited circular file rotations (
-Cand-W).
1. The Architecture of Packet Capture: AF_PACKET & In-Kernel BPF
How does tcpdump intercept network traffic without disrupting running applications? It relies on the Linux kernel's AF_PACKET socket interface:
%%{init: {'flowchart': {'wrappingWidth': 600}}}%%
flowchart TB
NIC["Physical NIC RX Ring"] --> DRIVER["NIC Device Driver"]
DRIVER --> CLONE["Kernel Packet Cloner"]
CLONE -->|"Regular Path"| STACK["TCP/IP Kernel Stack -> Application Socket"]
CLONE -->|"Raw Copy"| BPF["In-Kernel BPF Filter Engine<br/>(Evaluates Bytecode in RAM)"]
BPF -->|"Discard: No Match"| DROP["Dropped (0% User-Space CPU)"]
BPF -->|"Match"| RING["AF_PACKET Ring Buffer"]
RING -->|"sys_read() / recvfrom()"| TCPDUMP["tcpdump User-Space Process<br/>(Formats Output or Writes PCAP)"]
The Performance Imperative of In-Kernel BPF
If tcpdump copied every 10Gbps frame to user-space for string filtering, the CPU would collapse under live-lock. Instead, your BPF filter expression (e.g., 'tcp port 443') is compiled into BPF bytecode and injected directly into the kernel. The kernel executes this filter inside its interrupt/NAPI routine, discarding non-matching packets instantaneously.
2. The Golden Rule of Capture & Essential CLI Flags
The Golden Rule: ALWAYS use -nn
Never run tcpdump without -nn. Without -n, tcpdump attempts to resolve IP addresses to hostnames via reverse DNS (PTR). Without the second -n, it translates port numbers to service names (/etc/services). Resolving IPs generates immediate outbound DNS queries, which tcpdump captures, triggering a catastrophic infinite recursive loop that floods your network and freezes your terminal!
Essential CLI Flags Reference
| Flag | Meaning & Engineering Function |
|---|---|
-nn |
Disables DNS hostname and port name resolution. Mandatory for production. |
-i eth0 |
Specifies interface (-i any captures across all interfaces, bridges, and loopbacks). |
-s 0 |
Sets snapshot length to 0 (captures full packet payload without truncation). |
-v, -vv, -vvv |
Increases verbosity (prints TTL, IP IDs, TCP options, and checksums). |
-X / -XX |
Prints payload data in both Hex and ASCII (invaluable for HTTP, DNS, and SMTP debugging). |
-e |
Prints Layer-2 Ethernet headers (source/destination MAC addresses and 802.1Q VLAN tags). |
-c 100 |
Exits automatically after capturing exactly 100 matching packets. |
-w trace.pcap |
Writes raw binary packets directly to disk for analysis in Wireshark. |
-r trace.pcap |
Reads and replays an existing PCAP capture offline. |
3. Mastering Berkeley Packet Filters (BPF Syntax)
BPF filters combine Primitives, Directions, and Protocols using boolean logic (and, or, not):
1. Basic Protocol & Port Primitives
# Capture all traffic to or from host 10.10.20.11
sudo tcpdump -i eth0 -nn host 10.10.20.11
# Capture outbound HTTP/HTTPS traffic to a specific subnet
sudo tcpdump -i eth0 -nn 'dst net 198.51.100.0/24 and (port 80 or port 443)'
# Exclude noisy SSH traffic from your own management session
sudo tcpdump -i eth0 -nn 'not port 22'
2. Advanced Bitwise Flag Masking
The real power of BPF lies in bitwise operations on header bytes. The TCP flags field is located at byte offset 13 of the TCP header:
%%{init: {'flowchart': {'wrappingWidth': 600}}}%%
flowchart TB
BYTE["TCP Header Byte 13 (Flags):<br/>[CWR | ECE | URG | ACK | PSH | RST | SYN | FIN]"]
BYTE --> S["SYN (Bit 1): 0x02"]
BYTE --> R["RST (Bit 2): 0x04"]
BYTE --> F["FIN (Bit 0): 0x01"]
BYTE --> A["ACK (Bit 4): 0x10"]
# 1. Capture only initial connection attempts (SYN without ACK)
sudo tcpdump -i eth0 -nn 'tcp[tcpflags] & (tcp-syn|tcp-ack) == tcp-syn'
# 2. Capture connection resets (RST) indicating dropped or refused ports
sudo tcpdump -i eth0 -nn 'tcp[tcpflags] & tcp-rst != 0'
# 3. Capture IPv4 packets where the Don't Fragment (DF) bit is set
sudo tcpdump -i eth0 -nn 'ip[6] & 0x40 != 0'
4. Production Troubleshooting Recipes ("Packets Never Lie")
1. Diagnosing the TCP 3-Way Handshake
Run the following while initiating a connection with curl or a database client:
Evaluate the printed TCP flag patterns:
-
Pattern A: Healthy Open Port
Analysis:Client.49210 > Server.5432: Flags [S], seq 100234 Server.5432 > Client.49210: Flags [S.], seq 892301, ack 100235 Client.49210 > Server.5432: Flags [.], ack 892302[S](SYN) followed by[S.](SYN-ACK) and[.](ACK). The connection is established successfully. -
Pattern B: Port Reached, but Service Down (
Connection Refused)Analysis:Client.49210 > Server.5432: Flags [S], seq 100234 Server.5432 > Client.49210: Flags [R.], seq 0, ack 100235[R.](RST-ACK). The packet reached the destination host, but no process was listening on port 5432. -
Pattern C: Firewall Drop (
Connection Timed Out)Analysis: RepeatedClient.49210 > Server.5432: Flags [S], seq 100234 Client.49210 > Server.5432: Flags [S], seq 100234 (Retransmission)[S]retransmissions with zero reply from the server. The packet was dropped silently bynftables, UFW, or a cloud security group.
2. Diagnosing Broken DNS Lookups
Inspect query IDs and response status (NXDOMAIN, SERVFAIL, or timeouts).
3. Capturing Path MTU Discovery Failures
Detects ICMP Fragmentation Needed messages dropped by broken intermediate firewalls.
5. Circular PCAP Ringbuffers in Production
Capturing sporadic network glitches on high-throughput servers can quickly fill root partitions. Use -C (file size in MB) and -W (number of rotated files) to create a fixed-size circular ringbuffer:
# Capture full packets into /var/log/captures/
# Rotates between 5 files of 100MB each (Maximum disk consumption: 500MB)
sudo mkdir -p /var/log/captures
sudo tcpdump -i eth0 -nn -s 0 -C 100 -W 5 -w /var/log/captures/app_debug.pcap 'port 443 or port 5432'
tcpdump will create app_debug.pcap0, app_debug.pcap1, ..., and wrap around cyclically, keeping only the most recent 500MB of capture data.
6. Live Streaming Captures Directly into Wireshark
Rather than copying .pcap files back and forth with scp, stream packets directly from your remote Debian server into your local desktop Wireshark instance over an SSH pipe:
# Run on your local administrative workstation:
ssh root@203.0.113.10 "tcpdump -U -s 0 -nn -i eth0 -w - 'not port 22'" | wireshark -k -i -
-U: Instructstcpdumpto flush packets immediately to stdout without buffering.-w -: Writes raw PCAP data to standard output.wireshark -k -i -: Starts Wireshark immediately (-k) reading from standard input (-i -).
7. Where to Go Next: Cluster 3
Congratulations! You have completed Cluster 2: Security, Tunneling & Firewalls. Proceed to the architectural backbone of Linux enterprise infrastructure:
-
Cluster 3: Linux as a Layer-3 Router & NAT
Transform Debian into an enterprise router: IP forwarding, SNAT masquerading, DNAT port forwarding, and stateful conntrack tuning.