mirror of https://github.com/golang/go.git
net: add Resolver.StrictErrors
When LookupIP is performing multiple subqueries, this option causes a timeout/servfail affecting a single query to abort the whole operation, instead of returning a partial (IPv4/IPv6-only) result. Similarly, operations that walk the DNS search list will also abort when encountering one of these errors. Fixes #17448 Change-Id: Ice22e4aceb555c5a80d19bd1fde8b8fe87ac9517 Reviewed-on: https://go-review.googlesource.com/32572 Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org> Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org>
This commit is contained in:
parent
b0e91d836a
commit
bfc164c64d
|
|
@ -200,6 +200,11 @@ func tryOneName(ctx context.Context, cfg *dnsConfig, name string, qtype uint16)
|
|||
if nerr, ok := err.(Error); ok && nerr.Timeout() {
|
||||
lastErr.(*DNSError).IsTimeout = true
|
||||
}
|
||||
// Set IsTemporary for socket-level errors. Note that this flag
|
||||
// may also be used to indicate a SERVFAIL response.
|
||||
if _, ok := err.(*OpError); ok {
|
||||
lastErr.(*DNSError).IsTemporary = true
|
||||
}
|
||||
continue
|
||||
}
|
||||
// libresolv continues to the next server when it receives
|
||||
|
|
@ -314,7 +319,7 @@ func (conf *resolverConfig) releaseSema() {
|
|||
<-conf.ch
|
||||
}
|
||||
|
||||
func lookup(ctx context.Context, name string, qtype uint16) (cname string, rrs []dnsRR, err error) {
|
||||
func (r *Resolver) lookup(ctx context.Context, name string, qtype uint16) (cname string, rrs []dnsRR, err error) {
|
||||
if !isDomainName(name) {
|
||||
// We used to use "invalid domain name" as the error,
|
||||
// but that is a detail of the specific lookup mechanism.
|
||||
|
|
@ -332,6 +337,11 @@ func lookup(ctx context.Context, name string, qtype uint16) (cname string, rrs [
|
|||
if err == nil {
|
||||
break
|
||||
}
|
||||
if nerr, ok := err.(Error); ok && nerr.Temporary() && r.StrictErrors {
|
||||
// If we hit a temporary error with StrictErrors enabled,
|
||||
// stop immediately instead of trying more names.
|
||||
break
|
||||
}
|
||||
}
|
||||
if err, ok := err.(*DNSError); ok {
|
||||
// Show original name passed to lookup, not suffixed one.
|
||||
|
|
@ -432,11 +442,11 @@ func (o hostLookupOrder) String() string {
|
|||
// Normally we let cgo use the C library resolver instead of
|
||||
// depending on our lookup code, so that Go and C get the same
|
||||
// answers.
|
||||
func goLookupHost(ctx context.Context, name string) (addrs []string, err error) {
|
||||
return goLookupHostOrder(ctx, name, hostLookupFilesDNS)
|
||||
func (r *Resolver) goLookupHost(ctx context.Context, name string) (addrs []string, err error) {
|
||||
return r.goLookupHostOrder(ctx, name, hostLookupFilesDNS)
|
||||
}
|
||||
|
||||
func goLookupHostOrder(ctx context.Context, name string, order hostLookupOrder) (addrs []string, err error) {
|
||||
func (r *Resolver) goLookupHostOrder(ctx context.Context, name string, order hostLookupOrder) (addrs []string, err error) {
|
||||
if order == hostLookupFilesDNS || order == hostLookupFiles {
|
||||
// Use entries from /etc/hosts if they match.
|
||||
addrs = lookupStaticHost(name)
|
||||
|
|
@ -444,7 +454,7 @@ func goLookupHostOrder(ctx context.Context, name string, order hostLookupOrder)
|
|||
return
|
||||
}
|
||||
}
|
||||
ips, _, err := goLookupIPCNAMEOrder(ctx, name, order)
|
||||
ips, _, err := r.goLookupIPCNAMEOrder(ctx, name, order)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
|
@ -470,13 +480,13 @@ func goLookupIPFiles(name string) (addrs []IPAddr) {
|
|||
|
||||
// goLookupIP is the native Go implementation of LookupIP.
|
||||
// The libc versions are in cgo_*.go.
|
||||
func goLookupIP(ctx context.Context, host string) (addrs []IPAddr, err error) {
|
||||
func (r *Resolver) goLookupIP(ctx context.Context, host string) (addrs []IPAddr, err error) {
|
||||
order := systemConf().hostLookupOrder(host)
|
||||
addrs, _, err = goLookupIPCNAMEOrder(ctx, host, order)
|
||||
addrs, _, err = r.goLookupIPCNAMEOrder(ctx, host, order)
|
||||
return
|
||||
}
|
||||
|
||||
func goLookupIPCNAMEOrder(ctx context.Context, name string, order hostLookupOrder) (addrs []IPAddr, cname string, err error) {
|
||||
func (r *Resolver) goLookupIPCNAMEOrder(ctx context.Context, name string, order hostLookupOrder) (addrs []IPAddr, cname string, err error) {
|
||||
if order == hostLookupFilesDNS || order == hostLookupFiles {
|
||||
addrs = goLookupIPFiles(name)
|
||||
if len(addrs) > 0 || order == hostLookupFiles {
|
||||
|
|
@ -506,11 +516,16 @@ func goLookupIPCNAMEOrder(ctx context.Context, name string, order hostLookupOrde
|
|||
lane <- racer{cname, rrs, err}
|
||||
}(qtype)
|
||||
}
|
||||
hitStrictError := false
|
||||
for range qtypes {
|
||||
racer := <-lane
|
||||
if racer.error != nil {
|
||||
// Prefer error for original name.
|
||||
if lastErr == nil || fqdn == name+"." {
|
||||
if nerr, ok := racer.error.(Error); ok && nerr.Temporary() && r.StrictErrors {
|
||||
// This error will abort the nameList loop.
|
||||
hitStrictError = true
|
||||
lastErr = racer.error
|
||||
} else if lastErr == nil || fqdn == name+"." {
|
||||
// Prefer error for original name.
|
||||
lastErr = racer.error
|
||||
}
|
||||
continue
|
||||
|
|
@ -520,6 +535,13 @@ func goLookupIPCNAMEOrder(ctx context.Context, name string, order hostLookupOrde
|
|||
cname = racer.cname
|
||||
}
|
||||
}
|
||||
if hitStrictError {
|
||||
// If either family hit an error with StrictErrors enabled,
|
||||
// discard all addresses. This ensures that network flakiness
|
||||
// cannot turn a dualstack hostname IPv4/IPv6-only.
|
||||
addrs = nil
|
||||
break
|
||||
}
|
||||
if len(addrs) > 0 {
|
||||
break
|
||||
}
|
||||
|
|
@ -543,9 +565,9 @@ func goLookupIPCNAMEOrder(ctx context.Context, name string, order hostLookupOrde
|
|||
}
|
||||
|
||||
// goLookupCNAME is the native Go (non-cgo) implementation of LookupCNAME.
|
||||
func goLookupCNAME(ctx context.Context, host string) (cname string, err error) {
|
||||
func (r *Resolver) goLookupCNAME(ctx context.Context, host string) (cname string, err error) {
|
||||
order := systemConf().hostLookupOrder(host)
|
||||
_, cname, err = goLookupIPCNAMEOrder(ctx, host, order)
|
||||
_, cname, err = r.goLookupIPCNAMEOrder(ctx, host, order)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -554,7 +576,7 @@ func goLookupCNAME(ctx context.Context, host string) (cname string, err error) {
|
|||
// only if cgoLookupPTR is the stub in cgo_stub.go).
|
||||
// Normally we let cgo use the C library resolver instead of depending
|
||||
// on our lookup code, so that Go and C get the same answers.
|
||||
func goLookupPTR(ctx context.Context, addr string) ([]string, error) {
|
||||
func (r *Resolver) goLookupPTR(ctx context.Context, addr string) ([]string, error) {
|
||||
names := lookupStaticAddr(addr)
|
||||
if len(names) > 0 {
|
||||
return names, nil
|
||||
|
|
@ -563,7 +585,7 @@ func goLookupPTR(ctx context.Context, addr string) ([]string, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, rrs, err := lookup(ctx, arpa, dnsTypePTR)
|
||||
_, rrs, err := r.lookup(ctx, arpa, dnsTypePTR)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,9 @@ import (
|
|||
// Test address from 192.0.2.0/24 block, reserved by RFC 5737 for documentation.
|
||||
const TestAddr uint32 = 0xc0000201
|
||||
|
||||
// Test address from 2001:db8::/32 block, reserved by RFC 3849 for documentation.
|
||||
var TestAddr6 = [16]byte{0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}
|
||||
|
||||
var dnsTransportFallbackTests = []struct {
|
||||
server string
|
||||
name string
|
||||
|
|
@ -142,7 +145,7 @@ func TestAvoidDNSName(t *testing.T) {
|
|||
|
||||
// Issue 13705: don't try to resolve onion addresses, etc
|
||||
func TestLookupTorOnion(t *testing.T) {
|
||||
addrs, err := goLookupIP(context.Background(), "foo.onion")
|
||||
addrs, err := DefaultResolver.goLookupIP(context.Background(), "foo.onion")
|
||||
if len(addrs) > 0 {
|
||||
t.Errorf("unexpected addresses: %v", addrs)
|
||||
}
|
||||
|
|
@ -258,7 +261,7 @@ func TestUpdateResolvConf(t *testing.T) {
|
|||
for j := 0; j < N; j++ {
|
||||
go func(name string) {
|
||||
defer wg.Done()
|
||||
ips, err := goLookupIP(context.Background(), name)
|
||||
ips, err := DefaultResolver.goLookupIP(context.Background(), name)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
|
|
@ -406,7 +409,7 @@ func TestGoLookupIPWithResolverConfig(t *testing.T) {
|
|||
t.Error(err)
|
||||
continue
|
||||
}
|
||||
addrs, err := goLookupIP(context.Background(), tt.name)
|
||||
addrs, err := DefaultResolver.goLookupIP(context.Background(), tt.name)
|
||||
if err != nil {
|
||||
// This test uses external network connectivity.
|
||||
// We need to take care with errors on both
|
||||
|
|
@ -456,14 +459,14 @@ func TestGoLookupIPOrderFallbackToFile(t *testing.T) {
|
|||
name := fmt.Sprintf("order %v", order)
|
||||
|
||||
// First ensure that we get an error when contacting a non-existent host.
|
||||
_, _, err := goLookupIPCNAMEOrder(context.Background(), "notarealhost", order)
|
||||
_, _, err := DefaultResolver.goLookupIPCNAMEOrder(context.Background(), "notarealhost", order)
|
||||
if err == nil {
|
||||
t.Errorf("%s: expected error while looking up name not in hosts file", name)
|
||||
continue
|
||||
}
|
||||
|
||||
// Now check that we get an address when the name appears in the hosts file.
|
||||
addrs, _, err := goLookupIPCNAMEOrder(context.Background(), "thor", order) // entry is in "testdata/hosts"
|
||||
addrs, _, err := DefaultResolver.goLookupIPCNAMEOrder(context.Background(), "thor", order) // entry is in "testdata/hosts"
|
||||
if err != nil {
|
||||
t.Errorf("%s: expected to successfully lookup host entry", name)
|
||||
continue
|
||||
|
|
@ -519,14 +522,24 @@ func TestErrorForOriginalNameWhenSearching(t *testing.T) {
|
|||
return r, nil
|
||||
}
|
||||
|
||||
_, err = goLookupIP(context.Background(), fqdn)
|
||||
if err == nil {
|
||||
t.Fatal("expected an error")
|
||||
cases := []struct {
|
||||
strictErrors bool
|
||||
wantErr *DNSError
|
||||
}{
|
||||
{true, &DNSError{Name: fqdn, Err: "server misbehaving", IsTemporary: true}},
|
||||
{false, &DNSError{Name: fqdn, Err: errNoSuchHost.Error()}},
|
||||
}
|
||||
for _, tt := range cases {
|
||||
r := Resolver{StrictErrors: tt.strictErrors}
|
||||
_, err = r.goLookupIP(context.Background(), fqdn)
|
||||
if err == nil {
|
||||
t.Fatal("expected an error")
|
||||
}
|
||||
|
||||
want := &DNSError{Name: fqdn, Err: errNoSuchHost.Error()}
|
||||
if err, ok := err.(*DNSError); !ok || err.Name != want.Name || err.Err != want.Err {
|
||||
t.Errorf("got %v; want %v", err, want)
|
||||
want := tt.wantErr
|
||||
if err, ok := err.(*DNSError); !ok || err.Name != want.Name || err.Err != want.Err || err.IsTemporary != want.IsTemporary {
|
||||
t.Errorf("got %v; want %v", err, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -579,7 +592,7 @@ func TestIgnoreLameReferrals(t *testing.T) {
|
|||
return r, nil
|
||||
}
|
||||
|
||||
addrs, err := goLookupIP(context.Background(), "www.golang.org")
|
||||
addrs, err := DefaultResolver.goLookupIP(context.Background(), "www.golang.org")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
|
@ -598,7 +611,7 @@ func BenchmarkGoLookupIP(b *testing.B) {
|
|||
ctx := context.Background()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
goLookupIP(ctx, "www.example.com")
|
||||
DefaultResolver.goLookupIP(ctx, "www.example.com")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -607,7 +620,7 @@ func BenchmarkGoLookupIPNoSuchHost(b *testing.B) {
|
|||
ctx := context.Background()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
goLookupIP(ctx, "some.nonexistent")
|
||||
DefaultResolver.goLookupIP(ctx, "some.nonexistent")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -630,7 +643,7 @@ func BenchmarkGoLookupIPWithBrokenNameServer(b *testing.B) {
|
|||
ctx := context.Background()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
goLookupIP(ctx, "www.example.com")
|
||||
DefaultResolver.goLookupIP(ctx, "www.example.com")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -861,3 +874,309 @@ func mockTXTResponse(q *dnsMsg) *dnsMsg {
|
|||
|
||||
return r
|
||||
}
|
||||
|
||||
// Issue 17448. With StrictErrors enabled, temporary errors should make
|
||||
// LookupIP fail rather than return a partial result.
|
||||
func TestStrictErrorsLookupIP(t *testing.T) {
|
||||
origTestHookDNSDialer := testHookDNSDialer
|
||||
defer func() { testHookDNSDialer = origTestHookDNSDialer }()
|
||||
|
||||
conf, err := newResolvConfTest()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer conf.teardown()
|
||||
|
||||
confData := []string{
|
||||
"nameserver 192.0.2.53",
|
||||
"search x.golang.org y.golang.org",
|
||||
}
|
||||
if err := conf.writeAndUpdate(confData); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
const name = "test"
|
||||
const server = "192.0.2.53:53"
|
||||
const searchX = "test.x.golang.org."
|
||||
const searchY = "test.y.golang.org."
|
||||
const ip4 = "192.0.2.1"
|
||||
const ip6 = "2001:db8::1"
|
||||
|
||||
type resolveWhichEnum int
|
||||
const (
|
||||
resolveOK resolveWhichEnum = iota
|
||||
resolveOpError
|
||||
resolveServfail
|
||||
resolveTimeout
|
||||
)
|
||||
|
||||
makeTempError := func(err string) error {
|
||||
return &DNSError{
|
||||
Err: err,
|
||||
Name: name,
|
||||
Server: server,
|
||||
IsTemporary: true,
|
||||
}
|
||||
}
|
||||
makeTimeout := func() error {
|
||||
return &DNSError{
|
||||
Err: poll.ErrTimeout.Error(),
|
||||
Name: name,
|
||||
Server: server,
|
||||
IsTimeout: true,
|
||||
}
|
||||
}
|
||||
makeNxDomain := func() error {
|
||||
return &DNSError{
|
||||
Err: errNoSuchHost.Error(),
|
||||
Name: name,
|
||||
Server: server,
|
||||
}
|
||||
}
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
resolveWhich func(quest *dnsQuestion) resolveWhichEnum
|
||||
wantStrictErr error
|
||||
wantLaxErr error
|
||||
wantIPs []string
|
||||
}{
|
||||
{
|
||||
desc: "No errors",
|
||||
resolveWhich: func(quest *dnsQuestion) resolveWhichEnum {
|
||||
return resolveOK
|
||||
},
|
||||
wantIPs: []string{ip4, ip6},
|
||||
},
|
||||
{
|
||||
desc: "searchX error fails in strict mode",
|
||||
resolveWhich: func(quest *dnsQuestion) resolveWhichEnum {
|
||||
if quest.Name == searchX {
|
||||
return resolveTimeout
|
||||
}
|
||||
return resolveOK
|
||||
},
|
||||
wantStrictErr: makeTimeout(),
|
||||
wantIPs: []string{ip4, ip6},
|
||||
},
|
||||
{
|
||||
desc: "searchX IPv4-only timeout fails in strict mode",
|
||||
resolveWhich: func(quest *dnsQuestion) resolveWhichEnum {
|
||||
if quest.Name == searchX && quest.Qtype == dnsTypeA {
|
||||
return resolveTimeout
|
||||
}
|
||||
return resolveOK
|
||||
},
|
||||
wantStrictErr: makeTimeout(),
|
||||
wantIPs: []string{ip4, ip6},
|
||||
},
|
||||
{
|
||||
desc: "searchX IPv6-only servfail fails in strict mode",
|
||||
resolveWhich: func(quest *dnsQuestion) resolveWhichEnum {
|
||||
if quest.Name == searchX && quest.Qtype == dnsTypeAAAA {
|
||||
return resolveServfail
|
||||
}
|
||||
return resolveOK
|
||||
},
|
||||
wantStrictErr: makeTempError("server misbehaving"),
|
||||
wantIPs: []string{ip4, ip6},
|
||||
},
|
||||
{
|
||||
desc: "searchY error always fails",
|
||||
resolveWhich: func(quest *dnsQuestion) resolveWhichEnum {
|
||||
if quest.Name == searchY {
|
||||
return resolveTimeout
|
||||
}
|
||||
return resolveOK
|
||||
},
|
||||
wantStrictErr: makeTimeout(),
|
||||
wantLaxErr: makeNxDomain(), // This one reaches the "test." FQDN.
|
||||
},
|
||||
{
|
||||
desc: "searchY IPv4-only socket error fails in strict mode",
|
||||
resolveWhich: func(quest *dnsQuestion) resolveWhichEnum {
|
||||
if quest.Name == searchY && quest.Qtype == dnsTypeA {
|
||||
return resolveOpError
|
||||
}
|
||||
return resolveOK
|
||||
},
|
||||
wantStrictErr: makeTempError("write: socket on fire"),
|
||||
wantIPs: []string{ip6},
|
||||
},
|
||||
{
|
||||
desc: "searchY IPv6-only timeout fails in strict mode",
|
||||
resolveWhich: func(quest *dnsQuestion) resolveWhichEnum {
|
||||
if quest.Name == searchY && quest.Qtype == dnsTypeAAAA {
|
||||
return resolveTimeout
|
||||
}
|
||||
return resolveOK
|
||||
},
|
||||
wantStrictErr: makeTimeout(),
|
||||
wantIPs: []string{ip4},
|
||||
},
|
||||
}
|
||||
|
||||
for i, tt := range cases {
|
||||
d := &fakeDNSDialer{}
|
||||
testHookDNSDialer = func() dnsDialer { return d }
|
||||
|
||||
d.rh = func(s string, q *dnsMsg, deadline time.Time) (*dnsMsg, error) {
|
||||
t.Log(s, q)
|
||||
|
||||
switch tt.resolveWhich(&q.question[0]) {
|
||||
case resolveOK:
|
||||
// Handle below.
|
||||
case resolveOpError:
|
||||
return nil, &OpError{Op: "write", Err: fmt.Errorf("socket on fire")}
|
||||
case resolveServfail:
|
||||
return &dnsMsg{dnsMsgHdr: dnsMsgHdr{id: q.id, rcode: dnsRcodeServerFailure}}, nil
|
||||
case resolveTimeout:
|
||||
return nil, poll.ErrTimeout
|
||||
default:
|
||||
t.Fatal("Impossible resolveWhich")
|
||||
}
|
||||
|
||||
switch q.question[0].Name {
|
||||
case searchX, name + ".":
|
||||
// Return NXDOMAIN to utilize the search list.
|
||||
return &dnsMsg{dnsMsgHdr: dnsMsgHdr{id: q.id, rcode: dnsRcodeNameError}}, nil
|
||||
case searchY:
|
||||
// Return records below.
|
||||
default:
|
||||
return nil, fmt.Errorf("Unexpected Name: %v", q.question[0].Name)
|
||||
}
|
||||
|
||||
r := &dnsMsg{
|
||||
dnsMsgHdr: dnsMsgHdr{
|
||||
id: q.id,
|
||||
response: true,
|
||||
},
|
||||
question: q.question,
|
||||
}
|
||||
switch q.question[0].Qtype {
|
||||
case dnsTypeA:
|
||||
r.answer = []dnsRR{
|
||||
&dnsRR_A{
|
||||
Hdr: dnsRR_Header{
|
||||
Name: q.question[0].Name,
|
||||
Rrtype: dnsTypeA,
|
||||
Class: dnsClassINET,
|
||||
Rdlength: 4,
|
||||
},
|
||||
A: TestAddr,
|
||||
},
|
||||
}
|
||||
case dnsTypeAAAA:
|
||||
r.answer = []dnsRR{
|
||||
&dnsRR_AAAA{
|
||||
Hdr: dnsRR_Header{
|
||||
Name: q.question[0].Name,
|
||||
Rrtype: dnsTypeAAAA,
|
||||
Class: dnsClassINET,
|
||||
Rdlength: 16,
|
||||
},
|
||||
AAAA: TestAddr6,
|
||||
},
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("Unexpected Qtype: %v", q.question[0].Qtype)
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
for _, strict := range []bool{true, false} {
|
||||
r := Resolver{StrictErrors: strict}
|
||||
ips, err := r.goLookupIP(context.Background(), name)
|
||||
|
||||
var wantErr error
|
||||
if strict {
|
||||
wantErr = tt.wantStrictErr
|
||||
} else {
|
||||
wantErr = tt.wantLaxErr
|
||||
}
|
||||
if !reflect.DeepEqual(err, wantErr) {
|
||||
t.Errorf("#%d (%s) strict=%v: got err %#v; want %#v", i, tt.desc, strict, err, wantErr)
|
||||
}
|
||||
|
||||
gotIPs := map[string]struct{}{}
|
||||
for _, ip := range ips {
|
||||
gotIPs[ip.String()] = struct{}{}
|
||||
}
|
||||
wantIPs := map[string]struct{}{}
|
||||
if wantErr == nil {
|
||||
for _, ip := range tt.wantIPs {
|
||||
wantIPs[ip] = struct{}{}
|
||||
}
|
||||
}
|
||||
if !reflect.DeepEqual(gotIPs, wantIPs) {
|
||||
t.Errorf("#%d (%s) strict=%v: got ips %v; want %v", i, tt.desc, strict, gotIPs, wantIPs)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Issue 17448. With StrictErrors enabled, temporary errors should make
|
||||
// LookupTXT stop walking the search list.
|
||||
func TestStrictErrorsLookupTXT(t *testing.T) {
|
||||
origTestHookDNSDialer := testHookDNSDialer
|
||||
defer func() { testHookDNSDialer = origTestHookDNSDialer }()
|
||||
|
||||
conf, err := newResolvConfTest()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer conf.teardown()
|
||||
|
||||
confData := []string{
|
||||
"nameserver 192.0.2.53",
|
||||
"search x.golang.org y.golang.org",
|
||||
}
|
||||
if err := conf.writeAndUpdate(confData); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
const name = "test"
|
||||
const server = "192.0.2.53:53"
|
||||
const searchX = "test.x.golang.org."
|
||||
const searchY = "test.y.golang.org."
|
||||
const txt = "Hello World"
|
||||
|
||||
d := &fakeDNSDialer{}
|
||||
testHookDNSDialer = func() dnsDialer { return d }
|
||||
|
||||
d.rh = func(s string, q *dnsMsg, deadline time.Time) (*dnsMsg, error) {
|
||||
t.Log(s, q)
|
||||
|
||||
switch q.question[0].Name {
|
||||
case searchX:
|
||||
return nil, poll.ErrTimeout
|
||||
case searchY:
|
||||
return mockTXTResponse(q), nil
|
||||
default:
|
||||
return nil, fmt.Errorf("Unexpected Name: %v", q.question[0].Name)
|
||||
}
|
||||
}
|
||||
|
||||
for _, strict := range []bool{true, false} {
|
||||
r := Resolver{StrictErrors: strict}
|
||||
_, rrs, err := r.lookup(context.Background(), name, dnsTypeTXT)
|
||||
var wantErr error
|
||||
var wantRRs int
|
||||
if strict {
|
||||
wantErr = &DNSError{
|
||||
Err: poll.ErrTimeout.Error(),
|
||||
Name: name,
|
||||
Server: server,
|
||||
IsTimeout: true,
|
||||
}
|
||||
} else {
|
||||
wantRRs = 1
|
||||
}
|
||||
if !reflect.DeepEqual(err, wantErr) {
|
||||
t.Errorf("strict=%v: got err %#v; want %#v", strict, err, wantErr)
|
||||
}
|
||||
if len(rrs) != wantRRs {
|
||||
t.Errorf("strict=%v: got %v; want %v", strict, len(rrs), wantRRs)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -97,6 +97,16 @@ type Resolver struct {
|
|||
// GODEBUG=netdns=go, but scoped to just this resolver.
|
||||
PreferGo 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
|
||||
// sub-queries (such as an A+AAAA address lookup, or walking the
|
||||
// DNS search list), this option causes such errors to abort the
|
||||
// whole query instead of returning a partial result. This is
|
||||
// not enabled by default because it may affect compatibility
|
||||
// with resolvers that process AAAA queries incorrectly.
|
||||
StrictErrors bool
|
||||
|
||||
// TODO(bradfitz): optional interface impl override hook
|
||||
// TODO(bradfitz): Timeout time.Duration?
|
||||
}
|
||||
|
|
|
|||
|
|
@ -57,12 +57,12 @@ func (r *Resolver) lookupHost(ctx context.Context, host string) (addrs []string,
|
|||
// cgo not available (or netgo); fall back to Go's DNS resolver
|
||||
order = hostLookupFilesDNS
|
||||
}
|
||||
return goLookupHostOrder(ctx, host, order)
|
||||
return r.goLookupHostOrder(ctx, host, order)
|
||||
}
|
||||
|
||||
func (r *Resolver) lookupIP(ctx context.Context, host string) (addrs []IPAddr, err error) {
|
||||
if r.PreferGo {
|
||||
return goLookupIP(ctx, host)
|
||||
return r.goLookupIP(ctx, host)
|
||||
}
|
||||
order := systemConf().hostLookupOrder(host)
|
||||
if order == hostLookupCgo {
|
||||
|
|
@ -72,7 +72,7 @@ func (r *Resolver) lookupIP(ctx context.Context, host string) (addrs []IPAddr, e
|
|||
// cgo not available (or netgo); fall back to Go's DNS resolver
|
||||
order = hostLookupFilesDNS
|
||||
}
|
||||
addrs, _, err = goLookupIPCNAMEOrder(ctx, host, order)
|
||||
addrs, _, err = r.goLookupIPCNAMEOrder(ctx, host, order)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -98,17 +98,17 @@ func (r *Resolver) lookupCNAME(ctx context.Context, name string) (string, error)
|
|||
return cname, err
|
||||
}
|
||||
}
|
||||
return goLookupCNAME(ctx, name)
|
||||
return r.goLookupCNAME(ctx, name)
|
||||
}
|
||||
|
||||
func (*Resolver) lookupSRV(ctx context.Context, service, proto, name string) (string, []*SRV, error) {
|
||||
func (r *Resolver) lookupSRV(ctx context.Context, service, proto, name string) (string, []*SRV, error) {
|
||||
var target string
|
||||
if service == "" && proto == "" {
|
||||
target = name
|
||||
} else {
|
||||
target = "_" + service + "._" + proto + "." + name
|
||||
}
|
||||
cname, rrs, err := lookup(ctx, target, dnsTypeSRV)
|
||||
cname, rrs, err := r.lookup(ctx, target, dnsTypeSRV)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
|
|
@ -121,8 +121,8 @@ func (*Resolver) lookupSRV(ctx context.Context, service, proto, name string) (st
|
|||
return cname, srvs, nil
|
||||
}
|
||||
|
||||
func (*Resolver) lookupMX(ctx context.Context, name string) ([]*MX, error) {
|
||||
_, rrs, err := lookup(ctx, name, dnsTypeMX)
|
||||
func (r *Resolver) lookupMX(ctx context.Context, name string) ([]*MX, error) {
|
||||
_, rrs, err := r.lookup(ctx, name, dnsTypeMX)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -135,8 +135,8 @@ func (*Resolver) lookupMX(ctx context.Context, name string) ([]*MX, error) {
|
|||
return mxs, nil
|
||||
}
|
||||
|
||||
func (*Resolver) lookupNS(ctx context.Context, name string) ([]*NS, error) {
|
||||
_, rrs, err := lookup(ctx, name, dnsTypeNS)
|
||||
func (r *Resolver) lookupNS(ctx context.Context, name string) ([]*NS, error) {
|
||||
_, rrs, err := r.lookup(ctx, name, dnsTypeNS)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -148,7 +148,7 @@ func (*Resolver) lookupNS(ctx context.Context, name string) ([]*NS, error) {
|
|||
}
|
||||
|
||||
func (r *Resolver) lookupTXT(ctx context.Context, name string) ([]string, error) {
|
||||
_, rrs, err := lookup(ctx, name, dnsTypeTXT)
|
||||
_, rrs, err := r.lookup(ctx, name, dnsTypeTXT)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -165,5 +165,5 @@ func (r *Resolver) lookupAddr(ctx context.Context, addr string) ([]string, error
|
|||
return ptrs, err
|
||||
}
|
||||
}
|
||||
return goLookupPTR(ctx, addr)
|
||||
return r.goLookupPTR(ctx, addr)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ func TestGoLookupIP(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if _, err := goLookupIP(ctx, host); err != nil {
|
||||
if _, err := DefaultResolver.goLookupIP(ctx, host); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue