Two filter languages. Capture filter = BPF: runs in-kernel as packets arrive; rejected packets are gone. Display filter = Wireshark's own language: runs after capture over the buffer, hides rows without deleting. Same intent, different syntax -- udp port 37008 (BPF) vs udp.port == 37008 (display). Hover a field below for the byte, its BPF idiom, and its Wireshark field.
Pick the wrong one and you either throw away evidence you needed or wait for a filter that never narrows the capture. The rule: capture to keep the haystack small, display to find the needle.
| capture filter (BPF) | display filter | |
|---|---|---|
| when | as packets arrive, in the kernel | after capture, on stored packets |
| effect | rejected packets are gone forever | hidden rows, still in the file |
| syntax | udp port 53 | udp.port == 53 |
| power | fast, coarse, offset/mask math | per-field, dissector-aware, regex |
| used by | tcpdump, tshark -f, Wireshark capture box | Wireshark, tshark -Y |
| use it to | keep the capture small on a busy link | drill into what you already caught |
tzsp is a Wireshark display dissector name; as a capture filter you'd write udp port 37008. When a filter "does nothing," check which language the box speaks.There is no "tcpdump syntax" versus "Wireshark syntax" - there is BPF (capture) and the display language, and each tool is a frontend to one or both. Learn the two languages once; the tools are just ergonomics. A capture filter written for tcpdump pastes straight into Wireshark's capture box or tshark -f; a display filter from Wireshark pastes straight into tshark -Y.
| tool | capture (BPF) | display | best at |
|---|---|---|---|
| tcpdump | yes (filter arg) | no | fast CLI capture, headless boxes, writing pcap |
| tshark | yes (-f) | yes (-Y) | scripting, field extraction, stats, pipelines |
| Wireshark | yes (capture box) | yes (main bar) | interactive analysis, dissectors, follow-stream |
| termshark | yes | yes | Wireshark's TUI - full display filters over SSH |
| ngrep | yes + regex | no | grep-style payload matching live on the wire |
| tcpflow | yes | no | reassembling and dumping TCP stream contents |
A BPF expression is qualifiers (what to match) joined by and / or / not. Qualifiers come in three kinds: type (host, net, port, portrange), dir (src, dst), and proto (ether, ip, ip6, arp, tcp, udp, icmp).
| expression | matches |
|---|---|
| host 192.0.2.1 | any packet to or from that address |
| src net 198.51.100.0/24 | sourced from that subnet |
| tcp port 443 | TCP, either direction, port 443 |
| portrange 6000-6100 | any port in the range |
| udp and not port 53 | UDP except DNS |
| icmp or icmp6 | either ICMP |
| vlan and host 192.0.2.1 | inside an 802.1Q tag (resets offsets) |
| flag | does |
|---|---|
| -i eth0 | interface (-i any = all) |
| -n / -nn | don't resolve names / names and ports (do this - see gotchas) |
| -w f.pcap / -r f.pcap | write raw / read back a capture |
| -c 100 | stop after 100 packets |
| -s N | snaplen: keep only the first N bytes. Modern default is already full (262144), so -s0 is a legacy habit - use -s only to truncate |
| -A / -X | print ASCII payload / hex + ASCII |
| -e | show the link-layer (MAC) header |
| -C 100 -W 10 -G 300 | ring buffer: 100MB files, keep 10, rotate every 300s |
decode to the screen AND write -w at the same time | |
| -U | immediate/unbuffered - flush each packet (needed when piping live) |
| --nano | nanosecond timestamps (writes a different pcap magic; some old readers choke) |
When the named qualifiers aren't enough, BPF lets you index raw bytes: proto[offset:size] reads size bytes (1, 2, or 4) at offset relative to that protocol's header, and you mask/compare them. This is a real 54-byte Ethernet+IPv4+TCP SYN frame - hover any field for its bytes, the BPF idiom that matches it, and the equivalent Wireshark field.
| BPF | matches |
|---|---|
| tcp[13] & 2 != 0 | SYN bit set (the flags byte, masked) |
| tcp[13] == 2 | SYN and nothing else - a bare connection attempt / SYN flood |
| tcp[13] & 0x12 == 0x12 | SYN+ACK (the reply half of a handshake) |
| ip[6] & 0x20 != 0 | More Fragments set - a fragmented packet |
| ip[9] == 6 | protocol is TCP (same as just tcp) |
| icmp[0] == 8 | ICMP Echo Request (ping) |
| greater 512 | packet longer than 512 bytes (amp-answer sized) |
| tcp[tcpflags] & (tcp-syn|tcp-ack) == tcp-syn | same as tcp[13]==2 using readable flag constants (tcp-syn, tcp-fin, tcp-rst, tcp-ack...) |
| tcp[((tcp[12]&0xf0)>>2):4] = 0x47455420 | "GET " at the real payload start - computes header length so it survives TCP options |
tcp[13] is byte 13 of the TCP header - in this frame that lands at absolute byte 47, because BPF adds the 14-byte Ethernet and 20-byte IP headers for you. Change the link layer (VLAN, tunnels) and those base offsets shift, which is exactly why the vlan keyword exists.The offset-and-mask idea isn't only for observing traffic - the firewall uses it to drop it. iptables' u32 match reaches into header bytes exactly like BPF; nftables (its modern successor) does it with readable keywords or raw payload expressions. Same mental model, three contexts: capture → legacy drop → modern drop.
| context | match a bare SYN (proto = TCP, flags byte = 0x02) |
|---|---|
| BPF (capture) | tcp[13] == 2 |
| iptables u32 (legacy) | -m u32 --u32 "6&0xFF=0x6 && 4&0x1FFF=0 && 0>>22&0x3C@13&0xFF=0x02" |
| nftables (native) | tcp flags & (syn|ack) == syn |
| nftables (raw payload) | meta l4proto tcp @th,13,8 0x02 |
0>>22&0x3C@, u32's infamous incantation. It computes the IP header length so the match can jump past IP options to the transport header: read the first 4 bytes, >>22 shifts the IHL nibble into place, &0x3C masks it to IHL×4 (header length in bytes), and @ sets that as the new base offset. Then @13&0xFF=0x02 reads TCP byte 13 (the flags) and tests for SYN; the 4&0x1FFF=0 guard first excludes fragments, which have no usable transport offset.iptables is now the nft-backed compatibility CLI on most distros, and u32 survives only through the xt_u32 shim - there is no native u32 in nftables. Write new rules with nftables' readable tcp flags / ip protocol matching, dropping to raw @th,offset,len payload only when a field has no keyword. Add counter drop and a filter becomes a mitigation - the bridge to the attack & mitigation page.Display filters address dissected fields by name and compare them. Operators: == != > < >= <=, plus contains (substring), matches (regex), and set membership in {..}. Combine with && || !.
| display filter | shows |
|---|---|
| ip.addr == 192.0.2.1 | packets to or from that host |
| tcp.port == 443 && tcp.flags.syn == 1 | SYNs to/from 443 |
| tcp.flags.syn==1 && tcp.flags.ack==0 | bare SYNs (flood signature) |
| tcp.analysis.retransmission | Wireshark-flagged retransmits (loss/pressure) |
| http.request.method == "GET" | HTTP GET requests |
| dns.qry.name contains "example" | DNS queries for a name substring |
| tls.handshake.type == 1 | TLS ClientHello - the packet you fingerprint (JA4) |
| frame.len > 512 | large frames (amp answers) |
| ip.ttl < 32 | low TTL - possible spoofing / distant OS |
| tcp.stream eq 3 | one whole TCP conversation (pair with Follow → TCP Stream) |
| frame contains "password" | raw byte substring anywhere in the frame |
| tcp.len > 0 | segments actually carrying payload (skip bare ACKs) |
proto.field == value for you. Follow → TCP/HTTP Stream reassembles a whole conversation, and Statistics → Conversations / Endpoints ranks talkers by bytes - the fastest way to spot the one host drowning a link.Turning "something is wrong" into a specific offender. Vendor-neutral, documentation addresses. See attack & mitigation for what each pattern is.
-s (or an old tool) grabs only the first N bytes and payload/TLS analysis silently breaks. Check capinfos if a capture looks cut off.-n, tools do reverse-DNS on every address - generating traffic, polluting your own capture, and stalling on a busy link.not port 22 and the mirror port.vlan keyword or filter on the outer header.-U (immediate mode) to flush each packet.tcp.analysis.* flags are Wireshark's own sequence-number heuristics, not fields on the wire - treat them as strong hints, not truth.--nano writes a different pcap magic number - fine in modern tools, misread by some old ones.capinfos summarizes a pcap (count, duration, rate); editcap slices/trims it (time range, dedupe, re-snaplen); mergecap joins files; tshark -qz runs the same statistics as Wireshark's menus, headless - the right move for captures too big to open in the GUI.