diff --git a/TODO.md b/TODO.md index dbc8924..85263d0 100644 --- a/TODO.md +++ b/TODO.md @@ -19,6 +19,8 @@ trial run of the finished image: https://git.eeqj.de/sneak/dnswatcher/issues/149 # Completed Steps +- 2026-10-02: the refused-query test sends one query to four operators' public + resolvers in turn until one replies, not eight to one operator (closes #251). - 2026-10-02: a name removed from `DNSWATCHER_TARGETS` leaves the state, and so the dashboard and API, at startup, before the first check (closes #223). - 2026-10-02: a record type whose query to a nameserver fails keeps its previous diff --git a/internal/resolver/resolver_test.go b/internal/resolver/resolver_test.go index 94a2fa0..9399a1e 100644 --- a/internal/resolver/resolver_test.go +++ b/internal/resolver/resolver_test.go @@ -442,48 +442,45 @@ func TestQueryNameserver_Refused(t *testing.T) { assert.Equal(t, "server returned REFUSED", resp.Error) } -// TestQueryNameserverIP_RecursiveResolverRefused asks Quad9, a public -// recursive resolver, about google.com at both of its addresses. Quad9 -// refuses a query that does not ask for recursion and answers one that -// does. The resolver never asks for recursion, so it must be reported -// as refusing, never as answering. -func TestQueryNameserverIP_RecursiveResolverRefused(t *testing.T) { +// TestQueryServers_RecursiveResolverRefused passes a public recursive +// resolver to QueryServers as the server of google.com. These resolvers +// refuse a query that does not ask for recursion and answer one that +// does. The resolver never asks for recursion, so the query must be +// reported as refused, never answered. Each resolver is run by a +// different operator, and they are asked in turn until one replies, so +// one operator not answering does not fail the test. +func TestQueryServers_RecursiveResolverRefused(t *testing.T) { t.Parallel() r := newTestResolver(t) - - for _, ip := range []string{"9.9.9.9", "149.112.112.112"} { - var resp *resolver.NameserverResponse - - livednstest.Retry( - t, - "QueryNameserverIP("+ip+", google.com)", - func(ctx context.Context) error { - var err error - - resp, err = r.QueryNameserverIP( - ctx, ip, ip, "google.com", - ) - if err != nil { - return err - } - - // A timeout or a network error is no reply at all. - if resp.Status == resolver.StatusTimeout || - strings.HasPrefix(resp.Error, "network error") { - return fmt.Errorf( - "%w: %s: %s", - livednstest.ErrNoAnswer, ip, resp.Error, - ) - } - - return nil - }, - ) - - assert.Equal(t, resolver.StatusError, resp.Status, ip) - assert.Equal(t, "server returned REFUSED", resp.Error, ip) + resolvers := []string{ + "64.6.64.6", "185.222.222.222", "4.2.2.1", "9.9.9.9", } + + var err error + + livednstest.Retry( + t, + "QueryServers(public recursive resolvers, google.com)", + func(ctx context.Context) error { + for _, ip := range resolvers { + _, err = r.QueryServers( + ctx, []string{ip}, "google.com.", "google.com.", + dns.TypeA, + ) + + // A refusal or an answer is a reply; anything else may + // be no reply at all, so the next resolver is asked. + if err == nil || errors.Is(err, resolver.ErrRefused) { + return nil + } + } + + return fmt.Errorf("%w: %w", livednstest.ErrNoAnswer, err) + }, + ) + + require.ErrorIs(t, err, resolver.ErrRefused) } // googleNameserverIPv4s returns the IPv4 addresses of google.com's