Skip to content

Commit 2dfc5ea

Browse files
aojeagopherbot
authored andcommitted
net: don't retry truncated TCP responses
UDP messages may be truncated: https://www.rfc-editor.org/rfc/rfc1035#section-4.2.1 > Messages carried by UDP are restricted to 512 bytes (not counting > the IP or UDP headers). Longer messages are truncated and the TC > bit is set in the header. However, TCP also have a size limitation of 65535 bytes https://www.rfc-editor.org/rfc/rfc1035#section-4.2.2 > The message is prefixed with a two byte length field which gives the message length, excluding the two byte length field. These limitations makes that the maximum possible number of A records per RRSet is ~ 4090. There are environments like Kubernetes that may have larger number of records (5000+) that does not fit in a single message. In this cases, the DNS server sets the Truncated bit on the message to indicate that it could not send the full answer despite is using TCP. We should only retry when the TC bit is set and the connection is UDP, otherwise, we'll never being able to get an answer and the client will receive an errNoAnswerFromDNSServer, that is a different behavior than the existing in the glibc resolver, that returns all the existing addresses in the TCP truncated response. Fixes #64896 Signed-off-by: Antonio Ojea <aojea@google.com> Change-Id: I1bc2c85f67668765fa60b5c0378c9e1e1756dff2 Reviewed-on: https://go-review.googlesource.com/c/go/+/552418 Reviewed-by: Ian Lance Taylor <iant@google.com> Reviewed-by: Ian Gudger <ian@iangudger.com> Auto-Submit: Ian Lance Taylor <iant@google.com> Reviewed-by: Mateusz Poliwczak <mpoliwczak34@gmail.com> Reviewed-by: Mauri de Souza Meneguzzo <mauri870@gmail.com> Reviewed-by: Damien Neil <dneil@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
1 parent 69f1290 commit 2dfc5ea

File tree

2 files changed

+110
-1
lines changed

2 files changed

+110
-1
lines changed

src/net/dnsclient_unix.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,14 @@ func (r *Resolver) exchange(ctx context.Context, server string, q dnsmessage.Que
194194
if err := p.SkipQuestion(); err != dnsmessage.ErrSectionDone {
195195
return dnsmessage.Parser{}, dnsmessage.Header{}, errInvalidDNSResponse
196196
}
197-
if h.Truncated { // see RFC 5966
197+
// RFC 5966 indicates that when a client receives a UDP response with
198+
// the TC flag set, it should take the TC flag as an indication that it
199+
// should retry over TCP instead.
200+
// The case when the TC flag is set in a TCP response is not well specified,
201+
// so this implements the glibc resolver behavior, returning the existing
202+
// dns response instead of returning a "errNoAnswerFromDNSServer" error.
203+
// See go.dev/issue/64896
204+
if h.Truncated && network == "udp" {
198205
continue
199206
}
200207
return p, h, nil

src/net/dnsclient_unix_test.go

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,61 @@ func TestDNSTransportFallback(t *testing.T) {
9494
}
9595
}
9696

97+
func TestDNSTransportNoFallbackOnTCP(t *testing.T) {
98+
fake := fakeDNSServer{
99+
rh: func(n, _ string, q dnsmessage.Message, _ time.Time) (dnsmessage.Message, error) {
100+
r := dnsmessage.Message{
101+
Header: dnsmessage.Header{
102+
ID: q.Header.ID,
103+
Response: true,
104+
RCode: dnsmessage.RCodeSuccess,
105+
Truncated: true,
106+
},
107+
Questions: q.Questions,
108+
}
109+
if n == "tcp" {
110+
r.Answers = []dnsmessage.Resource{
111+
{
112+
Header: dnsmessage.ResourceHeader{
113+
Name: q.Questions[0].Name,
114+
Type: dnsmessage.TypeA,
115+
Class: dnsmessage.ClassINET,
116+
Length: 4,
117+
},
118+
Body: &dnsmessage.AResource{
119+
A: TestAddr,
120+
},
121+
},
122+
}
123+
}
124+
return r, nil
125+
},
126+
}
127+
r := Resolver{PreferGo: true, Dial: fake.DialContext}
128+
for _, tt := range dnsTransportFallbackTests {
129+
ctx, cancel := context.WithCancel(context.Background())
130+
defer cancel()
131+
p, h, err := r.exchange(ctx, tt.server, tt.question, time.Second, useUDPOrTCP, false)
132+
if err != nil {
133+
t.Error(err)
134+
continue
135+
}
136+
if h.RCode != tt.rcode {
137+
t.Errorf("got %v from %v; want %v", h.RCode, tt.server, tt.rcode)
138+
continue
139+
}
140+
a, err := p.AllAnswers()
141+
if err != nil {
142+
t.Errorf("unexpected error %v getting all answers from %v", err, tt.server)
143+
continue
144+
}
145+
if len(a) != 1 {
146+
t.Errorf("got %d answers from %v; want 1", len(a), tt.server)
147+
continue
148+
}
149+
}
150+
}
151+
97152
// See RFC 6761 for further information about the reserved, pseudo
98153
// domain names.
99154
var specialDomainNameTests = []struct {
@@ -1775,6 +1830,53 @@ func TestDNSUseTCP(t *testing.T) {
17751830
}
17761831
}
17771832

1833+
func TestDNSUseTCPTruncated(t *testing.T) {
1834+
fake := fakeDNSServer{
1835+
rh: func(n, _ string, q dnsmessage.Message, _ time.Time) (dnsmessage.Message, error) {
1836+
r := dnsmessage.Message{
1837+
Header: dnsmessage.Header{
1838+
ID: q.Header.ID,
1839+
Response: true,
1840+
RCode: dnsmessage.RCodeSuccess,
1841+
Truncated: true,
1842+
},
1843+
Questions: q.Questions,
1844+
Answers: []dnsmessage.Resource{
1845+
{
1846+
Header: dnsmessage.ResourceHeader{
1847+
Name: q.Questions[0].Name,
1848+
Type: dnsmessage.TypeA,
1849+
Class: dnsmessage.ClassINET,
1850+
Length: 4,
1851+
},
1852+
Body: &dnsmessage.AResource{
1853+
A: TestAddr,
1854+
},
1855+
},
1856+
},
1857+
}
1858+
if n == "udp" {
1859+
t.Fatal("udp protocol was used instead of tcp")
1860+
}
1861+
return r, nil
1862+
},
1863+
}
1864+
r := Resolver{PreferGo: true, Dial: fake.DialContext}
1865+
ctx, cancel := context.WithCancel(context.Background())
1866+
defer cancel()
1867+
p, _, err := r.exchange(ctx, "0.0.0.0", mustQuestion("com.", dnsmessage.TypeALL, dnsmessage.ClassINET), time.Second, useTCPOnly, false)
1868+
if err != nil {
1869+
t.Fatal("exchange failed:", err)
1870+
}
1871+
a, err := p.AllAnswers()
1872+
if err != nil {
1873+
t.Fatalf("unexpected error %v getting all answers", err)
1874+
}
1875+
if len(a) != 1 {
1876+
t.Fatalf("got %d answers; want 1", len(a))
1877+
}
1878+
}
1879+
17781880
// Issue 34660: PTR response with non-PTR answers should ignore non-PTR
17791881
func TestPTRandNonPTR(t *testing.T) {
17801882
fake := fakeDNSServer{

0 commit comments

Comments
 (0)