Commit fac3b5d0 authored by Shubham Sharma's avatar Shubham Sharma Committed by Brad Fitzpatrick

net: add IsNotFound field to DNSError

This adds the ability to determine if a lookup error was
due to a non-existent hostname. Previously users needed
to do string matching on the DNSError.Err value.

Fixes #28635

Change-Id: If4bd3ad32cbc2db5614f2c6b72e0a9161d813efa
Reviewed-on: https://go-review.googlesource.com/c/go/+/168597
Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: default avatarBrad Fitzpatrick <bradfitz@golang.org>
parent 7e08c7f4
...@@ -158,6 +158,7 @@ func cgoLookupIPCNAME(network, name string) (addrs []IPAddr, cname string, err e ...@@ -158,6 +158,7 @@ func cgoLookupIPCNAME(network, name string) (addrs []IPAddr, cname string, err e
var res *C.struct_addrinfo var res *C.struct_addrinfo
gerrno, err := C.getaddrinfo((*C.char)(unsafe.Pointer(&h[0])), nil, &hints, &res) gerrno, err := C.getaddrinfo((*C.char)(unsafe.Pointer(&h[0])), nil, &hints, &res)
if gerrno != 0 { if gerrno != 0 {
isErrorNoSuchHost := false
switch gerrno { switch gerrno {
case C.EAI_SYSTEM: case C.EAI_SYSTEM:
if err == nil { if err == nil {
...@@ -172,10 +173,12 @@ func cgoLookupIPCNAME(network, name string) (addrs []IPAddr, cname string, err e ...@@ -172,10 +173,12 @@ func cgoLookupIPCNAME(network, name string) (addrs []IPAddr, cname string, err e
} }
case C.EAI_NONAME: case C.EAI_NONAME:
err = errNoSuchHost err = errNoSuchHost
isErrorNoSuchHost = true
default: default:
err = addrinfoErrno(gerrno) err = addrinfoErrno(gerrno)
} }
return nil, "", &DNSError{Err: err.Error(), Name: name}
return nil, "", &DNSError{Err: err.Error(), Name: name, IsNotFound: isErrorNoSuchHost}
} }
defer C.freeaddrinfo(res) defer C.freeaddrinfo(res)
......
...@@ -284,10 +284,8 @@ func (r *Resolver) tryOneName(ctx context.Context, cfg *dnsConfig, name string, ...@@ -284,10 +284,8 @@ func (r *Resolver) tryOneName(ctx context.Context, cfg *dnsConfig, name string,
if err == errNoSuchHost { if err == errNoSuchHost {
// The name does not exist, so trying // The name does not exist, so trying
// another server won't help. // another server won't help.
//
// TODO: indicate this in a more dnsErr.IsNotFound = true
// obvious way, such as a field on
// DNSError?
return p, server, dnsErr return p, server, dnsErr
} }
lastErr = dnsErr lastErr = dnsErr
...@@ -306,9 +304,8 @@ func (r *Resolver) tryOneName(ctx context.Context, cfg *dnsConfig, name string, ...@@ -306,9 +304,8 @@ func (r *Resolver) tryOneName(ctx context.Context, cfg *dnsConfig, name string,
if err == errNoSuchHost { if err == errNoSuchHost {
// The name does not exist, so trying another // The name does not exist, so trying another
// server won't help. // server won't help.
//
// TODO: indicate this in a more obvious way, lastErr.(*DNSError).IsNotFound = true
// such as a field on DNSError?
return p, server, lastErr return p, server, lastErr
} }
} }
...@@ -398,7 +395,7 @@ func (r *Resolver) lookup(ctx context.Context, name string, qtype dnsmessage.Typ ...@@ -398,7 +395,7 @@ func (r *Resolver) lookup(ctx context.Context, name string, qtype dnsmessage.Typ
// Other lookups might allow broader name syntax // Other lookups might allow broader name syntax
// (for example Multicast DNS allows UTF-8; see RFC 6762). // (for example Multicast DNS allows UTF-8; see RFC 6762).
// For consistency with libc resolvers, report no such host. // For consistency with libc resolvers, report no such host.
return dnsmessage.Parser{}, "", &DNSError{Err: errNoSuchHost.Error(), Name: name} return dnsmessage.Parser{}, "", &DNSError{Err: errNoSuchHost.Error(), Name: name, IsNotFound: true}
} }
resolvConf.tryUpdate("/etc/resolv.conf") resolvConf.tryUpdate("/etc/resolv.conf")
resolvConf.mu.RLock() resolvConf.mu.RLock()
...@@ -575,7 +572,7 @@ func (r *Resolver) goLookupIPCNAMEOrder(ctx context.Context, name string, order ...@@ -575,7 +572,7 @@ func (r *Resolver) goLookupIPCNAMEOrder(ctx context.Context, name string, order
} }
if !isDomainName(name) { if !isDomainName(name) {
// See comment in func lookup above about use of errNoSuchHost. // See comment in func lookup above about use of errNoSuchHost.
return nil, dnsmessage.Name{}, &DNSError{Err: errNoSuchHost.Error(), Name: name} return nil, dnsmessage.Name{}, &DNSError{Err: errNoSuchHost.Error(), Name: name, IsNotFound: true}
} }
resolvConf.tryUpdate("/etc/resolv.conf") resolvConf.tryUpdate("/etc/resolv.conf")
resolvConf.mu.RLock() resolvConf.mu.RLock()
......
...@@ -666,7 +666,7 @@ func TestErrorForOriginalNameWhenSearching(t *testing.T) { ...@@ -666,7 +666,7 @@ func TestErrorForOriginalNameWhenSearching(t *testing.T) {
wantErr *DNSError wantErr *DNSError
}{ }{
{true, &DNSError{Name: fqdn, Err: "server misbehaving", IsTemporary: true}}, {true, &DNSError{Name: fqdn, Err: "server misbehaving", IsTemporary: true}},
{false, &DNSError{Name: fqdn, Err: errNoSuchHost.Error()}}, {false, &DNSError{Name: fqdn, Err: errNoSuchHost.Error(), IsNotFound: true}},
} }
for _, tt := range cases { for _, tt := range cases {
r := Resolver{PreferGo: true, StrictErrors: tt.strictErrors, Dial: fake.DialContext} r := Resolver{PreferGo: true, StrictErrors: tt.strictErrors, Dial: fake.DialContext}
...@@ -1138,9 +1138,10 @@ func TestStrictErrorsLookupIP(t *testing.T) { ...@@ -1138,9 +1138,10 @@ func TestStrictErrorsLookupIP(t *testing.T) {
} }
makeNxDomain := func() error { makeNxDomain := func() error {
return &DNSError{ return &DNSError{
Err: errNoSuchHost.Error(), Err: errNoSuchHost.Error(),
Name: name, Name: name,
Server: server, Server: server,
IsNotFound: true,
} }
} }
...@@ -1472,6 +1473,32 @@ func TestIssue8434(t *testing.T) { ...@@ -1472,6 +1473,32 @@ func TestIssue8434(t *testing.T) {
} }
} }
func TestIssueNoSuchHostExists(t *testing.T) {
err := lookupWithFake(fakeDNSServer{
rh: func(n, _ string, q dnsmessage.Message, _ time.Time) (dnsmessage.Message, error) {
return dnsmessage.Message{
Header: dnsmessage.Header{
ID: q.ID,
Response: true,
RCode: dnsmessage.RCodeNameError,
},
Questions: q.Questions,
}, nil
},
}, "golang.org.", dnsmessage.TypeALL)
if err == nil {
t.Fatal("expected an error")
}
if _, ok := err.(Error); !ok {
t.Fatalf("err = %#v; wanted something supporting net.Error", err)
}
if de, ok := err.(*DNSError); !ok {
t.Fatalf("err = %#v; wanted a *net.DNSError", err)
} else if !de.IsNotFound {
t.Fatalf("IsNotFound = false for err = %#v; want IsNotFound == true", err)
}
}
// TestNoSuchHost verifies that tryOneName works correctly when the domain does // TestNoSuchHost verifies that tryOneName works correctly when the domain does
// not exist. // not exist.
// //
...@@ -1541,6 +1568,9 @@ func TestNoSuchHost(t *testing.T) { ...@@ -1541,6 +1568,9 @@ func TestNoSuchHost(t *testing.T) {
if de.Err != errNoSuchHost.Error() { if de.Err != errNoSuchHost.Error() {
t.Fatalf("Err = %#v; wanted %q", de.Err, errNoSuchHost.Error()) t.Fatalf("Err = %#v; wanted %q", de.Err, errNoSuchHost.Error())
} }
if !de.IsNotFound {
t.Fatalf("IsNotFound = %v wanted true", de.IsNotFound)
}
}) })
} }
} }
......
...@@ -177,7 +177,7 @@ func (r *Resolver) LookupHost(ctx context.Context, host string) (addrs []string, ...@@ -177,7 +177,7 @@ func (r *Resolver) LookupHost(ctx context.Context, host string) (addrs []string,
// Make sure that no matter what we do later, host=="" is rejected. // Make sure that no matter what we do later, host=="" is rejected.
// parseIP, for example, does accept empty strings. // parseIP, for example, does accept empty strings.
if host == "" { if host == "" {
return nil, &DNSError{Err: errNoSuchHost.Error(), Name: host} return nil, &DNSError{Err: errNoSuchHost.Error(), Name: host, IsNotFound: true}
} }
if ip, _ := parseIPZone(host); ip != nil { if ip, _ := parseIPZone(host); ip != nil {
return []string{host}, nil return []string{host}, nil
...@@ -238,7 +238,7 @@ func (r *Resolver) lookupIPAddr(ctx context.Context, network, host string) ([]IP ...@@ -238,7 +238,7 @@ func (r *Resolver) lookupIPAddr(ctx context.Context, network, host string) ([]IP
// Make sure that no matter what we do later, host=="" is rejected. // Make sure that no matter what we do later, host=="" is rejected.
// parseIP, for example, does accept empty strings. // parseIP, for example, does accept empty strings.
if host == "" { if host == "" {
return nil, &DNSError{Err: errNoSuchHost.Error(), Name: host} return nil, &DNSError{Err: errNoSuchHost.Error(), Name: host, IsNotFound: true}
} }
if ip, zone := parseIPZone(host); ip != nil { if ip, zone := parseIPZone(host); ip != nil {
return []IPAddr{{IP: ip, Zone: zone}}, nil return []IPAddr{{IP: ip, Zone: zone}}, nil
......
...@@ -877,6 +877,9 @@ func TestLookupNonLDH(t *testing.T) { ...@@ -877,6 +877,9 @@ func TestLookupNonLDH(t *testing.T) {
if !strings.HasSuffix(err.Error(), errNoSuchHost.Error()) { if !strings.HasSuffix(err.Error(), errNoSuchHost.Error()) {
t.Fatalf("lookup error = %v, want %v", err, errNoSuchHost) t.Fatalf("lookup error = %v, want %v", err, errNoSuchHost)
} }
if !err.(*DNSError).IsNotFound {
t.Fatalf("lookup error = %v, want true", err.(*DNSError).IsNotFound)
}
} }
func TestLookupContextCancel(t *testing.T) { func TestLookupContextCancel(t *testing.T) {
......
...@@ -56,7 +56,12 @@ func lookupProtocol(ctx context.Context, name string) (int, error) { ...@@ -56,7 +56,12 @@ func lookupProtocol(ctx context.Context, name string) (int, error) {
if proto, err := lookupProtocolMap(name); err == nil { if proto, err := lookupProtocolMap(name); err == nil {
return proto, nil return proto, nil
} }
r.err = &DNSError{Err: r.err.Error(), Name: name}
dnsError := &DNSError{Err: r.err.Error(), Name: name}
if r.err == errNoSuchHost {
dnsError.IsNotFound = true
}
r.err = dnsError
} }
return r.proto, r.err return r.proto, r.err
case <-ctx.Done(): case <-ctx.Done():
...@@ -98,7 +103,12 @@ func (r *Resolver) lookupIP(ctx context.Context, network, name string) ([]IPAddr ...@@ -98,7 +103,12 @@ func (r *Resolver) lookupIP(ctx context.Context, network, name string) ([]IPAddr
var result *syscall.AddrinfoW var result *syscall.AddrinfoW
e := syscall.GetAddrInfoW(syscall.StringToUTF16Ptr(name), nil, &hints, &result) e := syscall.GetAddrInfoW(syscall.StringToUTF16Ptr(name), nil, &hints, &result)
if e != nil { if e != nil {
return nil, &DNSError{Err: winError("getaddrinfow", e).Error(), Name: name} err := winError("getaddrinfow", e)
dnsError := &DNSError{Err: err.Error(), Name: name}
if err == errNoSuchHost {
dnsError.IsNotFound = true
}
return nil, dnsError
} }
defer syscall.FreeAddrInfoW(result) defer syscall.FreeAddrInfoW(result)
addrs := make([]IPAddr, 0, 5) addrs := make([]IPAddr, 0, 5)
...@@ -176,7 +186,12 @@ func (r *Resolver) lookupPort(ctx context.Context, network, service string) (int ...@@ -176,7 +186,12 @@ func (r *Resolver) lookupPort(ctx context.Context, network, service string) (int
if port, err := lookupPortMap(network, service); err == nil { if port, err := lookupPortMap(network, service); err == nil {
return port, nil return port, nil
} }
return 0, &DNSError{Err: winError("getaddrinfow", e).Error(), Name: network + "/" + service} err := winError("getaddrinfow", e)
dnsError := &DNSError{Err: err.Error(), Name: network + "/" + service}
if err == errNoSuchHost {
dnsError.IsNotFound = true
}
return 0, dnsError
} }
defer syscall.FreeAddrInfoW(result) defer syscall.FreeAddrInfoW(result)
if result == nil { if result == nil {
......
...@@ -579,6 +579,7 @@ type DNSError struct { ...@@ -579,6 +579,7 @@ type DNSError struct {
Server string // server used Server string // server used
IsTimeout bool // if true, timed out; not all timeouts set this IsTimeout bool // if true, timed out; not all timeouts set this
IsTemporary bool // if true, error is temporary; not all errors set this IsTemporary bool // if true, error is temporary; not all errors set this
IsNotFound bool // if true, host could not be found
} }
func (e *DNSError) Error() string { func (e *DNSError) Error() string {
......
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