fe80::/10 in the IPv6 antispoof list (or your prefixes) dropped the ISP's router adverts and DHCPv6 replies in the early chain, before the ICMPv6 and DHCPv6 client built-ins could accept them: the router lost its IPv6 default route. Validation now refuses it (antispoof_link_local), the settings page says so, and the importer leaves it out with a note. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
165 lines
12 KiB
Markdown
165 lines
12 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) |
|
|
| `antispoof_link_local` | error | an own prefix or IPv6 antispoof entry holds link-local addresses (`fe80::/10`): the ISP's router adverts and DHCPv6 replies come from them, so dropping them on external zones loses the IPv6 default route and, later, the delegated prefix. A configuration saved before this check keeps being applied; the next change is refused until the entry is taken out |
|
|
| `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 |
|
|
| `stats_no_external` | warning | traffic per device is on, but no zone is external: it counts what goes to and comes from external zones |
|
|
| `stats_flowtable` | warning | traffic per device with the flowtable on: its fast path skips the forward chain, so only each connection's first packets are counted |
|
|
|
|
## 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, and the boot fallback lets nothing but loopback through without one ([operations.md](operations.md#if-the-ruleset-fails-to-load-at-boot)) |
|
|
|
|
## Sets and services
|
|
|
|
| Code | | When |
|
|
|---|---|---|
|
|
| `set_name_reserved` | error | a set name starting with `meter_`, `conn_`, `quota_`, `stats_` or `ban_`: Housecarl's own sets (rate limits, quotas, statistics, ban lists) use those |
|
|
| `set_entry` | error | an entry that isn't an address, prefix (without host bits) or range |
|
|
| `set_empty` | warning | a set (not dynamic, not from Jarl) 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 |
|
|
| `set_jarl_entries` | error | configured entries on a set from Jarl: its addresses are its names' ([jarl.md](jarl.md#sets-from-jarl)) |
|
|
| `set_jarl_dynamic` | error | a set both dynamic and from Jarl |
|
|
| `set_jarl_names` | error | a set from Jarl without names, with one that isn't a DNS name (a wildcard, say) or a bad view, or with a name twice |
|
|
| `set_jarl_within` | error | a set from Jarl without `within`, or with an entry there that is neither a zone nor an address, network or range |
|
|
| `set_jarl_anywhere` | warning | a set from Jarl `within 0.0.0.0/0` or `::/0`: any address may come in, so whoever changes the names in Jarl decides what it holds |
|
|
| `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 |
|
|
| `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 |
|
|
| `quota_restarts` | warning | a change makes a rule count its quota so that what it used can't carry over (a total to per address, per source to per destination, finer groups): it starts again. Only on the change that does it |
|
|
| `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 |
|
|
|
|
## 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`) |
|
|
| `conntrack_full` | warning, *host* | the connection tracking table is over 80% full; once it is full, the kernel drops new connections. Raise `net.netfilter.nf_conntrack_max`, or find what opens so many ([stats.md](stats.md#warnings)) |
|
|
| `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 |
|
|
| `shadow_unprotected` | warning, *host* | shadow mode, and no other table has a filter chain on the input hook: Housecarl's ruleset blocks nothing, and nothing else protects the machine. Load your firewall, or leave shadow mode ([shadow.md](shadow.md)) |
|
|
| `jarl_name` | warning, *host* | a set from Jarl has a name that gives no addresses, and why: no A, AAAA or CNAME record, a wildcard only, a CNAME out of Jarl's zones or in a loop, below a delegation, a zone Jarl doesn't hold the records of, or in more than one view without one named ([jarl.md](jarl.md#what-a-name-gives)) |
|
|
| `jarl_outside` | warning, *host* | an address a name has in Jarl is left out of its set: it isn't within the set's zones and networks ([jarl.md](jarl.md#within)) |
|
|
| `jarl_unreachable` | warning, *host* | reading Jarl failed (unreachable, the token refused…), for every name or one; the sets keep the addresses they had ([jarl.md](jarl.md#when-jarl-cant-be-reached)) |
|
|
| `jarl_not_configured` | warning, *host* | sets from Jarl, but no `jarl.server` in `housecarl.yaml`: they keep the addresses they last had |
|
|
| `shadow_raw_left_out` | warning, *host* | shadow mode leaves this raw rule out: it does more to packets than accept, drop or reject (NAT, setting a mark or field, a jump, a verdict map…). Its hits aren't counted, and the rules after it see what it would have taken ([shadow.md](shadow.md#what-changes-in-the-ruleset)) |
|
|
|
|
In shadow mode `nftables_service` and `takeover_pending` aren't raised:
|
|
another firewall beside Housecarl is the point there.
|
|
|
|
## Ban lists
|
|
|
|
| Code | | When |
|
|
|---|---|---|
|
|
| `banlist_name` | error | not a valid name (as for zones and sets) |
|
|
| `banlist_name_taken` | error | two ban lists with one name |
|
|
| `banlist_scope` | error | a scope other than `firewall`, `hosts` or `everything` |
|
|
| `banlist_hosts` | error | scope `hosts` without hosts, or hosts with another scope |
|
|
| `banlist_address` | error | a host or never entry that isn't an address, prefix or range |
|
|
| `unknown_set` | error | a host or never entry naming a set that doesn't exist |
|
|
| `banlist_time` | error | negative times (but `max_time` -1), or a default longer than the longest |
|
|
| `banlist_entries` | error | `max_entries` below 0 or above 1000000 |
|
|
| `banlist_group` | error | `group_v6` outside 16 to 128 |
|