go-freebsd-pf
Manage the FreeBSD PF firewall from Go through /dev/pf ioctls — rules, tables, states, anchors and atomic transactions, without pfctl.
Why
Programmatic control of firewall rules, tables and state from Go applications, without shelling out to pfctl.
How
- Three layers: a public API in Go types (
net.IP,net.IPNet, no C types leak out), a conversion layer, and one small C wrapper per ioctl so errno is captured. - cgo is required rather than optional:
pfvar.hstructs are full of nested unions, bitfields and version-specific layouts, and hand-computed layouts with rawsyscall.Ioctlwould be fragile. C helpers read and write union members that cgo only sees as byte arrays. - Atomic changes use
DIOCXBEGIN/DIOCXCOMMIT/DIOCXROLLBACKwith per-ruleset tickets:Begin,AddRule, thenCommitorRollback. - Every ioctl buffer is allocated with
C.malloc— the fix for cgo’s “Go pointer to unpinned Go pointer” panics. - A mutex guards the client; rules are validated before they reach the kernel.
Limits
Struct layouts come from the build host’s headers, so binaries must be built for the FreeBSD release they run on. State listing needs 14.x. It uses the legacy struct ioctls; the nvlist variants are where FreeBSD is heading.
tx, err := client.Begin(anchor, pf.RuleSetFilter)
if err := tx.AddRule(rule); err != nil { tx.Rollback(); log.Fatal(err) }
if err := tx.Commit(); err != nil { log.Fatal(err) }
$ git clone https://github.com/zombocoder/go-freebsd-pf← all projects