Add blocking: blocklists and block/allow rules with BIND response policy zones

jarld downloads blocklists (Adblock, hosts or plain domains), checks them
daily and reloads named only when a list changed; your own block and
allow rules take precedence, and the most specific rule wins. Lists and
rules apply per view (default: every view that offers recursion). Web UI
Blocking page with name checks, jarl blocklist and jarl block, the
/blocking API, blocked counts in the statistics. Database schema 6.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
adamandClaude Opus 5.5 committed 2026-10-10 09:24:19 +00:00
1 parent 436cd79ec9
commit 9fc23310bd
54 files changed
+4269 -85

No files matched your search

+27
View File
@@ -123,6 +123,10 @@ func openApp(allowMissingConfig bool) (*app, error) {
NoReload: cfg.BIND.NoReload,
Extras: renderExtras(cfg),
}, log)
eng.Limits = engine.BlocklistLimits{
Refresh: time.Duration(cfg.Blocking.Refresh), RetryAfter: time.Hour, Timeout: 2 * time.Minute,
MaxBytes: int64(cfg.Blocking.MaxDownloadMB) << 20, MaxEntries: cfg.Blocking.MaxEntries,
}
return &app{cfg: cfg, log: log, store: st, eng: eng, owner: owner}, nil
}
@@ -323,6 +327,7 @@ func serveCmd() *cobra.Command {
}
}()
go auditRetention(ctx, a)
go blocklistLoop(ctx, a)
a.log.Info("jarld starting", "version", version.String(), "database", a.cfg.Database, "bind_config", a.cfg.BIND.ConfigDir)
err = srv.Serve(ctx, func() { sdNotify("READY=1\nSTATUS=serving") })
sdNotify("STOPPING=1")
@@ -332,6 +337,28 @@ func serveCmd() *cobra.Command {
}
}
// blocklistLoop keeps blocklists up to date (docs/blocking.md): every few
// minutes it downloads the lists that are due, and at once when a list
// was added or changed.
func blocklistLoop(ctx context.Context, a *app) {
select {
case <-ctx.Done():
return
case <-time.After(10 * time.Second): // let jarld finish starting
}
t := time.NewTicker(5 * time.Minute)
defer t.Stop()
for {
a.eng.RefreshDue(ctx)
select {
case <-ctx.Done():
return
case <-t.C:
case <-a.eng.BlocklistKicks():
}
}
}
func auditRetention(ctx context.Context, a *app) {
if a.cfg.Audit.RetentionDays <= 0 {
return