# 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 | | `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) 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 | | `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)) | | `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 |