Filtering process
Warden’s configuration has seven nouns: blocklists, Custom Lists, profiles, devices, groups, subnets, schedules. Once you see how they reference each other and in what order the resolver consults them, the rest of the documentation reads as variations on a single theme. This page is that theme.
How a query gets resolved
For each query, Warden runs a single cascade in two phases: first picks the profile, then asks that profile what to do with the domain.
Phase 1 — which profile applies? — walks five levels. The first match wins. Phase 2 — what does that profile say about this domain? — walks the rule layers inside the chosen profile.
Phase 1 — which profile applies? · first match wins
- L2
Schedule active for this device?
[[schedules]]yes → profile - L1
Client IP/MAC has a device row?
[[devices]]yes → profile - L3
Device belongs to a matching group?
[[groups]]yes → profile - L4
Client IP inside a subnet CIDR?
[[subnets]]yes → profile - L5
default_profileset?[server]yes → profile - REFUSEDhardcoded — ignores any
block_response
Phase 2 — what does that profile say about the domain?
- R1
Custom List
$importantallow hit? yes → Allow - R2
Custom List
$importantdeny hit? yes → Block - R3 Custom List allow hit? yes → Allow
- R4 Custom List deny hit? yes → Block
- R5
Profile has
block_all? yes → Block - R6
Domain in a deny-direction blocklist and in no allow-direction one?
[[blocklists]]yes → Block - Allowno layer objected
| Pin | Why |
|---|---|
| Schedule beats the device’s own profile | An active schedule is checked before the device’s own profile, so it wins inside its window. L2 is checked before L1. A schedule on a device that already has a profile would do nothing otherwise. |
| Schedules need a device row | A schedule is keyed to a device id (or a group id, via device.groups). A client resolved at L4 (subnet) or L5 (default) cannot trigger one. Time-bounded behaviour for a device-less client is a subnet with its own profile. |
| Groups do not take raw IPs | A [[groups]] row references device ids only. A client without a device row never reaches L3. “Kids’ VLAN at 8pm” is usually subnet → strict profile, not a group plus a schedule. |
| Longest prefix, not file order | When two [[subnets]] overlap, the more specific CIDR wins. The optional priority only breaks ties at an equal prefix: higher wins, then id ascending. The same CIDR in two subnets at the same priority with different profiles is refused. |
| Custom List rules outrank imported lists | A Custom List allow beats a blocklist hit with no flag, because the winner among Custom List rules is picked first. Devices have no rules of their own. |
Strict-by-default fallback
If no level matches and default_profile is unset, the answer is REFUSED — never a permissive default.
Leave default_profile unset and any client that matches no device, group, or subnet drops to L5 and is refused. The RCODE=REFUSED at L5 is hardcoded — it ignores any profile’s block_response.
Set default_profile only when you want a catch-all (for example every guest gets an ads-blocked profile).
The cast
Profiles are the centre. Everything else either points at a profile or is referenced by one.
A profile is a bundle of policy: how each blocklist applies (the list’s own base, or the profile’s override: deny, allow or ignore), which Custom Lists to mount, what to answer with when a query is blocked. Devices, groups, and subnets are three ways of saying “this client gets that profile”. Schedules are a time-bounded override that swaps in a different profile. Blocklists and Custom Lists are the raw policy that profiles compose.
| Entity | Identified by | Points to | Referenced by |
|---|---|---|---|
[profiles.id] | id | lists, custom_lists | devices, groups, subnets, schedules, server.default_profile |
[[devices]] | ip and/or mac | profile, groups | groups (via id), schedules |
[[groups]] | id, priority | profile, devices | devices (via device.groups), schedules |
[[subnets]] | id, cidrs | profile | — |
[[schedules]] | target_type + target_id | a device or a group, profile | — |
[[blocklists]] | id | HTTPS list URL, or a locally imported file | profiles |
[[custom_lists]] | id | a pack file of rules | profiles |
- A device points at one profile (optional). It can belong to many groups.
- A group has one profile and many devices.
- A subnet has one profile and many CIDRs.
- A blocklist or a Custom List is used by many profiles.
- A schedule has exactly one target (a device or a group, never both) and one overlay profile.
The split between identity (devices, groups, subnets) and policy (profiles, blocklists, custom_lists) is deliberate. Identity entities only point at a profile; since 0.44.0 a device carries no rules of its own. That is why the same profile can govern a laptop, the kids’ tablets, and the guest VLAN.
How those entities are split across files: Declarative configuration.
Where each entity is documented
This page is the map. The territory lives one click away.
- Profiles — Profiles
- Devices, Groups, Subnets — Devices, Groups, Subnets
- Custom Lists — Custom rules
- Schedules — Schedules
- Blocklists — Blocklists
Verify it in the code
Every claim on this page is one function away in the source.
src/profilesresolver.rs → ProfileResolver::resolve()src/filterengine.rs → evaluate_active_operator_policy()src/filter/operator_rulesdecision.rs → reduce()src/profilesresolver.rs → ProfileResolver::resolve()src/profilesschedule.rs → active_schedule_profile()REFUSEDsrc/dnshandler.rs → handle_inner()src/dnshandler.rs → send_refused()