audit 4: DNS outages unknown, DMARC limits said, typed Health, one TLS mode per upstream
test / test (push) Canceled after 0s
test / test (push) Canceled after 0s
M14, M15: queryEach gives each resolver 2 s within the check's context and asks the one that answered last first; an address or PTR lookup not answered is Unknown, an answered mismatch beside it still an error; Checker.Server keeps the last answer's addresses, so SPF does not go Unknown with it. Notifications report faults only, now tested with SPF unknown alone and beside a wrong DKIM. DMARC reports: tests of malformed XML, gzip and zip bombs, a message over the limit, the service's Resync, hosted suggestion and validation. The finding: both limits cut silently and an oversized report failed as damaged; readAtMost refuses it for its size (MaxMessage 10 MiB, moved into IngestMessage). healthReadings replaces map[string]any for Health and Overview. L5: an inbound upstream has one TLS mode - a second domain to the same [host]:port with another mode is refused, naming the first; the TLS policy map holds each next hop once. Tested on the test server: go vet + go test of 25 packages, make e2e (all pass, 631 s) on this tree. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -5,6 +5,7 @@ import (
|
||||
"errors"
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestWithDefaultPort(t *testing.T) {
|
||||
@@ -48,10 +49,10 @@ func TestNewExternalResolverFallsBackToDefaults(t *testing.T) {
|
||||
// must not second-guess an authoritative "no such name" — otherwise a domain
|
||||
// that genuinely lacks a record costs one timeout per configured resolver.
|
||||
func TestQueryEachTriesTheNextResolverOnlyOnFailure(t *testing.T) {
|
||||
servers := []*net.Resolver{{}, {}, {}}
|
||||
e := &externalResolver{servers: []*net.Resolver{{}, {}, {}}, timeout: time.Second}
|
||||
|
||||
asked := 0
|
||||
got, err := queryEach(servers, func(*net.Resolver) (string, error) {
|
||||
got, err := queryEach(context.Background(), e, func(context.Context, int) (string, error) {
|
||||
asked++
|
||||
if asked < 3 {
|
||||
return "", &net.DNSError{Err: "timed out", IsTimeout: true}
|
||||
@@ -66,7 +67,7 @@ func TestQueryEachTriesTheNextResolverOnlyOnFailure(t *testing.T) {
|
||||
}
|
||||
|
||||
asked = 0
|
||||
_, err = queryEach(servers, func(*net.Resolver) (string, error) {
|
||||
_, err = queryEach(context.Background(), e, func(context.Context, int) (string, error) {
|
||||
asked++
|
||||
return "", notFound("absent.example")
|
||||
})
|
||||
@@ -79,6 +80,40 @@ func TestQueryEachTriesTheNextResolverOnlyOnFailure(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// A first resolver that drops packets costs its own time, not the check's:
|
||||
// the second is asked with time left, and answers; and the next question goes
|
||||
// to the one that answered (audit 2026-10-12, M14).
|
||||
func TestADroppingResolverLeavesTheNextItsTime(t *testing.T) {
|
||||
e := &externalResolver{servers: []*net.Resolver{{}, {}, {}}, timeout: 50 * time.Millisecond}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
||||
defer cancel()
|
||||
|
||||
var order []int
|
||||
drops := func(ctx context.Context, i int) (string, error) {
|
||||
order = append(order, i)
|
||||
if i == 0 {
|
||||
<-ctx.Done() // a resolver that never answers
|
||||
return "", &net.DNSError{Err: "i/o timeout", IsTimeout: true}
|
||||
}
|
||||
if err := ctx.Err(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return "answer", nil
|
||||
}
|
||||
start := time.Now()
|
||||
got, err := queryEach(ctx, e, drops)
|
||||
if err != nil || got != "answer" {
|
||||
t.Fatalf("got (%q, %v): the second resolver was not given its time", got, err)
|
||||
}
|
||||
if took := time.Since(start); took > 500*time.Millisecond {
|
||||
t.Errorf("the dropping resolver took %v of the check's second", took)
|
||||
}
|
||||
order = nil
|
||||
if _, err := queryEach(ctx, e, drops); err != nil || len(order) != 1 || order[0] != 1 {
|
||||
t.Errorf("the next question went to %v (%v), want the resolver that answered", order, err)
|
||||
}
|
||||
}
|
||||
|
||||
// The reason this package does not use net.DefaultResolver: systemd-resolved
|
||||
// answers PTR queries for the machine's own addresses out of the local
|
||||
// hostname, which is not what the rest of the internet sees. The dial hook must
|
||||
|
||||
Reference in New Issue
Block a user