Repository navigation
Block everything for a device for a while - #413
Merged
Merged
Conversation
A hold blocks every domain for one device except its allowed domains and the names Sable answers itself, for some minutes, until a time, or until it is removed. Holds live in [[blocking.holds]], so they replicate and show in the Change Center, and the query log explains held queries. The device address table now ties addresses to hardware addresses rather than rule set names, so rule sets and holds share one client lookup. The MCP tools block_device and unblock_device set and end holds; both start off.
drudge
marked this pull request as ready for review
October 8, 2026 21:10
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Before: the only way to cut a device off was to pause blocking for everyone or block domains for everyone. There was no "no internet until homework's done" for one device.
After: a hold blocks everything for one device, for some minutes, until a time, or until it's removed. The device still reaches its allowed domains (its rule set's and the global ones) and the names Sable answers itself from zones and host overrides, so the console and captive checks keep working. A hold wins over the device's rule set and
bypass_clients, and keeps blocking while blocking is paused. Holds live in config, so they replicate to every node and each one shows in the Change Center. The query log explains a held query as "Everything blocked for this device".This is PR 3 of #240. The countdown, Add 30 Minutes and End Now buttons come with the console PR, on top of the hold service added here. For now holds can be set through config or the new MCP tools
block_deviceandunblock_device, which start off like the other tools that change things a lot.How
dnsserver.clientTable[V]finds a client by exact address, then by the hardware address behind it, then by the most specific network. Rule sets useclientTable[int], holds useclientTable[int64](end time in Unix nanoseconds, 0 for no end).deviceAddressTable) tracks every MAC that a rule set or a hold names. A rule set rename no longer has to reach the table.config.SetHoldandconfig.EndHolddrop ended holds whenever a hold changes, so they don't pile up.holdService(ininternal/web) sets and ends holds throughpolicyService'sblocking.writecheck, replica refusal, config transaction and audit. With Insights off it refuses a hold by hardware address, matching Blocking: per-device rules and "Block everything" for a device #240's "IP/CIDR only" decision. An IP address Sable has tied to hardware is held by hardware, so the hold follows the device to its IPv6 addresses.querylog.PolicyHeld. It answers with the normal blocked response and counts as blocked.Benchmarks
BenchmarkPolicyDecision, 100k domains, 300k iterations, 3 runs on a noisy shared container. The rule-set cases now also have two holds on other clients:With any hold configured, a query pays one more map lookup, roughly 35 to 50 ns. A query without any holds pays nothing extra.
TestPolicyDecisionDoesNotAllocateWithHoldsasserts 0 allocations for held, ended, network and learned clients.Checks
go tool mage verifypasses,gofmt -l .is clean, and the new tests pass under-race.Refs #240
Generated by Claude Code