watcher: keep port state when no nameserver of a name answered (closes #193)
check / check (push) Successful in 1m47s
check / check (push) Successful in 1m47s
The port check removed the saved port state of every address no configured name resolves to. A name whose nameservers all timed out or failed is saved with no records, so its addresses looked gone and lost their port state; when the nameservers answered again it was recorded afresh, and a port that opened or closed meanwhile was not notified. An entry is now kept when one of the names saved on it is configured and none of its nameservers answered on its last check, and such a name stays on the entry when the port is checked again for another name. A name whose nameservers answer with no addresses still loses it, and so does a name no longer configured. Model: opus-5-5
This commit is contained in:
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"slices"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -709,15 +710,46 @@ func parsePortKey(key string) (string, int) {
|
||||
}
|
||||
|
||||
// cleanupStalePorts removes port state entries that are no
|
||||
// longer referenced by any hostname in the current DNS data.
|
||||
// longer referenced by any hostname in the current DNS data. An
|
||||
// entry saved for a configured name none of whose nameservers
|
||||
// answered is kept: that name's addresses are not known, not gone.
|
||||
func (w *Watcher) cleanupStalePorts(
|
||||
currentAssociations map[string][]string,
|
||||
) {
|
||||
for _, key := range w.state.GetAllPortKeys() {
|
||||
if _, exists := currentAssociations[key]; !exists {
|
||||
w.state.DeletePortState(key)
|
||||
if _, exists := currentAssociations[key]; exists {
|
||||
continue
|
||||
}
|
||||
|
||||
ps, ok := w.state.GetPortState(key)
|
||||
if ok && slices.ContainsFunc(ps.Hostnames, w.noNameserverAnswered) {
|
||||
continue
|
||||
}
|
||||
|
||||
w.state.DeletePortState(key)
|
||||
}
|
||||
}
|
||||
|
||||
// noNameserverAnswered reports whether name is a configured domain or
|
||||
// hostname and none of its nameservers answered on its last check.
|
||||
func (w *Watcher) noNameserverAnswered(name string) bool {
|
||||
if !slices.Contains(w.config.Hostnames, name) &&
|
||||
!slices.Contains(w.config.Domains, name) {
|
||||
return false
|
||||
}
|
||||
|
||||
hs, ok := w.state.GetHostnameState(name)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, nsState := range hs.RecordsByNameserver {
|
||||
if nsState.Status == statusOK {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (w *Watcher) collectIPs(hostname string) []string {
|
||||
@@ -796,9 +828,24 @@ func (w *Watcher) checkSinglePort(
|
||||
)
|
||||
}
|
||||
|
||||
// A configured name on the saved list none of whose nameservers
|
||||
// answered stays on it, so the entry is kept when the other names
|
||||
// stop resolving to this address.
|
||||
savedHostnames := slices.Clone(hostnames)
|
||||
|
||||
if hasPrev {
|
||||
for _, name := range prev.Hostnames {
|
||||
if !slices.Contains(hostnames, name) && w.noNameserverAnswered(name) {
|
||||
savedHostnames = append(savedHostnames, name)
|
||||
}
|
||||
}
|
||||
|
||||
sort.Strings(savedHostnames)
|
||||
}
|
||||
|
||||
w.state.SetPortState(key, &state.PortState{
|
||||
Open: result.Open,
|
||||
Hostnames: hostnames,
|
||||
Hostnames: savedHostnames,
|
||||
LastChecked: now,
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user