Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8fd2fadbd8 |
@@ -76,10 +76,10 @@ rejected.
|
|||||||
responding again.
|
responding again.
|
||||||
- **Inconsistency detected**: Two nameservers that agreed on the
|
- **Inconsistency detected**: Two nameservers that agreed on the
|
||||||
previous check now return different record sets for the same
|
previous check now return different record sets for the same
|
||||||
hostname. Every pair of nameservers is compared. The alert is sent
|
hostname. This is sent once, on the check where they start to
|
||||||
once for each such pair, on the check where they start to disagree,
|
disagree, and not again while they keep disagreeing, including
|
||||||
and not again while they keep disagreeing, including after a restart.
|
after a restart. If they agree again and later disagree, it is
|
||||||
If they agree again and later disagree, it is sent again.
|
sent again.
|
||||||
|
|
||||||
### TCP Port Monitoring
|
### TCP Port Monitoring
|
||||||
|
|
||||||
|
|||||||
@@ -25,8 +25,7 @@ Rationale, Design, TODO, License, Author) if any are still missing.
|
|||||||
|
|
||||||
- 2026-09-28: the inconsistency alert is sent once, on the check where two
|
- 2026-09-28: the inconsistency alert is sent once, on the check where two
|
||||||
nameservers that agreed start to disagree, instead of on every check while
|
nameservers that agreed start to disagree, instead of on every check while
|
||||||
they disagree, and not again after a restart. Every pair of nameservers is
|
they disagree, and not again after a restart (closes #158).
|
||||||
compared, not only neighbours in sorted order of name (closes #158).
|
|
||||||
- 2026-09-28: DNS names in record values (CNAME, MX, SRV and NS targets) are
|
- 2026-09-28: DNS names in record values (CNAME, MX, SRV and NS targets) are
|
||||||
lower-cased, so nameservers that answer in different letter case no longer
|
lower-cased, so nameservers that answer in different letter case no longer
|
||||||
count as inconsistent or as a record change (closes #157).
|
count as inconsistent or as a record change (closes #157).
|
||||||
|
|||||||
@@ -1,10 +1,6 @@
|
|||||||
package watcher
|
package watcher
|
||||||
|
|
||||||
import (
|
import "sneak.berlin/go/dnswatcher/internal/state"
|
||||||
"context"
|
|
||||||
|
|
||||||
"sneak.berlin/go/dnswatcher/internal/state"
|
|
||||||
)
|
|
||||||
|
|
||||||
// NewlyDisagreeingPairs exports newlyDisagreeingPairs for testing.
|
// NewlyDisagreeingPairs exports newlyDisagreeingPairs for testing.
|
||||||
func NewlyDisagreeingPairs(
|
func NewlyDisagreeingPairs(
|
||||||
@@ -13,13 +9,3 @@ func NewlyDisagreeingPairs(
|
|||||||
) [][2]string {
|
) [][2]string {
|
||||||
return newlyDisagreeingPairs(prev, current)
|
return newlyDisagreeingPairs(prev, current)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DetectHostnameChanges exports detectHostnameChanges for testing.
|
|
||||||
func (w *Watcher) DetectHostnameChanges(
|
|
||||||
ctx context.Context,
|
|
||||||
hostname string,
|
|
||||||
prev *state.HostnameState,
|
|
||||||
current map[string]map[string][]string,
|
|
||||||
) {
|
|
||||||
w.detectHostnameChanges(ctx, hostname, prev, current)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -8,16 +8,6 @@ import (
|
|||||||
"sneak.berlin/go/dnswatcher/internal/watcher"
|
"sneak.berlin/go/dnswatcher/internal/watcher"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
host = "www.example.net"
|
|
||||||
nsA = "a.ns.example.net."
|
|
||||||
nsB = "b.ns.example.net."
|
|
||||||
nsC = "c.ns.example.net."
|
|
||||||
ip1 = "192.0.2.1"
|
|
||||||
ip2 = "192.0.2.2"
|
|
||||||
ip3 = "192.0.2.3"
|
|
||||||
)
|
|
||||||
|
|
||||||
// hostnameState builds the state a check with these records leaves behind.
|
// hostnameState builds the state a check with these records leaves behind.
|
||||||
func hostnameState(
|
func hostnameState(
|
||||||
records map[string]map[string][]string,
|
records map[string]map[string][]string,
|
||||||
@@ -39,19 +29,23 @@ func hostnameState(
|
|||||||
func TestNewlyDisagreeingPairs(t *testing.T) {
|
func TestNewlyDisagreeingPairs(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
onlyA := map[string]map[string][]string{nsA: {"A": {ip1}}}
|
const (
|
||||||
agree := map[string]map[string][]string{nsA: {"A": {ip1}}, nsB: {"A": {ip1}}}
|
nsA = "a.ns.example.net."
|
||||||
disagree := map[string]map[string][]string{nsA: {"A": {ip1}}, nsB: {"A": {ip2}}}
|
nsB = "b.ns.example.net."
|
||||||
alert := [][2]string{{nsA, nsB}}
|
ip1 = "192.0.2.1"
|
||||||
|
ip2 = "192.0.2.2"
|
||||||
|
)
|
||||||
|
|
||||||
// b already disagrees with a and c; then c changes, so a and c,
|
onlyA := map[string]map[string][]string{nsA: {"A": {ip1}}}
|
||||||
// which agreed, now differ.
|
agree := map[string]map[string][]string{
|
||||||
bDiffers := map[string]map[string][]string{
|
nsA: {"A": {ip1}},
|
||||||
nsA: {"A": {ip1}}, nsB: {"A": {ip2}}, nsC: {"A": {ip1}},
|
nsB: {"A": {ip1}},
|
||||||
}
|
}
|
||||||
cChanges := map[string]map[string][]string{
|
disagree := map[string]map[string][]string{
|
||||||
nsA: {"A": {ip1}}, nsB: {"A": {ip2}}, nsC: {"A": {ip3}},
|
nsA: {"A": {ip1}},
|
||||||
|
nsB: {"A": {ip2}},
|
||||||
}
|
}
|
||||||
|
alert := [][2]string{{nsA, nsB}}
|
||||||
|
|
||||||
// Each case starts from the state loaded at startup and runs the
|
// Each case starts from the state loaded at startup and runs the
|
||||||
// checks in order; want[i] is what check i alerts for.
|
// checks in order; want[i] is what check i alerts for.
|
||||||
@@ -91,12 +85,6 @@ func TestNewlyDisagreeingPairs(t *testing.T) {
|
|||||||
checks: []map[string]map[string][]string{disagree, agree, disagree},
|
checks: []map[string]map[string][]string{disagree, agree, disagree},
|
||||||
want: [][][2]string{alert, nil, alert},
|
want: [][][2]string{alert, nil, alert},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: "new disagreement while another nameserver differs alerts",
|
|
||||||
loaded: bDiffers,
|
|
||||||
checks: []map[string]map[string][]string{cChanges, cChanges},
|
|
||||||
want: [][][2]string{{{nsA, nsC}}, nil},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
@@ -119,58 +107,3 @@ func TestNewlyDisagreeingPairs(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestInconsistencyAlert(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
agree := map[string]map[string][]string{nsA: {"A": {ip1}}, nsB: {"A": {ip1}}}
|
|
||||||
disagree := map[string]map[string][]string{nsA: {"A": {ip1}}, nsB: {"A": {ip2}}}
|
|
||||||
|
|
||||||
// Each case starts from the state loaded at startup and then sees
|
|
||||||
// the nameservers disagree on three checks in a row.
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
loaded map[string]map[string][]string
|
|
||||||
want int
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "disagreement lasting several checks alerts once",
|
|
||||||
loaded: agree,
|
|
||||||
want: 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "disagreement in the loaded state does not alert",
|
|
||||||
loaded: disagree,
|
|
||||||
want: 0,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
// The hostname change detection uses only the notifier.
|
|
||||||
notifier := &mockNotifier{}
|
|
||||||
w := watcher.NewForTest(nil, nil, nil, nil, nil, notifier)
|
|
||||||
|
|
||||||
prev := hostnameState(tt.loaded)
|
|
||||||
|
|
||||||
for range 3 {
|
|
||||||
w.DetectHostnameChanges(t.Context(), host, prev, disagree)
|
|
||||||
prev = hostnameState(disagree)
|
|
||||||
}
|
|
||||||
|
|
||||||
got := 0
|
|
||||||
|
|
||||||
for _, n := range notifier.getNotifications() {
|
|
||||||
if n.Title == "Inconsistency: "+host {
|
|
||||||
got++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if got != tt.want {
|
|
||||||
t.Errorf("sent %d inconsistency alerts, want %d", got, tt.want)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
+16
-15
@@ -470,8 +470,8 @@ func (w *Watcher) detectInconsistencies(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// newlyDisagreeingPairs returns every pair of nameservers whose records
|
// newlyDisagreeingPairs returns the pairs of nameservers, neighbours in
|
||||||
// differ in current but were equal in prev, in sorted order of name.
|
// sorted order, whose records differ in current but were equal in prev.
|
||||||
// A pair with a nameserver missing from prev is not returned.
|
// A pair with a nameserver missing from prev is not returned.
|
||||||
func newlyDisagreeingPairs(
|
func newlyDisagreeingPairs(
|
||||||
prev *state.HostnameState,
|
prev *state.HostnameState,
|
||||||
@@ -486,21 +486,22 @@ func newlyDisagreeingPairs(
|
|||||||
|
|
||||||
var pairs [][2]string
|
var pairs [][2]string
|
||||||
|
|
||||||
for i, ns1 := range nameservers {
|
for i := range len(nameservers) - 1 {
|
||||||
for _, ns2 := range nameservers[i+1:] {
|
ns1 := nameservers[i]
|
||||||
if recordsEqual(current[ns1], current[ns2]) {
|
ns2 := nameservers[i+1]
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
prev1, ok1 := prev.RecordsByNameserver[ns1]
|
if recordsEqual(current[ns1], current[ns2]) {
|
||||||
prev2, ok2 := prev.RecordsByNameserver[ns2]
|
continue
|
||||||
|
|
||||||
if !ok1 || !ok2 || !recordsEqual(prev1.Records, prev2.Records) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
pairs = append(pairs, [2]string{ns1, ns2})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
prev1, ok1 := prev.RecordsByNameserver[ns1]
|
||||||
|
prev2, ok2 := prev.RecordsByNameserver[ns2]
|
||||||
|
|
||||||
|
if !ok1 || !ok2 || !recordsEqual(prev1.Records, prev2.Records) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
pairs = append(pairs, [2]string{ns1, ns2})
|
||||||
}
|
}
|
||||||
|
|
||||||
return pairs
|
return pairs
|
||||||
|
|||||||
Reference in New Issue
Block a user