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:
1 parent
436cd79ec9
commit
9fc23310bd
54 files changed
+4269
-85
No files matched your search
+41
-5
@@ -11,18 +11,36 @@ import (
|
||||
type Event struct {
|
||||
Time time.Time
|
||||
Client netip.Addr
|
||||
Port string // the client's port: with Client and Name, it ties an RPZ rewrite to its response
|
||||
View string // "" without views
|
||||
Name string // lowercase, without the final dot ("." for the root)
|
||||
Type string // e.g. A, AAAA, PTR
|
||||
RCode string // e.g. NOERROR, NXDOMAIN
|
||||
}
|
||||
|
||||
// Rewrite is a response policy (RPZ) rewrite named logged: blocking.
|
||||
type Rewrite struct {
|
||||
Time time.Time
|
||||
Client netip.Addr
|
||||
Port string
|
||||
View string
|
||||
Name string // the query name, normalized like Event.Name
|
||||
Action string // NXDOMAIN, NODATA, PASSTHRU, DROP, ...
|
||||
Via string // the policy zone entry: OWNER.ZONE
|
||||
}
|
||||
|
||||
// responseRe matches named's response log lines (BIND 9.20, print-time):
|
||||
//
|
||||
// 07-Oct-2026 19:46:22.195 client @0x7135... 10.10.0.37#51709 (nas.adbraun.lol): view internal: response: nas.adbraun.lol IN A NOERROR 1 0 1 +E(0)K (10.10.0.2)
|
||||
//
|
||||
// The "view NAME: " part is missing when no views are configured.
|
||||
var responseRe = regexp.MustCompile(`^(\d{2}-[A-Za-z]{3}-\d{4} \d{2}:\d{2}:\d{2}\.\d{3}) client @0x[0-9a-fA-F]+ (\S+)#\d+ \(.*?\): (?:view (.+?): )?response: (\S+) (\S+) (\S+) ([A-Z0-9]+) `)
|
||||
var responseRe = regexp.MustCompile(`^(\d{2}-[A-Za-z]{3}-\d{4} \d{2}:\d{2}:\d{2}\.\d{3}) client @0x[0-9a-fA-F]+ (\S+)#(\d+) \(.*?\): (?:view (.+?): )?response: (\S+) (\S+) (\S+) ([A-Z0-9]+) `)
|
||||
|
||||
// rewriteRe matches named's RPZ rewrite lines (category rpz), logged just
|
||||
// before the response line of the same query:
|
||||
//
|
||||
// 09-Oct-2026 23:49:13.698 client @0x7135... 10.10.0.37#54468 (ads.example): view internal: rpz QNAME NXDOMAIN rewrite ads.example/A/IN via ads.example.hagezi.blocklist.jarl.invalid
|
||||
var rewriteRe = regexp.MustCompile(`^(\d{2}-[A-Za-z]{3}-\d{4} \d{2}:\d{2}:\d{2}\.\d{3}) client @0x[0-9a-fA-F]+ (\S+)#(\d+) \((.*?)\): (?:view (.+?): )?rpz \S+ (\S+) rewrite \S+ via (\S+)`)
|
||||
|
||||
const logTimeLayout = "02-Jan-2006 15:04:05.000"
|
||||
|
||||
@@ -44,13 +62,31 @@ func ParseResponse(line string, loc *time.Location) (ev Event, ok bool) {
|
||||
return Event{
|
||||
Time: t,
|
||||
Client: client.Unmap(),
|
||||
View: m[3],
|
||||
Name: normalizeName(m[4]),
|
||||
Type: m[6],
|
||||
RCode: m[7],
|
||||
Port: m[3],
|
||||
View: m[4],
|
||||
Name: normalizeName(m[5]),
|
||||
Type: m[7],
|
||||
RCode: m[8],
|
||||
}, true
|
||||
}
|
||||
|
||||
// ParseRewrite parses an RPZ rewrite log line.
|
||||
func ParseRewrite(line string, loc *time.Location) (rw Rewrite, ok bool) {
|
||||
m := rewriteRe.FindStringSubmatch(line)
|
||||
if m == nil {
|
||||
return rw, false
|
||||
}
|
||||
t, err := time.ParseInLocation(logTimeLayout, m[1], loc)
|
||||
if err != nil {
|
||||
return rw, false
|
||||
}
|
||||
client, err := netip.ParseAddr(m[2])
|
||||
if err != nil {
|
||||
return rw, false
|
||||
}
|
||||
return Rewrite{Time: t, Client: client.Unmap(), Port: m[3], Name: normalizeName(m[4]), View: m[5], Action: m[6], Via: strings.ToLower(m[7])}, true
|
||||
}
|
||||
|
||||
func normalizeName(n string) string {
|
||||
n = strings.ToLower(n)
|
||||
if n == "." {
|
||||
|
||||
Reference in new issue
Block a user