resolver: ask a referral's nameservers that come without addresses (closes #221)
check / check (push) Canceled after 0s

Looking up a nameserver's own address followed only the addresses a
referral gave, so a nameserver whose zone is delegated without them,
such as a.ntpns.org of pool.ntp.org, never resolved. The walk to a
name's nameservers looked addresses up only when a referral gave none,
so when it gave some it asked only those. Both now ask the nameservers
whose addresses the referral gives first and, if none of them gives a
usable reply, look up and ask the others; with no addresses given, all
are looked up, as before. Looking up all of them at once sent too many
queries to the root servers. maxLookupDepth stops lookups two deep, so
delegations that point at each other still end.

Model: opus-5-5
This commit is contained in:
2026-10-02 06:14:36 +00:00
parent a18803ff28
commit 031e595616
5 changed files with 199 additions and 37 deletions
+79
View File
@@ -162,6 +162,85 @@ func TestResolveNSIPs_EveryNameserver(t *testing.T) {
assert.ElementsMatch(t, want, got)
}
// TestResolveNSIPs_ZoneDelegatedWithoutAddresses looks up the address
// of a.ntpns.org, a nameserver of pool.ntp.org. The org servers delegate
// ntpns.org to nameservers in other zones and give none of their
// addresses, so those are looked up on the way.
func TestResolveNSIPs_ZoneDelegatedWithoutAddresses(t *testing.T) {
t.Parallel()
r := newTestResolver(t)
ips := liveResolveNSIPs(t, r, []string{"a.ntpns.org."}, 1)
for _, ip := range ips {
assert.NotNil(t, net.ParseIP(ip), "should be valid IP: %s", ip)
}
}
// TestQueryNameservers_GivenAddressesFail asks the servers of ntp.org
// about pool.ntp.org, as the walk to a name under ntp.org does after the
// org servers' referral. That referral names four nameservers and gives
// an address for ns1.everett.org alone; here the given address is
// 192.0.2.1, where nothing answers, so the other three must be looked
// up and asked.
func TestQueryNameservers_GivenAddressesFail(t *testing.T) {
t.Parallel()
r := newTestResolver(t)
var resp *dns.Msg
livednstest.Retry(
t,
"QueryNameservers(192.0.2.1 and three ntp.org nameservers, "+
"pool.ntp.org)",
func(ctx context.Context) error {
var err error
resp, err = r.QueryNameservers(
ctx, []string{"192.0.2.1"},
[]string{"anyns.pch.net.", "dns1.udel.edu.", "dns2.udel.edu."},
"ntp.org.", "pool.ntp.org.", dns.TypeNS, 0,
)
return err
},
)
assert.NotEmpty(t, resolver.NSSetFrom(resp, "pool.ntp.org."))
}
// TestQueryNameservers_LookupDepth asks the servers of desec.io about
// ns1.desec.io, giving no address and naming ns1.desec.io without one.
// Below maxLookupDepth its address is looked up and it is asked. At the
// limit it is not, so nothing can be asked: this is where lookups of
// nameserver addresses stop when delegations point at each other.
func TestQueryNameservers_LookupDepth(t *testing.T) {
t.Parallel()
r := newTestResolver(t)
withoutAddresses := []string{"ns1.desec.io."}
livednstest.Retry(
t,
"QueryNameservers(ns1.desec.io without its address, ns1.desec.io)",
func(ctx context.Context) error {
_, err := r.QueryNameservers(
ctx, nil, withoutAddresses, "desec.io.", "ns1.desec.io.",
dns.TypeA, 0,
)
return err
},
)
_, err := r.QueryNameservers(
t.Context(), nil, withoutAddresses, "desec.io.", "ns1.desec.io.",
dns.TypeA, resolver.MaxLookupDepth,
)
require.ErrorIs(t, err, resolver.ErrNoNameservers)
}
// ----------------------------------------------------------------
// QueryNameserver tests
// ----------------------------------------------------------------