net: implement DNS TCP fallback query if UDP response is truncated
Fixes #5686.
R=golang-dev, bradfitz, mikioh.mikioh
CC=golang-dev
https://golang.org/cl/12458043
diff --git a/src/pkg/net/dnsclient_unix.go b/src/pkg/net/dnsclient_unix.go
index c9a16a9..38fbf32 100644
--- a/src/pkg/net/dnsclient_unix.go
+++ b/src/pkg/net/dnsclient_unix.go
@@ -17,6 +17,7 @@
package net
import (
+ "io"
"math/rand"
"sync"
"time"
@@ -25,6 +26,13 @@
// 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 @@
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 @@
} 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 @@
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