From cd77ba359212760f7d259cbc524aad0c870e6982 Mon Sep 17 00:00:00 2001 From: jfbus Date: Thu, 20 Dec 2018 19:02:20 +0100 Subject: [PATCH 1/2] net: allow TCP only DNS requests in the pure Go resolver on unix --- src/net/conf.go | 12 ++++++++++-- src/net/dnsclient_unix.go | 8 +++++++- src/net/lookup.go | 5 +++++ 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/src/net/conf.go b/src/net/conf.go index 971b1a399a1bb8..093e6f97214857 100644 --- a/src/net/conf.go +++ b/src/net/conf.go @@ -22,6 +22,9 @@ type conf struct { netGo bool // go DNS resolution forced netCgo bool // cgo DNS resolution forced + // use TCP for netGo DNS queries + preferTCP bool + // machine has an /etc/mdns.allow file hasMDNSAllow bool @@ -44,10 +47,11 @@ func systemConf() *conf { } func initConfVal() { - dnsMode, debugLevel := goDebugNetDNS() + dnsMode, debugLevel, preferTCP := goDebugNetDNS() confVal.dnsDebugLevel = debugLevel confVal.netGo = netGo || dnsMode == "go" confVal.netCgo = netCgo || dnsMode == "cgo" + confVal.preferTCP = preferTCP if confVal.dnsDebugLevel > 0 { defer func() { @@ -286,11 +290,13 @@ func (c *conf) hostLookupOrder(r *Resolver, hostname string) (ret hostLookupOrde // 2 // debug level 2 // cgo // use cgo for DNS lookups // go // use go for DNS lookups +// tcp // use TCP for DNS lookups (go resolver only) // cgo+1 // use cgo for DNS lookups + debug level 1 // 1+cgo // same // cgo+2 // same, but debug level 2 +// go+tcp // use go and TCP // etc. -func goDebugNetDNS() (dnsMode string, debugLevel int) { +func goDebugNetDNS() (dnsMode string, debugLevel int, preferTCP bool) { goDebug := goDebugString("netdns") parsePart := func(s string) { if s == "" { @@ -298,6 +304,8 @@ func goDebugNetDNS() (dnsMode string, debugLevel int) { } if '0' <= s[0] && s[0] <= '9' { debugLevel, _, _ = dtoi(s) + } else if s == "tcp" { + preferTCP = true } else { dnsMode = s } diff --git a/src/net/dnsclient_unix.go b/src/net/dnsclient_unix.go index 86ce92dc437844..b1e8cebb474e44 100644 --- a/src/net/dnsclient_unix.go +++ b/src/net/dnsclient_unix.go @@ -137,7 +137,13 @@ func (r *Resolver) exchange(ctx context.Context, server string, q dnsmessage.Que if err != nil { return dnsmessage.Parser{}, dnsmessage.Header{}, errCannotMarshalDNSMessage } - for _, network := range []string{"udp", "tcp"} { + var networks []string + if r.PreferTCP || systemConf().preferTCP { + networks = []string{"tcp"} + } else { + networks = []string{"udp", "tcp"} + } + for _, network := range networks { ctx, cancel := context.WithDeadline(ctx, time.Now().Add(timeout)) defer cancel() diff --git a/src/net/lookup.go b/src/net/lookup.go index e10889331e4f5e..d6cad85ea45dbf 100644 --- a/src/net/lookup.go +++ b/src/net/lookup.go @@ -123,6 +123,11 @@ type Resolver struct { // GODEBUG=netdns=go, but scoped to just this resolver. PreferGo bool + // PreferTCP controls whether Go's built-in DNS resolver will use + // TCP instead of UDP with a fallback to TCP. It is equivalent to setting + // GODEBUG=netdns=go+tcp, but scoped to just this resolver. + PreferTCP bool + // StrictErrors controls the behavior of temporary errors // (including timeout, socket errors, and SERVFAIL) when using // Go's built-in resolver. For a query composed of multiple From 541f2d0b80cabe6bbcb9c94b4b18d61e9ef77afb Mon Sep 17 00:00:00 2001 From: jfbus Date: Thu, 20 Dec 2018 22:52:27 +0100 Subject: [PATCH 2/2] net: add Resolver.PreferTCP test + GODEBUG=netdns=go,tcp doc --- src/net/dnsclient_unix_test.go | 27 +++++++++++++++++++++++++++ src/net/net.go | 3 +++ 2 files changed, 30 insertions(+) diff --git a/src/net/dnsclient_unix_test.go b/src/net/dnsclient_unix_test.go index be04a44c14beaf..4ce74dc3b3bead 100644 --- a/src/net/dnsclient_unix_test.go +++ b/src/net/dnsclient_unix_test.go @@ -1621,3 +1621,30 @@ func TestTXTRecordTwoStrings(t *testing.T) { t.Errorf("txt[1], got %q, want %q", txt[1], want) } } + +// Issue 29358. Add configuration knob to force TCP-only DNS requests in the pure Go resolver. +func TestPreferTCP(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, PreferTCP: 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) + if err != nil { + t.Fatal("exchange failed:", err) + } +} diff --git a/src/net/net.go b/src/net/net.go index 77b8f69074e39c..a4baf1318b236e 100644 --- a/src/net/net.go +++ b/src/net/net.go @@ -66,6 +66,9 @@ GODEBUG environment variable (see package runtime) to go or cgo, as in: The decision can also be forced while building the Go source tree by setting the netgo or netcgo build tag. +The pure Go resolver can be configured to use only TCP to communicate +by using the tcp option, as in GODEBUG=netdns=go,tcp. + A numeric netdns setting, as in GODEBUG=netdns=1, causes the resolver to print debugging information about its decisions. To force a particular resolver while also printing debugging information,