net/dns/resolver: respond with SERVFAIL if all upstreams fail

Fixes #4722

Signed-off-by: Tom DNetto <tom@tailscale.com>
This commit is contained in:
Tom DNetto
2022-06-15 16:19:05 -07:00
committed by Tom
parent c93fd0d22b
commit d6817d0f22
3 changed files with 89 additions and 2 deletions

View File

@@ -518,6 +518,8 @@ func (f *forwarder) send(ctx context.Context, fq *forwardQuery, rr resolverAndDe
return f.sendUDP(ctx, fq, rr)
}
var errServerFailure = errors.New("response code indicates server issue")
func (f *forwarder) sendUDP(ctx context.Context, fq *forwardQuery, rr resolverAndDelay) (ret []byte, err error) {
ipp, ok := rr.name.IPPort()
if !ok {
@@ -581,7 +583,7 @@ func (f *forwarder) sendUDP(ctx context.Context, fq *forwardQuery, rr resolverAn
if rcode == dns.RCodeServerFailure {
f.logf("recv: response code indicating server failure: %d", rcode)
metricDNSFwdUDPErrorServer.Add(1)
return nil, errors.New("response code indicates server issue")
return nil, errServerFailure
}
if truncated {
@@ -751,6 +753,20 @@ func (f *forwarder) forwardWithDestChan(ctx context.Context, query packet, respo
}
numErr++
if numErr == len(resolvers) {
if firstErr == errServerFailure {
res, err := servfailResponse(query)
if err != nil {
f.logf("building servfail response: %v", err)
return firstErr
}
select {
case <-ctx.Done():
metricDNSFwdErrorContext.Add(1)
metricDNSFwdErrorContextGotError.Add(1)
case responseChan <- res:
}
}
return firstErr
}
case <-ctx.Done():
@@ -809,6 +825,27 @@ func nxDomainResponse(req packet) (res packet, err error) {
return res, err
}
// servfailResponse returns a SERVFAIL error reply for the provided request.
func servfailResponse(req packet) (res packet, err error) {
p := dnsParserPool.Get().(*dnsParser)
defer dnsParserPool.Put(p)
if err := p.parseQuery(req.bs); err != nil {
return packet{}, err
}
h := p.Header
h.Response = true
h.Authoritative = true
h.RCode = dns.RCodeServerFailure
b := dns.NewBuilder(nil, h)
b.StartQuestions()
b.Question(p.Question)
res.bs, err = b.Finish()
res.addr = req.addr
return res, err
}
// closePool is a dynamic set of io.Closers to close as a group.
// It's intended to be Closed at most once.
//