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:
1 parent
5b8f681e2a
commit
76aaaf3ef5
33 files changed
+2096
-55
No files matched your search
+7
-2
@@ -420,14 +420,19 @@ Needs `blocking:read` (`blocking:write` to change). See
|
||||
| Method | Path | Body / query |
|
||||
|--------|------|--------------|
|
||||
| GET | `/blocking/lists` | each with `views` (where it applies), `auto_views` (not chosen: every view that offers recursion), `state` (ok, waiting, error, disabled, idle), `status` (`entries`, `records`, `skipped`, `checked_at`, `updated_at`, `error`) and `memory_mb` (named's memory for it, estimated) |
|
||||
| POST | `/blocking/lists` | `{"name": "hagezi-multi", "url": "https://...", "views": ["internal"], "comment": ""}` (`views` empty or absent: automatic); jarld downloads it right after |
|
||||
| POST | `/blocking/lists` | `{"name": "hagezi-multi", "url": "https://...", "views": ["internal"], "comment": "", "step": "normal"}` (`views` empty or absent: automatic; `step` the blocking step it starts at); jarld downloads it right after |
|
||||
| GET / PATCH / DELETE | `/blocking/lists/{id or name}` | PATCH any of `name`, `url`, `views`, `disabled`, `comment` |
|
||||
| POST | `/blocking/lists/{id or name}/refresh` | download now and wait; `?force=1` even when the server says nothing changed. Returns the list; a failed update is in `state` and `status.error` |
|
||||
| GET | `/blocking/rules` | each with `views` and `auto_views` |
|
||||
| POST | `/blocking/rules` | `{"name": "doubleclick.net", "action": "block", "exact": false, "views": [], "comment": ""}` (`action` block or allow; `exact`: only the name, not the names below it) |
|
||||
| PATCH / DELETE | `/blocking/rules/{id}` | |
|
||||
| DELETE | `/blocking/rules` | `?name=DOMAIN[&view=V][&missing_ok=1]`: the rules for a domain |
|
||||
| GET | `/blocking/check` | `?name=DOMAIN`: per view with blocking, `{view, result: blocked\|allowed\|none, entry, rule_id, list}` (what applies when blocking isn't paused) |
|
||||
| GET | `/blocking/check` | `?name=DOMAIN`: per view with blocking, `{view, result: blocked\|allowed\|none, entry, rule_id, list, step}` (what applies to normal devices when blocking isn't paused; `step` when only devices at that step or above are blocked) |
|
||||
| GET / POST | `/blocking/steps` | the steps above normal, lowest first, with the `lists` and `groups` at each; POST `{"name": "strict", "comment": ""}` adds one on top |
|
||||
| PATCH / DELETE | `/blocking/steps/{id or name}` | `{"name", "position", "comment"}`; delete is refused while lists or groups use it |
|
||||
| POST | `/blocking/steps/order` | `{"steps": ["strict", "very-strict"]}`: every step above normal, lowest first |
|
||||
| GET / POST | `/blocking/groups` | client groups; POST `{"name": "kids", "members": ["10.10.0.50", "tablets"], "step": "strict", "views": []}` |
|
||||
| PATCH / DELETE | `/blocking/groups/{id or name}` | the same fields |
|
||||
| GET | `/blocking/status` | `{paused_until, paused_indefinitely}` (null and false when not paused) |
|
||||
| POST | `/blocking/pause` | `{"minutes": 15}` (1 to 1440): let every name through until then; jarld resumes by itself. `{"indefinitely": true}`: until resumed |
|
||||
| POST | `/blocking/resume` | end a pause now |
|
||||
|
||||
@@ -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)).
|
||||
|
||||
|
||||
@@ -476,6 +476,12 @@ jarl block delete DOMAIN [--view V] | --id ID
|
||||
jarl block check DOMAIN # blocked? by which rule or list?
|
||||
jarl block pause [DURATION] # let everything through for a while (default 10m, at most 24h)
|
||||
jarl block pause indefinitely # ... until 'jarl block resume'
|
||||
jarl block step list | add NAME | rename STEP NEW | order STEP... | delete STEP
|
||||
jarl block group list
|
||||
jarl block group add NAME MEMBER... [--step S] [--view V]... # addresses, networks or ACLs
|
||||
jarl block group update GROUP [--add M] [--remove M] [--step S] [--name N]
|
||||
jarl block group delete GROUP
|
||||
jarl blocklist update LIST --step STEP # devices at that step or above get it
|
||||
jarl block resume
|
||||
```
|
||||
|
||||
|
||||
@@ -115,6 +115,11 @@ From [blocking.md](blocking.md).
|
||||
| `blocking_view_missing` | error | A list or rule names a view that doesn't exist |
|
||||
| `blocking_no_view` | warning | A list or rule applies to no view: none offers recursion, and blocking only changes recursive answers |
|
||||
| `blocking_no_recursion` | warning | A list or rule names a view that doesn't offer recursion, where it has no effect |
|
||||
| `blocking_step_missing` | error | A list or client group is at a step that doesn't exist (a list can't be at `off`) |
|
||||
| `client_group_member_invalid` | error | Client group members are addresses, networks, or ACLs made only of those |
|
||||
| `client_group_overlap` | error | The same address or network is in two client groups of a view |
|
||||
| `client_group_empty` | warning | A client group has no members |
|
||||
| `blocklist_step_unused` | warning | A list is at a step that no client group reaches, so it blocks nothing |
|
||||
|
||||
## Security lints
|
||||
|
||||
|
||||
Reference in new issue
Block a user