DNS blocklists asked in the background, verdicts kept (closes #104)
check / check (push) Waiting to run

Zones in SWWAF_DNSBL_ZONES are asked about each client (RFC 5782 names)
in the background, through the host's resolver or SWWAF_DNSBL_RESOLVER;
no request waits. Verdicts last SWWAF_REPUTATION_CACHE_TTL and are kept
in reputation.json, at most 100,000. After the blocklists,
SWWAF_REPUTATION_ACTION (limit:25) denies, limits or logs a listed
client; the log line names the zones, each raises reputation_hit, with
metrics by zone. A failed, timed-out or refused query gives no verdict,
raises source_failure, and pauses the zone a minute.

Judgement call: answers in 127.255.255.0/24 or outside 127.0.0.0/8 are failures.
Judgement call: the minute's pause after a failure; at most 1,000 queries at once.
Rule suppressed: paralleltest on the DNSBL tests (Go's resolver shares state across synctest bubbles), funlen on the test of every logged setting.

Model: opus-5-5
This commit is contained in:
2026-10-07 18:00:50 +00:00
parent 2b8c98ba1f
commit ddb95f5411
18 changed files with 2125 additions and 236 deletions
+41 -12
View File
@@ -256,24 +256,53 @@ func (m *Metrics) AddLookupFile(lastRead func() time.Time, readFailures func() i
)
}
// AddReputation adds the metrics of the lists fetched from URLs, by
// source, each list's URL: the requests whose client a blocklist lists,
// which ReputationHit counts, and, read from lists as the metrics are
// asked for, the fetches that failed and when the copy in use was fetched.
// It is called once, before ReputationHit.
func (m *Metrics) AddReputation(lists *reputation.Lists) {
// AddReputation adds the metrics of the lists fetched from URLs and of the
// DNSBL zones, by source, each list's URL or each zone: the requests whose
// client a blocklist or a zone's verdict lists, which ReputationHit
// counts, and, read from lists and dnsbl as the metrics are asked for, for
// a list, the fetches that failed and when the copy in use was fetched,
// and for a zone, the queries made and those that failed. It is called
// once, before ReputationHit.
func (m *Metrics) AddReputation(lists *reputation.Lists, dnsbl *reputation.DNSBL) {
const (
sourceLabel = "source"
failuresHelp = "Fetches of the list, or queries to the DNSBL zone, that failed."
)
m.reputationHits = counterVec("smallwebwaf_reputation_hits_total",
"Requests whose client a blocklist lists, by the blocklist's URL.",
[]string{"source"})
"Requests whose client a blocklist or a DNSBL zone lists, by the "+
"blocklist's URL or the zone.",
[]string{sourceLabel})
m.registry.MustRegister(m.reputationHits)
for _, zone := range dnsbl.Zones() {
source := prometheus.Labels{sourceLabel: zone}
m.registry.MustRegister(
prometheus.NewCounterFunc(prometheus.CounterOpts{
Name: "smallwebwaf_reputation_queries_total",
Help: "Queries to the DNSBL zone.",
ConstLabels: source,
}, func() float64 {
return float64(dnsbl.Queries(zone))
}),
prometheus.NewCounterFunc(prometheus.CounterOpts{
Name: "smallwebwaf_reputation_failures_total",
Help: failuresHelp,
ConstLabels: source,
}, func() float64 {
return float64(dnsbl.Failures(zone))
}),
)
}
for _, listURL := range lists.URLs() {
source := prometheus.Labels{"source": listURL}
source := prometheus.Labels{sourceLabel: listURL}
m.registry.MustRegister(
prometheus.NewCounterFunc(prometheus.CounterOpts{
Name: "smallwebwaf_reputation_failures_total",
Help: "Fetches of the list that failed.",
Help: failuresHelp,
ConstLabels: source,
}, func() float64 {
return float64(lists.Failures(listURL))
@@ -295,8 +324,8 @@ func (m *Metrics) AddReputation(lists *reputation.Lists) {
}
}
// ReputationHit counts a request whose client the blocklist at source, its
// URL, lists.
// ReputationHit counts a request whose client source lists: a blocklist,
// by its URL, or a DNSBL zone.
func (m *Metrics) ReputationHit(source string) {
m.reputationHits.WithLabelValues(source).Inc()
}