Files
pf-linux-port/docs/en/guide.md
T

6.3 KiB
Raw Blame History

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: pfctl parses queue/queue on (8.0 syntax); kernel-side the ifq API is implemented by compat/pfk_ifq.c with priq semantics only; the pfq classful qdisc and the HFSC/FQ-CoDel back ends land in M5. ifq.c/hfsc.c/fq_codel.c are staged and compile-ready but are not in the module object list yet.
  • Divert: divert-to/divert tag evaluate to PF's PF_DIVERT verdict; 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. The pdivert relay (userspace) exists and expects socket(PF_DIVERT=32, SOCK_RAW, IPPROTO_DIVERT=254).
  • pflog: pflog_packet() returns success but logs nothing (one-time pr_warn "packet not logged"); if_pflog.c is 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 in progress — data path implemented, gates 01–04 not all green yet
M3 NAT (nat/rdr/binat), scrub e2e, pflog pending
M4 route-to/dup-to, divert e2e pending
M5 queues: ifq back ends (hfsc/fq_codel) + pfq qdisc pending
M6 docs complete, patchset tidy, full suite green, tagged releases in progress (this doc set)

Next reads

build.md → vm-lab.md → pf-guide.md → feature-matrix.md → caveats.md.