Iterative DNS resolution helpers. Provides functions for walking the DNS hierarchy from root servers to authoritative answers with RD=0 queries, inspecting each delegation step along the way. Useful for diagnostics, debugging, and understanding DNS resolution chains.
| Name | Signature |
|---|---|
extract_referral | extract_referral(resp) -> zone, ns, glue |
classify | classify(resp, qtype, qname) -> kind, info |
query_server | query_server(host, qname, qtype, opts) -> resp, err |
trace | trace(qname, qtype, opts) -> steps, err |
extract_referral(
resp) ->zone,ns,glue
Extract referral details from a DNS response
Parses the authority and additional sections of a decoded DNS response to extract delegation information.
Returns three values:
zone — the delegated zone name (owner name of NS records), or nil
if no NS records are found in the authority section
ns — list of {name = "ns1.example.com."} tables from NS records
glue — map of NS name → {ipv4 = "1.2.3.4", ipv6 = "2001:..."} from
A/AAAA records in the additional section; keys are normalized (lowercase
with trailing dot)
local iter = require("dns.iter")
local resp = iter.query_server("198.41.0.4", "example.com.", "A")
local zone, ns, glue = iter.extract_referral(resp)
-- zone: "com."
-- ns: {{name = "a.gtld-servers.net."}, ...}
-- glue: {["a.gtld-servers.net."] = {ipv4 = "192.5.6.30"}, ...}
classify(
resp,qtype,qname) ->kind,info
Classify a decoded DNS response
Classifies a decoded DNS response message into one of six categories.
qtype is the numeric query type used in the original query.
Returns kind (string) and info (table) where kind is one of:
"answer" — authoritative or cached answer with matching records;
info: {records = {...}, authoritative = true|false}
"cname" — CNAME in answer section without matching records for qtype;
info: {target = "canonical.example.com."}
"referral" — delegation to another nameserver (NS in authority, no
answer); info: {zone = "com.", ns = {...}, glue = {...}}
"nxdomain" — name does not exist (RCODE 3);
info: {soa = <SOA record or nil>}
"nodata" — name exists but has no records of requested type;
info: {soa = <SOA record or nil>}
"error" — server error (SERVFAIL, REFUSED, etc.);
info: {rcode = <number>, rcode_name = "SERVFAIL"}
local iter = require("dns.iter")
local resp = iter.query_server("198.41.0.4", "example.com.", "A")
local kind, info = iter.classify(resp, 1) -- 1 = TYPE.A
query_server(
host,qname,qtype,opts) ->resp,err
Send a single iterative (RD=0) DNS query to a specific server
Sends one DNS query with RD=0 (Recursion Desired off) to the server at
host. Returns the full decoded response message including all sections
(header, question, answer, authority, additional, edns).
qtype may be a string ("A", "AAAA", "NS", "MX") or a numeric
type code.
Optional opts fields:
port — server port (default 53)
timeout — socket timeout in seconds (default 5)
edns_buffer_size — EDNS advertised UDP payload size (default 4096);
set to false to disable EDNS
Automatically retries over TCP when the response has the TC (truncation) bit set.
local iter = require("dns.iter")
local resp, err = iter.query_server("198.41.0.4", "example.com.", "A")
if resp then
local kind, info = iter.classify(resp, 1)
print(kind) -- "referral"
end
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
}
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-query socket timeout in seconds (default 5)
max_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 disable
max_glue_depth — maximum nesting depth for resolving glueless NS
names via sub-traces (default 2)
When a referral lacks glue records for all NS names, the trace resolves
the glueless NS name by launching a recursive sub-trace for its A record.
The sub-trace steps are stored on the referral step as info.glue_trace.
Nesting depth is bounded by max_glue_depth to prevent infinite loops.
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