AF_DIVERT family restored & compiling: full socket family (PF_DIVERT=32, sock_register), blocking recv/poll, divert_output-compatible sendmsg reinject, mtag type passthrough; pfk_api decls
This commit is contained in:
1 parent
fe6f767561
commit
ac56534c3b
30 files changed
+2273
-62
No files matched your search
@@ -1,15 +1,29 @@
|
||||
# PF on Linux
|
||||
|
||||
Port of the OpenBSD **PF** (packet filter) to **Linux 7.3.4** as out-of-tree
|
||||
kernel modules — the real PF engine (`pf.c`, `pf_norm.c`, `pf_table.c`,
|
||||
`pf_lb.c`, … ported from OpenBSD 8.0 sources) driven by the real OpenBSD
|
||||
`pfctl` over the `/dev/pf` ioctl ABI.
|
||||
Port of the OpenBSD **PF** (packet filter) to **Linux 7.3.4** as an
|
||||
out-of-tree kernel module — the real PF engine (`pf.c`, `pf_norm.c`,
|
||||
`pf_table.c`, `pf_lb.c`, … ported from OpenBSD 8.0 sources) driven by the
|
||||
real OpenBSD `pfctl` over the `/dev/pf` ioctl ABI. (A second module for the
|
||||
`pfq` qdisc arrives with M5.)
|
||||
|
||||
**Status:** implementation in progress — see
|
||||
[`docs/superpowers/plans/2026-11-11-pf-linux-port.md`](docs/superpowers/plans/2026-11-11-pf-linux-port.md)
|
||||
for task progress and
|
||||
[`docs/superpowers/specs/2026-11-11-pf-linux-port-design.md`](docs/superpowers/specs/2026-11-11-pf-linux-port-design.md)
|
||||
for the design.
|
||||
**Status:** early milestones. The engine port and control plane (M1) are
|
||||
working; the M2 filter core is being gated in the VM lab; everything later
|
||||
is pending. Honest per-feature status:
|
||||
[`docs/en/feature-matrix.md`](docs/en/feature-matrix.md).
|
||||
Design: [`docs/superpowers/specs/2026-11-11-pf-linux-port-design.md`](docs/superpowers/specs/2026-11-11-pf-linux-port-design.md)
|
||||
· plan: [`docs/superpowers/plans/2026-11-11-pf-linux-port.md`](docs/superpowers/plans/2026-11-11-pf-linux-port.md).
|
||||
|
||||
## Milestones
|
||||
|
||||
| Milestone | Scope | Status (2026-11-11) |
|
||||
|---|---|---|
|
||||
| M0 | repo, scripts, kernel build, VM boots the 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 01–04 | **in progress** — data path implemented, gates 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: HFSC/FQ-CoDel back ends + `pfq` qdisc | pending |
|
||||
| M6 | docs complete, patchset tidy, full suite green, tagged release | in progress (docs) |
|
||||
|
||||
## What you get (planned)
|
||||
|
||||
@@ -20,7 +34,7 @@ for the design.
|
||||
| NAT | `nat-to` (port pools, sticky-port, static-port), `rdr-to`, `binat-to` |
|
||||
| Scrub | fragment reassembly, overlap drop, `min-ttl`/`max-mss`/`df`/`no-df`, `set reassemble` |
|
||||
| QoS (ALTQ successor) | `queue`/`queue on` with OpenBSD 8.0 `ifq` schedulers: **default (priq), HFSC, FQ-CoDel**, attached via the `pfq` qdisc; `pfctl -s queue -v` stats |
|
||||
| Divert | `divert-to` / `divert` tag + `pdivert` userspace channel (netlink; Linux has no `AF_DIVERT`) |
|
||||
| Divert | `divert-to` / `divert` tag + `pdivert` relay over an in-module `PF_DIVERT` socket family (pending — see `docs/en/feature-matrix.md`) |
|
||||
| Route | `route-to` (pools rr/hash-source/hash-state, `fail-to`), `reply-to`, `dup-to` |
|
||||
| Logging | `pflogd` via netlink pflog channel (DLT_PFLOG pcap) |
|
||||
| Control | `/dev/pf` (0600), OpenBSD 8.0 ioctl ABI, Linux build of `pfctl` (single binary) |
|
||||
@@ -29,24 +43,35 @@ Not in scope: `pfsync`/CARP failover, IPsec/pfkey, `ftp-proxy`, per-netns PF
|
||||
instances, classic-ALTQ-only schedulers (cbq/wfq/red — removed upstream before
|
||||
OpenBSD 8.0).
|
||||
|
||||
## Quick start (after M1+)
|
||||
## Quick start
|
||||
|
||||
Full recipe: [`docs/en/install.md`](docs/en/install.md). Short version
|
||||
(commands are read from the scripts; a fresh-clone end-to-end run is still
|
||||
pending):
|
||||
|
||||
```sh
|
||||
./scripts/fetch-sources.sh # OpenBSD 8.0 sources (C3SL mirror, checksummed)
|
||||
./scripts/apply-patches.sh ../linux-7.3.4 # core export patch(es)
|
||||
make module tools # pf.ko, pfq.ko, pfctl, pdivert, pflogd
|
||||
sudo insmod module/pf.ko module/pfq.ko
|
||||
./scripts/fetch-sources.sh # OpenBSD 8.0 sources (C3SL mirror, sha256-pinned)
|
||||
./scripts/vendorize.sh # stage the pristine files we port
|
||||
./scripts/build-kernel.sh # kernel 7.3.4: bzImage + modules_prepare (export patch: none needed)
|
||||
./scripts/build-module.sh # -> module/pf.ko
|
||||
make -C tools # -> tools/out/pfctl (static), ABI harness
|
||||
sudo insmod module/pf.ko # /dev/pf (0600) + netfilter hooks
|
||||
sudo pfctl -f /etc/pf.conf && sudo pfctl -e
|
||||
```
|
||||
|
||||
VM validation lab (Alpine on libvirt): see `docs/en/vm-lab.md`.
|
||||
Documentação em português: [`README.pt-BR.md`](README.pt-BR.md) e `docs/pt-BR/`.
|
||||
Read [`docs/en/pf-guide.md`](docs/en/pf-guide.md) **before** enabling PF on a
|
||||
path you need: only the M2 filter core is gated today; NAT/scrub/queues/
|
||||
divert/pflog are not usable yet ([`docs/en/feature-matrix.md`](docs/en/feature-matrix.md)).
|
||||
|
||||
VM validation lab (Alpine on libvirt): [`docs/en/vm-lab.md`](docs/en/vm-lab.md).
|
||||
Documentação em português: [`README.pt-BR.md`](README.pt-BR.md) e
|
||||
[`docs/pt-BR/`](docs/pt-BR/README.md).
|
||||
|
||||
## Layout
|
||||
|
||||
- `module/` — kernel sources (ported PF + compat layer + pfq qdisc)
|
||||
- `tools/` — pfctl/pdivert/pflogd Linux builds
|
||||
- `patches/kernel/` — documented Linux core patches (export symbols only)
|
||||
- `module/` — kernel sources (ported PF + compat layer; the `pfq` qdisc lands with M5)
|
||||
- `tools/` — pfctl (static Linux build), pdivert relay source, ABI harness (`pflogd` not ported yet)
|
||||
- `patches/kernel/` — Linux core patch set (currently empty; evidence in `p1-needs.txt`, see `docs/en/patches.md`)
|
||||
- `scripts/` — fetch/build/patch/VM automation
|
||||
- `tests/` — VM suite (`tests/vm/NN-*.sh`, scapy helpers)
|
||||
- `docs/en/` + `docs/pt-BR/` — full bilingual documentation
|
||||
|
||||
+46
-18
@@ -1,13 +1,29 @@
|
||||
# PF no Linux
|
||||
|
||||
Port do **PF** (packet filter) do OpenBSD para o **Linux 7.3.4** como módulos
|
||||
out-of-tree — o engine PF real (`pf.c`, `pf_norm.c`, `pf_table.c`, `pf_lb.c`, …
|
||||
portados dos fontes do OpenBSD 8.0) controlado pelo `pfctl` real do OpenBSD via
|
||||
a ABI ioctl de `/dev/pf`.
|
||||
Port do **PF** (packet filter) do OpenBSD para o **Linux 7.3.4** como um
|
||||
módulo de kernel out-of-tree — o engine PF real (`pf.c`, `pf_norm.c`,
|
||||
`pf_table.c`, `pf_lb.c`, … portados dos fontes do OpenBSD 8.0) controlado
|
||||
pelo `pfctl` real do OpenBSD via a ABI ioctl de `/dev/pf`. (Um segundo
|
||||
módulo para o qdisc `pfq` chega com o M5.)
|
||||
|
||||
**Status:** implementação em andamento — veja o
|
||||
[plano de implementação](docs/superpowers/plans/2026-11-11-pf-linux-port.md) e o
|
||||
[design doc](docs/superpowers/specs/2026-11-11-pf-linux-port-design.md).
|
||||
**Status:** milestones iniciais. O engine portado e o plano de controle (M1)
|
||||
funcionam; o núcleo de filtragem do M2 está em validação na bancada de VMs;
|
||||
todo o resto está pendente. Status honesto por feature:
|
||||
[`docs/pt-BR/feature-matrix.md`](docs/pt-BR/feature-matrix.md).
|
||||
Design: [`docs/superpowers/specs/2026-11-11-pf-linux-port-design.md`](docs/superpowers/specs/2026-11-11-pf-linux-port-design.md)
|
||||
· plano: [`docs/superpowers/plans/2026-11-11-pf-linux-port.md`](docs/superpowers/plans/2026-11-11-pf-linux-port.md).
|
||||
|
||||
## Milestones
|
||||
|
||||
| Milestone | Escopo | Status (2026-11-11) |
|
||||
|---|---|---|
|
||||
| M0 | repo, scripts, build do kernel, VM sobe com kernel próprio | concluído |
|
||||
| M1 | camada de compat, `pf.ko` carrega, `/dev/pf`, `pfctl -f` / `-s info` | concluído |
|
||||
| M2 | núcleo de filtragem: regras/estados/tabelas/anchors, gates VM 01–04 | **em andamento** — caminho de dados implementado, gates ainda não todos verdes |
|
||||
| M3 | NAT (nat/rdr/binat), scrub e2e, pflog | pendente |
|
||||
| M4 | route-to/dup-to/fail-to, divert e2e | pendente |
|
||||
| M5 | filas: back ends HFSC/FQ-CoDel + qdisc `pfq` | pendente |
|
||||
| M6 | docs completas, patchset arrumado, suíte inteira verde, release com tag | em andamento (docs) |
|
||||
|
||||
## O que você obtém (planejado)
|
||||
|
||||
@@ -18,7 +34,7 @@ a ABI ioctl de `/dev/pf`.
|
||||
| NAT | `nat-to` (pools de portas, sticky-port, static-port), `rdr-to`, `binat-to` |
|
||||
| Scrub | remontagmento de fragmentos, drop por overlap, `min-ttl`/`max-mss`/`df`/`no-df`, `set reassemble` |
|
||||
| QoS (sucessor do ALTQ) | `queue`/`queue on` com os agendadores `ifq` do OpenBSD 8.0: **default (priq), HFSC, FQ-CoDel**, anexados via qdisc `pfq`; estatísticas `pfctl -s queue -v` |
|
||||
| Divert | `divert-to` / tag `divert` + canal userspace `pdivert` (netlink; Linux não tem `AF_DIVERT`) |
|
||||
| Divert | `divert-to` / tag `divert` + relay `pdivert` sobre uma família de sockets `PF_DIVERT` dentro do módulo (pendente — ver `docs/pt-BR/feature-matrix.md`) |
|
||||
| Roteamento | `route-to` (pools rr/hash-source/hash-state, `fail-to`), `reply-to`, `dup-to` |
|
||||
| Logging | `pflogd` via canal netlink pflog (pcap DLT_PFLOG) |
|
||||
| Controle | `/dev/pf` (0600), ABI ioctl do OpenBSD 8.0, `pfctl` compilado para Linux (binário único) |
|
||||
@@ -27,24 +43,36 @@ Fora de escopo: failover `pfsync`/CARP, IPsec/pfkey, `ftp-proxy`, instâncias PF
|
||||
por netns, agendadores exclusivos do ALTQ clássico (cbq/wfq/red — removidos do
|
||||
OpenBSD antes da 8.0).
|
||||
|
||||
## Início rápido (após M1+)
|
||||
## Início rápido
|
||||
|
||||
Receita completa: [`docs/pt-BR/install.md`](docs/pt-BR/install.md). Versão
|
||||
curta (comandos lidos dos scripts; a execução de ponta a ponta a partir de
|
||||
clone limpo ainda está pendente):
|
||||
|
||||
```sh
|
||||
./scripts/fetch-sources.sh # fontes OpenBSD 8.0 (mirror C3SL, com checksum)
|
||||
./scripts/apply-patches.sh ../linux-7.3.4 # patch(es) de core (exports)
|
||||
make module tools # pf.ko, pfq.ko, pfctl, pdivert, pflogd
|
||||
sudo insmod module/pf.ko module/pfq.ko
|
||||
./scripts/fetch-sources.sh # fontes OpenBSD 8.0 (mirror C3SL, sha256 fixado)
|
||||
./scripts/vendorize.sh # staging dos arquivos upstream portados
|
||||
./scripts/build-kernel.sh # kernel 7.3.4: bzImage + modules_prepare (patch de export: nenhum necessário)
|
||||
./scripts/build-module.sh # -> module/pf.ko
|
||||
make -C tools # -> tools/out/pfctl (estático), verificador de ABI
|
||||
sudo insmod module/pf.ko # /dev/pf (0600) + hooks netfilter
|
||||
sudo pfctl -f /etc/pf.conf && sudo pfctl -e
|
||||
```
|
||||
|
||||
Lab de validação em VMs (Alpine via libvirt): `docs/pt-BR/vm-lab.md`.
|
||||
Documentation in English: [`README.md`](README.md) and `docs/en/`.
|
||||
Leia [`docs/pt-BR/pf-guide.md`](docs/pt-BR/pf-guide.md) **antes** de habilitar
|
||||
o PF num caminho que você precisa: só o núcleo de filtragem do M2 tem gate
|
||||
hoje; NAT/scrub/filas/divert/pflog ainda não são utilizáveis
|
||||
([`docs/pt-BR/feature-matrix.md`](docs/pt-BR/feature-matrix.md)).
|
||||
|
||||
Lab de validação em VMs (Alpine via libvirt): [`docs/pt-BR/vm-lab.md`](docs/pt-BR/vm-lab.md).
|
||||
English documentation: [`README.md`](README.md) e
|
||||
[`docs/en/`](docs/en/README.md).
|
||||
|
||||
## Estrutura
|
||||
|
||||
- `module/` — fontes de kernel (PF portado + camada de compat + qdisc pfq)
|
||||
- `tools/` — builds Linux de pfctl/pdivert/pflogd
|
||||
- `patches/kernel/` — patches documentados no core do Linux (apenas exports)
|
||||
- `module/` — fontes de kernel (PF portado + camada de compat; o qdisc `pfq` chega com o M5)
|
||||
- `tools/` — pfctl (build estático Linux), fonte do relay pdivert, verificador de ABI (`pflogd` ainda não portado)
|
||||
- `patches/kernel/` — conjunto de patches de core do Linux (hoje vazio; evidência em `p1-needs.txt`, ver `docs/pt-BR/patches.md`)
|
||||
- `scripts/` — automação de fetch/build/patch/VM
|
||||
- `tests/` — suíte em VM (`tests/vm/NN-*.sh`, helpers scapy)
|
||||
- `docs/en/` + `docs/pt-BR/` — documentação completa bilíngue
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
# PF-on-Linux documentation (English)
|
||||
|
||||
Documentação em português: [`docs/pt-BR/README.md`](../pt-BR/README.md).
|
||||
|
||||
This is the user documentation set for the port of the **OpenBSD 8.0 PF
|
||||
packet filter** to **Linux 7.3.4** — the real PF engine (`pf.c`, `pf_norm.c`,
|
||||
`pf_table.c`, `pf_lb.c`, …) running as the out-of-tree module `pf.ko`, driven
|
||||
by the real OpenBSD `pfctl` over the `/dev/pf` ioctl ABI.
|
||||
|
||||
## Index
|
||||
|
||||
| Doc | What it covers |
|
||||
|---|---|
|
||||
| [guide.md](guide.md) | One-page architecture: components, data path, control plane, milestones |
|
||||
| [install.md](install.md) | Apply/build/install recipe (kernel, module, tools) |
|
||||
| [build.md](build.md) | Build pipeline in detail: fetch → vendorize → kernel → module → pfctl |
|
||||
| [vm-lab.md](vm-lab.md) | The libvirt validation lab: topology, recreate/run, what each gate proves |
|
||||
| [feature-matrix.md](feature-matrix.md) | Per-feature status: VERIFIED-in-VM / COMPILES-untested / PENDING-Mx |
|
||||
| [pf-guide.md](pf-guide.md) | Daily use: what works today in `/etc/pf.conf`, what to avoid until M3/M4 |
|
||||
| [patches.md](patches.md) | Linux core patch set (currently empty) + rules and template for future `000N-*.patch` |
|
||||
| [caveats.md](caveats.md) | Known divergences from OpenBSD behavior and current limitations |
|
||||
|
||||
## Sources of truth
|
||||
|
||||
The docs describe the code as it exists in this tree. When in doubt, the
|
||||
authoritative references are:
|
||||
|
||||
- `docs/superpowers/specs/2026-11-11-pf-linux-port-design.md` — design
|
||||
- `docs/superpowers/plans/2026-11-11-pf-linux-port.md` — milestone plan (tasks T1–T19)
|
||||
- `docs/porting-notes.md` — staging log + M1 deviation record (the truth about compat-layer divergences)
|
||||
|
||||
Feature-status vocabulary used across these docs:
|
||||
|
||||
- **VERIFIED-in-VM** — exercised end-to-end in the libvirt lab (see vm-lab.md).
|
||||
- **COMPILES-untested** — the code is built into `pf.ko` but no gate has proven the behavior yet.
|
||||
- **PENDING-Mx** — lands (or lands fully) in milestone Mx; current observable behavior is documented.
|
||||
@@ -0,0 +1,116 @@
|
||||
# Build pipeline in detail
|
||||
|
||||
Português: [docs/pt-BR/build.md](../pt-BR/build.md).
|
||||
Quick recipe: [install.md](install.md). This page explains *what each stage
|
||||
does* and *what has actually been exercised*.
|
||||
|
||||
Everything below runs on the build host (the laptop); the VM lab (separate
|
||||
host "deb") only receives artifacts — see [vm-lab.md](vm-lab.md).
|
||||
|
||||
## 0. Prerequisites
|
||||
|
||||
From `docs/en/install.md`: gcc ≥ 15, GNU make, bison (flex is not needed —
|
||||
OpenBSD `parse.y` carries a hand-written `yylex`), `bc`, rsync, `glibc-static`
|
||||
for the static pfctl, root for `insmod`. Kernel tree expected at
|
||||
`../linux-7.3.4` (override with `KERNELDIR=`; the root `Makefile` defaults to
|
||||
`$(HOME)/lab/linux_kernel/linux-7.3.4`).
|
||||
|
||||
## 1. Fetch + verify OpenBSD sources — `scripts/fetch-sources.sh`
|
||||
|
||||
- Downloads `sys.tar.gz` and `src.tar.gz` from the primary mirror
|
||||
`https://openbsd.c3sl.ufpr.br/pub/OpenBSD/8.0/`, falling back to
|
||||
`cdn.openbsd.org`.
|
||||
- Verifies sha256 against the pins in `VERSIONS` (sys: `964b1aed…`, src:
|
||||
`9984c74f…`); an already-cached tarball is skipped when its hash matches.
|
||||
- Landing zone: `_vendor/openbsd/` (gitignored).
|
||||
|
||||
## 2. Vendorize — `scripts/vendorize.sh`
|
||||
|
||||
Extracts **only the files the port consumes** into `_vendor/upstream/`
|
||||
(gitignored): the `sys/net/pf*.c|h`, `ifq/hfsc/fq_codel/ip_pflog/ip_divert`,
|
||||
`radix/rtable/toeplitz`, `crypto/{md5,sha2,siphash}`, `sys/sys/*.h` list and
|
||||
`sbin/pfctl/*` + headers (see the `SYS_K`/`SRC_K` arrays). It then records
|
||||
each file's `$OpenBSD: …$` tag into `_vendor/upstream/IDS.txt` — the forensic
|
||||
baseline for every ported line and for future rebases.
|
||||
|
||||
## 3. Staging into the port — `scripts/stage-port.sh` (deterministic porting)
|
||||
|
||||
`stage-port.sh` turns pristine upstream files into the files under `module/`:
|
||||
|
||||
1. every `#include` is replaced by `pfk.h` + the staged `pfvar` headers
|
||||
(kernel branches selected with `-D_KERNEL=1`);
|
||||
2. the shared rename program (`pf_sed`) resolves BSD↔Linux name collisions
|
||||
(`struct ip` → `struct pf_ip`, `ifnet` → `pfk_ifnet`, `ip_output` →
|
||||
`pfk_ip_output`, `sa_len` assignments stripped, …);
|
||||
3. **each run is appended to `docs/porting-notes.md`** — that file is the
|
||||
staging log, not prose.
|
||||
|
||||
Ported files are then committed and hand-edited in-tree; re-staging is only
|
||||
for forensics (`stage-port.sh --diff <file>` compares our copy against the
|
||||
pristine upstream text). Radix/rtable/siphash/toeplitz are deliberately not
|
||||
auto-staged (hand-ported).
|
||||
|
||||
## 4. Kernel — `scripts/build-kernel.sh` (+ `kernel-config.sh`)
|
||||
|
||||
- `kernel-config.sh`: starts from `defconfig` if there is no `.config`, then
|
||||
sets (idempotently, via `scripts/config` + `make olddefconfig`):
|
||||
`VIRTIO_BLK/PCI/NET=y`, `EXT4_FS=y`, `NETFILTER=y`, `NETFILTER_ADVANCED`,
|
||||
`NET_SCHED=y`, `INET=y`, `IPV6=y`, `SERIAL_8250(+CONSOLE)=y` — i.e.
|
||||
everything needed to direct-boot the pf0 VM with **no initrd**.
|
||||
`kernel-config.sh test` additionally enables `DEBUG_INFO` +
|
||||
`DEBUG_KMEMLEAK` for soak builds.
|
||||
- `build-kernel.sh`: `make -j(nproc-2) bzImage modules_prepare`, ensures
|
||||
`Module.symvers` (copies `vmlinux.symvers` — kbuild does not write the
|
||||
former) and `scripts/module.lds` exist, then drops boot artifacts in
|
||||
`out/`: `linux-<ver>-bzImage`, `System.map`, `.config`, `kernelversion`,
|
||||
`bzImage.sha256`. Prints `KERNEL_BUILD_OK <kernelrelease>`.
|
||||
- **Kernel core patches are currently none**: `patches/kernel/` holds no
|
||||
`000N-*.patch` (see [patches.md](patches.md)).
|
||||
|
||||
## 5. Module — `scripts/build-module.sh`
|
||||
|
||||
`make -C "$KERNELDIR" M=module` → `module/pf.ko` (single module today; the
|
||||
`pfq` qdisc arrives with M5). The script self-prepares `Module.symvers` /
|
||||
`modules_prepare` first. Object list (`module/Makefile`): ported engine
|
||||
(`pf.c`, `pf_ioctl.c`, `pf_norm.c`, `pf_if.c`, `pf_lb.c`, `pf_ruleset.c`,
|
||||
`pf_table.c`, `pf_osfp.c`, `pf_syncookies.c`, `divert_sock.c`) + compat layer
|
||||
(mbuf shim, kern glue, if/ip4/ip6/pcb/xmit/ifq, crypto, radix, subr_tree) +
|
||||
`pfk_mod.c`/`pfk_hooks.c`. Compile flags define `PFLINUX=1` and `_KERNEL=1`.
|
||||
Prints `MODULE_BUILD_OK`.
|
||||
|
||||
`insmod module/pf.ko` creates `/dev/pf` (0600) and registers the netfilter
|
||||
hooks; `dmesg` shows `pfk: …` lines (see [caveats.md](caveats.md) for what
|
||||
the data path does).
|
||||
|
||||
## 6. Userland — `make -C tools`
|
||||
|
||||
Targets (from `tools/Makefile`): `pfctl`, `abitest`, `abi`, `test`, `clean`.
|
||||
|
||||
- `pfctl` — the OpenBSD 8.0 sources compiled against the copied ABI headers;
|
||||
result is a **static binary** at `tools/out/pfctl`. Every deviation from
|
||||
upstream is listed in `tools/UPSTREAM.md` (7 marked edits; the rest is the
|
||||
compat header/source set).
|
||||
- `abi` — builds `abitest` (userland) and `abitest-module` (same table under
|
||||
kernel compat headers), dumps both struct-size/offset tables and `diff`s
|
||||
them; a mismatch means the ioctl ABI drifted. `out/abi-*.tbl` are the
|
||||
reference inputs.
|
||||
- `test` — offline parse checks: runs `pfctl -n -f` over `tools/tests/*.conf`
|
||||
(basic / translations / queues+scrub / anchor / negative `broken.conf`).
|
||||
This is pure userland; it needs no `/dev/pf`.
|
||||
- `pdivert` — divert relay source under `tools/pdivert/`; see
|
||||
[feature-matrix.md](feature-matrix.md) for its (pending) kernel side.
|
||||
|
||||
## 7. Root `Makefile`
|
||||
|
||||
`make module | tools | kernel | vm | vm-destroy | test | docs | clean` — thin
|
||||
wrappers over the scripts above. `make test` runs the VM suite
|
||||
([vm-lab.md](vm-lab.md)); `make docs` just lists the bilingual doc set.
|
||||
|
||||
## What has been exercised vs. not
|
||||
|
||||
- Stages 1–6 have each been run during M0–M2 development on the build host
|
||||
(artifacts exist in-tree: `out/`, `module/pf.ko`, `tools/out/pfctl`), and
|
||||
the scripts above were verified line-by-line for this document.
|
||||
- A **fresh-clone end-to-end run** (`quickstart-check.sh`, plan task T19) has
|
||||
*not* been executed yet; treat the exact command sequence in install.md as
|
||||
"as designed, last-run state per milestone" rather than CI-verified.
|
||||
@@ -0,0 +1,137 @@
|
||||
# Caveats — divergences from OpenBSD behavior
|
||||
|
||||
Português: [docs/pt-BR/caveats.md](../pt-BR/caveats.md).
|
||||
|
||||
These are the ways this port differs from running the same ruleset on
|
||||
OpenBSD 8.0, or is not yet production-shaped. Everything here is grounded in
|
||||
the code (`module/pfsrc/pfk_hooks.c`, `module/pfsrc/pfk_mod.c`,
|
||||
`docs/porting-notes.md`) and in lab observations; nothing is speculative.
|
||||
|
||||
## Data path
|
||||
|
||||
### 1. PF runs in process context via a workqueue (netisr model), not in the packet fast path
|
||||
|
||||
OpenBSD evaluates `pf_test()` on the CPU that received the packet, at
|
||||
`IPL_NET`, with a non-sleeping NET read lock. Linux gives us softirq context
|
||||
and *sleepable* compat locks (rw-semaphores), so the hook never calls
|
||||
`pf_test()` inline: it steals the skb (`NF_STOLEN`) onto an **ordered
|
||||
workqueue** (`pfk_netisr`, `WQ_MEM_RECLAIM`) and runs the engine in process
|
||||
context, then re-injects (`netif_rx()` for input, `dst->output()` for
|
||||
output/transit).
|
||||
|
||||
Consequences, all observable:
|
||||
|
||||
- **Verdicts are asynchronous.** Anything chained after our hook on the same
|
||||
netfilter chain (other iptables/nft rules at higher priorities) does not
|
||||
see the packet until the reinjected pass comes through. Counters and log
|
||||
interleaving differ accordingly.
|
||||
- **Latency + throughput**: every packet takes a work-queue round trip
|
||||
serialized on one ordered worker. Functional gates are the current bar;
|
||||
the planned `bench-basic.sh` is informational only.
|
||||
- **Reordering**: the ordered queue preserves arrival order of *queued*
|
||||
work items, but the stack around it (RPS, multi-NAPI) can still hand us
|
||||
packets whose original completion order differs from reinjection order.
|
||||
|
||||
### 2. Re-entry protection is a per-direction tag in `skb->mark`
|
||||
|
||||
The reinjected survivor carries `skb->mark |= PFK_DONE_IN (0x50460010)` or
|
||||
`PFK_DONE_OUT (0x50460020)`; our hooks accept any packet already tagged for
|
||||
that direction instead of re-queuing it (otherwise PF_IN → netif_rx →
|
||||
PF_IN… would loop). `skb->mark` is a **shared, writable 32-bit field**: any
|
||||
subsystem that *overwrites* it — nft `meta mark set`, `CONNMARK --restore`,
|
||||
tc filters, some VPN stacks — can either (a) clear our tag and cause the
|
||||
packet to be **re-queued through PF again**, or (b) coincidentally set those
|
||||
bits and let a packet **skip** a PF pass. Practical rule on a PF-on-Linux
|
||||
host: do not use fwmark mangling on the same traffic; if you must, mask the
|
||||
PF tag bits out of your mark space.
|
||||
|
||||
### 3. No NET-lock on the data path (M2 interim)
|
||||
|
||||
`pf_test()` currently runs **without** the compat NET read lock (comment in
|
||||
`pfk_hooks.c`): with the workqueue model, a parked control writer (ruleset
|
||||
change, purge ioctl) starved the packet side badly enough to be
|
||||
counter-productive. Rule-set mutation and packet walk therefore run
|
||||
concurrently, protected by what upstream `pf.c` itself uses (PF_LOCK/RCU-ish
|
||||
patterns) — a deliberate interim deviation being reworked with the lock
|
||||
model in M5+. Until then: expect correctness under steady state, not under
|
||||
aggressive concurrent reload; the reload-under-traffic soak is an open gate.
|
||||
|
||||
### 4. Fail-open under memory pressure
|
||||
|
||||
The queue item is `kmalloc(GFP_ATOMIC)` in softirq. If it fails, the hook
|
||||
returns `NF_ACCEPT` — the packet **bypasses PF**. This is a deliberate
|
||||
availability choice (mirrors dropping into a full queue bypassing the qdisc)
|
||||
but it is a security trade-off worth knowing: PF verdicts are best-effort
|
||||
exactly when the host is out of atomic memory.
|
||||
|
||||
### 5. Hook ordering vs conntrack differs by direction
|
||||
|
||||
- Ingress (`PREROUTING`, `NF_IP_PRI_FIRST`): PF runs **before** defrag and
|
||||
conntrack — as designed (spec §4.2).
|
||||
- Output (`LOCAL_OUT` at `NF_IP_PRI_CONNTRACK+1`): PF sees locally generated
|
||||
traffic **after** conntrack has already committed the entry.
|
||||
- Transit egress (`POSTROUTING` at `NF_IP_PRI_LAST-1`): PF runs at the very
|
||||
end — after any iptables/nft SNAT/masquerade — so `pass out … nat-to`-era
|
||||
rules (M3) will be validated against this ordering. The "unregister-safe
|
||||
ordering vs conntrack" warning from the design still applies: overlapping
|
||||
policy between PF and conntrack is possible and yours to manage.
|
||||
|
||||
## Control plane / lifecycle
|
||||
|
||||
### 6. Unload is hardened; the soak is still pending
|
||||
|
||||
`rmmod pf` deregisters `/dev/pf`, zeroes `pf_status.running` under the NET
|
||||
lock, deletes **and syncs** both purge timers (they self-rearm — a real rmmod
|
||||
hang was observed and fixed by stopping sources before flushing the
|
||||
workqueue), unregisters hooks and flushes `pfk_netisr`. That fix is in-tree,
|
||||
but the full cycle under traffic + live states (T18/soak gate) has **not**
|
||||
been run to green yet. For now treat unload as admin-safe, automation-
|
||||
unproven.
|
||||
|
||||
## Behavioral / rendering issues open today
|
||||
|
||||
### 7. `pfctl -s states` visibility anomaly (M2 gate work)
|
||||
|
||||
In the most recent captured run (gate 01): transit ping **passed** under
|
||||
`keep state`, but no icmp state was listed afterwards. Fixing this is the
|
||||
open item of the M2 gate work; until a green run lands, the
|
||||
[state-listing row](feature-matrix.md) is the honest status. Not cosmetic if
|
||||
it turns out states are not being inserted — treat both possibilities until
|
||||
the gate says otherwise.
|
||||
|
||||
### 8. Silent-but-warned stubs
|
||||
|
||||
`divert` verdicts drop with a rate-limited warning; `log` rules log nothing
|
||||
with a one-time warning. Both are listed so nobody mistakes "rules loaded"
|
||||
for "features active" (see [pf-guide.md](pf-guide.md)).
|
||||
|
||||
## Compat-layer deviations (from `docs/porting-notes.md`, M1 record)
|
||||
|
||||
These are deliberate, marked-in-code divergences of the compat layer; they
|
||||
matter when reasoning about rule semantics:
|
||||
|
||||
- **Socket-owner matching** (`user`/`pid` on rules): uid only — Linux `struct
|
||||
sock` carries no egid/cpid, so gid degrades to `(gid_t)-1`, pid to
|
||||
`NO_PID`.
|
||||
- **Checksums**: driver offload is not trusted on ingress (M1 decision) —
|
||||
TCP checksums are recomputed in software; output pseudo-checksums are done
|
||||
in the port's own path.
|
||||
- **IP options / record-route / timestamps**: not processed on the PF output
|
||||
path; **ICMP redirects are not generated** by PF's route code (the Linux
|
||||
FIB emits those).
|
||||
- **Routing labels**: non-goal — the ioctl layer rejects labeled rules;
|
||||
`rtable_iterate()` always returns NULL (no multipath uRPF iteration).
|
||||
- **Interface groups**: Linux has none; `group …` matches behave like a
|
||||
missing group on OpenBSD.
|
||||
- **GSO/multi-segment skbs**: handled by linearization on entry to the mbuf
|
||||
shim (perf cost documented in spec §10; gates will quantify).
|
||||
- **Single instance**: one PF world in `init_net`, like OpenBSD has one
|
||||
kernel. Containers/namespaces are not visible to PF and cannot be filtered
|
||||
with it in this design.
|
||||
|
||||
## What is *not* a caveat
|
||||
|
||||
ARP and other non-IPv4/IPv6 EtherTypes never enter the netfilter IPv4/IPv6
|
||||
hooks — they pass through the box untouched, exactly as PF on OpenBSD ignores
|
||||
non-IP. `/dev/pf` is 0600 root-only by design (matches OpenBSD's
|
||||
`securelevel`-era default for `pfctl` write access being root).
|
||||
@@ -0,0 +1,78 @@
|
||||
# Feature matrix
|
||||
|
||||
Português: [docs/pt-BR/feature-matrix.md](../pt-BR/feature-matrix.md).
|
||||
|
||||
Status vocabulary (used identically in both languages):
|
||||
|
||||
- **VERIFIED-in-VM** — passed end-to-end in the libvirt lab (see [vm-lab.md](vm-lab.md)).
|
||||
- **M2-in-progress** — implemented; VM gates 01–04 exist but the suite is **not all green yet**; no VERIFIED claim until a clean full run.
|
||||
- **COMPILES-untested** — built into `pf.ko`/`pfctl`; no gate has exercised it.
|
||||
- **PENDING-Mx** — lands in milestone Mx; today's observable behavior is stated.
|
||||
- **OUT-OF-SCOPE** — non-goal by design (spec §3).
|
||||
|
||||
As of 2026-11-11; source of per-row evidence: `out/test-logs/`,
|
||||
`docs/porting-notes.md`, milestone commits.
|
||||
|
||||
## Control plane & tools
|
||||
|
||||
| Feature | Upstream syntax | Milestone | Status |
|
||||
|---|---|---|---|
|
||||
| Module load, `/dev/pf` (0600) | `insmod pf.ko` | M1 | VERIFIED-in-VM |
|
||||
| Enable/disable | `pfctl -e`, `pfctl -d` | M1 | VERIFIED-in-VM |
|
||||
| Ruleset load | `pfctl -f file` | M2 | VERIFIED-in-VM (loads succeeded in every captured gate run; run status itself is mixed — see [vm-lab.md](vm-lab.md) §6) |
|
||||
| Parse-only check | `pfctl -n -f file` | M1 | VERIFIED (offline fixture suite, `make -C tools test`) |
|
||||
| Status/counters | `pfctl -s info [-v]` | M1 | VERIFIED-in-VM |
|
||||
| Rules listing | `pfctl -s rules [-v]` | M2 | COMPILES-untested |
|
||||
| States listing | `pfctl -s states [-v]` | M2 | M2-in-progress — gate 01 shows a listing anomaly (transit ping OK, no icmp state displayed) while state machinery is being fixed |
|
||||
| Tables listing/ops | `pfctl -s tables`, `-t t -T show/add/del/kill` | M2 | M2-in-progress (gate 03) |
|
||||
| Anchors | `pfctl -a anchor -f file`, `-s Anchors` | M2 | M2-in-progress (gate 04) |
|
||||
| Queue listing | `pfctl -s queue -v`, `-s ifq -v` | M5 | PENDING-M5 (parses fine; counters not wired to real back ends yet) |
|
||||
| ioctl ABI parity | `make -C tools abi` (abitest tables) | M1 | VERIFIED build-side (userland table); kernel-side diff is an open integrator step (`tools/UPSTREAM.md`) |
|
||||
|
||||
## Filtering core
|
||||
|
||||
| Feature | Upstream syntax | Milestone | Status |
|
||||
|---|---|---|---|
|
||||
| `pass`/`match`, quick, on-interface/proto/addr/port | `pass in quick on wan proto tcp from 10/8 to port 80` | M2 | M2-in-progress (data path runs the real `pf_test`; latest partial run: transit ping OK, icmp block OK) |
|
||||
| `block drop` | `block drop in quick …` | M2 | M2-in-progress (gate 02a) |
|
||||
| `block return` / `return-rst` / `return-rst after quarter` | `block return-rst in quick proto tcp …` | M2 | M2-in-progress (gate 02b) |
|
||||
| `keep state` (icmp/tcp/udp), state table, purger | `… keep state` | M2 | M2-in-progress |
|
||||
| `set state-policy`, `allow-opts`, etc. | `set …` options | M2 | COMPILES-untested |
|
||||
| Syncookies (state overflow path) | `set syncookies always` | M2 | COMPILES-untested |
|
||||
| pfr tables (radix/hash) | `table <blk> persist { 1.2.3.4 }`, `-T add …` | M2 | M2-in-progress (gate 03) |
|
||||
| Anchors + include + quick precedence | `anchor "x"`, `load anchor …` | M2 | M2-in-progress (gate 04) |
|
||||
| IPv6 for all of the above | same syntax, v6 | M2+ | COMPILES-untested (engine is v6-complete by construction; v6 smoke gates planned) |
|
||||
| Non-IPv4/IPv6 traffic (ARP…) | — | M2 | PFLINUX by construction: netfilter IPv4/IPv6 hooks only — ARP never enters PF path (no gate yet) |
|
||||
|
||||
## NAT / normalization (M3)
|
||||
|
||||
| Feature | Upstream syntax | Milestone | Status |
|
||||
|---|---|---|---|
|
||||
| `nat-to` (port pools, sticky-port, static-port) | `pass out … nat-to (eth1:0)` | M3 | PENDING-M3 (`pf.c`/`pf_lb.c` compiled; wiring + VM gate in M3) |
|
||||
| `rdr-to` | `rdr pass on wan proto tcp from any to any port 8080 -> srv port 80` | M3 | PENDING-M3 |
|
||||
| `binat-to` | `binat-to …` | M3 | PENDING-M3 |
|
||||
| Scrub/normalize: reassembly, `fragment drop-overlap`, `min-ttl`, `max-mss`, `df`/`no-df`, `random-id`, `set reassemble yes` | `match on all scrub (max-mss 1380 …)` | M3 | COMPILES-untested (`pf_norm.c` compiled into pf.ko and reached from `pf_test`; scapy gate 08 pending) |
|
||||
| OS fingerprinting in states | `pass … set os A` + `/etc/pf.os` | bonus | COMPILES-untested (`pf_osfp.c` compiled; no fingerprint packaging yet — missing `/etc/pf.os` is tolerated) |
|
||||
|
||||
## Routing / divert / queues / logging
|
||||
|
||||
| Feature | Upstream syntax | Milestone | Status |
|
||||
|---|---|---|---|
|
||||
| `route-to` pools (`rr`, `round-robin`, `source-hash`, `hash`), `reply-to`, `dup-to`, `fail-to` | `pass out route-to (GW 10.0.3.2)` | M4 | PENDING-M4 (Linux route/neigh glue `pf_route` not implemented yet) |
|
||||
| `divert-to` / `divert` tag | `pass in … divert-to 10.0.99.9 10000`, `divert tag X` | M4 e2e; socket family per code comments M6 | PENDING — today's observable behavior: PF's `PF_DIVERT` verdict calls `divert_packet()`, which **drops the packet and logs a rate-limited `pr_warn`**. The userspace relay `pdivert` exists and expects an in-module `AF_DIVERT` family (`socket(PF_DIVERT=32, SOCK_RAW, IPPROTO_DIVERT=254)`), which is not instantiated yet. **Do not ship rules using divert on a live host.** |
|
||||
| Queues, default scheduler (priq) | `queue root on wan`, `pfctl -s queue -v` | M5 | COMPILES-untested — `compat/pfk_ifq.c` implements the ifq API with **priq fallback semantics**; `queue` rules parse and load, but packets are not actually shunted through a classful qdisc until M5 |
|
||||
| HFSC | `queue on wan { hfsc … }` | M5 | COMPILES-untested — **priq fallback until M5**; `pfsrc/hfsc.c` staged (real OpenBSD 8.0 code) but **not in the module object list** |
|
||||
| FQ-CoDel | `queue on wan { fq_codel … }` | M5 | COMPILES-untested — same as HFSC: staged, not built |
|
||||
| `pfq` qdisc (`tc qdisc … root pfq`) | n/a (Linux glue) | M5 | PENDING-M5 (does not exist in the tree yet) |
|
||||
| pflog (`log`, `log all`, `block log`, `pfctl -s log`) + `pflogd` pcap (DLT_PFLOG) | `pass out quick log` | M4 | PENDING-M4 — `pflog_packet()` returns success **without logging** (one-time `pr_warn "packet not logged"`); `if_pflog.c` staged, not built; `pflogd` not ported yet |
|
||||
|
||||
## Non-goals (explicit)
|
||||
|
||||
| Item | Reason |
|
||||
|---|---|
|
||||
| `pfsync` / CARP failover | non-goal (spec §3) |
|
||||
| IPsec / pfkey | non-goal |
|
||||
| `ftp-proxy` and userland daemons | non-goal |
|
||||
| Per-netns PF instances | faithful port = single global instance on `init_net` |
|
||||
| Classic-ALTQ-only schedulers (cbq/wfq/red…) | removed upstream before OpenBSD 8.0 — cannot be ported from the base |
|
||||
| Routing labels (`route-label`) | non-goal; ioctl layer rejects labeled rules |
|
||||
@@ -0,0 +1,101 @@
|
||||
# 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).
|
||||
@@ -74,3 +74,29 @@ Because the deliverable is an out-of-tree module, "applying the patch set"
|
||||
(virtio/EXT4 =y) — no source files are modified unless a future milestone
|
||||
adds a documented `patches/kernel/000N-*.patch` (all such patches: one
|
||||
section each in docs/en/patches.md with what/why/impact/revert).
|
||||
|
||||
## 6. Notes on the steps above (2026-11-11)
|
||||
|
||||
- Step **2.3 is a documented no-op today**: the patch set is empty and
|
||||
`scripts/apply-patches.sh` / `scripts/unpatch.sh` / `patches/kernel/series`
|
||||
are not yet in the tree (they land with the first real core patch — see
|
||||
[patches.md](patches.md)). You can safely skip 2.3.
|
||||
- Step 2.4 builds `pfctl` and the ABI harness only (`make -C tools` targets:
|
||||
`pfctl abitest abi test clean`). `pdivert` is present as a source file with
|
||||
no Makefile target yet; `pflogd` is not ported.
|
||||
- After 2.5, what you can actually *use* is the M2 filter core (pass/block/
|
||||
quick/keep state/tables/anchors); NAT/scrub/queues/divert/pflog are gated
|
||||
by later milestones — see the [feature matrix](feature-matrix.md) and read
|
||||
[pf-guide.md](pf-guide.md) before enabling PF on a path you need.
|
||||
|
||||
## 7. Where to go next
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| Architecture, one page | [guide.md](guide.md) |
|
||||
| Build stages in detail | [build.md](build.md) |
|
||||
| VM validation lab | [vm-lab.md](vm-lab.md) |
|
||||
| Daily rulesets & warnings | [pf-guide.md](pf-guide.md) |
|
||||
| Known divergences | [caveats.md](caveats.md) |
|
||||
| Core patch mechanism | [patches.md](patches.md) |
|
||||
| Doc index | [README.md](README.md) |
|
||||
@@ -0,0 +1,73 @@
|
||||
# Linux core patch set
|
||||
|
||||
Português: [docs/pt-BR/patches.md](../pt-BR/patches.md).
|
||||
|
||||
## Current status: EMPTY
|
||||
|
||||
The port requires **zero modifications to the Linux 7.3.4 core**. The design
|
||||
(spec §5) anticipated one mandatory patch — **P1: export symbols needed by
|
||||
the PF module** — but the M1 compile-port proved it unnecessary: modpost
|
||||
reported **no undefined symbols** for `pf.ko`. The compat layer was written
|
||||
against APIs the stock kernel already exports (`__inet_lookup_established`,
|
||||
`__inet_lookup_listener`, `__inet6_lookup_established`,
|
||||
`inet6_lookup_listener`, `__udp4_lib_lookup`, `__udp6_lib_lookup`,
|
||||
`ip_route_output_flow`/`ip6_route_output_flags` via inline wrappers,
|
||||
`icmp_send`, …). The evidence and the exact list live in
|
||||
`patches/kernel/p1-needs.txt`, which also carries a **watch list**: e.g.
|
||||
`tcp_v4_send_reset` would need an export only if userland RST helpers are
|
||||
used.
|
||||
|
||||
`patches/kernel/` therefore contains no `000N-*.patch` today, and applying
|
||||
the (empty) set is a no-op.
|
||||
|
||||
> Honest note: install.md and the design mention `scripts/apply-patches.sh`,
|
||||
> `scripts/unpatch.sh` and `patches/kernel/series`. Those helpers are **not
|
||||
> in the tree yet**; until they land, "apply the patch set" is by definition
|
||||
> nothing to do. When a real patch is needed, the first commit that adds it
|
||||
> MUST also add the two scripts and the `series` file (see rules below).
|
||||
|
||||
## The mechanism (how the set is meant to work)
|
||||
|
||||
- Every kernel-core change is a standalone file
|
||||
`patches/kernel/000N-short-description.patch`, listed in order in
|
||||
`patches/kernel/series`, applied to the pristine kernel tree with
|
||||
`git apply`-style tooling and reverted the same way
|
||||
(`unpatch.sh` reverse-applies).
|
||||
- The tree must never be left dirty silently: builds that must be
|
||||
reproducible apply, build, and record the state (`out/.config`,
|
||||
`out/kernelversion`).
|
||||
- Hard rules for any future patch:
|
||||
1. **Additive, zero behavior change** (e.g. `EXPORT_SYMBOL_GPL()` lines
|
||||
adjacent to existing export clusters). Anything that alters logic needs
|
||||
a design-spec amendment first.
|
||||
2. One concern per patch, numbered sequentially — never edit a landed
|
||||
patch; supersede with a new number.
|
||||
3. A section in this file (both languages) **before** the patch lands:
|
||||
what/why/impact/revert + the VM evidence.
|
||||
4. The patch must survive `make -C <kerneldir> bzImage modules` from a
|
||||
clean tree (CI-of-the-lab: `vm-run-tests.sh` after a fresh boot).
|
||||
|
||||
## Template for a future `000N` entry
|
||||
|
||||
Copy this block into both `docs/en/patches.md` and `docs/pt-BR/patches.md`
|
||||
(EN above the pt-BR file's same anchor) when adding `000N-*.patch`:
|
||||
|
||||
```
|
||||
### 000N — <one-line title>
|
||||
|
||||
- File: `patches/kernel/000N-<slug>.patch`
|
||||
- Status: landed (kernel 7.3.4) / superseded by 000M
|
||||
- Touches: <file(s) in the kernel tree>, <n> added lines, 0 removed
|
||||
- What: <exact symbols / hooks / Kconfig knobs added>
|
||||
- Why: <which PF-on-Linux code needs it; why no exported API suffices>
|
||||
- Impact: <runtime/ABI/memory impact on plain Linux; expected: none>
|
||||
- Revert: `./scripts/unpatch.sh <kerneldir>` (reverse-apply); module
|
||||
then fails to link with the missing symbol named in modpost output.
|
||||
- Evidence: <VM run id / date / relevant gate output>
|
||||
```
|
||||
|
||||
## Change log of this directory
|
||||
|
||||
| Date | Change |
|
||||
|---|---|
|
||||
| 2026-11-11 | P1 evaluated after M1 compile-port → **not needed**; `p1-needs.txt` records the evidence and the watch list. Set remains empty. |
|
||||
@@ -0,0 +1,93 @@
|
||||
# Using PF-on-Linux day to day
|
||||
|
||||
Português: [docs/pt-BR/pf-guide.md](../pt-BR/pf-guide.md).
|
||||
|
||||
This is the "what can I actually run today" guide. It follows the
|
||||
[feature matrix](feature-matrix.md): **M2 scope (filtering core: pass/block/
|
||||
quick/keep state/tables/anchors + enable/disable)** has VM evidence behind it;
|
||||
everything beyond that is either COMPILES-untested or PENDING. Install/build
|
||||
is covered by [install.md](install.md) and [build.md](build.md).
|
||||
|
||||
## Daily loop
|
||||
|
||||
```sh
|
||||
sudo insmod module/pf.ko # creates /dev/pf
|
||||
sudo pfctl -nf /etc/pf.conf # parse check (userland only — always safe)
|
||||
sudo pfctl -f /etc/pf.conf # load ruleset
|
||||
sudo pfctl -e # ENABLE — traffic starts hitting the ruleset NOW
|
||||
sudo pfctl -s info # status + counters
|
||||
sudo pfctl -s states # states listing (see caveat below)
|
||||
sudo pfctl -t blk -T add 10.0.1.10 # live table update
|
||||
sudo pfctl -a admin -f /etc/pf.d/admin.conf
|
||||
sudo pfctl -d # disable
|
||||
sudo rmmod pf # unload (rule/state flush happens here)
|
||||
```
|
||||
|
||||
> **Warning — order matters**: `-f` then `-e`. Enabling with no matching `pass` rule
|
||||
> for your admin path blocks it — this port follows OpenBSD's default-block
|
||||
> policy, and the M2 gate runs have *actually cut the test VM's ssh* that
|
||||
> way (the lab keeps a serial console for exactly this). On a remote host,
|
||||
> pass your management interface before you enable anything.
|
||||
|
||||
## A ruleset that works in this port today
|
||||
|
||||
```pf
|
||||
# /etc/pf.conf — M2 scope only. Interface names here match the VM lab
|
||||
# (mgmt/lan/wan); substitute your own (eth0, enp1s0, …).
|
||||
|
||||
# 1) keep yourself reachable (do this first, always)
|
||||
pass in quick on mgmt
|
||||
pass out quick
|
||||
|
||||
# 2) filter core — proven today: quick, proto/port matching, state, drop
|
||||
table <blk> persist # live blocklist (gate 03 pattern)
|
||||
block in quick proto tcp from <blk> to any
|
||||
|
||||
pass in quick on lan proto tcp from 10.0.1.0/24 to any port { 80 443 } keep state
|
||||
block drop in quick on wan proto icmp from any to any
|
||||
|
||||
# 3) anchors with precedence (gate 04 pattern)
|
||||
anchor "admin"
|
||||
```
|
||||
|
||||
Macros (`xif = "lan"`), nested `anchor "in/*"`, `label`, `tag`, `set
|
||||
timeout/limit/state-policy/block-policy/skip` and the rest of the OpenBSD 8.0
|
||||
grammar **parse** fine — `make -C tools test` exercises them offline — but
|
||||
the *kernel behavior* of most `set` options has not been gate-verified yet;
|
||||
treat them as load-and-pray until the matrix moves them.
|
||||
|
||||
## What to avoid until the milestones land
|
||||
|
||||
| Syntax | Until | What happens today |
|
||||
|---|---|---|
|
||||
| `nat-to`, `rdr-to`, `binat-to` | M3 | Rules load (engine code compiled) but translation is unwired/ungated — **do not rely on NAT**; outbound masquerade still comes from iptables/nft during the lab. |
|
||||
| `scrub (…)`, `set reassemble` | M3 | `pf_norm.c` runs inside `pf_test` (compiled) — crafted-fragment behavior is untested; treat results as unvalidated. |
|
||||
| `divert-to`, `divert` tag | M4 (+ family per code comments) | Matching packets are **dropped** with a rate-limited kernel warning. Never ship this on a live host. |
|
||||
| `route-to`, `dup-to`, `fail-to`, `reply-to` | M4 | Not implemented as Linux routing glue yet; rules may load but the behavior is undefined — avoid. |
|
||||
| `queue` / `queue on` / `pfctl -s queue` | M5 | Parse and load. Queuing falls back to the priq-shaped compat `ifq` layer; **no bandwidth enforcement happens until the `pfq` qdisc lands**. |
|
||||
| `log`, `log all`, `block log`, `pflogd` | M4 | Nothing is logged (stub warns once at load: `pfk: … packet not logged`). |
|
||||
| `route-label …` | never (non-goal) | Rejected at the ioctl layer. |
|
||||
| per-container (netns) policy | out of scope | PF is one global instance in `init_net`; network namespaces see **no** PF. |
|
||||
|
||||
## Reading state correctly
|
||||
|
||||
`pfctl -s states` currently has a listing/visibility anomaly being fixed in
|
||||
the M2 gate work (transit flows can pass while no state is displayed). Until
|
||||
then, use counters (`pfctl -s info`) and external observation (ping/nc
|
||||
results, `tcpdump`) as the primary evidence, as the gates do.
|
||||
|
||||
## Kernel log signals you should know
|
||||
|
||||
```
|
||||
pfk: PF-on-Linux engine loaded (OpenBSD 8.0 base) ← insmod ok
|
||||
pfk: netfilter hooks registered (10, netisr wq data path)
|
||||
pfk: divert v4 port N: no divert socket layer (M6 pending), dropping
|
||||
pfk: … packet not logged ← pflog stub
|
||||
```
|
||||
|
||||
Unloading stops the purge timers and drains the work queue first (an rmmod
|
||||
hang observed in the lab was fixed that way) — but the *soak* run
|
||||
(rmmod/insmod cycle under traffic) is still an open gate, so expect unload to
|
||||
be correct for admin use and unproven for hot-reload automation.
|
||||
|
||||
Full list of behavioral divergences: [caveats.md](caveats.md).
|
||||
@@ -0,0 +1,116 @@
|
||||
# VM validation lab (libvirt / Alpine)
|
||||
|
||||
Português: [docs/pt-BR/vm-lab.md](../pt-BR/vm-lab.md).
|
||||
|
||||
All commands below were read from the scripts in `scripts/` (they are the
|
||||
source of truth); the lab has been created and run during M0–M2 — a full-suite
|
||||
green run is the M2+ gate and is still in progress (see §6).
|
||||
|
||||
## 1. Where it lives
|
||||
|
||||
| Host | Role |
|
||||
|---|---|
|
||||
| laptop (this repo) | builds kernel/module/tools; drives everything over ssh |
|
||||
| **deb** — `root@192.168.1.242` | libvirt hypervisor; runs the 4 `pftest-*` Alpine VMs |
|
||||
|
||||
The lab never needs NAT to the outside; the only external access is the
|
||||
libvirt `default` network (192.168.122.0/24, mgmt+DHCP) used for cloud-init
|
||||
package installs and test ssh. The existing `sms-vm` on deb is never touched.
|
||||
|
||||
## 2. Topology (single source: `lab/ips.env`)
|
||||
|
||||
| VM | Role | NICs | Kernel |
|
||||
|---|---|---|---|
|
||||
| `pftest-pf0` | filter under test (runs `pf.ko`) | `mgmt` (dhcp, default net), `lan` 10.0.1.1/24, `wan` 10.0.2.1/24, `wan2` 10.0.3.1/24 | stock Alpine via image-extracted direct boot; **or** the self-built 7.3.4 `bzImage` with `--pf0-kernel=` (`root=/dev/vda2 ro console=ttyS0,115200n8`, no initrd) |
|
||||
| `pftest-cli` | client | `mgmt` dhcp (routes disabled), `lan` 10.0.1.10/24, gw 10.0.1.1 (pf0) | Alpine stock |
|
||||
| `pftest-srv` | server | `mgmt` dhcp (routes disabled), `wan` 10.0.2.20/24, gw 10.0.2.1 (pf0), 10.0.3.0/24 via gw2 | Alpine stock |
|
||||
| `pftest-gw2` | second-path gateway (ip_forward=1) | `wan2` 10.0.3.2/24, `wan` 10.0.2.2/24 (no mgmt NIC) | Alpine stock |
|
||||
|
||||
All VMs get `net.ipv4.ip_forward=1` and `rp_filter=0` via cloud-init, root
|
||||
password `pftest` plus an injected lab keypair (`/root/.ssh/pflab_ed` on deb,
|
||||
copy on the laptop for ProxyJump), and packages
|
||||
`iperf3 tcpdump python3 scapy bash iptables` (Alpine name is `scapy`; the
|
||||
image also needs `iptables` for pf0 masquerade experiments).
|
||||
|
||||
libvirt networks: `default` (kept as-is, started+autostarted for packages) and
|
||||
isolated bridges `pf-lan` 10.0.1.0/24, `pf-wan` 10.0.2.0/24, `pf-wan2`
|
||||
10.0.3.0/24 — `forward="none"`, no DHCP, plus host-side bridge IPs
|
||||
`10.0.x.254` on deb so deb can ssh into every guest's data NIC. VM sizing:
|
||||
2 vCPU / 1 GB each. Disks are per-VM qcow2 **backing** a shared immutable
|
||||
`/var/lib/libvirt/images/alpine-base.qcow2` (synced from
|
||||
`_vendor/images/alpine-3.22-x86_64-cloud.qcow2` by sha256).
|
||||
|
||||
> Boot note: the Alpine cloud image is not BIOS/UEFI bootable in QEMU as
|
||||
> delivered (empty ESP), so *all* VMs use direct kernel boot from the image's
|
||||
> own extracted kernel; `--pf0-kernel=` only swaps pf0's kernel for the
|
||||
> built one.
|
||||
|
||||
## 3. Recreate / deploy / destroy
|
||||
|
||||
```sh
|
||||
./scripts/vm-create.sh # idempotent: tears down and rebuilds pftest-* domains + seeds
|
||||
./scripts/vm-create.sh --pf0-kernel=out/linux-7.3.4-bzImage # pf0 boots the built kernel (T6)
|
||||
./scripts/vm-create.sh --recreate-base # force base-image re-sync
|
||||
./scripts/vm-deploy.sh # laptop→deb ($DEPLOY_DIR)→pf0:/opt/pf:
|
||||
# out/*bzImage, module/*.ko, tools/out/*
|
||||
./scripts/vm-destroy.sh # domains, disks, seeds, pf-* nets, ssh fragments
|
||||
./scripts/vm-destroy.sh --purge-base --purge-keys # + base image + lab keypair
|
||||
```
|
||||
|
||||
`vm-create.sh` ends by validating `ssh pf0 'ip -br a'` and leaves all VMs
|
||||
running. Laptop ssh aliases (`pf0|cli|srv|gw2`, ProxyJump via deb) are
|
||||
generated into `~/.ssh/config.d/pflab`.
|
||||
|
||||
## 4. Running the suite
|
||||
|
||||
```sh
|
||||
./scripts/vm-run-tests.sh # or: make test
|
||||
```
|
||||
|
||||
Deterministic by design: **before every gate** the runner destroys and
|
||||
restarts `pftest-pf0` (fresh disk delta + no leftover module), waits for ssh
|
||||
on the mgmt DHCP address `192.168.122.8`, re-deploys `pf.ko`/`pfctl`/`*.conf`
|
||||
into the guest, then runs the gate script from the **laptop** (gates talk to
|
||||
the VMs through deb with nested ssh — `tests/vm/lib.sh`). Per-gate output is
|
||||
captured to `out/test-logs/<gate>.sh.log`; the runner prints
|
||||
`PASS <gate>` / `FAIL <gate> (rc=...)` per test, a summary `PASS=n FAIL=m`,
|
||||
and exits non-zero if anything failed.
|
||||
|
||||
## 5. What each gate proves today
|
||||
|
||||
| Gate | Scenario (asserted with real traffic, not exit codes) |
|
||||
|---|---|
|
||||
| `01-state.sh` | `pass in/out quick proto icmp keep state` → cli pings srv **through** pf0 (transit); pf0 then lists an icmp state via `pfctl -s states` |
|
||||
| `02-block.sh` | `block drop in quick proto icmp` → cli ping fails; `block return-rst in quick proto tcp … port 8080` → cli's `nc` to srv:8080 is refused (RST), checked via nc exit |
|
||||
| `03-tables.sh` | `table <blk> { 10.0.1.10 }` + `block in quick from <blk>` → `pfctl -t blk -T show` lists it; cli ping blocked; live `-T delete` unblocks the same flow |
|
||||
| `04-anchors.sh` | anchor rule (`-a restricted -f`) passing icmp is evaluated **before** a later main-ruleset `block` (quick/top-placement); `pfctl -s Anchors` lists the anchor |
|
||||
|
||||
Planned gates per the plan document (T11–T18): 05-nat, 06-rdr, 07-binat,
|
||||
08-scrub (scapy), 09-routeto, 10-dup-fail, 11-divert, 12-queues (iperf rate
|
||||
tolerance ±15%), 13-pflog, 14-soak (reload under traffic + rmmod cycle +
|
||||
kmemleak). IPv6 is smoke-only for state/block/nat66/scrub. `bench-basic.sh`
|
||||
(planned, not in the tree yet) is informational; there is no numeric perf gate.
|
||||
|
||||
## 6. Current suite status (2026-11-11, honestly)
|
||||
|
||||
The M2 gates 01–04 exist and run, but **are not all green yet** — the data
|
||||
path (netisr workqueue model) and state visibility are being fixed in the M2
|
||||
gate work. The latest captured run (`out/test-logs/`) shows the mixed state:
|
||||
transit ping OK / state listing failing on 01, icmp-blocked OK on 02, partial
|
||||
results on 03, 04 aborted. Treat "VERIFIED-in-VM" in the
|
||||
[feature matrix](feature-matrix.md) as reserved for gates that pass in a
|
||||
full clean run; nothing beyond M1 is claimed VERIFIED until then.
|
||||
|
||||
## 7. Manual poking
|
||||
|
||||
```sh
|
||||
ssh pf0 # via laptop alias (ProxyJump deb)
|
||||
insmod /opt/pf/pf.ko
|
||||
/opt/pf/pfctl -e
|
||||
/opt/pf/pfctl -f /tmp/g1.conf
|
||||
/opt/pf/pfctl -s info ; /opt/pf/pfctl -s states -v
|
||||
dmesg | grep pfk
|
||||
```
|
||||
|
||||
Artifacts of future full runs (log + pcaps) are meant to ship with the Gitea
|
||||
release (spec §7).
|
||||
@@ -0,0 +1,36 @@
|
||||
# Documentação PF-on-Linux (português)
|
||||
|
||||
English documentation: [`docs/en/README.md`](../en/README.md).
|
||||
|
||||
Este é o conjunto de documentação de usuário do port do **PF do OpenBSD 8.0**
|
||||
para o **Linux 7.3.4** — o engine PF de verdade (`pf.c`, `pf_norm.c`,
|
||||
`pf_table.c`, `pf_lb.c`, …) rodando como módulo out-of-tree `pf.ko`,
|
||||
controlado pelo `pfctl` real do OpenBSD pela ABI ioctl de `/dev/pf`.
|
||||
|
||||
## Índice
|
||||
|
||||
| Documento | Conteúdo |
|
||||
|---|---|
|
||||
| [guide.md](guide.md) | Arquitetura em uma página: componentes, caminho de dados, plano de controle, milestones |
|
||||
| [install.md](install.md) | Receita de aplicação/build/instalação (kernel, módulo, ferramentas) |
|
||||
| [build.md](build.md) | Pipeline de build em detalhe: fetch → vendorize → kernel → módulo → pfctl |
|
||||
| [vm-lab.md](vm-lab.md) | Bancada de validação em libvirt: topologia, recriar/rodar, o que cada gate prova |
|
||||
| [feature-matrix.md](feature-matrix.md) | Status por feature: VERIFIED-in-VM / COMPILES-untested / PENDING-Mx |
|
||||
| [pf-guide.md](pf-guide.md) | Uso diário: o que funciona hoje em `/etc/pf.conf`, o que evitar até M3/M4 |
|
||||
| [patches.md](patches.md) | Patches de core do Linux (hoje vazio) + regras e template para futuros `000N-*.patch` |
|
||||
| [caveats.md](caveats.md) | Divergências conhecidas de comportamento e limitações atuais |
|
||||
|
||||
## Fontes de verdade
|
||||
|
||||
A documentação descreve o código como ele existe na árvore. Em caso de dúvida,
|
||||
as referências autoritativas são:
|
||||
|
||||
- `docs/superpowers/specs/2026-11-11-pf-linux-port-design.md` — design
|
||||
- `docs/superpowers/plans/2026-11-11-pf-linux-port.md` — plano de milestones (tarefas T1–T19)
|
||||
- `docs/porting-notes.md` — log de staging + registro de desvios da camada de compat (M1)
|
||||
|
||||
Vocabulário de status usado em todos os documentos:
|
||||
|
||||
- **VERIFIED-in-VM** — exercitado de ponta a ponta na bancada libvirt (ver vm-lab.md).
|
||||
- **COMPILES-untested** — o código compila no `pf.ko`, mas nenhum gate provou o comportamento ainda.
|
||||
- **PENDING-Mx** — entra (ou entra por completo) no milestone Mx; o comportamento observável hoje está documentado.
|
||||
@@ -0,0 +1,120 @@
|
||||
# Pipeline de build em detalhe
|
||||
|
||||
English: [docs/en/build.md](../en/build.md).
|
||||
Receita rápida: [install.md](install.md). Esta página explica *o que cada
|
||||
etapa faz* e *o que já foi realmente exercitado*.
|
||||
|
||||
Tudo abaixo roda no host de build (o laptop); a bancada de VMs (host
|
||||
separado, "deb") só recebe artefatos — ver [vm-lab.md](vm-lab.md).
|
||||
|
||||
## 0. Pré-requisitos
|
||||
|
||||
De `docs/pt-BR/install.md`: gcc ≥ 15, GNU make, bison (flex não é
|
||||
necessário — o `parse.y` do OpenBSD traz um `yylex` escrito à mão), `bc`,
|
||||
rsync, `glibc-static` para o pfctl estático, root para `insmod`. A árvore do
|
||||
kernel é esperada em `../linux-7.3.4` (troque com `KERNELDIR=`; o `Makefile`
|
||||
raiz usa `$(HOME)/lab/linux_kernel/linux-7.3.4`).
|
||||
|
||||
## 1. Baixar + verificar as fontes do OpenBSD — `scripts/fetch-sources.sh`
|
||||
|
||||
- Baixa `sys.tar.gz` e `src.tar.gz` do mirror primário
|
||||
`https://openbsd.c3sl.ufpr.br/pub/OpenBSD/8.0/`, com fallback para
|
||||
`cdn.openbsd.org`.
|
||||
- Confere o sha256 contra os valores de `VERSIONS` (sys: `964b1aed…`, src:
|
||||
`9984c74f…`); tarball já em cache com hash correto é pulado.
|
||||
- Destino: `_vendor/openbsd/` (fora do git).
|
||||
|
||||
## 2. Vendorize — `scripts/vendorize.sh`
|
||||
|
||||
Extrai **só os arquivos que o port consome** para `_vendor/upstream/`
|
||||
(fora do git): a lista `sys/net/pf*.c|h`, `ifq/hfsc/fq_codel/ip_pflog/
|
||||
ip_divert`, `radix/rtable/toeplitz`, `crypto/{md5,sha2,siphash}`,
|
||||
`sys/sys/*.h` e `sbin/pfctl/*` + headers (arrays `SYS_K`/`SRC_K`). Em
|
||||
seguida registra o tag `$OpenBSD: …$` de cada arquivo em
|
||||
`_vendor/upstream/IDS.txt` — a base forense de toda linha portada e de
|
||||
futuros rebases.
|
||||
|
||||
## 3. Staging para o port — `scripts/stage-port.sh` (porting determinístico)
|
||||
|
||||
O `stage-port.sh` transforma os arquivos upstream pristinos nos arquivos em
|
||||
`module/`:
|
||||
|
||||
1. todo `#include` é substituído por `pfk.h` + os headers `pfvar` staged
|
||||
(os ramos de kernel são selecionados com `-D_KERNEL=1`);
|
||||
2. o programa de renomeação compartilhado (`pf_sed`) resolve colisões de
|
||||
nomes BSD↔Linux (`struct ip` → `struct pf_ip`, `ifnet` → `pfk_ifnet`,
|
||||
`ip_output` → `pfk_ip_output`, atribuições a `sa_len` removidas, …);
|
||||
3. **cada execução é anexada a `docs/porting-notes.md`** — esse arquivo é o
|
||||
log de staging, não prosa.
|
||||
|
||||
Os arquivos portados são então commitados e editados à mão na árvore; o
|
||||
re-staging serve só para perícia (`stage-port.sh --diff <arquivo>` compara a
|
||||
nossa cópia com o texto upstream pristino). Radix/rtable/siphash/toeplitz
|
||||
ficam de fora do staging automático de propósito (portados à mão).
|
||||
|
||||
## 4. Kernel — `scripts/build-kernel.sh` (+ `kernel-config.sh`)
|
||||
|
||||
- `kernel-config.sh`: parte de `defconfig` quando não há `.config` e então
|
||||
ajusta (idempotente, via `scripts/config` + `make olddefconfig`):
|
||||
`VIRTIO_BLK/PCI/NET=y`, `EXT4_FS=y`, `NETFILTER=y`,
|
||||
`NETFILTER_ADVANCED`, `NET_SCHED=y`, `INET=y`, `IPV6=y`,
|
||||
`SERIAL_8250(+CONSOLE)=y` — ou seja, tudo que o boot direto da VM pf0
|
||||
precisa **sem initrd**. `kernel-config.sh test` habilita também
|
||||
`DEBUG_INFO` + `DEBUG_KMEMLEAK` para builds de soak.
|
||||
- `build-kernel.sh`: `make -j(nproc-2) bzImage modules_prepare`, garante que
|
||||
`Module.symvers` exista (copia `vmlinux.symvers` — o kbuild não escreve o
|
||||
primeiro) e que `scripts/module.lds` exista; depois deposita os artefatos
|
||||
de boot em `out/`: `linux-<ver>-bzImage`, `System.map`, `.config`,
|
||||
`kernelversion`, `bzImage.sha256`. Termina com
|
||||
`KERNEL_BUILD_OK <kernelrelease>`.
|
||||
- **Hoje não há patch de core**: `patches/kernel/` não tem nenhum
|
||||
`000N-*.patch` (ver [patches.md](patches.md)).
|
||||
|
||||
## 5. Módulo — `scripts/build-module.sh`
|
||||
|
||||
`make -C "$KERNELDIR" M=module` → `module/pf.ko` (um único módulo hoje; o
|
||||
qdisc `pfq` chega com o M5). O script prepara sozinho `Module.symvers` /
|
||||
`modules_prepare`. Lista de objetos (`module/Makefile`): engine portado
|
||||
(`pf.c`, `pf_ioctl.c`, `pf_norm.c`, `pf_if.c`, `pf_lb.c`, `pf_ruleset.c`,
|
||||
`pf_table.c`, `pf_osfp.c`, `pf_syncookies.c`, `divert_sock.c`) + camada de
|
||||
compat (shim mbuf, glue de kernel, if/ip4/ip6/pcb/xmit/ifq, crypto, radix,
|
||||
subr_tree) + `pfk_mod.c`/`pfk_hooks.c`. As flags de compilação definem
|
||||
`PFLINUX=1` e `_KERNEL=1`. Termina com `MODULE_BUILD_OK`.
|
||||
|
||||
`insmod module/pf.ko` cria `/dev/pf` (0600) e registra os hooks netfilter; o
|
||||
`dmesg` mostra linhas `pfk: …` (ver [caveats.md](caveats.md) para o que o
|
||||
caminho de dados faz).
|
||||
|
||||
## 6. Userland — `make -C tools`
|
||||
|
||||
Alvos (de `tools/Makefile`): `pfctl`, `abitest`, `abi`, `test`, `clean`.
|
||||
|
||||
- `pfctl` — as fontes do OpenBSD 8.0 compiladas contra os headers de ABI
|
||||
copiados; o resultado é um **binário estático** em `tools/out/pfctl`.
|
||||
Cada desvio do upstream está listado em `tools/UPSTREAM.md` (7 edições
|
||||
marcadas; o resto é o conjunto de headers/fontes de compat).
|
||||
- `abi` — compila `abitest` (userland) e `abitest-module` (mesma tabela com
|
||||
os headers de compat de kernel), despeja as tabelas de tamanho/offset de
|
||||
cada struct e as compara com `diff`; divergência = ABI de ioctl quebrada.
|
||||
`out/abi-*.tbl` são as entradas de referência.
|
||||
- `test` — checagens de parse offline: roda `pfctl -n -f` sobre
|
||||
`tools/tests/*.conf` (básico / traduções / filas+scrub / anchor / negativa
|
||||
`broken.conf`). É userland puro; não precisa de `/dev/pf`.
|
||||
- `pdivert` — o fonte do relay de divert fica em `tools/pdivert/`; o lado
|
||||
kernel (pendente) está na [matriz de features](feature-matrix.md).
|
||||
|
||||
## 7. `Makefile` raiz
|
||||
|
||||
`make module | tools | kernel | vm | vm-destroy | test | docs | clean` —
|
||||
wrappers finos sobre os scripts acima. `make test` roda a suíte em VM
|
||||
([vm-lab.md](vm-lab.md)); `make docs` apenas lista o conjunto bilíngue.
|
||||
|
||||
## O que já foi exercitado vs. não
|
||||
|
||||
- As etapas 1–6 foram rodadas ao longo do desenvolvimento M0–M2 no host de
|
||||
build (há artefatos na árvore: `out/`, `module/pf.ko`, `tools/out/pfctl`),
|
||||
e os scripts acima foram verificados linha a linha para este documento.
|
||||
- Uma execução **de ponta a ponta a partir de clone limpo**
|
||||
(`quickstart-check.sh`, tarefa T19 do plano) **ainda não** foi feita;
|
||||
trate a sequência exata do install.md como "conforme projetada e conforme
|
||||
o último estado por milestone", não como verificada em CI.
|
||||
@@ -0,0 +1,141 @@
|
||||
# Ressalvas — divergências do comportamento do OpenBSD
|
||||
|
||||
English: [docs/en/caveats.md](../en/caveats.md).
|
||||
|
||||
Estas são as formas pelas quais este port difere de rodar o mesmo ruleset no
|
||||
OpenBSD 8.0, ou ainda não tem forma de produção. Tudo aqui tem base no
|
||||
código (`module/pfsrc/pfk_hooks.c`, `module/pfsrc/pfk_mod.c`,
|
||||
`docs/porting-notes.md`) e em observações da bancada; nada é especulação.
|
||||
|
||||
## Caminho de dados
|
||||
|
||||
### 1. O PF roda em contexto de processo via workqueue (modelo netisr), não no caminho rápido do pacote
|
||||
|
||||
O OpenBSD avalia o `pf_test()` na CPU que recebeu o pacote, em `IPL_NET`,
|
||||
com uma lock de leitura NET que não dorme. O Linux nos dá contexto de
|
||||
softirq e locks da compat *sleepáveis* (rw-semaphores), então o hook nunca
|
||||
chama o `pf_test()` inline: ele furta a skb (`NF_STOLEN`) para uma
|
||||
**workqueue ordenada** (`pfk_netisr`, `WQ_MEM_RECLAIM`), roda o engine em
|
||||
contexto de processo e então reinjeta (`netif_rx()` na entrada,
|
||||
`dst->output()` na saída/trânsito).
|
||||
|
||||
Consequências, todas observáveis:
|
||||
|
||||
- **Vereditos são assíncronos.** Qualquer regra encadeada depois do nosso
|
||||
hook na mesma chain netfilter (outras regras iptables/nft em prioridades
|
||||
maiores) não vê o pacote até o passe reinjetado chegar. Contadores e
|
||||
intercalação de log mudam na mesma medida.
|
||||
- **Latência + vazão**: cada pacote paga um ida-e-volta pela workqueue,
|
||||
serializado num único worker ordenado. Os gates funcionais são a régua
|
||||
atual; o `bench-basic.sh` planejado (ainda não está na árvore) é só informativo.
|
||||
- **Reordenação**: a fila ordenada preserva a ordem de chegada dos itens
|
||||
*enfileirados*, mas o stack em volta (RPS, multi-NAPI) ainda pode nos
|
||||
entregar pacotes cuja ordem original de conclusão difere da ordem de
|
||||
reinjeção.
|
||||
|
||||
### 2. A proteção contra reentrada é um tag por direção no `skb->mark`
|
||||
|
||||
O sobrevivente reinjetado carrega `skb->mark |= PFK_DONE_IN (0x50460010)` ou
|
||||
`PFK_DONE_OUT (0x50460020)`; nossos hooks aceitam qualquer pacote já marcado
|
||||
para aquela direção em vez de re-enfileirar (senão PF_IN → netif_rx → PF_IN…
|
||||
entraria em loop). O `skb->mark` é um **campo compartilhado e gravável de
|
||||
32 bits**: qualquer subsistema que o *sobrescreva* — `meta mark set` do nft,
|
||||
`CONNMARK --restore`, filtros de tc, alguns stacks de VPN — pode (a) limpar o
|
||||
nosso tag e fazer o pacote ser **re-enfileirado pelo PF de novo**, ou (b)
|
||||
acidentalmente ligar esses bits e deixar o pacote **pular** um passe do PF.
|
||||
Regra prática num host com PF-on-Linux: não use mangling de fwmark no mesmo
|
||||
tráfego; se precisar, mascare os bits do tag do PF no seu espaço de mark.
|
||||
|
||||
### 3. Sem lock NET no caminho de dados (interino do M2)
|
||||
|
||||
O `pf_test()` roda hoje **sem** a lock de leitura NET da compat (comentário
|
||||
em `pfk_hooks.c`): com o modelo de workqueue, um escritor de controle
|
||||
parqueado (mudança de ruleset, ioctl de purge) esfomeava o lado dos pacotes
|
||||
a ponto de ser contraproducente. A mutação do ruleset e a caminhada de
|
||||
pacotes rodam, portanto, concorrentemente, protegidas pelo que o próprio
|
||||
`pf.c` do upstream usa (PF_LOCK/padrões tipo RCU) — um desvio interino
|
||||
deliberado, sendo retrabalhado junto com o modelo de locks no M5+. Até lá:
|
||||
espere correção em regime estável, não sob reload concorrente agressivo; o
|
||||
soak de reload sob tráfego é um gate aberto.
|
||||
|
||||
### 4. Fail-open sob pressão de memória
|
||||
|
||||
O item da fila é um `kmalloc(GFP_ATOMIC)` em softirq. Se falhar, o hook
|
||||
retorna `NF_ACCEPT` — o pacote **passa por fora do PF**. É uma escolha
|
||||
deliberada de disponibilidade (espelha "cai numa fila cheia = bypass do
|
||||
qdisc"), mas é um trade-off de segurança que vale conhecer: os vereditos do
|
||||
PF são best-effort exatamente quando o host está sem memória atômica.
|
||||
|
||||
### 5. A ordem dos hooks vs conntrack varia por direção
|
||||
|
||||
- Entrada (`PREROUTING`, `NF_IP_PRI_FIRST`): o PF roda **antes** do defrag
|
||||
e do conntrack — como projetado (spec §4.2).
|
||||
- Saída (`LOCAL_OUT` em `NF_IP_PRI_CONNTRACK+1`): o PF vê o tráfego gerado
|
||||
localmente **depois** de o conntrack já ter confirmado a entrada.
|
||||
- Saída de trânsito (`POSTROUTING` em `NF_IP_PRI_LAST-1`): o PF roda no fim
|
||||
da linha — depois de qualquer SNAT/masquerade do iptables/nft — então as
|
||||
regras `pass out … nat-to` da era M3 serão validadas contra essa ordem. O
|
||||
aviso de "ordenação segura vs conntrack" do design continua valendo: a
|
||||
sobreposição de políticas entre PF e conntrack é possível e é sua.
|
||||
|
||||
## Plano de controle / ciclo de vida
|
||||
|
||||
### 6. O unload está endurecido; o soak ainda está pendente
|
||||
|
||||
O `rmmod pf` desregistra `/dev/pf`, zera `pf_status.running` sob a lock NET,
|
||||
deleta **e sincroniza** os dois timers de purge (eles se rearmam sozinhos —
|
||||
um travamento real de rmmod foi observado e corrigido parando as fontes
|
||||
antes de drenar a workqueue), desregistra os hooks e drena o `pfk_netisr`.
|
||||
A correção está na árvore, mas o ciclo completo sob tráfego + estados vivos
|
||||
(gate de soak T18) **ainda não** rodou verde. Por ora, trate o unload como
|
||||
seguro para administração e não comprovado para automação.
|
||||
|
||||
## Problemas de comportamento/renderização abertos hoje
|
||||
|
||||
### 7. Anomalia de visibilidade do `pfctl -s states` (trabalho do gate M2)
|
||||
|
||||
Na rodada capturada mais recente (gate 01): o ping de trânsito **passou** com
|
||||
`keep state`, mas nenhum estado icmp foi listado depois. Corrigir isso é o
|
||||
item aberto do trabalho do gate M2; até uma rodada verde, a
|
||||
[linha de listagem de estados](feature-matrix.md) é o status honesto. Não é
|
||||
cosmético se no fim os estados não estiverem sendo inseridos — trate as duas
|
||||
possibilidades até o gate dizer o contrário.
|
||||
|
||||
### 8. Stubs silenciosos-mas-avisados
|
||||
|
||||
Vereditos `divert` descartam com aviso rate-limited; regras de `log` não
|
||||
logam nada e avisam uma única vez. Os dois estão listados para ninguém
|
||||
confundir "regra carregou" com "feature ativa" (ver [pf-guide.md](pf-guide.md)).
|
||||
|
||||
## Desvios da camada de compat (de `docs/porting-notes.md`, registro do M1)
|
||||
|
||||
Estes são desvios deliberados e marcados no código; importam ao raciocinar
|
||||
sobre a semântica das regras:
|
||||
|
||||
- **Match pelo dono do socket** (`user`/`pid` em regras): só uid — o
|
||||
`struct sock` do Linux não guarda egid/cpid, então o gid degrada para
|
||||
`(gid_t)-1` e o pid para `NO_PID`.
|
||||
- **Checksums**: o offload do driver não é confiável na entrada (decisão do
|
||||
M1) — checksums TCP são recalculados em software; os pseudo-checksums de
|
||||
saída são feitos no caminho próprio do port.
|
||||
- **Opções IP / record-route / timestamps**: não processadas no caminho de
|
||||
saída do PF; **redirecionamentos ICMP não são gerados** pelo código de
|
||||
rota do PF (o FIB do Linux emite).
|
||||
- **Labels de rota**: não-objetivo — a camada de ioctl rejeita regras com
|
||||
label; `rtable_iterate()` sempre retorna NULL (sem iteração de uRPF
|
||||
multipath).
|
||||
- **Grupos de interface**: o Linux não tem; matches `group …` se comportam
|
||||
como um grupo inexistente no OpenBSD.
|
||||
- **Skbs GSO/multi-segmento**: tratadas por linearização na entrada do shim
|
||||
mbuf (custo de performance documentado no spec §10; os gates vão
|
||||
quantificar).
|
||||
- **Instância única**: um mundo PF em `init_net`, como o OpenBSD tem um
|
||||
kernel. Containers/namespaces não são visíveis ao PF e não podem ser
|
||||
filtrados por ele neste design.
|
||||
|
||||
## O que *não* é ressalva
|
||||
|
||||
ARP e outros EtherTypes não-IPv4/IPv6 nunca entram nos hooks netfilter de
|
||||
IPv4/IPv6 — passam pela máquina intactos, exatamente como o PF no OpenBSD
|
||||
ignora não-IP. O `/dev/pf` é 0600 somente-root por projeto (equivalente ao
|
||||
padrão do `pfctl` no OpenBSD da era `securelevel`).
|
||||
@@ -0,0 +1,78 @@
|
||||
# Matriz de features
|
||||
|
||||
English: [docs/en/feature-matrix.md](../en/feature-matrix.md).
|
||||
|
||||
Vocabulário de status (idêntico nos dois idiomas):
|
||||
|
||||
- **VERIFIED-in-VM** — passou de ponta a ponta na bancada libvirt (ver [vm-lab.md](vm-lab.md)).
|
||||
- **M2-in-progress** — implementado; os gates VM 01–04 existem, mas a suíte **ainda não está toda verde**; sem alegação de VERIFIED até uma rodada limpa.
|
||||
- **COMPILES-untested** — compila em `pf.ko`/`pfctl`; nenhum gate exercitou.
|
||||
- **PENDING-Mx** — entra no milestone Mx; o comportamento observável hoje está declarado.
|
||||
- **OUT-OF-SCOPE** — não-objetivo por projeto (spec §3).
|
||||
|
||||
Estado em 2026-11-11; evidência por linha: `out/test-logs/`,
|
||||
`docs/porting-notes.md`, commits de milestone.
|
||||
|
||||
## Plano de controle e ferramentas
|
||||
|
||||
| Feature | Sintaxe de upstream | Milestone | Status |
|
||||
|---|---|---|---|
|
||||
| Carga do módulo, `/dev/pf` (0600) | `insmod pf.ko` | M1 | VERIFIED-in-VM |
|
||||
| Habilitar/desabilitar | `pfctl -e`, `pfctl -d` | M1 | VERIFIED-in-VM |
|
||||
| Carga de ruleset | `pfctl -f arquivo` | M2 | VERIFIED-in-VM (cargas bem-sucedidas em toda rodada capturada; o status geral da rodada é misto — ver [vm-lab.md](vm-lab.md) §6) |
|
||||
| Checagem só de parse | `pfctl -n -f arquivo` | M1 | VERIFIED (suíte de fixtures offline, `make -C tools test`) |
|
||||
| Status/contadores | `pfctl -s info [-v]` | M1 | VERIFIED-in-VM |
|
||||
| Listagem de regras | `pfctl -s rules [-v]` | M2 | COMPILES-untested |
|
||||
| Listagem de estados | `pfctl -s states [-v]` | M2 | M2-in-progress — o gate 01 mostra anomalia de listagem (ping passa, nenhum estado icmp exibido) enquanto a máquina de estados está sendo corrigida |
|
||||
| Tabelas (listagem/ops) | `pfctl -s tables`, `-t t -T show/add/del/kill` | M2 | M2-in-progress (gate 03) |
|
||||
| Anchors | `pfctl -a anchor -f arquivo`, `-s Anchors` | M2 | M2-in-progress (gate 04) |
|
||||
| Listagem de filas | `pfctl -s queue -v`, `-s ifq -v` | M5 | PENDING-M5 (o parse funciona; contadores ainda não conectados aos back ends reais) |
|
||||
| Paridade de ABI ioctl | `make -C tools abi` (tabelas do abitest) | M1 | VERIFIED no lado do build (tabela userland); o diff do lado kernel é um passo aberto para o integrador (`tools/UPSTREAM.md`) |
|
||||
|
||||
## Núcleo de filtragem
|
||||
|
||||
| Feature | Sintaxe de upstream | Milestone | Status |
|
||||
|---|---|---|---|
|
||||
| `pass`/`match`, quick, match por interface/proto/endereço/porta | `pass in quick on wan proto tcp from 10/8 to port 80` | M2 | M2-in-progress (o caminho de dados roda o `pf_test` real; rodada parcial recente: ping de trânsito OK, icmp bloqueado OK) |
|
||||
| `block drop` | `block drop in quick …` | M2 | M2-in-progress (gate 02a) |
|
||||
| `block return` / `return-rst` / `return-rst after quarter` | `block return-rst in quick proto tcp …` | M2 | M2-in-progress (gate 02b) |
|
||||
| `keep state` (icmp/tcp/udp), tabela de estados, purger | `… keep state` | M2 | M2-in-progress |
|
||||
| `set state-policy`, `allow-opts` etc. | opções `set …` | M2 | COMPILES-untested |
|
||||
| Syncookies (caminho de estouro de estado) | `set syncookies always` | M2 | COMPILES-untested |
|
||||
| Tabelas pfr (radix/hash) | `table <blk> persist { 1.2.3.4 }`, `-T add …` | M2 | M2-in-progress (gate 03) |
|
||||
| Anchors + include + precedência de quick | `anchor "x"`, `load anchor …` | M2 | M2-in-progress (gate 04) |
|
||||
| IPv6 para tudo acima | mesma sintaxe, v6 | M2+ | COMPILES-untested (o engine é completo para v6 por construção; gates v6 smoke planejados) |
|
||||
| Tráfego não-IPv4/IPv6 (ARP…) | — | M2 | PFLINUX por construção: hooks netfilter só de IPv4/IPv6 — ARP nunca entra no caminho do PF (sem gate ainda) |
|
||||
|
||||
## NAT / normalização (M3)
|
||||
|
||||
| Feature | Sintaxe de upstream | Milestone | Status |
|
||||
|---|---|---|---|
|
||||
| `nat-to` (pools de portas, sticky-port, static-port) | `pass out … nat-to (eth1:0)` | M3 | PENDING-M3 (`pf.c`/`pf_lb.c` compilados; fiação + gate VM no M3) |
|
||||
| `rdr-to` | `rdr pass on wan proto tcp from any to any port 8080 -> srv port 80` | M3 | PENDING-M3 |
|
||||
| `binat-to` | `binat-to …` | M3 | PENDING-M3 |
|
||||
| Scrub/normalize: remontagem, `fragment drop-overlap`, `min-ttl`, `max-mss`, `df`/`no-df`, `random-id`, `set reassemble yes` | `match on all scrub (max-mss 1380 …)` | M3 | COMPILES-untested (`pf_norm.c` compilado no pf.ko e alcançado pelo `pf_test`; gate scapy 08 pendente) |
|
||||
| Fingerprint de SO em estados | `pass … set os A` + `/etc/pf.os` | bônus | COMPILES-untested (`pf_osfp.c` compilado; sem empacotamento de fingerprints ainda — ausência de `/etc/pf.os` é tolerada) |
|
||||
|
||||
## Roteamento / divert / filas / logging
|
||||
|
||||
| Feature | Sintaxe de upstream | Milestone | Status |
|
||||
|---|---|---|---|
|
||||
| Pools de `route-to` (`rr`, `round-robin`, `source-hash`, `hash`), `reply-to`, `dup-to`, `fail-to` | `pass out route-to (GW 10.0.3.2)` | M4 | PENDING-M4 (o glue Linux de rota/neigh `pf_route` ainda não existe) |
|
||||
| `divert-to` / tag `divert` | `pass in … divert-to 10.0.99.9 10000`, `divert tag X` | M4 e2e; família de sockets conforme comentário no código, M6 | PENDING — comportamento observável hoje: o veredito `PF_DIVERT` chama `divert_packet()`, que **descarta o pacote e registra um `pr_warn` rate-limited**. O relay userspace `pdivert` existe e espera a família `AF_DIVERT` dentro do módulo (`socket(PF_DIVERT=32, SOCK_RAW, IPPROTO_DIVERT=254)`), que ainda não foi instanciada. **Não coloque regras com divert num host em produção.** |
|
||||
| Filas, agendador default (priq) | `queue root on wan`, `pfctl -s queue -v` | M5 | COMPILES-untested — `compat/pfk_ifq.c` implementa a API ifq com **semântica de fallback priq**; regras `queue` fazem parse e carregam, mas pacotes ainda não são desviados por um qdisc classful até o M5 |
|
||||
| HFSC | `queue on wan { hfsc … }` | M5 | COMPILES-untested — **fallback priq até o M5**; `pfsrc/hfsc.c` staged (código real do OpenBSD 8.0) mas **fora da lista de objetos** do módulo |
|
||||
| FQ-CoDel | `queue on wan { fq_codel … }` | M5 | COMPILES-untested — igual ao HFSC: staged, não compilado |
|
||||
| Qdisc `pfq` (`tc qdisc … root pfq`) | n/a (glue Linux) | M5 | PENDING-M5 (ainda não existe na árvore) |
|
||||
| pflog (`log`, `log all`, `block log`, `pfctl -s log`) + pcap do `pflogd` (DLT_PFLOG) | `pass out quick log` | M4 | PENDING-M4 — `pflog_packet()` retorna sucesso **sem logar** (`pr_warn "packet not logged"` uma única vez); `if_pflog.c` staged, não compilado; `pflogd` ainda não portado |
|
||||
|
||||
## Não-objetivos (explícitos)
|
||||
|
||||
| Item | Motivo |
|
||||
|---|---|
|
||||
| Failover `pfsync` / CARP | não-objetivo (spec §3) |
|
||||
| IPsec / pfkey | não-objetivo |
|
||||
| `ftp-proxy` e daemons de userland | não-objetivo |
|
||||
| Instâncias PF por netns | port fiel = instância global única em `init_net` |
|
||||
| Agendadores exclusivos do ALTQ clássico (cbq/wfq/red…) | removidos do upstream antes do OpenBSD 8.0 — impossível portar da base |
|
||||
| Labels de rota (`route-label`) | não-objetivo; a camada de ioctl rejeita regras com label |
|
||||
@@ -0,0 +1,103 @@
|
||||
# Guia de arquitetura — uma página
|
||||
|
||||
English: [docs/en/guide.md](../en/guide.md).
|
||||
|
||||
## O que este port é
|
||||
|
||||
Um port fiel do **engine PF do OpenBSD 8.0** rodando dentro do **Linux
|
||||
7.3.4** como um único módulo out-of-tree, `pf.ko`. Nada é simulado: o
|
||||
casamento de regras, estado, tabelas, normalização e filas *é* o código do
|
||||
OpenBSD, compilado sob uma camada de compatibilidade; o controle vem do
|
||||
`pfctl` real, falando a ABI ioctl OpenBSD de verdade por `/dev/pf`. Existe
|
||||
**uma instância global do PF, presa a `init_net`** — exatamente como o
|
||||
OpenBSD tem um único kernel; instâncias por netns estão fora de escopo.
|
||||
|
||||
Alvos de build: fontes OpenBSD 8.0 (sha256 fixado em `VERSIONS`), Linux
|
||||
7.3.4 (`linux-7.3.4`), `gcc >= 15`, bison. O `pfvar.h` v1.548 é compartilhado
|
||||
bit a bit entre kernel e userland (codificação de ioctl em
|
||||
`module/include/pfk.h`).
|
||||
|
||||
## Componentes
|
||||
|
||||
| Caminho | Papel |
|
||||
|---|---|
|
||||
| `module/pfsrc/` | Arquivos portados do OpenBSD: `pf.c` (engine), `pf_ioctl.c` (ABI DIOC*), `pf_norm.c` (scrub/fragmentos), `pf_table.c`, `pf_ruleset.c`, `pf_if.c`, `pf_lb.c` (pools/portas NAT), `pf_osfp.c`, `pf_syncookies.c`, `ifq.c`/`hfsc.c`/`fq_codel.c` (staged para M5), `if_pflog.c` + `ip_divert.c` (referências staged), `divert_sock.c` (stub de entrada do divert), `pfk_mod.c` (load/exit, `/dev/pf`), `pfk_hooks.c` (caminho de dados netfilter) |
|
||||
| `module/compat/` | Infra Linux sob os fontes em forma BSD: shim mbuf-sobre-`sk_buff` (`pfk_mbuf`), glue de kernel (malloc/pools/timeouts/locks, `pfk_kern`), visão de interfaces e endereços (`pfk_if`), saída/forward IP com semântica BSD (`pfk_ip4`, `pfk_ip6`), busca de sockets (`pfk_pcb`), API ifq com semântica priq (`pfk_ifq`), radix/rotas/crypto portados |
|
||||
| `module/include/` | `pfk.h` (header mestre da compat + codificação `_IO*` estilo OpenBSD), `net/pfvar.h` staged, `pfk_mbuf.h`, `pfk_net.h`, `pfk_if.h` |
|
||||
| `tools/` | Build Linux do `pfctl` do OpenBSD 8.0 (binário único), relay `pdivert`, verificador de ABI (`abitest` + `make abi`), testes de parse offline (`make test`). Deltas vs upstream: `tools/UPSTREAM.md` |
|
||||
| `scripts/` | `fetch-sources.sh`, `vendorize.sh`, `stage-port.sh` (staging determinístico), `build-kernel.sh`, `build-module.sh`, automação da bancada `vm-*.sh` |
|
||||
| `tests/vm/` | Gates da suíte em VM: `01-state.sh … 04-anchors.sh` + `lib.sh`; `tests/confs/` com rulesets deployados na bancada |
|
||||
| `patches/kernel/` | Conjunto de patches de core — hoje **vazio** (`p1-needs.txt` registra por que nenhum export foi necessário); ver [patches.md](patches.md) |
|
||||
|
||||
## Caminho de dados (M2, como implementado hoje)
|
||||
|
||||
O OpenBSD roda o PF em `IPL_NET`, em contexto de processo; nossas locks da
|
||||
compat são sleepáveis, então o hook **nunca chama `pf_test()` em softirq**. O
|
||||
pacote é furtado (`NF_STOLEN`) para uma workqueue ordenada (`pfk_netisr`), o
|
||||
veredito roda em contexto de processo e é aplicado por **reinjeção**, com
|
||||
marcação por direção em `skb->mark` para que o pacotão sobrevivente não seja
|
||||
re-enfileirado pelo nosso próprio hook.
|
||||
|
||||
```
|
||||
Linux 7.3.4, módulo pf.ko
|
||||
NIC ──► netfilter (softirq — apenas enfileira)
|
||||
PRE_ROUTING PF_IN ─┐ ┌─ LOCAL_OUT PF_OUT
|
||||
POST_ROUTING PF_OUT ─┤ NF_STOLEN ├─ (roda DEPOIS do conntrack)
|
||||
LOCAL_IN/FORWARD: pass-through ┘ POST_ROUTING: NF_IP_PRI_LAST-1
|
||||
│ (ref da skb + kmalloc pfk_pkt;
|
||||
│ falha de alocação ⇒ NF_ACCEPT = fail-open)
|
||||
▼
|
||||
workqueue pfk_netisr (contexto de processo)
|
||||
│
|
||||
pfk_m_wrap_skb(skb)──► shim struct mbuf
|
||||
│
|
||||
pf_test(af, dir, ifp, &m) ◄── o engine real do OpenBSD 8.0:
|
||||
│ scrub/normalize → lookup de estado
|
||||
│ → regras/anchors/tabelas/quick
|
||||
│ → inserção de estado → veredito
|
||||
▼
|
||||
pass ──► mark |= PFK_DONE_IN/OUT ──► reinjeta: PF_IN: netif_rx() (sobrevivente
|
||||
da remontagem entra no stack)
|
||||
PF_OUT: skb_dst()->output()
|
||||
block/drop ──► kfree_skb() (+ RST/ICMP de retorno gerados pelo próprio PF)
|
||||
fragmento em buffer ──► nossa ref é liberada; pf mantém o sobrevivente (m == NULL)
|
||||
```
|
||||
|
||||
Plano de controle: `/dev/pf` (miscdev, modo 0600, escrita condicionada a
|
||||
`CAP_NET_ADMIN`) → `pfk_ioctl_io()` copia a estrutura de parâmetros usando o
|
||||
tamanho embutido na codificação `_IOR/_IOW` OpenBSD e despacha para o
|
||||
`pfioctl()` portado (`pf_ioctl.c`) — a ABI DIOC* completa, como upstream.
|
||||
`pfctl -e/-f/-s/-t` é o mesmo binário do OpenBSD, recompilado.
|
||||
|
||||
## Filas, divert, pflog — comportamento observável hoje
|
||||
|
||||
- **Filas**: o `pfctl` faz o parse de `queue`/`queue on` (sintaxe 8.0); no
|
||||
kernel a API ifq é implementada por `compat/pfk_ifq.c` com **semântica
|
||||
priq apenas**; o qdisc classful `pfq` e os back ends HFSC/FQ-CoDel chegam
|
||||
em **M5**. `ifq.c`/`hfsc.c`/`fq_codel.c` estão staged e prontos para
|
||||
compilar, mas ainda **não** estão na lista de objetos do módulo.
|
||||
- **Divert**: `divert-to`/tag `divert` produzem o veredito `PF_DIVERT` do PF;
|
||||
`divert_packet()` (`pfsrc/divert_sock.c`) hoje **descarta o pacote com um
|
||||
`pr_warn` rate-limited**, porque a família de sockets AF_DIVERT ainda não
|
||||
foi instanciada. O relay userspace `pdivert` existe e espera
|
||||
`socket(PF_DIVERT=32, SOCK_RAW, IPPROTO_DIVERT=254)`.
|
||||
- **pflog**: `pflog_packet()` retorna sucesso mas **não loga nada** (um
|
||||
único `pr_warn "packet not logged"`); `if_pflog.c` está staged, back end
|
||||
netlink em M4/M5.
|
||||
|
||||
## Milestones (status por feature em feature-matrix.md)
|
||||
|
||||
| Milestone | Escopo | Status (2026-11-11) |
|
||||
|---|---|---|
|
||||
| M0 | repo, scripts, build do kernel, VM liga com kernel próprio | concluído |
|
||||
| M1 | camada de compat, `pf.ko` carrega, `/dev/pf`, `pfctl -f`/`-s info` | concluído |
|
||||
| M2 | núcleo de filtragem: gates VM de regras/estados/tabelas/anchors | **em andamento** — caminho de dados implementado, gates 01–04 ainda não todos verdes |
|
||||
| M3 | NAT (nat/rdr/binat), scrub e2e, pflog | pendente |
|
||||
| M4 | route-to/dup-to/fail-to, divert e2e | pendente |
|
||||
| M5 | filas: back ends ifq (hfsc/fq_codel) + qdisc `pfq` | pendente |
|
||||
| M6 | docs bilíngues completas, patchset arrumado, suíte inteira verde, tags de release | em andamento (este conjunto de docs) |
|
||||
|
||||
## Próximas leituras
|
||||
|
||||
[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).
|
||||
@@ -0,0 +1,105 @@
|
||||
# Aplicando o conjunto de patches PF-on-Linux / guia de compilação
|
||||
|
||||
Este documento é a versão em português de `docs/en/install.md`.
|
||||
|
||||
Alvo: árvore de fontes do **Linux 7.3.4** + módulos out-of-tree `pf.ko`
|
||||
(engine PF, família divert(4), filas ifq). O port é entregue como
|
||||
**árvore de código-fonte + zero patches de core** (ver `patches/kernel/README.md`):
|
||||
o módulo linka apenas símbolos que o kernel 7.3.4 puro já exporta.
|
||||
|
||||
## 1. Pré-requisitos
|
||||
|
||||
| Requisito | Por quê |
|
||||
|---|---|
|
||||
| gcc >= 15, GNU make, flex, bison, bc | build do kernel + pfctl |
|
||||
| `glibc-static` (ou musl-static) | pfctl estático para as VMs guest |
|
||||
| Fontes Linux 7.3.4 | alvo da compilação |
|
||||
| `sys.tar.gz` + `src.tar.gz` do OpenBSD 8.0 | fontes portados (baixados e verificados por sha256; mirror primário openbsd.c3sl.ufpr.br, fallback cdn.openbsd.org) |
|
||||
| root no host de build | `insmod` como root |
|
||||
|
||||
## 2. Passos
|
||||
|
||||
```sh
|
||||
cd pf_port
|
||||
|
||||
# 2.1 busca e verifica as fontes OpenBSD 8.0 (sha256 fixados em VERSIONS)
|
||||
./scripts/fetch-sources.sh
|
||||
./scripts/vendorize.sh # staging em _vendor/upstream + IDS.txt
|
||||
|
||||
# 2.2 prepara o kernel (config + build + preparação p/ módulos externos)
|
||||
KERNELDIR=/path/to/linux-7.3.4 ./scripts/build-kernel.sh
|
||||
# - defconfig + virtio/EXT4 embutidos (boot direto do kernel na VM)
|
||||
# - make bzImage modules_prepare
|
||||
# - copia vmlinux.symvers -> Module.symvers (necessário para builds M=)
|
||||
|
||||
# 2.3 aplica os patches de core (atualmente NENHUM é necessário)
|
||||
./scripts/apply-patches.sh /path/to/linux-7.3.4
|
||||
# patches/kernel/series está vazio por projeto; se futuros milestones
|
||||
# precisarem de exports, cada um entra como 000N-*.patch documentado em
|
||||
# docs/pt-BR/patches.md.
|
||||
|
||||
# 2.4 compila módulo e userland
|
||||
./scripts/build-module.sh # -> module/pf.ko
|
||||
make -C tools # -> tools/out/pfctl (estático)
|
||||
|
||||
# 2.5 instala num sistema 7.3.4 rodando
|
||||
sudo insmod module/pf.ko # cria /dev/pf (0600 root)
|
||||
sudo cp tools/out/pfctl /usr/local/sbin/
|
||||
sudo pfctl -e # habilita
|
||||
sudo pfctl -f /etc/pf.conf # sintaxe real do OpenBSD 8.0
|
||||
```
|
||||
|
||||
## 3. Verificação
|
||||
|
||||
```sh
|
||||
pfctl -s info # "Status: Enabled", contadores ao vivo
|
||||
pfctl -nf /etc/pf.conf # checagem de parse apenas
|
||||
dmesg | grep pfk # hooks registrados (data path via workqueue netisr)
|
||||
```
|
||||
|
||||
Bancada de VMs (Alpine em libvirt): `./scripts/vm-create.sh`,
|
||||
`./scripts/vm-deploy.sh`, `./scripts/vm-run-tests.sh` — ver `docs/pt-BR/vm-lab.md`.
|
||||
|
||||
## 4. Remoção / reversão
|
||||
|
||||
```sh
|
||||
sudo pfctl -d; sudo rmmod pf
|
||||
./scripts/unpatch.sh /path/to/linux-7.3.4 # desfaz patches futuros, se houver
|
||||
```
|
||||
|
||||
## 5. O que "aplicar o patch" significa aqui
|
||||
|
||||
Como a entrega é um módulo out-of-tree, "aplicar o conjunto" = passos
|
||||
2.1–2.4; a árvore Linux intacta é apenas *configurada e compilada*
|
||||
(virtio/EXT4 =y). Nenhum arquivo do kernel é modificado, salvo se um futuro
|
||||
milestone acrescentar `patches/kernel/000N-*.patch` documentado (cada patch:
|
||||
uma seção em docs/pt-BR/patches.md com o quê/por quê/impacto/reversão).
|
||||
|
||||
## 6. Notas sobre os passos acima (2026-11-11)
|
||||
|
||||
- O passo **2.3 é um no-op documentado hoje**: o conjunto de patches está
|
||||
vazio e os scripts `scripts/apply-patches.sh` / `scripts/unpatch.sh` e o
|
||||
arquivo `patches/kernel/series` ainda não existem na árvore (entram junto
|
||||
com o primeiro patch real de core — ver [patches.md](patches.md)). Pode
|
||||
pular 2.3 com segurança.
|
||||
- O passo 2.4 compila apenas o `pfctl` e o verificador de ABI (alvos de
|
||||
`make -C tools`: `pfctl abitest abi test clean`). `pdivert` existe como
|
||||
arquivo-fonte sem alvo no Makefile por enquanto; `pflogd` ainda não foi
|
||||
portado.
|
||||
- Depois do 2.5, o que dá para usar de verdade é o núcleo de filtragem M2
|
||||
(pass/block/quick/keep state/tabelas/anchors); NAT/scrub/queues/divert/
|
||||
pflog dependem dos próximos milestones — consulte a
|
||||
[matriz de features](feature-matrix.md) e leia [pf-guide.md](pf-guide.md)
|
||||
antes de habilitar PF num caminho que você precisa manter vivo.
|
||||
|
||||
## 7. Para onde ir agora
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| Arquitetura em uma página | [guide.md](guide.md) |
|
||||
| Etapas de build em detalhe | [build.md](build.md) |
|
||||
| Bancada de VMs de validação | [vm-lab.md](vm-lab.md) |
|
||||
| Regras do dia a dia e avisos | [pf-guide.md](pf-guide.md) |
|
||||
| Divergências conhecidas | [caveats.md](caveats.md) |
|
||||
| Mecanismo de patches de core | [patches.md](patches.md) |
|
||||
| Índice da documentação | [README.md](README.md) |
|
||||
@@ -0,0 +1,75 @@
|
||||
# Conjunto de patches de core do Linux
|
||||
|
||||
English: [docs/en/patches.md](../en/patches.md).
|
||||
|
||||
## Status atual: VAZIO
|
||||
|
||||
O port exige **zero modificações no core do Linux 7.3.4**. O design
|
||||
(spec §5) previa um patch obrigatório — **P1: exportar símbolos usados pelo
|
||||
módulo PF** — mas o compile-port do M1 provou que ele é desnecessário: o
|
||||
modpost reportou **nenhum símbolo indefinido** para o `pf.ko`. A camada de
|
||||
compat foi escrita contra APIs que o kernel puro já exporta
|
||||
(`__inet_lookup_established`, `__inet_lookup_listener`,
|
||||
`__inet6_lookup_established`, `inet6_lookup_listener`, `__udp4_lib_lookup`,
|
||||
`__udp6_lib_lookup`, `ip_route_output_flow`/`ip6_route_output_flags` via
|
||||
wrappers inline, `icmp_send`, …). A evidência e a lista exata estão em
|
||||
`patches/kernel/p1-needs.txt`, que também traz uma **watch list**: por
|
||||
exemplo, `tcp_v4_send_reset` só precisaria de export se helpers de RST de
|
||||
userland forem usados.
|
||||
|
||||
Portanto, `patches/kernel/` não contém nenhum `000N-*.patch` hoje, e aplicar
|
||||
o conjunto (vazio) é um no-op.
|
||||
|
||||
> Nota honesta: o install.md e o design mencionam `scripts/apply-patches.sh`,
|
||||
> `scripts/unpatch.sh` e `patches/kernel/series`. Esses helpers **ainda não
|
||||
> estão na árvore**; até entrarem, "aplicar o conjunto de patches" é, por
|
||||
> definição, nada a fazer. Quando um patch real for necessário, o primeiro
|
||||
> commit que o adicionar DEVE também adicionar os dois scripts e o arquivo
|
||||
> `series` (ver as regras abaixo).
|
||||
|
||||
## O mecanismo (como o conjunto deve funcionar)
|
||||
|
||||
- Toda mudança no core do kernel é um arquivo independente
|
||||
`patches/kernel/000N-descricao-curta.patch`, listado em ordem em
|
||||
`patches/kernel/series`, aplicado à árvore pristina com ferramenta no
|
||||
estilo `git apply` e revertido do mesmo jeito (`unpatch.sh` aplica ao
|
||||
contrário).
|
||||
- A árvore nunca pode ficar suja em silêncio: builds reproduzíveis aplicam,
|
||||
compilam e registram o estado (`out/.config`, `out/kernelversion`).
|
||||
- Regras obrigatórias para qualquer patch futuro:
|
||||
1. **Aditivo, zero mudança de comportamento** (por exemplo, linhas
|
||||
`EXPORT_SYMBOL_GPL()` junto aos clusters de export existentes). Qualquer
|
||||
coisa que altere lógica exige antes uma emenda ao design.
|
||||
2. Uma preocupação por patch, numerado em sequência — nunca editar um
|
||||
patch já aceito; substitua com um novo número.
|
||||
3. Uma seção neste arquivo (nos dois idiomas) **antes** do patch entrar:
|
||||
o quê/por quê/impacto/reversão + a evidência em VM.
|
||||
4. O patch tem de sobreviver a `make -C <kerneldir> bzImage modules` a
|
||||
partir de árvore limpa (CI da bancada: `vm-run-tests.sh` depois de um
|
||||
boot novo).
|
||||
|
||||
## Template para uma futura entrada `000N`
|
||||
|
||||
Copie este bloco para `docs/en/patches.md` e `docs/pt-BR/patches.md` (mesma
|
||||
âncora) ao adicionar `000N-*.patch`:
|
||||
|
||||
```
|
||||
### 000N — <título em uma linha>
|
||||
|
||||
- Arquivo: `patches/kernel/000N-<slug>.patch`
|
||||
- Status: aplicado (kernel 7.3.4) / substituído por 000M
|
||||
- Toca: <arquivo(s) da árvore do kernel>, <n> linhas adicionadas, 0 removidas
|
||||
- O quê: <símbolos / hooks / opções de Kconfig adicionados, exatos>
|
||||
- Por quê: <qual código do PF-on-Linux precisa; por que nenhuma API
|
||||
exportada basta>
|
||||
- Impacto: <impacto em runtime/ABI/memória no Linux puro; esperado: nenhum>
|
||||
- Reversão: `./scripts/unpatch.sh <kerneldir>` (aplicação reversa); o módulo
|
||||
passa a não linkar, com o símbolo faltante nomeado no modpost.
|
||||
- Evidência: <id da rodada na VM / data / saída do gate relevante>
|
||||
```
|
||||
|
||||
## Histórico deste diretório
|
||||
|
||||
| Data | Mudança |
|
||||
|---|---|
|
||||
| 2026-11-11 | P1 avaliado após o compile-port do M1 → **não necessário**; `p1-needs.txt` registra a evidência e a watch list. Conjunto permanece vazio. |
|
||||
@@ -0,0 +1,98 @@
|
||||
# Usando o PF-on-Linux no dia a dia
|
||||
|
||||
English: [docs/en/pf-guide.md](../en/pf-guide.md).
|
||||
|
||||
Este é o guia do "o que dá para rodar hoje". Ele segue a
|
||||
[matriz de features](feature-matrix.md): o escopo do **M2 (núcleo de
|
||||
filtragem: pass/block/quick/keep state/tabelas/anchors + habilitar/
|
||||
desabilitar)** tem evidência em VM; tudo além disso é COMPILES-untested ou
|
||||
PENDING. Instalação/build ficam em [install.md](install.md) e
|
||||
[build.md](build.md).
|
||||
|
||||
## Ciclo diário
|
||||
|
||||
```sh
|
||||
sudo insmod module/pf.ko # cria /dev/pf
|
||||
sudo pfctl -nf /etc/pf.conf # checagem de parse (só userland — sempre segura)
|
||||
sudo pfctl -f /etc/pf.conf # carrega o ruleset
|
||||
sudo pfctl -e # HABILITA — o tráfego passa a bater nas regras AGORA
|
||||
sudo pfctl -s info # status + contadores
|
||||
sudo pfctl -s states # listagem de estados (ver ressalva abaixo)
|
||||
sudo pfctl -t blk -T add 10.0.1.10 # atualização de tabela ao vivo
|
||||
sudo pfctl -a admin -f /etc/pf.d/admin.conf
|
||||
sudo pfctl -d # desabilita
|
||||
sudo rmmod pf # descarrega (regras/estados são liberados aqui)
|
||||
```
|
||||
|
||||
> **Atenção — a ordem importa**: `-f` e depois `-e`. Habilitar sem uma regra `pass`
|
||||
> que cubra o caminho de administração corta esse caminho — este port segue
|
||||
> a política padrão de bloqueio do OpenBSD, e as rodadas do gate M2
|
||||
> *realmente derrubaram o ssh da VM de teste* assim (a bancada mantém console
|
||||
> serial exatamente por isso). Num host remoto, libere a interface de
|
||||
> gerência antes de habilitar qualquer coisa.
|
||||
|
||||
## Um ruleset que funciona neste port hoje
|
||||
|
||||
```pf
|
||||
# /etc/pf.conf — apenas escopo M2. Os nomes de interface aqui são os da
|
||||
# bancada (mgmt/lan/wan); troque pelos seus (eth0, enp1s0, …).
|
||||
|
||||
# 1) mantenha você mesmo alcançável (faça isso primeiro, sempre)
|
||||
pass in quick on mgmt
|
||||
pass out quick
|
||||
|
||||
# 2) núcleo de filtragem — comprovado hoje: quick, match de proto/porta, state, drop
|
||||
table <blk> persist # blocklist viva (padrão do gate 03)
|
||||
block in quick proto tcp from <blk> to any
|
||||
|
||||
pass in quick on lan proto tcp from 10.0.1.0/24 to any port { 80 443 } keep state
|
||||
block drop in quick on wan proto icmp from any to any
|
||||
|
||||
# 3) anchors com precedência (padrão do gate 04)
|
||||
anchor "admin"
|
||||
```
|
||||
|
||||
Macros (`xif = "lan"`), `anchor "in/*"` aninhado, `label`, `tag`,
|
||||
`set timeout/limit/state-policy/block-policy/skip` e o resto da gramática do
|
||||
OpenBSD 8.0 **fazem parse** sem problema — o `make -C tools test` exercita
|
||||
tudo isso offline — mas o *comportamento no kernel* da maioria das opções
|
||||
`set` ainda não foi verificado em gate; trate como "carrega e reza" até a
|
||||
matriz mudar.
|
||||
|
||||
## O que evitar até os milestones chegarem
|
||||
|
||||
| Sintaxe | Até | O que acontece hoje |
|
||||
|---|---|---|
|
||||
| `nat-to`, `rdr-to`, `binat-to` | M3 | As regras carregam (código do engine compilado), mas a tradução não está fiada/verificada — **não conte com NAT**; no laboratório o masquerade de saída ainda vem do iptables/nft. |
|
||||
| `scrub (…)`, `set reassemble` | M3 | O `pf_norm.c` roda dentro do `pf_test` (compilado) — o comportamento com fragmentos forjados não foi testado; trate o resultado como não validado. |
|
||||
| `divert-to`, tag `divert` | M4 (+ família de sockets conforme comentário no código) | Os pacotes casados são **descartados** com um aviso de kernel rate-limited. Nunca coloque isso num host em produção. |
|
||||
| `route-to`, `dup-to`, `fail-to`, `reply-to` | M4 | Ainda não implementados como glue de roteamento Linux; as regras podem carregar, mas o comportamento é indefinido — evite. |
|
||||
| `queue` / `queue on` / `pfctl -s queue` | M5 | Faz parse e carrega. A fila cai no fallback priq da camada `ifq` de compat; **nenhuma limitação de banda acontece até o qdisc `pfq` chegar**. |
|
||||
| `log`, `log all`, `block log`, `pflogd` | M4 | Nada é logado (o stub avisa uma vez no load: `pfk: … packet not logged`). |
|
||||
| `route-label …` | nunca (não-objetivo) | Rejeitado na camada de ioctl. |
|
||||
| política por container (netns) | fora de escopo | O PF é uma instância global em `init_net`; network namespaces **não** enxergam o PF. |
|
||||
|
||||
## Lendo o estado corretamente
|
||||
|
||||
O `pfctl -s states` tem hoje uma anomalia de listagem/visibilidade sendo
|
||||
corrigida no trabalho do gate M2 (fluxos de trânsito podem passar enquanto
|
||||
nenhum estado é exibido). Até lá, use contadores (`pfctl -s info`) e
|
||||
observação externa (resultado do ping/nc, `tcpdump`) como evidência
|
||||
primária, exatamente como os gates fazem.
|
||||
|
||||
## Sinais no log do kernel que você deve conhecer
|
||||
|
||||
```
|
||||
pfk: PF-on-Linux engine loaded (OpenBSD 8.0 base) ← insmod ok
|
||||
pfk: netfilter hooks registered (10, netisr wq data path)
|
||||
pfk: divert v4 port N: no divert socket layer (M6 pending), dropping
|
||||
pfk: … packet not logged ← stub do pflog
|
||||
```
|
||||
|
||||
Descarregar o módulo primeiro para os timers de purge e drena a workqueue
|
||||
(um travamento de rmmod observado no lab foi corrigido assim) — mas a rodada
|
||||
de *soak* (ciclo rmmod/insmod sob tráfego) ainda é um gate aberto, então
|
||||
trate o unload como correto para uso administrativo e não comprovado para
|
||||
automação de hot-reload.
|
||||
|
||||
Lista completa das divergências de comportamento: [caveats.md](caveats.md).
|
||||
@@ -0,0 +1,122 @@
|
||||
# Bancada de validação em VMs (libvirt / Alpine)
|
||||
|
||||
English: [docs/en/vm-lab.md](../en/vm-lab.md).
|
||||
|
||||
Todos os comandos abaixo foram lidos dos scripts em `scripts/` (eles são a
|
||||
fonte de verdade); a bancada já foi criada e executada durante M0–M2 — uma
|
||||
rodada completa e verde da suíte é o gate do M2+ e ainda está em andamento
|
||||
(ver §6).
|
||||
|
||||
## 1. Onde ela mora
|
||||
|
||||
| Host | Papel |
|
||||
|---|---|
|
||||
| laptop (este repo) | compila kernel/módulo/ferramentas; comanda tudo por ssh |
|
||||
| **deb** — `root@192.168.1.242` | hipervisor libvirt; roda as 4 VMs Alpine `pftest-*` |
|
||||
|
||||
A bancada não precisa de NAT para a internet; o único acesso externo é a
|
||||
rede libvirt `default` (192.168.122.0/24, mgmt+DHCP) usada para o cloud-init
|
||||
instalar pacotes e para o ssh dos testes. A `sms-vm` existente no deb nunca é
|
||||
tocada.
|
||||
|
||||
## 2. Topologia (fonte única: `lab/ips.env`)
|
||||
|
||||
| VM | Papel | NICs | Kernel |
|
||||
|---|---|---|---|
|
||||
| `pftest-pf0` | filtro sob teste (roda o `pf.ko`) | `mgmt` (dhcp, rede default), `lan` 10.0.1.1/24, `wan` 10.0.2.1/24, `wan2` 10.0.3.1/24 | Alpine padrão via boot direto do kernel extraído da imagem; **ou** o `bzImage` 7.3.4 compilado com `--pf0-kernel=` (`root=/dev/vda2 ro console=ttyS0,115200n8`, sem initrd) |
|
||||
| `pftest-cli` | cliente | `mgmt` dhcp (rotas desabilitadas), `lan` 10.0.1.10/24, gw 10.0.1.1 (pf0) | Alpine padrão |
|
||||
| `pftest-srv` | servidor | `mgmt` dhcp (rotas desabilitadas), `wan` 10.0.2.20/24, gw 10.0.2.1 (pf0), 10.0.3.0/24 via gw2 | Alpine padrão |
|
||||
| `pftest-gw2` | gateway do segundo caminho (ip_forward=1) | `wan2` 10.0.3.2/24, `wan` 10.0.2.2/24 (sem NIC mgmt) | Alpine padrão |
|
||||
|
||||
Todas as VMs recebem `net.ipv4.ip_forward=1` e `rp_filter=0` via cloud-init,
|
||||
senha de root `pftest` mais um par de chaves de laboratório injetado
|
||||
(`/root/.ssh/pflab_ed` no deb, cópia no laptop para ProxyJump), e os pacotes
|
||||
`iperf3 tcpdump python3 scapy bash iptables` (o nome no Alpine é `scapy`; a
|
||||
imagem também precisa de `iptables` para os experimentos de masquerade do
|
||||
pf0).
|
||||
|
||||
Redes libvirt: `default` (mantida como está, iniciada+autostart para os
|
||||
pacotes) e bridges isoladas `pf-lan` 10.0.1.0/24, `pf-wan` 10.0.2.0/24,
|
||||
`pf-wan2` 10.0.3.0/24 — `forward="none"`, sem DHCP, mais IPs de bridge no
|
||||
host `10.0.x.254` no deb para que o deb consiga ssh/icmp na NIC de dados de
|
||||
cada guest. Dimensionamento: 2 vCPU / 1 GB por VM. Os discos são qcow2 por
|
||||
VM com **backing** na imagem compartilhada imutável
|
||||
`/var/lib/libvirt/images/alpine-base.qcow2` (sincronizada de
|
||||
`_vendor/images/alpine-3.22-x86_64-cloud.qcow2` por sha256).
|
||||
|
||||
> Nota de boot: a imagem cloud do Alpine não é inicializável em BIOS/UEFI no
|
||||
> QEMU como entregue (ESP vazia), então *todas* as VMs usam boot direto do
|
||||
> kernel extraído da própria imagem; `--pf0-kernel=` apenas troca o kernel
|
||||
> do pf0 pelo compilado.
|
||||
|
||||
## 3. Recriar / deployar / destruir
|
||||
|
||||
```sh
|
||||
./scripts/vm-create.sh # idempotente: derruba e recria domínios + seeds pftest-*
|
||||
./scripts/vm-create.sh --pf0-kernel=out/linux-7.3.4-bzImage # pf0 sobe com o kernel compilado (T6)
|
||||
./scripts/vm-create.sh --recreate-base # força re-sync da imagem base
|
||||
./scripts/vm-deploy.sh # laptop→deb ($DEPLOY_DIR)→pf0:/opt/pf:
|
||||
# out/*bzImage, module/*.ko, tools/out/*
|
||||
./scripts/vm-destroy.sh # domínios, discos, seeds, redes pf-* e fragmentos ssh
|
||||
./scripts/vm-destroy.sh --purge-base --purge-keys # + imagem base + chaves do lab
|
||||
```
|
||||
|
||||
O `vm-create.sh` termina validando `ssh pf0 'ip -br a'` e deixa todas as VMs
|
||||
ligadas. Os aliases de ssh do laptop (`pf0|cli|srv|gw2`, ProxyJump via deb)
|
||||
são gerados em `~/.ssh/config.d/pflab`.
|
||||
|
||||
## 4. Rodando a suíte
|
||||
|
||||
```sh
|
||||
./scripts/vm-run-tests.sh # ou: make test
|
||||
```
|
||||
|
||||
Determinístico por projeto: **antes de cada gate** o runner destrói e
|
||||
reinicia o `pftest-pf0` (delta de disco e módulo zerados), espera o ssh no
|
||||
endereço DHCP de mgmt `192.168.122.8`, re-deploya `pf.ko`/`pfctl`/`*.conf` no
|
||||
guest e então roda o script do gate a partir do **laptop** (os gates falam
|
||||
com as VMs através do deb via ssh aninhado — `tests/vm/lib.sh`). A saída de
|
||||
cada gate é gravada em `out/test-logs/<gate>.sh.log`; o runner imprime
|
||||
`PASS <gate>` / `FAIL <gate> (rc=...)` por teste, um resumo `PASS=n FAIL=m`
|
||||
e sai com código diferente de zero se algo falhou.
|
||||
|
||||
## 5. O que cada gate prova hoje
|
||||
|
||||
| Gate | Cenário (verificado com tráfego real, não com código de saída) |
|
||||
|---|---|
|
||||
| `01-state.sh` | `pass in/out quick proto icmp keep state` → cli pinga srv **através** do pf0 (trânsito); depois o pf0 lista um estado icmp via `pfctl -s states` |
|
||||
| `02-block.sh` | `block drop in quick proto icmp` → o ping do cli falha; `block return-rst in quick proto tcp … port 8080` → o `nc` do cli para srv:8080 é recusado (RST), checado pelo código de saída do nc |
|
||||
| `03-tables.sh` | `table <blk> { 10.0.1.10 }` + `block in quick from <blk>` → `pfctl -t blk -T show` lista; o ping do cli é bloqueado; `-T delete` ao vivo libera o fluxo |
|
||||
| `04-anchors.sh` | regra de anchor (`-a restricted -f`) liberando icmp é avaliada **antes** do `block` do ruleset principal (posicionamento top/quick); `pfctl -s Anchors` lista o anchor |
|
||||
|
||||
Gates planejados (documento de plano, T11–T18): 05-nat, 06-rdr, 07-binat,
|
||||
08-scrub (scapy), 09-routeto, 10-dup-fail, 11-divert, 12-queues (tolerância
|
||||
de ±15% no rate do iperf), 13-pflog, 14-soak (reload sob tráfego + ciclo de
|
||||
rmmod + kmemleak). IPv6 é smoke test para state/block/nat66/scrub. O
|
||||
`bench-basic.sh` (planejado, ainda não está na árvore) é informativo; não
|
||||
existe gate numérico de performance.
|
||||
|
||||
## 6. Status atual da suíte (2026-11-11, com honestidade)
|
||||
|
||||
Os gates 01–04 do M2 existem e rodam, mas **ainda não estão todos verdes** —
|
||||
o caminho de dados (modelo netisr/workqueue) e a visibilidade de estados
|
||||
estão sendo corrigidos no trabalho do gate M2. A última rodada capturada
|
||||
(`out/test-logs/`) mostra o estado misto: ping de trânsito OK / listagem de
|
||||
estado falhando no 01, icmp bloqueado OK no 02, resultados parciais no 03, 04
|
||||
abortado. Trate "VERIFIED-in-VM" na
|
||||
[matriz de features](feature-matrix.md) como reservado a gates que passam
|
||||
numa rodada completa e limpa; nada além do M1 é declarado VERIFIED até lá.
|
||||
|
||||
## 7. Inspeção manual
|
||||
|
||||
```sh
|
||||
ssh pf0 # via alias do laptop (ProxyJump deb)
|
||||
insmod /opt/pf/pf.ko
|
||||
/opt/pf/pfctl -e
|
||||
/opt/pf/pfctl -f /tmp/g1.conf
|
||||
/opt/pf/pfctl -s info ; /opt/pf/pfctl -s states -v
|
||||
dmesg | grep pfk
|
||||
```
|
||||
|
||||
Artefatos de rodadas completas futuras (log + pcaps) devem ir junto com o
|
||||
release no Gitea (spec §7).
|
||||
@@ -23,10 +23,14 @@ static inline bool refcnt_take_not0(struct refcnt *r)
|
||||
{
|
||||
return refcount_inc_not_zero(&r->rc);
|
||||
}
|
||||
/*
|
||||
* OpenBSD semantics (sys/kern/kern_synch.c): refcnt_rele() returns 1 when
|
||||
* the last reference is dropped and 0 otherwise. PF_REF_RELE() and the
|
||||
* pf_ruleset anchor release rely on that polarity.
|
||||
*/
|
||||
static inline unsigned int refcnt_rele(struct refcnt *r)
|
||||
{
|
||||
return (unsigned int)refcount_dec_and_test(&r->rc) ?
|
||||
refcnt_value(r) : refcnt_value(r) + 1;
|
||||
return (unsigned int)refcount_dec_and_test(&r->rc) ? 1 : 0;
|
||||
}
|
||||
static inline unsigned int refcnt_rele_wake(struct refcnt *r)
|
||||
{
|
||||
|
||||
@@ -9,5 +9,11 @@ int pfk_enable(int on);
|
||||
void pfk_status(struct pf_status *s);
|
||||
int pfk_hooks_register(void);
|
||||
void pfk_hooks_unregister(void);
|
||||
/* AF_DIVERT family (module-internal socket protocol, PF_DIVERT=32) */
|
||||
int pfk_divert_sock_register(void);
|
||||
void pfk_divert_sock_unregister(void);
|
||||
void divert_packet(struct mbuf *, int, u_int16_t);
|
||||
void divert6_packet(struct mbuf *, int, u_int16_t);
|
||||
void pfk_hooks_unregister(void);
|
||||
|
||||
#endif
|
||||
@@ -1828,6 +1828,7 @@ deps_pfsrc/divert_sock.o := \
|
||||
include/compat/sys/rwlock.h \
|
||||
include/compat/sys/mutex.h \
|
||||
include/compat/sys/pclock.h \
|
||||
include/pfk_api.h \
|
||||
|
||||
pfsrc/divert_sock.o: $(deps_pfsrc/divert_sock.o)
|
||||
|
||||
|
||||
+481
-17
@@ -1,33 +1,497 @@
|
||||
/*
|
||||
* PF-on-Linux divert entry points used by pf.c (ISC 2026).
|
||||
* PF-on-Linux: AF_DIVERT socket family (ISC 2026)
|
||||
*
|
||||
* BSD semantics (OpenBSD netinet/ip_divert.c): divert_packet()/divert6_packet()
|
||||
* find the AF_DIVERT pcb bound to `divert_port`, queue the packet to it and
|
||||
* consume the mbuf; when NO divert socket is attached, OpenBSD drops the
|
||||
* packet (m_freem) — that is exactly the observable behaviour implemented
|
||||
* here until the pfdiv socket layer is instantiated for Linux (milestone
|
||||
* M6); a rate-limited kernel log records the event so drops are never
|
||||
* silent.
|
||||
* Implements OpenBSD's divert(4) as a runtime-registered protocol family
|
||||
* inside the PF module: PF_DIVERT = 32 (BSD value; the unused legacy
|
||||
* PF_IUCV slot on Linux, and NPROTO = AF_MAX = 45 admits it),
|
||||
* IPPROTO_DIVERT = 254. No core patch is required — families are
|
||||
* registered with sock_register() at module init.
|
||||
*
|
||||
* The upstream BSD socket layer is staged in pfsrc/ip_divert.c and becomes
|
||||
* the real back end in M6; these wrappers keep the same call signature
|
||||
* (struct mbuf *, int dir, u_int16_t divert_port).
|
||||
* Userspace ABI (see docs/en/divert-wireformat.md):
|
||||
* fd = socket(PF_DIVERT, SOCK_RAW, IPPROTO_DIVERT);
|
||||
* struct sockaddr_in b = { .sin_family = AF_INET, .sin_port = port };
|
||||
* bind(fd, &b); # binds the divert "port"
|
||||
* n = recv(fd, pkt, ...) # full IP packet incl. header
|
||||
* send(fd, pkt, n) # reinject per BSD semantics
|
||||
*
|
||||
* Re-injection mirrors OpenBSD netinet/ip_divert.c:divert_output():
|
||||
* type PF_DIVERT_TO -> local delivery as if arrived on the recorded
|
||||
* interface (netif_rx)
|
||||
* plain divert tag -> route again (dst output), matching BSD ip_output
|
||||
*
|
||||
* Semantics notes (PFLINUX, docs/porting-notes.md):
|
||||
* - queue selection scans the bound list under pfk_divert_lock;
|
||||
* - full-socket drop counts divs_fullsock like BSD;
|
||||
* - no socket bound to the port: divs_noport + drop (BSD identical).
|
||||
*/
|
||||
#include <pfk.h>
|
||||
#include <net/pfvar.h>
|
||||
#include <pfk_api.h>
|
||||
#include <net/sock.h>
|
||||
|
||||
#define PF_DIVERT_FAMILY 32 /* BSD PF_DIVERT */
|
||||
#ifndef IPPROTO_DIVERT
|
||||
#define IPPROTO_DIVERT 254
|
||||
#endif
|
||||
|
||||
/* control message codes reported to userspace (sendmsg accepts them back) */
|
||||
#define PFDIVERT_CMSG_IFINDEX 1
|
||||
#define PFDIVERT_CMSG_DIR 2
|
||||
#define PFDIVERT_CMSG_TYPE 3
|
||||
|
||||
struct pfk_divert_pkt {
|
||||
struct list_head node;
|
||||
struct sk_buff *skb;
|
||||
int dir;
|
||||
int type;
|
||||
u16 port;
|
||||
int ifindex;
|
||||
};
|
||||
|
||||
struct pfk_divert_stat {
|
||||
u64 divs_ipackets;
|
||||
u64 divs_noport;
|
||||
u64 divs_fullsock;
|
||||
u64 divs_opackets;
|
||||
u64 divs_errors;
|
||||
};
|
||||
|
||||
static const struct proto_ops pfk_divert_ops;
|
||||
|
||||
struct pfk_divert_sock {
|
||||
struct list_head link; /* global bound list */
|
||||
struct list_head q;
|
||||
spinlock_t lock;
|
||||
u16 port;
|
||||
u32 qlen;
|
||||
wait_queue_head_t wq;
|
||||
u32 qspace;
|
||||
struct sock *sk;
|
||||
struct pfk_divert_stat stats;
|
||||
};
|
||||
|
||||
static LIST_HEAD(pfk_divert_list);
|
||||
static DEFINE_SPINLOCK(pfk_divert_lock);
|
||||
static struct pfk_divert_stat pfk_divert_global;
|
||||
static struct proto pfk_divert_proto = {
|
||||
.name = "PFK_DIVERT",
|
||||
.owner = THIS_MODULE,
|
||||
.obj_size = sizeof(struct sock),
|
||||
};
|
||||
|
||||
static inline struct pfk_divert_sock *
|
||||
pfk_ds(const struct sock *sk)
|
||||
{
|
||||
return sk ? (struct pfk_divert_sock *)sk->sk_user_data : NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
pfk_divert_pkt_free(struct pfk_divert_pkt *dp)
|
||||
{
|
||||
if (!dp)
|
||||
return;
|
||||
if (dp->skb)
|
||||
kfree_skb(dp->skb);
|
||||
kfree(dp);
|
||||
}
|
||||
|
||||
/* PF engine entry points (called from the ported pf.c) */
|
||||
static int
|
||||
pfk_divert_queue(struct sk_buff *skb, int dir, int type, u16 port)
|
||||
{
|
||||
struct pfk_divert_sock *ds;
|
||||
struct pfk_divert_pkt *dp;
|
||||
unsigned long flags;
|
||||
|
||||
dp = kmalloc(sizeof(*dp), GFP_ATOMIC);
|
||||
if (!dp) {
|
||||
kfree_skb(skb);
|
||||
return ENOMEM;
|
||||
}
|
||||
dp->skb = skb;
|
||||
dp->dir = dir;
|
||||
dp->type = type;
|
||||
dp->port = port;
|
||||
dp->ifindex = skb->dev ? skb->dev->ifindex : 0;
|
||||
|
||||
spin_lock_irqsave(&pfk_divert_lock, flags);
|
||||
ds = NULL;
|
||||
list_for_each_entry(ds, &pfk_divert_list, link) {
|
||||
if (ds->port == port)
|
||||
break;
|
||||
}
|
||||
if (&ds->link == &pfk_divert_list) {
|
||||
pfk_divert_global.divs_noport++;
|
||||
spin_unlock_irqrestore(&pfk_divert_lock, flags);
|
||||
pr_info_ratelimited("pfk: divert port %u: no socket bound\n",
|
||||
port);
|
||||
pfk_divert_pkt_free(dp);
|
||||
return ENOENT;
|
||||
}
|
||||
if (ds->qlen >= ds->qspace) {
|
||||
ds->stats.divs_fullsock++;
|
||||
pfk_divert_global.divs_fullsock++;
|
||||
spin_unlock_irqrestore(&pfk_divert_lock, flags);
|
||||
pfk_divert_pkt_free(dp);
|
||||
return ENOBUFS;
|
||||
}
|
||||
spin_lock(&ds->lock);
|
||||
list_add_tail(&dp->node, &ds->q);
|
||||
ds->qlen++;
|
||||
ds->stats.divs_ipackets++;
|
||||
spin_unlock(&ds->lock);
|
||||
spin_unlock_irqrestore(&pfk_divert_lock, flags);
|
||||
|
||||
wake_up_interruptible(&ds->wq);
|
||||
return 0;
|
||||
}
|
||||
|
||||
extern struct pf_divert *pf_find_divert(struct mbuf *);
|
||||
|
||||
void
|
||||
divert_packet(struct mbuf *m, int dir, u_int16_t divert_port)
|
||||
{
|
||||
pr_warn_ratelimited("pfk: divert v4 port %u: no divert socket layer (M6 pending), dropping\n",
|
||||
ntohs(divert_port));
|
||||
struct sk_buff *skb;
|
||||
int type = PF_DIVERT_TO;
|
||||
|
||||
if (!m)
|
||||
return;
|
||||
{
|
||||
struct pf_divert *dv = pf_find_divert(m);
|
||||
|
||||
if (dv)
|
||||
type = dv->type;
|
||||
}
|
||||
skb = m_to_skb(m);
|
||||
m->skb = NULL; /* ownership moves into the divert layer */
|
||||
m_freem(m);
|
||||
pfk_divert_queue(skb, dir, type, divert_port);
|
||||
}
|
||||
|
||||
void
|
||||
divert6_packet(struct mbuf *m, int dir, u_int16_t divert_port)
|
||||
{
|
||||
pr_warn_ratelimited("pfk: divert v6 port %u: no divert socket layer (M6 pending), dropping\n",
|
||||
ntohs(divert_port));
|
||||
m_freem(m);
|
||||
divert_packet(m, dir, divert_port);
|
||||
}
|
||||
|
||||
/* ---------------- socket ops ---------------- */
|
||||
|
||||
static int
|
||||
pfk_divert_create(struct net *net, struct socket *sock, int protocol, int kern)
|
||||
{
|
||||
struct sock *sk;
|
||||
struct pfk_divert_sock *ds;
|
||||
|
||||
if (protocol != IPPROTO_DIVERT && protocol != 0)
|
||||
return -EPROTONOSUPPORT;
|
||||
if (sock->type != SOCK_RAW)
|
||||
return -ESOCKTNOSUPPORT;
|
||||
|
||||
ds = kzalloc(sizeof(*ds), GFP_KERNEL);
|
||||
if (!ds)
|
||||
return -ENOMEM;
|
||||
spin_lock_init(&ds->lock);
|
||||
init_waitqueue_head(&ds->wq);
|
||||
INIT_LIST_HEAD(&ds->link);
|
||||
INIT_LIST_HEAD(&ds->q);
|
||||
ds->qspace = 65536 + 100; /* BSD DIVERT_RECVSPACE */
|
||||
|
||||
sk = sk_alloc(net, PF_DIVERT_FAMILY, GFP_KERNEL, &pfk_divert_proto,
|
||||
kern);
|
||||
if (!sk) {
|
||||
kfree(ds);
|
||||
return -ENOMEM;
|
||||
}
|
||||
sock_init_data(sock, sk);
|
||||
sk->sk_user_data = ds;
|
||||
ds->sk = sk;
|
||||
sock->state = SS_UNCONNECTED;
|
||||
sock->ops = &pfk_divert_ops; /* forward decl below via struct */
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
pfk_divert_bind(struct socket *sock, struct sockaddr_unsized *uaddr,
|
||||
int addr_len)
|
||||
{
|
||||
struct pfk_divert_sock *ds = pfk_ds(sock->sk);
|
||||
unsigned long flags;
|
||||
|
||||
if (!ds)
|
||||
return -EINVAL;
|
||||
if (addr_len < (int)sizeof(struct sockaddr_in))
|
||||
return -EINVAL;
|
||||
|
||||
ds->port = ntohs(((struct sockaddr_in *)uaddr)->sin_port);
|
||||
if (ds->port == 0)
|
||||
return -EADDRNOTAVAIL; /* divert port 0 not allowed */
|
||||
|
||||
spin_lock_irqsave(&pfk_divert_lock, flags);
|
||||
if (!list_empty(&ds->link)) {
|
||||
spin_unlock_irqrestore(&pfk_divert_lock, flags);
|
||||
return -EADDRINUSE;
|
||||
}
|
||||
list_add_tail(&ds->link, &pfk_divert_list);
|
||||
spin_unlock_irqrestore(&pfk_divert_lock, flags);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
pfk_divert_recvmsg(struct socket *sock, struct msghdr *msg, size_t len,
|
||||
int flags)
|
||||
{
|
||||
struct pfk_divert_sock *ds = pfk_ds(sock->sk);
|
||||
struct pfk_divert_pkt *dp;
|
||||
struct sk_buff *skb;
|
||||
unsigned long iflags;
|
||||
int copied;
|
||||
|
||||
if (!ds)
|
||||
return -EINVAL;
|
||||
if (msg_data_left(msg) == 0)
|
||||
return -EINVAL;
|
||||
|
||||
for (;;) {
|
||||
spin_lock_irqsave(&ds->lock, iflags);
|
||||
if (!list_empty(&ds->q)) {
|
||||
dp = list_first_entry(&ds->q, struct pfk_divert_pkt,
|
||||
node);
|
||||
list_del_init(&dp->node);
|
||||
ds->qlen--;
|
||||
spin_unlock_irqrestore(&ds->lock, iflags);
|
||||
goto have;
|
||||
}
|
||||
spin_unlock_irqrestore(&ds->lock, iflags);
|
||||
if (flags & MSG_DONTWAIT ||
|
||||
READ_ONCE(sock->sk->sk_shutdown) != 0)
|
||||
return -EAGAIN;
|
||||
wait_event_interruptible(ds->wq,
|
||||
!list_empty(&ds->q) ||
|
||||
READ_ONCE(sock->sk->sk_shutdown) != 0);
|
||||
if (signal_pending(current))
|
||||
return -ERESTARTSYS;
|
||||
}
|
||||
|
||||
have:
|
||||
skb = dp->skb;
|
||||
copied = min_t(size_t, len, skb->len);
|
||||
if (skb_copy_datagram_msg(skb, 0, msg, copied)) {
|
||||
pfk_divert_pkt_free(dp);
|
||||
return -EFAULT;
|
||||
}
|
||||
/* metadata as control data (BSD equivalent: sockaddr_div) */
|
||||
{
|
||||
struct cmsghdr *cm = CMSG_FIRSTHDR(msg);
|
||||
|
||||
if (cm && msg->msg_controllen >= sizeof(struct cmsghdr) + sizeof(int)) {
|
||||
msg->msg_controllen = 0; /* busybox-safe default */
|
||||
cm = NULL;
|
||||
}
|
||||
if (cm) {
|
||||
int ifindex = dp->ifindex;
|
||||
|
||||
cm->cmsg_level = SOL_SOCKET;
|
||||
cm->cmsg_type = PFDIVERT_CMSG_IFINDEX;
|
||||
cm->cmsg_len = CMSG_LEN(sizeof(int));
|
||||
memcpy(CMSG_DATA(cm), &ifindex, sizeof(ifindex));
|
||||
msg->msg_controllen = CMSG_SPACE(sizeof(int));
|
||||
}
|
||||
}
|
||||
sock_net_set(sock->sk, &init_net);
|
||||
ds->stats.divs_opackets++;
|
||||
pfk_divert_pkt_free(dp);
|
||||
return copied;
|
||||
}
|
||||
|
||||
/*
|
||||
* sendmsg: userspace hands the (possibly rewritten) packet back. The
|
||||
* ip header inside the bytes decides the flow; for PF_DIVERT_TO we inject
|
||||
* as input on the socket's last-recorded interface, otherwise we route.
|
||||
*/
|
||||
static int
|
||||
pfk_divert_sendmsg(struct socket *sock, struct msghdr *msg, size_t len)
|
||||
{
|
||||
struct pfk_divert_sock *ds = pfk_ds(sock->sk);
|
||||
struct sk_buff *skb;
|
||||
struct pf_ip *ip;
|
||||
int ifindex = 0;
|
||||
int type = PF_DIVERT_TO;
|
||||
struct cmsghdr *cm;
|
||||
|
||||
if (!ds)
|
||||
return -EINVAL;
|
||||
if (len > 65535)
|
||||
return -EMSGSIZE;
|
||||
|
||||
for (cm = CMSG_FIRSTHDR(msg); cm; cm = CMSG_NXTHDR(msg, cm)) {
|
||||
if (cm->cmsg_level != SOL_SOCKET)
|
||||
continue;
|
||||
if (cm->cmsg_type == PFDIVERT_CMSG_IFINDEX &&
|
||||
cm->cmsg_len >= CMSG_LEN(sizeof(int)))
|
||||
memcpy(&ifindex, CMSG_DATA(cm), sizeof(int));
|
||||
if (cm->cmsg_type == PFDIVERT_CMSG_TYPE &&
|
||||
cm->cmsg_len >= CMSG_LEN(sizeof(int)))
|
||||
memcpy(&type, CMSG_DATA(cm), sizeof(int));
|
||||
}
|
||||
|
||||
skb = alloc_skb(len + NET_SKB_PAD + NET_IP_ALIGN, GFP_KERNEL);
|
||||
if (!skb)
|
||||
return -ENOMEM;
|
||||
skb_reserve(skb, NET_SKB_PAD + NET_IP_ALIGN);
|
||||
if (memcpy_from_msg(skb_put(skb, len), msg, len)) {
|
||||
kfree_skb(skb);
|
||||
return -EFAULT;
|
||||
}
|
||||
skb->protocol = htons(ETH_P_IP);
|
||||
|
||||
if (type == PF_DIVERT_TO) {
|
||||
/* deliver locally as if arrived on ifindex (default: last
|
||||
* ingress iface seen by this socket's queue) */
|
||||
struct net_device *dev = ifindex > 0 ?
|
||||
dev_get_by_index(&init_net, ifindex) : NULL;
|
||||
|
||||
if (dev) {
|
||||
skb->dev = dev;
|
||||
dev_put(dev);
|
||||
skb_reset_network_header(skb);
|
||||
/* netif_rx wants data at L3 */
|
||||
netif_rx(skb);
|
||||
return (int)len;
|
||||
}
|
||||
/* no device: route below */
|
||||
}
|
||||
|
||||
/* plain divert: route again (BSD divert_output -> ip_output) */
|
||||
if (pskb_may_pull(skb, sizeof(*ip))) {
|
||||
struct flowi4 fl4 = { };
|
||||
struct dst_entry *d;
|
||||
|
||||
ip = (struct pf_ip *)skb->data;
|
||||
fl4.daddr = ip->ip_dst.s_addr;
|
||||
fl4.saddr = ip->ip_src.s_addr;
|
||||
{
|
||||
struct rtable *rt = ip_route_output_key(&init_net,
|
||||
&fl4);
|
||||
|
||||
if (IS_ERR(rt))
|
||||
rt = NULL;
|
||||
d = rt ? &rt->dst : NULL;
|
||||
}
|
||||
if (d) {
|
||||
skb_dst_set(skb, d);
|
||||
skb_reset_network_header(skb);
|
||||
if (!skb->dev)
|
||||
skb->dev = skb_dst(skb)->dev;
|
||||
d->output(&init_net, NULL, skb);
|
||||
return (int)len;
|
||||
}
|
||||
}
|
||||
kfree_skb(skb);
|
||||
ds->stats.divs_errors++;
|
||||
return -EHOSTUNREACH;
|
||||
}
|
||||
|
||||
static int
|
||||
pfk_divert_release(struct socket *sock)
|
||||
{
|
||||
struct sock *sk = sock->sk;
|
||||
struct pfk_divert_sock *ds;
|
||||
unsigned long flags;
|
||||
|
||||
if (!sk)
|
||||
return 0;
|
||||
ds = pfk_ds(sk);
|
||||
if (ds) {
|
||||
spin_lock_irqsave(&pfk_divert_lock, flags);
|
||||
if (!list_empty(&ds->link))
|
||||
list_del_init(&ds->link);
|
||||
spin_unlock_irqrestore(&pfk_divert_lock, flags);
|
||||
while (!list_empty(&ds->q)) {
|
||||
struct pfk_divert_pkt *dp = list_first_entry(&ds->q,
|
||||
struct pfk_divert_pkt, node);
|
||||
|
||||
list_del_init(&dp->node);
|
||||
pfk_divert_pkt_free(dp);
|
||||
}
|
||||
kfree(ds);
|
||||
sk->sk_user_data = NULL;
|
||||
}
|
||||
sock_orphan(sk);
|
||||
sock_put(sk);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static __poll_t
|
||||
pfk_divert_poll(struct file *file, struct socket *sock,
|
||||
poll_table *wait)
|
||||
{
|
||||
struct pfk_divert_sock *ds = pfk_ds(sock->sk);
|
||||
__poll_t mask = 0;
|
||||
|
||||
if (ds)
|
||||
poll_wait(file, &ds->wq, wait);
|
||||
else
|
||||
sock_poll_wait(file, sock, wait);
|
||||
if (ds && !list_empty(&ds->q))
|
||||
mask |= EPOLLIN | EPOLLRDNORM;
|
||||
return mask;
|
||||
}
|
||||
|
||||
static int
|
||||
pfk_divert_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
struct pfk_divert_sock *ds = pfk_ds(sock->sk);
|
||||
|
||||
if (!ds)
|
||||
return -EINVAL;
|
||||
switch (cmd) {
|
||||
case 3: { /* DIVERTCTL_STATS-ish: export queue stats */
|
||||
struct pfk_divert_stat st;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&ds->lock, flags);
|
||||
st = ds->stats;
|
||||
spin_unlock_irqrestore(&ds->lock, flags);
|
||||
if (copy_to_user((void __user *)arg, &st, sizeof(st)))
|
||||
return -EFAULT;
|
||||
return 0;
|
||||
}
|
||||
default:
|
||||
return -ENOIOCTLCMD;
|
||||
}
|
||||
}
|
||||
|
||||
static const struct proto_ops pfk_divert_ops = {
|
||||
.family = PF_DIVERT_FAMILY,
|
||||
.owner = THIS_MODULE,
|
||||
.release = pfk_divert_release,
|
||||
.bind = pfk_divert_bind,
|
||||
.connect = sock_no_connect,
|
||||
.socketpair = sock_no_socketpair,
|
||||
.accept = sock_no_accept,
|
||||
.getname = sock_no_getname,
|
||||
.poll = pfk_divert_poll,
|
||||
.ioctl = pfk_divert_ioctl,
|
||||
.listen = sock_no_listen,
|
||||
.shutdown = sock_no_shutdown,
|
||||
.sendmsg = pfk_divert_sendmsg,
|
||||
.recvmsg = pfk_divert_recvmsg,
|
||||
.mmap = sock_no_mmap,
|
||||
};
|
||||
|
||||
static const struct net_proto_family pfk_divert_family = {
|
||||
.family = PF_DIVERT_FAMILY,
|
||||
.create = pfk_divert_create,
|
||||
.owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
int
|
||||
pfk_divert_sock_register(void)
|
||||
{
|
||||
return sock_register(&pfk_divert_family);
|
||||
}
|
||||
|
||||
void
|
||||
pfk_divert_sock_unregister(void)
|
||||
{
|
||||
sock_unregister(PF_DIVERT_FAMILY);
|
||||
}
|
||||
@@ -360,6 +360,8 @@ pfr_add_addrs(struct pfr_table *tbl, struct pfr_addr *addr, int size,
|
||||
else
|
||||
ke->pfrke_fb = PFR_FB_NONE;
|
||||
}
|
||||
pr_info("pfkD: add_addrs ke af=%d p=%p q=%p\n",
|
||||
ke->pfrke_af, (void *)p, (void *)q);
|
||||
if (p == NULL && q == NULL) {
|
||||
if (pfr_route_kentry(tmpkt, ke)) {
|
||||
/* defer destroy after feedback is processed */
|
||||
@@ -1931,6 +1933,9 @@ pfr_ina_define(struct pfr_table *tbl, struct pfr_addr *addr, int size,
|
||||
if (error)
|
||||
return (error);
|
||||
rs = pf_find_ruleset(tbl->pfrt_anchor);
|
||||
pr_info("pfkD: ina_define name=%s anchor=%s size=%d flags=%x rs=%p topen=%d tticket=%u ticket=%u\n",
|
||||
tbl->pfrt_name, tbl->pfrt_anchor, size, flags, (void *)rs,
|
||||
rs ? rs->topen : -1, rs ? rs->tticket : 0, ticket);
|
||||
if (rs == NULL || !rs->topen || ticket != rs->tticket)
|
||||
return (EBUSY);
|
||||
tbl->pfrt_flags |= PFR_TFLAG_INACTIVE;
|
||||
@@ -1978,12 +1983,16 @@ _skip:
|
||||
senderr(EFAULT);
|
||||
if (pfr_validate_addr(&ad))
|
||||
senderr(EINVAL);
|
||||
pr_info("pfkD: ina_define addr[%d] af=%d net=%d flags=%llx ifindex=%d\n",
|
||||
i, ad.pfra_af, ad.pfra_net, (unsigned long long)ad.pfra_flags,
|
||||
ad.pfra_ifindex);
|
||||
if (pfr_lookup_addr(shadow, &ad, 1) != NULL)
|
||||
continue;
|
||||
p = pfr_create_kentry(&ad);
|
||||
if (p == NULL)
|
||||
senderr(ENOMEM);
|
||||
if (pfr_route_kentry(shadow, p)) {
|
||||
pr_info("pfkD: ina_define route failed\n");
|
||||
pfr_destroy_kentry(p);
|
||||
continue;
|
||||
}
|
||||
@@ -2000,6 +2009,8 @@ _skip:
|
||||
pfr_insert_ktables(&tableq);
|
||||
shadow->pfrkt_cnt = (flags & PFR_FLAG_ADDRSTOO) ?
|
||||
xaddr : NO_ADDRESSES;
|
||||
pr_info("pfkD: ina_define done xadd=%d xaddr=%d shadow_cnt=%d\n",
|
||||
xadd, xaddr, shadow->pfrkt_cnt);
|
||||
kt->pfrkt_shadow = shadow;
|
||||
} else {
|
||||
pfr_clean_node_mask(shadow, &addrq);
|
||||
@@ -2066,7 +2077,13 @@ pfr_ina_commit(struct pfr_table *trs, u_int32_t ticket, int *nadd,
|
||||
return (EBUSY);
|
||||
|
||||
SLIST_INIT(&workq);
|
||||
pr_info("pfkD: ina_commit name=%s anchor=%s ticket=%u\n",
|
||||
trs->pfrt_name, trs->pfrt_anchor, ticket);
|
||||
RB_FOREACH(p, pfr_ktablehead, &pfr_ktables) {
|
||||
pr_info("pfkD: ina_commit cand name=%s flags=%x inactive=%d shadow=%p\n",
|
||||
p->pfrkt_t.pfrt_name, p->pfrkt_flags,
|
||||
!!(p->pfrkt_flags & PFR_TFLAG_INACTIVE),
|
||||
(void *)p->pfrkt_shadow);
|
||||
if (!(p->pfrkt_flags & PFR_TFLAG_INACTIVE) ||
|
||||
pfr_skip_table(trs, p, 0))
|
||||
continue;
|
||||
@@ -2098,6 +2115,10 @@ pfr_commit_ktable(struct pfr_ktable *kt, time_t tzero)
|
||||
struct pfr_ktable *shadow = kt->pfrkt_shadow;
|
||||
int nflags;
|
||||
|
||||
pr_info("pfkD: commit_ktable name=%s flags=%x shadow_cnt=%d\n",
|
||||
kt->pfrkt_t.pfrt_name, kt->pfrkt_flags,
|
||||
shadow ? shadow->pfrkt_cnt : -1);
|
||||
|
||||
if (shadow->pfrkt_cnt == NO_ADDRESSES) {
|
||||
if (!(kt->pfrkt_flags & PFR_TFLAG_ACTIVE))
|
||||
pfr_clstats_ktable(kt, tzero, 1);
|
||||
|
||||
@@ -96,6 +96,8 @@ static struct miscdevice pfk_dev = {
|
||||
|
||||
extern void pf_status_init(void);
|
||||
extern int pfattach(int);
|
||||
extern int pfk_divert_sock_register(void);
|
||||
extern void pfk_divert_sock_unregister(void);
|
||||
|
||||
static void
|
||||
pfk_pfi_event(int added, struct pfk_ifnet *ifp)
|
||||
@@ -134,15 +136,20 @@ static int __init pfk_init(void)
|
||||
pfattach(0);
|
||||
pfk_if_set_event_cb(pfk_pfi_event);
|
||||
pfk_if_iterate(pfk_pfi_attach_one);
|
||||
err = pfk_hooks_register();
|
||||
err = pfk_divert_sock_register();
|
||||
if (err)
|
||||
goto err_if;
|
||||
err = pfk_hooks_register();
|
||||
if (err)
|
||||
goto err_divert;
|
||||
err = misc_register(&pfk_dev);
|
||||
if (err)
|
||||
goto err_hooks;
|
||||
pr_info("pfk: PF-on-Linux engine loaded (OpenBSD 8.0 base)\n");
|
||||
return 0;
|
||||
err_hooks:
|
||||
pfk_divert_sock_unregister();
|
||||
err_divert:
|
||||
pfk_hooks_unregister();
|
||||
err_if:
|
||||
pfk_if_exit();
|
||||
@@ -161,6 +168,7 @@ static void __exit pfk_exit(void)
|
||||
* self-rearms (pf_purge re-arms on every run) and would keep the
|
||||
* workqueues non-empty forever (observed rmmod hang). */
|
||||
misc_deregister(&pfk_dev);
|
||||
pfk_divert_sock_unregister();
|
||||
pfk_net_lock();
|
||||
pf_status.running = 0;
|
||||
pfk_net_unlock();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
# M2 gate 1: stateful transit + state visibility
|
||||
set -uo pipefail; cd "$(dirname "$0")"; . ./lib.sh
|
||||
pfensure
|
||||
pf0 'insmod /tmp/pf.ko 2>/dev/null || insmod /opt/pf/pf.ko; true'
|
||||
pf0 '/opt/pf/pfctl -e; /opt/pf/pfctl -f /dev/null; echo READY' | grep -q READY || { bad load; finish; }
|
||||
cat > /tmp/g1.conf <<'C'
|
||||
pass in quick proto icmp keep state
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
# M2 gate 2: block drop + block return-rst
|
||||
set -uo pipefail; cd "$(dirname "$0")"; . ./lib.sh
|
||||
pfensure; pf0 '/opt/pf/pfctl -e' >/dev/null
|
||||
pf0 'insmod /tmp/pf.ko 2>/dev/null || insmod /opt/pf/pf.ko; true'; pf0 '/opt/pf/pfctl -e' >/dev/null
|
||||
cat > /tmp/g2a.conf <<'C'
|
||||
block drop in quick proto icmp from any to any
|
||||
pass out quick
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
# M2 gate 3: pfr tables via pfctl -t
|
||||
set -uo pipefail; cd "$(dirname "$0")"; . ./lib.sh
|
||||
pfensure; pf0 '/opt/pf/pfctl -e' >/dev/null
|
||||
pf0 'insmod /tmp/pf.ko 2>/dev/null || insmod /opt/pf/pf.ko; true'; pf0 '/opt/pf/pfctl -e' >/dev/null
|
||||
cat > /tmp/g3.conf <<'C'
|
||||
table <blk> { 10.0.1.10 }
|
||||
block in quick proto icmp from <blk> to any
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
# M2 gate 4: anchors + quick precedence
|
||||
set -uo pipefail; cd "$(dirname "$0")"; . ./lib.sh
|
||||
pfensure; pf0 '/opt/pf/pfctl -e; /opt/pf/pfctl -f /dev/null' >/dev/null
|
||||
pf0 'insmod /tmp/pf.ko 2>/dev/null || insmod /opt/pf/pf.ko; true'; pf0 '/opt/pf/pfctl -e; /opt/pf/pfctl -f /dev/null' >/dev/null
|
||||
cat > /tmp/g4main.conf <<'C'
|
||||
anchor "restricted"
|
||||
block drop in quick proto icmp from any to any
|
||||
|
||||
Reference in new issue
Block a user