Skip to content

Commit 11e3955

Browse files
committed
net: restore per-query timeout logic
The handling of "options timeout:n" is supposed to be per individual DNS server exchange, not per Lookup call. Fixes #16865. Change-Id: I2304579b9169c1515292f142cb372af9d37ff7c1 Reviewed-on: https://go-review.googlesource.com/28057 Run-TryBot: Matthew Dempsky <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Brad Fitzpatrick <[email protected]>
1 parent aaa6b53 commit 11e3955

File tree

2 files changed

+92
-21
lines changed

2 files changed

+92
-21
lines changed

src/net/dnsclient_unix.go

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ func (d *Dialer) dialDNS(ctx context.Context, network, server string) (dnsConn,
141141
}
142142

143143
// exchange sends a query on the connection and hopes for a response.
144-
func exchange(ctx context.Context, server, name string, qtype uint16) (*dnsMsg, error) {
144+
func exchange(ctx context.Context, server, name string, qtype uint16, timeout time.Duration) (*dnsMsg, error) {
145145
d := testHookDNSDialer()
146146
out := dnsMsg{
147147
dnsMsgHdr: dnsMsgHdr{
@@ -152,6 +152,12 @@ func exchange(ctx context.Context, server, name string, qtype uint16) (*dnsMsg,
152152
},
153153
}
154154
for _, network := range []string{"udp", "tcp"} {
155+
// TODO(mdempsky): Refactor so defers from UDP-based
156+
// exchanges happen before TCP-based exchange.
157+
158+
ctx, cancel := context.WithDeadline(ctx, time.Now().Add(timeout))
159+
defer cancel()
160+
155161
c, err := d.dialDNS(ctx, network, server)
156162
if err != nil {
157163
return nil, err
@@ -180,17 +186,10 @@ func tryOneName(ctx context.Context, cfg *dnsConfig, name string, qtype uint16)
180186
return "", nil, &DNSError{Err: "no DNS servers", Name: name}
181187
}
182188

183-
deadline := time.Now().Add(cfg.timeout)
184-
if old, ok := ctx.Deadline(); !ok || deadline.Before(old) {
185-
var cancel context.CancelFunc
186-
ctx, cancel = context.WithDeadline(ctx, deadline)
187-
defer cancel()
188-
}
189-
190189
var lastErr error
191190
for i := 0; i < cfg.attempts; i++ {
192191
for _, server := range cfg.servers {
193-
msg, err := exchange(ctx, server, name, qtype)
192+
msg, err := exchange(ctx, server, name, qtype, cfg.timeout)
194193
if err != nil {
195194
lastErr = &DNSError{
196195
Err: err.Error(),

src/net/dnsclient_unix_test.go

Lines changed: 84 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,9 @@ func TestDNSTransportFallback(t *testing.T) {
4040
testenv.MustHaveExternalNetwork(t)
4141

4242
for _, tt := range dnsTransportFallbackTests {
43-
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(tt.timeout)*time.Second)
43+
ctx, cancel := context.WithCancel(context.Background())
4444
defer cancel()
45-
msg, err := exchange(ctx, tt.server, tt.name, tt.qtype)
45+
msg, err := exchange(ctx, tt.server, tt.name, tt.qtype, time.Second)
4646
if err != nil {
4747
t.Error(err)
4848
continue
@@ -82,9 +82,9 @@ func TestSpecialDomainName(t *testing.T) {
8282

8383
server := "8.8.8.8:53"
8484
for _, tt := range specialDomainNameTests {
85-
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
85+
ctx, cancel := context.WithCancel(context.Background())
8686
defer cancel()
87-
msg, err := exchange(ctx, server, tt.name, tt.qtype)
87+
msg, err := exchange(ctx, server, tt.name, tt.qtype, 3*time.Second)
8888
if err != nil {
8989
t.Error(err)
9090
continue
@@ -501,7 +501,7 @@ func TestErrorForOriginalNameWhenSearching(t *testing.T) {
501501
d := &fakeDNSDialer{}
502502
testHookDNSDialer = func() dnsDialer { return d }
503503

504-
d.rh = func(s string, q *dnsMsg) (*dnsMsg, error) {
504+
d.rh = func(s string, q *dnsMsg, _ time.Time) (*dnsMsg, error) {
505505
r := &dnsMsg{
506506
dnsMsgHdr: dnsMsgHdr{
507507
id: q.id,
@@ -540,14 +540,15 @@ func TestIgnoreLameReferrals(t *testing.T) {
540540
}
541541
defer conf.teardown()
542542

543-
if err := conf.writeAndUpdate([]string{"nameserver 192.0.2.1", "nameserver 192.0.2.2"}); err != nil {
543+
if err := conf.writeAndUpdate([]string{"nameserver 192.0.2.1", // the one that will give a lame referral
544+
"nameserver 192.0.2.2"}); err != nil {
544545
t.Fatal(err)
545546
}
546547

547548
d := &fakeDNSDialer{}
548549
testHookDNSDialer = func() dnsDialer { return d }
549550

550-
d.rh = func(s string, q *dnsMsg) (*dnsMsg, error) {
551+
d.rh = func(s string, q *dnsMsg, _ time.Time) (*dnsMsg, error) {
551552
t.Log(s, q)
552553
r := &dnsMsg{
553554
dnsMsgHdr: dnsMsgHdr{
@@ -634,28 +635,30 @@ func BenchmarkGoLookupIPWithBrokenNameServer(b *testing.B) {
634635

635636
type fakeDNSDialer struct {
636637
// reply handler
637-
rh func(s string, q *dnsMsg) (*dnsMsg, error)
638+
rh func(s string, q *dnsMsg, t time.Time) (*dnsMsg, error)
638639
}
639640

640641
func (f *fakeDNSDialer) dialDNS(_ context.Context, n, s string) (dnsConn, error) {
641-
return &fakeDNSConn{f.rh, s}, nil
642+
return &fakeDNSConn{f.rh, s, time.Time{}}, nil
642643
}
643644

644645
type fakeDNSConn struct {
645-
rh func(s string, q *dnsMsg) (*dnsMsg, error)
646+
rh func(s string, q *dnsMsg, t time.Time) (*dnsMsg, error)
646647
s string
648+
t time.Time
647649
}
648650

649651
func (f *fakeDNSConn) Close() error {
650652
return nil
651653
}
652654

653-
func (f *fakeDNSConn) SetDeadline(time.Time) error {
655+
func (f *fakeDNSConn) SetDeadline(t time.Time) error {
656+
f.t = t
654657
return nil
655658
}
656659

657660
func (f *fakeDNSConn) dnsRoundTrip(q *dnsMsg) (*dnsMsg, error) {
658-
return f.rh(f.s, q)
661+
return f.rh(f.s, q, f.t)
659662
}
660663

661664
// UDP round-tripper algorithm should ignore invalid DNS responses (issue 13281).
@@ -725,3 +728,72 @@ func TestIgnoreDNSForgeries(t *testing.T) {
725728
t.Errorf("got address %v, want %v", got, TestAddr)
726729
}
727730
}
731+
732+
// Issue 16865. If a name server times out, continue to the next.
733+
func TestRetryTimeout(t *testing.T) {
734+
origTestHookDNSDialer := testHookDNSDialer
735+
defer func() { testHookDNSDialer = origTestHookDNSDialer }()
736+
737+
conf, err := newResolvConfTest()
738+
if err != nil {
739+
t.Fatal(err)
740+
}
741+
defer conf.teardown()
742+
743+
if err := conf.writeAndUpdate([]string{"nameserver 192.0.2.1", // the one that will timeout
744+
"nameserver 192.0.2.2"}); err != nil {
745+
t.Fatal(err)
746+
}
747+
748+
d := &fakeDNSDialer{}
749+
testHookDNSDialer = func() dnsDialer { return d }
750+
751+
var deadline0 time.Time
752+
753+
d.rh = func(s string, q *dnsMsg, deadline time.Time) (*dnsMsg, error) {
754+
t.Log(s, q, deadline)
755+
756+
if deadline.IsZero() {
757+
t.Error("zero deadline")
758+
}
759+
760+
if s == "192.0.2.1:53" {
761+
deadline0 = deadline
762+
time.Sleep(10 * time.Millisecond)
763+
return nil, errTimeout
764+
}
765+
766+
if deadline == deadline0 {
767+
t.Error("deadline didn't change")
768+
}
769+
770+
r := &dnsMsg{
771+
dnsMsgHdr: dnsMsgHdr{
772+
id: q.id,
773+
response: true,
774+
recursion_available: true,
775+
},
776+
question: q.question,
777+
answer: []dnsRR{
778+
&dnsRR_CNAME{
779+
Hdr: dnsRR_Header{
780+
Name: q.question[0].Name,
781+
Rrtype: dnsTypeCNAME,
782+
Class: dnsClassINET,
783+
},
784+
Cname: "golang.org",
785+
},
786+
},
787+
}
788+
return r, nil
789+
}
790+
791+
_, err = goLookupCNAME(context.Background(), "www.golang.org")
792+
if err != nil {
793+
t.Fatal(err)
794+
}
795+
796+
if deadline0.IsZero() {
797+
t.Error("deadline0 still zero", deadline0)
798+
}
799+
}

0 commit comments

Comments
 (0)