From 865760373194e358fefa4d1e45ebdf2141b77b59 Mon Sep 17 00:00:00 2001 From: Mateusz Poliwczak Date: Tue, 1 Aug 2023 18:02:54 +0000 Subject: [PATCH] net: use the extended RCode from EDNS(0) OPT resources For a while now we support EDNS, but the current implementation only sends the OPT resource and doesn't do anything with the response OPT resource. For reference the miekg/dns updates the RCode in the header when there is a OPT resource: https://github.com/miekg/dns/blob/48f38ebef989eedc6b57f1869ae849ccc8f5fe29/msg.go#L868-L872 Change-Id: I0a7146aed3e50654f340a3925f48612561cb85f4 GitHub-Last-Rev: adc304167e0540cb1f066f07a249d67fad89182e GitHub-Pull-Request: golang/go#61695 Reviewed-on: https://go-review.googlesource.com/c/go/+/514835 TryBot-Result: Gopher Robot Reviewed-by: David Chase Run-TryBot: Ian Lance Taylor Reviewed-by: Ian Lance Taylor Auto-Submit: Ian Lance Taylor --- src/net/dnsclient_unix.go | 27 +++++++++++++++++++++++---- src/net/dnsclient_unix_test.go | 31 +++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 4 deletions(-) diff --git a/src/net/dnsclient_unix.go b/src/net/dnsclient_unix.go index dab5144e5d7..ed32ba02806 100644 --- a/src/net/dnsclient_unix.go +++ b/src/net/dnsclient_unix.go @@ -205,7 +205,9 @@ func (r *Resolver) exchange(ctx context.Context, server string, q dnsmessage.Que // checkHeader performs basic sanity checks on the header. func checkHeader(p *dnsmessage.Parser, h dnsmessage.Header) error { - if h.RCode == dnsmessage.RCodeNameError { + rcode := extractExtendedRCode(*p, h) + + if rcode == dnsmessage.RCodeNameError { return errNoSuchHost } @@ -216,17 +218,17 @@ func checkHeader(p *dnsmessage.Parser, h dnsmessage.Header) error { // libresolv continues to the next server when it receives // an invalid referral response. See golang.org/issue/15434. - if h.RCode == dnsmessage.RCodeSuccess && !h.Authoritative && !h.RecursionAvailable && err == dnsmessage.ErrSectionDone { + if rcode == dnsmessage.RCodeSuccess && !h.Authoritative && !h.RecursionAvailable && err == dnsmessage.ErrSectionDone { return errLameReferral } - if h.RCode != dnsmessage.RCodeSuccess && h.RCode != dnsmessage.RCodeNameError { + if rcode != dnsmessage.RCodeSuccess && rcode != dnsmessage.RCodeNameError { // None of the error codes make sense // for the query we sent. If we didn't get // a name error and we didn't get success, // the server is behaving incorrectly or // having temporary trouble. - if h.RCode == dnsmessage.RCodeServerFailure { + if rcode == dnsmessage.RCodeServerFailure { return errServerTemporarilyMisbehaving } return errServerMisbehaving @@ -253,6 +255,23 @@ func skipToAnswer(p *dnsmessage.Parser, qtype dnsmessage.Type) error { } } +// extractExtendedRCode extracts the extended RCode from the OPT resource (EDNS(0)) +// If an OPT record is not found, the RCode from the hdr is returned. +func extractExtendedRCode(p dnsmessage.Parser, hdr dnsmessage.Header) dnsmessage.RCode { + p.SkipAllAnswers() + p.SkipAllAuthorities() + for { + ahdr, err := p.AdditionalHeader() + if err != nil { + return hdr.RCode + } + if ahdr.Type == dnsmessage.TypeOPT { + return ahdr.ExtendedRCode(hdr.RCode) + } + p.SkipAdditional() + } +} + // Do a lookup for a single name, which must be rooted // (otherwise answer will not find the answers). func (r *Resolver) tryOneName(ctx context.Context, cfg *dnsConfig, name string, qtype dnsmessage.Type) (dnsmessage.Parser, string, error) { diff --git a/src/net/dnsclient_unix_test.go b/src/net/dnsclient_unix_test.go index 8d435a557f2..9ae68f9a992 100644 --- a/src/net/dnsclient_unix_test.go +++ b/src/net/dnsclient_unix_test.go @@ -2598,3 +2598,34 @@ func TestLookupOrderFilesNoSuchHost(t *testing.T) { } } } + +func TestExtendedRCode(t *testing.T) { + fake := fakeDNSServer{ + rh: func(_, _ string, q dnsmessage.Message, _ time.Time) (dnsmessage.Message, error) { + fraudSuccessCode := dnsmessage.RCodeSuccess | 1<<10 + + var edns0Hdr dnsmessage.ResourceHeader + edns0Hdr.SetEDNS0(maxDNSPacketSize, fraudSuccessCode, false) + + return dnsmessage.Message{ + Header: dnsmessage.Header{ + ID: q.Header.ID, + Response: true, + RCode: fraudSuccessCode, + }, + Questions: []dnsmessage.Question{q.Questions[0]}, + Additionals: []dnsmessage.Resource{{ + Header: edns0Hdr, + Body: &dnsmessage.OPTResource{}, + }}, + }, nil + }, + } + + r := &Resolver{PreferGo: true, Dial: fake.DialContext} + _, _, err := r.tryOneName(context.Background(), getSystemDNSConfig(), "go.dev.", dnsmessage.TypeA) + var dnsErr *DNSError + if !(errors.As(err, &dnsErr) && dnsErr.Err == errServerMisbehaving.Error()) { + t.Fatalf("r.tryOneName(): unexpected error: %v", err) + } +}