watcher: a removed nameserver is an NS Change, not an NS Failure (closes #262)
check / check (push) Canceled after 0s
check / check (push) Canceled after 0s
A nameserver that left a domain's delegation was reported twice: as the domain's NS Change, and by the check of the domain's own records as an error-level NS Failure saying it disappeared. The check of a name's records now sends one NS Change at warning level, starting Hostname: or Domain:, listing the nameservers it was asked at before and not now. For a domain it leaves out those of its saved delegation, which the domain's own NS Change lists, so a domain's new delegation is now notified and saved only after, and only together with, its records. Nothing is sent for a nameserver that joins. A nameserver that was failing before it left is now listed too; the NS Failure skipped it. Model: opus-5-5
This commit was merged in pull request #273.
This commit is contained in:
+60
-30
@@ -322,6 +322,20 @@ func (w *Watcher) checkDomain(
|
||||
ctx, nameservers, prevAddresses,
|
||||
)
|
||||
|
||||
// A domain that does not exist has no records of its own: none are
|
||||
// asked for, and those saved by an earlier check are removed.
|
||||
// Otherwise the apex domain's records are also checked and saved as
|
||||
// a hostname's, so that the port and TLS checks find its addresses.
|
||||
// Notifications about them name it as a domain (see nameLine). When
|
||||
// their lookup fails, the check fails as when the NS lookup does, so
|
||||
// the saved delegation and the saved records come from the same
|
||||
// check, as detectNSDisappearances needs.
|
||||
if nxdomain {
|
||||
w.state.DeleteHostnameState(domain)
|
||||
} else if !w.checkHostname(ctx, domain) {
|
||||
return
|
||||
}
|
||||
|
||||
if hasPrev && !w.firstRun {
|
||||
w.detectNSChanges(ctx, domain, prev.Nameservers, nameservers)
|
||||
w.detectNSAddressChanges(ctx, domain, prevAddresses, addresses)
|
||||
@@ -333,19 +347,6 @@ func (w *Watcher) checkDomain(
|
||||
NXDomain: nxdomain,
|
||||
LastChecked: now,
|
||||
})
|
||||
|
||||
// A domain that does not exist has no records of its own: none are
|
||||
// asked for, and those saved by an earlier check are removed.
|
||||
if nxdomain {
|
||||
w.state.DeleteHostnameState(domain)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// The apex domain's records are also checked and saved as a
|
||||
// hostname's, so that the port and TLS checks find its addresses.
|
||||
// Notifications about them name it as a domain (see nameLine).
|
||||
w.checkHostname(ctx, domain)
|
||||
}
|
||||
|
||||
func (w *Watcher) detectNSChanges(
|
||||
@@ -456,10 +457,12 @@ func (w *Watcher) detectNSAddressChanges(
|
||||
}
|
||||
}
|
||||
|
||||
// checkHostname checks hostname's records and reports whether it saved
|
||||
// them; it does not when their lookup fails.
|
||||
func (w *Watcher) checkHostname(
|
||||
ctx context.Context,
|
||||
hostname string,
|
||||
) {
|
||||
) bool {
|
||||
results, err := w.resolver.LookupAllRecords(ctx, hostname)
|
||||
if err != nil {
|
||||
w.logFailedLookup(
|
||||
@@ -469,7 +472,7 @@ func (w *Watcher) checkHostname(
|
||||
"error", err,
|
||||
)
|
||||
|
||||
return
|
||||
return false
|
||||
}
|
||||
|
||||
prev, _ := w.state.GetHostnameState(hostname)
|
||||
@@ -477,6 +480,8 @@ func (w *Watcher) checkHostname(
|
||||
w.updateHostnameState(
|
||||
ctx, hostname, buildHostnameState(results, prev, time.Now().UTC()),
|
||||
)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// updateHostnameState finishes a check of hostname from newState, built
|
||||
@@ -772,28 +777,53 @@ func (w *Watcher) detectRecordChanges(
|
||||
}
|
||||
}
|
||||
|
||||
// detectNSDisappearances sends one NS Change listing the nameservers of
|
||||
// the previous check that are no longer those of the zone hostname is
|
||||
// in. For a configured apex domain it leaves out those of the domain's
|
||||
// saved delegation, which detectNSChanges notifies when they are removed
|
||||
// from it. checkDomain saves a new delegation only after this runs, and
|
||||
// only together with the records, so the saved delegation and the
|
||||
// records in prev come from the same check.
|
||||
func (w *Watcher) detectNSDisappearances(
|
||||
ctx context.Context,
|
||||
hostname string,
|
||||
prev, current *state.HostnameState,
|
||||
) {
|
||||
for ns, prevNS := range prev.RecordsByNameserver {
|
||||
if _, ok := current.RecordsByNameserver[ns]; ok || prevNS.Status != statusOK {
|
||||
continue
|
||||
var delegation []string
|
||||
|
||||
if w.isDomain(hostname) {
|
||||
if domainState, ok := w.state.GetDomainState(hostname); ok {
|
||||
delegation = domainState.Nameservers
|
||||
}
|
||||
|
||||
msg := fmt.Sprintf(
|
||||
"%s\nNameserver: %s disappeared",
|
||||
w.nameLine(hostname), ns,
|
||||
)
|
||||
|
||||
w.notify.SendNotification(
|
||||
ctx,
|
||||
"NS Failure: "+hostname,
|
||||
msg,
|
||||
"error",
|
||||
)
|
||||
}
|
||||
|
||||
var removed []string
|
||||
|
||||
for ns := range prev.RecordsByNameserver {
|
||||
_, ok := current.RecordsByNameserver[ns]
|
||||
if !ok && !slices.Contains(delegation, ns) {
|
||||
removed = append(removed, ns)
|
||||
}
|
||||
}
|
||||
|
||||
if len(removed) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
slices.Sort(removed)
|
||||
|
||||
msg := fmt.Sprintf(
|
||||
"%s\nRemoved: %s",
|
||||
w.nameLine(hostname),
|
||||
strings.Join(removed, ", "),
|
||||
)
|
||||
|
||||
w.notify.SendNotification(
|
||||
ctx,
|
||||
"NS Change: "+hostname,
|
||||
msg,
|
||||
"warning",
|
||||
)
|
||||
}
|
||||
|
||||
// detectNSFailures notifies when a nameserver that answered on the
|
||||
|
||||
Reference in New Issue
Block a user