diff --git a/cmd/cli/cli.go b/cmd/cli/cli.go index 08aa97c..b2cd3b9 100644 --- a/cmd/cli/cli.go +++ b/cmd/cli/cli.go @@ -369,7 +369,7 @@ func initCLI() { } // If ctrld service is running but selfCheckStatus failed, it could be related // to user's system firewall configuration, notice users about it. - if status == service.StatusRunning { + if status == service.StatusRunning && err == nil { _, _ = mainLog.Load().Write(marker) mainLog.Load().Write([]byte(`ctrld service was running, but a DNS query could not be sent to its listener`)) mainLog.Load().Write([]byte(`Please check your system firewall if it is configured to block/intercept/redirect DNS queries`)) @@ -1705,6 +1705,12 @@ func selfCheckStatus(s service.Service) (bool, service.Status, error) { bo.BackOff(ctx, fmt.Errorf("ExchangeContext: %w", exErr)) } mainLog.Load().Debug().Msgf("self-check against %q failed", domain) + // Ping all upstreams to provide better error message to users. + for name, uc := range cfg.Upstream { + if err := uc.ErrorPing(); err != nil { + mainLog.Load().Err(err).Msgf("failed to connect to upstream.%s, endpoint: %s", name, uc.Endpoint) + } + } lc := cfg.FirstListener() addr := net.JoinHostPort(lc.IP, strconv.Itoa(lc.Port)) marker := strings.Repeat("=", 32) diff --git a/cmd/cli/dns_proxy.go b/cmd/cli/dns_proxy.go index 790a993..0477320 100644 --- a/cmd/cli/dns_proxy.go +++ b/cmd/cli/dns_proxy.go @@ -962,6 +962,7 @@ func isWanClient(na net.Addr) bool { // resolveInternalDomainTestQuery resolves internal test domain query, returning the answer to the caller. func resolveInternalDomainTestQuery(ctx context.Context, domain string, m *dns.Msg) *dns.Msg { ctrld.Log(ctx, mainLog.Load().Debug(), "internal domain test query") + q := m.Question[0] answer := new(dns.Msg) rrStr := fmt.Sprintf("%s A %s", domain, net.IPv4zero) diff --git a/config.go b/config.go index 202f105..8c99a8e 100644 --- a/config.go +++ b/config.go @@ -521,35 +521,55 @@ func (uc *UpstreamConfig) newDOHTransport(addrs []string) *http.Transport { // Ping warms up the connection to DoH/DoH3 upstream. func (uc *UpstreamConfig) Ping() { + _ = uc.ping() +} + +// ErrorPing is like Ping, but return an error if any. +func (uc *UpstreamConfig) ErrorPing() error { + return uc.ping() +} + +func (uc *UpstreamConfig) ping() error { switch uc.Type { case ResolverTypeDOH, ResolverTypeDOH3: default: - return + return nil } - ping := func(t http.RoundTripper) { + ping := func(t http.RoundTripper) error { if t == nil { - return + return nil } ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) defer cancel() - req, _ := http.NewRequestWithContext(ctx, "HEAD", uc.Endpoint, nil) - resp, _ := t.RoundTrip(req) - if resp == nil { - return + req, err := http.NewRequestWithContext(ctx, "HEAD", uc.Endpoint, nil) + if err != nil { + return err + } + resp, err := t.RoundTrip(req) + if err != nil { + return err } defer resp.Body.Close() _, _ = io.Copy(io.Discard, resp.Body) + return nil } for _, typ := range []uint16{dns.TypeA, dns.TypeAAAA} { switch uc.Type { case ResolverTypeDOH: - ping(uc.dohTransport(typ)) + + if err := ping(uc.dohTransport(typ)); err != nil { + return err + } case ResolverTypeDOH3: - ping(uc.doh3Transport(typ)) + if err := ping(uc.doh3Transport(typ)); err != nil { + return err + } } } + + return nil } func (uc *UpstreamConfig) isControlD() bool {