trace(
qname,qtype,opts) ->steps,err
Trace iterative DNS resolution from root servers to authoritative answer
Walks the DNS hierarchy starting from root servers, following referrals through TLD and authoritative nameservers until an answer (or terminal error) is reached. Each step in the resolution chain is recorded and returned.
Returns a list of step tables on success:
{
server = "198.41.0.4", -- IP address queried
server_name = "a.root-servers.net.",-- name if known, nil otherwise
zone = ".", -- zone this server is authoritative for
qname = "example.com.", -- domain queried
qtype = "A", -- record type queried (string)
kind = "referral", -- classification (see classify())
info = { ... }, -- info table from classify()
response = <decoded msg>, -- full decoded DNS message
rtt_ms = 12, -- round-trip time in milliseconds
proto = "udp", -- transport that got the response
attempts = { ... }, -- failed attempts, or nil (see below)
}
When candidate servers failed before one answered, the step carries the
failures in attempts, so a trace whose wall time exceeds the summed
rtt_ms of its steps stays explainable:
attempts = {
{ server = "192.112.36.4", server_name = "g.root-servers.net.",
proto = "udp", error = "timeout", rtt_ms = 2000 },
}
The last step in the list contains the terminal result: "answer",
"nxdomain", "nodata", or "error". Intermediate steps are
typically "referral" or "cname".
Optional opts fields:
timeout — per-attempt socket timeout in seconds (default 2)udp_attempts — maximum UDP queries per step before the TCP pass
(default 3); 0 disables the UDP passtcp_attempts — maximum TCP queries per step after UDP is exhausted
(default 2); 0 disables the TCP passprefer_tcp — run the TCP pass before the UDP pass; for callers that
have observed the UDP path failing and do not want to pay a full pass
of UDP timeouts per step to rediscover itmax_referrals — maximum referral steps before giving up (default 30)max_cnames — maximum CNAME restarts from root (default 10)root_servers — override root server list (default: IANA root servers)edns_buffer_size — EDNS buffer size (default 4096); false to disablemax_glue_depth — maximum nesting depth for resolving glueless NS
names via sub-traces (default 2)max_duration — wall-clock budget for the whole trace in seconds
(default 15)Every step walks a candidate server list: the shuffled root list at the
root and after a CNAME restart, the referral's nameservers otherwise. A
step fails only after udp_attempts UDP queries and tcp_attempts TCP
queries have all failed; the TCP pass reaches servers on paths that drop
or rate-limit UDP/53.
The attempt counts multiply: a trace can spend max_referrals steps of
udp_attempts plus tcp_attempts queries, each up to timeout long.
max_duration bounds that product, and sub-traces share the parent's
deadline rather than starting their own, so a caller waiting on a trace
waits max_duration at worst however the resolution branches.
When a referral lacks glue records for all NS names, the trace resolves
glueless NS names by launching recursive sub-traces for their A records,
accumulating candidate addresses until two exist or every NS name has
been tried. The first successful sub-trace is stored on the referral
step as info.glue_trace. Nesting depth is bounded by max_glue_depth
to prevent infinite loops. AAAA records never enter the candidate list:
lev sockets are IPv4-only, so an IPv6 address cannot be queried.
local iter = require("dns.iter")
local steps, err = iter.trace("example.com", "A")
if steps then
for i, step in ipairs(steps) do
print(i, step.server, step.kind, step.zone)
end
end