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.
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. maxLookupDepth
stops lookups three deep, so delegations that point at each other
still end. g.ntpns.org's address needs all three when anyns.pch.net
gives the referral to g.ntpns.org without addresses.

Model: opus-5-5
This commit is contained in:
2026-10-02 07:16:14 +00:00
committed by sneak
parent 250f3dd687
commit 661ef8d937
5 changed files with 227 additions and 37 deletions
+105
View File
@@ -162,6 +162,96 @@ 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 g.ntpns.org, a
// nameserver of pool.ntp.org, for its address, as looking that address
// up does when anyns.pch.net, one of the servers of ntpns.org, gives the
// referral to g.ntpns.org without addresses. Their addresses are looked
// up (here only a.ntpns.org's), and that needs a bitnames.com
// nameserver's address, as the org servers delegate ntpns.org without
// addresses. From depth 1, where looking up g.ntpns.org's address
// starts, that makes three lookups and the address is found. From one
// below maxLookupDepth, the bitnames.com lookup would be past the limit,
// so nothing can be asked.
func TestQueryNameservers_LookupDepth(t *testing.T) {
t.Parallel()
r := newTestResolver(t)
withoutAddresses := []string{"a.ntpns.org."}
var resp *dns.Msg
livednstest.Retry(
t,
"QueryNameservers(a.ntpns.org without its address, g.ntpns.org)",
func(ctx context.Context) error {
var err error
resp, err = r.QueryNameservers(
ctx, nil, withoutAddresses, "g.ntpns.org.", "g.ntpns.org.",
dns.TypeA, 1,
)
return err
},
)
assert.NotEmpty(t, resp.Answer)
_, err := r.QueryNameservers(
t.Context(), nil, withoutAddresses, "g.ntpns.org.", "g.ntpns.org.",
dns.TypeA, resolver.MaxLookupDepth-1,
)
require.ErrorIs(t, err, resolver.ErrNoNameservers)
}
// ----------------------------------------------------------------
// QueryNameserver tests
// ----------------------------------------------------------------
@@ -681,6 +771,21 @@ func TestLookupNS_MatchesFindAuthoritative(t *testing.T) {
assert.Equal(t, fromFind, fromLookup)
}
// TestLookupNS_ParentZoneDelegatedWithoutAddresses looks up the
// nameservers of g.ntpns.org. The org servers delegate its parent zone,
// ntpns.org, without the addresses of its nameservers, so the walk has
// to look them up to ask them. If it did not, the walk for g.ntpns.org
// would fail and LookupNS would return the nameservers of ntpns.org,
// which a.ntpns.org is not one of.
func TestLookupNS_ParentZoneDelegatedWithoutAddresses(t *testing.T) {
t.Parallel()
r := newTestResolver(t)
nameservers := liveLookupNS(t, r, "g.ntpns.org")
assert.Contains(t, nameservers, "a.ntpns.org.")
}
// ----------------------------------------------------------------
// ResolveIPAddresses tests
// ----------------------------------------------------------------