go-firewall/manager_linux.go
James Coleman 5095d90fa4 Advertise capability flags and harden backend semantics
- New Capabilities: PortPair, Negation, RejectAction,
  FamilyWithoutAddress, DenyActionFromConfig, advertised per backend.
- coversDirection isolates DirForward even when output is unowned;
  add splitNATDualRow so a concrete-family removal re-adds the opposite
  family's NAT translation.
- Resolve ip6tables/ufw ICMPv6 type aliases; ParseNATKind rejects the
  "invalid" sentinel as input while JSON round-trips it.
- Sync counts additions on mid-batch failure and uses RuleBatcher.
- NewManager runs a probe loop joining each backend's reason for
  diagnosability; services.go drops "generated" from enabled, handles it
  on enable, clears start-limit-hit on restart, and matches rc.local by
  token.
- nftables: per-source connection limits (meter set), quoted-token
  parsing preserving log-prefix spacing, digit-led prefix sanitizing.
- apf/csf: deny-action-from-config with cached STOP settings, port lists
  and inexpressible shapes routed through the pre-hook, confKeyApplies
  guard against a missing config line.
- atomic config writes fsync before rename and resolve symlinks;
  readConfValue is last-assignment-wins; runCommand preserves the exit
  code through the wrapped error.
- Move coreos/go-systemd to the maintained v22 module directly.
2026-07-13 17:50:43 -05:00

39 lines
1.5 KiB
Go

package firewall
import (
"context"
"errors"
"fmt"
)
// NewManager gets a firewall manager for this server, probing the higher-level
// managers first: firewalld, then ufw/csf/apf, then plain iptables, and
// nftables last, so a manager that is itself backed by iptables or nftables is
// preferred over managing its tables behind its back. The context bounds the
// detection probes (each shells out or opens a D-Bus/systemd connection).
func NewManager(ctx context.Context, rulePrefix string) (Manager, error) {
// A probe error usually means "not installed", but it can also be a
// transient failure on a host whose firewall IS that manager (a D-Bus
// hiccup with firewalld running); every probe's reason is carried in the
// final error so a mis-detection is diagnosable.
var errs []error
probes := []struct {
name string
try func() (Manager, error)
}{
{"firewalld", func() (Manager, error) { return NewFirewallD(ctx, rulePrefix) }},
{"ufw", func() (Manager, error) { return NewUFW(ctx, rulePrefix) }},
{"csf", func() (Manager, error) { return NewCSF(ctx, rulePrefix) }},
{"apf", func() (Manager, error) { return NewAPF(ctx, rulePrefix) }},
{"iptables", func() (Manager, error) { return NewIPTables(ctx, rulePrefix) }},
{"nftables", func() (Manager, error) { return NewNFT(ctx, rulePrefix) }},
}
for _, p := range probes {
mgr, err := p.try()
if err == nil {
return mgr, nil
}
errs = append(errs, fmt.Errorf("%s: %w", p.name, err))
}
return nil, fmt.Errorf("no firewall manager found: %w", errors.Join(errs...))
}