Commit bb870689 authored by Matthew Dempsky's avatar Matthew Dempsky Committed by Chris Broadfoot

[release-branch.go1.7] 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 <mdempsky@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: 's avatarBrad Fitzpatrick <bradfitz@golang.org>
Reviewed-on: https://go-review.googlesource.com/28640
parent 287be1e4
...@@ -141,7 +141,7 @@ func (d *Dialer) dialDNS(ctx context.Context, network, server string) (dnsConn, ...@@ -141,7 +141,7 @@ func (d *Dialer) dialDNS(ctx context.Context, network, server string) (dnsConn,
} }
// 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 exchange(ctx context.Context, server, name string, qtype uint16) (*dnsMsg, error) { func exchange(ctx context.Context, server, name string, qtype uint16, timeout time.Duration) (*dnsMsg, error) {
d := testHookDNSDialer() d := testHookDNSDialer()
out := dnsMsg{ out := dnsMsg{
dnsMsgHdr: dnsMsgHdr{ dnsMsgHdr: dnsMsgHdr{
...@@ -152,6 +152,12 @@ func exchange(ctx context.Context, server, name string, qtype uint16) (*dnsMsg, ...@@ -152,6 +152,12 @@ func exchange(ctx context.Context, server, name string, qtype uint16) (*dnsMsg,
}, },
} }
for _, network := range []string{"udp", "tcp"} { for _, network := range []string{"udp", "tcp"} {
// TODO(mdempsky): Refactor so defers from UDP-based
// exchanges happen before TCP-based exchange.
ctx, cancel := context.WithDeadline(ctx, time.Now().Add(timeout))
defer cancel()
c, err := d.dialDNS(ctx, network, server) c, err := d.dialDNS(ctx, network, server)
if err != nil { if err != nil {
return nil, err return nil, err
...@@ -180,17 +186,10 @@ func tryOneName(ctx context.Context, cfg *dnsConfig, name string, qtype uint16) ...@@ -180,17 +186,10 @@ func tryOneName(ctx context.Context, cfg *dnsConfig, name string, qtype uint16)
return "", nil, &DNSError{Err: "no DNS servers", Name: name} return "", nil, &DNSError{Err: "no DNS servers", Name: name}
} }
deadline := time.Now().Add(cfg.timeout)
if old, ok := ctx.Deadline(); !ok || deadline.Before(old) {
var cancel context.CancelFunc
ctx, cancel = context.WithDeadline(ctx, deadline)
defer cancel()
}
var lastErr error var lastErr error
for i := 0; i < cfg.attempts; i++ { for i := 0; i < cfg.attempts; i++ {
for _, server := range cfg.servers { for _, server := range cfg.servers {
msg, err := exchange(ctx, server, name, qtype) msg, err := exchange(ctx, server, name, qtype, cfg.timeout)
if err != nil { if err != nil {
lastErr = &DNSError{ lastErr = &DNSError{
Err: err.Error(), Err: err.Error(),
......
...@@ -40,9 +40,9 @@ func TestDNSTransportFallback(t *testing.T) { ...@@ -40,9 +40,9 @@ func TestDNSTransportFallback(t *testing.T) {
testenv.MustHaveExternalNetwork(t) testenv.MustHaveExternalNetwork(t)
for _, tt := range dnsTransportFallbackTests { for _, tt := range dnsTransportFallbackTests {
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(tt.timeout)*time.Second) ctx, cancel := context.WithCancel(context.Background())
defer cancel() defer cancel()
msg, err := exchange(ctx, tt.server, tt.name, tt.qtype) msg, err := exchange(ctx, tt.server, tt.name, tt.qtype, time.Second)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
continue continue
...@@ -82,9 +82,9 @@ func TestSpecialDomainName(t *testing.T) { ...@@ -82,9 +82,9 @@ func TestSpecialDomainName(t *testing.T) {
server := "8.8.8.8:53" server := "8.8.8.8:53"
for _, tt := range specialDomainNameTests { for _, tt := range specialDomainNameTests {
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) ctx, cancel := context.WithCancel(context.Background())
defer cancel() defer cancel()
msg, err := exchange(ctx, server, tt.name, tt.qtype) msg, err := exchange(ctx, server, tt.name, tt.qtype, 3*time.Second)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
continue continue
...@@ -501,7 +501,7 @@ func TestErrorForOriginalNameWhenSearching(t *testing.T) { ...@@ -501,7 +501,7 @@ func TestErrorForOriginalNameWhenSearching(t *testing.T) {
d := &fakeDNSDialer{} d := &fakeDNSDialer{}
testHookDNSDialer = func() dnsDialer { return d } testHookDNSDialer = func() dnsDialer { return d }
d.rh = func(s string, q *dnsMsg) (*dnsMsg, error) { d.rh = func(s string, q *dnsMsg, _ time.Time) (*dnsMsg, error) {
r := &dnsMsg{ r := &dnsMsg{
dnsMsgHdr: dnsMsgHdr{ dnsMsgHdr: dnsMsgHdr{
id: q.id, id: q.id,
...@@ -540,14 +540,15 @@ func TestIgnoreLameReferrals(t *testing.T) { ...@@ -540,14 +540,15 @@ func TestIgnoreLameReferrals(t *testing.T) {
} }
defer conf.teardown() defer conf.teardown()
if err := conf.writeAndUpdate([]string{"nameserver 192.0.2.1", "nameserver 192.0.2.2"}); err != nil { if err := conf.writeAndUpdate([]string{"nameserver 192.0.2.1", // the one that will give a lame referral
"nameserver 192.0.2.2"}); err != nil {
t.Fatal(err) t.Fatal(err)
} }
d := &fakeDNSDialer{} d := &fakeDNSDialer{}
testHookDNSDialer = func() dnsDialer { return d } testHookDNSDialer = func() dnsDialer { return d }
d.rh = func(s string, q *dnsMsg) (*dnsMsg, error) { d.rh = func(s string, q *dnsMsg, _ time.Time) (*dnsMsg, error) {
t.Log(s, q) t.Log(s, q)
r := &dnsMsg{ r := &dnsMsg{
dnsMsgHdr: dnsMsgHdr{ dnsMsgHdr: dnsMsgHdr{
...@@ -634,28 +635,30 @@ func BenchmarkGoLookupIPWithBrokenNameServer(b *testing.B) { ...@@ -634,28 +635,30 @@ func BenchmarkGoLookupIPWithBrokenNameServer(b *testing.B) {
type fakeDNSDialer struct { type fakeDNSDialer struct {
// reply handler // reply handler
rh func(s string, q *dnsMsg) (*dnsMsg, error) rh func(s string, q *dnsMsg, t time.Time) (*dnsMsg, error)
} }
func (f *fakeDNSDialer) dialDNS(_ context.Context, n, s string) (dnsConn, error) { func (f *fakeDNSDialer) dialDNS(_ context.Context, n, s string) (dnsConn, error) {
return &fakeDNSConn{f.rh, s}, nil return &fakeDNSConn{f.rh, s, time.Time{}}, nil
} }
type fakeDNSConn struct { type fakeDNSConn struct {
rh func(s string, q *dnsMsg) (*dnsMsg, error) rh func(s string, q *dnsMsg, t time.Time) (*dnsMsg, error)
s string s string
t time.Time
} }
func (f *fakeDNSConn) Close() error { func (f *fakeDNSConn) Close() error {
return nil return nil
} }
func (f *fakeDNSConn) SetDeadline(time.Time) error { func (f *fakeDNSConn) SetDeadline(t time.Time) error {
f.t = t
return nil return nil
} }
func (f *fakeDNSConn) dnsRoundTrip(q *dnsMsg) (*dnsMsg, error) { func (f *fakeDNSConn) dnsRoundTrip(q *dnsMsg) (*dnsMsg, error) {
return f.rh(f.s, q) return f.rh(f.s, q, f.t)
} }
// UDP round-tripper algorithm should ignore invalid DNS responses (issue 13281). // UDP round-tripper algorithm should ignore invalid DNS responses (issue 13281).
...@@ -725,3 +728,72 @@ func TestIgnoreDNSForgeries(t *testing.T) { ...@@ -725,3 +728,72 @@ func TestIgnoreDNSForgeries(t *testing.T) {
t.Errorf("got address %v, want %v", got, TestAddr) t.Errorf("got address %v, want %v", got, TestAddr)
} }
} }
// Issue 16865. If a name server times out, continue to the next.
func TestRetryTimeout(t *testing.T) {
origTestHookDNSDialer := testHookDNSDialer
defer func() { testHookDNSDialer = origTestHookDNSDialer }()
conf, err := newResolvConfTest()
if err != nil {
t.Fatal(err)
}
defer conf.teardown()
if err := conf.writeAndUpdate([]string{"nameserver 192.0.2.1", // the one that will timeout
"nameserver 192.0.2.2"}); err != nil {
t.Fatal(err)
}
d := &fakeDNSDialer{}
testHookDNSDialer = func() dnsDialer { return d }
var deadline0 time.Time
d.rh = func(s string, q *dnsMsg, deadline time.Time) (*dnsMsg, error) {
t.Log(s, q, deadline)
if deadline.IsZero() {
t.Error("zero deadline")
}
if s == "192.0.2.1:53" {
deadline0 = deadline
time.Sleep(10 * time.Millisecond)
return nil, errTimeout
}
if deadline == deadline0 {
t.Error("deadline didn't change")
}
r := &dnsMsg{
dnsMsgHdr: dnsMsgHdr{
id: q.id,
response: true,
recursion_available: true,
},
question: q.question,
answer: []dnsRR{
&dnsRR_CNAME{
Hdr: dnsRR_Header{
Name: q.question[0].Name,
Rrtype: dnsTypeCNAME,
Class: dnsClassINET,
},
Cname: "golang.org",
},
},
}
return r, nil
}
_, err = goLookupCNAME(context.Background(), "www.golang.org")
if err != nil {
t.Fatal(err)
}
if deadline0.IsZero() {
t.Error("deadline0 still zero", deadline0)
}
}
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