Skip to content

net: use DNS over TCP when use-vc is set in resolv.conf #29594

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 15 additions & 3 deletions src/net/dnsclient_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -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()

Expand Down Expand Up @@ -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(),
Expand Down
33 changes: 30 additions & 3 deletions src/net/dnsclient_unix_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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)
}
}
9 changes: 9 additions & 0 deletions src/net/dnsconfig_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
}
Expand Down
33 changes: 33 additions & 0 deletions src/net/dnsconfig_unix_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
1 change: 1 addition & 0 deletions src/net/testdata/freebsd-usevc-resolv.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
options usevc
1 change: 1 addition & 0 deletions src/net/testdata/linux-use-vc-resolv.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
options use-vc
1 change: 1 addition & 0 deletions src/net/testdata/openbsd-tcp-resolv.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
options tcp