Commit 825ff1e3 authored by jfbus's avatar jfbus Committed by Brad Fitzpatrick

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: 70bc00fe41f44f0b2b3cfebe67bbcc45701968cf
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: default avatarBrad Fitzpatrick <bradfitz@golang.org>
parent e900964e
...@@ -26,6 +26,12 @@ import ( ...@@ -26,6 +26,12 @@ import (
"golang.org/x/net/dns/dnsmessage" "golang.org/x/net/dns/dnsmessage"
) )
const (
// to be used as a useTCP parameter to exchange
useTCPOnly = true
useUDPOrTCP = false
)
var ( var (
errLameReferral = errors.New("lame referral") errLameReferral = errors.New("lame referral")
errCannotUnmarshalDNSMessage = errors.New("cannot unmarshal DNS message") errCannotUnmarshalDNSMessage = errors.New("cannot unmarshal DNS message")
...@@ -131,13 +137,19 @@ func dnsStreamRoundTrip(c Conn, id uint16, query dnsmessage.Question, b []byte) ...@@ -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. // 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 q.Class = dnsmessage.ClassINET
id, udpReq, tcpReq, err := newRequest(q) id, udpReq, tcpReq, err := newRequest(q)
if err != nil { if err != nil {
return dnsmessage.Parser{}, dnsmessage.Header{}, errCannotMarshalDNSMessage 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)) ctx, cancel := context.WithDeadline(ctx, time.Now().Add(timeout))
defer cancel() defer cancel()
...@@ -241,7 +253,7 @@ func (r *Resolver) tryOneName(ctx context.Context, cfg *dnsConfig, name string, ...@@ -241,7 +253,7 @@ func (r *Resolver) tryOneName(ctx context.Context, cfg *dnsConfig, name string,
for j := uint32(0); j < sLen; j++ { for j := uint32(0); j < sLen; j++ {
server := cfg.servers[(serverOffset+j)%sLen] 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 { if err != nil {
dnsErr := &DNSError{ dnsErr := &DNSError{
Err: err.Error(), Err: err.Error(),
......
...@@ -81,7 +81,7 @@ func TestDNSTransportFallback(t *testing.T) { ...@@ -81,7 +81,7 @@ func TestDNSTransportFallback(t *testing.T) {
for _, tt := range dnsTransportFallbackTests { for _, tt := range dnsTransportFallbackTests {
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
defer cancel() 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 { if err != nil {
t.Error(err) t.Error(err)
continue continue
...@@ -137,7 +137,7 @@ func TestSpecialDomainName(t *testing.T) { ...@@ -137,7 +137,7 @@ func TestSpecialDomainName(t *testing.T) {
for _, tt := range specialDomainNameTests { for _, tt := range specialDomainNameTests {
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
defer cancel() 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 { if err != nil {
t.Error(err) t.Error(err)
continue continue
...@@ -1564,7 +1564,7 @@ func TestDNSDialTCP(t *testing.T) { ...@@ -1564,7 +1564,7 @@ func TestDNSDialTCP(t *testing.T) {
} }
r := Resolver{PreferGo: true, Dial: fake.DialContext} r := Resolver{PreferGo: true, Dial: fake.DialContext}
ctx := context.Background() 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 { if err != nil {
t.Fatal("exhange failed:", err) t.Fatal("exhange failed:", err)
} }
...@@ -1695,3 +1695,30 @@ func TestSingleRequestLookup(t *testing.T) { ...@@ -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 { ...@@ -33,6 +33,7 @@ type dnsConfig struct {
mtime time.Time // time of resolv.conf modification mtime time.Time // time of resolv.conf modification
soffset uint32 // used by serverOffset soffset uint32 // used by serverOffset
singleRequest bool // use sequential A and AAAA queries instead of parallel queries 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. // See resolv.conf(5) on a Linux machine.
...@@ -123,6 +124,14 @@ func dnsReadConfig(filename string) *dnsConfig { ...@@ -123,6 +124,14 @@ func dnsReadConfig(filename string) *dnsConfig {
// This option disables the behavior and makes glibc // This option disables the behavior and makes glibc
// perform the IPv6 and IPv4 requests sequentially." // perform the IPv6 and IPv4 requests sequentially."
conf.singleRequest = true 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: default:
conf.unknownOpt = true conf.unknownOpt = true
} }
......
...@@ -124,6 +124,39 @@ var dnsReadConfigTests = []struct { ...@@ -124,6 +124,39 @@ var dnsReadConfigTests = []struct {
search: []string{"domain.local."}, 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) { func TestDNSReadConfig(t *testing.T) {
......
options usevc
\ No newline at end of file
options use-vc
\ No newline at end of file
options tcp
\ No newline at end of file
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment