Skip to content

Commit 825ff1e

Browse files
jfbusbradfitz
authored andcommitted
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: 70bc00f GitHub-Pull-Request: #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>
1 parent e900964 commit 825ff1e

7 files changed

+90
-6
lines changed

src/net/dnsclient_unix.go

+15-3
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,12 @@ import (
2626
"golang.org/x/net/dns/dnsmessage"
2727
)
2828

29+
const (
30+
// to be used as a useTCP parameter to exchange
31+
useTCPOnly = true
32+
useUDPOrTCP = false
33+
)
34+
2935
var (
3036
errLameReferral = errors.New("lame referral")
3137
errCannotUnmarshalDNSMessage = errors.New("cannot unmarshal DNS message")
@@ -131,13 +137,19 @@ func dnsStreamRoundTrip(c Conn, id uint16, query dnsmessage.Question, b []byte)
131137
}
132138

133139
// exchange sends a query on the connection and hopes for a response.
134-
func (r *Resolver) exchange(ctx context.Context, server string, q dnsmessage.Question, timeout time.Duration) (dnsmessage.Parser, dnsmessage.Header, error) {
140+
func (r *Resolver) exchange(ctx context.Context, server string, q dnsmessage.Question, timeout time.Duration, useTCP bool) (dnsmessage.Parser, dnsmessage.Header, error) {
135141
q.Class = dnsmessage.ClassINET
136142
id, udpReq, tcpReq, err := newRequest(q)
137143
if err != nil {
138144
return dnsmessage.Parser{}, dnsmessage.Header{}, errCannotMarshalDNSMessage
139145
}
140-
for _, network := range []string{"udp", "tcp"} {
146+
var networks []string
147+
if useTCP {
148+
networks = []string{"tcp"}
149+
} else {
150+
networks = []string{"udp", "tcp"}
151+
}
152+
for _, network := range networks {
141153
ctx, cancel := context.WithDeadline(ctx, time.Now().Add(timeout))
142154
defer cancel()
143155

@@ -241,7 +253,7 @@ func (r *Resolver) tryOneName(ctx context.Context, cfg *dnsConfig, name string,
241253
for j := uint32(0); j < sLen; j++ {
242254
server := cfg.servers[(serverOffset+j)%sLen]
243255

244-
p, h, err := r.exchange(ctx, server, q, cfg.timeout)
256+
p, h, err := r.exchange(ctx, server, q, cfg.timeout, cfg.useTCP)
245257
if err != nil {
246258
dnsErr := &DNSError{
247259
Err: err.Error(),

src/net/dnsclient_unix_test.go

+30-3
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ func TestDNSTransportFallback(t *testing.T) {
8181
for _, tt := range dnsTransportFallbackTests {
8282
ctx, cancel := context.WithCancel(context.Background())
8383
defer cancel()
84-
_, h, err := r.exchange(ctx, tt.server, tt.question, time.Second)
84+
_, h, err := r.exchange(ctx, tt.server, tt.question, time.Second, useUDPOrTCP)
8585
if err != nil {
8686
t.Error(err)
8787
continue
@@ -137,7 +137,7 @@ func TestSpecialDomainName(t *testing.T) {
137137
for _, tt := range specialDomainNameTests {
138138
ctx, cancel := context.WithCancel(context.Background())
139139
defer cancel()
140-
_, h, err := r.exchange(ctx, server, tt.question, 3*time.Second)
140+
_, h, err := r.exchange(ctx, server, tt.question, 3*time.Second, useUDPOrTCP)
141141
if err != nil {
142142
t.Error(err)
143143
continue
@@ -1564,7 +1564,7 @@ func TestDNSDialTCP(t *testing.T) {
15641564
}
15651565
r := Resolver{PreferGo: true, Dial: fake.DialContext}
15661566
ctx := context.Background()
1567-
_, _, err := r.exchange(ctx, "0.0.0.0", mustQuestion("com.", dnsmessage.TypeALL, dnsmessage.ClassINET), time.Second)
1567+
_, _, err := r.exchange(ctx, "0.0.0.0", mustQuestion("com.", dnsmessage.TypeALL, dnsmessage.ClassINET), time.Second, useUDPOrTCP)
15681568
if err != nil {
15691569
t.Fatal("exhange failed:", err)
15701570
}
@@ -1695,3 +1695,30 @@ func TestSingleRequestLookup(t *testing.T) {
16951695
}
16961696
}
16971697
}
1698+
1699+
// Issue 29358. Add configuration knob to force TCP-only DNS requests in the pure Go resolver.
1700+
func TestDNSUseTCP(t *testing.T) {
1701+
fake := fakeDNSServer{
1702+
rh: func(n, _ string, q dnsmessage.Message, _ time.Time) (dnsmessage.Message, error) {
1703+
r := dnsmessage.Message{
1704+
Header: dnsmessage.Header{
1705+
ID: q.Header.ID,
1706+
Response: true,
1707+
RCode: dnsmessage.RCodeSuccess,
1708+
},
1709+
Questions: q.Questions,
1710+
}
1711+
if n == "udp" {
1712+
t.Fatal("udp protocol was used instead of tcp")
1713+
}
1714+
return r, nil
1715+
},
1716+
}
1717+
r := Resolver{PreferGo: true, Dial: fake.DialContext}
1718+
ctx, cancel := context.WithCancel(context.Background())
1719+
defer cancel()
1720+
_, _, err := r.exchange(ctx, "0.0.0.0", mustQuestion("com.", dnsmessage.TypeALL, dnsmessage.ClassINET), time.Second, useTCPOnly)
1721+
if err != nil {
1722+
t.Fatal("exchange failed:", err)
1723+
}
1724+
}

src/net/dnsconfig_unix.go

+9
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ type dnsConfig struct {
3333
mtime time.Time // time of resolv.conf modification
3434
soffset uint32 // used by serverOffset
3535
singleRequest bool // use sequential A and AAAA queries instead of parallel queries
36+
useTCP bool // force usage of TCP for DNS resolutions
3637
}
3738

3839
// See resolv.conf(5) on a Linux machine.
@@ -123,6 +124,14 @@ func dnsReadConfig(filename string) *dnsConfig {
123124
// This option disables the behavior and makes glibc
124125
// perform the IPv6 and IPv4 requests sequentially."
125126
conf.singleRequest = true
127+
case s == "use-vc" || s == "usevc" || s == "tcp":
128+
// Linux (use-vc), FreeBSD (usevc) and OpenBSD (tcp) option:
129+
// http://man7.org/linux/man-pages/man5/resolv.conf.5.html
130+
// "Sets RES_USEVC in _res.options.
131+
// This option forces the use of TCP for DNS resolutions."
132+
// https://www.freebsd.org/cgi/man.cgi?query=resolv.conf&sektion=5&manpath=freebsd-release-ports
133+
// https://man.openbsd.org/resolv.conf.5
134+
conf.useTCP = true
126135
default:
127136
conf.unknownOpt = true
128137
}

src/net/dnsconfig_unix_test.go

+33
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,39 @@ var dnsReadConfigTests = []struct {
124124
search: []string{"domain.local."},
125125
},
126126
},
127+
{
128+
name: "testdata/linux-use-vc-resolv.conf",
129+
want: &dnsConfig{
130+
servers: defaultNS,
131+
ndots: 1,
132+
useTCP: true,
133+
timeout: 5 * time.Second,
134+
attempts: 2,
135+
search: []string{"domain.local."},
136+
},
137+
},
138+
{
139+
name: "testdata/freebsd-usevc-resolv.conf",
140+
want: &dnsConfig{
141+
servers: defaultNS,
142+
ndots: 1,
143+
useTCP: true,
144+
timeout: 5 * time.Second,
145+
attempts: 2,
146+
search: []string{"domain.local."},
147+
},
148+
},
149+
{
150+
name: "testdata/openbsd-tcp-resolv.conf",
151+
want: &dnsConfig{
152+
servers: defaultNS,
153+
ndots: 1,
154+
useTCP: true,
155+
timeout: 5 * time.Second,
156+
attempts: 2,
157+
search: []string{"domain.local."},
158+
},
159+
},
127160
}
128161

129162
func TestDNSReadConfig(t *testing.T) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
options usevc
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
options use-vc
+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
options tcp

0 commit comments

Comments
 (0)