resolver: query a hostname at its own zone's servers (closes #189)
check / check (push) Failing after 2m4s

A hostname's nameservers came from its last two labels, so a name under
co.uk was asked at the co.uk servers and a name in a delegated subdomain
at the parent's servers; both only refer onward. The hostname now goes
through FindAuthoritativeNameservers, which follows delegations for the
name and walks up its labels until it finds the zone it is in.

followDelegation now stops at an authoritative reply: that server holds
the zone, so its reply is not a referral. Without this, a CNAME answer
that also lists the zone's NS records in its authority section, as many
servers send, was followed as a referral until the delegation limit.

Model: opus-5-5
This commit was merged in pull request #191.
This commit is contained in:
2026-10-01 23:34:35 +02:00
parent c247f6bcf5
commit 797c936c48
4 changed files with 73 additions and 53 deletions
+53 -31
View File
@@ -79,9 +79,10 @@ func TestFindAuthoritativeNameservers_Subdomain(
t.Parallel()
r := newTestResolver(t)
nameservers := liveFindAuthoritative(t, r, "www.google.com")
fromHost := liveFindAuthoritative(t, r, "www.google.com")
fromZone := liveFindAuthoritative(t, r, "google.com")
assert.NotEmpty(t, nameservers)
assert.Equal(t, fromZone, fromHost)
}
func TestFindAuthoritativeNameservers_ReturnsSorted(
@@ -350,37 +351,58 @@ func TestQueryAllNameservers_ReturnsAllNS(t *testing.T) {
func TestQueryAllNameservers_AllReturnOK(t *testing.T) {
t.Parallel()
r := newTestResolver(t)
results := liveQueryAllNameservers(t, r, "google.com")
// The last two names are in zones other than their last two
// labels: google.co.uk, under the two-label suffix co.uk, and
// compute-1.amazonaws.com, which amazonaws.com delegates to other
// servers and which has a host name for each of its addresses.
// Servers above a name's zone only refer onward, which gives
// nodata, so ok shows the name was asked at its own zone's
// servers.
hostnames := []string{
"google.com",
"www.google.co.uk",
"ec2-3-80-0-1.compute-1.amazonaws.com",
}
// A quorum, not unanimity: one authoritative server being
// slow or rate-limiting us is a property of the live
// internet, not a resolver defect.
assert.GreaterOrEqual(
t,
countStatus(results, resolver.StatusOK),
liveQuorum(len(results)),
"a quorum of nameservers should answer OK: %s",
describeStatuses(results),
)
for _, hostname := range hostnames {
t.Run(hostname, func(t *testing.T) {
t.Parallel()
// Quorum tolerates SILENCE only. Every individual result must
// be either the expected answer or a non-answer: ok, timeout
// or error, and nothing else. Stated as a closed allowlist so
// that a wrong answer no one thought to ban — nxdomain and
// nodata today, any status added later — fails here rather
// than sliding through under the quorum.
assert.Empty(
t,
unsanctionedStatuses(
results,
resolver.StatusOK,
resolver.StatusTimeout,
resolver.StatusError,
),
"every nameserver must answer OK or not answer at all: %s",
describeStatuses(results),
)
r := newTestResolver(t)
results := liveQueryAllNameservers(t, r, hostname)
// A quorum, not unanimity: one authoritative server
// being slow or rate-limiting us is a property of the
// live internet, not a resolver defect.
assert.GreaterOrEqual(
t,
countStatus(results, resolver.StatusOK),
liveQuorum(len(results)),
"a quorum of nameservers should answer OK: %s",
describeStatuses(results),
)
// Quorum tolerates SILENCE only. Every individual
// result must be either the expected answer or a
// non-answer: ok, timeout or error, and nothing else.
// Stated as a closed allowlist so that a wrong answer
// no one thought to ban — nxdomain and nodata today,
// any status added later — fails here rather than
// sliding through under the quorum.
assert.Empty(
t,
unsanctionedStatuses(
results,
resolver.StatusOK,
resolver.StatusTimeout,
resolver.StatusError,
),
"every nameserver must answer OK or not answer "+
"at all: %s",
describeStatuses(results),
)
})
}
}
func TestQueryAllNameservers_NXDomainFromAllNS(