diff --git a/apps/checker/checker/dns.go b/apps/checker/checker/dns.go index a3f2f2311..b005420e3 100644 --- a/apps/checker/checker/dns.go +++ b/apps/checker/checker/dns.go @@ -2,6 +2,7 @@ package checker import ( "context" + "errors" "fmt" "net" "strings" @@ -26,8 +27,10 @@ var resolver = net.DefaultResolver func Dns(ctx context.Context, host string) (*DnsResponse, error) { logger := log.Ctx(ctx).With().Str("monitor", host).Logger() + // A host can legitimately carry no address or CNAME (e.g. a TXT-only + // record), so a not-found answer is an empty set, not a failed check. ips, err := resolver.LookupIP(ctx, "ip", host) - if err != nil { + if err != nil && !isNotFound(err) { logger.Warn().Err(err).Msg("DNS IP lookup failed") return nil, fmt.Errorf("failed to lookup IPs: %w", err) } @@ -43,7 +46,7 @@ func Dns(ctx context.Context, host string) (*DnsResponse, error) { } } CNAME, err := lookupCNAME(ctx, host) - if err != nil { + if err != nil && !isNotFound(err) { logger.Warn().Err(err).Msg("DNS CNAME record lookup failed") return nil, fmt.Errorf("failed to lookup CNAME record: %w", err) } @@ -63,6 +66,11 @@ func Dns(ctx context.Context, host string) (*DnsResponse, error) { return nil, fmt.Errorf("DNS lookup for %s did not complete: %w", host, err) } + if len(A) == 0 && len(AAAA) == 0 && CNAME == "" && len(MXRecords) == 0 && len(NS) == 0 && len(TXT) == 0 { + logger.Warn().Msg("DNS lookup returned no records") + return nil, fmt.Errorf("no DNS records found for %s", host) + } + response := &DnsResponse{ A: A, AAAA: AAAA, @@ -138,6 +146,13 @@ func lookupTXT(ctx context.Context, domain string) []string { return records } +// isNotFound reports whether a lookup failed because the name has no records of +// the requested type, as opposed to a resolver or network failure. +func isNotFound(err error) bool { + var dnsErr *net.DNSError + return errors.As(err, &dnsErr) && dnsErr.IsNotFound +} + func isSubdomain(domain string) bool { parent := strings.Split(domain, ".") if len(parent) < 3 { diff --git a/apps/checker/checker/dns_test.go b/apps/checker/checker/dns_test.go index d8d34fca2..06a64e21c 100644 --- a/apps/checker/checker/dns_test.go +++ b/apps/checker/checker/dns_test.go @@ -74,3 +74,18 @@ func useBlackholeResolver(t *testing.T) { }, }) } + +// A TXT-only host has no address or CNAME; the check must still read its TXT +// record rather than fail on the empty IP lookup. +func TestDns_TXTOnlyHost(t *testing.T) { + data, err := checker.Dns(t.Context(), "health.onporter.run") + if err != nil { + t.Fatalf("Dns() error = %v", err) + } + if len(data.A) != 0 || len(data.AAAA) != 0 { + t.Errorf("Dns() expected no address records, got A=%v AAAA=%v", data.A, data.AAAA) + } + if len(data.TXT) == 0 { + t.Errorf("Dns() TXT records = %v", data.TXT) + } +}