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

+20 -10
View File
@@ -153,14 +153,19 @@ type Summary struct {
Queries int64 `json:"queries"`
Per string `json:"per"` // minute, hour or day
} `json:"peak,omitempty"`
RCodes []Entry `json:"rcodes"`
QTypes []Entry `json:"qtypes"`
Views []Entry `json:"views"`
Zones []Entry `json:"zones"`
Answers []Entry `json:"answers"` // authoritative, recursive
Transport []Entry `json:"transport"` // udp, tcp
CacheHitRatio *float64 `json:"cache_hit_ratio,omitempty"`
RCodes []Entry `json:"rcodes"`
QTypes []Entry `json:"qtypes"`
Views []Entry `json:"views"`
Zones []Entry `json:"zones"`
Answers []Entry `json:"answers"` // authoritative, recursive
Transport []Entry `json:"transport"` // udp, tcp
CacheHitRatio *float64 `json:"cache_hit_ratio,omitempty"`
// NXDomainRatio leaves out answers blocking gave (which are
// NXDOMAIN too); Blocked counts those (docs/blocking.md).
NXDomainRatio *float64 `json:"nxdomain_ratio,omitempty"`
Blocked int64 `json:"blocked"`
BlockedRatio *float64 `json:"blocked_ratio,omitempty"`
Blocklists []Entry `json:"blocklists,omitempty"`
Tops map[string][]Entry `json:"tops,omitempty"`
}
@@ -181,9 +186,14 @@ func (q *Querier) Summary(ctx context.Context, since time.Duration, topLimit int
r := float64(h) / float64(h+m)
s.CacheHitRatio = &r
}
s.Blocked, s.Blocklists = all["blocked"], sorted(all, "blocklist:")
if s.Queries > 0 {
r := float64(all["rcode:NXDOMAIN"]) / float64(s.Queries)
r := float64(max(all["rcode:NXDOMAIN"]-s.Blocked, 0)) / float64(s.Queries)
s.NXDomainRatio = &r
if s.Blocked > 0 {
b := float64(s.Blocked) / float64(s.Queries)
s.BlockedRatio = &b
}
}
per, err := q.Store.series(ctx, res, from, to, "queries")
if err != nil {
@@ -209,7 +219,7 @@ func (q *Querier) Summary(ctx context.Context, since time.Duration, topLimit int
}
if topLimit > 0 {
s.Tops = map[string][]Entry{}
for _, k := range []string{KindName, KindClient, KindNXDomain} {
for _, k := range []string{KindName, KindClient, KindNXDomain, KindBlocked} {
es, err := q.Top(ctx, k, since, topLimit)
if err != nil {
return nil, err
@@ -246,7 +256,7 @@ func (q *Querier) Breakdown(ctx context.Context, group string, since time.Durati
}
// Groups are the counter groups Breakdown accepts.
var Groups = []string{"qtype", "rcode", "view", "zone", "answer", "transport", "rtt", "dnssec", "cache", "ratelimit"}
var Groups = []string{"qtype", "rcode", "view", "zone", "answer", "transport", "rtt", "dnssec", "cache", "ratelimit", "blocklist"}
// Timeline is a set of series over time.
type Timeline struct {