DNS communications
Concept
A DNS communication is one DNS question and its response: a query sent by a client to a resolver, paired with the answer that came back. It is decoded from the transport sessions captured by the Whisperer, whatever carried it:
| Transport | Transport session | Framing |
|---|---|---|
| UDP (53) | UdpConversation or UdpFlow | One datagram is one DNS message |
| TCP (53) | TcpSession | 2-byte length prefix, a message split by a parsing pass is carried over |
| DNS over TLS (853) | TcpSession, TLS | Decrypted with the TLS keys captured by Gocipher, then framed as TCP |
| DNS over QUIC (853) | UdpFlow, TLS | QUIC decrypted with the TLS secrets captured by Gocipher, one DNS exchange per QUIC stream |
Each DNS communication is associated with:
- Source and destination IP address and port
- The transport (
UDPorTCP) and the kind of transport session it came from - The question: name, type and class, with the header flags and EDNS
- The response: rcode, header flags, EDNS, and the answer, authority and additional records
- The number of times the client resent the same question
- A template and tags, computed from the question and the response
DNS has no body: the question and the records are the communication. There is no separate content store, and no payload gating: a query name or an answer IP is metadata, like an HTTP URI. A user without payload access still sees DNS communications in full. Access filters apply as for every protocol.
DNS communications appear in the dedicated DNS view - grid, timeline, dashboard, sequence diagram, waterfall, map, statistics and diff - and in the unified All view.
DNS parsing needs the dnsParsing feature flag and
the DNS parsing settings
of the Whisperer. DNS over UDP also needs UDP sessions
to be tracked on the capture side. All are off by default.
Pairing a query with its response
A response is paired with its query on (client IP, client port, transaction id).
- On a
UdpConversation, the session groups all the client ports talking to one resolver: the client port is read from each datagram, not from the session. - A retransmit is not a new question. The same key and the same question while the query is pending increments
req.retries, and no second communication is created. - A resolver answering both copies of a retransmitted query is recognised: the second answer is absorbed.
- An answer whose query was not captured still makes a full communication, rebuilt from the question the response
echoes. It is flagged
stats.orphanResponse.
On DNS over QUIC, the transaction id is always 0 (RFC 9250): the QUIC stream identifies the exchange. The
pairing key is then (client IP, client port, stream id), and the stream id is part of the communication @id.
When a query is unanswered
A query waits server.dns.responseTimeout (PT5S by default) for its response. This delay is measured on the
capture timeline, not wall-clock: against the instant up to which the parser knows it has seen every packet.
| The parsing pass... | Horizon |
|---|---|
| reached the live end of the session | now minus the parsing delay |
| was cut by the per-pass bound | capture time of the last packet read |
| ended the session | none: everything pending is flushed |
A late response seen before that horizon still pairs, even past the timeout. The consequence: an unanswered query on a conversation that is still open is emitted once the session has been parsed past it, or when the session ends.
Global Tab
The Global tab summarises the exchange: timing, general, question, response, tags and Spider info.
A Pcap button downloads the packets of the communication.
Timing
The timing diagram shows the client and the resolver, the query and the response scaled by the packet timestamps,
the question (A api.example.com) and the response summary (NOERROR (1 answer)).
General
| Field | Description |
|---|---|
| From | The client host and port |
| Client workload | Kubernetes workload of the client, when resolved - see Workload identity |
| To | The resolver host and port |
| Server workload | Kubernetes workload of the resolver, when resolved |
| Transport | UDP or TCP, with the kind of transport session: TCP, UDP flow or UDP conv. |
| Retries | Number of times the client resent the question. Shown only when not zero. |
| Orphan | Shown when the response was captured without its query |
Question
| Field | Description |
|---|---|
| Date | Date the query was sent, user's local time |
| Question | Name, type and class of the question (api.example.com A IN) |
| Template | Computed name of the question, see Templates |
| Opcode | DNS opcode, usually QUERY |
| Size | Size of the query |
Response
| Field | Description |
|---|---|
| Rcode | Response code name and number, colored by status |
| Answers | Number of records in the answer section |
| Min TTL | Smallest TTL of the answer records, in seconds |
| Duration | Delay between the query and the response |
| Size | Size of the response |
When no response was captured, the section shows No response.
Tags
Tags are configured at Whisperer level,
on the question (qname, qtype, qclass) or on the response (rcode, answers, min_ttl).
A DNS tag is multi-valued: an answer tag holds every matching record. Each tag shows its count and cardinality.
Spider info
| Field | Description |
|---|---|
| Whisperer | Name and link to the Whisperer |
| Instance | Instance of the Whisperer that captured the communication |
| Tcp session | Link to the transport session. Titled UDP flow or UDP conversation for UDP |
| Parsing session | The parsing log, when saved |
| Request parsing | Parsing status of the request |
| Response parsing | Parsing status of the response, MISSING when there is none |
Request tab
The Request tab shows the DNS header and its question.
| Section | Fields |
|---|---|
| Header | Transaction id, opcode, flags (RD, CD), retries |
| Question | Name, type, class |
| EDNS | UDP payload size, DNSSEC OK (DO) bit, and the EDNS options (code and data) |
| Client socket | The real client IP and port, read from the packet |
On an orphan response, these values are rebuilt from the response.
Response tab
The Response tab shows the DNS header of the response and its three record sections.
| Section | Fields |
|---|---|
| Header | Rcode and its number, flags (AA, TC, RA, AD), EDNS UDP size and DO bit |
| Answers | One row per record: name, type, class, TTL, data |
| Authority | Same |
| Additional | Same |
- Records are decoded with the library CoreDNS is built on: SVCB and HTTPS records, DNSSEC types and EDNS0 options decode properly. An unknown record type is kept as hex.
- Each section keeps at most 64 records. Beyond that,
res.rrTruncatedis set, and the counts still give the wire values. - A response with the
TCbit was truncated on the wire: the client should retry over TCP. That retry is another communication, on another session, and the two are not linked.
Packets tab
The Packets tab lists the packets the communication was decoded from. You may open them by clicking on the rows.
On DNS over TLS and DNS over QUIC, the packets are encrypted.
Source
The Source tab shows the DNS communication in JSON format, as available through the API.
Below is a sample, abridged:
{
"@id": "...",
"@type": "DnsCom",
"kind": "dns",
"whisperer": "...",
"tcpSession": "...",
"stats": {
"transport": "UDP",
"sessionType": "UdpConversation",
"withTls": false,
"statusCode": 200,
"statusText": "NOERROR",
"duration": 0.0011,
"orphanResponse": false
},
"req": {
"txid": 40211,
"opcode": "QUERY",
"qname": "api.example.com",
"qtype": "A",
"qclass": "IN",
"rd": true,
"retries": 0,
"template": "api.example.com"
},
"res": {
"rcode": "NOERROR",
"rcodeCode": 0,
"aa": false,
"tc": false,
"ra": true,
"answerCount": 1,
"answers": [
{"name": "api.example.com", "type": "A", "class": "IN", "ttl": 30, "data": "10.0.12.7"}
],
"minTtl": 30
}
}
| Field | Description |
|---|---|
| stats.transport | UDP or TCP. DNS over QUIC is UDP with withTls, DNS over TLS is TCP with withTls |
| stats.sessionType | TcpSession, UdpFlow or UdpConversation |
| stats.withTls | true for DNS over TLS and DNS over QUIC |
| stats.statusCode | 200, 500 or 1000, see Statuses |
| stats.statusText | The rcode name, or No response |
| stats.orphanResponse | true when the response was captured without its query |
| stats.tags.* | Extracted tag values, count and cardinality |
| req.qname | Queried name, without its trailing dot |
| req.retries | Number of times the client resent the question |
| req.edns | udpSize, do and options of the query |
| req.clientIp/Port | Real client socket, read from the packet |
| req.template | Computed name of the question, see Templates |
| res.rcode | Response code name (NOERROR, NXDOMAIN, SERVFAIL...). Absent when there is no response |
| res.rcodeCode | Response code number |
| res.answerCount, res.authorityCount, res.additionalCount | Record counts, as on the wire |
| res.answers, res.authority, res.additional | Records: name, type, class, ttl, data. At most 64 each |
| res.rrTruncated | true when records beyond the 64 per section were dropped |
| res.minTtl | Smallest TTL of the answer records |
| dnsParsing | Id of the parsing log document for this communication |
Templates
req.qname has a high cardinality: pod IPs, hashes, UUIDs, reverse lookups. The req.template collapses it:
- The first template rule whose pattern matches the name, lower-cased and without its trailing dot.
- Otherwise the normalised name: labels that are numbers, UUIDs, long hexadecimal strings or encoded IP
addresses become
?.pod-10-1-2-3.ns.pod.cluster.localbecomespod-?.ns.pod.cluster.local, and every reverse lookup becomes?.in-addr.arpaor?.ip6.arpa.
Statuses
DNS uses the same status classes as the other protocols:
stats.statusCode | stats.statusText | Meaning |
|---|---|---|
200 | NOERROR | The resolver answered without error |
500 | the rcode name | Any other rcode (NXDOMAIN, SERVFAIL, REFUSED...), raw value on res.rcode |
500 | Incomplete | A response started on TCP or QUIC but was cut off; res holds what was captured |
1000 | No response | No response was captured: res is absent |
1000 is never counted as an error: it is its own class. Filter on res.rcode to tell NXDOMAIN from SERVFAIL.
DNS over QUIC
DNS over QUIC (DoQ, RFC 9250) is read from the UdpFlow of the QUIC connection, on the ports listed in
server.dns.quicPorts (853 by default):
- The Whisperer reads the client random from the QUIC Initial packets, and the TLS secrets captured by Gocipher are linked to the flow on it. A flow on a QUIC port that never gets a key is skipped. It is never read as plain DNS.
- QUIC v1 and v2 are decrypted, key updates are followed. 0-RTT early data is not decrypted: a query sent as early data has no communication.
- On a stream reset by the server, the partial response is dropped and the query is a
1000 No response. So is a query whose stream stopped coming. A response with a hole is a500 Incomplete. - A connection whose Initial packets were not captured cannot be decrypted. Restart the client after attaching the Whisperer.
- A client that changes address or port starts a new flow, which cannot be decrypted. A flow idle for longer than the
QUIC flow timeout (
PT120Sby default) is split, and its second part cannot be decrypted either. - The Whisperer needs
client.tlsKeys.trackand a Gocipher on the client or the server.
Search-list noise
The DNS dashboard has a Search-list noise category. With the default Kubernetes ndots:5, a name such as
api.example.com is first tried with every search suffix (api.example.com.<ns>.svc.cluster.local...), each an
NXDOMAIN, before the real name resolves.
The widgets match each NXDOMAIN name to a successful name it is an expansion of, and report the share of queries
that are pure search-list waste, the time spent on them, and the names responsible.
This is a heuristic: it works on the top 2000 NXDOMAIN and NOERROR names of the window, it is not per client, and
a name that never resolved successfully in the window cannot be matched.
Export
The CSV export of DNS communications gives one row per answer record: the record name,
type and ttl as meta.name, meta.type and meta.ttl, its data as the payload value. A communication with no
answer exports as a single row. Authority and additional records are not exported.
Retention and purge
DNS communications follow the same Data Storage Policies as the other protocols. They can be removed from Spider with the purge tool, by whisperer and time window.
Limits
- mDNS and LLMNR are not paired. DNS over HTTPS is an HTTP exchange (
application/dns-message), not a DNS communication. - IP fragments and IPv6 extension headers are not reassembled: a fragmented answer shows as no response.
- A transaction id reused on the same client port can, rarely, pair a late answer with the wrong question.
- DNS over HTTP/3 is not decoded.
Access from the CLI and AI agents
DNS communications are available in the spider CLI
(spider search|get|stats|aggs|outliers dns, with --qname, --qtype and --rcode) and in the
MCP server (type: dns).