Both found by running the resolver locally against real upstreams. response_ttl is a duration (60s) and a DNS TTL is seconds, but eight places converted with a bare uint32(), which keeps the low 32 bits of the nanosecond count. A blocked name's negative answer carried an SOA TTL and minimum of 997195776 (about 31 years), so any client or downstream cache that honoured it would remember the block, and keep remembering it after the name was unblocked. blocklist.TTLSeconds converts properly (whole seconds, clamped to 2^31-1) and is used by the rule parser and the block, sinkhole and redirect answers. The tests read the TTL off the wire; with the old conversion they fail with 4165425152. An ordinary upstream NXDOMAIN was classified as an error. A name that does not exist is the normal result of a typo, a search-domain expansion or a leaked mDNS query, so on a real network the error metric counted mostly those and, with the access log's errors-only default, every one wrote a line. Only the upstream failing or refusing (SERVFAIL, REFUSED, FORMERR, NOTIMP) is an error now. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
77 lines
2.7 KiB
Go
77 lines
2.7 KiB
Go
package engine
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/miekg/dns"
|
|
|
|
"git.k8s-git-k6n.net/mats/fancy-dns/internal/blocklist"
|
|
"git.k8s-git-k6n.net/mats/fancy-dns/internal/config"
|
|
)
|
|
|
|
// soaTTLs returns the TTL and minimum of the SOA in a response's authority
|
|
// section.
|
|
func soaTTLs(t *testing.T, m *dns.Msg) (ttl, min uint32) {
|
|
t.Helper()
|
|
for _, rr := range m.Ns {
|
|
if soa, ok := rr.(*dns.SOA); ok {
|
|
return soa.Hdr.Ttl, soa.Minttl
|
|
}
|
|
}
|
|
t.Fatalf("no SOA in %v", m)
|
|
return 0, 0
|
|
}
|
|
|
|
// TestBlockedAnswersCarryTheConfiguredTTLInSeconds: response_ttl is a duration
|
|
// ("60s"), and the TTL on the wire is seconds. The conversion once took the
|
|
// duration's nanoseconds as seconds and truncated them, so a blocked name's
|
|
// negative answer told every client to remember it for about thirty years.
|
|
func TestBlockedAnswersCarryTheConfiguredTTLInSeconds(t *testing.T) {
|
|
const want = 60 // config.Default's response_ttl
|
|
|
|
t.Run("nxdomain", func(t *testing.T) {
|
|
h := newHarness(t, hopt{rules: []blocklist.Rule{{Name: "blocked.example.test"}}})
|
|
res := h.ask("blocked.example.test.", dns.TypeA)
|
|
requireRcode(t, res, dns.RcodeNameError)
|
|
if ttl, min := soaTTLs(t, res.Msg); ttl != want || min != want {
|
|
t.Errorf("SOA ttl=%d minimum=%d, want %d", ttl, min, want)
|
|
}
|
|
})
|
|
t.Run("zero", func(t *testing.T) {
|
|
h := newHarness(t, hopt{
|
|
blockCfg: func(b *config.Block) { b.DefaultAction = "zero" },
|
|
rules: []blocklist.Rule{{Name: "blocked.example.test"}},
|
|
})
|
|
res := h.ask("blocked.example.test.", dns.TypeA)
|
|
if len(res.Msg.Answer) != 1 || res.Msg.Answer[0].Header().Ttl != want {
|
|
t.Errorf("answer = %v, want one record with TTL %d", res.Msg.Answer, want)
|
|
}
|
|
})
|
|
t.Run("a rule's own ttl", func(t *testing.T) {
|
|
h := newHarness(t, hopt{rules: []blocklist.Rule{{Name: "blocked.example.test", TTL: 120}}})
|
|
res := h.ask("blocked.example.test.", dns.TypeA)
|
|
if ttl, _ := soaTTLs(t, res.Msg); ttl != 120 {
|
|
t.Errorf("SOA ttl = %d, want the rule's 120", ttl)
|
|
}
|
|
})
|
|
t.Run("redirect", func(t *testing.T) {
|
|
h := newHarness(t, hopt{rules: []blocklist.Rule{{Name: "blocked.example.test", Action: blocklist.ActionRedirect, Response: "10.9.9.9"}}})
|
|
res := h.ask("blocked.example.test.", dns.TypeA)
|
|
if len(res.Msg.Answer) != 1 || res.Msg.Answer[0].Header().Ttl != want {
|
|
t.Errorf("answer = %v, want one record with TTL %d", res.Msg.Answer, want)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestTTLSecondsClamps(t *testing.T) {
|
|
for in, want := range map[time.Duration]uint32{
|
|
0: 0, 60 * time.Second: 60, 1500 * time.Millisecond: 1, 24 * time.Hour: 86400,
|
|
-time.Second: 0, 1000000 * time.Hour: 1<<31 - 1,
|
|
} {
|
|
if got := blocklist.TTLSeconds(in); got != want {
|
|
t.Errorf("TTLSeconds(%v) = %d, want %d", in, got, want)
|
|
}
|
|
}
|
|
}
|