mirror of
https://github.com/lgcosta/pf-linux-port.git
synced 2026-11-12 22:48:03 -03:00
docs: record the stress findings and the 16-gate suite
caveats §13 documents the capacity ceiling the stress test found: the workqueue backlog grows with the offered rate and OOM-panics a 1 GB guest above ~1 Gbit/s. Gate count, suite status and the doc indexes move to 16/16 green.
This commit is contained in:
1 parent
928322edf7
commit
0a8ba69340
15 files changed
+191
-30
No files matched your search
+3
-2
@@ -13,8 +13,9 @@ packet filter** to **Linux 7.3.4** — the real PF engine (`pf.c`, `pf_norm.c`,
|
||||
|---|---|
|
||||
| [guide.md](guide.md) | One-page architecture: components, data path, control plane, milestones |
|
||||
| [install.md](install.md) | Apply/build/install recipe (kernel, module, tools) |
|
||||
| [debian13.md](debian13.md) | Full from-zero implementation on Debian 13 (trixie): host prep, kernel/module/tools build, libvirt lab, stress test |
|
||||
| [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 of the 15 gates proves |
|
||||
| [vm-lab.md](vm-lab.md) | The libvirt validation lab: topology, recreate/run, what each of the 16 gates proves |
|
||||
| [feature-matrix.md](feature-matrix.md) | Per-feature status: VERIFIED-in-VM / VERIFIED-offline / COMPILES-untested / NOT-ENABLED |
|
||||
| [pf-guide.md](pf-guide.md) | Daily use: what works today in `/etc/pf.conf`, and the deviations to know about |
|
||||
| [screencast.md](screencast.md) | The narrated videos (English and Portuguese): phases, pipeline, how to re-record |
|
||||
@@ -38,4 +39,4 @@ Feature-status vocabulary used across these docs:
|
||||
- **NOT-ENABLED** — the code exists but the feature is off in this build.
|
||||
- **OUT-OF-SCOPE** — non-goal by design (spec §3).
|
||||
|
||||
The lab suite is **15/15 green** (`./scripts/vm-run-tests.sh`).
|
||||
The lab suite is **16/16 green** (`./scripts/vm-run-tests.sh`).
|
||||
@@ -186,6 +186,45 @@ The videos are a demonstration, not a test run: the lab's own gate suite
|
||||
in `out/test-logs/`. The screencasts show the same commands, but a video frame
|
||||
is not a verdict.
|
||||
|
||||
## Capacity findings (2026-11-12, from `scripts/stress/run-stress.sh`)
|
||||
|
||||
### 13. The workqueue backlog grows with the offered rate: above ~1 Gbit/s the guest OOM-panics
|
||||
|
||||
This is the sharpest consequence of §1 and it is **reproducible**, not
|
||||
theoretical. PF steals every skb onto one ordered workqueue and runs the
|
||||
engine in process context, so the number of packets in flight is bounded only
|
||||
by the memory the backlog takes.
|
||||
|
||||
Measured on the lab (Alpine guest, **1 GB** RAM, 4 TCP streams through a
|
||||
stateful ruleset):
|
||||
|
||||
| Offered rate (per stream × 4) | Result |
|
||||
|---|---|
|
||||
| 50 Mbit/s (200 Mbit/s total) | stable |
|
||||
| 100 Mbit/s (400 Mbit/s total) | stable |
|
||||
| 250 Mbit/s (**1 Gbit/s** total) | **kernel panic: "System is deadlocked on memory"** |
|
||||
|
||||
The panic is an OOM with no killable process; the slab at that moment shows
|
||||
`kmalloc-8k` ≈ 50 MB, `skbuff_head_cache` ≈ 18 MB and `files_cache` ≈ 40 MB,
|
||||
i.e. the backlog is what ate the 1 GB. The guest reboots (120 s watchdog
|
||||
delay) and every connection through the firewall is lost with it.
|
||||
|
||||
Practical consequences:
|
||||
|
||||
- **Rate-limit what you point at PF**, and size the box for the backlog:
|
||||
memory needed grows with the offered rate, not with the number of
|
||||
concurrent flows.
|
||||
- The stress test (`tests/vm/16-stress.sh`) therefore runs its traffic phases
|
||||
at a **bounded** rate (default 100 Mbit/s per stream = 400 Mbit/s total) —
|
||||
the regime this port survives — and reports the ceiling separately instead
|
||||
of OOM-killing the firewall under test.
|
||||
- The functional gates (01-15) are unaffected: they move megabytes, not gigabits
|
||||
per second.
|
||||
|
||||
What would fix it is a real fast path (evaluate `pf_test()` inline in
|
||||
softirq, as OpenBSD does) or a bounded queue with a drop policy — both are
|
||||
design changes beyond this port's scope.
|
||||
|
||||
## What is *not* a caveat
|
||||
|
||||
ARP and other non-IPv4/IPv6 EtherTypes never enter the netfilter IPv4/IPv6
|
||||
|
||||
@@ -14,7 +14,7 @@ As of 2026-11-12; source of per-row evidence: `out/test-logs/`,
|
||||
`docs/porting-notes.md`, milestone commits, and the screencasts
|
||||
([screencast.md](screencast.md)) for the visible proof.
|
||||
|
||||
The suite is **15/15 green** (`./scripts/vm-run-tests.sh`).
|
||||
The suite is **16/16 green** (`./scripts/vm-run-tests.sh`).
|
||||
|
||||
## Control plane & tools
|
||||
|
||||
|
||||
+2
-2
@@ -25,7 +25,7 @@ between kernel and userland (ioctl encoding per `module/include/pfk.h`).
|
||||
| `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, `screencast/` (video pipeline) |
|
||||
| `tests/vm/` | VM suite: 15 gates `01-state.sh … 15-queue.sh` + `lib.sh`; `tests/confs/` rulesets deployed to the lab |
|
||||
| `tests/vm/` | VM suite: 16 gates `01-state.sh … 16-stress.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)
|
||||
@@ -100,7 +100,7 @@ rebuilt.
|
||||
| M3 | NAT (`nat-to`/`rdr-to`/`binat-to`), scrub, pflog | **done** — gates 05/06/07/09 (incl. 2 MB integrity through a redirect) and pflog gate 12 |
|
||||
| M4 | route-to/dup-to/reply-to, pools, divert e2e | **done** — gates 08/10/13 and divert gate 11 |
|
||||
| M5 | queues: ifq back ends (hfsc/fq_codel) | **done (mechanical)** — gate 15: hfsc attaches, `pfctl -s queue -v` reports class stats, a 2 MB queued flow is counted by the class. `bandwidth` = HFSC linkshare (a guarantee under contention, not a cap) |
|
||||
| M6 | docs complete, patchset tidy, full suite green, tagged releases | **done** — docs EN+PT-BR, 15/15 suite green, tagged (`v1.0.0`) |
|
||||
| M6 | docs complete, patchset tidy, full suite green, tagged releases | **done** — docs EN+PT-BR, 16/16 suite green, tagged (`v1.0.0`) |
|
||||
|
||||
## Next reads
|
||||
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@ This is the "what can I actually run today" guide. It follows the
|
||||
[feature matrix](feature-matrix.md): the filtering core (pass/block/quick/
|
||||
keep state/tables/anchors), the NAT family, scrub, forced routing, pools,
|
||||
`divert`, `pflog` and the OpenBSD 8.0 queues all have **VM evidence** behind
|
||||
them — 15/15 lab gates are green. What is *not* gate-verified is listed
|
||||
them — 16/16 lab gates are green. What is *not* gate-verified is listed
|
||||
explicitly below. Install/build is covered by [install.md](install.md) and
|
||||
[build.md](build.md).
|
||||
|
||||
|
||||
+14
-5
@@ -97,7 +97,7 @@ code: the packet either arrives, is dropped, or is translated, and the proof
|
||||
is read back from the firewall's own counters, state table, capture files or
|
||||
peer.
|
||||
|
||||
The full list is in §6 (15 gates, all green). Each gate is a shell script in
|
||||
The full list is in §6 (16 gates, all green). Each gate is a shell script in
|
||||
`tests/vm/` that talks to the VMs through deb with nested ssh, using the
|
||||
helpers in `tests/vm/lib.sh` (`pf0`, `cli`, `srv`, `gw2`, `pfput`, `pfload`,
|
||||
`ok`, `bad`). `scripts/vm-run-tests.sh` runs them in order and captures
|
||||
@@ -108,14 +108,22 @@ Not gated (honest gaps): the IPv6 path, OS fingerprinting, `set` options
|
||||
`pf.ko` but no gate drives it. FQ-CoDel compiles and shares its stats header
|
||||
with `pfctl`, but only the **hfsc** discipline is exercised.
|
||||
|
||||
There is **no numeric performance gate**: `bench-basic.sh` does not exist in
|
||||
the tree and no throughput target is claimed.
|
||||
There is **no numeric performance gate**: the design decision (spec §7) is
|
||||
that *functional gates are the bar* — a perf contract would force a
|
||||
non-faithful rewrite-first shim. What exists instead is a **stress test**
|
||||
(`tests/vm/16-stress.sh`, driven by `scripts/stress/run-stress.sh`) that
|
||||
asserts *invariants under load* — no state/table leak, no rule-load failure,
|
||||
byte-identical payload under a saturating stream, no `BUG/WARNING/Oops`, the
|
||||
firewall still routing — and **reports** the throughput comparison
|
||||
(baseline without the module vs. with PF enabled) as information, without a
|
||||
numeric pass/fail target. Report and raw log land in
|
||||
`out/stress/<timestamp>/`.
|
||||
|
||||
## 6. Current suite status (2026-11-12)
|
||||
|
||||
**15/15 gates green.** A full clean run (`./scripts/vm-run-tests.sh`, ~9 min)
|
||||
**16/16 gates green.** A full clean run (`./scripts/vm-run-tests.sh`, ~13 min)
|
||||
recreates and reboots `pftest-pf0` before every gate, redeploys the module
|
||||
and the tools, and ends with `PASS=15 FAIL=0`. The captured logs live in
|
||||
and the tools, and ends with `PASS=16 FAIL=0`. The captured logs live in
|
||||
`out/test-logs/<gate>.sh.log`.
|
||||
|
||||
| Gate | What it proves now |
|
||||
@@ -135,6 +143,7 @@ and the tools, and ends with `PASS=15 FAIL=0`. The captured logs live in
|
||||
| `13-replyto.sh` | `reply-to` forces the reply onto the gateway link |
|
||||
| `14-soak.sh` | ruleset reload under traffic, `rmmod` cycle, leak check |
|
||||
| `15-queue.sh` | hfsc queues: attach, class statistics, a 2 MB queued flow counted by the class |
|
||||
| `16-stress.sh` | stress: throughput baseline vs. PF, state/rule/table churn with leak checks, 2 MB through NAT under a saturating stream, dmesg health (run by `scripts/stress/run-stress.sh`, which writes a report) |
|
||||
|
||||
The screencasts ([screencast.md](screencast.md)) show most of these gates
|
||||
being run, with the lab's network diagram drawn over the terminal.
|
||||
|
||||
@@ -14,8 +14,9 @@ de `/dev/pf`.
|
||||
|---|---|
|
||||
| [guide.md](guide.md) | Arquitetura em uma página: componentes, caminho de dados, plano de controle, milestones |
|
||||
| [install.md](install.md) | Receita de apply/build/install (kernel, módulo, ferramentas) |
|
||||
| [debian13.md](debian13.md) | Implementação completa do zero no Debian 13 (trixie): preparar host, compilar kernel/módulo/ferramentas, lab libvirt, teste de stress |
|
||||
| [build.md](build.md) | Pipeline de build em detalhe: fetch → vendorize → kernel → módulo → pfctl |
|
||||
| [vm-lab.md](vm-lab.md) | O lab de validação em libvirt: topologia, recriar/rodar, o que cada um dos 15 gates prova |
|
||||
| [vm-lab.md](vm-lab.md) | O lab de validação em libvirt: topologia, recriar/rodar, o que cada um dos 16 gates prova |
|
||||
| [feature-matrix.md](feature-matrix.md) | Status por feature: VERIFIED-in-VM / VERIFIED-offline / COMPILES-untested / NOT-ENABLED |
|
||||
| [pf-guide.md](pf-guide.md) | Uso diário: o que funciona hoje em `/etc/pf.conf` e as divergências a conhecer |
|
||||
| [screencast.md](screencast.md) | Os vídeos narrados (inglês e português): fases, pipeline, como regravar |
|
||||
@@ -39,4 +40,4 @@ Vocabulário de status usado nestes docs:
|
||||
- **NOT-ENABLED** — o código existe mas a feature está desligada neste build.
|
||||
- **OUT-OF-SCOPE** — fora de escopo por desenho (spec §3).
|
||||
|
||||
A suíte do lab está **15/15 verde** (`./scripts/vm-run-tests.sh`).
|
||||
A suíte do lab está **16/16 verde** (`./scripts/vm-run-tests.sh`).
|
||||
@@ -191,6 +191,45 @@ lab (`scripts/vm-run-tests.sh`) é a autoridade para PASS/FAIL, e os logs
|
||||
dela ficam em `out/test-logs/`. Os screencasts mostram os mesmos comandos,
|
||||
mas um quadro de vídeo não é um veredito.
|
||||
|
||||
## Achados de capacidade (2026-11-12, a partir de `scripts/stress/run-stress.sh`)
|
||||
|
||||
### 13. O backlog da workqueue cresce com a taxa oferecida: acima de ~1 Gbit/s a VM dá OOM-panic
|
||||
|
||||
Esta é a consequência mais dura da §1 e ela é **reproduzível**, não teórica.
|
||||
O PF rouba cada skb para uma única workqueue ordenada e roda o engine em
|
||||
contexto de processo, então o número de pacotes em voo só é limitado pela
|
||||
memória que o backlog consome.
|
||||
|
||||
Medido no lab (guest Alpine, **1 GB** de RAM, 4 fluxos TCP por um ruleset
|
||||
stateful):
|
||||
|
||||
| Taxa oferecida (por fluxo × 4) | Resultado |
|
||||
|---|---|
|
||||
| 50 Mbit/s (200 Mbit/s no total) | estável |
|
||||
| 100 Mbit/s (400 Mbit/s no total) | estável |
|
||||
| 250 Mbit/s (**1 Gbit/s** no total) | **kernel panic: "System is deadlocked on memory"** |
|
||||
|
||||
O panic é um OOM sem processo matável; o slab naquele momento mostra
|
||||
`kmalloc-8k` ≈ 50 MB, `skbuff_head_cache` ≈ 18 MB e `files_cache` ≈ 40 MB —
|
||||
ou seja, o backlog comeu o 1 GB. A VM reinicia (watchdog de 120 s) e todas as
|
||||
conexões que passavam pelo firewall caem junto.
|
||||
|
||||
Consequências práticas:
|
||||
|
||||
- **Limite a taxa que você aponta para o PF** e dimensione a máquina para o
|
||||
backlog: a memória necessária cresce com a taxa oferecida, não com o
|
||||
número de fluxos concorrentes.
|
||||
- Por isso o teste de stress (`tests/vm/16-stress.sh`) roda as fases de
|
||||
tráfego a uma taxa **limitada** (padrão: 100 Mbit/s por fluxo = 400 Mbit/s
|
||||
no total) — o regime que este port suporta — e reporta o teto
|
||||
separadamente, em vez de dar OOM no firewall sob teste.
|
||||
- Os gates funcionais (01-15) não são afetados: eles movem megabytes, não gigabits
|
||||
por segundo.
|
||||
|
||||
O que resolveria isso é um fast path de verdade (avaliar `pf_test()` inline em
|
||||
softirq, como o OpenBSD faz) ou uma fila limitada com política de drop — as
|
||||
duas são mudanças de design fora do escopo deste port.
|
||||
|
||||
## O que *não* é ressalva
|
||||
|
||||
ARP e outros EtherTypes não-IPv4/IPv6 nunca entram nos hooks netfilter de
|
||||
|
||||
@@ -14,7 +14,7 @@ Atualizado em 2026-11-12; fonte da evidência por linha: `out/test-logs/`,
|
||||
`docs/porting-notes.md`, commits dos milestones e os screencasts
|
||||
([screencast.md](screencast.md)) para a prova visível.
|
||||
|
||||
A suíte está **15/15 verde** (`./scripts/vm-run-tests.sh`).
|
||||
A suíte está **16/16 verde** (`./scripts/vm-run-tests.sh`).
|
||||
|
||||
## Plano de controle e ferramentas
|
||||
|
||||
|
||||
+1
-1
@@ -103,7 +103,7 @@ tamanho embutido na codificação `_IOR/_IOW` OpenBSD e despacha para o
|
||||
| M3 | NAT (`nat-to`/`rdr-to`/`binat-to`), scrub, pflog | **pronto** — gates 05/06/07/09 (incl. integridade de 2 MB via redirect) e pflog gate 12 |
|
||||
| M4 | route-to/dup-to/reply-to, pools, divert e2e | **pronto** — gates 08/10/13 e divert gate 11 |
|
||||
| M5 | filas: back ends ifq (hfsc/fq_codel) | **concluído (mecânico)** — gate 15: o hfsc é anexado, `pfctl -s queue -v` mostra as stats das classes, um fluxo de 2 MB pela fila é contado pela classe. `bandwidth` = linkshare do HFSC (garantia sob contenção, não teto) |
|
||||
| M6 | docs completas, patchset limpo, suíte verde, releases com tag | **concluído** — docs EN+PT-BR, suíte 15/15 verde, tag `v1.0.0` |
|
||||
| M6 | docs completas, patchset limpo, suíte verde, releases com tag | **concluído** — docs EN+PT-BR, suíte 16/16 verde, tag `v1.0.0` |
|
||||
|
||||
## Próximas leituras
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ Este é o guia do "o que dá para rodar hoje". Ele segue a
|
||||
[matriz de features](feature-matrix.md): o núcleo de filtragem
|
||||
(pass/block/quick/keep state/tabelas/anchors), a família de NAT, o scrub, o
|
||||
roteamento forçado, os pools, o `divert`, o `pflog` e as filas do OpenBSD
|
||||
8.0 têm todos **evidência em VM** — os 15 gates do lab estão verdes. O que
|
||||
8.0 têm todos **evidência em VM** — os 16 gates do lab estão verdes. O que
|
||||
*não* tem gate está listado explicitamente abaixo. Instalação/build ficam em
|
||||
[install.md](install.md) e [build.md](build.md).
|
||||
|
||||
|
||||
+15
-6
@@ -102,7 +102,7 @@ de saída: o pacote chega, é descartado ou é traduzido, e a prova é lida de
|
||||
volta nos contadores do próprio firewall, na tabela de estados, em capturas de
|
||||
pacote ou no par.
|
||||
|
||||
A lista completa está na §6 (15 gates, todos verdes). Cada gate é um script
|
||||
A lista completa está na §6 (16 gates, todos verdes). Cada gate é um script
|
||||
shell em `tests/vm/` que fala com as VMs através do deb com ssh aninhado,
|
||||
usando os helpers de `tests/vm/lib.sh` (`pf0`, `cli`, `srv`, `gw2`, `pfput`,
|
||||
`pfload`, `ok`, `bad`). O `scripts/vm-run-tests.sh` roda tudo em ordem e
|
||||
@@ -114,15 +114,23 @@ compila dentro do `pf.ko`, mas nenhum gate os exercita. O FQ-CoDel compila e
|
||||
compartilha o header de estatísticas com o `pfctl`, mas só a disciplina
|
||||
**hfsc** é exercitada.
|
||||
|
||||
**Não existe gate numérico de performance**: o `bench-basic.sh` não está na
|
||||
árvore e nenhuma meta de vazão é reivindicada.
|
||||
**Não existe gate numérico de performance**: a decisão de design (spec §7) é
|
||||
que *os gates funcionais são a régua* — um contrato de performance forçaria um
|
||||
shim reescrito, pouco fiel. O que existe é um **teste de stress**
|
||||
(`tests/vm/16-stress.sh`, disparado por `scripts/stress/run-stress.sh`) que
|
||||
afere **invariantes sob carga** — sem vazamento de estados/tabelas, sem falha
|
||||
de carga de regras, payload idêntico sob um stream saturando o link, sem
|
||||
`BUG/WARNING/Oops`, o firewall ainda roteando — e **reporta** a comparação de
|
||||
vazão (baseline sem o módulo vs. com PF ligado) como informação, sem meta
|
||||
numérica de pass/fail. O relatório e o log bruto ficam em
|
||||
`out/stress/<timestamp>/`.
|
||||
|
||||
## 6. Status atual da suíte (2026-11-12)
|
||||
|
||||
**15/15 gates verdes.** Uma rodada completa e limpa
|
||||
(`./scripts/vm-run-tests.sh`, ~9 min) recria e reinicia o `pftest-pf0` antes
|
||||
**16/16 gates verdes.** Uma rodada completa e limpa
|
||||
(`./scripts/vm-run-tests.sh`, ~13 min) recria e reinicia o `pftest-pf0` antes
|
||||
de cada gate, reimplanta o módulo e as ferramentas, e termina com
|
||||
`PASS=15 FAIL=0`. Os logs capturados ficam em
|
||||
`PASS=16 FAIL=0`. Os logs capturados ficam em
|
||||
`out/test-logs/<gate>.sh.log`.
|
||||
|
||||
| Gate | O que prova hoje |
|
||||
@@ -142,6 +150,7 @@ de cada gate, reimplanta o módulo e as ferramentas, e termina com
|
||||
| `13-replyto.sh` | `reply-to` força a resposta para o link do gateway |
|
||||
| `14-soak.sh` | reload de ruleset sob tráfego, ciclo de `rmmod`, checagem de vazamento |
|
||||
| `15-queue.sh` | filas hfsc: anexação, estatísticas da classe, um fluxo de 2 MB contado pela classe |
|
||||
| `16-stress.sh` | stress: vazão baseline vs. PF, churn de estados/regras/tabelas com verificação de vazamento, 2 MB por NAT sob stream saturante, saúde do dmesg (rodado por `scripts/stress/run-stress.sh`, que grava um relatório) |
|
||||
|
||||
Os screencasts ([screencast.md](screencast.md)) mostram boa parte desses
|
||||
gates sendo executados, com o diagrama de rede do lab desenhado sobre o
|
||||
|
||||
Reference in new issue
Block a user