courses

Capturing traffic with tcpdump

The tool you keep coming back to

Almost every packet-capture tool you’ll meet — including the GUI Wireshark — is ultimately a wrapper around libpcap, and tcpdump is its no-frills command-line front end. It is on essentially every Linux system, runs without a display, and filters efficiently in the kernel before packets ever reach userspace. Learn it first and the rest follow. This page walks from basic capture through direction control, the BPF filter language, and reading captures back for analysis.

Introduction

tcpdump is a command line utility for capturing packets. While the name would imply that it can only capture TCP packets, it will also capture UDP based flows. The tool can be used on arbitrary interfaces, capturing either or both directions (in/out), capture only packets which match a filter, etc.

tcpdump is one of those foundational tools. While there are newer packet capture tools, including GUI versions (such as wireshark), ultimately they all come down to a wrapper around libpcap – of which tcpdump is the front-end. With low overhead, lots of flexibility, and a no-frills approach to packet capture, tcpdump really is the tool you’ll keep coming back to. So many systems I work with don’t have any graphical UI – learning tcpdump first has really benefited me over the years. And I’ve yet to encounter a linux based product that doesn’t have it!

Take a look at the following example usages.

Take a look at the following example usages

Text transcript: Introduction to tcpdump</summary>

$ which tcpdump
/sbin/tcpdump

$ man tcpdump
TCPDUMP(8)   System Manager's Manual

NAME
    tcpdump - dump traffic on a network

