Connections, "every packet" rules, and a packet tracer
- Connections: the kernel's tracked connections, read and closed over ctnetlink (internal/conntrack, raw netlink like the drop log), with their NAT and TCP state; closing one makes the rules check its next packet again (GET /connections, POST /connections/close; hc connections; Traffic → Connections). - Rules can check every packet (every_packet, --every-packet): before established connections are accepted, so a drop or reject also stops connections already open. They can't accept. - A packet tracer (internal/trace): a table of its own, inet housecarl-trace, marks the packets a filter picks, `nft monitor trace` reports every rule and policy they meet in every table, and the tracer names what accepted or dropped them, Housecarl's rules by name (POST, GET, DELETE /trace; hc trace; Traffic → Trace). One trace at a time, at most 200 packets and 20 a second; the table goes when it ends. e2e: 157 checks, among them an upload a normal block doesn't stop and an every-packet one does, and a trace naming the blocking rule. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
98e11a8bec
commit
a45c893830
34 files changed
+1830
-41
No files matched your search
@@ -80,6 +80,11 @@ Creating answers 201, other changes 200.
|
||||
| `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` |
|
||||
| `GET /connections[?q=&limit=]` | tracked connections: `family`, `protocol`, `source`, `source_port`, `destination`, `destination_port` (or `icmp_id`, `icmp_type`, `icmp_code`), `dnat_to`, `dnat_port`, `snat_to`, `state`, `assured`, `timeout` (seconds left), `packets` and `bytes` when the kernel counts them. `q`: all of its words in the addresses, `:port`, protocol, state |
|
||||
| `POST /connections/close` | `{"connections": [...]}` (as listed) or `{"search": "192.168.1.50"}`: delete their tracking entries (`config:write`, audited); answers `{"closed": N}` |
|
||||
| `POST /trace` | `{"address", "port", "protocol", "seconds"}` (5-600, default 60): start tracing (`config:write`, audited); 409 while one runs |
|
||||
| `GET /trace` | the trace running, or the last: `running`, `input`, `started`, `ends`, `error`, `packets`: each with `time`, `in`, `out`, `protocol`, addresses and ports, `tcp_flags`, `icmp_type`, `steps` (`table`, `chain`, `kind` rule or policy, `rule`, `mark`, `label`, `verdict`), `verdict` (accepted, dropped) and `decided_by` |
|
||||
| `DELETE /trace` | stop it |
|
||||
| `GET /log[?limit=&after=&q=]` | logged packets, newest first: `time`, `prefix`, `in`, `out`, `family`, `protocol`, `source`, `source_port`, `destination`, `destination_port`, `icmp_type`, `tcp_flags`, `length`, `mark` (the rule's). `after` (a time) for those since; `q` searches |
|
||||
| `GET /catalog` | built-in services, chains, actions, roles, the log group |
|
||||
|
||||
|
||||
+3
-1
@@ -137,6 +137,8 @@ by name.
|
||||
| `counters` | the hits of every rule, NAT rule, built-in and policy, by mark (`r12`, `f3`, `b-established`, `p-input`) |
|
||||
| `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)) |
|
||||
| `trace [--address A] [--port N] [--protocol P] [--seconds S]` | follow packets through every table's rules ([operations.md](operations.md#tracing)) |
|
||||
|
||||
### zone, set, service
|
||||
|
||||
@@ -182,7 +184,7 @@ The match flags (`rule add`, `snat add`, `redirect add`):
|
||||
And for rules: `--state`, `--status`, `--days`, `--hours`, `--limit
|
||||
10/minute|1mbytes/second[,burst=N][,over][,per-source|,per-destination]`,
|
||||
`--quota 50gbytes[,over][,per-source|,per-destination][,daily|,weekly|,monthly=DAY]`,
|
||||
`--group-v4 N`, `--group-v6 N`, `--conn-limit N`, `-a
|
||||
`--group-v4 N`, `--group-v6 N`, `--conn-limit N`, `--every-packet`, `-a
|
||||
accept|drop|reject|log`, `--reject-with tcp-reset`, `--log`,
|
||||
`--log-prefix`, `--raw RULE`, `--comment`, `--disabled`
|
||||
([rules.md](rules.md#rules)).
|
||||
|
||||
+51
-5
@@ -133,16 +133,62 @@ the last 2000 are kept in memory. They aren't written to the kernel log
|
||||
or the journal. A rule's log isn't rate-limited: on a busy rule, give it
|
||||
a limit, or log only new connections (`--state new`).
|
||||
|
||||
## Connections
|
||||
|
||||
The built-in that accepts established connections lets their packets
|
||||
through without checking the rules again: a rule added later doesn't
|
||||
stop a connection already open. Traffic → Connections (`hc connections`)
|
||||
lists what the kernel tracks, with NAT (`to 192.168.1.10:443`: a port
|
||||
forward; `as 203.0.113.10`: masqueraded) and the time left:
|
||||
|
||||
```sh
|
||||
hc connections --search 192.168.1.50
|
||||
hc connections --search ":443 tcp"
|
||||
hc connections close --search 192.168.1.50
|
||||
```
|
||||
|
||||
Closing a connection deletes its tracking entry: its next packet is
|
||||
checked by the rules as they are now. One they block is stopped; one they
|
||||
allow carries on (TCP connections are picked up again mid-stream).
|
||||
|
||||
To make a block stop open connections by itself, tick "every packet" on
|
||||
the rule (`--every-packet`): it is checked before established connections
|
||||
are accepted, like rules with a quota ([rules.md](rules.md#rules)).
|
||||
|
||||
## Tracing
|
||||
|
||||
Traffic → Trace (`hc trace`) follows packets through the whole ruleset:
|
||||
Housecarl's rules and every other table (Docker's, fail2ban's, an old
|
||||
`inet main`), each rule and policy they meet, and what accepted or
|
||||
dropped them:
|
||||
|
||||
```
|
||||
$ hc trace --address 192.168.1.50 --port 443 --seconds 60
|
||||
14:02:13.120 192.168.1.50:51432 -> 198.51.100.9:443 tcp syn in eth1 out wan
|
||||
inet housecarl forward: built-in: mss-clamp -> continue
|
||||
inet housecarl forward: rule forward #12 (kids at night) -> drop
|
||||
=> DROPPED by rule forward #12 (kids at night)
|
||||
```
|
||||
|
||||
It traces packets from or to the address, on the port (either way), for
|
||||
5 to 600 seconds, at most 200 packets and 20 a second. It works with
|
||||
nftables' own tracing: a table of its own (`inet housecarl-trace`,
|
||||
hooked before everything else) marks the packets, and `nft monitor
|
||||
trace` reports them; the table goes when the trace ends. One trace runs
|
||||
at a time.
|
||||
|
||||
## When something doesn't get through
|
||||
|
||||
1. `hc log -f` with `--log-drops on`: is it dropped by a policy (no rule
|
||||
1. Trace it (above): it names the rule or policy that dropped it, in
|
||||
whichever table.
|
||||
2. `hc log -f` with `--log-drops on`: is it dropped by a policy (no rule
|
||||
accepted it), or by a rule (its number is in the line)?
|
||||
2. `hc rule list forward` (or `input`): hits on the rule you expect?
|
||||
3. `hc ruleset`: the rule as nftables sees it, with the zones' interfaces
|
||||
3. `hc rule list forward` (or `input`): hits on the rule you expect?
|
||||
4. `hc ruleset`: the rule as nftables sees it, with the zones' interfaces
|
||||
and the sets' families.
|
||||
4. `hc status`: other tables. A Docker or Incus table, or an old one,
|
||||
5. `hc status`: other tables. A Docker or Incus table, or an old one,
|
||||
can drop what Housecarl accepts.
|
||||
5. Port forwards: `hc forward list` hits, and the inside host's own
|
||||
6. Port forwards: `hc forward list` hits, and the inside host's own
|
||||
firewall and default route (answers must go back through Housecarl).
|
||||
|
||||
## Backups
|
||||
|
||||
+2
-2
@@ -7,9 +7,9 @@ else gets **grants**, `resource:access`:
|
||||
|
||||
| Resource | read | write |
|
||||
|---|---|---|
|
||||
| `config` | settings, zones, sets, services, rules, NAT, the ruleset, history, interfaces, probation, changesets (the web UI shows the forms locked) | change them, stage and apply changesets (your own, or anyone's for administrators), undo, import, confirm and roll back |
|
||||
| `config` | settings, zones, sets, services, rules, NAT, the ruleset, history, interfaces, probation, changesets (the web UI shows the forms locked) | change them, stage and apply changesets (your own, or anyone's for administrators), undo, import, confirm and roll back; close connections; start and stop traces |
|
||||
| `sets` | dynamic sets' entries | add and remove entries |
|
||||
| `log` | hit counters and the log | – |
|
||||
| `log` | hit counters, the log, connections, traces | – |
|
||||
| `audit` | the audit log | – |
|
||||
| `*` | all of the above | |
|
||||
|
||||
|
||||
@@ -119,6 +119,10 @@ The action is `accept`, `drop`, `reject` (an ICMP error, or with
|
||||
`--reject-with tcp-reset` a TCP reset) or `log` (log and go on).
|
||||
`--log` logs the packets of any rule ([operations.md](operations.md#the-log)).
|
||||
`--disabled` (or `hc rule disable ID`) keeps a rule without using it.
|
||||
`--every-packet` ("every packet" in the web UI) checks a drop or reject
|
||||
against every packet, before established connections are accepted, so it
|
||||
also stops connections already open, not just new ones; it can't accept
|
||||
([operations.md](operations.md#connections)).
|
||||
|
||||
Examples:
|
||||
|
||||
|
||||
+2
-1
@@ -93,7 +93,8 @@ These apply to rules, source NAT and redirects.
|
||||
| `rule_quota_reset` | error | a reset that isn't daily, weekly or monthly; a day with another reset than monthly; a day outside 1-31 |
|
||||
| `rule_group` | error | a grouping prefix outside 1-32 (IPv4) or 1-128 (IPv6) |
|
||||
| `rule_traffic_accept` | error | a rule with a quota or a byte rate that accepts: it is checked before established connections are accepted, so it can only drop, reject or log |
|
||||
| `flowtable_traffic` | warning | a forward rule with a quota or a byte rate while the flowtable is on: it only counts each connection's first packets |
|
||||
| `rule_every_packet_accept` | error | a rule checked against every packet (`every_packet`) that accepts: it comes before established connections are accepted, so it can only drop, reject or log |
|
||||
| `flowtable_traffic` | warning | a forward rule checked against every packet (a quota, a byte rate, `every_packet`) while the flowtable is on: it only sees each connection's first packets |
|
||||
| `reject_with` | error | `tcp-reset` on a rule that matches more than TCP, or with an action other than reject |
|
||||
| `log_prefix` | error | a log prefix over 100 characters |
|
||||
|
||||
|
||||
Reference in new issue
Block a user