]> Cypherpunks repositories - gostls13.git/commitdiff
net: implement DNS TCP fallback query if UDP response is truncated
authorAlex A Skinner <alex@lx.lc>
Tue, 13 Aug 2013 16:44:12 +0000 (09:44 -0700)
committerBrad Fitzpatrick <bradfitz@golang.org>
Tue, 13 Aug 2013 16:44:12 +0000 (09:44 -0700)
Fixes #5686.

R=golang-dev, bradfitz, mikioh.mikioh
CC=golang-dev
https://golang.org/cl/12458043

src/pkg/net/dnsclient_unix.go
src/pkg/net/dnsclient_unix_test.go [new file with mode: 0644]

index c9a16a94d84eab1237a3ffd6c1da026382b2501d..38fbf322cf2622a1d7a45e990c2986e8220c5511 100644 (file)
@@ -17,6 +17,7 @@
 package net
 
 import (
+       "io"
        "math/rand"
        "sync"
        "time"
@@ -25,6 +26,13 @@ import (
 // Send a request on the connection and hope for a reply.
 // Up to cfg.attempts attempts.
 func exchange(cfg *dnsConfig, c Conn, name string, qtype uint16) (*dnsMsg, error) {
+       var useTCP bool
+       switch c.(type) {
+       case *UDPConn:
+               useTCP = false
+       case *TCPConn:
+               useTCP = true
+       }
        if len(name) >= 256 {
                return nil, &DNSError{Err: "name too long", Name: name}
        }
@@ -38,7 +46,10 @@ func exchange(cfg *dnsConfig, c Conn, name string, qtype uint16) (*dnsMsg, error
        if !ok {
                return nil, &DNSError{Err: "internal error - cannot pack message", Name: name}
        }
-
+       if useTCP {
+               mlen := uint16(len(msg))
+               msg = append([]byte{byte(mlen >> 8), byte(mlen)}, msg...)
+       }
        for attempt := 0; attempt < cfg.attempts; attempt++ {
                n, err := c.Write(msg)
                if err != nil {
@@ -50,9 +61,19 @@ func exchange(cfg *dnsConfig, c Conn, name string, qtype uint16) (*dnsMsg, error
                } else {
                        c.SetReadDeadline(time.Now().Add(time.Duration(cfg.timeout) * time.Second))
                }
-
-               buf := make([]byte, 2000) // More than enough.
-               n, err = c.Read(buf)
+               buf := make([]byte, 2000)
+               if useTCP {
+                       n, err = io.ReadFull(c, buf[:2])
+                       if err != nil {
+                               if e, ok := err.(Error); ok && e.Timeout() {
+                                       continue
+                               }
+                       }
+                       buf = make([]byte, uint16(buf[0])<<8+uint16(buf[1]))
+                       n, err = io.ReadFull(c, buf)
+               } else {
+                       n, err = c.Read(buf)
+               }
                if err != nil {
                        if e, ok := err.(Error); ok && e.Timeout() {
                                continue
@@ -98,6 +119,19 @@ func tryOneName(cfg *dnsConfig, name string, qtype uint16) (cname string, addrs
                        err = merr
                        continue
                }
+               if msg.truncated { // see RFC 5966
+                       c, cerr = Dial("tcp", server)
+                       if cerr != nil {
+                               err = cerr
+                               continue
+                       }
+                       msg, merr = exchange(cfg, c, name, qtype)
+                       c.Close()
+                       if merr != nil {
+                               err = merr
+                               continue
+                       }
+               }
                cname, addrs, err = answer(name, server, msg, qtype)
                if err == nil || err.(*DNSError).Err == noSuchHost {
                        break
diff --git a/src/pkg/net/dnsclient_unix_test.go b/src/pkg/net/dnsclient_unix_test.go
new file mode 100644 (file)
index 0000000..fe51f22
--- /dev/null
@@ -0,0 +1,29 @@
+// Copyright 2013 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package net
+
+import (
+       "runtime"
+       "testing"
+)
+
+func TestTCPLookup(t *testing.T) {
+       if runtime.GOOS == "windows" || runtime.GOOS == "plan9" {
+               t.Skip("skipping unix dns test")
+       }
+       if testing.Short() || !*testExternal {
+               t.Skip("skipping test to avoid external network")
+       }
+       c, err := Dial("tcp", "8.8.8.8:53")
+       defer c.Close()
+       if err != nil {
+               t.Fatalf("Dial failed: %v", err)
+       }
+       cfg := &dnsConfig{timeout: 10, attempts: 3}
+       _, err = exchange(cfg, c, "com.", dnsTypeALL)
+       if err != nil {
+               t.Fatalf("exchange failed: %v", err)
+       }
+}