go-firewall/integration_linux_test.go
James Coleman a036c8e6e9 Add TCPUDP protocol, coverage relation, and drop read-side merging
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.
2026-07-09 17:52:19 -05:00

41 lines
1.6 KiB
Go

//go:build integration
package firewall
import (
"context"
"testing"
)
// linuxBackends lists every Linux backend in the same order NewManager probes them.
func linuxBackends() []backendFactory {
return []backendFactory{
{"firewalld", func(ctx context.Context, p string) (Manager, error) { return NewFirewallD(ctx, p) }},
{"ufw", func(ctx context.Context, p string) (Manager, error) { return NewUFW(ctx, p) }},
{"csf", func(ctx context.Context, p string) (Manager, error) { return NewCSF(ctx, p) }},
{"apf", func(ctx context.Context, p string) (Manager, error) { return NewAPF(ctx, p) }},
{"iptables", func(ctx context.Context, p string) (Manager, error) { return NewIPTables(ctx, p) }},
{"nft", func(ctx context.Context, p string) (Manager, error) { return NewNFT(ctx, p) }},
}
}
// TestIntegration runs the capability-driven suite against the Linux backends.
// See integration_test.go for the shared suite and runIntegration.
func TestIntegration(t *testing.T) {
runIntegration(t, linuxBackends())
}
// hookPlanter returns a function that writes a rule directly into the backend's
// raw-iptables pre-hook, standing in for a copy a customer added by hand, or nil when
// the backend has no pre-hook. Only csf and apf carry one. It lives here rather than
// in the shared suite because it names the Linux-only backend types, which do not
// compile for the pf and Windows targets.
func hookPlanter(mgr Manager) func(*Rule) error {
switch b := mgr.(type) {
case *APF:
return func(r *Rule) error { _, err := b.hook().edit(r, false); return err }
case *CSF:
return func(r *Rule) error { _, err := b.hook().edit(r, false); return err }
}
return nil
}