6.5 KiB
Architecture guide — one page
Português: docs/pt-BR/guide.md.
What this port is
A faithful port of the OpenBSD 8.0 PF engine running inside Linux
7.3.4 as one out-of-tree module, pf.ko. Nothing is faked: the matching,
state, table, normalization and queueing code is OpenBSD's, compiled under a
compatibility layer; control comes from the real pfctl speaking the real
OpenBSD ioctl ABI through /dev/pf. There is one global PF instance bound
to init_net — exactly like OpenBSD has one kernel; per-netns instances are
out of scope.
Build targets: OpenBSD 8.0 sources (sha256-pinned, VERSIONS), Linux 7.3.4
(linux-7.3.4), gcc >= 15, bison. pfvar.h v1.548 is shared bit-for-bit
between kernel and userland (ioctl encoding per module/include/pfk.h).
Components
| Path | Role |
|---|---|
module/pfsrc/ |
Ported OpenBSD files: pf.c (engine), pf_ioctl.c (DIOC* ABI), pf_norm.c (scrub/fragments), pf_table.c, pf_ruleset.c, pf_if.c, pf_lb.c (pools/NAT ports), pf_osfp.c, pf_syncookies.c, ifq.c/hfsc.c/fq_codel.c (staged for M5), if_pflog.c + ip_divert.c (staged references), divert_sock.c (divert entry stub), pfk_mod.c (load/exit, /dev/pf), pfk_hooks.c (netfilter data path) |
module/compat/ |
Linux plumbing under the BSD-shaped sources: mbuf-over-sk_buff shim (pfk_mbuf), kernel glue (malloc/pools/timeouts/locks, pfk_kern), interface+address views (pfk_if), BSD-semantics IP output/forward (pfk_ip4, pfk_ip6), socket lookups (pfk_pcb), ifq API with priq semantics (pfk_ifq), radix/route-table/crypto ports |
module/include/ |
pfk.h (master compat header + OpenBSD-style _IO* encoding), staged net/pfvar.h, pfk_mbuf.h, pfk_net.h, pfk_if.h |
tools/ |
Linux build of OpenBSD 8.0 pfctl (single binary), pdivert relay, ABI parity harness (abitest + make abi), offline parse tests (make test). Deltas vs upstream: tools/UPSTREAM.md |
scripts/ |
fetch-sources.sh, vendorize.sh, stage-port.sh (deterministic staging), build-kernel.sh, build-module.sh, vm-*.sh lab automation |
tests/vm/ |
VM suite gates 01-state.sh … 04-anchors.sh + lib.sh; tests/confs/ rulesets deployed to the lab |
patches/kernel/ |
Core patch set — currently empty (p1-needs.txt records why no exports were needed); see patches.md |
Data path (M2, as implemented today)
OpenBSD runs PF at IPL_NET in process-ish context; our compat locks are
sleepable, so the hook never calls pf_test() in softirq. Packets are
stolen onto an ordered workqueue (pfk_netisr) and the verdict is applied by
re-injection, tagged per direction via skb->mark so the survivor is not
re-queued by our own hook.
Linux 7.3.4, module pf.ko
NIC ──► netfilter (softirq — enqueue only)
PRE_ROUTING PF_IN ─┐ ┌─ LOCAL_OUT PF_OUT
POST_ROUTING PF_OUT ─┤ NF_STOLEN ├─ (runs AFTER conntrack)
LOCAL_IN/FORWARD: pass-through ┘ POST_ROUTING: NF_IP_PRI_LAST-1
│ (skb ref + kmalloc pfk_pkt;
│ alloc failure ⇒ NF_ACCEPT = fail-open)
▼
pfk_netisr workqueue (process context)
│
pfk_m_wrap_skb(skb)──► struct mbuf shim
│
pf_test(af, dir, ifp, &m) ◄── the real OpenBSD 8.0 engine:
│ scrub/normalize → state lookup
│ → rules/anchors/tables/quick
│ → state insert → verdict
▼
pass ──► mark |= PFK_DONE_IN/OUT ──► reinject: PF_IN: netif_rx() (survivor
of reassembly allowed through)
PF_OUT: skb_dst()->output()
block/drop ──► kfree_skb() (+ PF's own return-rst/ICMP generation)
fragment-buffered ──► our reference dropped; pf keeps the survivor (m == NULL)
Control plane: /dev/pf (miscdev, mode 0600, write gated on CAP_NET_ADMIN)
→ pfk_ioctl_io() copies the parameter struct sized from the OpenBSD _IOR/ _IOW encoding, dispatches into the ported pfioctl() (pf_ioctl.c) — the
full DIOC* ABI as upstream. pfctl -e/-f/-s/-t are that same OpenBSD binary,
rebuilt.
Queues, divert, pflog — today's observable behavior
- Queues:
pfctlparsesqueue/queue on(8.0 syntax); kernel-side the ifq API is implemented bycompat/pfk_ifq.cwith priq semantics only; thepfqclassful qdisc and the HFSC/FQ-CoDel back ends land in M5.ifq.c/hfsc.c/fq_codel.care staged and compile-ready but are not in the module object list yet. - Divert:
divert-to/diverttag evaluate to PF'sPF_DIVERTverdict;divert_packet()(pfsrc/divert_sock.c) currently drops the packet with a rate-limited kernel log because the AF_DIVERT socket family is not yet instantiated. Thepdivertrelay (userspace) exists and expectssocket(PF_DIVERT=32, SOCK_RAW, IPPROTO_DIVERT=254). - pflog:
pflog_packet()returns success but logs nothing (one-timepr_warn "packet not logged");if_pflog.cis staged, netlink back end is M4/M5.
Milestones (see feature-matrix.md for per-feature status)
| Milestone | Scope | Status (2026-11-11) |
|---|---|---|
| M0 | repo, scripts, kernel build, VM boots custom kernel | done |
| M1 | compat layer, pf.ko loads, /dev/pf, pfctl -f/-s info |
done |
| M2 | filter core: rules/states/tables/anchors VM gates | done — gates 01–04 green |
| M3 | NAT (nat-to/rdr-to/binat-to), scrub, pflog |
done — gates 05/06/07/09 (incl. 2 MB integrity through a redirect) and pflog gate 12 |
| M4 | route-to/dup-to/reply-to, pools, divert e2e | done — gates 08/10/13 and divert gate 11 |
| M5 | queues: ifq back ends (hfsc/fq_codel) | partial — back ends compiled into pf.ko; queue attachment/pacing pending |
| M6 | docs complete, patchset tidy, full suite green, tagged releases | in progress — docs EN+PT-BR complete, 14/14 suite green, tagged |
Next reads
build.md → vm-lab.md → pf-guide.md → feature-matrix.md → caveats.md.