From d2f7d59fce9125d6f32f2173f7ef8bb2c6a6ec27 Mon Sep 17 00:00:00 2001
From: Mechiel Lukkien <mechiel@ueber.net>
Date: Mon, 12 Jun 2023 14:45:29 +0200
Subject: [PATCH] for dns resolve errors likely due to a missing name server in
 /etc/resolv.conf, point user to man page of systemd-resolved, the likely
 cause

it seems linux machines with systemd-resolved don't always set up
/etc/resolv.conf correctly. there may be no "nameserver" entry, causing Go's
net resolver to fallback to 127.0.0.1 and ::1. Systemd-resolved is listening on
127.0.0.53, so users will likely get a "connection refused". So point users to
the systemd-resolved manual page.

for issue #38 by ArnoSen
---
 dns/resolver.go | 28 ++++++++++++++++++++++++++++
 1 file changed, 28 insertions(+)

diff --git a/dns/resolver.go b/dns/resolver.go
index ac04341..0a61311 100644
--- a/dns/resolver.go
+++ b/dns/resolver.go
@@ -3,8 +3,10 @@ package dns
 import (
 	"context"
 	"errors"
+	"fmt"
 	"net"
 	"os"
+	"runtime"
 	"strings"
 	"time"
 
@@ -104,12 +106,29 @@ func (r StrictResolver) resolver() Resolver {
 	return r.Resolver
 }
 
+func resolveErrorHint(err *error) {
+	e := *err
+	if e == nil {
+		return
+	}
+	dnserr, ok := e.(*net.DNSError)
+	if !ok {
+		return
+	}
+	// If the dns server is not running, and it is one of the default/fallback IPs,
+	// hint at where to look.
+	if dnserr.IsTemporary && runtime.GOOS == "linux" && (dnserr.Server == "127.0.0.1:53" || dnserr.Server == "[::1]:53") && strings.HasSuffix(dnserr.Err, "connection refused") {
+		*err = fmt.Errorf("%w (hint: does /etc/resolv.conf point to a running nameserver? in case of systemd-resolved, see systemd-resolved.service(8))", *err)
+	}
+}
+
 func (r StrictResolver) LookupAddr(ctx context.Context, addr string) (resp []string, err error) {
 	start := time.Now()
 	defer func() {
 		metricLookupObserve(r.Pkg, "addr", err, start)
 		xlog.WithContext(ctx).Debugx("dns lookup result", err, mlog.Field("pkg", r.Pkg), mlog.Field("type", "addr"), mlog.Field("addr", addr), mlog.Field("resp", resp), mlog.Field("duration", time.Since(start)))
 	}()
+	defer resolveErrorHint(&err)
 
 	resp, err = r.resolver().LookupAddr(ctx, addr)
 	return
@@ -123,6 +142,7 @@ func (r StrictResolver) LookupCNAME(ctx context.Context, host string) (resp stri
 		metricLookupObserve(r.Pkg, "cname", err, start)
 		xlog.WithContext(ctx).Debugx("dns lookup result", err, mlog.Field("pkg", r.Pkg), mlog.Field("type", "cname"), mlog.Field("host", host), mlog.Field("resp", resp), mlog.Field("duration", time.Since(start)))
 	}()
+	defer resolveErrorHint(&err)
 
 	if !strings.HasSuffix(host, ".") {
 		return "", ErrRelativeDNSName
@@ -144,6 +164,7 @@ func (r StrictResolver) LookupHost(ctx context.Context, host string) (resp []str
 		metricLookupObserve(r.Pkg, "host", err, start)
 		xlog.WithContext(ctx).Debugx("dns lookup result", err, mlog.Field("pkg", r.Pkg), mlog.Field("type", "host"), mlog.Field("host", host), mlog.Field("resp", resp), mlog.Field("duration", time.Since(start)))
 	}()
+	defer resolveErrorHint(&err)
 
 	if !strings.HasSuffix(host, ".") {
 		return nil, ErrRelativeDNSName
@@ -158,6 +179,7 @@ func (r StrictResolver) LookupIP(ctx context.Context, network, host string) (res
 		metricLookupObserve(r.Pkg, "ip", err, start)
 		xlog.WithContext(ctx).Debugx("dns lookup result", err, mlog.Field("pkg", r.Pkg), mlog.Field("type", "ip"), mlog.Field("network", network), mlog.Field("host", host), mlog.Field("resp", resp), mlog.Field("duration", time.Since(start)))
 	}()
+	defer resolveErrorHint(&err)
 
 	if !strings.HasSuffix(host, ".") {
 		return nil, ErrRelativeDNSName
@@ -172,6 +194,7 @@ func (r StrictResolver) LookupIPAddr(ctx context.Context, host string) (resp []n
 		metricLookupObserve(r.Pkg, "ipaddr", err, start)
 		xlog.WithContext(ctx).Debugx("dns lookup result", err, mlog.Field("pkg", r.Pkg), mlog.Field("type", "ipaddr"), mlog.Field("host", host), mlog.Field("resp", resp), mlog.Field("duration", time.Since(start)))
 	}()
+	defer resolveErrorHint(&err)
 
 	if !strings.HasSuffix(host, ".") {
 		return nil, ErrRelativeDNSName
@@ -186,6 +209,7 @@ func (r StrictResolver) LookupMX(ctx context.Context, name string) (resp []*net.
 		metricLookupObserve(r.Pkg, "mx", err, start)
 		xlog.WithContext(ctx).Debugx("dns lookup result", err, mlog.Field("pkg", r.Pkg), mlog.Field("type", "mx"), mlog.Field("name", name), mlog.Field("resp", resp), mlog.Field("duration", time.Since(start)))
 	}()
+	defer resolveErrorHint(&err)
 
 	if !strings.HasSuffix(name, ".") {
 		return nil, ErrRelativeDNSName
@@ -200,6 +224,7 @@ func (r StrictResolver) LookupNS(ctx context.Context, name string) (resp []*net.
 		metricLookupObserve(r.Pkg, "ns", err, start)
 		xlog.WithContext(ctx).Debugx("dns lookup result", err, mlog.Field("pkg", r.Pkg), mlog.Field("type", "ns"), mlog.Field("name", name), mlog.Field("resp", resp), mlog.Field("duration", time.Since(start)))
 	}()
+	defer resolveErrorHint(&err)
 
 	if !strings.HasSuffix(name, ".") {
 		return nil, ErrRelativeDNSName
@@ -214,6 +239,7 @@ func (r StrictResolver) LookupPort(ctx context.Context, network, service string)
 		metricLookupObserve(r.Pkg, "port", err, start)
 		xlog.WithContext(ctx).Debugx("dns lookup result", err, mlog.Field("pkg", r.Pkg), mlog.Field("type", "port"), mlog.Field("network", network), mlog.Field("service", service), mlog.Field("resp", resp), mlog.Field("duration", time.Since(start)))
 	}()
+	defer resolveErrorHint(&err)
 
 	resp, err = r.resolver().LookupPort(ctx, network, service)
 	return
@@ -225,6 +251,7 @@ func (r StrictResolver) LookupSRV(ctx context.Context, service, proto, name stri
 		metricLookupObserve(r.Pkg, "srv", err, start)
 		xlog.WithContext(ctx).Debugx("dns lookup result", err, mlog.Field("pkg", r.Pkg), mlog.Field("type", "srv"), mlog.Field("service", service), mlog.Field("proto", proto), mlog.Field("name", name), mlog.Field("resp0", resp0), mlog.Field("resp1", resp1), mlog.Field("duration", time.Since(start)))
 	}()
+	defer resolveErrorHint(&err)
 
 	if !strings.HasSuffix(name, ".") {
 		return "", nil, ErrRelativeDNSName
@@ -239,6 +266,7 @@ func (r StrictResolver) LookupTXT(ctx context.Context, name string) (resp []stri
 		metricLookupObserve(r.Pkg, "txt", err, start)
 		xlog.WithContext(ctx).Debugx("dns lookup result", err, mlog.Field("pkg", r.Pkg), mlog.Field("type", "txt"), mlog.Field("name", name), mlog.Field("resp", resp), mlog.Field("duration", time.Since(start)))
 	}()
+	defer resolveErrorHint(&err)
 
 	if !strings.HasSuffix(name, ".") {
 		return nil, ErrRelativeDNSName