diff --git a/src/net/dnsclient_unix.go b/src/net/dnsclient_unix.go index 86ce92dc437844..86ab14ddf2c363 100644 --- a/src/net/dnsclient_unix.go +++ b/src/net/dnsclient_unix.go @@ -576,9 +576,18 @@ func (r *Resolver) goLookupIPCNAMEOrder(ctx context.Context, name string, order } lane := make(chan racer, 1) qtypes := [...]dnsmessage.Type{dnsmessage.TypeA, dnsmessage.TypeAAAA} - var lastErr error - for _, fqdn := range conf.nameList(name) { - for _, qtype := range qtypes { + var queryFn func(fqdn string, qtype dnsmessage.Type) + var responseFn func(fqdn string, qtype dnsmessage.Type) racer + if conf.singleRequest { + queryFn = func(fqdn string, qtype dnsmessage.Type) {} + responseFn = func(fqdn string, qtype dnsmessage.Type) racer { + dnsWaitGroup.Add(1) + defer dnsWaitGroup.Done() + p, server, err := r.tryOneName(ctx, conf, fqdn, qtype) + return racer{p, server, err} + } + } else { + queryFn = func(fqdn string, qtype dnsmessage.Type) { dnsWaitGroup.Add(1) go func(qtype dnsmessage.Type) { p, server, err := r.tryOneName(ctx, conf, fqdn, qtype) @@ -586,9 +595,18 @@ func (r *Resolver) goLookupIPCNAMEOrder(ctx context.Context, name string, order dnsWaitGroup.Done() }(qtype) } + responseFn = func(fqdn string, qtype dnsmessage.Type) racer { + return <-lane + } + } + var lastErr error + for _, fqdn := range conf.nameList(name) { + for _, qtype := range qtypes { + queryFn(fqdn, qtype) + } hitStrictError := false - for range qtypes { - racer := <-lane + for _, qtype := range qtypes { + racer := responseFn(fqdn, qtype) if racer.error != nil { if nerr, ok := racer.error.(Error); ok && nerr.Temporary() && r.strictErrors() { // This error will abort the nameList loop. diff --git a/src/net/dnsclient_unix_test.go b/src/net/dnsclient_unix_test.go index be04a44c14beaf..45b2fe004fc8b9 100644 --- a/src/net/dnsclient_unix_test.go +++ b/src/net/dnsclient_unix_test.go @@ -17,6 +17,7 @@ import ( "reflect" "strings" "sync" + "sync/atomic" "testing" "time" @@ -1621,3 +1622,76 @@ func TestTXTRecordTwoStrings(t *testing.T) { t.Errorf("txt[1], got %q, want %q", txt[1], want) } } + +// Issue 29644: support single-request resolv.conf option in pure Go resolver. +// The A and AAAA queries will be sent sequentially, not in parallel. +func TestSingleRequestLookup(t *testing.T) { + defer dnsWaitGroup.Wait() + var ( + firstcalled int32 + ipv4 int32 = 1 + ipv6 int32 = 2 + ) + fake := fakeDNSServer{rh: func(n, s string, q dnsmessage.Message, _ time.Time) (dnsmessage.Message, error) { + r := dnsmessage.Message{ + Header: dnsmessage.Header{ + ID: q.ID, + Response: true, + }, + Questions: q.Questions, + } + for _, question := range q.Questions { + switch question.Type { + case dnsmessage.TypeA: + if question.Name.String() == "slowipv4.example.net." { + time.Sleep(10 * time.Millisecond) + } + if !atomic.CompareAndSwapInt32(&firstcalled, 0, ipv4) { + t.Errorf("the A query was received after the AAAA query !") + } + r.Answers = append(r.Answers, dnsmessage.Resource{ + Header: dnsmessage.ResourceHeader{ + Name: q.Questions[0].Name, + Type: dnsmessage.TypeA, + Class: dnsmessage.ClassINET, + Length: 4, + }, + Body: &dnsmessage.AResource{ + A: TestAddr, + }, + }) + case dnsmessage.TypeAAAA: + atomic.CompareAndSwapInt32(&firstcalled, 0, ipv6) + r.Answers = append(r.Answers, dnsmessage.Resource{ + Header: dnsmessage.ResourceHeader{ + Name: q.Questions[0].Name, + Type: dnsmessage.TypeAAAA, + Class: dnsmessage.ClassINET, + Length: 16, + }, + Body: &dnsmessage.AAAAResource{ + AAAA: TestAddr6, + }, + }) + } + } + return r, nil + }} + r := Resolver{PreferGo: true, Dial: fake.DialContext} + + conf, err := newResolvConfTest() + if err != nil { + t.Fatal(err) + } + defer conf.teardown() + if err := conf.writeAndUpdate([]string{"options single-request"}); err != nil { + t.Fatal(err) + } + for _, name := range []string{"hostname.example.net", "slowipv4.example.net"} { + firstcalled = 0 + _, err := r.LookupIPAddr(context.Background(), name) + if err != nil { + t.Error(err) + } + } +} diff --git a/src/net/dnsconfig_unix.go b/src/net/dnsconfig_unix.go index 842d408e5625bd..3ca8d71f5f9f2e 100644 --- a/src/net/dnsconfig_unix.go +++ b/src/net/dnsconfig_unix.go @@ -21,17 +21,18 @@ var ( ) type dnsConfig struct { - servers []string // server addresses (in host:port form) to use - search []string // rooted suffixes to append to local name - ndots int // number of dots in name to trigger absolute lookup - timeout time.Duration // wait before giving up on a query, including retries - attempts int // lost packets before giving up on server - rotate bool // round robin among servers - unknownOpt bool // anything unknown was encountered - lookup []string // OpenBSD top-level database "lookup" order - err error // any error that occurs during open of resolv.conf - mtime time.Time // time of resolv.conf modification - soffset uint32 // used by serverOffset + servers []string // server addresses (in host:port form) to use + search []string // rooted suffixes to append to local name + ndots int // number of dots in name to trigger absolute lookup + timeout time.Duration // wait before giving up on a query, including retries + attempts int // lost packets before giving up on server + rotate bool // round robin among servers + unknownOpt bool // anything unknown was encountered + lookup []string // OpenBSD top-level database "lookup" order + err error // any error that occurs during open of resolv.conf + mtime time.Time // time of resolv.conf modification + soffset uint32 // used by serverOffset + singleRequest bool // use sequential A and AAAA queries instead of parallel queries } // See resolv.conf(5) on a Linux machine. @@ -115,6 +116,13 @@ func dnsReadConfig(filename string) *dnsConfig { conf.attempts = n case s == "rotate": conf.rotate = true + case s == "single-request" || s == "single-request-reopen": + // Linux option: + // http://man7.org/linux/man-pages/man5/resolv.conf.5.html + // "By default, glibc performs IPv4 and IPv6 lookups in parallel [...] + // This option disables the behavior and makes glibc + // perform the IPv6 and IPv4 requests sequentially." + conf.singleRequest = true default: conf.unknownOpt = true } diff --git a/src/net/dnsconfig_unix_test.go b/src/net/dnsconfig_unix_test.go index 0797559d1a208a..f16f90ad505309 100644 --- a/src/net/dnsconfig_unix_test.go +++ b/src/net/dnsconfig_unix_test.go @@ -102,6 +102,28 @@ var dnsReadConfigTests = []struct { search: []string{"c.symbolic-datum-552.internal."}, }, }, + { + name: "testdata/single-request-resolv.conf", + want: &dnsConfig{ + servers: defaultNS, + ndots: 1, + singleRequest: true, + timeout: 5 * time.Second, + attempts: 2, + search: []string{"domain.local."}, + }, + }, + { + name: "testdata/single-request-reopen-resolv.conf", + want: &dnsConfig{ + servers: defaultNS, + ndots: 1, + singleRequest: true, + timeout: 5 * time.Second, + attempts: 2, + search: []string{"domain.local."}, + }, + }, } func TestDNSReadConfig(t *testing.T) { diff --git a/src/net/testdata/single-request-reopen-resolv.conf b/src/net/testdata/single-request-reopen-resolv.conf new file mode 100644 index 00000000000000..9bddeb3844436b --- /dev/null +++ b/src/net/testdata/single-request-reopen-resolv.conf @@ -0,0 +1 @@ +options single-request-reopen \ No newline at end of file diff --git a/src/net/testdata/single-request-resolv.conf b/src/net/testdata/single-request-resolv.conf new file mode 100644 index 00000000000000..5595d29a66abc1 --- /dev/null +++ b/src/net/testdata/single-request-resolv.conf @@ -0,0 +1 @@ +options single-request \ No newline at end of file