mirror of
https://github.com/golang/go
synced 2024-11-17 13:14:56 -07:00
net: use DNS over TCP when use-vc is set in resolv.conf
There is a DNS resolution bug in Kubernetes (UDP response packets get dropped by conntrack, causing timeouts in DNS queries).
The recommended workaround on Linux is to configure the resolver to use TCP for DNS queries, by setting the use-vc option in resolv.conf.
With this PR, the pure Go resolver searches for "use-vc" in resolv.conf and switches to TCP when found.
Fixes #29358
Change-Id: I26b935cae2c80e5bb9955da83299a8dea84591de
GitHub-Last-Rev: 70bc00fe41
GitHub-Pull-Request: golang/go#29594
Reviewed-on: https://go-review.googlesource.com/c/go/+/156366
Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
This commit is contained in:
parent
e900964e0f
commit
825ff1e317
@ -26,6 +26,12 @@ import (
|
||||
"golang.org/x/net/dns/dnsmessage"
|
||||
)
|
||||
|
||||
const (
|
||||
// to be used as a useTCP parameter to exchange
|
||||
useTCPOnly = true
|
||||
useUDPOrTCP = false
|
||||
)
|
||||
|
||||
var (
|
||||
errLameReferral = errors.New("lame referral")
|
||||
errCannotUnmarshalDNSMessage = errors.New("cannot unmarshal DNS message")
|
||||
@ -131,13 +137,19 @@ func dnsStreamRoundTrip(c Conn, id uint16, query dnsmessage.Question, b []byte)
|
||||
}
|
||||
|
||||
// exchange sends a query on the connection and hopes for a response.
|
||||
func (r *Resolver) exchange(ctx context.Context, server string, q dnsmessage.Question, timeout time.Duration) (dnsmessage.Parser, dnsmessage.Header, error) {
|
||||
func (r *Resolver) exchange(ctx context.Context, server string, q dnsmessage.Question, timeout time.Duration, useTCP bool) (dnsmessage.Parser, dnsmessage.Header, error) {
|
||||
q.Class = dnsmessage.ClassINET
|
||||
id, udpReq, tcpReq, err := newRequest(q)
|
||||
if err != nil {
|
||||
return dnsmessage.Parser{}, dnsmessage.Header{}, errCannotMarshalDNSMessage
|
||||
}
|
||||
for _, network := range []string{"udp", "tcp"} {
|
||||
var networks []string
|
||||
if useTCP {
|
||||
networks = []string{"tcp"}
|
||||
} else {
|
||||
networks = []string{"udp", "tcp"}
|
||||
}
|
||||
for _, network := range networks {
|
||||
ctx, cancel := context.WithDeadline(ctx, time.Now().Add(timeout))
|
||||
defer cancel()
|
||||
|
||||
@ -241,7 +253,7 @@ func (r *Resolver) tryOneName(ctx context.Context, cfg *dnsConfig, name string,
|
||||
for j := uint32(0); j < sLen; j++ {
|
||||
server := cfg.servers[(serverOffset+j)%sLen]
|
||||
|
||||
p, h, err := r.exchange(ctx, server, q, cfg.timeout)
|
||||
p, h, err := r.exchange(ctx, server, q, cfg.timeout, cfg.useTCP)
|
||||
if err != nil {
|
||||
dnsErr := &DNSError{
|
||||
Err: err.Error(),
|
||||
|
@ -81,7 +81,7 @@ func TestDNSTransportFallback(t *testing.T) {
|
||||
for _, tt := range dnsTransportFallbackTests {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
_, h, err := r.exchange(ctx, tt.server, tt.question, time.Second)
|
||||
_, h, err := r.exchange(ctx, tt.server, tt.question, time.Second, useUDPOrTCP)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
continue
|
||||
@ -137,7 +137,7 @@ func TestSpecialDomainName(t *testing.T) {
|
||||
for _, tt := range specialDomainNameTests {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
_, h, err := r.exchange(ctx, server, tt.question, 3*time.Second)
|
||||
_, h, err := r.exchange(ctx, server, tt.question, 3*time.Second, useUDPOrTCP)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
continue
|
||||
@ -1564,7 +1564,7 @@ func TestDNSDialTCP(t *testing.T) {
|
||||
}
|
||||
r := Resolver{PreferGo: true, Dial: fake.DialContext}
|
||||
ctx := context.Background()
|
||||
_, _, err := r.exchange(ctx, "0.0.0.0", mustQuestion("com.", dnsmessage.TypeALL, dnsmessage.ClassINET), time.Second)
|
||||
_, _, err := r.exchange(ctx, "0.0.0.0", mustQuestion("com.", dnsmessage.TypeALL, dnsmessage.ClassINET), time.Second, useUDPOrTCP)
|
||||
if err != nil {
|
||||
t.Fatal("exhange failed:", err)
|
||||
}
|
||||
@ -1695,3 +1695,30 @@ func TestSingleRequestLookup(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Issue 29358. Add configuration knob to force TCP-only DNS requests in the pure Go resolver.
|
||||
func TestDNSUseTCP(t *testing.T) {
|
||||
fake := fakeDNSServer{
|
||||
rh: func(n, _ string, q dnsmessage.Message, _ time.Time) (dnsmessage.Message, error) {
|
||||
r := dnsmessage.Message{
|
||||
Header: dnsmessage.Header{
|
||||
ID: q.Header.ID,
|
||||
Response: true,
|
||||
RCode: dnsmessage.RCodeSuccess,
|
||||
},
|
||||
Questions: q.Questions,
|
||||
}
|
||||
if n == "udp" {
|
||||
t.Fatal("udp protocol was used instead of tcp")
|
||||
}
|
||||
return r, nil
|
||||
},
|
||||
}
|
||||
r := Resolver{PreferGo: true, Dial: fake.DialContext}
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
_, _, err := r.exchange(ctx, "0.0.0.0", mustQuestion("com.", dnsmessage.TypeALL, dnsmessage.ClassINET), time.Second, useTCPOnly)
|
||||
if err != nil {
|
||||
t.Fatal("exchange failed:", err)
|
||||
}
|
||||
}
|
||||
|
@ -33,6 +33,7 @@ type dnsConfig struct {
|
||||
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
|
||||
useTCP bool // force usage of TCP for DNS resolutions
|
||||
}
|
||||
|
||||
// See resolv.conf(5) on a Linux machine.
|
||||
@ -123,6 +124,14 @@ func dnsReadConfig(filename string) *dnsConfig {
|
||||
// This option disables the behavior and makes glibc
|
||||
// perform the IPv6 and IPv4 requests sequentially."
|
||||
conf.singleRequest = true
|
||||
case s == "use-vc" || s == "usevc" || s == "tcp":
|
||||
// Linux (use-vc), FreeBSD (usevc) and OpenBSD (tcp) option:
|
||||
// http://man7.org/linux/man-pages/man5/resolv.conf.5.html
|
||||
// "Sets RES_USEVC in _res.options.
|
||||
// This option forces the use of TCP for DNS resolutions."
|
||||
// https://www.freebsd.org/cgi/man.cgi?query=resolv.conf&sektion=5&manpath=freebsd-release-ports
|
||||
// https://man.openbsd.org/resolv.conf.5
|
||||
conf.useTCP = true
|
||||
default:
|
||||
conf.unknownOpt = true
|
||||
}
|
||||
|
@ -124,6 +124,39 @@ var dnsReadConfigTests = []struct {
|
||||
search: []string{"domain.local."},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "testdata/linux-use-vc-resolv.conf",
|
||||
want: &dnsConfig{
|
||||
servers: defaultNS,
|
||||
ndots: 1,
|
||||
useTCP: true,
|
||||
timeout: 5 * time.Second,
|
||||
attempts: 2,
|
||||
search: []string{"domain.local."},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "testdata/freebsd-usevc-resolv.conf",
|
||||
want: &dnsConfig{
|
||||
servers: defaultNS,
|
||||
ndots: 1,
|
||||
useTCP: true,
|
||||
timeout: 5 * time.Second,
|
||||
attempts: 2,
|
||||
search: []string{"domain.local."},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "testdata/openbsd-tcp-resolv.conf",
|
||||
want: &dnsConfig{
|
||||
servers: defaultNS,
|
||||
ndots: 1,
|
||||
useTCP: true,
|
||||
timeout: 5 * time.Second,
|
||||
attempts: 2,
|
||||
search: []string{"domain.local."},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestDNSReadConfig(t *testing.T) {
|
||||
|
1
src/net/testdata/freebsd-usevc-resolv.conf
vendored
Normal file
1
src/net/testdata/freebsd-usevc-resolv.conf
vendored
Normal file
@ -0,0 +1 @@
|
||||
options usevc
|
1
src/net/testdata/linux-use-vc-resolv.conf
vendored
Normal file
1
src/net/testdata/linux-use-vc-resolv.conf
vendored
Normal file
@ -0,0 +1 @@
|
||||
options use-vc
|
1
src/net/testdata/openbsd-tcp-resolv.conf
vendored
Normal file
1
src/net/testdata/openbsd-tcp-resolv.conf
vendored
Normal file
@ -0,0 +1 @@
|
||||
options tcp
|
Loading…
Reference in New Issue
Block a user