is it dns? / wiki ← back to the live monitor

Wireshark: DNS capture and display filters

Wireshark filters land in two places. Capture filters use BPF syntax and decide what gets recorded at all; you set them before you press start. Display filters use Wireshark's own syntax and decide what you see in the pane once you have captured. They are not interchangeable. This page covers both, with the filters you reach for when something DNS-shaped is wrong. For a plain-English walk through what a DNS query looks like on the wire, start there first.


Capture filters (BPF): what to record#

BPF is the kernel-level filter language. It is terser, narrower, and case-sensitive. Keep them simple and broad: you can always narrow further with a display filter.

GoalFilter
All DNS over UDP+TCPport 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 resolverhost 192.0.2.53 and port 53
DNS to/from a specific clienthost 192.0.2.42 and port 53
Any VLAN-tagged DNS trafficvlan and port 53
DNS or the L2 ARP needed to reach DNSport 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

Capture filters cannot reference DNS field names. They are packet-shape filters. If you need to filter on qname or rcode, do it as a display filter on a broader capture.

Two gotchas: host cloudflare-dns.com resolves the name to its addresses once, when the capture starts, so a DoH service spread across many IPs needs those IPs listed explicitly. And DNS over QUIC (RFC 9250) shares port 853 with DoT but runs over UDP, so tcp port 853 isolates DoT alone.


Display filters (Wireshark syntax): what to look at#

This is where the DNS-aware part lives. Wireshark parses every DNS field by name, so you can filter on qname, rcode, flags, EDNS options, and anything else in the DNS wire format.

Sieve everything down to DNS first#

dns

Then layer in the question.

One catch: mDNS (UDP 5353) and LLMNR (UDP 5355) are parsed by the same dissector but register as their own protocols, so the bare dns filter excludes them while field filters like dns.qry.name still match their chatter. Add udp.port == 53 when you only want classic unicast DNS.

By query name#

GoalFilter
Queries for a specific namedns.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 TLDdns.qry.name matches "\\.gov$"
Reverse-lookup queriesdns.qry.name contains "in-addr.arpa" or dns.qry.name contains "ip6.arpa"

By query type (qtype)#

Use the numeric type or the symbolic name. Common ones:

QtypeNumericFilter
A1dns.qry.type == 1
AAAA28dns.qry.type == 28
CNAME5dns.qry.type == 5
MX15dns.qry.type == 15
NS2dns.qry.type == 2
PTR12dns.qry.type == 12
SOA6dns.qry.type == 6
TXT16dns.qry.type == 16
SRV33dns.qry.type == 33
CAA257dns.qry.type == 257
SVCB64dns.qry.type == 64
HTTPS65dns.qry.type == 65
DNSKEY48dns.qry.type == 48
DS43dns.qry.type == 43
RRSIG46dns.qry.type == 46
SSHFP44dns.qry.type == 44
TLSA52dns.qry.type == 52
ANY255dns.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 rcode / DNS errors#

GoalFilter
Only responsesdns.flags.response == 1
Only NXDOMAIN (see glossary)dns.flags.rcode == 3
Only SERVFAILdns.flags.rcode == 2
Only REFUSEDdns.flags.rcode == 5
Any non-zero rcode (anything that is not NOERROR)dns.flags.rcode != 0 and dns.flags.response == 1
FORMERRdns.flags.rcode == 1
NOTIMPdns.flags.rcode == 4

By DNS header flags#

GoalFilter
Recursion-desired queriesdns.flags.recdesired == 1
Authoritative answersdns.flags.authoritative == 1
DNSSEC-authenticated answers (AD flag)dns.flags.authenticated == 1
Truncated responses (UDP overflow)dns.flags.truncated == 1
Recursion-available answersdns.flags.recavail == 1
Checking-disabled (client opted out of DNSSEC)dns.flags.checkdisable == 1

Field names verified against the official Wireshark DNS display filter reference. The CD field really is dns.flags.checkdisable, with no trailing d.

By transport / direction#

