Skip to content

Add blocking rule sets for per-device policies - #411

Merged
drudge merged 1 commit into
mainfrom
claude/per-device-blocking-0lzpfb
Oct 8, 2026
Merged

drudge merged 1 commit into
mainfrom
claude/per-device-blocking-0lzpfb

Conversation

@drudge

@drudge drudge commented Oct 8, 2026

Copy link
Copy Markdown
Owner

Requested by Nick · project thread

Before: every blocking rule applied to every device. The only per-client control was bypass_clients, which turned blocking off completely.

After: [[blocking.rule_sets]] give some devices their own block lists plus their own blocked and allowed domains. A device joins a set through rule_set on its [[clients]] entry, and blocking.default_lists picks the lists for everyone else. Everyone else blocks exactly as before.

This is PR 1 of #240: the engine and config only, no console UI. A device named by mac keeps its rule set in config but only takes effect in PR 2, once identities feed an address table. The docs say so.

How

  • Lists per set without per-domain memory. The block-list compiler already groups each domain's lists into a small number of owner sets. Each rule set gets a []bool over those owner sets. When the most specific match comes from a list the set doesn't use, the lookup keeps walking up to the parent name. A @@ exception only counts when the set uses its list.
  • Choosing a set. The client address is parsed once and looked up in an exact-address map, then checked against networks from most to least specific. An exact address beats a network, and a smaller network beats a larger one.
  • Precedence. A set's own allowed and blocked domains win over the global ones. The operator's blocking.domains and blocking.allowed_domains apply in every set.
  • Bypass Clients is now a rule set with blocking off, so there's one code path. The config key and its Settings field stay until the UI PR moves them.
  • Replication. Devices ([[clients]]) now replicate with the runtime settings, because a device's rule set decides how every node blocks for it. A snapshot from an older primary leaves a replica's devices alone, and an empty list still clears them.
  • Config shape. Membership lives on the existing [[clients]] device entry rather than in a devices list on the rule set as Blocking: per-device rules and "Block everything" for a device #240 sketched. That keeps one device registry, which the Insights drawer already writes.

Benchmarks

BenchmarkPolicyDecision uses 100k blocked domains with 2 lists, 200k iterations, median of 5 runs. The container's CPU is shared and noisy, so read these as "no regression":

main this PR
NoRuleSets (bypass network configured) 216 ns, 0 allocs 119 ns, 0 allocs
OutsideRuleSets n/a 123 ns, 0 allocs
InRuleSet (Strict list only) n/a 217 ns, 0 allocs
BlockedLookup 79 ns 76 ns

TestPolicyDecisionDoesNotAllocateWithRuleSets asserts zero allocations for clients with no set, in a set, and bypassed.

Checks

go tool mage verify passes, and gofmt -l . is clean. The race detector passes on dnsserver, config and app.

Refs #240


Generated by Claude Code

Rule sets give some devices their own block lists and their own blocked
and allowed domains. A device joins one through its [[clients]] entry's
rule_set, and blocking.default_lists narrows the lists for everyone else.

Each rule set gets an on/off table over the compiler's owner sets, so
choosing lists costs nothing per blocked domain. The query path parses
the client address once and looks it up in an exact-address map, then
the networks most specific first, with no allocations. Bypass clients
are now a rule set with blocking off, so there is one path for both.

Devices now replicate with the rest of the runtime settings, since a
device's rule set decides how every node blocks for it.

Refs #240
@drudge drudge self-assigned this Oct 8, 2026
@drudge
drudge marked this pull request as ready for review October 8, 2026 19:29
@drudge
drudge merged commit 1af1088 into main Oct 8, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant