102 lines
6.3 KiB
Markdown
102 lines
6.3 KiB
Markdown
# Architecture guide — one page
|
||
|
||
Português: [docs/pt-BR/guide.md](../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](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/fail-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](build.md) → [vm-lab.md](vm-lab.md) → [pf-guide.md](pf-guide.md) →
|
||
[feature-matrix.md](feature-matrix.md) → [caveats.md](caveats.md).
|