From dc8160a44db7d201e5e473e7183320e6dad6c24d Mon Sep 17 00:00:00 2001 From: yohimik Date: Sun, 30 Aug 2026 15:36:26 +0400 Subject: [PATCH] net: report a name that does not exist as a not-found lookup error The resolver learns from an NXDOMAIN reply that the name does not exist, but both layers above it lost that fact. dnsLookup replaced the error with a generic "no address found", and the Resolver methods built the DNSError from the message alone. A caller that reads DNSError.IsNotFound, to tell "no such host" from "the resolver did not answer", thus never saw a not-found lookup. Carry the flag out through both layers. dnsLookup reports not-found when the A query and the AAAA query both said so. The Resolver methods copy IsNotFound, IsTimeout and IsTemporary from the DNSError of the netdev, keep it as the unwrap target, and take its description and not its Error(), which repeats the "lookup " prefix. --- lookup.go | 26 +++++++++++++++++++++++--- netdev_native.go | 18 +++++++++++++++--- 2 files changed, 38 insertions(+), 6 deletions(-) diff --git a/lookup.go b/lookup.go index 5dbe657..6d8ff84 100644 --- a/lookup.go +++ b/lookup.go @@ -59,7 +59,7 @@ func (r *Resolver) LookupHost(ctx context.Context, host string) (addrs []string, } ip, err := netdev.GetHostByName(host) if err != nil { - return nil, &DNSError{Err: err.Error(), Name: host} + return nil, newLookupError(host, err) } return []string{ip.String()}, nil } @@ -88,7 +88,7 @@ func (r *Resolver) LookupIPAddr(ctx context.Context, host string) ([]IPAddr, err } ip, err := netdev.GetHostByName(host) if err != nil { - return nil, &DNSError{Err: err.Error(), Name: host} + return nil, newLookupError(host, err) } return []IPAddr{{IP: ip.AsSlice(), Zone: ip.Zone()}}, nil } @@ -104,7 +104,7 @@ func (r *Resolver) LookupNetIP(ctx context.Context, network, host string) ([]net } ip, err := netdev.GetHostByName(host) if err != nil { - return nil, &DNSError{Err: err.Error(), Name: host} + return nil, newLookupError(host, err) } return []netip.Addr{ip}, nil } @@ -149,3 +149,23 @@ func (r *Resolver) LookupTXT(ctx context.Context, name string) ([]string, error) // errNoSuchHost is returned when the host lookup finds no matching records. var errNoSuchHost = errors.New("no such host") + +// newLookupError wraps a netdev resolver error in a DNSError for host. +// +// TINYGO: the netdev can report a DNSError of its own, and callers read +// IsNotFound and IsTimeout on the outer error, so carry those flags out. +func newLookupError(host string, err error) *DNSError { + dnsErr := &DNSError{Err: err.Error(), Name: host, UnwrapErr: err} + if inner, ok := err.(*DNSError); ok { + // Take the inner description and not its Error(), which repeats the + // "lookup " prefix that this error adds. + dnsErr.Err = inner.Err + dnsErr.IsNotFound = inner.IsNotFound + dnsErr.IsTimeout = inner.IsTimeout + dnsErr.IsTemporary = inner.IsTemporary + if inner.Server != "" { + dnsErr.Server = inner.Server + } + } + return dnsErr +} diff --git a/netdev_native.go b/netdev_native.go index e522a08..d303f16 100644 --- a/netdev_native.go +++ b/netdev_native.go @@ -379,13 +379,25 @@ const ( // dnsLookup resolves name by querying the system nameservers over UDP, // preferring an IPv4 (A) answer and falling back to IPv6 (AAAA). func dnsLookup(name string) (netip.Addr, error) { - if addr, err := dnsLookupType(name, dnsTypeA); err == nil { + addr, errA := dnsLookupType(name, dnsTypeA) + if errA == nil { return addr, nil } - if addr, err := dnsLookupType(name, dnsTypeAAAA); err == nil { + addr, errAAAA := dnsLookupType(name, dnsTypeAAAA) + if errAAAA == nil { return addr, nil } - return netip.Addr{}, &DNSError{Err: "no address found", Name: name} + // A name that the servers answered NXDOMAIN for is a different condition + // from a name that they did not answer at all. + notFound := isNotFoundErr(errA) && isNotFoundErr(errAAAA) + return netip.Addr{}, &DNSError{Err: "no address found", Name: name, IsNotFound: notFound} +} + +// isNotFoundErr reports whether err is a DNSError for a name or a record type +// that does not exist. +func isNotFoundErr(err error) bool { + dnsErr, ok := err.(*DNSError) + return ok && dnsErr.IsNotFound } // dnsLookupType resolves name for a single DNS record type (A or AAAA).