What is tcpdump?
tcpdump is a command-line packet analyser. While netstat shows you the current state of sockets, tcpdump shows you the actual packets flying over the network in real time. You can see the SYN, SYN-ACK, FIN, and data packets of every TCP connection — exactly like drawing the sequence diagrams we studied in Parts 1 and 2, but from live traffic.
It requires superuser (root) privileges to capture packets because it puts the network interface into promiscuous mode, which allows it to see all packets on the network — not just those addressed to the local machine.
Despite the name, tcpdump handles all IP-based traffic: TCP, UDP, ICMP, ARP, and more. The name is historical.
Key Terms to Know
# Capture on default interface (requires root/sudo)
sudo tcpdump
# Capture on a specific interface
sudo tcpdump -i eth0
sudo tcpdump -i lo # loopback — for local connections
# Capture only TCP traffic on port 9090
sudo tcpdump -i lo tcp port 9090
# Capture TCP traffic between two hosts
sudo tcpdump -i eth0 host 192.168.1.10
# Show packet content in hex and ASCII
sudo tcpdump -i lo -X tcp port 9090
# Show numeric addresses (no DNS lookup — faster)
sudo tcpdump -n -i eth0 tcp port 80
# Capture to a file (pcap format) — view later with Wireshark
sudo tcpdump -i eth0 -w capture.pcap tcp port 9090
# Read from a previously captured file
sudo tcpdump -r capture.pcap
# Show verbose output (TTL, checksum, etc.)
sudo tcpdump -v -i eth0 tcp port 9090
# Limit number of packets captured
sudo tcpdump -c 50 -i eth0 tcp port 9090
Here is a sample tcpdump session capturing a full TCP 3-way handshake + data + 4-way close:
$ sudo tcpdump -n -i lo tcp port 9090
# --- TCP 3-Way Handshake (Connection Setup) ---
12:00:00.000000 IP 127.0.0.1.54321 > 127.0.0.1.9090: Flags [S], seq 1000, win 65495, length 0
12:00:00.000050 IP 127.0.0.1.9090 > 127.0.0.1.54321: Flags [S.], seq 2000, ack 1001, win 65483, length 0
12:00:00.000060 IP 127.0.0.1.54321 > 127.0.0.1.9090: Flags [.], ack 2001, win 512, length 0
# --- Data Transfer ---
12:00:00.001000 IP 127.0.0.1.54321 > 127.0.0.1.9090: Flags [P.], seq 1001:1019, ack 2001, win 512, length 18
12:00:00.001010 IP 127.0.0.1.9090 > 127.0.0.1.54321: Flags [.], ack 1019, win 512, length 0
# --- TCP 4-Way Handshake (Connection Teardown) ---
12:00:01.000000 IP 127.0.0.1.54321 > 127.0.0.1.9090: Flags [F.], seq 1019, ack 2001, win 512, length 0
12:00:01.000010 IP 127.0.0.1.9090 > 127.0.0.1.54321: Flags [.], ack 1020, win 512, length 0
12:00:01.000020 IP 127.0.0.1.9090 > 127.0.0.1.54321: Flags [F.], seq 2001, ack 1020, win 512, length 0
12:00:01.000030 IP 127.0.0.1.54321 > 127.0.0.1.9090: Flags [.], ack 2002, win 512, length 0
| Flag | Meaning | When You See It |
|---|---|---|
[S] |
SYN only | First packet of 3-way handshake (client → server) |
[S.] |
SYN + ACK | Second packet of 3-way handshake (server → client) |
[.] |
ACK only | Pure acknowledgement, no data, no flags |
[P.] |
PSH + ACK | Data segment with push flag (tells receiver to deliver immediately) |
[F.] |
FIN + ACK | Connection termination step (FIN segment) |
[R.] |
RST + ACK | Connection reset — abrupt close (error or refused connection) |
[R] |
RST only | Refused connection or half-open connection detected |
| Client (port 54321) | Packet (from tcpdump) | Server (port 9090) |
|---|---|---|
| CLOSED | LISTEN | |
| SYN_SENT | ── [S] seq=1000 ──────>Flags [S], seq 1000 |
SYN_RECV |
| ESTABLISHED | <─ [S.] seq=2000, ack=1001 ─Flags [S.], seq 2000, ack 1001 |
SYN_RECV |
── [.] ack=2001 ──────>Flags [.], ack 2001 |
ESTABLISHED | |
| DATA | ── [P.] seq=1001:1019 ──>Flags [P.], seq 1001:1019, length 18 |
receives 18 bytes |
<─ [.] ack=1019 ──────Flags [.], ack 1019 |
ACK of data | |
| FIN_WAIT1 | ── [F.] seq=1019 ──────>Flags [F.], seq 1019 |
CLOSE_WAIT |
| FIN_WAIT2 | <─ [.] ack=1020 ──────Flags [.], ack 1020 |
|
| TIME_WAIT | <─ [F.] seq=2001 ──────Flags [F.], seq 2001 |
LAST_ACK |
| TIME_WAIT | ── [.] ack=2002 ──────>Flags [.], ack 2002 |
CLOSED |
tcpdump uses Berkeley Packet Filter (BPF) syntax to filter which packets to display. Without a filter you see everything — on a busy system this is overwhelming. Good filter expressions are essential.
# Filter by host
sudo tcpdump -n host 192.168.1.10
# Filter by source host only
sudo tcpdump -n src host 192.168.1.10
# Filter by destination host only
sudo tcpdump -n dst host 192.168.1.10
# Filter by port
sudo tcpdump -n port 9090
sudo tcpdump -n port 80 or port 443
# Filter by protocol
sudo tcpdump -n tcp
sudo tcpdump -n udp
sudo tcpdump -n icmp
# Combine: TCP traffic to port 9090 from a specific host
sudo tcpdump -n tcp and host 192.168.1.10 and port 9090
# Capture only SYN packets (connection initiation)
sudo tcpdump -n 'tcp[13] & 2 != 0'
# Capture only FIN packets (connection teardown)
sudo tcpdump -n 'tcp[13] & 1 != 0'
# Capture only RST packets (abrupt close or refused connection)
sudo tcpdump -n 'tcp[13] & 4 != 0'
# Capture packets larger than 1400 bytes (near MTU — possible fragmentation)
sudo tcpdump -n 'greater 1400'
Understanding the flags byte filter tcp[13]: In the TCP header, byte offset 13 (0-indexed) contains all the control flags. Each bit position corresponds to a flag:
Bit 0 (0x01) = FIN
Bit 1 (0x02) = SYN
Bit 2 (0x04) = RST
Bit 3 (0x08) = PSH
Bit 4 (0x10) = ACK
Bit 5 (0x20) = URG
# Capture SYN+ACK packets (second step of handshake)
sudo tcpdump -n 'tcp[13] & 0x12 = 0x12' # both SYN(0x02) and ACK(0x10) set
sudo tcpdump -n -i lo tcp port 9090 &
# Run your client in another terminal
# You should see: [S] → [S.] → [.] within a few milliseconds
# If you only see [S] and nothing back → server is not listening or firewall blocking
sudo tcpdump -n -i lo 'tcp[13] & 1 != 0' port 9090
# Shows only packets with FIN bit set
# You should see FIN from client, then FIN from server for a clean close
# If you see RST instead of FIN → abrupt close (crash, SO_LINGER with l_linger=0)
sudo tcpdump -n -i eth0 tcp port 9090 | grep -i "retransmission\|dup ack"
# Retransmissions mean the network is dropping packets or the peer is very slow
# Multiple DUP ACKs for the same sequence number = receiver is missing a segment
# Capture 200 packets on port 9090 to a file
sudo tcpdump -n -i lo -c 200 -w /tmp/session.pcap tcp port 9090
# Transfer to your laptop and open in Wireshark for GUI analysis
# Or analyse on the command line:
sudo tcpdump -r /tmp/session.pcap -n | head -50
| Network Interface (eth0 / lo) |
| ↓ all packets |
| Kernel: BPF (Berkeley Packet Filter) |
| ↓ only matching packets pass the filter |
| libpcap library (userspace) |
| ↓ packet data |
| tcpdump (decodes and prints) / Wireshark (GUI analysis) |
- BPF (Berkeley Packet Filter): The filter expression you write (
tcp port 9090) is compiled into BPF bytecode that runs inside the kernel. Only packets matching the filter are copied to userspace. This keeps overhead low even on high-traffic interfaces. - libpcap: The userspace library that provides a uniform API for packet capture across different operating systems. Both tcpdump and Wireshark use libpcap.
- Promiscuous mode: Normally, a network card only passes packets addressed to the local machine to the OS. In promiscuous mode, it passes all packets — including those addressed to other machines on the same segment. This is why root is needed.
- pcap file format: The standard format for saved packet captures. Files have a
.pcapextension and can be opened by any tool that uses libpcap (tcpdump, Wireshark, scapy, etc.).
- Command-line only — works over SSH
- Available on all Linux/UNIX systems
- Fast, low memory usage
- Best for quick checks and capture-to-file
- Can be used in scripts and automation
- Good for remote server debugging
sudo tcpdump -n -i eth0 -w capture.pcap
- GUI — visual packet inspection
- Requires a desktop environment
- Deep protocol dissection (understands 1000+ protocols)
- TCP stream reassembly and following
- Best for deep analysis of .pcap files
- Good for complex protocol debugging
Typical workflow: capture with tcpdump remotely, analyse pcap in Wireshark locally.
You can write your own packet capture tool using the same libpcap library that tcpdump uses.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pcap.h>
#include <arpa/inet.h>
#include <netinet/ip.h>
#include <netinet/tcp.h>
/*
* This callback is called by pcap for each captured packet.
* We parse the IP and TCP headers and print key fields.
*/
void packet_handler(unsigned char *args,
const struct pcap_pkthdr *header,
const unsigned char *packet)
{
/* Ethernet header is 14 bytes */
const struct ip *ip_hdr = (struct ip *)(packet + 14);
int ip_header_len = ip_hdr->ip_hl * 4;
const struct tcphdr *tcp_hdr =
(struct tcphdr *)((unsigned char *)ip_hdr + ip_header_len);
/* Extract TCP flags */
int flag_syn = tcp_hdr->syn;
int flag_ack = tcp_hdr->ack;
int flag_fin = tcp_hdr->fin;
int flag_rst = tcp_hdr->rst;
int flag_psh = tcp_hdr->psh;
char flags_str[32] = "[";
if (flag_syn) strcat(flags_str, "S");
if (flag_ack) strcat(flags_str, ".");
if (flag_fin) strcat(flags_str, "F");
if (flag_rst) strcat(flags_str, "R");
if (flag_psh) strcat(flags_str, "P");
strcat(flags_str, "]");
printf("%s:%d > %s:%d Flags %s seq=%u ack=%u len=%d\n",
inet_ntoa(ip_hdr->ip_src),
ntohs(tcp_hdr->source),
inet_ntoa(ip_hdr->ip_dst),
ntohs(tcp_hdr->dest),
flags_str,
ntohl(tcp_hdr->seq),
ntohl(tcp_hdr->ack_seq),
header->len);
}
int main() {
char errbuf[PCAP_ERRBUF_SIZE];
pcap_t *handle;
struct bpf_program fp;
char filter_expr[] = "tcp port 9090";
bpf_u_int32 net, mask;
/* Open the loopback interface */
const char *dev = "lo";
pcap_lookupnet(dev, &net, &mask, errbuf);
handle = pcap_open_live(dev,
65536, /* Snapshot length */
1, /* Promiscuous mode */
1000, /* Read timeout (ms) */
errbuf);
if (!handle) {
fprintf(stderr, "pcap_open_live: %s\n", errbuf);
return 1;
}
/* Compile and apply the BPF filter */
if (pcap_compile(handle, &fp, filter_expr, 0, net) == -1) {
fprintf(stderr, "pcap_compile: %s\n", pcap_geterr(handle));
return 1;
}
pcap_setfilter(handle, &fp);
printf("Capturing TCP packets on %s port 9090...\n", dev);
printf("%-20s %-20s %-8s %-12s %-12s %s\n",
"Source", "Destination", "Flags", "Seq", "Ack", "Len");
printf("%-80s\n",
"-----------------------------------------------------------"
"-------------------");
/* Capture 20 packets then exit */
pcap_loop(handle, 20, packet_handler, NULL);
pcap_freecode(&fp);
pcap_close(handle);
return 0;
}
Compile and run (requires libpcap-dev):
# Install libpcap
sudo apt-get install libpcap-dev
# Compile
gcc libpcap_example.c -o libpcap_example -lpcap
# Run as root
sudo ./libpcap_example
Q1. What does tcpdump capture and what privileges does it need?
tcpdump captures network packets at the raw packet level — it can see TCP segments, UDP datagrams, ICMP packets, and other IP traffic. It requires root (superuser) privileges because it uses the libpcap library to put the network interface into promiscuous mode, which allows it to see all packets on the network segment, not just those addressed to the local host.
Q2. What do the tcpdump flags [S], [.], [P.], [F.], and [R] mean?
[S] is SYN (connection initiation); [S.] is SYN+ACK (server’s response to SYN); [.] is ACK only (pure acknowledgement); [P.] is PSH+ACK (data segment with push flag — signals receiver to deliver data immediately); [F.] is FIN+ACK (connection teardown); [R] or [R.] is RST (reset — abrupt close, refused connection, or error).
Q3. What is the BPF filter and how does it improve performance?
BPF (Berkeley Packet Filter) is a kernel-level packet filtering mechanism. When you specify a filter expression in tcpdump (like tcp port 80), it is compiled into BPF bytecode that executes inside the kernel. Only matching packets are copied from kernel space to userspace. This keeps the overhead very low even on high-traffic interfaces — non-matching packets are discarded in the kernel before any userspace copy takes place.
Q4. What is a pcap file and how do you use it?
A pcap file (packet capture file) is the standard format for storing captured network packets, created with tcpdump -w filename.pcap. It stores the raw bytes of each packet along with a timestamp. The file can be read back with tcpdump -r filename.pcap for command-line analysis, or opened in Wireshark for GUI-based deep inspection. Any tool using libpcap can read pcap files.
Q5. Despite its name, can tcpdump capture UDP and ICMP traffic?
Yes. Despite the name, tcpdump can capture all IP-based traffic including TCP, UDP, ICMP, ARP, and other protocols. The name is historical (it was originally used mainly for TCP debugging). You can use BPF filters like udp or icmp to capture only those protocol types.
Q6. How would you use tcpdump to verify that a TCP connection is being closed cleanly versus being reset?
A clean close produces FIN segments: you will see Flags [F.] followed by Flags [.] (ACK), then another Flags [F.] from the other side. An abrupt reset produces Flags [R] or Flags [R.] without a preceding FIN. To filter specifically: use 'tcp[13] & 1 != 0' to see only FIN packets, or 'tcp[13] & 4 != 0' to see RST packets. Seeing RST instead of FIN usually means a crash, a call to close() with SO_LINGER set to zero timeout, or a connection refused.
Q7. What is the difference between tcpdump and Wireshark?
Both use libpcap for packet capture, but tcpdump is command-line only and outputs text, while Wireshark has a graphical interface that shows decoded protocol fields, follows TCP streams, and can reassemble application data. A common workflow is to run tcpdump on a remote server (where no GUI is available) with the -w flag to save to a pcap file, then copy the file to a local machine and open it in Wireshark for deep analysis.
You have covered all the advanced TCP socket topics from Chapter 61.
