Wireshark: DNS filters
Record DNS with a capture filter, then find what you need with a display filter. Field names are from Wireshark's DNS filter reference.
Sends one query to capture while the filters on this page are running.
dig @1.1.1.1 example.isitdns.net AGO NOERROR, and one request and one response in the capture
NO GO nothing in the capture, which means the wrong interface or capture filter
Capture filters: what to record#
Capture filters are BPF, the same syntax tcpdump takes. They see packet shape only, never DNS fields: keep them broad and narrow later.
| Goal | Filter |
|---|---|
| All DNS over UDP+TCP | port 53 |
| DoT (TCP 853) and DoQ (UDP 853) | port 853 |
| DoH (one of many TLS streams) | port 443 and host cloudflare-dns.com |
| DNS to/from a specific resolver | host 192.0.2.53 and port 53 |
| DNS to/from a specific client | host 192.0.2.42 and port 53 |
| Any VLAN-tagged DNS traffic | vlan and port 53 |
| DNS or the L2 ARP needed to reach DNS | port 53 or arp |
| Only the resolver leg (drop client noise) | port 53 and host 192.0.2.53 and not host 192.0.2.42 |
port 853 catches DoT and DoQ (RFC 9250); tcp port 853 is DoT alone. A hostname in a capture filter is resolved once, when the capture starts.
Display filters: what to look at#
Start with dns. It leaves out mDNS and LLMNR; add udp.port == 53 if they still show up through a field filter.
By name#
| Goal | Filter |
|---|---|
| Queries for a specific name | dns.qry.name == "example.com" |
Any name containing a string (contains matches anywhere, not only the end) | dns.qry.name contains "example.com" |
| Queries for a specific TLD | dns.qry.name matches "\\.gov$" |
| Reverse-lookup queries | dns.qry.name contains "in-addr.arpa" or dns.qry.name contains "ip6.arpa" |
By type#
| Qtype | Numeric | Filter |
|---|---|---|
| A | 1 | dns.qry.type == 1 |
| AAAA | 28 | dns.qry.type == 28 |
| CNAME | 5 | dns.qry.type == 5 |
| MX | 15 | dns.qry.type == 15 |
| NS | 2 | dns.qry.type == 2 |
| PTR | 12 | dns.qry.type == 12 |
| SOA | 6 | dns.qry.type == 6 |
| TXT | 16 | dns.qry.type == 16 |
| SRV | 33 | dns.qry.type == 33 |
| CAA | 257 | dns.qry.type == 257 |
| SVCB | 64 | dns.qry.type == 64 |
| HTTPS | 65 | dns.qry.type == 65 |
| DNSKEY | 48 | dns.qry.type == 48 |
| DS | 43 | dns.qry.type == 43 |
| RRSIG | 46 | dns.qry.type == 46 |
| SSHFP | 44 | dns.qry.type == 44 |
| TLSA | 52 | dns.qry.type == 52 |
| ANY | 255 | dns.qry.type == 255 (expect a minimal HINFO answer per RFC 8482, or a NOTIMP from resolvers like Cloudflare that decline ANY, not full record sets) |
By error code#
| Goal | Filter |
|---|---|
| Only responses | dns.flags.response == 1 |
Only NXDOMAIN (see glossary) | dns.flags.rcode == 3 |
Only SERVFAIL | dns.flags.rcode == 2 |
Only REFUSED | dns.flags.rcode == 5 |
Any non-zero rcode (anything that is not NOERROR) | dns.flags.rcode != 0 and dns.flags.response == 1 |
FORMERR | dns.flags.rcode == 1 |
NOTIMP | dns.flags.rcode == 4 |
By header flag#
| Goal | Filter |
|---|---|
| Recursion-desired queries | dns.flags.recdesired == 1 |
| Authoritative answers | dns.flags.authoritative == 1 |
| DNSSEC-authenticated answers (AD flag) | dns.flags.authenticated == 1 |
| Truncated responses (UDP overflow) | dns.flags.truncated == 1 |
| Recursion-available answers | dns.flags.recavail == 1 |
| Checking-disabled (client opted out of DNSSEC) | dns.flags.checkdisable == 1 |
By transport#
| Goal | Filter |
|---|---|
| UDP DNS only | dns and udp |
| TCP DNS only (large answers, AXFR) | dns and tcp |
| DoT (DNS over TLS) | tcp.port == 853 |
| DoH ClientHello (no TLS keys needed) | tls.handshake.extensions_server_name contains "dns" (TLS fields) |
| Outbound queries from a resolver | ip.src == 192.0.2.53 and dns.flags.response == 0 |
| Inbound responses to a resolver | ip.dst == 192.0.2.53 and dns.flags.response == 1 |
DoH is inside TLS: without the keys, Wireshark shows only which server was asked (the SNI). With an SSLKEYLOGFILE loaded under Preferences > Protocols > TLS, the dns filter works again.
Size and fragments#
| Goal | Filter |
|---|---|
| EDNS0 OPT pseudo-records | dns.resp.type == 41 |
| Anything fragmented at IP (v4 or v6) | ip.flags.mf == 1 or ip.frag_offset > 0 or ipv6.fraghdr |
| UDP DNS payloads over the 1232-byte EDNS default | dns and udp.length > 1240 |
udp.length includes the 8-byte UDP header, so a 1232-byte payload reads 1240. 1232 is the buffer size DNS Flag Day 2020 recommends.
See also#
- dig-flags: what each header flag means
- dot-doh: DoT, DoH and DoQ
- query-types: every record type number
- servfail: when the answers are
SERVFAIL