resolver: ask again over TCP when a reply offers recursion (closes #300)
check / check (push) Canceled after 0s
check / check (push) Canceled after 0s
dnswatcher never asks for recursion, so a reply marked recursion available comes from a resolver on the network answering in the server's place, as on a network that sends all DNS to one resolver. Such a resolver answers only from its cache, or with SERVFAIL or REFUSED, which made make check fail there. A reply over UDP marked so is now asked again over TCP, which many such networks leave alone, and the TCP reply is used. A reply from a root server's address still marked so is not used, and when every root server refuses or gives one, the error is the existing "this network intercepts DNS queries". Model: opus-5-5
This commit is contained in:
@@ -94,15 +94,18 @@ func (r *Resolver) tryExchange(
|
||||
}
|
||||
|
||||
// retryTCP returns the reply to msg over TCP when resp, its reply over
|
||||
// UDP, is truncated. When that fails it returns resp, still truncated,
|
||||
// which holds only the records that fit.
|
||||
// UDP, is truncated or marked recursion available. msg does not ask for
|
||||
// recursion, so a reply marked so came from a recursive resolver on the
|
||||
// network answering in the server's place, and many networks that send
|
||||
// DNS to one do so only for UDP. When the retry fails it returns resp;
|
||||
// a truncated one holds only the records that fit.
|
||||
func (r *Resolver) retryTCP(
|
||||
ctx context.Context,
|
||||
msg *dns.Msg,
|
||||
addr string,
|
||||
resp *dns.Msg,
|
||||
) *dns.Msg {
|
||||
if !resp.Truncated {
|
||||
if !resp.Truncated && !resp.RecursionAvailable {
|
||||
return resp
|
||||
}
|
||||
|
||||
@@ -115,7 +118,9 @@ func (r *Resolver) retryTCP(
|
||||
}
|
||||
|
||||
// queryDNS sends a DNS query to a specific server IP, never asking it
|
||||
// for recursion. A reply of REFUSED is returned as ErrRefused.
|
||||
// for recursion. When the reply over UDP is truncated or marked
|
||||
// recursion available, the query is sent again over TCP (see retryTCP).
|
||||
// A reply of REFUSED is returned as ErrRefused.
|
||||
func (r *Resolver) queryDNS(
|
||||
ctx context.Context,
|
||||
serverIP string,
|
||||
@@ -138,13 +143,15 @@ func (r *Resolver) queryDNS(
|
||||
return nil, fmt.Errorf("query %s @%s: %w", name, serverIP, err)
|
||||
}
|
||||
|
||||
resp = r.retryTCP(ctx, msg, addr, resp)
|
||||
|
||||
if resp.Rcode == dns.RcodeRefused {
|
||||
return nil, fmt.Errorf(
|
||||
"query %s @%s: %w", name, serverIP, ErrRefused,
|
||||
)
|
||||
}
|
||||
|
||||
return r.retryTCP(ctx, msg, addr, resp), nil
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func extractNSSet(rrs []dns.RR) []string {
|
||||
@@ -284,10 +291,11 @@ func shuffled(
|
||||
}
|
||||
|
||||
// queryServers asks servers, the servers of zone, about name in a random
|
||||
// order until one gives a usable reply. A server that times out, refuses
|
||||
// or gives a reply that is not usable is passed over for the next. When
|
||||
// every server refused, the error says so, and when they are the root
|
||||
// servers it is ErrIntercepted.
|
||||
// order until one gives a reply it uses (see replyError). A server that
|
||||
// times out, refuses or gives a reply it does not use is passed over for
|
||||
// the next. When every server refused, the error says so. When they are
|
||||
// the root servers and each one refused or gave a reply marked recursion
|
||||
// available, the error is ErrIntercepted.
|
||||
func (r *Resolver) queryServers(
|
||||
ctx context.Context,
|
||||
servers []string,
|
||||
@@ -298,6 +306,7 @@ func (r *Resolver) queryServers(
|
||||
var lastErr error
|
||||
|
||||
refused := 0
|
||||
recursionAvailable := 0
|
||||
|
||||
for _, ip := range shuffled(servers, rand.Shuffle) {
|
||||
if checkCtx(ctx) != nil {
|
||||
@@ -305,10 +314,8 @@ func (r *Resolver) queryServers(
|
||||
}
|
||||
|
||||
resp, err := r.queryDNS(ctx, ip, name, qtype)
|
||||
if err == nil && !usableReply(resp, zone, name) {
|
||||
err = fmt.Errorf(
|
||||
"query %s @%s: %w", name, ip, ErrUnusableReply,
|
||||
)
|
||||
if err == nil {
|
||||
err = replyError(resp, ip, zone, name)
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
@@ -319,6 +326,10 @@ func (r *Resolver) queryServers(
|
||||
refused++
|
||||
}
|
||||
|
||||
if errors.Is(err, ErrRecursionAvailable) {
|
||||
recursionAvailable++
|
||||
}
|
||||
|
||||
lastErr = err
|
||||
}
|
||||
|
||||
@@ -329,6 +340,14 @@ func (r *Resolver) queryServers(
|
||||
)
|
||||
}
|
||||
|
||||
if refused+recursionAvailable == len(servers) && zone == "." {
|
||||
return nil, fmt.Errorf(
|
||||
"every root server refused a query for %s or gave a reply "+
|
||||
"marked recursion available: %w",
|
||||
name, ErrIntercepted,
|
||||
)
|
||||
}
|
||||
|
||||
if refused == len(servers) {
|
||||
return nil, fmt.Errorf(
|
||||
"every server of %s refused a query for %s: %w",
|
||||
@@ -339,6 +358,27 @@ func (r *Resolver) queryServers(
|
||||
return nil, fmt.Errorf("all servers failed: %w", lastErr)
|
||||
}
|
||||
|
||||
// replyError returns why resp, the reply of the server at ip, one of the
|
||||
// servers of zone, to a query about name, is not used, or nil when it
|
||||
// is. A reply that is not usable is not used. Nor is a reply from a root
|
||||
// server marked recursion available: root servers offer no recursion,
|
||||
// so it came from something on the network answering in their place.
|
||||
func replyError(resp *dns.Msg, ip string, zone string, name string) error {
|
||||
if zone == "." && resp.RecursionAvailable {
|
||||
return fmt.Errorf(
|
||||
"query %s @%s: %w", name, ip, ErrRecursionAvailable,
|
||||
)
|
||||
}
|
||||
|
||||
if !usableReply(resp, zone, name) {
|
||||
return fmt.Errorf(
|
||||
"query %s @%s: %w", name, ip, ErrUnusableReply,
|
||||
)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// isErrorReply reports whether msg is an error reply: one with any code
|
||||
// but NOERROR and NXDOMAIN, such as SERVFAIL, NOTIMP or FORMERR. An error
|
||||
// reply says nothing about the name's records.
|
||||
|
||||
Reference in New Issue
Block a user