resolver: look up every nameserver a referral gives no address for (closes #221)
check / check (push) Canceled after 0s
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 every nameserver a referral names, looking up the addresses it lacks. A lookup can start further lookups; maxLookupDepth stops them two deep, so zones delegated to each other's nameservers, as desec.io and desec.org are, still end. A referral that gives addresses for only some nameservers now costs one lookup per nameserver without one. Model: opus-5-5
This commit is contained in:
@@ -19,6 +19,14 @@ const (
|
||||
maxRetries = 2
|
||||
maxDelegation = 20
|
||||
timeoutMultiplier = 2
|
||||
|
||||
// maxLookupDepth is how many lookups of nameserver addresses may be
|
||||
// under way one inside another. Looking up a nameserver's address
|
||||
// can meet a referral that names nameservers without their
|
||||
// addresses, which are then looked up in turn; without a limit,
|
||||
// delegations that point at each other would never end. Each level
|
||||
// multiplies the queries sent.
|
||||
maxLookupDepth = 2
|
||||
)
|
||||
|
||||
// ErrRefused is returned when a DNS server refuses a query.
|
||||
@@ -229,13 +237,7 @@ func (r *Resolver) followDelegation(
|
||||
return r.resolveNSIterative(ctx, domain)
|
||||
}
|
||||
|
||||
glue := extractGlue(resp.Extra)
|
||||
nextServers := glueIPs(authNS, glue)
|
||||
|
||||
if len(nextServers) == 0 {
|
||||
nextServers = r.resolveNSIPs(ctx, authNS)
|
||||
}
|
||||
|
||||
nextServers := r.referralServers(ctx, resp, 0)
|
||||
if len(nextServers) == 0 {
|
||||
return nil, ErrNoNameservers
|
||||
}
|
||||
@@ -366,18 +368,50 @@ func nsSetFrom(resp *dns.Msg, domain string) []string {
|
||||
return extractNSSet(resp.Answer)
|
||||
}
|
||||
|
||||
// referralServers returns the IPv4 addresses of every nameserver that
|
||||
// resp, a referral, names: the addresses resp gives for it, or else the
|
||||
// addresses its name resolves to. depth is how many lookups of a
|
||||
// nameserver's address the referral was met in, 0 in the walk to a
|
||||
// domain's nameservers; at maxLookupDepth, the nameservers resp gives
|
||||
// no addresses for are left out.
|
||||
func (r *Resolver) referralServers(
|
||||
ctx context.Context,
|
||||
resp *dns.Msg,
|
||||
depth int,
|
||||
) []string {
|
||||
glue := extractGlue(resp.Extra)
|
||||
|
||||
var ips, withoutAddresses []string
|
||||
|
||||
for _, ns := range extractNSSet(resp.Ns) {
|
||||
nsIPs := glueIPs([]string{ns}, glue)
|
||||
if len(nsIPs) == 0 {
|
||||
withoutAddresses = append(withoutAddresses, ns)
|
||||
}
|
||||
|
||||
ips = append(ips, nsIPs...)
|
||||
}
|
||||
|
||||
if depth < maxLookupDepth {
|
||||
ips = append(ips, r.resolveNSIPs(ctx, withoutAddresses, depth+1)...)
|
||||
}
|
||||
|
||||
return ips
|
||||
}
|
||||
|
||||
// resolveNSIPs returns the addresses of every nameserver in nsNames
|
||||
// whose name resolves, for a referral that carries none. The walk can
|
||||
// then go on to the zone's other nameservers when one gives no usable
|
||||
// reply.
|
||||
// whose name resolves, each looked up at depth (see resolveARecord).
|
||||
// The walk can then go on to the zone's other nameservers when one
|
||||
// gives no usable reply.
|
||||
func (r *Resolver) resolveNSIPs(
|
||||
ctx context.Context,
|
||||
nsNames []string,
|
||||
depth int,
|
||||
) []string {
|
||||
var ips []string
|
||||
|
||||
for _, ns := range nsNames {
|
||||
resolved, err := r.resolveARecord(ctx, ns)
|
||||
resolved, err := r.resolveARecord(ctx, ns, depth)
|
||||
if err == nil {
|
||||
ips = append(ips, resolved...)
|
||||
}
|
||||
@@ -438,11 +472,14 @@ func (r *Resolver) resolveNSIterative(
|
||||
return nil, ErrNoNameservers
|
||||
}
|
||||
|
||||
// resolveARecord resolves a hostname to IPv4 addresses using
|
||||
// iterative resolution through the delegation chain.
|
||||
// resolveARecord resolves a hostname, a nameserver's name, to IPv4
|
||||
// addresses using iterative resolution through the delegation chain.
|
||||
// depth is how many lookups of a nameserver's address are under way,
|
||||
// this one included: 1 for a lookup that no other lookup started.
|
||||
func (r *Resolver) resolveARecord(
|
||||
ctx context.Context,
|
||||
hostname string,
|
||||
depth int,
|
||||
) ([]string, error) {
|
||||
if checkCtx(ctx) != nil {
|
||||
return nil, ErrContextCanceled
|
||||
@@ -485,13 +522,8 @@ func (r *Resolver) resolveARecord(
|
||||
break
|
||||
}
|
||||
|
||||
glue := extractGlue(resp.Extra)
|
||||
nextServers := glueIPs(authNS, glue)
|
||||
|
||||
nextServers := r.referralServers(ctx, resp, depth)
|
||||
if len(nextServers) == 0 {
|
||||
// Resolve NS IPs iteratively — but guard
|
||||
// against infinite recursion by using only
|
||||
// already-resolved servers.
|
||||
break
|
||||
}
|
||||
|
||||
@@ -584,7 +616,7 @@ func (r *Resolver) queryNameserver(
|
||||
return nil, ErrContextCanceled
|
||||
}
|
||||
|
||||
nsIPs, err := r.resolveARecord(ctx, nsHostname)
|
||||
nsIPs, err := r.resolveARecord(ctx, nsHostname, 1)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("resolving NS %s: %w", nsHostname, err)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user