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
|
- 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).
|
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
|
- 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).
|
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
|
- 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
|
// liveQueryNameserver queries one nameserver, retrying while that
|
||||||
// nameserver fails to answer. NXDOMAIN and NODATA are answers and
|
// nameserver fails to answer. NXDOMAIN and NODATA are answers and
|
||||||
// are returned to the caller to assert on.
|
// 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(
|
func liveQueryNameserver(
|
||||||
t *testing.T,
|
t *testing.T,
|
||||||
r *resolver.Resolver,
|
r *resolver.Resolver,
|
||||||
nameserver string,
|
nameserver string,
|
||||||
hostname string,
|
hostname string,
|
||||||
types ...string,
|
|
||||||
) *resolver.NameserverResponse {
|
) *resolver.NameserverResponse {
|
||||||
t.Helper()
|
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
|
out = resp
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -171,7 +171,7 @@ func TestQueryNameserver_BasicA(t *testing.T) {
|
|||||||
|
|
||||||
r := newTestResolver(t)
|
r := newTestResolver(t)
|
||||||
ns := findOneNSForDomain(t, r, "google.com")
|
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)
|
require.NotNil(t, resp)
|
||||||
|
|
||||||
@@ -190,7 +190,7 @@ func TestQueryNameserver_AAAA(t *testing.T) {
|
|||||||
|
|
||||||
r := newTestResolver(t)
|
r := newTestResolver(t)
|
||||||
ns := findOneNSForDomain(t, r, "cloudflare.com")
|
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"]
|
aaaaRecords := resp.Records["AAAA"]
|
||||||
require.NotEmpty(t, aaaaRecords,
|
require.NotEmpty(t, aaaaRecords,
|
||||||
@@ -210,7 +210,7 @@ func TestQueryNameserver_MX(t *testing.T) {
|
|||||||
|
|
||||||
r := newTestResolver(t)
|
r := newTestResolver(t)
|
||||||
ns := findOneNSForDomain(t, r, "google.com")
|
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"]
|
mxRecords := resp.Records["MX"]
|
||||||
require.NotEmpty(t, mxRecords,
|
require.NotEmpty(t, mxRecords,
|
||||||
@@ -223,7 +223,7 @@ func TestQueryNameserver_TXT(t *testing.T) {
|
|||||||
|
|
||||||
r := newTestResolver(t)
|
r := newTestResolver(t)
|
||||||
ns := findOneNSForDomain(t, r, "google.com")
|
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"]
|
txtRecords := resp.Records["TXT"]
|
||||||
require.NotEmpty(t, txtRecords,
|
require.NotEmpty(t, txtRecords,
|
||||||
|
|||||||
@@ -47,45 +47,35 @@ func TestCNAMEIntoAnotherZonePortAndTLSChecks(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestCNAMEThatCannotBeFollowedKeepsPrevious runs a check of a name, not
|
// TestCNAMEThatCannotBeFollowedKeepsPrevious gives a name a CNAME to a
|
||||||
// the watcher's first, from the point where its records have been looked
|
// target under .invalid, whose lookup fails. The addresses the previous
|
||||||
// up: they hold a CNAME to a target under .invalid, whose lookup fails.
|
// check saved from following its CNAME are kept.
|
||||||
// The previous check found the same records, and oldIP at the end of the
|
|
||||||
// CNAME. The check must keep oldIP and send nothing.
|
|
||||||
func TestCNAMEThatCannotBeFollowedKeepsPrevious(t *testing.T) {
|
func TestCNAMEThatCannotBeFollowedKeepsPrevious(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
w, deps := newTestWatcher(t, defaultTestConfig(t))
|
w := watcher.NewForTest(
|
||||||
w.SetFirstRun(false)
|
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."),
|
nsA: cnameTo("target.example.invalid."),
|
||||||
}
|
})
|
||||||
|
prev := &state.HostnameState{CNAMEAddresses: []string{oldIP}}
|
||||||
prev := hostnameState(records)
|
|
||||||
prev.CNAMEAddresses = []string{oldIP}
|
|
||||||
deps.state.SetHostnameState(host, prev)
|
|
||||||
|
|
||||||
// The result is the same whether or not live DNS answers, so the
|
// The result is the same whether or not live DNS answers, so the
|
||||||
// lookup is not retried.
|
// lookup is not retried.
|
||||||
_ = livednstest.Run(func(ctx context.Context) error {
|
_ = livednstest.Run(func(ctx context.Context) error {
|
||||||
w.UpdateHostnameState(ctx, host, hostnameState(records))
|
w.ResolveCNAMEAddresses(ctx, host, current, prev)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
hs, _ := deps.state.GetHostnameState(host)
|
if !slices.Equal(current.CNAMEAddresses, prev.CNAMEAddresses) {
|
||||||
if !slices.Equal(hs.CNAMEAddresses, prev.CNAMEAddresses) {
|
|
||||||
t.Errorf(
|
t.Errorf(
|
||||||
"saved %v, want %v",
|
"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
|
// followLive follows in live DNS the CNAMEs in a name's records, built
|
||||||
|
|||||||
@@ -38,21 +38,6 @@ func NewlyDisagreeingPairs(
|
|||||||
return newlyDisagreeingPairs(prev, current)
|
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.
|
// DetectHostnameChanges exports detectHostnameChanges for testing.
|
||||||
func (w *Watcher) DetectHostnameChanges(
|
func (w *Watcher) DetectHostnameChanges(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
|
|||||||
@@ -379,19 +379,8 @@ func (w *Watcher) checkHostname(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
w.updateHostnameState(
|
newState := buildHostnameState(results, time.Now().UTC())
|
||||||
ctx, hostname, 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)
|
prev, hasPrev := w.state.GetHostnameState(hostname)
|
||||||
|
|
||||||
w.resolveCNAMEAddresses(ctx, hostname, newState, prev)
|
w.resolveCNAMEAddresses(ctx, hostname, newState, prev)
|
||||||
|
|||||||
Reference in New Issue
Block a user