Files

102 lines
6.5 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 | **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](build.md) → [vm-lab.md](vm-lab.md) → [pf-guide.md](pf-guide.md) →
[feature-matrix.md](feature-matrix.md) → [caveats.md](caveats.md).