Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3baead677c |
@@ -21,8 +21,6 @@ trial run of the finished image: https://git.eeqj.de/sneak/dnswatcher/issues/149
|
||||
|
||||
- 2026-10-02: a watched name whose nameservers answer with a CNAME and no
|
||||
address gets port and TLS checks at the end of its CNAME chain (closes #203).
|
||||
- 2026-10-02: a resolver test that reads one record type from a nameserver's
|
||||
answer asks again when that type is missing from it (closes #218).
|
||||
- 2026-10-02: a plain `docker build .` of a clone stamps its tag or short
|
||||
commit, not `dev`: the build context now carries `.git` (closes #210).
|
||||
- 2026-10-02: a query a server refuses is not resent asking for recursion, and
|
||||
|
||||
@@ -226,17 +226,11 @@ func liveLookupNS(
|
||||
// liveQueryNameserver queries one nameserver, retrying while that
|
||||
// nameserver fails to answer. NXDOMAIN and NODATA are answers and
|
||||
// are returned to the caller to assert on.
|
||||
//
|
||||
// QueryNameserver sends one query per record type, so one lost query
|
||||
// leaves its type out of an answer that is otherwise fine. A test names
|
||||
// in types the record types it reads; an answer holding records of none
|
||||
// of them is retried too.
|
||||
func liveQueryNameserver(
|
||||
t *testing.T,
|
||||
r *resolver.Resolver,
|
||||
nameserver string,
|
||||
hostname string,
|
||||
types ...string,
|
||||
) *resolver.NameserverResponse {
|
||||
t.Helper()
|
||||
|
||||
@@ -266,18 +260,6 @@ func liveQueryNameserver(
|
||||
)
|
||||
}
|
||||
|
||||
hasRecords := func(recordType string) bool {
|
||||
return len(resp.Records[recordType]) > 0
|
||||
}
|
||||
|
||||
if len(types) > 0 && !slices.ContainsFunc(types, hasRecords) {
|
||||
return fmt.Errorf(
|
||||
"%w: %s returned no %s records",
|
||||
livednstest.ErrNoAnswer, nameserver,
|
||||
strings.Join(types, " or "),
|
||||
)
|
||||
}
|
||||
|
||||
out = resp
|
||||
|
||||
return nil
|
||||
|
||||
@@ -171,7 +171,7 @@ func TestQueryNameserver_BasicA(t *testing.T) {
|
||||
|
||||
r := newTestResolver(t)
|
||||
ns := findOneNSForDomain(t, r, "google.com")
|
||||
resp := liveQueryNameserver(t, r, ns, "www.google.com", "A", "CNAME")
|
||||
resp := liveQueryNameserver(t, r, ns, "www.google.com")
|
||||
|
||||
require.NotNil(t, resp)
|
||||
|
||||
@@ -190,7 +190,7 @@ func TestQueryNameserver_AAAA(t *testing.T) {
|
||||
|
||||
r := newTestResolver(t)
|
||||
ns := findOneNSForDomain(t, r, "cloudflare.com")
|
||||
resp := liveQueryNameserver(t, r, ns, "cloudflare.com", "AAAA")
|
||||
resp := liveQueryNameserver(t, r, ns, "cloudflare.com")
|
||||
|
||||
aaaaRecords := resp.Records["AAAA"]
|
||||
require.NotEmpty(t, aaaaRecords,
|
||||
@@ -210,7 +210,7 @@ func TestQueryNameserver_MX(t *testing.T) {
|
||||
|
||||
r := newTestResolver(t)
|
||||
ns := findOneNSForDomain(t, r, "google.com")
|
||||
resp := liveQueryNameserver(t, r, ns, "google.com", "MX")
|
||||
resp := liveQueryNameserver(t, r, ns, "google.com")
|
||||
|
||||
mxRecords := resp.Records["MX"]
|
||||
require.NotEmpty(t, mxRecords,
|
||||
@@ -223,7 +223,7 @@ func TestQueryNameserver_TXT(t *testing.T) {
|
||||
|
||||
r := newTestResolver(t)
|
||||
ns := findOneNSForDomain(t, r, "google.com")
|
||||
resp := liveQueryNameserver(t, r, ns, "google.com", "TXT")
|
||||
resp := liveQueryNameserver(t, r, ns, "google.com")
|
||||
|
||||
txtRecords := resp.Records["TXT"]
|
||||
require.NotEmpty(t, txtRecords,
|
||||
|
||||
@@ -47,45 +47,35 @@ func TestCNAMEIntoAnotherZonePortAndTLSChecks(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestCNAMEThatCannotBeFollowedKeepsPrevious runs a check of a name, not
|
||||
// the watcher's first, from the point where its records have been looked
|
||||
// up: they hold a CNAME to a target under .invalid, whose lookup fails.
|
||||
// The previous check found the same records, and oldIP at the end of the
|
||||
// CNAME. The check must keep oldIP and send nothing.
|
||||
// TestCNAMEThatCannotBeFollowedKeepsPrevious gives a name a CNAME to a
|
||||
// target under .invalid, whose lookup fails. The addresses the previous
|
||||
// check saved from following its CNAME are kept.
|
||||
func TestCNAMEThatCannotBeFollowedKeepsPrevious(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
w, deps := newTestWatcher(t, defaultTestConfig(t))
|
||||
w.SetFirstRun(false)
|
||||
w := watcher.NewForTest(
|
||||
nil, nil, resolver.NewFromLogger(slog.Default()), nil, nil, nil,
|
||||
)
|
||||
|
||||
records := map[string]map[string][]string{
|
||||
current := hostnameState(map[string]map[string][]string{
|
||||
nsA: cnameTo("target.example.invalid."),
|
||||
}
|
||||
|
||||
prev := hostnameState(records)
|
||||
prev.CNAMEAddresses = []string{oldIP}
|
||||
deps.state.SetHostnameState(host, prev)
|
||||
})
|
||||
prev := &state.HostnameState{CNAMEAddresses: []string{oldIP}}
|
||||
|
||||
// The result is the same whether or not live DNS answers, so the
|
||||
// lookup is not retried.
|
||||
_ = livednstest.Run(func(ctx context.Context) error {
|
||||
w.UpdateHostnameState(ctx, host, hostnameState(records))
|
||||
w.ResolveCNAMEAddresses(ctx, host, current, prev)
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
hs, _ := deps.state.GetHostnameState(host)
|
||||
if !slices.Equal(hs.CNAMEAddresses, prev.CNAMEAddresses) {
|
||||
if !slices.Equal(current.CNAMEAddresses, prev.CNAMEAddresses) {
|
||||
t.Errorf(
|
||||
"saved %v, want %v",
|
||||
hs.CNAMEAddresses, prev.CNAMEAddresses,
|
||||
current.CNAMEAddresses, prev.CNAMEAddresses,
|
||||
)
|
||||
}
|
||||
|
||||
notifications := deps.notifier.getNotifications()
|
||||
if len(notifications) != 0 {
|
||||
t.Errorf("sent %v, want no notifications", notifications)
|
||||
}
|
||||
}
|
||||
|
||||
// followLive follows in live DNS the CNAMEs in a name's records, built
|
||||
|
||||
@@ -38,21 +38,6 @@ func NewlyDisagreeingPairs(
|
||||
return newlyDisagreeingPairs(prev, current)
|
||||
}
|
||||
|
||||
// SetFirstRun sets whether the watcher is on its first check, in which
|
||||
// nothing is compared with the previous check. NewForTest's watcher is.
|
||||
func (w *Watcher) SetFirstRun(firstRun bool) {
|
||||
w.firstRun = firstRun
|
||||
}
|
||||
|
||||
// UpdateHostnameState exports updateHostnameState for testing.
|
||||
func (w *Watcher) UpdateHostnameState(
|
||||
ctx context.Context,
|
||||
hostname string,
|
||||
newState *state.HostnameState,
|
||||
) {
|
||||
w.updateHostnameState(ctx, hostname, newState)
|
||||
}
|
||||
|
||||
// DetectHostnameChanges exports detectHostnameChanges for testing.
|
||||
func (w *Watcher) DetectHostnameChanges(
|
||||
ctx context.Context,
|
||||
|
||||
@@ -379,19 +379,8 @@ func (w *Watcher) checkHostname(
|
||||
return
|
||||
}
|
||||
|
||||
w.updateHostnameState(
|
||||
ctx, hostname, buildHostnameState(results, time.Now().UTC()),
|
||||
)
|
||||
}
|
||||
newState := buildHostnameState(results, time.Now().UTC())
|
||||
|
||||
// updateHostnameState finishes a check of hostname from newState, built
|
||||
// from its nameservers' answers: it follows the CNAME in them, notifies
|
||||
// what changed since the previous check, and saves newState.
|
||||
func (w *Watcher) updateHostnameState(
|
||||
ctx context.Context,
|
||||
hostname string,
|
||||
newState *state.HostnameState,
|
||||
) {
|
||||
prev, hasPrev := w.state.GetHostnameState(hostname)
|
||||
|
||||
w.resolveCNAMEAddresses(ctx, hostname, newState, prev)
|
||||
|
||||
Reference in New Issue
Block a user