SYNOPSIS
    tcpdump [-AbdDefhHIJKlLnNOpqStuUvxX#] [ -B buffer_size ]
            [ -c count ] [ -C file_size ] [ -G rotate_seconds ]
            [ -F file ] [ -i interface ] [ -j tstamp_type ]
            [ -m module ] [ -M secret ] [ --number ] [ -Q in|out|inout ]
            [ -r file ] [ -V file ] [ -s snaplen ] [ -T type ]
            [ -w file ] [ -W filecount ] [ -y datalinktype ]
            [ -z postrotate-command ] [ -Z user ]
            [ --time-stamp-precision=tstamp_precision ]
            [ --immediate-mode ] [ --version ]
            [ expression ]

The recording demonstrates which tcpdump (located at /sbin/tcpdump), then opens the man tcpdump manual page, showing the synopsis and key options. Notable flags highlighted: -c (packet count limit), -Q (direction: in/out/inout), -w (write to file), -r (read from file), -v/-vv/-vvv (verbosity levels). </details>

Some specific options to call out here:

  • -c count: Exit after receiving count packets.
  • --print: Print parsed packet output, even if the raw packets are being saved to a file with the -w flag.
  • -Q direction/--direction=direction: Choose send/receive direction for which packets should be captured. Possible values are in, out and inout.
  • -w file: Write the raw packets to file rather than parsing and printing them out. They can later be printed with the -r option. Standard output is used if file is -.
  • -r file: Read packets from file

Direction Capture

From the simple options shown above, we can get more complex in terms of capturing. Let’s do the same example, but only capture outbound traffic.

only capture outbound traffic

Text transcript: Capturing outbound traffic only</summary>

$ tcpdump -Q out -w second_capture.pcap
tcpdump: listening on eth1, link-type EN10MB (Ethernet), capture size 262144 bytes
^C
882 packets captured
1818 packets received by filter
0 packets dropped by kernel

$ tcpdump -vv -r second_capture.pcap
reading from file second_capture.pcap, link-type EN10MB (Ethernet)
19:37:16.443500 IP (tos 0x0, ttl 64, id 30745, offset 0, flags [DF], proto UDP (17), length 60)
    kali478-0.50412 > _gateway.domain: 12548+ A? www.google.com. (32)
[... additional outbound packets ...]

Captures only outbound (-Q out) traffic on eth1 to second_capture.pcap, then reads it back with verbose output (-vv). Shows 882 outbound packets including a DNS query for www.google.com. </details>

And now again, but inbound traffic.

now again, but inbound traffic

Text transcript: Capturing inbound traffic only</summary>

$ tcpdump -Q in -w third_capture.pcap
tcpdump: listening on eth1, link-type EN10MB (Ethernet), capture size 262144 bytes
^C
910 packets captured
1770 packets received by filter
0 packets dropped by kernel

$ tcpdump -r third_capture.pcap
reading from file third_capture.pcap, link-type EN10MB (Ethernet)
19:39:00.533447 IP server-99-84-73-214.hio50.r.cloudfront.net.https > kali478-0.57274: Flags [.]
19:39:01.325071 ARP, Request who-has kali478-0 tell _gateway, length 46
19:39:01.325174 IP a104-69-16-114.deploy.static.akamaitechnologies.com.https > kali478-0.57984: Flags [.]
[... additional inbound packets ...]

Captures only inbound (-Q in) traffic on eth1 to third_capture.pcap, then reads it back. Shows 910 inbound packets including HTTPS responses from CDN servers and an ARP request. </details>

And again, explicitly capturing both directions.

explicitly capturing both directions

Text transcript: Capturing both inbound and outbound traffic</summary>

$ tcpdump -Q inout -w fourth_capture.pcap
tcpdump: listening on eth1, link-type EN10MB (Ethernet), capture size 262144 bytes
^C
1554 packets captured
1554 packets received by filter
0 packets dropped by kernel

$ tcpdump -r fourth_capture.pcap
reading from file fourth_capture.pcap, link-type EN10MB (Ethernet)
19:40:34.703773 IP 172.16.139.1.mdns > 224.0.0.251.mdns: 0*- [0q] 13/0/6 PTR _ssh._tcp.local.
19:40:34.782741 IP kali478-0.43845 > _gateway.domain: 5734+ A? www.google.com. (32)
19:40:34.782799 IP kali478-0.43845 > _gateway.domain: 46443+ AAAA? www.google.com. (32)
19:40:34.784797 IP _gateway.domain > kali478-0.43845: 5734 1/0/0 A 172.217.3.196 (48)
19:40:34.784951 IP _gateway.domain > kali478-0.43845: 46443 1/0/0 AAAA 2607:f8b0:400a:803::2004 (60)
[... additional packets ...]

Captures both directions (-Q inout) to fourth_capture.pcap, yielding 1554 packets. Playback shows a complete DNS exchange: outbound A and AAAA queries for www.google.com, with inbound responses returning both IPv4 and IPv6 addresses. </details>

Filters

tcpdump supports the usage of filters to limit what is capture (or displayed). These filters can be applied at the time of capture, or at the time of view (with tcpdump -r). Filters are an expression which is passed to tcpdump and consists of one or more primitives, where primitives consist of a qualified id. Qualifiers take the form of a type (host, port, etc.), a direction (src, dst, etc.), and a protocol (tcp, udp, ether, etc.). Primitives can be combined with and, or, and not. A quick cast could make this clearer:

Filters

Text transcript: tcpdump BPF filters and verbose output</summary>

# Re-reading fourth_capture.pcap with verbose flags and hex dump:
$ tcpdump -X -K -vvv -r fourth_capture.pcap
reading from file fourth_capture.pcap, link-type EN10MB (Ethernet)

# -X  : print each packet in hex and ASCII
# -K  : don't verify TCP, UDP, or SCTP checksums
# -vvv: maximum verbosity

19:40:34.703773 IP (tos 0x0, ttl 255, id 11705, proto UDP (17), length 660)
    172.16.139.1.mdns > 224.0.0.251.mdns: mDNS record for _ssh._tcp.local.
        0x0000:  4500 0294 2db9 0000 ff11 7392  E...-......s...
        0x0010:  e000 00fb 14e9 14e9 0280 a689  ................
        [... hex dump continues ...]

# Example BPF filter — capture only TCP port 443 (HTTPS):
$ tcpdump -Q inout tcp port 443

# Example filter — capture traffic to/from a specific host:
$ tcpdump host 192.168.1.1

# Example filter — capture DNS (UDP port 53):
$ tcpdump udp port 53

Demonstrates re-reading a capture file with -X (hex+ASCII dump), -K (skip checksum verification), and -vvv (maximum verbosity). Also shows representative BPF filter expressions for common use cases. </details>

Filters use a notation known as BPF, or Berkeley packet filter. See man pcap-filter for more information. Don’t be fooled that there is a bpf page in section 4 of the manual. That’s for the pseudo-device that the kernel exposes.

Interestingly enough, the Linux kernel supports eBPF, which is the extended BPF. This provides a Turing-complete language that can be used to instantiate many different, potentially complex, operations. Which, of course, means it is also a security nightmare and the source of many CVEs over the years.

BPF: Berkeley Packet Filter

BPF is the filter language used by tcpdump (and libpcap generally). When you pass a filter expression to tcpdump, it is compiled into BPF bytecode and loaded into the kernel, where it runs in a small virtual machine. Packets are evaluated against the bytecode before being copied to userspace — this is why filtering at capture time is far more efficient than capturing everything and filtering later.

Filter Primitives

A BPF expression is built from primitives combined with boolean operators. Each primitive has up to three qualifiers:

Qualifier Values Example
type host, net, port, portrange host 10.0.0.1
dir src, dst, src or dst, src and dst src 10.0.0.1
proto tcp, udp, icmp, arp, ip, ip6, ether tcp

Primitives are combined with and (&&), or (||), and not (!). Parentheses group subexpressions (quote or escape them in the shell).

Common Examples

Capture traffic to/from a specific host:

tcpdump host 192.168.1.1

Capture traffic from a source host only:

tcpdump src host 192.168.1.1

Capture traffic to a destination host only:

tcpdump dst host 192.168.1.1

Capture a specific port:

tcpdump port 443

Capture a port range:

tcpdump portrange 8000-8080

Capture by protocol:

tcpdump icmp
tcpdump udp
tcpdump arp

Capture a specific host and port:

tcpdump 'host 10.0.0.1 and port 80'

Exclude a host:

tcpdump not host 10.0.0.1

Capture traffic between two hosts:

tcpdump 'host 10.0.0.1 and host 10.0.0.2'

Capture a subnet:

tcpdump net 10.0.0.0/24

Capture DNS queries (UDP port 53):

tcpdump 'udp port 53'

Capture HTTP and HTTPS:

tcpdump 'tcp port 80 or tcp port 443'

Capture all traffic except SSH (avoid polluting captures on remote sessions):

tcpdump 'not tcp port 22'

Capture TCP SYN packets only (connection initiations):

tcpdump 'tcp[tcpflags] & tcp-syn != 0 and tcp[tcpflags] & tcp-ack == 0'

Capture TCP RST packets (connection resets — useful for diagnosing refused connections):

tcpdump 'tcp[tcpflags] & tcp-rst != 0'

Capture ICMP echo requests (ping):

tcpdump 'icmp[icmptype] == icmp-echo'

Offset-Based Filters

BPF allows direct inspection of packet bytes using the syntax proto[offset:size], where offset is in bytes from the start of the protocol header and size is 1, 2, or 4 bytes.

Capture IPv4 packets with TTL less than 10 (useful for detecting TTL-based evasion):

tcpdump 'ip[8] < 10'

Capture packets with IP “Don’t Fragment” bit set:

tcpdump 'ip[6] & 64 != 0'

Capture HTTP GET requests (match literal bytes in TCP payload):

tcpdump 'tcp port 80 and (tcp[((tcp[12:1] & 0xf0) >> 2):4] = 0x47455420)'

The expression tcp[12:1] & 0xf0 >> 2 computes the TCP data offset to find the start of the payload, then checks if the first four bytes are GET (0x47455420).

Saving and Reusing Filters

Filters apply equally when reading a saved capture with -r:

# Capture everything, filter later
tcpdump -w capture.pcap

# Apply BPF filter to saved file
tcpdump -r capture.pcap 'host 10.0.0.1 and tcp port 443'

This is useful when you are unsure which traffic is relevant at capture time — save everything, then slice it with filters during analysis.

Interface-specific capture

You can use the -i flag to tcpdump to specify a particular interface on which to capture. I use this frequently when I am capturing both ends of a conversation, or from multiple taps. Many taps have USB interfaces, and present as a USB ethernet adapter – this flag allows you to specify that interface, so you can capture from it rather than the default interface.

There’s also a special interface called any that captures from ALL interfaces. This can be useful in situations where you are unsure which interface is being used, or if you really do want to capture all network traffic hitting your machine.

Key takeaways

  • tcpdump is the libpcap front end — low overhead, no GUI required, and present on practically every Linux system. The skills here transfer directly to Wireshark and anything else built on libpcap.
  • Capture everything, filter later: write raw packets with -w file, read them back with -r file, and apply BPF filters at either capture or read time.
  • Control direction with -Q in|out|inout, pick interfaces with -i (including the special any), and limit volume with -c count.
  • BPF filters are built from primitives — a type (host/net/port), a dir (src/dst), and a proto (tcp/udp/icmp/arp) — combined with and/or/not. They compile to bytecode that runs in the kernel, so filtering at capture time is far cheaper than capturing everything.
  • Offset-based filters (proto[offset:size]) let you match on raw header or payload bytes — TCP flags, TTL, the DF bit, even literal payload strings like an HTTP GET.

References


Related course pages: Analyzing traffic with Wireshark · Introduction to Networking

🛠️ Maintenance note: the asciinema casts and their text transcripts reflect an older Kali (/sbin/tcpdump, eth1); interface names and paths vary by host, and the man tcpdump synopsis option string changes between releases. Re-check the linked man pages and confirm the casts still play each term.