« All posts

PFL Reimagines OpenBSD's pf Firewall as a Linux eBPF/XDP Dataplane

pfSense's creator built PFL, porting OpenBSD's pf firewall language onto Linux XDP. Here's how stateful filtering and NAT survive the eBPF verifier's strict constraints.

PFL is an experimental project from pfSense's original creator that ports OpenBSD's pf firewall language and stateful semantics onto Linux, but targets XDP instead of the traditional netfilter path. The goal isn't to replace pfSense but to test how much of pf's rich feature set—NAT, state tracking, scrub normalization, anchors, policy routing—can survive translation into a verifier-accepted XDP program, given XDP's driver-level execution ahead of socket buffer allocation.

The architecture splits into pfld, a Rust control plane that parses pf.conf into BPF maps, and a ~7,000-line Rust/aya dataplane. Because a single XDP program can't fit full firewall logic under the verifier's 512-byte stack limit, the pipeline is broken into tail-called stages—PARSE, SCRUB, CONNTRACK, a 16-way FILTER, NAT, ROUTE, LOG—linked through BPF program arrays, with an 80-byte scratchpad carried in packet metadata to avoid redundant computation between stages. Established connections are tracked via LRU hash maps running a genuine TCP state machine, giving in-state packets a fast path: one map lookup, in-place state mutation, incremental RFC 1624 checksum updates, and a cached FIB/L2 forwarding decision.

Most of the engineering effort went into making correct firewall logic acceptable to the eBPF verifier rather than inventing new packet-processing primitives: isolating stack-heavy operations like NAT and IPv6 handling into separate tail-called programs, replacing unbounded loops with compile-time-bounded rule chunks, and pinning a reproducible Rust/aya/LLVM-21 toolchain. The result—25 programs loading and attaching in native driver mode on Linux 7.0—demonstrates that a pf-grade policy language can be reimplemented as a general-purpose, in-kernel, stateful XDP firewall.