Files
housecarl/docs/validation.md
T
abrown38andClaude Opus 5.5 9a165cf70b Staged undo, built-ins window, machine warnings; raw and dynamic set fixes
- Undo is staged like any edit: in review mode the web UI's Undo, and hc
  history undo with -C or HOUSECARL_CHANGESET, add a revert operation
  that checks, when replayed, that nothing it touches changed since;
  applied, History links the entries. Imports and takeovers still can't
  be staged.
- The rules page sums up a chain's built-ins in one line; a window lists
  them with what they do and their hits, and switches them.
- Warnings about the machine (dashboard, hc status): nftables.service
  enabled (it loads /etc/nftables.conf at boot and flushes every table
  when stopped), and the imported table still in force beside
  Housecarl's. The docs say to disable nftables.service but not stop it.
- A raw rule with other fields set is refused (raw_conflict) instead of
  silently ignoring them; the rule dialog's nftables section hides what
  it replaces, and the importer's raw rules carry only their text.
- A dynamic set with configured entries is refused
  (set_dynamic_entries): they were silently ignored. The set dialog
  shows entries or the entry timeout, as fits.
- The rule dialog's chain choices are just the chains' names.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 14:52:55 +01:00

130 lines
7.4 KiB
Markdown

# Validation
Every change is checked as a whole configuration before it is applied.
An **error** stops the change; a **warning** is reported with it, and
stays on the dashboard and in `hc status` until fixed. Each problem has a
stable code, shown by the CLI as `[code]` and in the API as
`problems[].code`. `housecarld check` runs the checks on the saved
configuration.
After Housecarl's own checks, **nftables** checks the rendered ruleset
(`nft -c`). What it refuses (mostly raw rules) comes back as a
`firewall` error with nftables' message, and nothing is changed.
The warnings marked *host* compare the configuration with this machine's
interfaces.
## Names
Zones, sets, services and port forwards have names (a forward's is
optional): a letter, then letters, digits, `-` and `_`, up to 48. Names
are unique per kind, ignoring case.
| Code | | When |
|---|---|---|
| `zone_name`, `set_name`, `service_name`, `forward_name` | error | not a valid name, or one that looks like an address |
| `zone_name_taken`, `set_name_taken`, `service_name_taken`, `forward_name_taken` | error | another object of the kind has that name |
## Settings
| Code | | When |
|---|---|---|
| `policy` | error | a policy that isn't `accept` or `drop` |
| `settings_address` | error | an own prefix or antispoof entry that isn't an address or prefix, or is in the wrong list (an IPv6 prefix in the IPv4 list) |
| `flowtable_device` | error | a flowtable device with a wildcard: name each device |
| `flowtable_device_missing` | warning, *host* | a flowtable device doesn't exist; the flowtable is left out until it does |
| `probation_range` | error | the probation isn't 10 to 3600 seconds |
## Zones
| Code | | When |
|---|---|---|
| `zone_empty` | error | no interfaces |
| `zone_interface` | error | not an interface name (up to 15 characters; a trailing `*` matches many) |
| `zone_interface_taken` | error | the interface is in another zone already |
| `zone_interface_missing` | warning, *host* | the interface doesn't exist here (yet); its rules match once it does |
| `no_external_zone` | warning | no zone is external: antispoofing, MSS clamping, the DHCPv6 client rule and port forwards without public addresses need one |
## Sets and services
| Code | | When |
|---|---|---|
| `set_entry` | error | an entry that isn't an address, prefix (without host bits) or range |
| `set_empty` | warning | a set (not dynamic) with no entries: rules using it match nothing |
| `set_dynamic_entries` | error | configured entries on a dynamic set: its entries come at run time |
| `set_timeout` | error | a timeout on a set that isn't dynamic, or under a second |
| `service_empty` | error | a service with no entries |
| `service_entry` | error | an entry that isn't `PROTO/PORTS` (`tcp`, `udp`, `sctp`; a port or range) or `icmp/TYPE`, `icmpv6/TYPE` with a type nftables knows |
## Matches
These apply to rules, source NAT and redirects.
| Code | | When |
|---|---|---|
| `unknown_zone` | error | a zone that doesn't exist |
| `match_in_zone` | error | in zones on the output chain (it has no incoming interface) |
| `match_out_zone` | error | out zones on the input or early chain, or on a redirect |
| `match_address` | error | an address that can't be read |
| `unknown_set` | error | a name that is neither an address nor a set |
| `unknown_service` | error | a service that is neither built in nor yours |
| `family` | error | a family that isn't `ipv4` or `ipv6` |
| `match_never` | error | the addresses, services and family can't all match the same packet (IPv4 sources and an `icmpv6` service) |
## Rules
| Code | | When |
|---|---|---|
| `rule_chain` | error | a chain that isn't `input`, `forward`, `output` or `early` |
| `rule_action` | error | an action that isn't `accept`, `drop`, `reject` or `log` |
| `raw_rule` | error | a raw rule over several lines, or with `;` |
| `raw_conflict` | error | a raw rule with other fields set (zones, addresses, services, limits, log…): it is used as written, so they would be ignored |
| `early_conntrack` | error | connection states, limits or a connection limit in the early chain, which runs before connection tracking |
| `early_reject` | error | reject in the early chain: drop instead |
| `ct_state`, `ct_status` | error | an unknown connection state or status |
| `rule_day`, `rule_hours` | error | an unknown day, or hours that aren't `HH:MM`, or only one of them |
| `rule_limit` | error | a rate that isn't a number per second, minute, hour or day |
| `rule_conn_limit` | error | a connection limit under 1 |
| `rule_limit_unit` | error | a rate's unit that isn't packets, bytes, kbytes or mbytes |
| `rule_limit_bytes` | error | a byte rate nftables can't hold: over 4 GB a second, 293 MB a minute, 4.9 MB an hour or 208 KB a day (the message gives it per second) |
| `rule_limit_key` | error | a limit both per source and per destination |
| `rule_quota` | error | a quota under 1 byte |
| `rule_quota_key` | error | a quota both per source and per destination |
| `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 |
| `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 |
## Port forwards
| Code | | When |
|---|---|---|
| `forward_protocol` | error | no protocols, or one that isn't `tcp`, `udp` or `sctp` |
| `forward_ports` | error | the public port isn't a port or range |
| `forward_to_port` | error | the inside port isn't one port, or is given for a range of public ports |
| `forward_target` | error | neither `to` nor `to6`, or `to` that isn't one IPv4 address, or `to6` one IPv6 address |
| `forward_public` | error | no public addresses and no external zone, or an IPv6 public address |
| `forward_overlap` | error | another forward has the same protocol, port and public address |
| `forward_hairpin` | warning | hairpin without public addresses: inside clients' connections can't be recognised |
## Source NAT and redirects
| Code | | When |
|---|---|---|
| `snat_to` | error | `to` isn't one address, or of another family than the rule (give the rule the family) |
| `snat_everything` | warning | no out zones and no sources: everything leaving the firewall is translated |
| `redirect_service` | error | no service, or one without TCP, UDP or SCTP ports |
| `redirect_to_port` | error | the local port isn't one port |
## The machine
These come from housecarld looking at the machine, not at the
configuration: the dashboard and `hc status` show them.
| Code | | When |
|---|---|---|
| `nftables_service` | warning, *host* | Debian's `nftables.service` is enabled: at boot it loads `/etc/nftables.conf`, and stopping or restarting it runs `nft flush ruleset`, deleting Housecarl's table too. Disable it with `systemctl disable nftables` (without `--now`) |
| `takeover_pending` | warning, *host* | the table you imported from is still in force beside Housecarl's, so a packet must get past both; `hc takeover TABLE` deletes it once Housecarl's rules are right |