]> Cypherpunks repositories - gostls13.git/commitdiff
net: support single-request resolv.conf option in pure Go resolver
authorjfbus <jf@bustarret.com>
Tue, 26 Mar 2019 18:21:53 +0000 (18:21 +0000)
committerBrad Fitzpatrick <bradfitz@golang.org>
Wed, 17 Apr 2019 14:42:35 +0000 (14:42 +0000)
There is a DNS resolution issue in Kubernetes (UDP response packets get dropped due to a race in conntrack between the parallel A and AAAA queries, causing timeouts in DNS queries).

A workaround is to enable single-request / single-request-reopen in resolv.conf in order to use sequential A and AAAA queries instead of parallel queries.

With this PR, the pure Go resolver searches for "single-request" and "single-request-reopen" in resolv.conf and send A and AAAA queries sequentially when found.

Fixes #29644

Change-Id: I906b3484008c1b9adf2e3e9241ea23767e29df59
GitHub-Last-Rev: d481acfb4c49d82fd474078b31a1a4697b57dadf
GitHub-Pull-Request: golang/go#29661
Reviewed-on: https://go-review.googlesource.com/c/go/+/157377
Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
src/net/dnsclient_unix.go
src/net/dnsclient_unix_test.go
src/net/dnsconfig_unix.go
src/net/dnsconfig_unix_test.go
src/net/testdata/single-request-reopen-resolv.conf [new file with mode: 0644]
src/net/testdata/single-request-resolv.conf [new file with mode: 0644]

index 5472494356688a38717d523f8c47380d78821a45..4e7462b66fac48586eec1e6b7e5a081c21915881 100644 (file)
@@ -569,34 +569,52 @@ func (r *Resolver) goLookupIPCNAMEOrder(ctx context.Context, name string, order
        resolvConf.mu.RLock()
        conf := resolvConf.dnsConfig
        resolvConf.mu.RUnlock()
-       type racer struct {
+       type result struct {
                p      dnsmessage.Parser
                server string
                error
        }
-       lane := make(chan racer, 1)
+       lane := make(chan result, 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) result
+       if conf.singleRequest {
+               queryFn = func(fqdn string, qtype dnsmessage.Type) {}
+               responseFn = func(fqdn string, qtype dnsmessage.Type) result {
+                       dnsWaitGroup.Add(1)
+                       defer dnsWaitGroup.Done()
+                       p, server, err := r.tryOneName(ctx, conf, fqdn, qtype)
+                       return result{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)
-                               lane <- racer{p, server, err}
+                               lane <- result{p, server, err}
                                dnsWaitGroup.Done()
                        }(qtype)
                }
+               responseFn = func(fqdn string, qtype dnsmessage.Type) result {
+                       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
-                       if racer.error != nil {
-                               if nerr, ok := racer.error.(Error); ok && nerr.Temporary() && r.strictErrors() {
+               for _, qtype := range qtypes {
+                       result := responseFn(fqdn, qtype)
+                       if result.error != nil {
+                               if nerr, ok := result.error.(Error); ok && nerr.Temporary() && r.strictErrors() {
                                        // This error will abort the nameList loop.
                                        hitStrictError = true
-                                       lastErr = racer.error
+                                       lastErr = result.error
                                } else if lastErr == nil || fqdn == name+"." {
                                        // Prefer error for original name.
-                                       lastErr = racer.error
+                                       lastErr = result.error
                                }
                                continue
                        }
@@ -618,12 +636,12 @@ func (r *Resolver) goLookupIPCNAMEOrder(ctx context.Context, name string, order
 
                loop:
                        for {
-                               h, err := racer.p.AnswerHeader()
+                               h, err := result.p.AnswerHeader()
                                if err != nil && err != dnsmessage.ErrSectionDone {
                                        lastErr = &DNSError{
                                                Err:    "cannot marshal DNS message",
                                                Name:   name,
-                                               Server: racer.server,
+                                               Server: result.server,
                                        }
                                }
                                if err != nil {
@@ -631,35 +649,35 @@ func (r *Resolver) goLookupIPCNAMEOrder(ctx context.Context, name string, order
                                }
                                switch h.Type {
                                case dnsmessage.TypeA:
-                                       a, err := racer.p.AResource()
+                                       a, err := result.p.AResource()
                                        if err != nil {
                                                lastErr = &DNSError{
                                                        Err:    "cannot marshal DNS message",
                                                        Name:   name,
-                                                       Server: racer.server,
+                                                       Server: result.server,
                                                }
                                                break loop
                                        }
                                        addrs = append(addrs, IPAddr{IP: IP(a.A[:])})
 
                                case dnsmessage.TypeAAAA:
-                                       aaaa, err := racer.p.AAAAResource()
+                                       aaaa, err := result.p.AAAAResource()
                                        if err != nil {
                                                lastErr = &DNSError{
                                                        Err:    "cannot marshal DNS message",
                                                        Name:   name,
-                                                       Server: racer.server,
+                                                       Server: result.server,
                                                }
                                                break loop
                                        }
                                        addrs = append(addrs, IPAddr{IP: IP(aaaa.AAAA[:])})
 
                                default:
-                                       if err := racer.p.SkipAnswer(); err != nil {
+                                       if err := result.p.SkipAnswer(); err != nil {
                                                lastErr = &DNSError{
                                                        Err:    "cannot marshal DNS message",
                                                        Name:   name,
-                                                       Server: racer.server,
+                                                       Server: result.server,
                                                }
                                                break loop
                                        }
index 810f400f0bd770c2931225b4e5ac00f2101357e3..51d54a4ccadf22d499131e737350e65995fd65cb 100644 (file)
@@ -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)
+               }
+       }
+}
index 842d408e5625bdc318dc5f81c61f5214f36264dc..3ca8d71f5f9f2e058023bdb66bc81773784a050e 100644 (file)
@@ -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
                                }
index 0797559d1a208a604581051300556a9fc09762d6..f16f90ad505309856ddb9dcd72c30dd05cdcdf0d 100644 (file)
@@ -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 (file)
index 0000000..9bddeb3
--- /dev/null
@@ -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 (file)
index 0000000..5595d29
--- /dev/null
@@ -0,0 +1 @@
+options single-request
\ No newline at end of file