GoalFilter
Just UDP DNSdns and udp
Just TCP DNS (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"
Outbound queries from a resolverip.src == 192.0.2.53 and dns.flags.response == 0
Inbound responses to a resolverip.dst == 192.0.2.53 and dns.flags.response == 1

DoH rides inside TLS on port 443, so without decryption the most Wireshark can show you is which server the client talked to: the SNI in the ClientHello (absent when ECH encrypts it). To see the queries themselves, capture with an SSLKEYLOGFILE and point Wireshark at it (Preferences > Protocols > TLS > (Pre)-Master-Secret log filename); once decrypted, the HTTP/2 stream and the DNS inside it dissect normally and the plain dns filter works again.

Fragmentation / EDNS#

GoalFilter
EDNS0 OPT pseudo-recordsdns.opt
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 defaultdns and udp.length > 1240

udp.length counts the 8-byte UDP header, so a DNS payload over 1232 bytes shows a length over 1240. 1232 bytes is the EDNS buffer size recommended by DNS Flag Day 2020 to avoid IP fragmentation; it is what Cloudflare and most large resolvers advertise today.

Useful column setup#

In Edit > Preferences > Columns, add:

ColumnFieldWhy
Query namedns.qry.nameThe thing being asked
Qtypedns.qry.typeA, AAAA, etc.
Rcodedns.flags.rcodeNOERROR / NXDOMAIN / SERVFAIL at a glance
AD flagdns.flags.authenticatedDNSSEC AD bit set by resolver
Clientip.src (for queries)Who is asking
RTTdns.timeResolver latency (appears on responses only)

Common scenarios: worked filters#

Catch every NXDOMAIN from a single client#

dns.flags.rcode == 3 and ip.dst == 192.0.2.42

Filtering on ip.dst because the NXDOMAIN response travels to the client.

Find truncated UDP answers that forced TCP fallback#

dns.flags.truncated == 1

If you see many of these, the server is producing answers larger than the client's advertised EDNS UDP buffer (or the classic 512-byte limit when the query carries no EDNS), and clients are retrying over TCP. Common causes: large TXT records, RRSIG inflation, or a small EDNS UDP buffer advertised by the client.

Spot a misconfigured stub resolver#

dns.flags.recdesired == 0 and dns.flags.response == 0

Stub clients set RD=1; queries with RD=0 are what a recursive resolver sends while walking the delegation chain. Seeing RD=0 from a resolver toward authoritative servers is normal iteration. Seeing it from an end-user device is not: either the machine thinks it is a resolver, or its stub configuration is broken.

Confirm DNSSEC validation is happening#

dns and dns.flags.authenticated == 1

If the AD-flag count is near zero across a long capture, either the resolver is not validating, or your capture point is upstream of where validation happens (you are seeing pre-validation queries to the authoritative server, not post-validation responses to the client). See dnssec.

The AD bit is set by the resolver to signal that it validated the answer. Seeing AD=0 does not always mean validation failed; the resolver may have validated internally without setting the bit for stub clients that did not request DNSSEC data. Use dns.resp.z.do == 1 to check whether the client set the DO bit in its EDNS OPT record.

Find the slowest queries#

Add the dns.time column, sort descending. Anything above 500 ms during normal operation is worth investigating. Cold-cache misses for slow authoritative servers make up most of the long tail; repeated slow lookups to the same name point to pathology.


Capturing DNS traffic with tcpdump#

You can capture DNS traffic on any interface with tcpdump and open the result in Wireshark:

sudo tcpdump -i eth0 -nn -w /tmp/dns.pcap 'port 53'

Transfer the file to your workstation, then open it in Wireshark:

scp user@capture-host:/tmp/dns.pcap .

For VLAN-tagged environments, capture at the trunk interface and let Wireshark decode the 802.1Q headers:

sudo tcpdump -i eth0 -nn -w /tmp/dns-vlan.pcap 'vlan and port 53'

dnstap vs. wire capture#

dnstap is a different mechanism: it captures DNS messages at the resolver process level (BIND, Knot, Unbound support it) before or after the resolver acts on them. Wireshark sees the IP packets on the wire; dnstap sees the DNS messages as the resolver sees them, including queries the resolver generated itself for recursive resolution. The two approaches are complementary.


See also#