firewalld — Linux hosts firewalld manages

Blocks on hosts where firewalld owns the firewall — where driving iptables directly fights the daemon and loses on reload. Uses ipset for the IP sets and the firewalld direct interface for the block rules, so the daemon and the bans coexist.

[kur.sshd]
backend   = "firewalld"
ports     = [ "22" ]
protocols = [ "tcp" ]

[kur.sshd.options]
kill = 1

What it creates

firewall-cmd --direct --add-rule ipv4 filter INPUT_direct 0 \
    -m set --match-set kur_sshd_4 src -p tcp -m multiport --dports 22 -j DROP
firewall-cmd --direct --add-rule ipv6 filter INPUT_direct 0 \
    -m set --match-set kur_sshd_6 src -p tcp -m multiport --dports 22 -j DROP

The rule bodies are iptables argument strings — the direct interface passes them through to iptables/ip6tables underneath.

Requirements

Ports, protocols, and names

Options

| option | default | what | |---------|----------------|---------------------------------------------------------------| | type | drop | drop silently drops; reject sends ICMP port-unreachable | | chain | INPUT_direct | direct interface chain for the rules; /^[a-zA-Z0-9_\-]+$/ | | kill | 0 | drop existing conntrack entries for a banned IP |

chain

INPUT_direct is evaluated by firewalld ahead of its zone rules for incoming traffic, which is what you want for bans. Point it elsewhere (e.g. FORWARD_direct) if the host is routing for the machines you are protecting.

type / kill

Same semantics as the iptables backend — reject is REJECT --reject-with icmp-port-unreachable / icmp6-port-unreachable per family, and kill runs the same protocol-scoped conntrack -D -s <ip> commands, exit codes ignored.

What each operation runs

With S4/S6 the sets and one <rule> per generated rule body:

| operation | commands | |------------|--------------------------------------------------------------------------------------| | init | cleanup (failures ok): firewall-cmd --direct --remove-rule <fam> filter <chain> 0 <rule> for each, ipset destroy S4/S6; then (fatal): ipset create S4/S6, firewall-cmd --direct --add-rule <fam> filter <chain> 0 <rule> for each | | ban | ipset add <S4\|S6> <ip>, then the conntrack kills if enabled | | unban | ipset del <S4\|S6> <ip> | | list | no command — the kur's own ban book | | check | firewall-cmd --state, ipset list S4, ipset list S6, and firewall-cmd --direct --query-rule ... per rule | | flush | ipset flush S4, ipset flush S6 (rules stay) | | re_init | teardown (best effort), init, re-add every banned IP | | teardown | --remove-rule per rule, then ipset destroy S4, ipset destroy S6 |

self_heal and reloads — the firewalld-specific point

Direct rules added this way are runtime only: firewall-cmd --reload or a daemon restart wipes them. The ipsets survive (ipset is its own kernel subsystem firewalld doesn't own), so the post-reload state is sets full, rules gone — every banned IP still listed, none of them actually blocked.

This is exactly what self_heal (on by default) exists for: check queries each rule individually, the next ban/unban notices the loss and re-inits, restoring the rules and re-banning from the kur's book. If reloads are frequent and bans are sparse, note the window between a reload and the next ban/unban — ereshkigal re-init [<kur>] closes it on demand.

Gotchas