Skip to content

Commit 11b3ee6

Browse files
committed
net: fix DNS NXDOMAIN performance regression
golang.org/cl/37879 unintentionally changed the way NXDOMAIN errors were handled. Before that change, resolution would fail on the first NXDOMAIN error and return to the user. After that change, the next server would be consulted and resolution would fail only after all servers had been consulted. This change restores the old behavior. Go 10.10.2: BenchmarkGoLookupIP-12 10000 174883 ns/op 11450 B/op 163 allocs/op BenchmarkGoLookupIPNoSuchHost-12 3000 670140 ns/op 52189 B/op 544 allocs/op BenchmarkGoLookupIPWithBrokenNameServer-12 1 5002568137 ns/op 163792 B/op 375 allocs/op before this change: BenchmarkGoLookupIP-12 10000 165501 ns/op 8585 B/op 94 allocs/op BenchmarkGoLookupIPNoSuchHost-12 1000 1204117 ns/op 83661 B/op 674 allocs/op BenchmarkGoLookupIPWithBrokenNameServer-12 1 5002629186 ns/op 159128 B/op 275 allocs/op after this change: BenchmarkGoLookupIP-12 10000 158102 ns/op 8585 B/op 94 allocs/op BenchmarkGoLookupIPNoSuchHost-12 2000 645364 ns/op 42990 B/op 356 allocs/op BenchmarkGoLookupIPWithBrokenNameServer-12 1 5002163437 ns/op 159144 B/op 275 allocs/op Fixes #25336 Change-Id: I315cd70330d1f66e54ce5a189a61c99f095bc138 Reviewed-on: https://go-review.googlesource.com/113815 Reviewed-by: Brad Fitzpatrick <[email protected]> Run-TryBot: Brad Fitzpatrick <[email protected]> TryBot-Result: Gobot Gobot <[email protected]>
1 parent 0979767 commit 11b3ee6

File tree

2 files changed

+42
-27
lines changed

2 files changed

+42
-27
lines changed

src/net/dnsclient_unix.go

+11-10
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,8 @@ func (r *Resolver) exchange(ctx context.Context, server string, q dnsmessage.Que
157157
return dnsmessage.Parser{}, dnsmessage.Header{}, errors.New("no answer from DNS server")
158158
}
159159

