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
+31 -11
View File
@@ -71,15 +71,15 @@ type Params struct {
Rules *rules.Files
// Alerts receive the alert for each ban the proxy makes or makes
// permanent, for each count over an anomaly threshold, for each request
// whose client a blocklist lists, and for GeoJS failing or a fetch of a
// list failing.
// whose client a blocklist or a DNSBL zone lists, and for GeoJS failing,
// a fetch of a list failing or a query to a DNSBL zone failing.
Alerts *alerts.Queue
}
// Server is the server smallwebwaf runs, with the parts of the proxy
// whose state the state files keep, the lookup database, nil unless
// SWWAF_LOOKUP_SOURCE is file, the lists fetched from URLs, which its Run
// fetches, and the metrics.
// fetches, the DNSBL zones' verdicts, and the metrics.
type Server struct {
*http.Server
@@ -89,6 +89,7 @@ type Server struct {
Anomalies *anomaly.Counters
LookupFile *lookup.File
Lists *reputation.Lists
DNSBL *reputation.DNSBL
Metrics *metrics.Metrics
}
@@ -101,6 +102,7 @@ type Server struct {
func New(params Params) *Server {
errorLog := slog.NewLogLogger(params.ProcessLog.Handler(), slog.LevelWarn)
m := metrics.New(params.Config.MetricsTopN, params.Config.InstanceName)
lists, dnsbl := newReputation(params)
h := &handler{
config: params.Config,
requestLog: params.RequestLog,
@@ -136,13 +138,10 @@ func New(params Params) *Server {
Alerts: params.Alerts,
}),
lookupFile: params.LookupFile,
lists: reputation.New(reputation.Params{
BlocklistURLs: params.Config.BlocklistURLs, Refresh: params.Config.BlocklistRefresh,
ASNLimitPercentURL: params.Config.ASNLimitPercentURL, Now: params.Now,
ProcessLog: params.ProcessLog, Alerts: params.Alerts,
}),
rules: params.Rules,
alerts: params.Alerts,
lists: lists,
dnsbl: dnsbl,
rules: params.Rules,
alerts: params.Alerts,
}
h.geojs = lookup.New(lookup.Params{
URL: params.GeoJSURL,
@@ -160,7 +159,7 @@ func New(params Params) *Server {
})
m.AddBansAndClients(h.ledger, h.limiter, params.Now)
m.AddRules(params.Rules)
m.AddReputation(h.lists)
m.AddReputation(h.lists, h.dnsbl)
return &Server{
Server: &http.Server{
@@ -181,10 +180,30 @@ func New(params Params) *Server {
Anomalies: h.anomalies,
LookupFile: h.lookupFile,
Lists: h.lists,
DNSBL: h.dnsbl,
Metrics: m,
}
}
// newReputation returns the lists fetched from URLs and the DNSBL zones'
// verdicts, as the settings in params name them, with none fetched or
// asked for yet.
func newReputation(params Params) (*reputation.Lists, *reputation.DNSBL) {
cfg := params.Config
lists := reputation.New(reputation.Params{
BlocklistURLs: cfg.BlocklistURLs, Refresh: cfg.BlocklistRefresh,
ASNLimitPercentURL: cfg.ASNLimitPercentURL, Now: params.Now,
ProcessLog: params.ProcessLog, Alerts: params.Alerts,
})
dnsbl := reputation.NewDNSBL(reputation.DNSBLParams{
Zones: cfg.DNSBLZones, Resolver: cfg.DNSBLResolver, CacheTTL: cfg.ReputationCacheTTL,
Timeout: cfg.ReputationTimeout, Now: params.Now, ProcessLog: params.ProcessLog,
Alerts: params.Alerts,
})
return lists, dnsbl
}
// handler is the proxy. It holds what every request shares; what belongs
// to one request is in a request.
type handler struct {
@@ -201,6 +220,7 @@ type handler struct {
anomalies *anomaly.Counters
lookupFile *lookup.File
lists *reputation.Lists
dnsbl *reputation.DNSBL
rules *rules.Files
alerts *alerts.Queue
}