Skip to content

Commit 5c0629b

Browse files
danprsc
authored andcommitted
net: prefer error for original name on lookups
With certain names and search domain configurations the returned error would be one encountered while querying a generated name instead of the original name. This caused confusion when a manual check of the same name produced different results. Now prefer errors encountered for the original name. Also makes the low-level DNS connection plumbing swappable in tests enabling tighter control over responses without relying on the network. Fixes #12712 Updates #13295 Change-Id: I780d628a762006bb11899caf20b5f97b462a717f Reviewed-on: https://go-review.googlesource.com/16953 Reviewed-by: Russ Cox <[email protected]>
1 parent be7544b commit 5c0629b

File tree

3 files changed

+98
-5
lines changed

3 files changed

+98
-5
lines changed

src/net/dnsclient_unix.go

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,16 @@ import (
2424
"time"
2525
)
2626

27+
// A dnsDialer provides dialing suitable for DNS queries.
28+
type dnsDialer interface {
29+
dialDNS(string, string) (dnsConn, error)
30+
}
31+
2732
// A dnsConn represents a DNS transport endpoint.
2833
type dnsConn interface {
29-
Conn
34+
io.Closer
35+
36+
SetDeadline(time.Time) error
3037

3138
// readDNSResponse reads a DNS response message from the DNS
3239
// transport endpoint and returns the received DNS response
@@ -121,7 +128,7 @@ func (d *Dialer) dialDNS(network, server string) (dnsConn, error) {
121128

122129
// exchange sends a query on the connection and hopes for a response.
123130
func exchange(server, name string, qtype uint16, timeout time.Duration) (*dnsMsg, error) {
124-
d := Dialer{Timeout: timeout}
131+
d := testHookDNSDialer(timeout)
125132
out := dnsMsg{
126133
dnsMsgHdr: dnsMsgHdr{
127134
recursion_desired: true,
@@ -440,7 +447,8 @@ func goLookupIPOrder(name string, order hostLookupOrder) (addrs []IPAddr, err er
440447
conf := resolvConf.dnsConfig
441448
resolvConf.mu.RUnlock()
442449
type racer struct {
443-
rrs []dnsRR
450+
fqdn string
451+
rrs []dnsRR
444452
error
445453
}
446454
lane := make(chan racer, 1)
@@ -450,13 +458,16 @@ func goLookupIPOrder(name string, order hostLookupOrder) (addrs []IPAddr, err er
450458
for _, qtype := range qtypes {
451459
go func(qtype uint16) {
452460
_, rrs, err := tryOneName(conf, fqdn, qtype)
453-
lane <- racer{rrs, err}
461+
lane <- racer{fqdn, rrs, err}
454462
}(qtype)
455463
}
456464
for range qtypes {
457465
racer := <-lane
458466
if racer.error != nil {
459-
lastErr = racer.error
467+
// Prefer error for original name.
468+
if lastErr == nil || racer.fqdn == name+"." {
469+
lastErr = racer.error
470+
}
460471
continue
461472
}
462473
addrs = append(addrs, addrRecordList(racer.rrs)...)

src/net/dnsclient_unix_test.go

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -424,6 +424,57 @@ func TestGoLookupIPOrderFallbackToFile(t *testing.T) {
424424
defer conf.teardown()
425425
}
426426

427+
// Issue 12712.
428+
// When using search domains, return the error encountered
429+
// querying the original name instead of an error encountered
430+
// querying a generated name.
431+
func TestErrorForOriginalNameWhenSearching(t *testing.T) {
432+
const fqdn = "doesnotexist.domain"
433+
434+
origTestHookDNSDialer := testHookDNSDialer
435+
defer func() { testHookDNSDialer = origTestHookDNSDialer }()
436+
437+
conf, err := newResolvConfTest()
438+
if err != nil {
439+
t.Fatal(err)
440+
}
441+
defer conf.teardown()
442+
443+
if err := conf.writeAndUpdate([]string{"search servfail"}); err != nil {
444+
t.Fatal(err)
445+
}
446+
447+
d := &fakeDNSConn{}
448+
testHookDNSDialer = func(time.Duration) dnsDialer { return d }
449+
450+
d.rh = func(q *dnsMsg) (*dnsMsg, error) {
451+
r := &dnsMsg{
452+
dnsMsgHdr: dnsMsgHdr{
453+
id: q.id,
454+
},
455+
}
456+
457+
switch q.question[0].Name {
458+
case fqdn + ".servfail.":
459+
r.rcode = dnsRcodeServerFailure
460+
default:
461+
r.rcode = dnsRcodeNameError
462+
}
463+
464+
return r, nil
465+
}
466+
467+
_, err = goLookupIP(fqdn)
468+
if err == nil {
469+
t.Fatal("expected an error")
470+
}
471+
472+
want := &DNSError{Name: fqdn, Err: errNoSuchHost.Error()}
473+
if err, ok := err.(*DNSError); !ok || err.Name != want.Name || err.Err != want.Err {
474+
t.Errorf("got %v; want %v", err, want)
475+
}
476+
}
477+
427478
func BenchmarkGoLookupIP(b *testing.B) {
428479
testHookUninstaller.Do(uninstallTestHooks)
429480

@@ -461,3 +512,31 @@ func BenchmarkGoLookupIPWithBrokenNameServer(b *testing.B) {
461512
goLookupIP("www.example.com")
462513
}
463514
}
515+
516+
type fakeDNSConn struct {
517+
// last query
518+
q *dnsMsg
519+
// reply handler
520+
rh func(*dnsMsg) (*dnsMsg, error)
521+
}
522+
523+
func (f *fakeDNSConn) dialDNS(n, s string) (dnsConn, error) {
524+
return f, nil
525+
}
526+
527+
func (f *fakeDNSConn) Close() error {
528+
return nil
529+
}
530+
531+
func (f *fakeDNSConn) SetDeadline(time.Time) error {
532+
return nil
533+
}
534+
535+
func (f *fakeDNSConn) writeDNSQuery(q *dnsMsg) error {
536+
f.q = q
537+
return nil
538+
}
539+
540+
func (f *fakeDNSConn) readDNSResponse() (*dnsMsg, error) {
541+
return f.rh(f.q)
542+
}

src/net/hook.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,11 @@
44

55
package net
66

7+
import "time"
8+
79
var (
810
testHookDialTCP = dialTCP
11+
testHookDNSDialer = func(d time.Duration) dnsDialer { return &Dialer{Timeout: d} }
912
testHookHostsPath = "/etc/hosts"
1013
testHookLookupIP = func(fn func(string) ([]IPAddr, error), host string) ([]IPAddr, error) { return fn(host) }
1114
testHookSetKeepAlive = func() {}

0 commit comments

Comments
 (0)