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:
lgcosta committed 2026-11-11 18:47:04 -03:00
1 parent fe6f767561
commit ac56534c3b
30 files changed
+2273 -62

No files matched your search

+36
View File
@@ -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.
+120
View File
@@ -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.
+141
View File
@@ -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`).
+78
View File
@@ -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 |
+103
View File
@@ -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).
+105
View File
@@ -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) |
+75
View File
@@ -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. |
+98
View File
@@ -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).
+122
View File
@@ -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).