Zone matrix: what the rules do between zones

internal/matrix walks each pair's rules in order, as nftables would, for
new connections: zone → zone (forward), zone → the firewall (input) and
back (output), with the built-ins that act on new connections. A rule
for the whole pair decides it; one for part of its traffic (addresses,
services, times, rates, a quota) is an exception; the rest falls to the
policy. Rules whose addresses all lie in networks connected to other
zones are left out of a pair. GET /matrix (with ?changeset, pending
changes), hc matrix [FROM TO], and a Matrix page whose cells open their
rules.

e2e: 166 checks, among them the matrix of the maintainer's imported
ruleset.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
abrown38andClaude Opus 5.5 committed 2026-10-19 20:35:26 +01:00
1 parent e9cbb3e4e9
commit b6f31c86a6
17 files changed
+725 -2

No files matched your search

+1
View File
@@ -77,6 +77,7 @@ Creating answers 201, other changes 200.
| `GET /healthz` | `{"ok": true, "version"}`, without authentication |
| `GET /status` | `build`, `started`, `applied` (the ruleset is in force), `apply_error`, `problems`, `probation`, `interfaces`, `counts` (objects by kind), `other_tables` (other nftables tables, `"inet main"`) |
| `GET /interfaces` | the machine's interfaces: `name`, `up`, `addresses`, `zone` |
| `GET /matrix[?changeset=ID]` | the zone matrix: `zones`, `cells` (`from`, `to` (a zone, or `firewall`), `chain`, `verdict` (allowed, blocked), `decided_by`, `rule_id`, `exceptions`: `rule_id`, `chain`, `label`, `verdict` (allowed, blocked, logged, unknown), `what`) ([rules.md](rules.md#the-zone-matrix)) |
| `GET /ruleset` | the nftables ruleset Housecarl builds, as text |
| `GET /counters` | `{"since", "counters": {"r12": {"packets", "bytes"}, ...}}`, by mark ([design.md](design.md#its-own-table-only)); totals since housecarld started |
| `GET /quotas` | each rule's quota: `rule_id`, `rule`, `quota`, `used` (bytes), `addresses` (`[{"address", "used"}]` for a per-address quota), `last_reset`, `next_reset` |
+1
View File
@@ -138,6 +138,7 @@ by name.
| `quotas` | what each rule's quota used (each address's, for per-address quotas), and when it resets |
| `log [-f] [--search TEXT] [--limit N]` | the packets logged by rules and policies ([operations.md](operations.md#the-log)) |
| `connections [--search TEXT] [--limit N]`, `connections close --search TEXT [-y]` | tracked connections; closing makes the rules check them again ([operations.md](operations.md#connections)) |
| `matrix [FROM TO]` | what the rules do to new connections between zones; with FROM and TO, that pair's rules in order ([rules.md](rules.md#the-zone-matrix)) |
| `trace [--address A] [--port N] [--protocol P] [--seconds S]` | follow packets through every table's rules ([operations.md](operations.md#tracing)) |
### zone, set, service
+41
View File
@@ -235,6 +235,47 @@ TCP and UDP connections forwarded between those devices skip the rules,
which helps on a slow CPU. It needs exact device names. Counters of
forward rules then only count each connection's first packets.
## The zone matrix
The Matrix page (`hc matrix`) sums up what the rules do to **new
connections** between zones: a row per zone the traffic comes from, a
column per zone it goes to, and the firewall itself (to it: the input
chain; from it: output; zone to zone: forward).
```
$ hc matrix
FROM \ TO guest lan wan firewall
guest block block block +1 block +3
lan block block allow block +4
wan block +2 block +2 block +1 block +3
firewall allow allow allow -
```
Each cell walks that pair's rules in order, as nftables would, with the
built-ins that act on new connections (ping from internal zones,
ICMPv6, the DHCPv6 client, antispoofing, port forwards):
* a rule that applies to the whole pair decides it ("allow", "block");
* a rule that applies to part of its traffic (some addresses, services,
times, rates, a quota) is an **exception** (+N), and the walk goes on;
* what's left falls to the deciding rule or the chain's policy.
`hc matrix guest wan` (or clicking the cell) lists the exceptions in
order:
```
guest → wan (the forward chain), new connections:
allowed http, https rule forward #14
blocked the rest forward policy
```
A rule whose addresses all lie in networks directly connected to other
zones is left out of a pair: traffic between those zones can't have them
(`from wg_net` doesn't concern traffic coming in on the LAN). Routed
networks, mixed lists and dynamic sets might, so they stay. Replies and
established connections aren't shown: the built-in lets them through. In
review mode, the matrix shows your pending changes.
## The ruleset
`hc ruleset` prints what Housecarl builds; the web UI and `hc rule list`