csf and apf both back a native config with a raw-iptables pre-hook, but a rule
their config could not express was reported as unsupported even when iptables
applies it directly. Route those shapes to the hook, and share the routing
predicates between the two backends.
Shared hook layer (hooks_linux.go): extract parseAddrFamily, listRow/listRows,
bareHostShape, hostNeedsHook, advRuleNeedsHook and bareProtoNeedsHook so both
backends route on the same predicates. MarshalAdvRule no longer returns an error
for shapes its caller has already routed away.
csf: replace checkConnLimit, checkICMP and checkPortProto with a single needsHook
gate mirroring apf's. A connection limit CONNLIMIT cannot express, an ICMP rule
the advanced-line format cannot carry (see nativeICMP), and a port on a protocol
its TCP_IN/UDP_IN lists cannot hold now reach the hook. A port on ProtocolAny has
no form in iptables either, so it stays on the native path and iptablesRuleValid
rejects it with ErrUnsupported.
csf: remove a deny from csf.deny whatever action the caller names, as apf does.
The file encodes no action of its own, so the deny of an address is a single
entry; the old early return reported success while csf kept enforcing it.
IPv6: ipv6Unavailable only rejects concrete-IPv6 rules, so a FamilyAny rule
passed the gate and then fanned out into an IPv6 row. With csf.conf's IPV6 or
conf.apf's USE_IPV6 off that row is never enforced, and in the hook it is worse
than inert: the pre-hook runs on every reload while neither backend flushes
ip6tables, so an injected ip6tables line is re-appended each time and outlives
its removal from the hook. Narrow both write-side fan-outs to the families the
backend enforces (hookScript.writeFamilies, writeFamilyRows). Removals stay wide
so a v6 line written while IPv6 was on is still swept once it is off.
Integration: the shared probes take the first variant a backend accepts, so
widening csf's accepted shapes silently moved icmp/icmptype/connlimit onto its
hook path. Add csf-gated subtests to keep the native csf.conf CONNLIMIT and
csf.allow advanced-rule paths covered, alongside one for action-agnostic
csf.deny removal.
Introduce TCPUDP as the protocol analog of FamilyAny and DirAny: a merged
value spanning both transports, distinct from ProtocolAny (which matches
every IP protocol and carries no port). Backends whose native syntax holds
both transports in one row (nftables, ufw, apf) store and read it as one
rule; the rest fan it out with expandProtocols. Removing one transport of a
merged row splits it via splitMergedRow, which composes the family and
protocol splits so an nftables row merged on both axes leaves a correct,
non-overlapping remainder. NAT rejects TCPUDP with ErrUnsupportedNAT.
Remove read-side merging. GetRules now reports the firewall's actual rows
and never synthesizes a FamilyAny, TCPUDP, or DirAny rule by pairing up
separately-stored ones, so mergeFamilies, mergeDirections and their helpers
are gone and mergedInsertIndex becomes logicalInsertIndex. Rules are instead
compared by coverage: the new exported Rule.Covers / Rule.CoveredBy (and the
NATRule pair) expand a rule across family, transport and direction and decide
containment cell by cell, which is what lets Sync stay a no-op against its
own output whichever representation a backend chose.
Extract the systemd/SysV service helpers out of the iptables backend into
services.go so every Linux backend shares one implementation, and document
the multi-state rule model and the coverage helpers in the README.
iptables: persist ipsets across reboot to match rule persistence.
- Detect the ipset save-file + restore unit (RHEL ipset.service /
/etc/sysconfig/ipset; Debian netfilter-persistent / /etc/iptables/ipsets),
non-fatally.
- After each set mutation, `ipset save` into the layout's save-file and
auto-enable a present-but-disabled restore unit; warn when no mechanism
exists (sets stay live-only).
- Use ListUnitFiles (not ListUnitFilesByPatterns, which needs systemd >= 230;
CentOS 7 ships 219).
APF/CSF: gain address sets by persisting ipset commands in the pre-hook.
- The hook carries an `ipset create/flush/add` block ordered ahead of the
`-m set --match-set` rule lines, so the firewall recreates the set on every
(re)start before any rule references it.
- Route set-referencing rules (Source/Destination names an ipset) through the
hook rather than a literal trust-file line (ruleNeedsHook/bareHostShape).
- Implement the six address-set methods, advertise AddressSets, and wire sets
into Backup/Restore via captureBackupState/restoreBackupSets.
Validated live: reboot simulation for iptables; generated-hook source for
APF/CSF. Unit tests cover the hook ipset round-trip, ordering, in-use guard and
set-ref routing; the capability-gated integration subtest now covers APF/CSF.