The most surprising thing about tcpdump’s filter syntax is that it’s not tcpdump’s syntax at all; it’s the Berkeley Packet Filter (BPF) language, and it runs directly on the kernel.

Let’s see it in action. Imagine you want to capture only TCP traffic to or from port 80 on your network interface eth0.

sudo tcpdump -i eth0 'tcp port 80'

You’ll see output like this, showing handshake packets, data transfers, and acknowledgments, all on port 80:

14:05:30.123456 IP 192.168.1.10.54321 > 10.0.0.5.http: Flags [S], seq 123456789, win 65535, options [mss 1460,nop,wscale 6,sackOK,TS val 12345678 ecr 0], length 0
14:05:30.123500 IP 10.0.0.5.http > 192.168.1.10.54321: Flags [S.], seq 987654321, ack 123456790, win 65535, options [mss 1460,nop,wscale 6,sackOK,TS val 87654321 ecr 12345678], length 0
14:05:30.123550 IP 192.168.1.10.54321 > 10.0.0.5.http: Flags [.], ack 987654322, win 65535, options [nop,nop,TS val 12345679 ecr 87654321], length 0
14:05:30.123600 IP 192.168.1.10.54321 > 10.0.0.5.http: Flags [P.], seq 1, ack 1, win 65535, options [nop,nop,TS val 12345679 ecr 87654321], length 1024: HTTP: GET / HTTP/1.1

The core problem BPF solves is the inability for userspace tools like tcpdump to directly read raw network packets without the operating system’s kernel first deciding what to do with them. Historically, this meant all packets had to be copied from the kernel to userspace, a huge performance bottleneck for busy networks. BPF allows you to define a filter expression in userspace, which tcpdump then compiles into a compact bytecode. This bytecode is loaded into the kernel, where a BPF virtual machine sits right at the network interface driver level. The VM then evaluates incoming packets against your filter. Only packets that match the filter are copied from the kernel to tcpdump in userspace. This dramatically reduces overhead, allowing tcpdump to capture traffic on high-speed links without dropping packets.

The BPF syntax is built on a series of primitives, which can be combined with logical operators. A primitive typically consists of a type, a qualifier, and a relation.

  • Types: host, net, port, portrange.
  • Qualifiers: src, dst, proto (e.g., tcp, udp, icmp, ip, arp, ether).
  • Relations: ==, !=, <, >, <=, >=.

Let’s break down that port 80 example: 'tcp port 80'.

  • tcp is a qualifier specifying the protocol.
  • port is a type specifying a port number.
  • 80 is the value.

The implicit logic here is (proto tcp) and (port 80). tcpdump intelligently combines these. If you wanted to be more explicit, you could write tcp and port 80.

You can combine these with logical operators:

  • and or &&: Both conditions must be true.
  • or or ||: Either condition can be true.
  • not or !: Negates the following condition.
  • Parentheses () are used for grouping.

For instance, to capture traffic from host 192.168.1.5 OR from host 192.168.1.10 on any port, but NOT TCP traffic:

sudo tcpdump -i eth0 '(host 192.168.1.5 or host 192.168.1.10) and not tcp'

This filter first checks if the source or destination IP address is either 192.168.1.5 or 192.168.1.10. If that’s true, it then checks if the protocol is NOT tcp.

You can also filter by network:

sudo tcpdump -i eth0 'net 192.168.1.0/24'

This captures all traffic where the source or destination IP address belongs to the 192.168.1.0/24 subnet. The /24 is CIDR notation for the netmask.

Filtering by MAC address is also possible using ether:

sudo tcpdump -i eth0 'ether src 00:11:22:33:44:55'

This captures all packets originating from the specified MAC address.

Here’s a more complex example: capture UDP traffic on ports 53 (DNS) or 123 (NTP) from any host on the 10.0.0.0/8 network, but exclude traffic to host 10.0.0.1:

sudo tcpdump -i eth0 'udp and (port 53 or port 123) and net 10.0.0.0/8 and not dst host 10.0.0.1'

The order of evaluation for BPF expressions matters, and parentheses are your best friend for clarity and correctness. When tcpdump compiles your expression, it generates a series of jump instructions for the BPF VM. The VM processes each packet by executing these instructions. If an instruction causes the VM to jump to an "accept" path, the packet is copied to userspace. If it jumps to a "reject" path, the packet is dropped immediately. This efficient, low-level filtering is why BPF is so critical for network analysis.

The most common pitfall for newcomers is forgetting that BPF filters are applied before packets are sent to userspace, meaning you can’t filter on information that tcpdump itself adds to the output (like timestamps or packet lengths that aren’t directly part of the on-the-wire payload).

Once you’ve mastered basic filtering, the next logical step is learning about packet content matching using byte.

Want structured learning?

Take the full Tcpdump course →