160-
func checkHeaders(p *dnsmessage.Parser, h dnsmessage.Header, name, server string) error {
160+
// checkHeader performs basic sanity checks on the header.
161+
func checkHeader(p *dnsmessage.Parser, h dnsmessage.Header, name, server string) error {
161162
_, err := p.AnswerHeader()
162163
if err != nil && err != dnsmessage.ErrSectionDone {
163164
return &DNSError{
@@ -173,14 +174,7 @@ func checkHeaders(p *dnsmessage.Parser, h dnsmessage.Header, name, server string
173174
return &DNSError{Err: "lame referral", Name: name, Server: server}
174175
}
175176

176-
// If answer errored for rcodes dnsRcodeSuccess or dnsRcodeNameError,
177-
// it means the response in msg was not useful and trying another
178-
// server probably won't help. Return now in those cases.
179-
// TODO: indicate this in a more obvious way, such as a field on DNSError?
180-
if h.RCode == dnsmessage.RCodeNameError {
181-
return &DNSError{Err: errNoSuchHost.Error(), Name: name, Server: server}
182-
}
183-
if h.RCode != dnsmessage.RCodeSuccess {
177+
if h.RCode != dnsmessage.RCodeSuccess && h.RCode != dnsmessage.RCodeNameError {
184178
// None of the error codes make sense
185179
// for the query we sent. If we didn't get
186180
// a name error and we didn't get success,
@@ -265,7 +259,14 @@ func (r *Resolver) tryOneName(ctx context.Context, cfg *dnsConfig, name string,
265259
continue
266260
}
267261

268-
lastErr = checkHeaders(&p, h, name, server)
262+
// The name does not exist, so trying another server won't help.
263+
//
264+
// TODO: indicate this in a more obvious way, such as a field on DNSError?
265+
if h.RCode == dnsmessage.RCodeNameError {
266+
return dnsmessage.Parser{}, "", &DNSError{Err: errNoSuchHost.Error(), Name: name, Server: server}
267+
}
268+
269+
lastErr = checkHeader(&p, h, name, server)
269270
if lastErr != nil {
270271
continue
271272
}

src/net/dnsclient_unix_test.go

+31-17
Original file line numberDiff line numberDiff line change
@@ -746,6 +746,7 @@ func TestIgnoreLameReferrals(t *testing.T) {
746746
func BenchmarkGoLookupIP(b *testing.B) {
747747
testHookUninstaller.Do(uninstallTestHooks)
748748
ctx := context.Background()
749+
b.ReportAllocs()
749750

750751
for i := 0; i < b.N; i++ {
751752
goResolver.LookupIPAddr(ctx, "www.example.com")
@@ -755,6 +756,7 @@ func BenchmarkGoLookupIP(b *testing.B) {
755756
func BenchmarkGoLookupIPNoSuchHost(b *testing.B) {
756757
testHookUninstaller.Do(uninstallTestHooks)
757758
ctx := context.Background()
759+
b.ReportAllocs()
758760

759761
for i := 0; i < b.N; i++ {
760762
goResolver.LookupIPAddr(ctx, "some.nonexistent")
@@ -778,6 +780,7 @@ func BenchmarkGoLookupIPWithBrokenNameServer(b *testing.B) {
778780
b.Fatal(err)
779781
}
780782
ctx := context.Background()
783+
b.ReportAllocs()
781784

782785
for i := 0; i < b.N; i++ {
783786
goResolver.LookupIPAddr(ctx, "www.example.com")
@@ -1437,7 +1440,7 @@ func TestIssue8434(t *testing.T) {
14371440
t.Fatal("SkipAllQuestions failed:", err)
14381441
}
14391442

1440-
err = checkHeaders(&p, h, "golang.org", "foo:53")
1443+
err = checkHeader(&p, h, "golang.org", "foo:53")
14411444
if err == nil {
14421445
t.Fatal("expected an error")
14431446
}
@@ -1455,28 +1458,39 @@ func TestIssue8434(t *testing.T) {
14551458

14561459
// Issue 12778: verify that NXDOMAIN without RA bit errors as
14571460
// "no such host" and not "server misbehaving"
1461+
//
1462+
// Issue 25336: verify that NXDOMAIN errors fail fast.
14581463
func TestIssue12778(t *testing.T) {
1459-
msg := dnsmessage.Message{
1460-
Header: dnsmessage.Header{
1461-
RCode: dnsmessage.RCodeNameError,
1462-
RecursionAvailable: false,
1464+
lookups := 0
1465+
fake := fakeDNSServer{
1466+
rh: func(n, _ string, q dnsmessage.Message, _ time.Time) (dnsmessage.Message, error) {
1467+
lookups++
1468+
return dnsmessage.Message{
1469+
Header: dnsmessage.Header{
1470+
ID: q.ID,
1471+
Response: true,
1472+
RCode: dnsmessage.RCodeNameError,
1473+
RecursionAvailable: false,
1474+
},
1475+
Questions: q.Questions,
1476+
}, nil
14631477
},
14641478
}
1479+
r := Resolver{PreferGo: true, Dial: fake.DialContext}
14651480

1466-
b, err := msg.Pack()
1467-
if err != nil {
1468-
t.Fatal("Pack failed:", err)
1469-
}
1470-
var p dnsmessage.Parser
1471-
h, err := p.Start(b)
1472-
if err != nil {
1473-
t.Fatal("Start failed:", err)
1474-
}
1475-
if err := p.SkipAllQuestions(); err != nil {
1476-
t.Fatal("SkipAllQuestions failed:", err)
1481+
resolvConf.mu.RLock()
1482+
conf := resolvConf.dnsConfig
1483+
resolvConf.mu.RUnlock()
1484+
1485+
ctx, cancel := context.WithCancel(context.Background())
1486+
defer cancel()
1487+
1488+
_, _, err := r.tryOneName(ctx, conf, ".", dnsmessage.TypeALL)
1489+
1490+
if lookups != 1 {
1491+
t.Errorf("got %d lookups, wanted 1", lookups)
14771492
}
14781493

1479-
err = checkHeaders(&p, h, "golang.org", "foo:53")
14801494
if err == nil {
14811495
t.Fatal("expected an error")
14821496
}

0 commit comments

Comments
 (0)