Different blocking for different devices: steps and client groups

Client groups put devices (addresses, networks, or ACLs of them) at a
blocking step: off (only your rules), normal (the default), or steps
added above it, each including the lists of the steps below. Lists
start at a step. In each view, a gate policy zone in front of a step's
lists lets the devices below that step through (rpz-client-ip triggers
with rpz-passthru), so every list is still loaded once. The IPv6 part
of a gate leaves the IPv4-mapped range out: named matches IPv4 clients
as ::ffff:a.b.c.d.

Web UI: a Devices panel on the Blocking page and a step on each list.
CLI: jarl block step, jarl block group, jarl blocklist --step. API:
/blocking/steps, /blocking/groups. Database schema 8.

Also: copying the state kept empty lists as empty (not nil), so
reverting a changeset no longer adds restore operations for unchanged
blocklists, rules and views.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
adamandClaude Opus 5.5 committed 2026-10-11 21:33:16 +01:00
1 parent 5b8f681e2a
commit 76aaaf3ef5
33 files changed
+2096 -55

No files matched your search

+60
View File
@@ -147,6 +147,56 @@ A list's state is **ok** (in use), **waiting** (not downloaded yet),
**error** (the last update failed; the previous version stays in use),
**disabled**, or **idle** (it applies to no view, so it does nothing).
## Different blocking for different devices
Client groups give some devices less or more blocking than the rest, like
Pi-hole's groups: no blocking for a work laptop, an extra list for the
kids' tablets.
**Steps** decide which lists a device gets. Each step includes the lists
of the steps below it, plus its own:
| Step | Gets |
|------|------|
| `off` (built in) | only your rules |
| `normal` (built in) | your rules and the normal lists; every device not in a group |
| `strict` (one you add) | normal, plus the lists at step `strict` |
| `very-strict` (one you add) | strict, plus the lists at step `very-strict` |
Every list starts at a step (normal unless you choose one), and every
group is at a step. Your rules apply at every step, `off` included.
```sh
jarl block step add strict
jarl blocklist update social-media --step strict
jarl block group add work 10.10.0.20 --step off
jarl block group add kids 10.10.0.50 10.10.0.51 2001:db8:1::/64 --step strict
jarl block group list
```
In the web UI, the Blocking page's **Devices** panel holds the steps and
the groups, and each list has a step.
- **Members** are addresses, networks (`10.10.0.0/24`), or ACLs made only
of those (`key`, `localnets` and `!` can't be used: blocking matches
clients by address). A device is in one group: two groups with the same
address are refused, but a more specific address in another group wins
(`10.10.0.0/24` in one group, `10.10.0.20` in another).
- **Steps only add.** A group gets everything up to its step, so "kids get
list A, guests get list B, and neither gets the other's" can't be done
with steps. That needs a view per group, each with its own lists (and
their memory).
- Lists aren't copied per step: each is loaded once, and the steps cost a
small zone each. Lists at a step no group reaches block nothing (Jarl
warns, and shows them as "no effect").
- named must see the device's own address. When a router hands out itself
as DNS server and forwards to named, every query comes from the router,
and groups can't tell devices apart. Devices also need fixed addresses
(DHCP reservations); IPv6 privacy addresses change, so add a device's
network, or rely on its IPv4 address.
- `jarl block check NAME` (and the Blocking page) answers for normal
devices, and says when a list of a higher step blocks the name.
## Checking a name
`jarl block check NAME`, or the box at the top of the Blocking page, says
@@ -254,6 +304,16 @@ response-policy {
blocking in the same view: Jarl refuses the change and says so.
- Rewrites are logged to Jarl's response log when statistics read it, and
not at all otherwise.
- With client groups, a **gate** zone stands in front of the lists of each
step: `off.gate.jarl.invalid` before the normal lists,
`STEP.gate.jarl.invalid` before a step's. named checks the zones in
order and the first match decides; a gate matches the devices below its
step by address (`rpz-client-ip` triggers) with `rpz-passthru`, so they
never reach the lists behind it. A gate is never logged, and its file is
next to the rules zone (`/var/lib/bind/jarl/VIEW/strict.gate.jarl.invalid.rpz`).
For "everyone except these devices", a gate lists the address ranges
around them, at most a few hundred entries. named matches IPv4 clients as
IPv4-mapped IPv6 addresses, so the IPv6 part leaves `::ffff:0:0/96` out.
Lists and rules need the `blocking` permission ([permissions.md](permissions.md)).