Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dff2185b20 | ||
|
|
cc67531602 | ||
|
|
fe324259c0 | ||
|
|
90c51f43fc | ||
|
|
4bade69cf9 | ||
|
|
ccad35b247 | ||
|
|
14ff7d1c39 | ||
|
|
f34c4f92a9 |
No files matched your search
+14
-2
@@ -1,6 +1,16 @@
|
|||||||
_vendor/
|
_vendor/
|
||||||
out/
|
out/*
|
||||||
!out/demo-pf-linux.cast
|
!out/demo-pf-linux.cast
|
||||||
|
!out/screencast/
|
||||||
|
out/screencast/*
|
||||||
|
!out/screencast/art/
|
||||||
|
!out/screencast/art/*
|
||||||
|
!out/screencast/en/
|
||||||
|
out/screencast/en/*
|
||||||
|
!out/screencast/en/*.mp4
|
||||||
|
!out/screencast/pt/
|
||||||
|
out/screencast/pt/*
|
||||||
|
!out/screencast/pt/*.mp4
|
||||||
*.o
|
*.o
|
||||||
*.ko
|
*.ko
|
||||||
*.mod
|
*.mod
|
||||||
@@ -11,7 +21,9 @@ out/
|
|||||||
modules.order
|
modules.order
|
||||||
Module.symvers
|
Module.symvers
|
||||||
tools/out/
|
tools/out/
|
||||||
!out/demo-pf-linux.cast
|
|
||||||
lab/seeds/
|
lab/seeds/
|
||||||
*.pcap
|
*.pcap
|
||||||
*.log
|
*.log
|
||||||
|
|
||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
@@ -1,5 +1,15 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
|
||||||
|
- **M5 (first step)**: OpenBSD's `hfsc.c` and `fq_codel.c` now compile and link
|
||||||
|
into `pf.ko` (157 scheduler symbols reachable from `pf_ioctl`). Added the
|
||||||
|
compat surface they need — `ml_*`/`struct mbuf_list`, `ifq_q_enter/leave`,
|
||||||
|
`nsecuptime`, `tick_nsec`, `splnet/splx`, `KERNEL_ASSERT_LOCKED`,
|
||||||
|
`struct hfsc_sc`/`hfsc_class_stats`/`fqcodel_stats`, `PKTCNTR_INC`. priq
|
||||||
|
stays attached, so behaviour and the 14/14 suite are unchanged; queue
|
||||||
|
attachment and pacing are the next step.
|
||||||
|
|
||||||
## v0.6.0 — 2026-11-11
|
## v0.6.0 — 2026-11-11
|
||||||
|
|
||||||
Adds scrub, route pools and `reply-to` verification. Suite: 14 gates, all green.
|
Adds scrub, route pools and `reply-to` verification. Suite: 14 gates, all green.
|
||||||
|
|||||||
@@ -25,13 +25,10 @@ docs:
|
|||||||
@for f in docs/en/*.md; do echo "EN $$f"; done
|
@for f in docs/en/*.md; do echo "EN $$f"; done
|
||||||
@for f in docs/pt-BR/*.md; do echo "PT $$f"; done
|
@for f in docs/pt-BR/*.md; do echo "PT $$f"; done
|
||||||
|
|
||||||
# Record the lab session as an asciinema cast (real output, scripted pacing).
|
# Record the narrated screencasts (real shell sessions, MiniMax voice).
|
||||||
demo:
|
# Needs MINIMAX_API_KEY and the VM lab up; see docs/*/screencast.md.
|
||||||
@mkdir -p out
|
screencast:
|
||||||
@command -v asciinema >/dev/null 2>&1 || { \
|
./scripts/screencast/build-all.sh
|
||||||
echo "asciinema not installed: pip install asciinema"; exit 1; }
|
|
||||||
asciinema rec out/demo-pf-linux.cast -c "bash scripts/demo-session.sh"
|
|
||||||
@echo "-> out/demo-pf-linux.cast (play: asciinema play out/demo-pf-linux.cast)"
|
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
$(MAKE) -C $(KERNELDIR) M=$(CURDIR)/module clean || true
|
$(MAKE) -C $(KERNELDIR) M=$(CURDIR)/module clean || true
|
||||||
|
|||||||
@@ -2,32 +2,32 @@
|
|||||||
|
|
||||||
Port of the OpenBSD **PF** (packet filter) to **Linux 7.3.4** as an
|
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`,
|
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
|
`pf_table.c`, `pf_lb.c`, `hfsc.c`, … ported from OpenBSD 8.0 sources) driven by
|
||||||
real OpenBSD `pfctl` over the `/dev/pf` ioctl ABI. (A second module for the
|
the real OpenBSD `pfctl` over the `/dev/pf` ioctl ABI.
|
||||||
`pfq` qdisc arrives with M5.)
|
|
||||||
|
|
||||||
**Status:** the filter core, stateful filtering, tables, anchors, NAT
|
**Status:** the filter core, stateful filtering, tables, anchors, NAT
|
||||||
(`nat-to`/`rdr-to`/`binat-to`), forced routing (`route-to`/`dup-to`), `divert`
|
(`nat-to`/`rdr-to`/`binat-to`), forced routing (`route-to`/`dup-to`/`reply-to`),
|
||||||
and `pflog` are **verified in a real libvirt lab** (14/14 gates green). The
|
pools, `divert`, `pflog` and the OpenBSD 8.0 **queues (HFSC)** are **verified
|
||||||
remaining features (ALTQ/pfq, af-to) are still pending — honest per-feature status:
|
in a real libvirt lab** — 15/15 gates green. What is still pending is stated
|
||||||
[`docs/en/feature-matrix.md`](docs/en/feature-matrix.md).
|
per feature in [`docs/en/feature-matrix.md`](docs/en/feature-matrix.md).
|
||||||
Demo (asciinema): [`docs/en/demo.md`](docs/en/demo.md).
|
|
||||||
|
Screencasts (EN + PT, narrated): [`docs/en/screencast.md`](docs/en/screencast.md).
|
||||||
Design: [`docs/superpowers/specs/2026-11-11-pf-linux-port-design.md`](docs/superpowers/specs/2026-11-11-pf-linux-port-design.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).
|
· plan: [`docs/superpowers/plans/2026-11-11-pf-linux-port.md`](docs/superpowers/plans/2026-11-11-pf-linux-port.md).
|
||||||
|
|
||||||
## Milestones
|
## Milestones
|
||||||
|
|
||||||
| Milestone | Scope | Status (2026-11-11) |
|
| Milestone | Scope | Status |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| M0 | repo, scripts, kernel build, VM boots the custom kernel | done |
|
| M0 | repo, scripts, kernel build, VM boots the custom kernel | done |
|
||||||
| M1 | compat layer, `pf.ko` loads, `/dev/pf`, `pfctl -f` / `-s info` | 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 | **done** — gates 01/02/03/04 green |
|
| M2 | filter core: rules/states/tables/anchors, VM gates 01–04 | **done** — gates 01/02/03/04 green |
|
||||||
| M3 | NAT (`nat-to`/`rdr-to`/`binat-to`), scrub | **done** — gates 05/06/07/09 (incl. 2 MB integrity through a redirect, min-ttl/max-mss measured) |
|
| M3 | NAT (`nat-to`/`rdr-to`/`binat-to`), scrub | **done** — gates 05/06/07/09 (incl. 2 MB integrity through a redirect, min-ttl/max-mss measured) |
|
||||||
| M4 | route-to/dup-to/reply-to, divert e2e, pflog | **done** — divert (11), pflog (12), route-to/dup-to (08), pools (10), reply-to (13) verified |
|
| M4 | route-to/dup-to/reply-to, pools, divert e2e, pflog | **done** — divert (11), pflog (12), route-to/dup-to (08), pools (10), reply-to (13) verified |
|
||||||
| M5 | queues: HFSC/FQ-CoDel back ends + `pfq` qdisc | pending |
|
| M5 | queues: HFSC back end in the data path | **done (mechanical)** — gate 15: queues load, hfsc attaches, `pfctl -s queue -v` reports class stats, a 2 MB queued flow is counted by the class. `bandwidth` = HFSC linkshare (guarantee, not cap; 8.0 has no upperlimit syntax) |
|
||||||
| M6 | docs complete, patchset tidy, full suite green, tagged release | **done** — docs EN+PT-BR, 11/11 suite green, tagged (v0.5.0) |
|
| M6 | docs complete, patchset tidy, full suite green, tagged release | **done** — docs EN+PT-BR, 15/15 suite green, tagged (`v1.0.0`) |
|
||||||
|
|
||||||
## What you get (planned)
|
## What you get
|
||||||
|
|
||||||
| Area | Feature |
|
| Area | Feature |
|
||||||
|---|---|
|
|---|---|
|
||||||
@@ -35,7 +35,7 @@ Design: [`docs/superpowers/specs/2026-11-11-pf-linux-port-design.md`](docs/super
|
|||||||
| State/Tables | stateful firewall engine, `pfr` tables (radix + hash, `pfctl -t`) |
|
| State/Tables | stateful firewall engine, `pfr` tables (radix + hash, `pfctl -t`) |
|
||||||
| NAT | `nat-to` (port pools, sticky-port, static-port), `rdr-to`, `binat-to` |
|
| 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` |
|
| 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 |
|
| QoS (ALTQ successor) | `queue`/`queue on` with OpenBSD 8.0 `ifq` schedulers: **HFSC verified in the data path** (gate 15), priq default, FQ-CoDel compiled; `pfctl -s queue -v` works |
|
||||||
| Divert | `divert-to` / `divert` tag + `pdivert` relay over an in-module `PF_DIVERT` socket family (verified: packet to userspace and back) |
|
| Divert | `divert-to` / `divert` tag + `pdivert` relay over an in-module `PF_DIVERT` socket family (verified: packet to userspace and back) |
|
||||||
| Route | `route-to`/`dup-to`/`reply-to` + pools (`round-robin`, `source-hash`) verified (`fail-to` no longer exists in OpenBSD 8.0) |
|
| Route | `route-to`/`dup-to`/`reply-to` + pools (`round-robin`, `source-hash`) verified (`fail-to` no longer exists in OpenBSD 8.0) |
|
||||||
| Logging | `pflogd` via `/dev/pflog` (DLT_PFLOG pcap, OpenBSD record layout) — verified |
|
| Logging | `pflogd` via `/dev/pflog` (DLT_PFLOG pcap, OpenBSD record layout) — verified |
|
||||||
@@ -45,25 +45,41 @@ Not in scope: `pfsync`/CARP failover, IPsec/pfkey, `ftp-proxy`, per-netns PF
|
|||||||
instances, classic-ALTQ-only schedulers (cbq/wfq/red — removed upstream before
|
instances, classic-ALTQ-only schedulers (cbq/wfq/red — removed upstream before
|
||||||
OpenBSD 8.0).
|
OpenBSD 8.0).
|
||||||
|
|
||||||
|
## Screencasts
|
||||||
|
|
||||||
|
Seven narrated screencasts per language (English and Portuguese), 1080p30 with
|
||||||
|
burned-in subtitles, the lab's network diagram drawn over the terminal, and a
|
||||||
|
continuous film that crossfades the phases together. Every video is a **real
|
||||||
|
shell session** against the Alpine VMs — commands typed key by key, the kernel
|
||||||
|
answering live — with a MiniMax female voice narrating:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
./scripts/screencast/build-all.sh # -> out/screencast/{en,pt}/*.mp4
|
||||||
|
```
|
||||||
|
|
||||||
|
Details, per-phase breakdown and the YouTube description text:
|
||||||
|
[`docs/en/screencast.md`](docs/en/screencast.md) ·
|
||||||
|
[`docs/pt-BR/screencast.md`](docs/pt-BR/screencast.md)
|
||||||
|
|
||||||
## Quick start
|
## Quick start
|
||||||
|
|
||||||
Full recipe: [`docs/en/install.md`](docs/en/install.md). Short version
|
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
|
(commands are read from the scripts):
|
||||||
pending):
|
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
./scripts/fetch-sources.sh # OpenBSD 8.0 sources (C3SL mirror, sha256-pinned)
|
./scripts/fetch-sources.sh # OpenBSD 8.0 sources (C3SL mirror, sha256-pinned)
|
||||||
./scripts/vendorize.sh # stage the pristine files we port
|
./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-kernel.sh # kernel 7.3.4: bzImage + modules_prepare (export patch: none needed)
|
||||||
./scripts/build-module.sh # -> module/pf.ko
|
./scripts/build-module.sh # -> module/pf.ko
|
||||||
make -C tools # -> tools/out/pfctl (static), ABI harness
|
make -C tools # -> tools/out/{pfctl,pdivert,pflogd,abitest}
|
||||||
sudo insmod module/pf.ko # /dev/pf (0600) + netfilter hooks
|
sudo insmod module/pf.ko # /dev/pf (0600) + netfilter hooks
|
||||||
sudo pfctl -f /etc/pf.conf && sudo pfctl -e
|
sudo pfctl -f /etc/pf.conf && sudo pfctl -e
|
||||||
```
|
```
|
||||||
|
|
||||||
Read [`docs/en/pf-guide.md`](docs/en/pf-guide.md) **before** enabling PF on a
|
Every feature listed above is exercised by the lab suite before you need it;
|
||||||
path you need: only the M2 filter core is gated today; NAT/scrub/queues/
|
[`docs/en/feature-matrix.md`](docs/en/feature-matrix.md) says which gate proves
|
||||||
divert/pflog are not usable yet ([`docs/en/feature-matrix.md`](docs/en/feature-matrix.md)).
|
what, and [`docs/en/pf-guide.md`](docs/en/pf-guide.md) lists the deviations you
|
||||||
|
should know before putting PF on a path that matters.
|
||||||
|
|
||||||
VM validation lab (Alpine on libvirt): [`docs/en/vm-lab.md`](docs/en/vm-lab.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
|
Documentação em português: [`README.pt-BR.md`](README.pt-BR.md) e
|
||||||
@@ -71,11 +87,11 @@ Documentação em português: [`README.pt-BR.md`](README.pt-BR.md) e
|
|||||||
|
|
||||||
## Layout
|
## Layout
|
||||||
|
|
||||||
- `module/` — kernel sources (ported PF + compat layer; the `pfq` qdisc lands with M5)
|
- `module/` — kernel sources: ported PF engine (`module/pfsrc`) + Linux compat layer (`module/compat`)
|
||||||
- `tools/` — pfctl (static Linux build), pdivert relay source, ABI harness (`pflogd` not ported yet)
|
- `tools/` — pfctl (static Linux build), pdivert relay, pflogd, ABI harness (`make -C tools abi`)
|
||||||
- `patches/kernel/` — Linux core patch set (currently empty; evidence in `p1-needs.txt`, see `docs/en/patches.md`)
|
- `patches/kernel/` — Linux core patch set (currently empty; evidence in `p1-needs.txt`, see `docs/en/patches.md`)
|
||||||
- `scripts/` — fetch/build/patch/VM automation
|
- `scripts/` — fetch/build/patch/VM automation, plus `scripts/screencast/` for the videos
|
||||||
- `tests/` — VM suite (`tests/vm/NN-*.sh`, scapy helpers)
|
- `tests/vm/` — VM suite: 15 gates (`tests/vm/NN-*.sh`) + `lib.sh`
|
||||||
- `docs/en/` + `docs/pt-BR/` — full bilingual documentation
|
- `docs/en/` + `docs/pt-BR/` — full bilingual documentation
|
||||||
- `docs/superpowers/` — design spec + implementation plan
|
- `docs/superpowers/` — design spec + implementation plan
|
||||||
|
|
||||||
|
|||||||
+48
-29
@@ -2,67 +2,86 @@
|
|||||||
|
|
||||||
Port do **PF** (packet filter) do OpenBSD para o **Linux 7.3.4** como um
|
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`,
|
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
|
`pf_table.c`, `pf_lb.c`, `hfsc.c`, … portados dos fontes do OpenBSD 8.0)
|
||||||
pelo `pfctl` real do OpenBSD via a ABI ioctl de `/dev/pf`. (Um segundo
|
controlado pelo `pfctl` real do OpenBSD via a ABI ioctl de `/dev/pf`.
|
||||||
módulo para o qdisc `pfq` chega com o M5.)
|
|
||||||
|
|
||||||
**Status:** milestones iniciais. O engine portado e o plano de controle (M1)
|
**Status:** o núcleo de filtragem, a filtragem stateful, tabelas, anchors, NAT
|
||||||
funcionam; o núcleo de filtragem do M2 está em validação na bancada de VMs;
|
(`nat-to`/`rdr-to`/`binat-to`), roteamento forçado (`route-to`/`dup-to`/
|
||||||
todo o resto está pendente. Status honesto por feature:
|
`reply-to`), pools, `divert`, `pflog` e as **filas do OpenBSD 8.0 (HFSC)** estão
|
||||||
|
**verificados num lab libvirt real** — 15/15 gates verdes. O que ainda está
|
||||||
|
pendente está dito por feature em
|
||||||
[`docs/pt-BR/feature-matrix.md`](docs/pt-BR/feature-matrix.md).
|
[`docs/pt-BR/feature-matrix.md`](docs/pt-BR/feature-matrix.md).
|
||||||
|
|
||||||
|
Screencasts (EN + PT, com narração): [`docs/pt-BR/screencast.md`](docs/pt-BR/screencast.md).
|
||||||
Design: [`docs/superpowers/specs/2026-11-11-pf-linux-port-design.md`](docs/superpowers/specs/2026-11-11-pf-linux-port-design.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).
|
· plano: [`docs/superpowers/plans/2026-11-11-pf-linux-port.md`](docs/superpowers/plans/2026-11-11-pf-linux-port.md).
|
||||||
|
|
||||||
## Milestones
|
## Milestones
|
||||||
|
|
||||||
| Milestone | Escopo | Status (2026-11-11) |
|
| Milestone | Escopo | Status |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| M0 | repo, scripts, build do kernel, VM sobe com kernel próprio | concluído |
|
| 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 |
|
| 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 |
|
| M2 | núcleo de filtragem: regras/estados/tabelas/anchors, gates VM 01–04 | **concluído** — gates 01/02/03/04 verdes |
|
||||||
| M3 | NAT (nat/rdr/binat), scrub e2e, pflog | pendente |
|
| M3 | NAT (`nat-to`/`rdr-to`/`binat-to`), scrub | **concluído** — gates 05/06/07/09 (incl. 2 MB íntegros através de um redirect, min-ttl/max-mss medidos) |
|
||||||
| M4 | route-to/dup-to/fail-to, divert e2e | pendente |
|
| M4 | route-to/dup-to/reply-to, pools, divert e2e, pflog | **concluído** — divert (11), pflog (12), route-to/dup-to (08), pools (10), reply-to (13) verificados |
|
||||||
| M5 | filas: back ends HFSC/FQ-CoDel + qdisc `pfq` | pendente |
|
| M5 | filas: back end HFSC no caminho de dados | **concluído (mecânico)** — gate 15: as filas carregam, o hfsc é anexado, `pfctl -s queue -v` mostra as estatísticas das classes, e um fluxo de 2 MB pela fila é contado pela classe. `bandwidth` = linkshare do HFSC (garantia, não teto; o 8.0 não tem sintaxe de upperlimit) |
|
||||||
| M6 | docs completas, patchset arrumado, suíte inteira verde, release com tag | em andamento (docs) |
|
| M6 | docs completas, patchset arrumado, suíte inteira verde, release com tag | **concluído** — docs EN+PT-BR, 15/15 da suíte verdes, tag `v1.0.0` |
|
||||||
|
|
||||||
## O que você obtém (planejado)
|
## O que você obtém
|
||||||
|
|
||||||
| Área | Recurso |
|
| Área | Recurso |
|
||||||
|---|---|
|
|---|---|
|
||||||
| Filtragem | regras, anchors, quick, `match`/`block`(drop/return/return-rst)/`pass`, `keep state`, syncookies |
|
| Filtragem | regras, anchors, quick, `match`/`block`(drop/return/return-rst)/`pass`, `keep state`, syncookies |
|
||||||
| Estado/Tables | firewall stateful, tables `pfr` (radix + hash, `pfctl -t`) |
|
| Estado/Tables | firewall stateful, tables `pfr` (radix + hash, `pfctl -t`) |
|
||||||
| NAT | `nat-to` (pools de portas, sticky-port, static-port), `rdr-to`, `binat-to` |
|
| 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` |
|
| Scrub | remontagem 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` |
|
| QoS (sucessor do ALTQ) | `queue`/`queue on` com os agendadores `ifq` do OpenBSD 8.0: **HFSC verificado no caminho de dados** (gate 15), priq como default, FQ-CoDel compilado; `pfctl -s queue -v` funciona |
|
||||||
| 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`) |
|
| Divert | `divert-to` / tag `divert` + relay `pdivert` sobre uma família de sockets `PF_DIVERT` dentro do módulo (verificado: pacote vai para o userspace e volta) |
|
||||||
| Roteamento | `route-to` (pools rr/hash-source/hash-state, `fail-to`), `reply-to`, `dup-to` |
|
| Roteamento | `route-to`/`dup-to`/`reply-to` + pools (`round-robin`, `source-hash`) verificados (`fail-to` não existe mais no OpenBSD 8.0) |
|
||||||
| Logging | `pflogd` via canal netlink pflog (pcap DLT_PFLOG) |
|
| Logging | `pflogd` via `/dev/pflog` (pcap DLT_PFLOG, layout de registro do OpenBSD) — verificado |
|
||||||
| Controle | `/dev/pf` (0600), ABI ioctl do OpenBSD 8.0, `pfctl` compilado para Linux (binário único) |
|
| Controle | `/dev/pf` (0600), ABI ioctl do OpenBSD 8.0, `pfctl` compilado para Linux (binário único) |
|
||||||
|
|
||||||
Fora de escopo: failover `pfsync`/CARP, IPsec/pfkey, `ftp-proxy`, instâncias PF
|
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
|
por netns, agendadores exclusivos do ALTQ clássico (cbq/wfq/red — removidos do
|
||||||
OpenBSD antes da 8.0).
|
OpenBSD antes da 8.0).
|
||||||
|
|
||||||
|
## Screencasts
|
||||||
|
|
||||||
|
Sete screencasts narrados por idioma (inglês e português), 1080p30 com
|
||||||
|
legendas queimadas, o diagrama de rede do lab desenhado sobre o terminal e um
|
||||||
|
filme contínuo que emenda as fases com transições. Cada vídeo é uma **sessão
|
||||||
|
shell real** contra as VMs Alpine — comandos digitados tecla a tecla, o kernel
|
||||||
|
respondendo ao vivo — com narração de voz feminina gerada pelo MiniMax:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
./scripts/screencast/build-all.sh # -> out/screencast/{en,pt}/*.mp4
|
||||||
|
```
|
||||||
|
|
||||||
|
Detalhes, quebra por fase e o texto de descrição do YouTube:
|
||||||
|
[`docs/pt-BR/screencast.md`](docs/pt-BR/screencast.md) ·
|
||||||
|
[`docs/en/screencast.md`](docs/en/screencast.md)
|
||||||
|
|
||||||
## Início rápido
|
## Início rápido
|
||||||
|
|
||||||
Receita completa: [`docs/pt-BR/install.md`](docs/pt-BR/install.md). Versão
|
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
|
curta (comandos lidos dos scripts):
|
||||||
clone limpo ainda está pendente):
|
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
./scripts/fetch-sources.sh # fontes OpenBSD 8.0 (mirror C3SL, sha256 fixado)
|
./scripts/fetch-sources.sh # fontes OpenBSD 8.0 (mirror C3SL, sha256 fixado)
|
||||||
./scripts/vendorize.sh # staging dos arquivos upstream portados
|
./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-kernel.sh # kernel 7.3.4: bzImage + modules_prepare (patch de export: nenhum necessário)
|
||||||
./scripts/build-module.sh # -> module/pf.ko
|
./scripts/build-module.sh # -> module/pf.ko
|
||||||
make -C tools # -> tools/out/pfctl (estático), verificador de ABI
|
make -C tools # -> tools/out/{pfctl,pdivert,pflogd,abitest}
|
||||||
sudo insmod module/pf.ko # /dev/pf (0600) + hooks netfilter
|
sudo insmod module/pf.ko # /dev/pf (0600) + hooks netfilter
|
||||||
sudo pfctl -f /etc/pf.conf && sudo pfctl -e
|
sudo pfctl -f /etc/pf.conf && sudo pfctl -e
|
||||||
```
|
```
|
||||||
|
|
||||||
Leia [`docs/pt-BR/pf-guide.md`](docs/pt-BR/pf-guide.md) **antes** de habilitar
|
Toda feature listada acima é exercitada pela suíte do lab antes de você
|
||||||
o PF num caminho que você precisa: só o núcleo de filtragem do M2 tem gate
|
precisar dela; [`docs/pt-BR/feature-matrix.md`](docs/pt-BR/feature-matrix.md)
|
||||||
hoje; NAT/scrub/filas/divert/pflog ainda não são utilizáveis
|
diz qual gate prova o quê, e [`docs/pt-BR/pf-guide.md`](docs/pt-BR/pf-guide.md)
|
||||||
([`docs/pt-BR/feature-matrix.md`](docs/pt-BR/feature-matrix.md)).
|
lista as divergências que você deve conhecer antes de colocar o PF num
|
||||||
|
caminho que importa.
|
||||||
|
|
||||||
Lab de validação em VMs (Alpine via libvirt): [`docs/pt-BR/vm-lab.md`](docs/pt-BR/vm-lab.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
|
English documentation: [`README.md`](README.md) e
|
||||||
@@ -70,11 +89,11 @@ English documentation: [`README.md`](README.md) e
|
|||||||
|
|
||||||
## Estrutura
|
## Estrutura
|
||||||
|
|
||||||
- `module/` — fontes de kernel (PF portado + camada de compat; o qdisc `pfq` chega com o M5)
|
- `module/` — fontes de kernel: engine PF portado (`module/pfsrc`) + camada de compat Linux (`module/compat`)
|
||||||
- `tools/` — pfctl (build estático Linux), fonte do relay pdivert, verificador de ABI (`pflogd` ainda não portado)
|
- `tools/` — pfctl (build estático Linux), relay pdivert, pflogd, verificador de ABI (`make -C tools abi`)
|
||||||
- `patches/kernel/` — conjunto de patches de core do Linux (hoje vazio; evidência em `p1-needs.txt`, ver `docs/pt-BR/patches.md`)
|
- `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
|
- `scripts/` — automação de fetch/build/patch/VM, mais `scripts/screencast/` para os vídeos
|
||||||
- `tests/` — suíte em VM (`tests/vm/NN-*.sh`, helpers scapy)
|
- `tests/vm/` — suíte em VM: 15 gates (`tests/vm/NN-*.sh`) + `lib.sh`
|
||||||
- `docs/en/` + `docs/pt-BR/` — documentação completa bilíngue
|
- `docs/en/` + `docs/pt-BR/` — documentação completa bilíngue
|
||||||
- `docs/superpowers/` — design + plano de implementação
|
- `docs/superpowers/` — design + plano de implementação
|
||||||
|
|
||||||
|
|||||||
+14
-9
@@ -4,8 +4,8 @@ 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
|
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`,
|
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
|
`pf_table.c`, `pf_lb.c`, `hfsc.c`, …) running as the out-of-tree module
|
||||||
by the real OpenBSD `pfctl` over the `/dev/pf` ioctl ABI.
|
`pf.ko`, driven by the real OpenBSD `pfctl` over the `/dev/pf` ioctl ABI.
|
||||||
|
|
||||||
## Index
|
## Index
|
||||||
|
|
||||||
@@ -14,9 +14,10 @@ by the real OpenBSD `pfctl` over the `/dev/pf` ioctl ABI.
|
|||||||
| [guide.md](guide.md) | One-page architecture: components, data path, control plane, milestones |
|
| [guide.md](guide.md) | One-page architecture: components, data path, control plane, milestones |
|
||||||
| [install.md](install.md) | Apply/build/install recipe (kernel, module, tools) |
|
| [install.md](install.md) | Apply/build/install recipe (kernel, module, tools) |
|
||||||
| [build.md](build.md) | Build pipeline in detail: fetch → vendorize → kernel → module → pfctl |
|
| [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 |
|
| [vm-lab.md](vm-lab.md) | The libvirt validation lab: topology, recreate/run, what each of the 15 gates proves |
|
||||||
| [feature-matrix.md](feature-matrix.md) | Per-feature status: VERIFIED-in-VM / COMPILES-untested / PENDING-Mx |
|
| [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`, what to avoid until M3/M4 |
|
| [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 |
|
||||||
| [patches.md](patches.md) | Linux core patch set (currently empty) + rules and template for future `000N-*.patch` |
|
| [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 |
|
| [caveats.md](caveats.md) | Known divergences from OpenBSD behavior and current limitations |
|
||||||
|
|
||||||
@@ -27,10 +28,14 @@ authoritative references are:
|
|||||||
|
|
||||||
- `docs/superpowers/specs/2026-11-11-pf-linux-port-design.md` — design
|
- `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/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)
|
- `docs/porting-notes.md` — staging log + the deviation record (the truth about compat-layer divergences)
|
||||||
|
|
||||||
Feature-status vocabulary used across these docs:
|
Feature-status vocabulary used across these docs:
|
||||||
|
|
||||||
- **VERIFIED-in-VM** — exercised end-to-end in the libvirt lab (see vm-lab.md).
|
- **VERIFIED-in-VM** — exercised end-to-end in the libvirt lab; the gate that proves it is named (see [vm-lab.md](vm-lab.md)).
|
||||||
- **COMPILES-untested** — the code is built into `pf.ko` but no gate has proven the behavior yet.
|
- **VERIFIED-offline** — proven by a fixture or ABI test, not by traffic.
|
||||||
- **PENDING-Mx** — lands (or lands fully) in milestone Mx; current observable behavior is documented.
|
- **COMPILES-untested** — the code is built into `pf.ko`/`pfctl` but no gate has exercised it.
|
||||||
|
- **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`).
|
||||||
+42
-17
@@ -69,14 +69,15 @@ auto-staged (hand-ported).
|
|||||||
|
|
||||||
## 5. Module — `scripts/build-module.sh`
|
## 5. Module — `scripts/build-module.sh`
|
||||||
|
|
||||||
`make -C "$KERNELDIR" M=module` → `module/pf.ko` (single module today; the
|
`make -C "$KERNELDIR" M=module` → `module/pf.ko` (a single module: the BSD
|
||||||
`pfq` qdisc arrives with M5). The script self-prepares `Module.symvers` /
|
`ifq`/`pfq` interface lives inside it, it is not a separate `tc` qdisc). The
|
||||||
`modules_prepare` first. Object list (`module/Makefile`): ported engine
|
script self-prepares `Module.symvers` / `modules_prepare` first. Object list
|
||||||
(`pf.c`, `pf_ioctl.c`, `pf_norm.c`, `pf_if.c`, `pf_lb.c`, `pf_ruleset.c`,
|
(`module/Makefile`): ported engine (`pf.c`, `pf_ioctl.c`, `pf_norm.c`,
|
||||||
`pf_table.c`, `pf_osfp.c`, `pf_syncookies.c`, `divert_sock.c`) + compat layer
|
`pf_if.c`, `pf_lb.c`, `pf_ruleset.c`, `pf_table.c`, `pf_osfp.c`,
|
||||||
(mbuf shim, kern glue, if/ip4/ip6/pcb/xmit/ifq, crypto, radix, subr_tree) +
|
`pf_syncookies.c`, `divert_sock.c`, `pfk_pflog.c`, `hfsc.c`, `fq_codel.c`) +
|
||||||
`pfk_mod.c`/`pfk_hooks.c`. Compile flags define `PFLINUX=1` and `_KERNEL=1`.
|
compat layer (mbuf shim, kern glue, if/ip4/ip6/pcb/xmit/ifq, crypto, radix,
|
||||||
Prints `MODULE_BUILD_OK`.
|
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
|
`insmod module/pf.ko` creates `/dev/pf` (0600) and registers the netfilter
|
||||||
hooks; `dmesg` shows `pfk: …` lines (see [caveats.md](caveats.md) for what
|
hooks; `dmesg` shows `pfk: …` lines (see [caveats.md](caveats.md) for what
|
||||||
@@ -84,7 +85,8 @@ the data path does).
|
|||||||
|
|
||||||
## 6. Userland — `make -C tools`
|
## 6. Userland — `make -C tools`
|
||||||
|
|
||||||
Targets (from `tools/Makefile`): `pfctl`, `abitest`, `abi`, `test`, `clean`.
|
Targets (from `tools/Makefile`): `pfctl`, `pdivert`, `pflogd`, `abitest`,
|
||||||
|
`abi`, `test`, `clean`.
|
||||||
|
|
||||||
- `pfctl` — the OpenBSD 8.0 sources compiled against the copied ABI headers;
|
- `pfctl` — the OpenBSD 8.0 sources compiled against the copied ABI headers;
|
||||||
result is a **static binary** at `tools/out/pfctl`. Every deviation from
|
result is a **static binary** at `tools/out/pfctl`. Every deviation from
|
||||||
@@ -97,20 +99,43 @@ Targets (from `tools/Makefile`): `pfctl`, `abitest`, `abi`, `test`, `clean`.
|
|||||||
- `test` — offline parse checks: runs `pfctl -n -f` over `tools/tests/*.conf`
|
- `test` — offline parse checks: runs `pfctl -n -f` over `tools/tests/*.conf`
|
||||||
(basic / translations / queues+scrub / anchor / negative `broken.conf`).
|
(basic / translations / queues+scrub / anchor / negative `broken.conf`).
|
||||||
This is pure userland; it needs no `/dev/pf`.
|
This is pure userland; it needs no `/dev/pf`.
|
||||||
- `pdivert` — divert relay source under `tools/pdivert/`; see
|
- `pdivert` — the divert relay, built from `tools/pdivert/`. It talks to the
|
||||||
[feature-matrix.md](feature-matrix.md) for its (pending) kernel side.
|
`AF_DIVERT` socket family the module registers at runtime
|
||||||
|
(`socket(PF_DIVERT=32, SOCK_RAW, IPPROTO_DIVERT=254)`); gate `11-divert.sh`
|
||||||
|
proves the round trip.
|
||||||
|
- `pflogd` — reads `/dev/pflog` and writes a DLT_PFLOG pcap whose record
|
||||||
|
layout is byte-compatible with OpenBSD `pflog(4)`; gate `12-pflog.sh`.
|
||||||
|
|
||||||
## 7. Root `Makefile`
|
## 7. Screencasts — `scripts/screencast/build-all.sh`
|
||||||
|
|
||||||
`make module | tools | kernel | vm | vm-destroy | test | docs | clean` — thin
|
Not part of the kernel build, but part of the deliverable. The pipeline:
|
||||||
|
|
||||||
|
| Stage | Script | What it does |
|
||||||
|
|---|---|---|
|
||||||
|
| narration text | `make_narration.py` | writes `scripts/screencast/narration/<phase>.{en,pt}.json` (one segment per beat of the session) |
|
||||||
|
| voice | `make_audio.py` | MiniMax TTS, one MP3 per segment, female voice per language |
|
||||||
|
| session | `record.py` | real SSH session on the lab: commands typed key by key, captured as asciicast v2 |
|
||||||
|
| video | `render_video.py` | replays the capture through a terminal emulator, draws the lab diagram, burns the subtitles |
|
||||||
|
| assembly | `assemble.py` | mixes the narration onto the timeline |
|
||||||
|
| full film | `join.py` | crossfades the phases through chapter cards |
|
||||||
|
|
||||||
|
`make screencast` (or the script) runs all of it; it needs
|
||||||
|
`MINIMAX_API_KEY` and the lab up. Details: [screencast.md](screencast.md).
|
||||||
|
|
||||||
|
## 8. Root `Makefile`
|
||||||
|
|
||||||
|
`make module | tools | kernel | vm | vm-destroy | test | screencast | docs | clean` — thin
|
||||||
wrappers over the scripts above. `make test` runs the VM suite
|
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.
|
([vm-lab.md](vm-lab.md)); `make screencast` renders the videos;
|
||||||
|
`make docs` just lists the bilingual doc set.
|
||||||
|
|
||||||
## What has been exercised vs. not
|
## What has been exercised vs. not
|
||||||
|
|
||||||
- Stages 1–6 have each been run during M0–M2 development on the build host
|
- Stages 1–6 have each been run repeatedly on the build host (artifacts exist
|
||||||
(artifacts exist in-tree: `out/`, `module/pf.ko`, `tools/out/pfctl`), and
|
in-tree: `out/`, `module/pf.ko`, `tools/out/{pfctl,pdivert,pflogd}`), and the
|
||||||
the scripts above were verified line-by-line for this document.
|
scripts above were verified line-by-line for this document.
|
||||||
|
- Stage 7 (the screencasts) has been run end to end for all seven phases in
|
||||||
|
both languages; the videos are in `out/screencast/{en,pt}/`.
|
||||||
- A **fresh-clone end-to-end run** (`quickstart-check.sh`, plan task T19) has
|
- 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
|
*not* been executed yet; treat the exact command sequence in install.md as
|
||||||
"as designed, last-run state per milestone" rather than CI-verified.
|
"as designed, last-run state per milestone" rather than CI-verified.
|
||||||
@@ -111,6 +111,30 @@ kernel prints `pfk: divert port N: no socket bound`. `log` needs
|
|||||||
(256 records / 1 MiB) and drops. Neither is a stub any more, but neither does
|
(256 records / 1 MiB) and drops. Neither is a stub any more, but neither does
|
||||||
anything until its consumer runs (see [pf-guide.md](pf-guide.md)).
|
anything until its consumer runs (see [pf-guide.md](pf-guide.md)).
|
||||||
|
|
||||||
|
### 9. Queues: path verified; shaping semantics are HFSC's, not a bug
|
||||||
|
|
||||||
|
`15-queue.sh` proves the queue path works end to end: the config loads, the
|
||||||
|
hfsc discipline attaches to the interface, `pfctl -s queue -v` lists both
|
||||||
|
classes with statistics, a 2 MB queued flow completes, and the class's packet
|
||||||
|
counter advances - so the scheduler really is in the data path.
|
||||||
|
|
||||||
|
What the lab cannot show is a *rate cap*, and that is HFSC semantics rather
|
||||||
|
than a port defect: OpenBSD 8.0's `pf.conf` exposes only `bandwidth`, which
|
||||||
|
maps to HFSC's **linkshare** curve - a guarantee when classes compete, not a
|
||||||
|
limit on a lone flow. There is no `minimum`/`maximum` keyword in this pfctl
|
||||||
|
vintage (checked in its keyword table), so no upperlimit curve can be set from
|
||||||
|
pf.conf. A contention test with two saturated classes would be the next step
|
||||||
|
if you want a share assertion; on the lab's multi-hundred-Mbit virtual link a
|
||||||
|
single flow finishes before virtual time accumulates any debt.
|
||||||
|
|
||||||
|
Bugs found and fixed while getting here (details in `docs/porting-notes.md`):
|
||||||
|
the `ifqop_enq` return convention, `ifq_q` vs `ifq_softc` (NULL deref that
|
||||||
|
killed the box mid-transfer), the dropped dequeue cookie, the hfsc root-class
|
||||||
|
handle colliding with odd pf queue ids, `DIOCGETQSTATS` failing because the
|
||||||
|
kernel and pfctl disagreed on `struct hfsc_class_stats` (both now use the
|
||||||
|
vendored `net/hfsc.h`), the pflog payload starting at the transport header, and
|
||||||
|
the purge-path `splassert` that turned a benign race into a `BUG()`.
|
||||||
|
|
||||||
## Compat-layer deviations (from `docs/porting-notes.md`, M1 record)
|
## Compat-layer deviations (from `docs/porting-notes.md`, M1 record)
|
||||||
|
|
||||||
These are deliberate, marked-in-code divergences of the compat layer; they
|
These are deliberate, marked-in-code divergences of the compat layer; they
|
||||||
@@ -135,6 +159,33 @@ matter when reasoning about rule semantics:
|
|||||||
kernel. Containers/namespaces are not visible to PF and cannot be filtered
|
kernel. Containers/namespaces are not visible to PF and cannot be filtered
|
||||||
with it in this design.
|
with it in this design.
|
||||||
|
|
||||||
|
## Screencast pipeline (2026-11-12)
|
||||||
|
|
||||||
|
### 10. MiniMax video generation is not enabled on this account
|
||||||
|
|
||||||
|
Every MiniMax video model (`T2V-01`, `T2V-01-Director`, `MiniMax-Hailuo-02`)
|
||||||
|
answers with API error **2068 — "The current Token Plan tier does not support
|
||||||
|
this model"**. The narration (TTS) and the title artwork work fine; only the
|
||||||
|
video models are blocked. The phase transitions are therefore rendered
|
||||||
|
locally with ffmpeg (a crossfade over a chapter card) instead of AI-generated
|
||||||
|
clips. Upgrading the plan is the only thing needed to change that — the
|
||||||
|
extension point is `scripts/screencast/join.py`, which builds the cards.
|
||||||
|
|
||||||
|
### 11. The narration is a separate track, not a dubbing of the video
|
||||||
|
|
||||||
|
Each phase is recorded **once** and serves both languages: the terminal
|
||||||
|
session is language-neutral, and the two narrated versions differ only in the
|
||||||
|
audio track and the burned-in subtitles. The recorder holds each beat on
|
||||||
|
screen for as long as the longest narration segment (EN or PT) needs, so the
|
||||||
|
voice and the terminal action stay in step in both.
|
||||||
|
|
||||||
|
### 12. What the videos do not show
|
||||||
|
|
||||||
|
The videos are a demonstration, not a test run: the lab's own gate suite
|
||||||
|
(`scripts/vm-run-tests.sh`) is the authority for PASS/FAIL, and its logs live
|
||||||
|
in `out/test-logs/`. The screencasts show the same commands, but a video frame
|
||||||
|
is not a verdict.
|
||||||
|
|
||||||
## What is *not* a caveat
|
## What is *not* a caveat
|
||||||
|
|
||||||
ARP and other non-IPv4/IPv6 EtherTypes never enter the netfilter IPv4/IPv6
|
ARP and other non-IPv4/IPv6 EtherTypes never enter the netfilter IPv4/IPv6
|
||||||
|
|||||||
@@ -1,41 +0,0 @@
|
|||||||
# Demo (asciinema)
|
|
||||||
|
|
||||||
The lab can be recorded as an [asciinema](https://asciinema.org) cast. Every
|
|
||||||
byte in the recording is the real output of the lab VMs (Alpine under libvirt
|
|
||||||
running the kernel built from source with `pf.ko`); only the pacing is
|
|
||||||
scripted.
|
|
||||||
|
|
||||||
## Record
|
|
||||||
|
|
||||||
```sh
|
|
||||||
pip install asciinema # once
|
|
||||||
bash scripts/vm-run-tests.sh # or: scripts/vm-create.sh + deploy
|
|
||||||
asciinema rec out/demo-pf-linux.cast -c "bash scripts/demo-session.sh"
|
|
||||||
```
|
|
||||||
|
|
||||||
The session walks through:
|
|
||||||
|
|
||||||
1. the lab topology and the freshly loaded `pf.ko` (`dmesg` proof),
|
|
||||||
2. `pfctl -s info` over the OpenBSD ioctl ABI,
|
|
||||||
3. a real `pf.conf` (table, stateful rules, explicit drop) and `pfctl -sr`,
|
|
||||||
4. transit ping + `pfctl -ss` (stateful),
|
|
||||||
5. `pfctl -t <table> -T show` (live tables),
|
|
||||||
6. a blocked ping (counters with `pfctl -sr -vv`),
|
|
||||||
7. `divert-to` with the `pdivert` relay (packet to userspace and back),
|
|
||||||
8. shutdown.
|
|
||||||
|
|
||||||
## Play back
|
|
||||||
|
|
||||||
```sh
|
|
||||||
asciinema play out/demo-pf-linux.cast
|
|
||||||
```
|
|
||||||
|
|
||||||
## Upload
|
|
||||||
|
|
||||||
```sh
|
|
||||||
asciinema upload out/demo-pf-linux.cast
|
|
||||||
```
|
|
||||||
|
|
||||||
`scripts/demo-record.py` is an alternative recorder that produces the same
|
|
||||||
asciicast v2 format without the asciinema binary (it drives the lab over
|
|
||||||
ssh and writes the cast itself).
|
|
||||||
+39
-34
@@ -4,67 +4,71 @@ Português: [docs/pt-BR/feature-matrix.md](../pt-BR/feature-matrix.md).
|
|||||||
|
|
||||||
Status vocabulary (used identically in both languages):
|
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)).
|
- **VERIFIED-in-VM** — passed end-to-end in the libvirt lab (see [vm-lab.md](vm-lab.md)); the gate that proves it is named.
|
||||||
- **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.
|
- **VERIFIED-offline** — proven by a fixture/ABI test, not by traffic (e.g. `make -C tools test`).
|
||||||
- **COMPILES-untested** — built into `pf.ko`/`pfctl`; no gate has exercised it.
|
- **COMPILES-untested** — built into `pf.ko`/`pfctl`; no gate has exercised it.
|
||||||
- **PENDING-Mx** — lands in milestone Mx; today's observable behavior is stated.
|
- **NOT-ENABLED** — the code exists but the feature is off in this build (e.g. `af-to`, which needs the `INET6` paths).
|
||||||
- **OUT-OF-SCOPE** — non-goal by design (spec §3).
|
- **OUT-OF-SCOPE** — non-goal by design (spec §3).
|
||||||
|
|
||||||
As of 2026-11-11; source of per-row evidence: `out/test-logs/`,
|
As of 2026-11-12; source of per-row evidence: `out/test-logs/`,
|
||||||
`docs/porting-notes.md`, milestone commits.
|
`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`).
|
||||||
|
|
||||||
## Control plane & tools
|
## Control plane & tools
|
||||||
|
|
||||||
| Feature | Upstream syntax | Milestone | Status |
|
| Feature | Upstream syntax | Milestone | Status |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| Module load, `/dev/pf` (0600) | `insmod pf.ko` | M1 | VERIFIED-in-VM |
|
| Module load, `/dev/pf` (0600) | `insmod pf.ko` | M1 | VERIFIED-in-VM (gate `02-ativacao.sh`) |
|
||||||
| Enable/disable | `pfctl -e`, `pfctl -d` | 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) |
|
| Ruleset load | `pfctl -f file` | M2 | VERIFIED-in-VM (every gate loads a real ruleset) |
|
||||||
| Parse-only check | `pfctl -n -f file` | M1 | VERIFIED (offline fixture suite, `make -C tools test`) |
|
| Parse-only check | `pfctl -n -f file` | M1 | VERIFIED-offline (`make -C tools test`) |
|
||||||
| Status/counters | `pfctl -s info [-v]` | M1 | VERIFIED-in-VM |
|
| Status/counters | `pfctl -s info [-v]` | M1 | VERIFIED-in-VM |
|
||||||
| Rules listing | `pfctl -s rules [-v]` | M2 | COMPILES-untested |
|
| Rules listing | `pfctl -s rules [-v]`, `pfctl -sr` | M2 | VERIFIED-in-VM (gates 02–06) |
|
||||||
| 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 |
|
| States listing | `pfctl -s states [-v]`, `pfctl -ss` | M2 | VERIFIED-in-VM (gate `01-state.sh`) |
|
||||||
| Tables listing/ops | `pfctl -s tables`, `-t t -T show/add/del/kill` | M2 | M2-in-progress (gate 03) |
|
| Tables listing/ops | `pfctl -s tables`, `-t t -T show/add/del/kill` | M2 | VERIFIED-in-VM (gate `03-tables.sh`, incl. a live `-T delete` unblocking the flow) |
|
||||||
| Anchors | `pfctl -a anchor -f file`, `-s Anchors` | M2 | M2-in-progress (gate 04) |
|
| Anchors | `pfctl -a anchor -f file`, `-s anchors` | M2 | VERIFIED-in-VM (gate `04-anchors.sh`) |
|
||||||
| Queue listing | `pfctl -s queue -v`, `-s ifq -v` | M5 | PENDING-M5 (parses fine; counters not wired to real back ends yet) |
|
| Queue listing | `pfctl -s queue -v` | M5 | VERIFIED-in-VM (gate `15-queue.sh`: both classes with statistics) |
|
||||||
| 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`) |
|
| ioctl ABI parity | `make -C tools abi` (abitest tables) | M1 | VERIFIED-offline (userland table); the kernel-side diff is an open integrator step (`tools/UPSTREAM.md`) |
|
||||||
|
|
||||||
## Filtering core
|
## Filtering core
|
||||||
|
|
||||||
| Feature | Upstream syntax | Milestone | Status |
|
| 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) |
|
| `pass`/`match`, quick, on-interface/proto/addr/port | `pass in quick on wan proto tcp from 10/8 to port 80` | M2 | VERIFIED-in-VM (gate `01-state.sh`: transit ping through the firewall) |
|
||||||
| `block drop` | `block drop in quick …` | M2 | M2-in-progress (gate 02a) |
|
| `block drop` | `block drop in quick …` | M2 | VERIFIED-in-VM (gate `02-block.sh`: ping dies, counters prove it) |
|
||||||
| `block return` / `return-rst` / `return-rst after quarter` | `block return-rst in quick proto tcp …` | M2 | M2-in-progress (gate 02b) |
|
| `block return` / `return-rst` / `return-rst after quarter` | `block return-rst in quick proto tcp …` | M2 | VERIFIED-in-VM (gate `02-block.sh`: the client's `nc` gets an RST) |
|
||||||
| `keep state` (icmp/tcp/udp), state table, purger | `… keep state` | M2 | M2-in-progress |
|
| `keep state` (icmp/tcp/udp), state table, purger | `… keep state` | M2 | VERIFIED-in-VM (gate `01-state.sh`; state visible in `pfctl -ss`) |
|
||||||
| `set state-policy`, `allow-opts`, etc. | `set …` options | M2 | COMPILES-untested |
|
| `set state-policy`, `allow-opts`, etc. | `set …` options | M2 | COMPILES-untested |
|
||||||
| Syncookies (state overflow path) | `set syncookies always` | 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) |
|
| pfr tables (radix/hash) | `table <blk> persist { 1.2.3.4 }`, `-T add …` | M2 | VERIFIED-in-VM (gate `03-tables.sh`) |
|
||||||
| Anchors + include + quick precedence | `anchor "x"`, `load anchor …` | M2 | M2-in-progress (gate 04) |
|
| Anchors + include + quick precedence | `anchor "x"`, `load anchor …` | M2 | VERIFIED-in-VM (gate `04-anchors.sh`) |
|
||||||
| IPv6 for all of the above | same syntax, v6 | M2+ | COMPILES-untested (engine is v6-complete by construction; v6 smoke gates planned) |
|
| IPv6 for all of the above | same syntax, v6 | M2+ | COMPILES-untested — the engine is v6-complete by construction, but the v6 path is not exercised by a gate |
|
||||||
| Non-IPv4/IPv6 traffic (ARP…) | — | M2 | PFLINUX by construction: netfilter IPv4/IPv6 hooks only — ARP never enters PF path (no gate yet) |
|
| Non-IPv4/IPv6 traffic (ARP…) | — | M2 | PFLINUX by construction: netfilter IPv4/IPv6 hooks only — ARP never enters the PF path (no gate) |
|
||||||
|
|
||||||
## NAT / normalization (M3)
|
## NAT / normalization (M3)
|
||||||
|
|
||||||
| Feature | Upstream syntax | Milestone | Status |
|
| Feature | Upstream syntax | Milestone | Status |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| `nat-to` (port pools, sticky-port, static-port) | `pass out … nat-to (eth1:0)` | M3 | **VERIFIED in lab** — source NAT works end to end: the packet leaves with the pool address (tcpdump proof), the reply is de-NATed and re-routed to the client, and the state shows both tuples (`10.0.2.1:59335 (10.0.1.10:4380) -> 10.0.2.20:8`). Gate: `tests/vm/05-nat.sh`. |
|
| `nat-to` (port pools, sticky-port, static-port) | `pass out … nat-to (eth1:0)` | M3 | **VERIFIED-in-VM** — gate `05-nat.sh`: source NAT works end to end (tcpdump proof), the reply is de-NATed and re-routed to the client, and the state shows both tuples (`10.0.2.1:59335 (10.0.1.10:4380) -> 10.0.2.20:8`). |
|
||||||
| `rdr-to` | `pass in … to <fw> port 8080 rdr-to <srv> port 80 keep state` | M3 | **VERIFIED in lab** — port redirect works end to end (gate `06-rdr.sh`): the client talks to the firewall's address, pf rewrites the destination, re-routes the packet, and the reply is un-redirected. The gate also pulls 2 MB through the redirect and checks byte-for-byte integrity (GSO/TSO regression guard). |
|
| `rdr-to` | `pass in … to <fw> port 8080 rdr-to <srv> port 80 keep state` | M3 | **VERIFIED-in-VM** — gate `06-rdr.sh`: the client talks to the firewall's address, pf rewrites the destination, re-routes the packet, and the reply is un-redirected. The gate also pulls 2 MB through the redirect and checks byte-for-byte integrity (GSO/TSO regression guard). |
|
||||||
| `binat-to` | `pass quick proto icmp from 10.0.2.20 to 10.0.1.10 binat-to 10.0.2.50 keep state` | M3 | **VERIFIED in lab** — gate `07-binat.sh`. pfctl expands one `binat-to` rule into the documented pair (`pass out … nat-to <mapped> static-port` + `pass in … rdr-to <real>`), and both halves work: the peer sees the mapped address, the original never leaks, and the reply is translated back. |
|
| `binat-to` | `pass quick proto icmp from 10.0.2.20 to 10.0.1.10 binat-to 10.0.2.50 keep state` | M3 | **VERIFIED-in-VM** — gate `07-binat.sh`. pfctl expands one `binat-to` rule into the documented pair (`pass out … nat-to <mapped> static-port` + `pass in … rdr-to <real>`), and both halves work: the peer sees the mapped address, the original never leaks, and the reply is translated back. |
|
||||||
| Scrub/normalize: reassembly, `fragment drop-overlap`, `min-ttl`, `max-mss`, `df`/`no-df`, `random-id`, `set reassemble yes` | `match in all scrub (min-ttl 100 max-mss 500)` | M3 | **VERIFIED in lab** — gate `09-scrub.sh`: `min-ttl` raises the TTL (egress shows 99 after the forward decrement) and `max-mss` clamps a SYN's MSS option, both measured on the firewall's own egress. Reassembly/overlap paths are compiled and reached but not yet gated. |
|
| NAT pools (`round-robin`, `source-hash`) | `nat-to { 10.0.2.1, 10.0.3.1 } round-robin` | M4 | **VERIFIED-in-VM** — gate `10-pools.sh`, and visible in the screencast: round-robin alternates the source address between the members (3 packets each), source-hash keeps one origin on one member. |
|
||||||
| 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) |
|
| Scrub/normalize: reassembly, `fragment drop-overlap`, `min-ttl`, `max-mss`, `df`/`no-df`, `random-id`, `set reassemble yes` | `match in all scrub (min-ttl 100 max-mss 500)` | M3 | **VERIFIED-in-VM** — gate `09-scrub.sh`: `min-ttl` raises the TTL (egress shows 99 after the forward decrement) and `max-mss` clamps a SYN's MSS option, both measured on the firewall's own egress. Reassembly/overlap paths are compiled and reached but not gated. |
|
||||||
|
| OS fingerprinting in states | `pass … set os A` + `/etc/pf.os` | bonus | COMPILES-untested (`pf_osfp.c` compiled; no fingerprint packaging yet — a missing `/etc/pf.os` is tolerated) |
|
||||||
|
|
||||||
## Routing / divert / queues / logging
|
## Routing / divert / queues / logging
|
||||||
|
|
||||||
| Feature | Upstream syntax | Milestone | Status |
|
| Feature | Upstream syntax | Milestone | Status |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| `route-to` / `dup-to` / `reply-to` | `pass in … route-to { 10.0.3.2 } keep state`, `reply-to { gw }` | M4 | **VERIFIED in lab** — gates `08-routeto.sh` (route-to leaves via the gateway's link, dup-to copies while keeping the flow) and `13-replyto.sh` (the reply of a flow is forced onto the gateway link instead of the client's). Deviation: the forced nexthop is installed by setting `rt_gw4`/`rt_gw_family` on the looked-up route (Linux only ARPs the gateway when the route declares one). `fail-to` is **not a keyword in OpenBSD 8.0** (removed upstream), so it is out of scope. |
|
| `route-to` / `dup-to` / `reply-to` | `pass in … route-to { 10.0.3.2 } keep state`, `reply-to { gw }` | M4 | **VERIFIED-in-VM** — gates `08-routeto.sh` (route-to leaves via the gateway's link, dup-to copies while keeping the flow) and `13-replyto.sh` (the reply of a flow is forced onto the gateway link instead of the client's). Deviation: the forced nexthop is installed by setting `rt_gw4`/`rt_gw_family` on the looked-up route (Linux only ARPs the gateway when the route declares one). `fail-to` is **not a keyword in OpenBSD 8.0** (removed upstream), so it is out of scope. |
|
||||||
| `divert-to` / `divert` tag | `pass in … divert-to <host> port <n>` | VERIFIED in lab | **Works end-to-end.** The module registers an `AF_DIVERT` socket family (`PF_DIVERT=32`) at runtime — no kernel-tree patch. A `divert-to` rule hands the packet to that family; with no listener the packet is dropped (`divs_noport`, BSD parity); with `tools/pdivert` running the packet is delivered to userspace, rewritten and reinjected (verified with a payload mutation + checksum fix, ping 0% loss). |
|
| `divert-to` / `divert` tag | `pass in … divert-to <host> port <n>` | M4 | **VERIFIED-in-VM** — gate `11-divert.sh`. The module registers an `AF_DIVERT` socket family (`PF_DIVERT=32`) at runtime — no kernel-tree patch. A `divert-to` rule hands the packet to that family; with no listener the packet is dropped (`divs_noport`, BSD parity); with `tools/pdivert` running the packet is delivered to userspace, rewritten and reinjected (verified with a payload mutation + checksum fix, ping 0% loss). |
|
||||||
| 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 |
|
| Queues (`queue`, `queue on`, `pfctl -s queue -v`) | `queue rootq on eth2 bandwidth 10Mbit` + `queue leafq on eth2 bandwidth 2Mbit parent rootq default` | M5 | **VERIFIED-in-VM** — gate `15-queue.sh`: the queue config loads, the hfsc discipline attaches to the interface, `pfctl -s queue -v` lists both classes with statistics, a 2 MB queued flow completes, and the class's packet counter advances (the scheduler is in the data path). Note `bandwidth` is HFSC *linkshare* — a guarantee under contention, not a cap: OpenBSD 8.0's pf.conf has no `minimum`/`maximum`, so a lone flow is not rate-limited, on Linux or on OpenBSD. |
|
||||||
| 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** |
|
| HFSC | `queue on wan { hfsc … }` | M5 | **VERIFIED-in-VM** — `pfsrc/hfsc.c` (real OpenBSD 8.0 code) is in the module object list and is the discipline the gate exercises. The class handles use the vendored `net/hfsc.h` (root class `0x10000`, default class `0x1000`) shared with `pfctl`, so the stats ABI matches on both sides. |
|
||||||
| FQ-CoDel | `queue on wan { fq_codel … }` | M5 | COMPILES-untested — same as HFSC: staged, not built |
|
| FQ-CoDel | `queue on wan { fq_codel … }` | M5 | COMPILES-untested — `pfsrc/fq_codel.c` is built into `pf.ko` and the vendored `net/fq_codel.h` is shared with `pfctl`, but no gate drives it. |
|
||||||
| `pfq` qdisc (`tc qdisc … root pfq`) | n/a (Linux glue) | M5 | PENDING-M5 (does not exist in the tree yet) |
|
| `pfq` interface | n/a (BSD `ifq`/`pfq` interface, not a Linux `tc` qdisc) | M5 | **VERIFIED-in-VM** — the port keeps the BSD shape: `compat/pfk_ifq.c` implements the `ifq_ops`/`pfq_ops` interfaces and the OpenBSD back ends plug into them (`pfq` is not a Linux `tc` qdisc and never was meant to be). priq is the default discipline. |
|
||||||
| pflog (`log`, `log all`, `block log`) + `pflogd` pcap (DLT_PFLOG) | `pass out quick log` + `pflogd /tmp/x.pcap` | M4 | **VERIFIED in lab** — gate `12-pflog.sh`: logged packets are queued by the engine, read through `/dev/pflog` and written as a pcap with linktype DLT_PFLOG (117); the dump contains the real packet bytes. Deviation: a chardev instead of an if_pflog/bpf interface (Linux has no bpf over such a virtual interface here); record layout is byte-compatible with OpenBSD `pflog(4)`. `if_pflog.c` stays unbuilt. |
|
| pflog (`log`, `log all`, `block log`) + `pflogd` pcap (DLT_PFLOG) | `pass out quick log` + `pflogd /tmp/x.pcap` | M4 | **VERIFIED-in-VM** — gate `12-pflog.sh`: logged packets are queued by the engine, read through `/dev/pflog` and written as a pcap with linktype DLT_PFLOG (117); the dump contains the real packet bytes. Deviation: a chardev instead of an if_pflog/bpf interface (Linux has no bpf over such a virtual interface here); record layout is byte-compatible with OpenBSD `pflog(4)`. `if_pflog.c` stays unbuilt. |
|
||||||
|
|
||||||
## Non-goals (explicit)
|
## Non-goals (explicit)
|
||||||
|
|
||||||
@@ -76,3 +80,4 @@ As of 2026-11-11; source of per-row evidence: `out/test-logs/`,
|
|||||||
| Per-netns PF instances | faithful port = single global instance on `init_net` |
|
| 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 |
|
| 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 |
|
| Routing labels (`route-label`) | non-goal; ioctl layer rejects labeled rules |
|
||||||
|
| `af-to` (IPv4↔IPv6 translation) | the code sits under `#ifdef INET6` and this build has `INET6 0`; enabling it is a separate milestone, not a missing feature in the base |
|
||||||
+27
-19
@@ -24,8 +24,8 @@ between kernel and userland (ioctl encoding per `module/include/pfk.h`).
|
|||||||
| `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/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` |
|
| `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` |
|
| `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 |
|
| `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 gates `01-state.sh … 04-anchors.sh` + `lib.sh`; `tests/confs/` rulesets deployed to the lab |
|
| `tests/vm/` | VM suite: 15 gates `01-state.sh … 15-queue.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) |
|
| `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)
|
## Data path (M2, as implemented today)
|
||||||
@@ -69,33 +69,41 @@ rebuilt.
|
|||||||
|
|
||||||
## Queues, divert, pflog — today's observable behavior
|
## Queues, divert, pflog — today's observable behavior
|
||||||
|
|
||||||
- **Queues**: `pfctl` parses `queue`/`queue on` (8.0 syntax); kernel-side the
|
- **Queues**: `pfctl` parses the OpenBSD 8.0 `queue`/`queue on` syntax and the
|
||||||
ifq API is implemented by `compat/pfk_ifq.c` with **priq semantics only**;
|
kernel side implements it in the BSD shape: `compat/pfk_ifq.c` provides the
|
||||||
the `pfq` classful qdisc and the HFSC/FQ-CoDel back ends land in **M5**.
|
`ifq_ops`/`pfq_ops` interfaces and the OpenBSD back ends (`pfsrc/hfsc.c`,
|
||||||
`ifq.c`/`hfsc.c`/`fq_codel.c` are staged and compile-ready but are not in
|
`pfsrc/fq_codel.c`) plug into them; **priq** is the default discipline.
|
||||||
the module object list yet.
|
Gate `15-queue.sh` proves the **hfsc** discipline end to end: the queue
|
||||||
- **Divert**: `divert-to`/`divert` tag evaluate to PF's `PF_DIVERT` verdict;
|
config loads, the discipline attaches to the interface, `pfctl -s queue -v`
|
||||||
`divert_packet()` (`pfsrc/divert_sock.c`) currently **drops the packet with
|
lists both classes with statistics, a 2 MB queued flow completes and the
|
||||||
a rate-limited kernel log** because the AF_DIVERT socket family is not yet
|
class's packet counter advances. The class handles and the stats structs
|
||||||
instantiated. The `pdivert` relay (userspace) exists and expects
|
come from OpenBSD's own `net/hfsc.h`/`net/fq_codel.h`, vendored so the
|
||||||
`socket(PF_DIVERT=32, SOCK_RAW, IPPROTO_DIVERT=254)`.
|
kernel and `pfctl` cannot drift apart.
|
||||||
- **pflog**: `pflog_packet()` returns success but **logs nothing** (one-time
|
- **Divert**: `divert-to`/`divert` tag evaluate to PF's `PF_DIVERT` verdict
|
||||||
`pr_warn "packet not logged"`); `if_pflog.c` is staged, netlink back end is
|
and the module registers an `AF_DIVERT` socket family (`PF_DIVERT=32`) at
|
||||||
M4/M5.
|
runtime — no kernel-tree patch. With no listener the packet is dropped
|
||||||
|
(`divs_noport`, BSD parity); with the `pdivert` relay running the packet is
|
||||||
|
delivered to userspace, rewritten and reinjected. Gate `11-divert.sh`.
|
||||||
|
- **pflog**: the engine queues logged packets, `pfsrc/pfk_pflog.c` exposes
|
||||||
|
them through `/dev/pflog`, and `tools/pflogd` writes a DLT_PFLOG pcap whose
|
||||||
|
record layout is byte-compatible with OpenBSD `pflog(4)`. Gate
|
||||||
|
`12-pflog.sh`. The deviation from upstream is the transport (a chardev
|
||||||
|
instead of an `if_pflog`/bpf interface), not the format.
|
||||||
|
|
||||||
## Milestones (see feature-matrix.md for per-feature status)
|
## Milestones (see feature-matrix.md for per-feature status)
|
||||||
|
|
||||||
| Milestone | Scope | Status (2026-11-11) |
|
| Milestone | Scope | Status |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| M0 | repo, scripts, kernel build, VM boots custom kernel | done |
|
| M0 | repo, scripts, kernel build, VM boots custom kernel | done |
|
||||||
| M1 | compat layer, `pf.ko` loads, `/dev/pf`, `pfctl -f`/`-s info` | done |
|
| M1 | compat layer, `pf.ko` loads, `/dev/pf`, `pfctl -f`/`-s info` | done |
|
||||||
| M2 | filter core: rules/states/tables/anchors VM gates | **done** — gates 01–04 green |
|
| M2 | filter core: rules/states/tables/anchors VM gates | **done** — gates 01–04 green |
|
||||||
| M3 | NAT (`nat-to`/`rdr-to`/`binat-to`), scrub, pflog | **done** — gates 05/06/07/09 (incl. 2 MB integrity through a redirect) and pflog gate 12 |
|
| 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 |
|
| 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) + `pfq` qdisc | pending (scheduler code ported, qdisc missing) |
|
| 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 | in progress — docs EN+PT-BR complete, 14/14 suite green, tagged |
|
| M6 | docs complete, patchset tidy, full suite green, tagged releases | **done** — docs EN+PT-BR, 15/15 suite green, tagged (`v1.0.0`) |
|
||||||
|
|
||||||
## Next reads
|
## Next reads
|
||||||
|
|
||||||
[build.md](build.md) → [vm-lab.md](vm-lab.md) → [pf-guide.md](pf-guide.md) →
|
[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).
|
[feature-matrix.md](feature-matrix.md) → [screencast.md](screencast.md) →
|
||||||
|
[caveats.md](caveats.md).
|
||||||
+24
-8
@@ -75,19 +75,34 @@ Because the deliverable is an out-of-tree module, "applying the patch set"
|
|||||||
adds a documented `patches/kernel/000N-*.patch` (all such patches: one
|
adds a documented `patches/kernel/000N-*.patch` (all such patches: one
|
||||||
section each in docs/en/patches.md with what/why/impact/revert).
|
section each in docs/en/patches.md with what/why/impact/revert).
|
||||||
|
|
||||||
## 6. Notes on the steps above (2026-11-11)
|
## 6. Notes on the steps above (2026-11-12)
|
||||||
|
|
||||||
- Step **2.3 is a documented no-op today**: the patch set is empty and
|
- 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`
|
`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
|
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.
|
[patches.md](patches.md)). You can safely skip 2.3.
|
||||||
- Step 2.4 builds `pfctl` and the ABI harness only (`make -C tools` targets:
|
- Step 2.4 builds every userland tool: `make -C tools` targets are `pfctl`,
|
||||||
`pfctl abitest abi test clean`). `pdivert` is present as a source file with
|
`pdivert`, `pflogd` and the ABI harness (`abitest`, `abi`, `test`, `clean`).
|
||||||
no Makefile target yet; `pflogd` is not ported.
|
- After 2.5, the whole feature set is usable and gated by the lab suite —
|
||||||
- After 2.5, what you can actually *use* is the M2 filter core (pass/block/
|
filtering with state, tables and anchors, NAT (`nat-to`/`rdr-to`/`binat-to`
|
||||||
quick/keep state/tables/anchors); NAT/scrub/queues/divert/pflog are gated
|
plus pools), scrub, forced routing (`route-to`/`dup-to`/`reply-to`),
|
||||||
by later milestones — see the [feature matrix](feature-matrix.md) and read
|
`divert`, `pflog` and the OpenBSD 8.0 queues (HFSC). The
|
||||||
[pf-guide.md](pf-guide.md) before enabling PF on a path you need.
|
[feature matrix](feature-matrix.md) says which gate proves what; read
|
||||||
|
[pf-guide.md](pf-guide.md) for the deviations before enabling PF on a path
|
||||||
|
you need.
|
||||||
|
|
||||||
|
## 6.1 Optional: the narrated screencasts
|
||||||
|
|
||||||
|
```sh
|
||||||
|
export MINIMAX_API_KEY=... # MiniMax TTS for the narration
|
||||||
|
./scripts/vm-create.sh # the lab must be up
|
||||||
|
./scripts/screencast/build-all.sh # -> out/screencast/{en,pt}/*.mp4
|
||||||
|
```
|
||||||
|
|
||||||
|
It records real shell sessions against the lab, generates the narration with
|
||||||
|
a MiniMax female voice (English and Portuguese) and renders 1080p30 MP4s
|
||||||
|
with burned-in subtitles and the lab's network diagram. Details:
|
||||||
|
[screencast.md](screencast.md).
|
||||||
|
|
||||||
## 7. Where to go next
|
## 7. Where to go next
|
||||||
|
|
||||||
@@ -99,4 +114,5 @@ section each in docs/en/patches.md with what/why/impact/revert).
|
|||||||
| Daily rulesets & warnings | [pf-guide.md](pf-guide.md) |
|
| Daily rulesets & warnings | [pf-guide.md](pf-guide.md) |
|
||||||
| Known divergences | [caveats.md](caveats.md) |
|
| Known divergences | [caveats.md](caveats.md) |
|
||||||
| Core patch mechanism | [patches.md](patches.md) |
|
| Core patch mechanism | [patches.md](patches.md) |
|
||||||
|
| Narrated screencasts | [screencast.md](screencast.md) |
|
||||||
| Doc index | [README.md](README.md) |
|
| Doc index | [README.md](README.md) |
|
||||||
+20
-13
@@ -3,10 +3,12 @@
|
|||||||
Português: [docs/pt-BR/pf-guide.md](../pt-BR/pf-guide.md).
|
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
|
This is the "what can I actually run today" guide. It follows the
|
||||||
[feature matrix](feature-matrix.md): **M2 scope (filtering core: pass/block/
|
[feature matrix](feature-matrix.md): the filtering core (pass/block/quick/
|
||||||
quick/keep state/tables/anchors + enable/disable)** has VM evidence behind it;
|
keep state/tables/anchors), the NAT family, scrub, forced routing, pools,
|
||||||
everything beyond that is either COMPILES-untested or PENDING. Install/build
|
`divert`, `pflog` and the OpenBSD 8.0 queues all have **VM evidence** behind
|
||||||
is covered by [install.md](install.md) and [build.md](build.md).
|
them — 15/15 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).
|
||||||
|
|
||||||
## Daily loop
|
## Daily loop
|
||||||
|
|
||||||
@@ -25,21 +27,21 @@ sudo rmmod pf # unload (rule/state flush happens here)
|
|||||||
|
|
||||||
> **Warning — order matters**: `-f` then `-e`. Enabling with no matching `pass` rule
|
> **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
|
> 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
|
> policy, and the lab runs have *actually cut the test VM's ssh* that way
|
||||||
> way (the lab keeps a serial console for exactly this). On a remote host,
|
> (the lab keeps a serial console for exactly this). On a remote host, pass
|
||||||
> pass your management interface before you enable anything.
|
> your management interface before you enable anything.
|
||||||
|
|
||||||
## A ruleset that works in this port today
|
## A ruleset that works in this port today
|
||||||
|
|
||||||
```pf
|
```pf
|
||||||
# /etc/pf.conf — M2 scope only. Interface names here match the VM lab
|
# /etc/pf.conf. Interface names here match the VM lab (mgmt/lan/wan);
|
||||||
# (mgmt/lan/wan); substitute your own (eth0, enp1s0, …).
|
# substitute your own (eth0, enp1s0, …).
|
||||||
|
|
||||||
# 1) keep yourself reachable (do this first, always)
|
# 1) keep yourself reachable (do this first, always)
|
||||||
pass in quick on mgmt
|
pass in quick on mgmt
|
||||||
pass out quick
|
pass out quick
|
||||||
|
|
||||||
# 2) filter core — proven today: quick, proto/port matching, state, drop
|
# 2) filter core — proven in the lab: quick, proto/port matching, state, drop
|
||||||
table <blk> persist # live blocklist (gate 03 pattern)
|
table <blk> persist # live blocklist (gate 03 pattern)
|
||||||
block in quick proto tcp from <blk> to any
|
block in quick proto tcp from <blk> to any
|
||||||
|
|
||||||
@@ -56,14 +58,19 @@ grammar **parse** fine — `make -C tools test` exercises them offline — but
|
|||||||
the *kernel behavior* of most `set` options has not been gate-verified yet;
|
the *kernel behavior* of most `set` options has not been gate-verified yet;
|
||||||
treat them as load-and-pray until the matrix moves them.
|
treat them as load-and-pray until the matrix moves them.
|
||||||
|
|
||||||
## What to avoid until the milestones land
|
**Not gate-verified** (honest gaps): the IPv6 path, OS fingerprinting, the
|
||||||
|
`set` options above, syncookies and the FQ-CoDel discipline. `af-to` is
|
||||||
|
compiled out in this build (`INET6 0`).
|
||||||
|
|
||||||
|
## Feature notes — what each syntax does today
|
||||||
|
|
||||||
| Syntax | Until | What happens today |
|
| Syntax | Until | What happens today |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `nat-to`, `rdr-to`, `binat-to` | — | **Verified in the lab** (gates `05-nat`, `06-rdr`, the latter pulls 2 MB through a redirect and compares it byte for byte). `nat-to (egress)` expands the OpenBSD interface *group*; on a multi-NIC host prefer the explicit `nat-to (<iface>)` form. `binat-to` is expanded by pfctl into a `nat-to … static-port` + `rdr-to` pair (gate `07-binat`). |
|
| `nat-to`, `rdr-to`, `binat-to` | — | **Verified in the lab** (gates `05-nat`, `06-rdr`, the latter pulls 2 MB through a redirect and compares it byte for byte). `nat-to (egress)` expands the OpenBSD interface *group*; on a multi-NIC host prefer the explicit `nat-to (<iface>)` form. `binat-to` is expanded by pfctl into a `nat-to … static-port` + `rdr-to` pair (gate `07-binat`). |
|
||||||
| `scrub (min-ttl, max-mss, df, random-id)`, `set reassemble` | — | **Verified in the lab** (gate `09-scrub.sh`): `min-ttl` and `max-mss` measurably rewrite transit packets. || `divert-to`, `divert` tag | — | **Verified in the lab** (gate `11-divert`): the packet is delivered to an `AF_DIVERT` socket (`PF_DIVERT=32`) served by `tools/pdivert`; with no listener it is dropped, exactly like BSD's `divs_noport`. |
|
| `scrub (min-ttl, max-mss, df, random-id)`, `set reassemble` | — | **Verified in the lab** (gate `09-scrub.sh`): `min-ttl` and `max-mss` measurably rewrite transit packets. |
|
||||||
|
| `divert-to`, `divert` tag | — | **Verified in the lab** (gate `11-divert`): the packet is delivered to an `AF_DIVERT` socket (`PF_DIVERT=32`) served by `tools/pdivert`; with no listener it is dropped, exactly like BSD's `divs_noport`. |
|
||||||
| `route-to`, `dup-to`, `reply-to` | — | **Verified in the lab** (gates `08-routeto.sh`, `13-replyto.sh`): the forced gateway is used instead of the normal egress, `dup-to` copies while keeping the flow, and `reply-to` pushes a flow's answers out through the gateway link. `fail-to` is not a keyword in OpenBSD 8.0 (removed upstream). |
|
| `route-to`, `dup-to`, `reply-to` | — | **Verified in the lab** (gates `08-routeto.sh`, `13-replyto.sh`): the forced gateway is used instead of the normal egress, `dup-to` copies while keeping the flow, and `reply-to` pushes a flow's answers out through the gateway link. `fail-to` is not a keyword in OpenBSD 8.0 (removed upstream). |
|
||||||
| `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**. |
|
| `queue` / `queue on` / `pfctl -s queue` | — | **Verified in the lab** (gate `15-queue.sh`): queues load, hfsc attaches, `pfctl -s queue -v` reports class statistics and a queued 2 MB flow is counted by the class. `bandwidth` is a *guarantee under contention*, not a cap - 8.0's pf.conf has no upperlimit keyword. |
|
||||||
| `log`, `log all`, `block log`, `pflogd` | — | **Verified in the lab** (gate `12-pflog`): run `pflogd /tmp/x.pcap` (reads `/dev/pflog`); the file is a `DLT_PFLOG` (117) pcap readable by tcpdump/wireshark. |
|
| `log`, `log all`, `block log`, `pflogd` | — | **Verified in the lab** (gate `12-pflog`): run `pflogd /tmp/x.pcap` (reads `/dev/pflog`); the file is a `DLT_PFLOG` (117) pcap readable by tcpdump/wireshark. |
|
||||||
| `route-label …` | never (non-goal) | Rejected at the ioctl layer. |
|
| `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. |
|
| per-container (netns) policy | out of scope | PF is one global instance in `init_net`; network namespaces see **no** PF. |
|
||||||
|
|||||||
@@ -0,0 +1,123 @@
|
|||||||
|
# Screencasts / Videos da demonstracao
|
||||||
|
|
||||||
|
> PT-BR: [docs/pt-BR/screencast.md](../pt-BR/screencast.md) · EN: this page
|
||||||
|
|
||||||
|
Sete videos por idioma, com **narracao com voz feminina** (MiniMax),
|
||||||
|
**legendas queimadas**, **diagrama de rede do lab** e um **filme completo**
|
||||||
|
com transicoes entre as fases. 1920x1080 @ 30fps, H.264/MP4 + AAC — prontos
|
||||||
|
para subir no YouTube.
|
||||||
|
|
||||||
|
Cada video e a gravacao de uma **sessao shell real**: uma conexao SSH de
|
||||||
|
verdade ao firewall Alpine, com os comandos digitados tecla a tecla e a saida
|
||||||
|
do kernel chegando em tempo real. Nada e simulado.
|
||||||
|
|
||||||
|
## As fases
|
||||||
|
|
||||||
|
| # | Video | O que mostra |
|
||||||
|
|---|-------|--------------|
|
||||||
|
| 0 | `00-ambiente.mp4` | O lab: 4 VMs Alpine sob libvirt, as quatro interfaces do firewall, e o diagrama de rede completo |
|
||||||
|
| 1 | `01-implementacao.mp4` | A implementacao no kernel: fontes do OpenBSD 8.0 + camada de compatibilidade = `pf.ko` |
|
||||||
|
| 2 | `02-ativacao.mp4` | `insmod pf.ko`, `/dev/pf`, `pfctl -e`, `pfctl -s info` |
|
||||||
|
| 3 | `03-regras.mp4` | Regras basicas: tabela, anchor, `keep state`, `block drop` (o ping morre) |
|
||||||
|
| 4 | `04-roteamento.mp4` | `route-to` e `reply-to`, com o diagrama mostrando o caminho real do pacote |
|
||||||
|
| 5 | `05-balanceamento.mp4` | Pool `round-robin`, pool `source-hash` e `dup-to` |
|
||||||
|
| 6 | `06-qos.mp4` | Filas `hfsc` do OpenBSD 8.0 e uma transferencia de 2 MB pela classe |
|
||||||
|
|
||||||
|
Mais `pf-on-linux-full.mp4`: o filme completo, com as sete fases emendadas por
|
||||||
|
transicoes (crossfade sobre um cartao de capitulo).
|
||||||
|
|
||||||
|
## Vozes MiniMax
|
||||||
|
|
||||||
|
| Idioma | Voz | Id |
|
||||||
|
|--------|-----|-----|
|
||||||
|
| Ingles (en_US) | Confident Woman | `English_ConfidentWoman` |
|
||||||
|
| Portugues (pt_BR) | Confident Woman | `Portuguese_ConfidentWoman` |
|
||||||
|
|
||||||
|
A audio e gerada pelo MiniMax TTS, **um arquivo por segmento** de fala. Como
|
||||||
|
cada segmento corresponde a uma batida da sessao, a voz comeca exatamente
|
||||||
|
quando a acao correspondente aparece na tela.
|
||||||
|
|
||||||
|
## Como regravar
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# tudo: audio -> sessoes -> videos -> filme completo
|
||||||
|
./scripts/screencast/build-all.sh
|
||||||
|
|
||||||
|
# uma fase so
|
||||||
|
./scripts/screencast/build-all.sh 04-roteamento
|
||||||
|
```
|
||||||
|
|
||||||
|
Etapas individuais:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
python3 scripts/screencast/make_audio.py --lang pt # audio MiniMax
|
||||||
|
python3 scripts/screencast/record.py --plan scripts/screencast/plans/04-roteamento.json \\
|
||||||
|
--out out/screencast/caps/04-roteamento.cast --pace --ensure-pf
|
||||||
|
python3 scripts/screencast/assemble.py --phase 04-roteamento --lang pt
|
||||||
|
python3 scripts/screencast/join.py --lang pt
|
||||||
|
```
|
||||||
|
|
||||||
|
### Como o pipeline funciona
|
||||||
|
|
||||||
|
```
|
||||||
|
planos/*.json --record.py--> caps/*.cast --render_video.py--> video mudo
|
||||||
|
(comandos) (pty real) (asciicast) (pyte + PIL) |
|
||||||
|
|
|
||||||
|
narracao/*.json --make_audio.py--> vo/<fase>.<lang>/*.mp3 ----------+
|
||||||
|
(MiniMax TTS) |
|
||||||
|
assemble.py: mix + legendas
|
||||||
|
|
|
||||||
|
join.py: transicoes
|
||||||
|
```
|
||||||
|
|
||||||
|
Pontos que importam:
|
||||||
|
|
||||||
|
* **Sessao real.** `record.py` abre um pty, faz SSH na VM e digita os comandos
|
||||||
|
um caractere por vez (~34 c/s, com jitter). O `bash --noediting` evita que o
|
||||||
|
editor de linha redesenhe o comando e o tty o repita.
|
||||||
|
* **Pacing.** A gravacao segura a tela enquanto a voz fala: cada batida dura
|
||||||
|
no maximo o trecho de audio que a acompanha (o maior entre EN e PT), para
|
||||||
|
uma unica captura servir as duas versoes.
|
||||||
|
* **Legendas.** O texto da narracao e queimado no video, na lingua da trilha.
|
||||||
|
* **Diagrama.** Nas fases 0, 4 e 5 o mapa do lab e desenhado sobre o
|
||||||
|
terminal, e o caminho em discussao fica destacado (eth1/eth2/eth3).
|
||||||
|
|
||||||
|
Requisitos: `ffmpeg`, `python3` com `PIL` e `pyte`, a fonte DejaVu, a chave
|
||||||
|
`MINIMAX_API_KEY` no ambiente, e o lab de VMs no ar.
|
||||||
|
|
||||||
|
## Onde ficam os arquivos
|
||||||
|
|
||||||
|
| Caminho | Conteudo |
|
||||||
|
|---------|----------|
|
||||||
|
| `out/screencast/en/`, `out/screencast/pt/` | os videos finais |
|
||||||
|
| `out/screencast/caps/` | as capturas asciicast das sessoes |
|
||||||
|
| `out/screencast/vo/` | o audio gerado pelo MiniMax |
|
||||||
|
| `scripts/screencast/plans/` | o roteiro de comandos de cada fase |
|
||||||
|
| `scripts/screencast/narration/` | os textos da narracao (EN e PT) |
|
||||||
|
|
||||||
|
## Texto de descricao (YouTube)
|
||||||
|
|
||||||
|
```
|
||||||
|
PF on Linux - o motor do OpenBSD PF 8.0 dentro do kernel Linux
|
||||||
|
|
||||||
|
Port completo do Packet Filter do OpenBSD 8.0 para o kernel Linux 7.3.4,
|
||||||
|
empacotado como modulo (pf.ko) e controlado pelo pfctl original do OpenBSD
|
||||||
|
sobre a ABI de ioctls do /dev/pf.
|
||||||
|
|
||||||
|
O lab e real: 4 VMs Alpine sob libvirt, kernel compilado do fonte com o
|
||||||
|
modulo, trafego real entre cliente, firewall e servidor.
|
||||||
|
|
||||||
|
Fases:
|
||||||
|
0. O ambiente de testes - as 4 VMs e o diagrama de rede
|
||||||
|
1. Implementacao no kernel - das fontes OpenBSD ao pf.ko
|
||||||
|
2. Ativacao - insmod, /dev/pf, pfctl -e, pfctl -s info
|
||||||
|
3. Regras basicas - tabelas, anchors, keep state, block drop
|
||||||
|
4. Roteamento - route-to e reply-to, com o caminho real no diagrama
|
||||||
|
5. Balanceamento - pools round-robin, source-hash e dup-to
|
||||||
|
6. QOS - filas hfsc do OpenBSD 8.0 no caminho de dados
|
||||||
|
|
||||||
|
Suite automatizada: 15 gates, todos verdes.
|
||||||
|
Codigo e documentacao: https://git.labexposed.com/lgcosta/pf-linux-port
|
||||||
|
|
||||||
|
#linux #kernel #openbsd #pf #firewall #netfilter #screencast
|
||||||
|
```
|
||||||
+60
-22
@@ -3,8 +3,8 @@
|
|||||||
Português: [docs/pt-BR/vm-lab.md](../pt-BR/vm-lab.md).
|
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
|
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
|
source of truth). The lab has been created and run through M0–M6; the full
|
||||||
green run is the M2+ gate and is still in progress (see §6).
|
suite is green (§6).
|
||||||
|
|
||||||
## 1. Where it lives
|
## 1. Where it lives
|
||||||
|
|
||||||
@@ -61,6 +61,20 @@ isolated bridges `pf-lan` 10.0.1.0/24, `pf-wan` 10.0.2.0/24, `pf-wan2`
|
|||||||
running. Laptop ssh aliases (`pf0|cli|srv|gw2`, ProxyJump via deb) are
|
running. Laptop ssh aliases (`pf0|cli|srv|gw2`, ProxyJump via deb) are
|
||||||
generated into `~/.ssh/config.d/pflab`.
|
generated into `~/.ssh/config.d/pflab`.
|
||||||
|
|
||||||
|
**From pf0 to the other VMs.** The firewall has its own keypair and the
|
||||||
|
peers trust it, so a session on pf0 can drive the client and the server
|
||||||
|
directly — this is what the screencasts do (`ssh cli 'ping …'`):
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# once per lab, from the hypervisor
|
||||||
|
ssh pf0 'ssh-keygen -t ed25519 -N "" -C pf0 -f /root/.ssh/id_ed25519'
|
||||||
|
# then authorize that public key on cli, srv and gw2
|
||||||
|
```
|
||||||
|
|
||||||
|
`/root/.ssh/config` on pf0 maps the short names `cli`, `srv` and `gw2` to
|
||||||
|
10.0.1.10, 10.0.2.20 and 10.0.3.2, and `/etc/hosts` carries the same
|
||||||
|
mapping.
|
||||||
|
|
||||||
## 4. Running the suite
|
## 4. Running the suite
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
@@ -78,28 +92,52 @@ and exits non-zero if anything failed.
|
|||||||
|
|
||||||
## 5. What each gate proves today
|
## 5. What each gate proves today
|
||||||
|
|
||||||
| Gate | Scenario (asserted with real traffic, not exit codes) |
|
Every gate asserts **real traffic and observable state**, never just an exit
|
||||||
|
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
|
||||||
|
`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
|
||||||
|
`out/test-logs/<gate>.sh.log`.
|
||||||
|
|
||||||
|
Not gated (honest gaps): the IPv6 path, OS fingerprinting, `set` options
|
||||||
|
(`state-policy`, `allow-opts`) and syncookies — the code is compiled into
|
||||||
|
`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.
|
||||||
|
|
||||||
|
## 6. Current suite status (2026-11-12)
|
||||||
|
|
||||||
|
**15/15 gates green.** A full clean run (`./scripts/vm-run-tests.sh`, ~9 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
|
||||||
|
`out/test-logs/<gate>.sh.log`.
|
||||||
|
|
||||||
|
| Gate | What it proves now |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `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` |
|
| `01-state.sh` | `keep state` across a transit ping; the state shows up in `pfctl -ss` |
|
||||||
| `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 |
|
| `02-block.sh` | `block drop` kills the ping; `block return-rst` gets the client an RST |
|
||||||
| `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 |
|
| `03-tables.sh` | a `pfr` table blocks a host; a 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 |
|
| `04-anchors.sh` | an anchor rule is evaluated before a later main-ruleset `block` |
|
||||||
|
| `05-nat.sh` | source NAT: the packet leaves with the pool address, the reply is de-NATed |
|
||||||
|
| `06-rdr.sh` | port redirect end to end, plus 2 MB through it with byte-for-byte integrity |
|
||||||
|
| `07-binat.sh` | `binat-to`: the peer sees the mapped address, the original never leaks |
|
||||||
|
| `08-routeto.sh` | `route-to` leaves via the gateway's link; `dup-to` copies and keeps the flow |
|
||||||
|
| `09-scrub.sh` | `min-ttl` and `max-mss` measured on the firewall's own egress |
|
||||||
|
| `10-pools.sh` | NAT pool selection (`round-robin`, `source-hash`) |
|
||||||
|
| `11-divert.sh` | `divert-to` to the `pdivert` relay: packet to userspace and back |
|
||||||
|
| `12-pflog.sh` | logged packets reach `/dev/pflog` and land in a DLT_PFLOG pcap |
|
||||||
|
| `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 |
|
||||||
|
|
||||||
Planned gates per the plan document (T11–T18): 05-nat, 06-rdr, 07-binat,
|
The screencasts ([screencast.md](screencast.md)) show most of these gates
|
||||||
08-scrub (scapy), 09-routeto, 10-dup-fail, 11-divert, 12-queues (iperf rate
|
being run, with the lab's network diagram drawn over the terminal.
|
||||||
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
|
## 7. Manual poking
|
||||||
|
|
||||||
|
|||||||
@@ -939,3 +939,119 @@ Rationale: a single consumer is the real use case, and Linux gives no bpf hook
|
|||||||
for a non-`net_device` virtual interface without pulling in a much larger
|
for a non-`net_device` virtual interface without pulling in a much larger
|
||||||
amount of the BSD if layer. The on-disk format is unchanged, so existing
|
amount of the BSD if layer. The on-disk format is unchanged, so existing
|
||||||
tooling works.
|
tooling works.
|
||||||
|
|
||||||
|
## Scheduler back ends in the build (M5, first step)
|
||||||
|
|
||||||
|
`pfsrc/hfsc.c` and `pfsrc/fq_codel.c` are now compiled into `pf.ko` (the
|
||||||
|
`pf_ioctl` queue manager already references `hfsc_initialize`,
|
||||||
|
`hfsc_pf_addqueue`, `ifq_hfsc_ops`, `fqcodel_pf_addqueue`, `ifq_fqcodel_ops`).
|
||||||
|
The priq shim in `compat/pfk_ifq.c` remains the *attached* scheduler, so
|
||||||
|
behaviour is unchanged until the data path enqueues into a queue.
|
||||||
|
|
||||||
|
Compat surface added for the back ends (all in `module/include/pfk.h` /
|
||||||
|
`pfk_if.h` / `compat/pfk_ifq.c`):
|
||||||
|
|
||||||
|
| OpenBSD | This port |
|
||||||
|
|---|---|
|
||||||
|
| `struct mbuf_list` + `ml_init/ml_enqueue/ml_dequeue/ml_dequeue_tail/ml_enlist/ml_len/ml_empty/ml_purge`, `MBUF_LIST_FIRST/INITIALIZER` | inline helpers in `pfk_if.h` (`ml_purge` in `compat/pfk_mbuf.c`) |
|
||||||
|
| `ifq_q_enter()/ifq_q_leave()` (scheduler ownership handshake, guarded by the ifq mutex) | ownership check against `ifq->ifq_ops`, returning `ifq_softc`; `leave` is a no-op (the port has no ifq mutex — the data path is one ordered work queue) |
|
||||||
|
| `nsecuptime()` | `getnsecuptime()` (`ktime_get_ns`) |
|
||||||
|
| `tick_nsec` (`HFSC_CLK_PER_TICK`) | `1000000000 / HZ` |
|
||||||
|
| `splnet()/splx()` | no-ops, kept as markers of the critical regions |
|
||||||
|
| `KERNEL_ASSERT_LOCKED()` | no-op (structural, not runtime) |
|
||||||
|
| `struct hfsc_sc`, `struct hfsc_class_stats`, `struct fqcodel_stats`, `PKTCNTR_INC`, `HFSC_*` limits | declared in `pfk.h` (OpenBSD keeps them in `net/if_hfsc.h` / `net/if_fq_codel.h`) |
|
||||||
|
|
||||||
|
Two port fixes worth recording:
|
||||||
|
|
||||||
|
- `min(cd->drops - 1, nitems(...) - 1)` → `min_t(unsigned int, ...)`: Linux
|
||||||
|
type-checks `min()` and `nitems()` is `size_t`.
|
||||||
|
- `hfsc_more_slots(u_int current)` → renamed the parameter: **`current` is a
|
||||||
|
kernel macro** (`get_current()`), which turned the definition into a K&R
|
||||||
|
function and produced a baffling "makes pointer from integer" error at the
|
||||||
|
call site.
|
||||||
|
|
||||||
|
## ifq contract details found while wiring the schedulers (M5)
|
||||||
|
|
||||||
|
The staged OpenBSD back ends assume three things the first version of the
|
||||||
|
port's ifq shim got wrong. All three were reproduced in the lab and fixed:
|
||||||
|
|
||||||
|
| Assumption | What the port did | Fix |
|
||||||
|
|---|---|---|
|
||||||
|
| `ifqop_enq()` returns **the packet to drop** (NULL = accepted) | treated a non-NULL return as an error (`ENOMEM`) and the NULL return as success → every accepted packet was reported as dropped | `ifq_enqueue()` frees the returned mbuf and reports success |
|
||||||
|
| the scheduler state lives in `ifq->ifq_q` | `ifq_attach()` stored it in `ifq_softc` only → `hfsc_enq()` dereferenced NULL and the box died mid-transfer | `ifq_attach()` keeps both fields in sync |
|
||||||
|
| `ifq_deq_begin()`'s cookie is consumed by `ifq_deq_commit()` | the cookie was dropped on the floor (always NULL) | a per-queue `ifq_dequeue` slot carries it |
|
||||||
|
|
||||||
|
Also fixed: the hfsc **root-class handle** used bit `0x1`, which is exactly the
|
||||||
|
handle of any odd pf queue id, so `hfsc_pf_addqueue()` saw the root class as a
|
||||||
|
duplicate of the queue being added and returned `EBUSY` ("cannot add queue").
|
||||||
|
Handles now use bit `0x80000000`; pf queue ids are `u_int16_t`, so they can
|
||||||
|
never collide.
|
||||||
|
|
||||||
|
### `splassert` under rw-semas
|
||||||
|
|
||||||
|
`PF_ASSERT_UNLOCKED()` (used by `pf_purge_expired_states()`) is a BSD
|
||||||
|
per-CPU-spl check: "this context did not take the lock". With an rw-semaphore
|
||||||
|
`rw_status()` is global, so the assert fires whenever *another* CPU holds the
|
||||||
|
lock - which is not a bug, and `BUG()` there turned a harmless race into a
|
||||||
|
wedged module (`Unloading`, refcount -1). It now logs once
|
||||||
|
(`pr_warn_ratelimited`) instead of killing the host; the invariant it protects
|
||||||
|
(purge never acquires `pf_lock` itself) is upheld by the call sites, which are
|
||||||
|
only reached from `systqmp` tasks and timer work.
|
||||||
|
|
||||||
|
### What `bandwidth` can and cannot do here
|
||||||
|
|
||||||
|
OpenBSD 8.0's `pfctl` keyword table exposes `burst`, `flows`, `qlimit`,
|
||||||
|
`parent`, `default` - **not** `minimum`/`maximum`. A pf `queue` therefore only
|
||||||
|
sets HFSC's **linkshare** curve (`qspec.linkshare = scspec` in `expand_queue`).
|
||||||
|
Linkshare is a *guarantee under contention*, not a cap: a single active class
|
||||||
|
may run at line rate, and `bandwidth 2Mbit` alone will not make one flow
|
||||||
|
measure 2 Mbit/s. Verifying HFSC in this port therefore requires two competing
|
||||||
|
leaf queues and comparing their shares - which is the remaining M5 item, not a
|
||||||
|
code defect in the queue path (attach, enqueue, drain and transmit are
|
||||||
|
verified: `eth2` carries the hfsc ops, 117 drains observed, transfer completes,
|
||||||
|
box stays alive).
|
||||||
|
|
||||||
|
## Scheduler ABI must be shared, not re-implemented (M5)
|
||||||
|
|
||||||
|
The first version of this note hand-wrote `struct hfsc_class_stats`,
|
||||||
|
`struct fqcodel_stats`, `struct hfsc_sc` and the `HFSC_*` constants in `pfk.h`
|
||||||
|
from memory of the BSD sources. Two consequences showed up in the lab:
|
||||||
|
|
||||||
|
1. `pfctl -s queue -v` failed with `EINVAL` - `hfsc_pf_qstats()` rejects the
|
||||||
|
call when `*nbytes < sizeof(stats)`, and pfctl's struct (from the vendored
|
||||||
|
`net/hfsc.h`) was smaller than the kernel's invented one.
|
||||||
|
2. `HFSC_ROOT_CLASS` was guessed as `0x1`, which is exactly the handle of every
|
||||||
|
odd pf queue id, so `hfsc_pf_addqueue()` refused every root queue with
|
||||||
|
`EBUSY`. The real value is `0x10000`, safely above the `u_int16_t` qid
|
||||||
|
range.
|
||||||
|
|
||||||
|
The port now vendors OpenBSD's `net/hfsc.h` and `net/fq_codel.h` into
|
||||||
|
`module/include/net/` and includes them from `pfk.h`, so kernel and pfctl
|
||||||
|
compile against one definition. Rule for anything else in this area: if a
|
||||||
|
struct crosses the ioctl boundary, take the header from OpenBSD's tree - do not
|
||||||
|
reconstruct it.
|
||||||
|
|
||||||
|
## Screencast pipeline: what the recording taught us (2026-11-12)
|
||||||
|
|
||||||
|
Not a compat-layer deviation, but the same kind of note - the things that
|
||||||
|
only show up when you watch the thing run:
|
||||||
|
|
||||||
|
1. **The tty echo is the terminal, and readline fights it.** With bash's
|
||||||
|
default line editing, the remote tty echoes every keystroke *and*
|
||||||
|
readline redraws the line when you press enter, so the command appeared
|
||||||
|
twice on screen. `bash --noediting` leaves the tty echo alone and the
|
||||||
|
command shows exactly once.
|
||||||
|
2. **`pfctl -e` prints "pf enabled" and so does the `echo` after it.** What
|
||||||
|
looked like a duplicated output line was two different programs saying the
|
||||||
|
same thing; the capture was right.
|
||||||
|
3. **MiniMax returns the audio as a hex string**, not base64 - the payload
|
||||||
|
starts with the ASCII of the ID3 header (`49443304`). Decoding it as
|
||||||
|
base64 produced files that ffmpeg called "Header missing".
|
||||||
|
4. **Pacing has to be driven by the narration.** If the voice for a beat is
|
||||||
|
longer than the beat, the audio runs over the next action. The recorder now
|
||||||
|
holds each beat for as long as the longest segment (EN or PT) needs, so one
|
||||||
|
capture serves both languages.
|
||||||
|
5. **A phase that runs `rmmod` can break another recording.** The activation
|
||||||
|
phase unloads the module on purpose; running two captures at once left the
|
||||||
|
routing phase without `/dev/pf` mid-session. `record.py --ensure-pf` now
|
||||||
|
makes sure the module is in place before the capture starts.
|
||||||
+27
-21
@@ -1,36 +1,42 @@
|
|||||||
# Documentação PF-on-Linux (português)
|
# Documentação do PF-on-Linux (português)
|
||||||
|
|
||||||
English documentation: [`docs/en/README.md`](../en/README.md).
|
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**
|
Este é o conjunto de documentação de usuário do port do **packet filter do
|
||||||
para o **Linux 7.3.4** — o engine PF de verdade (`pf.c`, `pf_norm.c`,
|
OpenBSD 8.0 (PF)** para o **Linux 7.3.4** — o engine PF real (`pf.c`,
|
||||||
`pf_table.c`, `pf_lb.c`, …) rodando como módulo out-of-tree `pf.ko`,
|
`pf_norm.c`, `pf_table.c`, `pf_lb.c`, `hfsc.c`, …) rodando como o módulo
|
||||||
controlado pelo `pfctl` real do OpenBSD pela ABI ioctl de `/dev/pf`.
|
out-of-tree `pf.ko`, controlado pelo `pfctl` real do OpenBSD sobre a ABI ioctl
|
||||||
|
de `/dev/pf`.
|
||||||
|
|
||||||
## Índice
|
## Índice
|
||||||
|
|
||||||
| Documento | Conteúdo |
|
| Doc | O que cobre |
|
||||||
|---|---|
|
|---|---|
|
||||||
| [guide.md](guide.md) | Arquitetura em uma página: componentes, caminho de dados, plano de controle, milestones |
|
| [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) |
|
| [install.md](install.md) | Receita de apply/build/install (kernel, módulo, ferramentas) |
|
||||||
| [build.md](build.md) | Pipeline de build em detalhe: fetch → vendorize → kernel → módulo → pfctl |
|
| [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 |
|
| [vm-lab.md](vm-lab.md) | O lab de validação em libvirt: topologia, recriar/rodar, o que cada um dos 15 gates prova |
|
||||||
| [feature-matrix.md](feature-matrix.md) | Status por feature: VERIFIED-in-VM / COMPILES-untested / PENDING-Mx |
|
| [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`, o que evitar até M3/M4 |
|
| [pf-guide.md](pf-guide.md) | Uso diário: o que funciona hoje em `/etc/pf.conf` e as divergências a conhecer |
|
||||||
| [patches.md](patches.md) | Patches de core do Linux (hoje vazio) + regras e template para futuros `000N-*.patch` |
|
| [screencast.md](screencast.md) | Os vídeos narrados (inglês e português): fases, pipeline, como regravar |
|
||||||
| [caveats.md](caveats.md) | Divergências conhecidas de comportamento e limitações atuais |
|
| [patches.md](patches.md) | Conjunto de patches de core do Linux (hoje vazio) + regras e template para futuros `000N-*.patch` |
|
||||||
|
| [caveats.md](caveats.md) | Divergências conhecidas em relação ao OpenBSD e limitações atuais |
|
||||||
|
|
||||||
## Fontes de verdade
|
## Fontes da verdade
|
||||||
|
|
||||||
A documentação descreve o código como ele existe na árvore. Em caso de dúvida,
|
Os docs descrevem o código como ele existe nesta árvore. Na dúvida, as
|
||||||
as referências autoritativas são:
|
referências autoritativas são:
|
||||||
|
|
||||||
- `docs/superpowers/specs/2026-11-11-pf-linux-port-design.md` — design
|
- `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/superpowers/plans/2026-11-11-pf-linux-port.md` — plano dos milestones (tarefas T1–T19)
|
||||||
- `docs/porting-notes.md` — log de staging + registro de desvios da camada de compat (M1)
|
- `docs/porting-notes.md` — log de staging + registro das divergências (a verdade sobre as divergências da camada de compat)
|
||||||
|
|
||||||
Vocabulário de status usado em todos os documentos:
|
Vocabulário de status usado nestes docs:
|
||||||
|
|
||||||
- **VERIFIED-in-VM** — exercitado de ponta a ponta na bancada libvirt (ver vm-lab.md).
|
- **VERIFIED-in-VM** — exercitado de ponta a ponta no lab libvirt; o gate que prova está nomeado (veja [vm-lab.md](vm-lab.md)).
|
||||||
- **COMPILES-untested** — o código compila no `pf.ko`, mas nenhum gate provou o comportamento ainda.
|
- **VERIFIED-offline** — provado por um teste de fixture ou de ABI, não por tráfego.
|
||||||
- **PENDING-Mx** — entra (ou entra por completo) no milestone Mx; o comportamento observável hoje está documentado.
|
- **COMPILES-untested** — o código é compilado dentro do `pf.ko`/`pfctl` mas nenhum gate o exercitou.
|
||||||
|
- **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`).
|
||||||
+42
-16
@@ -72,13 +72,14 @@ ficam de fora do staging automático de propósito (portados à mão).
|
|||||||
|
|
||||||
## 5. Módulo — `scripts/build-module.sh`
|
## 5. Módulo — `scripts/build-module.sh`
|
||||||
|
|
||||||
`make -C "$KERNELDIR" M=module` → `module/pf.ko` (um único módulo hoje; o
|
`make -C "$KERNELDIR" M=module` → `module/pf.ko` (um único módulo: a
|
||||||
qdisc `pfq` chega com o M5). O script prepara sozinho `Module.symvers` /
|
interface BSD `ifq`/`pfq` mora dentro dele, não é um qdisc `tc` separado). O
|
||||||
`modules_prepare`. Lista de objetos (`module/Makefile`): engine portado
|
script prepara sozinho `Module.symvers` / `modules_prepare`. Lista de objetos
|
||||||
(`pf.c`, `pf_ioctl.c`, `pf_norm.c`, `pf_if.c`, `pf_lb.c`, `pf_ruleset.c`,
|
(`module/Makefile`): engine portado (`pf.c`, `pf_ioctl.c`, `pf_norm.c`,
|
||||||
`pf_table.c`, `pf_osfp.c`, `pf_syncookies.c`, `divert_sock.c`) + camada de
|
`pf_if.c`, `pf_lb.c`, `pf_ruleset.c`, `pf_table.c`, `pf_osfp.c`,
|
||||||
compat (shim mbuf, glue de kernel, if/ip4/ip6/pcb/xmit/ifq, crypto, radix,
|
`pf_syncookies.c`, `divert_sock.c`, `pfk_pflog.c`, `hfsc.c`, `fq_codel.c`) +
|
||||||
subr_tree) + `pfk_mod.c`/`pfk_hooks.c`. As flags de compilação definem
|
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`.
|
`PFLINUX=1` e `_KERNEL=1`. Termina com `MODULE_BUILD_OK`.
|
||||||
|
|
||||||
`insmod module/pf.ko` cria `/dev/pf` (0600) e registra os hooks netfilter; o
|
`insmod module/pf.ko` cria `/dev/pf` (0600) e registra os hooks netfilter; o
|
||||||
@@ -87,7 +88,8 @@ caminho de dados faz).
|
|||||||
|
|
||||||
## 6. Userland — `make -C tools`
|
## 6. Userland — `make -C tools`
|
||||||
|
|
||||||
Alvos (de `tools/Makefile`): `pfctl`, `abitest`, `abi`, `test`, `clean`.
|
Alvos (de `tools/Makefile`): `pfctl`, `pdivert`, `pflogd`, `abitest`, `abi`,
|
||||||
|
`test`, `clean`.
|
||||||
|
|
||||||
- `pfctl` — as fontes do OpenBSD 8.0 compiladas contra os headers de ABI
|
- `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`.
|
copiados; o resultado é um **binário estático** em `tools/out/pfctl`.
|
||||||
@@ -100,20 +102,44 @@ Alvos (de `tools/Makefile`): `pfctl`, `abitest`, `abi`, `test`, `clean`.
|
|||||||
- `test` — checagens de parse offline: roda `pfctl -n -f` sobre
|
- `test` — checagens de parse offline: roda `pfctl -n -f` sobre
|
||||||
`tools/tests/*.conf` (básico / traduções / filas+scrub / anchor / negativa
|
`tools/tests/*.conf` (básico / traduções / filas+scrub / anchor / negativa
|
||||||
`broken.conf`). É userland puro; não precisa de `/dev/pf`.
|
`broken.conf`). É userland puro; não precisa de `/dev/pf`.
|
||||||
- `pdivert` — o fonte do relay de divert fica em `tools/pdivert/`; o lado
|
- `pdivert` — o relay de divert, compilado a partir de `tools/pdivert/`. Ele
|
||||||
kernel (pendente) está na [matriz de features](feature-matrix.md).
|
fala com a família de sockets `AF_DIVERT` que o módulo registra em runtime
|
||||||
|
(`socket(PF_DIVERT=32, SOCK_RAW, IPPROTO_DIVERT=254)`); o gate
|
||||||
|
`11-divert.sh` prova a ida e a volta.
|
||||||
|
- `pflogd` — lê `/dev/pflog` e escreve um pcap DLT_PFLOG cujo layout de
|
||||||
|
registro é byte a byte compatível com o `pflog(4)` do OpenBSD; gate
|
||||||
|
`12-pflog.sh`.
|
||||||
|
|
||||||
## 7. `Makefile` raiz
|
## 7. Screencasts — `scripts/screencast/build-all.sh`
|
||||||
|
|
||||||
`make module | tools | kernel | vm | vm-destroy | test | docs | clean` —
|
Não faz parte do build do kernel, mas faz parte da entrega. O pipeline:
|
||||||
|
|
||||||
|
| Etapa | Script | O que faz |
|
||||||
|
|---|---|---|
|
||||||
|
| texto da narração | `make_narration.py` | escreve `scripts/screencast/narration/<fase>.{en,pt}.json` (um segmento por batida da sessão) |
|
||||||
|
| voz | `make_audio.py` | TTS do MiniMax, um MP3 por segmento, voz feminina por idioma |
|
||||||
|
| sessão | `record.py` | sessão SSH real no lab: comandos digitados tecla a tecla, capturada como asciicast v2 |
|
||||||
|
| vídeo | `render_video.py` | reproduz a captura por um emulador de terminal, desenha o diagrama do lab, queima as legendas |
|
||||||
|
| montagem | `assemble.py` | mixa a narração na linha do tempo |
|
||||||
|
| filme completo | `join.py` | emenda as fases com crossfade sobre cartões de capítulo |
|
||||||
|
|
||||||
|
`make screencast` (ou o script) roda tudo; precisa de `MINIMAX_API_KEY` e do
|
||||||
|
lab no ar. Detalhes: [screencast.md](screencast.md).
|
||||||
|
|
||||||
|
## 8. `Makefile` raiz
|
||||||
|
|
||||||
|
`make module | tools | kernel | vm | vm-destroy | test | screencast | docs | clean` —
|
||||||
wrappers finos sobre os scripts acima. `make test` roda a suíte em VM
|
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.
|
([vm-lab.md](vm-lab.md)); `make screencast` renderiza os vídeos;
|
||||||
|
`make docs` apenas lista o conjunto bilíngue.
|
||||||
|
|
||||||
## O que já foi exercitado vs. não
|
## O que já foi exercitado vs. não
|
||||||
|
|
||||||
- As etapas 1–6 foram rodadas ao longo do desenvolvimento M0–M2 no host de
|
- As etapas 1–6 foram rodadas repetidamente no host de build (há artefatos
|
||||||
build (há artefatos na árvore: `out/`, `module/pf.ko`, `tools/out/pfctl`),
|
na árvore: `out/`, `module/pf.ko`, `tools/out/{pfctl,pdivert,pflogd}`), e os
|
||||||
e os scripts acima foram verificados linha a linha para este documento.
|
scripts acima foram verificados linha a linha para este documento.
|
||||||
|
- A etapa 7 (os screencasts) foi rodada de ponta a ponta para as sete fases
|
||||||
|
nos dois idiomas; os vídeos estão em `out/screencast/{en,pt}/`.
|
||||||
- Uma execução **de ponta a ponta a partir de clone limpo**
|
- Uma execução **de ponta a ponta a partir de clone limpo**
|
||||||
(`quickstart-check.sh`, tarefa T19 do plano) **ainda não** foi feita;
|
(`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
|
trate a sequência exata do install.md como "conforme projetada e conforme
|
||||||
|
|||||||
@@ -111,6 +111,32 @@ lendo `/dev/pflog`; sem ele a fila atinge o limite (256 registros / 1 MiB) e
|
|||||||
descarta. Nenhum dos dois é mais stub, mas nenhum faz algo sem o consumidor
|
descarta. Nenhum dos dois é mais stub, mas nenhum faz algo sem o consumidor
|
||||||
rodar (ver [pf-guide.md](pf-guide.md)).
|
rodar (ver [pf-guide.md](pf-guide.md)).
|
||||||
|
|
||||||
|
### 9. Filas: caminho verificado; a semântica de modelagem é do HFSC, não um bug
|
||||||
|
|
||||||
|
`15-queue.sh` prova que o caminho de filas funciona de ponta a ponta: a
|
||||||
|
configuração carrega, a disciplina hfsc é anexada à interface, `pfctl -s
|
||||||
|
queue -v` lista as duas classes com estatísticas, um fluxo de 2 MB pela fila
|
||||||
|
completa, e o contador de pacotes da classe avança - o escalonador está mesmo
|
||||||
|
no caminho de dados.
|
||||||
|
|
||||||
|
O que o lab não mostra é um *teto de taxa*, e isso é semântica do HFSC, não
|
||||||
|
defeito do port: o `pf.conf` do OpenBSD 8.0 expõe apenas `bandwidth`, que mapeia
|
||||||
|
para a curva **linkshare** do HFSC - garantia quando classes competem, não limite
|
||||||
|
de um fluxo isolado. Não existe palavra-chave `minimum`/`maximum` nesta versão do
|
||||||
|
pfctl (verificado na sua tabela de palavras-chave), logo não há como definir
|
||||||
|
upperlimit pelo pf.conf. Um teste de contenção com duas classes saturadas seria o
|
||||||
|
próximo passo se você quiser asserir sobre share; no enlace virtual de centenas de
|
||||||
|
Mbit do lab, um único fluxo termina antes de acumular dívida de tempo virtual.
|
||||||
|
|
||||||
|
Bugs encontrados e corrigidos no caminho (detalhes em `docs/porting-notes.md`): a
|
||||||
|
convenção de retorno de `ifqop_enq`, `ifq_q` vs `ifq_softc` (deref de NULL que
|
||||||
|
matava a máquina no meio da transferência), o cookie de dequeue perdido, o
|
||||||
|
handle da classe raiz do hfsc colidindo com qids ímpares do pf, o
|
||||||
|
`DIOCGETQSTATS` falhando porque kernel e pfctl discordavam sobre
|
||||||
|
`struct hfsc_class_stats` (ambos agora usam o `net/hfsc.h` vendorizado), o
|
||||||
|
payload do pflog começando no cabeçalho de transporte, e o `splassert` do caminho
|
||||||
|
de purge que transformava uma corrida benigna em `BUG()`.
|
||||||
|
|
||||||
## Desvios da camada de compat (de `docs/porting-notes.md`, registro do M1)
|
## 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
|
Estes são desvios deliberados e marcados no código; importam ao raciocinar
|
||||||
@@ -137,6 +163,34 @@ sobre a semântica das regras:
|
|||||||
kernel. Containers/namespaces não são visíveis ao PF e não podem ser
|
kernel. Containers/namespaces não são visíveis ao PF e não podem ser
|
||||||
filtrados por ele neste design.
|
filtrados por ele neste design.
|
||||||
|
|
||||||
|
## Pipeline dos screencasts (2026-11-12)
|
||||||
|
|
||||||
|
### 10. A geração de vídeo do MiniMax não está habilitada nesta conta
|
||||||
|
|
||||||
|
Todos os modelos de vídeo do MiniMax (`T2V-01`, `T2V-01-Director`,
|
||||||
|
`MiniMax-Hailuo-02`) respondem com o erro de API **2068 — "The current Token
|
||||||
|
Plan tier does not support this model"**. A narração (TTS) e a arte do
|
||||||
|
cartão de título funcionam normalmente; só os modelos de vídeo estão
|
||||||
|
bloqueados. Por isso as transições entre fases são renderizadas localmente
|
||||||
|
com ffmpeg (um crossfade sobre um cartão de capítulo) em vez de clipes
|
||||||
|
gerados por IA. Mudar isso exige só subir o tier da conta — o ponto de
|
||||||
|
extensão é `scripts/screencast/join.py`, que constrói os cartões.
|
||||||
|
|
||||||
|
### 11. A narração é uma trilha separada, não uma dublagem do vídeo
|
||||||
|
|
||||||
|
Cada fase é gravada **uma vez** e serve aos dois idiomas: a sessão de
|
||||||
|
terminal é neutra em relação ao idioma, e as duas versões narradas diferem
|
||||||
|
apenas na trilha de áudio e nas legendas queimadas. O gravador segura cada
|
||||||
|
batida na tela pelo tempo que o segmento de narração mais longo (EN ou PT)
|
||||||
|
precisa, então a voz e a ação no terminal ficam em sincronia nos dois.
|
||||||
|
|
||||||
|
### 12. O que os vídeos não mostram
|
||||||
|
|
||||||
|
Os vídeos são uma demonstração, não uma rodada de teste: a suíte de gates do
|
||||||
|
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.
|
||||||
|
|
||||||
## O que *não* é ressalva
|
## O que *não* é ressalva
|
||||||
|
|
||||||
ARP e outros EtherTypes não-IPv4/IPv6 nunca entram nos hooks netfilter de
|
ARP e outros EtherTypes não-IPv4/IPv6 nunca entram nos hooks netfilter de
|
||||||
|
|||||||
@@ -1,40 +0,0 @@
|
|||||||
# Demo (asciinema)
|
|
||||||
|
|
||||||
O lab pode ser gravado como um cast do [asciinema](https://asciinema.org).
|
|
||||||
Cada byte da gravação é a saída real das VMs do lab (Alpine sobre libvirt,
|
|
||||||
rodando o kernel compilado do fonte com o `pf.ko`); só o ritmo é roteirizado.
|
|
||||||
|
|
||||||
## Gravar
|
|
||||||
|
|
||||||
```sh
|
|
||||||
pip install asciinema # uma vez
|
|
||||||
bash scripts/vm-run-tests.sh # ou: scripts/vm-create.sh + deploy
|
|
||||||
asciinema rec out/demo-pf-linux.cast -c "bash scripts/demo-session.sh"
|
|
||||||
```
|
|
||||||
|
|
||||||
A sessão percorre:
|
|
||||||
|
|
||||||
1. a topologia do lab e o `pf.ko` recém-carregado (prova no `dmesg`),
|
|
||||||
2. `pfctl -s info` pela ABI de ioctl do OpenBSD,
|
|
||||||
3. um `pf.conf` real (tabela, regras stateful, drop explícito) e `pfctl -sr`,
|
|
||||||
4. ping em trânsito + `pfctl -ss` (stateful),
|
|
||||||
5. `pfctl -t <tabela> -T show` (tabelas vivas),
|
|
||||||
6. um ping bloqueado (contadores com `pfctl -sr -vv`),
|
|
||||||
7. `divert-to` com o relay `pdivert` (pacote vai ao userspace e volta),
|
|
||||||
8. desligamento.
|
|
||||||
|
|
||||||
## Reproduzir
|
|
||||||
|
|
||||||
```sh
|
|
||||||
asciinema play out/demo-pf-linux.cast
|
|
||||||
```
|
|
||||||
|
|
||||||
## Publicar
|
|
||||||
|
|
||||||
```sh
|
|
||||||
asciinema upload out/demo-pf-linux.cast
|
|
||||||
```
|
|
||||||
|
|
||||||
`scripts/demo-record.py` é um gravador alternativo que produz o mesmo formato
|
|
||||||
asciicast v2 sem o binário asciinema (ele conduz o lab por ssh e escreve o
|
|
||||||
cast diretamente).
|
|
||||||
@@ -1,78 +1,83 @@
|
|||||||
# Matriz de features
|
# Matriz de features
|
||||||
|
|
||||||
English: [docs/en/feature-matrix.md](../en/feature-matrix.md).
|
Inglês: [docs/en/feature-matrix.md](../en/feature-matrix.md).
|
||||||
|
|
||||||
Vocabulário de status (idêntico nos dois idiomas):
|
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)).
|
- **VERIFIED-in-VM** — exercitado de ponta a ponta no lab libvirt (veja [vm-lab.md](vm-lab.md)); o gate que prova está nomeado.
|
||||||
- **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.
|
- **VERIFIED-offline** — provado por um teste de fixture/ABI, não por tráfego (ex.: `make -C tools test`).
|
||||||
- **COMPILES-untested** — compila em `pf.ko`/`pfctl`; nenhum gate exercitou.
|
- **COMPILES-untested** — compilado dentro do `pf.ko`/`pfctl`; nenhum gate exercitou ainda.
|
||||||
- **PENDING-Mx** — entra no milestone Mx; o comportamento observável hoje está declarado.
|
- **NOT-ENABLED** — o código existe mas a feature está desligada neste build (ex.: `af-to`, que depende dos caminhos `INET6`).
|
||||||
- **OUT-OF-SCOPE** — não-objetivo por projeto (spec §3).
|
- **OUT-OF-SCOPE** — fora de escopo por desenho (spec §3).
|
||||||
|
|
||||||
Estado em 2026-11-11; evidência por linha: `out/test-logs/`,
|
Atualizado em 2026-11-12; fonte da evidência por linha: `out/test-logs/`,
|
||||||
`docs/porting-notes.md`, commits de milestone.
|
`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`).
|
||||||
|
|
||||||
## Plano de controle e ferramentas
|
## Plano de controle e ferramentas
|
||||||
|
|
||||||
| Feature | Sintaxe de upstream | Milestone | Status |
|
| Feature | Sintaxe upstream | Milestone | Status |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| Carga do módulo, `/dev/pf` (0600) | `insmod pf.ko` | M1 | VERIFIED-in-VM |
|
| Carga do módulo, `/dev/pf` (0600) | `insmod pf.ko` | M1 | VERIFIED-in-VM (gate `02-ativacao.sh`) |
|
||||||
| Habilitar/desabilitar | `pfctl -e`, `pfctl -d` | 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) |
|
| Carga de ruleset | `pfctl -f file` | M2 | VERIFIED-in-VM (todo gate carrega um ruleset real) |
|
||||||
| Checagem só de parse | `pfctl -n -f arquivo` | M1 | VERIFIED (suíte de fixtures offline, `make -C tools test`) |
|
| Checagem só de parse | `pfctl -n -f file` | M1 | VERIFIED-offline (`make -C tools test`) |
|
||||||
| Status/contadores | `pfctl -s info [-v]` | M1 | VERIFIED-in-VM |
|
| Status/contadores | `pfctl -s info [-v]` | M1 | VERIFIED-in-VM |
|
||||||
| Listagem de regras | `pfctl -s rules [-v]` | M2 | COMPILES-untested |
|
| Listagem de regras | `pfctl -s rules [-v]`, `pfctl -sr` | M2 | VERIFIED-in-VM (gates 02–06) |
|
||||||
| 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 |
|
| Listagem de estados | `pfctl -s states [-v]`, `pfctl -ss` | M2 | VERIFIED-in-VM (gate `01-state.sh`) |
|
||||||
| Tabelas (listagem/ops) | `pfctl -s tables`, `-t t -T show/add/del/kill` | M2 | M2-in-progress (gate 03) |
|
| Listagem/operações de tabelas | `pfctl -s tables`, `-t t -T show/add/del/kill` | M2 | VERIFIED-in-VM (gate `03-tables.sh`, incluindo um `-T delete` ao vivo liberando o fluxo) |
|
||||||
| Anchors | `pfctl -a anchor -f arquivo`, `-s Anchors` | M2 | M2-in-progress (gate 04) |
|
| Anchors | `pfctl -a anchor -f file`, `-s anchors` | M2 | VERIFIED-in-VM (gate `04-anchors.sh`) |
|
||||||
| 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) |
|
| Listagem de filas | `pfctl -s queue -v` | M5 | VERIFIED-in-VM (gate `15-queue.sh`: as duas classes com estatísticas) |
|
||||||
| 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`) |
|
| Paridade da ABI ioctl | `make -C tools abi` (tabelas do abitest) | M1 | VERIFIED-offline (tabela do userland); o diff do lado do kernel é um passo de integração em aberto (`tools/UPSTREAM.md`) |
|
||||||
|
|
||||||
## Núcleo de filtragem
|
## Núcleo de filtragem
|
||||||
|
|
||||||
| Feature | Sintaxe de upstream | Milestone | Status |
|
| Feature | Sintaxe 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) |
|
| `pass`/`match`, quick, on-interface/proto/addr/port | `pass in quick on wan proto tcp from 10/8 to port 80` | M2 | VERIFIED-in-VM (gate `01-state.sh`: ping de trânsito atravessando o firewall) |
|
||||||
| `block drop` | `block drop in quick …` | M2 | M2-in-progress (gate 02a) |
|
| `block drop` | `block drop in quick …` | M2 | VERIFIED-in-VM (gate `02-block.sh`: o ping morre e os contadores provam) |
|
||||||
| `block return` / `return-rst` / `return-rst after quarter` | `block return-rst in quick proto tcp …` | M2 | M2-in-progress (gate 02b) |
|
| `block return` / `return-rst` / `return-rst after quarter` | `block return-rst in quick proto tcp …` | M2 | VERIFIED-in-VM (gate `02-block.sh`: o `nc` do cliente recebe RST) |
|
||||||
| `keep state` (icmp/tcp/udp), tabela de estados, purger | `… keep state` | M2 | M2-in-progress |
|
| `keep state` (icmp/tcp/udp), tabela de estados, purger | `… keep state` | M2 | VERIFIED-in-VM (gate `01-state.sh`; o estado aparece no `pfctl -ss`) |
|
||||||
| `set state-policy`, `allow-opts` etc. | opções `set …` | M2 | COMPILES-untested |
|
| `set state-policy`, `allow-opts`, etc. | opções `set …` | M2 | COMPILES-untested |
|
||||||
| Syncookies (caminho de estouro de estado) | `set syncookies always` | M2 | COMPILES-untested |
|
| Syncookies (caminho de overflow de estados) | `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) |
|
| Tabelas pfr (radix/hash) | `table <blk> persist { 1.2.3.4 }`, `-T add …` | M2 | VERIFIED-in-VM (gate `03-tables.sh`) |
|
||||||
| Anchors + include + precedência de quick | `anchor "x"`, `load anchor …` | M2 | M2-in-progress (gate 04) |
|
| Anchors + include + precedência de quick | `anchor "x"`, `load anchor …` | M2 | VERIFIED-in-VM (gate `04-anchors.sh`) |
|
||||||
| IPv6 para tudo acima | mesma sintaxe, v6 | M2+ | COMPILES-untested (o engine é completo para v6 por construção; gates v6 smoke planejados) |
|
| IPv6 para tudo acima | mesma sintaxe, v6 | M2+ | COMPILES-untested — o engine é completo para v6 por construção, mas o caminho v6 não é exercitado por gate |
|
||||||
| 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) |
|
| Tráfego não-IPv4/IPv6 (ARP…) | — | M2 | PFLINUX por construção: só os hooks netfilter IPv4/IPv6 — ARP nunca entra no caminho do PF (sem gate) |
|
||||||
|
|
||||||
## NAT / normalização (M3)
|
## NAT / normalização (M3)
|
||||||
|
|
||||||
| Feature | Sintaxe de upstream | Milestone | Status |
|
| Feature | Sintaxe upstream | Milestone | Status |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| `nat-to` (pools de portas, sticky-port, static-port) | `pass out … nat-to (eth1:0)` | M3 | **VERIFICADO em lab** — NAT de origem funciona ponta a ponta: o pacote sai com o endereço do pool (prova via tcpdump), a resposta é des-NATeada e roteada de novo para o cliente, e o estado mostra as duas tuplas (`10.0.2.1:59335 (10.0.1.10:4380) -> 10.0.2.20:8`). Gate: `tests/vm/05-nat.sh`. |
|
| `nat-to` (pools de portas, sticky-port, static-port) | `pass out … nat-to (eth1:0)` | M3 | **VERIFIED-in-VM** — gate `05-nat.sh`: o NAT de origem funciona de ponta a ponta (prova por tcpdump), a resposta é des-NATeada e re-encaminhada para o cliente, e o estado mostra as duas tuplas (`10.0.2.1:59335 (10.0.1.10:4380) -> 10.0.2.20:8`). |
|
||||||
| `rdr-to` | `pass in … to <fw> port 8080 rdr-to <srv> port 80 keep state` | M3 | **VERIFICADO em lab** — o redirect de portas funciona ponta a ponta (gate `06-rdr.sh`): o cliente fala com o endereço do firewall, o pf reescreve o destino, re-rotea o pacote e a resposta é des-redirectuada. O gate também baixa 2 MB pelo redirect e confere a integridade byte a byte (guarda de regressão GSO/TSO). |
|
| `rdr-to` | `pass in … to <fw> port 8080 rdr-to <srv> port 80 keep state` | M3 | **VERIFIED-in-VM** — gate `06-rdr.sh`: o cliente fala com o endereço do firewall, o pf reescreve o destino, re-encaminha o pacote, e a resposta é des-redirecionada. O gate também puxa 2 MB pelo redirect e confere a integridade byte a byte (guarda de regressão GSO/TSO). |
|
||||||
| `binat-to` | `pass quick proto icmp from 10.0.2.20 to 10.0.1.10 binat-to 10.0.2.50 keep state` | M3 | **VERIFICADO em lab** — gate `07-binat.sh`. O pfctl expande uma regra `binat-to` no par documentado (`pass out … nat-to <mapeado> static-port` + `pass in … rdr-to <real>`), e as duas metades funcionam: o peer vê o endereço mapeado, o original não vaza, e a resposta é traduzida de volta. |
|
| `binat-to` | `pass quick proto icmp from 10.0.2.20 to 10.0.1.10 binat-to 10.0.2.50 keep state` | M3 | **VERIFIED-in-VM** — gate `07-binat.sh`. O pfctl expande uma regra `binat-to` no par documentado (`pass out … nat-to <mapped> static-port` + `pass in … rdr-to <real>`), e as duas metades funcionam: o par vê o endereço mapeado, o original nunca vaza, e a resposta é traduzida de volta. |
|
||||||
| Scrub/normalização: remonteagem, `fragment drop-overlap`, `min-ttl`, `max-mss`, `df`/`no-df`, `random-id`, `set reassemble yes` | `match in all scrub (min-ttl 100 max-mss 500)` | M3 | **VERIFICADO em lab** — gate `09-scrub.sh`: `min-ttl` eleva o TTL (o egress mostra 99 após o decremento do encaminhamento) e `max-mss` limita o MSS de um SYN, ambos medidos no egress do próprio firewall. Os caminhos de remonteagem/overlap estão compilados e alcançados, mas ainda sem gate. |
|
| Pools de NAT (`round-robin`, `source-hash`) | `nat-to { 10.0.2.1, 10.0.3.1 } round-robin` | M4 | **VERIFIED-in-VM** — gate `10-pools.sh`, e visível no screencast: o round-robin alterna o endereço de origem entre os membros (3 pacotes cada), o source-hash mantém uma origem em um membro só. |
|
||||||
| 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) |
|
| Scrub/normalize: remontagem, `fragment drop-overlap`, `min-ttl`, `max-mss`, `df`/`no-df`, `random-id`, `set reassemble yes` | `match in all scrub (min-ttl 100 max-mss 500)` | M3 | **VERIFIED-in-VM** — gate `09-scrub.sh`: o `min-ttl` aumenta o TTL (egresso mostra 99 depois do decremento do forward) e o `max-mss` limita a opção MSS do SYN, ambos medidos no egresso do próprio firewall. Os caminhos de remontagem/overlap compilam e são alcançados, mas não têm gate. |
|
||||||
|
| Fingerprint de OS nos estados | `pass … set os A` + `/etc/pf.os` | bonus | COMPILES-untested (`pf_osfp.c` compilado; ainda sem o pacote de fingerprints — a ausência de `/etc/pf.os` é tolerada) |
|
||||||
|
|
||||||
## Roteamento / divert / filas / logging
|
## Roteamento / divert / filas / logging
|
||||||
|
|
||||||
| Feature | Sintaxe de upstream | Milestone | Status |
|
| Feature | Sintaxe upstream | Milestone | Status |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| Pools de `route-to` (`rr`, `round-robin`, `source-hash`, `hash`), `reply-to`, `dup-to` | `pass in … route-to { 10.0.3.2, 10.0.2.2 } round-robin keep state` | M4 | **`round-robin` e `source-hash` VERIFICADOS em lab** (gate `10-pools.sh`): com dois gateways em dois enlaces, o round-robin usa ambos e o source-hash fixa todo fluxo de uma origem em um único enlace. `dup-to` verificado no gate 08; `reply-to` ainda sem gate. `fail-to` **não existe no OpenBSD 8.0** (removido upstream), então está fora de escopo. |
|
| `route-to` / `dup-to` / `reply-to` | `pass in … route-to { 10.0.3.2 } keep state`, `reply-to { gw }` | M4 | **VERIFIED-in-VM** — gates `08-routeto.sh` (o route-to sai pelo link do gateway, o dup-to copia mantendo o fluxo) e `13-replyto.sh` (a resposta de um fluxo é forçada para o link do gateway em vez do cliente). Divergência: o próximo salto forçado é instalado definindo `rt_gw4`/`rt_gw_family` na rota consultada (o Linux só faz ARP do gateway quando a rota declara um). `fail-to` **não é palavra-chave no OpenBSD 8.0** (removido upstream), logo está fora de escopo. |
|
||||||
| `divert-to` / tag `divert` | `pass in … divert-to <host> port <n>` | VERIFICADO em lab | **Funciona ponta a ponta.** O módulo registra em runtime a família de sockets `AF_DIVERT` (`PF_DIVERT=32`) — sem patch no kernel. A regra `divert-to` entrega o pacote a essa família; sem listener o pacote é descartado (`divs_noport`, paridade BSD); com `tools/pdivert` rodando o pacote vai ao userspace, é reescrito e reinjetado (verificado com mutação de payload + correção de checksum, ping 0% loss). |
|
| `divert-to` / tag `divert` | `pass in … divert-to <host> port <n>` | M4 | **VERIFIED-in-VM** — gate `11-divert.sh`. O módulo registra uma família de sockets `AF_DIVERT` (`PF_DIVERT=32`) em runtime — sem patch no kernel. Uma regra `divert-to` entrega o pacote a essa família; sem listener o pacote é descartado (`divs_noport`, paridade com o BSD); com o `tools/pdivert` rodando o pacote vai para o userspace, é reescrito e reinjetado (verificado com mutação do payload + correção de checksum, ping com 0% de perda). |
|
||||||
| 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 |
|
| Filas (`queue`, `queue on`, `pfctl -s queue -v`) | `queue rootq on eth2 bandwidth 10Mbit` + `queue leafq on eth2 bandwidth 2Mbit parent rootq default` | M5 | **VERIFIED-in-VM** — gate `15-queue.sh`: a configuração de filas carrega, a disciplina hfsc é anexada à interface, `pfctl -s queue -v` lista as duas classes com estatísticas, um fluxo de 2 MB pela fila completa, e o contador de pacotes da classe avança (o escalonador está no caminho de dados). Note que `bandwidth` é *linkshare* do HFSC — garantia sob contenção, não teto: o pf.conf do OpenBSD 8.0 não tem `minimum`/`maximum`, então um fluxo isolado não é limitado, nem no Linux nem no OpenBSD. |
|
||||||
| 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 |
|
| HFSC | `queue on wan { hfsc … }` | M5 | **VERIFIED-in-VM** — o `pfsrc/hfsc.c` (código real do OpenBSD 8.0) está na lista de objetos do módulo e é a disciplina que o gate exercita. As classes usam o `net/hfsc.h` vendorizado (classe raiz `0x10000`, classe default `0x1000`) compartilhado com o `pfctl`, então a ABI de estatísticas bate nos dois lados. |
|
||||||
| FQ-CoDel | `queue on wan { fq_codel … }` | M5 | COMPILES-untested — igual ao HFSC: staged, não compilado |
|
| FQ-CoDel | `queue on wan { fq_codel … }` | M5 | COMPILES-untested — o `pfsrc/fq_codel.c` é compilado dentro do `pf.ko` e o `net/fq_codel.h` vendorizado é compartilhado com o `pfctl`, mas nenhum gate o exercita. |
|
||||||
| Qdisc `pfq` (`tc qdisc … root pfq`) | n/a (glue Linux) | M5 | PENDING-M5 (ainda não existe na árvore) |
|
| Interface `pfq` | n/a (interfaces BSD `ifq`/`pfq`, não um qdisc `tc` do Linux) | M5 | **VERIFIED-in-VM** — o port mantém a forma do BSD: `compat/pfk_ifq.c` implementa as interfaces `ifq_ops`/`pfq_ops` e os back ends do OpenBSD se plugam nelas (`pfq` nunca foi um qdisc `tc` do Linux, nem nunca pretendeu ser). priq é a disciplina default. |
|
||||||
| pflog (`log`, `log all`, `block log`) + `pflogd` pcap (DLT_PFLOG) | `pass out quick log` + `pflogd /tmp/x.pcap` | M4 | **VERIFICADO em lab** — gate `12-pflog.sh`: os pacotes logados são enfileirados pelo engine, lidos via `/dev/pflog` e gravados como pcap com linktype DLT_PFLOG (117); o dump contém os bytes reais do pacote. Desvio: um chardev em vez de interface if_pflog/bpf (Linux não tem bpf sobre essa interface virtual aqui); o layout do registro é byte-compatível com `pflog(4)` do OpenBSD. `if_pflog.c` permanece fora do build. |
|
| pflog (`log`, `log all`, `block log`) + pcap do `pflogd` (DLT_PFLOG) | `pass out quick log` + `pflogd /tmp/x.pcap` | M4 | **VERIFIED-in-VM** — gate `12-pflog.sh`: os pacotes logados são enfileirados pelo engine, lidos por `/dev/pflog` e escritos como pcap com linktype DLT_PFLOG (117); o dump contém os bytes reais do pacote. Divergência: um chardev em vez de uma interface if_pflog/bpf (o Linux não tem bpf sobre uma interface virtual dessas aqui); o layout do registro é byte a byte compatível com o `pflog(4)` do OpenBSD. O `if_pflog.c` continua fora da build. |
|
||||||
|
|
||||||
## Não-objetivos (explícitos)
|
## Fora de escopo (explícito)
|
||||||
|
|
||||||
| Item | Motivo |
|
| Item | Motivo |
|
||||||
|---|---|
|
|---|---|
|
||||||
| Failover `pfsync` / CARP | não-objetivo (spec §3) |
|
| Failover `pfsync` / CARP | fora de escopo (spec §3) |
|
||||||
| IPsec / pfkey | não-objetivo |
|
| IPsec / pfkey | fora de escopo |
|
||||||
| `ftp-proxy` e daemons de userland | não-objetivo |
|
| `ftp-proxy` e daemons de userland | fora de escopo |
|
||||||
| Instâncias PF por netns | port fiel = instância global única em `init_net` |
|
| Instâncias PF por netns | port fiel = uma instância global no `init_net` |
|
||||||
| Agendadores exclusivos do ALTQ clássico (cbq/wfq/red…) | removidos do upstream antes do OpenBSD 8.0 — impossível portar da base |
|
| Agendadores exclusivos do ALTQ clássico (cbq/wfq/red…) | removidos upstream antes do OpenBSD 8.0 — impossíveis de portar a partir da base |
|
||||||
| Labels de rota (`route-label`) | não-objetivo; a camada de ioctl rejeita regras com label |
|
| Routing labels (`route-label`) | fora de escopo; a camada de ioctl rejeita regras com label |
|
||||||
|
| `af-to` (tradução IPv4↔IPv6) | o código está sob `#ifdef INET6` e este build tem `INET6 0`; habilitá-lo é um milestone à parte, não uma feature faltando na base |
|
||||||
+25
-17
@@ -21,7 +21,7 @@ bit a bit entre kernel e userland (codificação de ioctl em
|
|||||||
|
|
||||||
| Caminho | Papel |
|
| 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/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`, `hfsc.c`/`fq_codel.c` (back ends de fila, no módulo), `divert_sock.c` (família AF_DIVERT), `pfk_pflog.c` (back end do pflog), `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/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` |
|
| `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` |
|
| `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` |
|
||||||
@@ -71,31 +71,39 @@ tamanho embutido na codificação `_IOR/_IOW` OpenBSD e despacha para o
|
|||||||
|
|
||||||
## Filas, divert, pflog — comportamento observável hoje
|
## Filas, divert, pflog — comportamento observável hoje
|
||||||
|
|
||||||
- **Filas**: o `pfctl` faz o parse de `queue`/`queue on` (sintaxe 8.0); no
|
- **Filas**: o `pfctl` faz o parse da sintaxe `queue`/`queue on` do OpenBSD 8.0
|
||||||
kernel a API ifq é implementada por `compat/pfk_ifq.c` com **semântica
|
e o kernel a implementa na forma do BSD: `compat/pfk_ifq.c` fornece as
|
||||||
priq apenas**; o qdisc classful `pfq` e os back ends HFSC/FQ-CoDel chegam
|
interfaces `ifq_ops`/`pfq_ops` e os back ends do OpenBSD (`pfsrc/hfsc.c`,
|
||||||
em **M5**. `ifq.c`/`hfsc.c`/`fq_codel.c` estão staged e prontos para
|
`pfsrc/fq_codel.c`) se plugam nelas; **priq** é a disciplina default. O
|
||||||
compilar, mas ainda **não** estão na lista de objetos do módulo.
|
gate `15-queue.sh` prova a disciplina **hfsc** de ponta a ponta: a
|
||||||
- **Divert**: `divert-to`/tag `divert` produzem o veredito `PF_DIVERT` do PF;
|
configuração de filas carrega, a disciplina é anexada à interface, o
|
||||||
`divert_packet()` (`pfsrc/divert_sock.c`) hoje **descarta o pacote com um
|
`pfctl -s queue -v` lista as duas classes com estatísticas, um fluxo de 2 MB
|
||||||
`pr_warn` rate-limited**, porque a família de sockets AF_DIVERT ainda não
|
pela fila completa e o contador de pacotes da classe avança. Os handles de
|
||||||
foi instanciada. O relay userspace `pdivert` existe e espera
|
classe e as structs de estatísticas vêm do próprio `net/hfsc.h` e
|
||||||
`socket(PF_DIVERT=32, SOCK_RAW, IPPROTO_DIVERT=254)`.
|
`net/fq_codel.h` do OpenBSD, vendorizados para que kernel e `pfctl` não
|
||||||
- **pflog**: `pflog_packet()` retorna sucesso mas **não loga nada** (um
|
puedan divergir.
|
||||||
único `pr_warn "packet not logged"`); `if_pflog.c` está staged, back end
|
- **Divert**: `divert-to`/tag `divert` produzem o veredito `PF_DIVERT` do PF e
|
||||||
netlink em M4/M5.
|
o módulo registra uma família de sockets `AF_DIVERT` (`PF_DIVERT=32`) em
|
||||||
|
runtime — sem patch no kernel. Sem listener o pacote é descartado
|
||||||
|
(`divs_noport`, paridade com o BSD); com o relay `pdivert` rodando o pacote
|
||||||
|
vai para o userspace, é reescrito e reinjetado. Gate `11-divert.sh`.
|
||||||
|
- **pflog**: o engine enfileira os pacotes logados, `pfsrc/pfk_pflog.c` os
|
||||||
|
expõe por `/dev/pflog` e o `tools/pflogd` escreve um pcap DLT_PFLOG cujo
|
||||||
|
layout de registro é byte a byte compatível com o `pflog(4)` do OpenBSD.
|
||||||
|
Gate `12-pflog.sh`. A divergência em relação ao upstream é o transporte (um
|
||||||
|
chardev em vez de uma interface `if_pflog`/bpf), não o formato.
|
||||||
|
|
||||||
## Milestones (status por feature em feature-matrix.md)
|
## Milestones (status por feature em feature-matrix.md)
|
||||||
|
|
||||||
| Milestone | Escopo | Status (2026-11-11) |
|
| Milestone | Escopo | Status |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| M0 | repo, scripts, build do kernel, VM liga com kernel próprio | concluído |
|
| 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 |
|
| M1 | camada de compat, `pf.ko` carrega, `/dev/pf`, `pfctl -f`/`-s info` | concluído |
|
||||||
| M2 | núcleo do filtro: regras/estados/tabelas/âncoras, gates VM | **pronto** — gates 01–04 verdes |
|
| M2 | núcleo do filtro: regras/estados/tabelas/âncoras, gates VM | **pronto** — gates 01–04 verdes |
|
||||||
| 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 |
|
| 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 |
|
| 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) + qdisc `pfq` | pendente (código dos escalonadores portado, qdisc faltando) |
|
| 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 | em andamento — docs EN+PT-BR completas, suíte 14/14 verde, com tag |
|
| 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` |
|
||||||
|
|
||||||
## Próximas leituras
|
## Próximas leituras
|
||||||
|
|
||||||
|
|||||||
+24
-10
@@ -75,22 +75,36 @@ Como a entrega é um módulo out-of-tree, "aplicar o conjunto" = passos
|
|||||||
milestone acrescentar `patches/kernel/000N-*.patch` documentado (cada patch:
|
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).
|
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)
|
## 6. Notas sobre os passos acima (2026-11-12)
|
||||||
|
|
||||||
- O passo **2.3 é um no-op documentado hoje**: o conjunto de patches está
|
- 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
|
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
|
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
|
com o primeiro patch real de core — ver [patches.md](patches.md)). Pode
|
||||||
pular 2.3 com segurança.
|
pular 2.3 com segurança.
|
||||||
- O passo 2.4 compila apenas o `pfctl` e o verificador de ABI (alvos de
|
- O passo 2.4 compila todas as ferramentas de userland: os alvos de
|
||||||
`make -C tools`: `pfctl abitest abi test clean`). `pdivert` existe como
|
`make -C tools` são `pfctl`, `pdivert`, `pflogd` e o verificador de ABI
|
||||||
arquivo-fonte sem alvo no Makefile por enquanto; `pflogd` ainda não foi
|
(`abitest`, `abi`, `test`, `clean`).
|
||||||
portado.
|
- Depois do 2.5, todo o conjunto de features é usável e coberto pela suíte do
|
||||||
- Depois do 2.5, o que dá para usar de verdade é o núcleo de filtragem M2
|
lab — filtragem com estado, tabelas e anchors, NAT (`nat-to`/`rdr-to`/
|
||||||
(pass/block/quick/keep state/tabelas/anchors); NAT/scrub/queues/divert/
|
`binat-to` e pools), scrub, roteamento forçado (`route-to`/`dup-to`/
|
||||||
pflog dependem dos próximos milestones — consulte a
|
`reply-to`), `divert`, `pflog` e as filas do OpenBSD 8.0 (HFSC). A
|
||||||
[matriz de features](feature-matrix.md) e leia [pf-guide.md](pf-guide.md)
|
[matriz de features](feature-matrix.md) diz qual gate prova o quê; leia
|
||||||
antes de habilitar PF num caminho que você precisa manter vivo.
|
[pf-guide.md](pf-guide.md) para conhecer as divergências antes de habilitar
|
||||||
|
o PF num caminho que você precisa manter vivo.
|
||||||
|
|
||||||
|
## 6.1 Opcional: os screencasts narrados
|
||||||
|
|
||||||
|
```sh
|
||||||
|
export MINIMAX_API_KEY=... # TTS do MiniMax para a narração
|
||||||
|
./scripts/vm-create.sh # o lab precisa estar no ar
|
||||||
|
./scripts/screencast/build-all.sh # -> out/screencast/{en,pt}/*.mp4
|
||||||
|
```
|
||||||
|
|
||||||
|
O pipeline grava sessões shell reais contra o lab, gera a narração com uma
|
||||||
|
voz feminina do MiniMax (inglês e português) e renderiza MP4 1080p30 com
|
||||||
|
legendas queimadas e o diagrama de rede do lab. Detalhes:
|
||||||
|
[screencast.md](screencast.md).
|
||||||
|
|
||||||
## 7. Para onde ir agora
|
## 7. Para onde ir agora
|
||||||
|
|
||||||
|
|||||||
+15
-10
@@ -3,11 +3,12 @@
|
|||||||
English: [docs/en/pf-guide.md](../en/pf-guide.md).
|
English: [docs/en/pf-guide.md](../en/pf-guide.md).
|
||||||
|
|
||||||
Este é o guia do "o que dá para rodar hoje". Ele segue a
|
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
|
[matriz de features](feature-matrix.md): o núcleo de filtragem
|
||||||
filtragem: pass/block/quick/keep state/tabelas/anchors + habilitar/
|
(pass/block/quick/keep state/tabelas/anchors), a família de NAT, o scrub, o
|
||||||
desabilitar)** tem evidência em VM; tudo além disso é COMPILES-untested ou
|
roteamento forçado, os pools, o `divert`, o `pflog` e as filas do OpenBSD
|
||||||
PENDING. Instalação/build ficam em [install.md](install.md) e
|
8.0 têm todos **evidência em VM** — os 15 gates do lab estão verdes. O que
|
||||||
[build.md](build.md).
|
*não* tem gate está listado explicitamente abaixo. Instalação/build ficam em
|
||||||
|
[install.md](install.md) e [build.md](build.md).
|
||||||
|
|
||||||
## Ciclo diário
|
## Ciclo diário
|
||||||
|
|
||||||
@@ -26,7 +27,7 @@ sudo rmmod pf # descarrega (regras/estados são liberados aq
|
|||||||
|
|
||||||
> **Atenção — a ordem importa**: `-f` e depois `-e`. Habilitar sem uma regra `pass`
|
> **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
|
> 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
|
> a política padrão de bloqueio do OpenBSD, e as rodadas do lab
|
||||||
> *realmente derrubaram o ssh da VM de teste* assim (a bancada mantém console
|
> *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
|
> serial exatamente por isso). Num host remoto, libere a interface de
|
||||||
> gerência antes de habilitar qualquer coisa.
|
> gerência antes de habilitar qualquer coisa.
|
||||||
@@ -34,8 +35,8 @@ sudo rmmod pf # descarrega (regras/estados são liberados aq
|
|||||||
## Um ruleset que funciona neste port hoje
|
## Um ruleset que funciona neste port hoje
|
||||||
|
|
||||||
```pf
|
```pf
|
||||||
# /etc/pf.conf — apenas escopo M2. Os nomes de interface aqui são os da
|
# /etc/pf.conf. Os nomes de interface aqui são os da bancada
|
||||||
# bancada (mgmt/lan/wan); troque pelos seus (eth0, enp1s0, …).
|
# (mgmt/lan/wan); troque pelos seus (eth0, enp1s0, …).
|
||||||
|
|
||||||
# 1) mantenha você mesmo alcançável (faça isso primeiro, sempre)
|
# 1) mantenha você mesmo alcançável (faça isso primeiro, sempre)
|
||||||
pass in quick on mgmt
|
pass in quick on mgmt
|
||||||
@@ -59,14 +60,18 @@ 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
|
`set` ainda não foi verificado em gate; trate como "carrega e reza" até a
|
||||||
matriz mudar.
|
matriz mudar.
|
||||||
|
|
||||||
## O que evitar até os milestones chegarem
|
**Sem gate** (lacunas honestas): o caminho IPv6, o fingerprint de OS, as
|
||||||
|
opções `set` acima, os syncookies e a disciplina FQ-CoDel. O `af-to` está
|
||||||
|
compilado fora neste build (`INET6 0`).
|
||||||
|
|
||||||
|
## Notas por feature — o que cada sintaxe faz hoje
|
||||||
|
|
||||||
| Sintaxe | Até | O que acontece hoje |
|
| Sintaxe | Até | O que acontece hoje |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `nat-to`, `rdr-to` | — | **Verificado em lab** (gates `05-nat`, `06-rdr`; este último baixa 2 MB por um redirect e compara byte a byte). `nat-to (egress)` expande o *grupo* de interfaces do OpenBSD; num host multi-NIC prefira a forma explícita `nat-to (<iface>)`. O `binat-to` é expandido pelo pfctl num par `nat-to … static-port` + `rdr-to` (gate `07-binat`). |
|
| `nat-to`, `rdr-to` | — | **Verificado em lab** (gates `05-nat`, `06-rdr`; este último baixa 2 MB por um redirect e compara byte a byte). `nat-to (egress)` expande o *grupo* de interfaces do OpenBSD; num host multi-NIC prefira a forma explícita `nat-to (<iface>)`. O `binat-to` é expandido pelo pfctl num par `nat-to … static-port` + `rdr-to` (gate `07-binat`). |
|
||||||
| `scrub (min-ttl, max-mss, df, random-id)`, `set reassemble` | — | **Verificado em lab** (gate `09-scrub.sh`): `min-ttl` e `max-mss` reescrevem de fato pacotes em trânsito. || `divert-to`, tag `divert` | — | **Verificado em lab** (gate `11-divert`): o pacote é entregue a um socket `AF_DIVERT` (`PF_DIVERT=32`) atendido pelo `tools/pdivert`; sem listener é descartado, exatamente como o `divs_noport` do BSD. |
|
| `scrub (min-ttl, max-mss, df, random-id)`, `set reassemble` | — | **Verificado em lab** (gate `09-scrub.sh`): `min-ttl` e `max-mss` reescrevem de fato pacotes em trânsito. || `divert-to`, tag `divert` | — | **Verificado em lab** (gate `11-divert`): o pacote é entregue a um socket `AF_DIVERT` (`PF_DIVERT=32`) atendido pelo `tools/pdivert`; sem listener é descartado, exatamente como o `divs_noport` do BSD. |
|
||||||
| `route-to`, `dup-to`, `reply-to` | — | **Verificado em lab** (gates `08-routeto.sh`, `13-replyto.sh`): o gateway forçado é usado em vez do egress normal, `dup-to` copia mantendo o fluxo, e `reply-to` empurra as respostas de um fluxo para o enlace do gateway. `fail-to` não é palavra-chave no OpenBSD 8.0 (removido upstream). |
|
| `route-to`, `dup-to`, `reply-to` | — | **Verificado em lab** (gates `08-routeto.sh`, `13-replyto.sh`): o gateway forçado é usado em vez do egress normal, `dup-to` copia mantendo o fluxo, e `reply-to` empurra as respostas de um fluxo para o enlace do gateway. `fail-to` não é palavra-chave no OpenBSD 8.0 (removido upstream). |
|
||||||
| `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**. |
|
| `queue` / `queue on` / `pfctl -s queue` | — | **Verificado em lab** (gate `15-queue.sh`): as filas carregam, o hfsc é anexado, `pfctl -s queue -v` mostra as estatísticas das classes e um fluxo de 2 MB pela fila é contado pela classe. `bandwidth` é *garantia sob contenção*, não teto - o pf.conf 8.0 não tem palavra-chave de upperlimit. |
|
||||||
| `log`, `log all`, `block log`, `pflogd` | — | **Verificado em lab** (gate `12-pflog`): rode `pflogd /tmp/x.pcap` (lê `/dev/pflog`); o arquivo é um pcap `DLT_PFLOG` (117) legível por tcpdump/wireshark. |
|
| `log`, `log all`, `block log`, `pflogd` | — | **Verificado em lab** (gate `12-pflog`): rode `pflogd /tmp/x.pcap` (lê `/dev/pflog`); o arquivo é um pcap `DLT_PFLOG` (117) legível por tcpdump/wireshark. |
|
||||||
| `route-label …` | nunca (não-objetivo) | Rejeitado na camada de ioctl. |
|
| `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. |
|
| política por container (netns) | fora de escopo | O PF é uma instância global em `init_net`; network namespaces **não** enxergam o PF. |
|
||||||
|
|||||||
@@ -0,0 +1,123 @@
|
|||||||
|
# Screencasts / Vídeos da demonstração
|
||||||
|
|
||||||
|
> PT-BR: esta página · EN: [docs/en/screencast.md](../en/screencast.md)
|
||||||
|
|
||||||
|
Sete vídeos por idioma, com **narração de voz feminina** (MiniMax), **legendas
|
||||||
|
queimadas**, **diagrama de rede do lab** e um **filme completo** com transições
|
||||||
|
entre as fases. 1920×1080 @ 30fps, H.264/MP4 + AAC — prontos para subir no
|
||||||
|
YouTube.
|
||||||
|
|
||||||
|
Cada vídeo é a gravação de uma **sessão shell real**: uma conexão SSH de
|
||||||
|
verdade ao firewall Alpine, com os comandos digitados tecla a tecla e a saída
|
||||||
|
do kernel chegando em tempo real. Nada é simulado.
|
||||||
|
|
||||||
|
## As fases
|
||||||
|
|
||||||
|
| # | Vídeo | O que mostra |
|
||||||
|
|---|-------|--------------|
|
||||||
|
| 0 | `00-ambiente.mp4` | O lab: 4 VMs Alpine sob libvirt, as quatro interfaces do firewall e o diagrama de rede completo |
|
||||||
|
| 1 | `01-implementacao.mp4` | A implementação no kernel: fontes do OpenBSD 8.0 + camada de compatibilidade = `pf.ko` |
|
||||||
|
| 2 | `02-ativacao.mp4` | `insmod pf.ko`, `/dev/pf`, `pfctl -e`, `pfctl -s info` |
|
||||||
|
| 3 | `03-regras.mp4` | Regras básicas: tabela, anchor, `keep state`, `block drop` (o ping morre) |
|
||||||
|
| 4 | `04-roteamento.mp4` | `route-to` e `reply-to`, com o diagrama mostrando o caminho real do pacote |
|
||||||
|
| 5 | `05-balanceamento.mp4` | Pool `round-robin`, pool `source-hash` e `dup-to` |
|
||||||
|
| 6 | `06-qos.mp4` | Filas `hfsc` do OpenBSD 8.0 e uma transferência de 2 MB pela classe |
|
||||||
|
|
||||||
|
Mais `pf-on-linux-full.mp4`: o filme completo, com as sete fases emendadas por
|
||||||
|
transições (crossfade sobre um cartão de capítulo).
|
||||||
|
|
||||||
|
## Vozes MiniMax
|
||||||
|
|
||||||
|
| Idioma | Voz | Id |
|
||||||
|
|--------|-----|-----|
|
||||||
|
| Inglês (en_US) | Confident Woman | `English_ConfidentWoman` |
|
||||||
|
| Português (pt_BR) | Confident Woman | `Portuguese_ConfidentWoman` |
|
||||||
|
|
||||||
|
O áudio é gerado pelo MiniMax TTS, **um arquivo por segmento** de fala. Como
|
||||||
|
cada segmento corresponde a uma batida da sessão, a voz começa exatamente
|
||||||
|
quando a ação correspondente aparece na tela.
|
||||||
|
|
||||||
|
## Como regravar
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# tudo: áudio -> sessões -> vídeos -> filme completo
|
||||||
|
./scripts/screencast/build-all.sh
|
||||||
|
|
||||||
|
# uma fase só
|
||||||
|
./scripts/screencast/build-all.sh 04-roteamento
|
||||||
|
```
|
||||||
|
|
||||||
|
Etapas individuais:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
python3 scripts/screencast/make_audio.py --lang pt # áudio MiniMax
|
||||||
|
python3 scripts/screencast/record.py --plan scripts/screencast/plans/04-roteamento.json \\
|
||||||
|
--out out/screencast/caps/04-roteamento.cast --pace --ensure-pf
|
||||||
|
python3 scripts/screencast/assemble.py --phase 04-roteamento --lang pt
|
||||||
|
python3 scripts/screencast/join.py --lang pt
|
||||||
|
```
|
||||||
|
|
||||||
|
### Como o pipeline funciona
|
||||||
|
|
||||||
|
```
|
||||||
|
planos/*.json --record.py--> caps/*.cast --render_video.py--> vídeo mudo
|
||||||
|
(comandos) (pty real) (asciicast) (pyte + PIL) |
|
||||||
|
|
|
||||||
|
narracao/*.json --make_audio.py--> vo/<fase>.<lang>/*.mp3 ----------+
|
||||||
|
(MiniMax TTS) |
|
||||||
|
assemble.py: mix + legendas
|
||||||
|
|
|
||||||
|
join.py: transições
|
||||||
|
```
|
||||||
|
|
||||||
|
Pontos que importam:
|
||||||
|
|
||||||
|
- **Sessão real.** `record.py` abre um pty, faz SSH na VM e digita os comandos
|
||||||
|
um caractere por vez (~34 c/s, com jitter). O `bash --noediting` evita que o
|
||||||
|
editor de linha redesenhe o comando e o tty o repita.
|
||||||
|
- **Pacing.** A gravação segura a tela enquanto a voz fala: cada batida dura no
|
||||||
|
máximo o trecho de áudio que a acompanha (o maior entre EN e PT), para uma
|
||||||
|
única captura servir as duas versões.
|
||||||
|
- **Legendas.** O texto da narração é queimado no vídeo, na língua da trilha.
|
||||||
|
- **Diagrama.** Nas fases 0, 4 e 5 o mapa do lab é desenhado sobre o terminal,
|
||||||
|
e o caminho em discussão fica destacado (eth1/eth2/eth3).
|
||||||
|
|
||||||
|
Requisitos: `ffmpeg`, `python3` com `PIL` e `pyte`, a fonte DejaVu, a chave
|
||||||
|
`MINIMAX_API_KEY` no ambiente, e o lab de VMs no ar.
|
||||||
|
|
||||||
|
## Onde ficam os arquivos
|
||||||
|
|
||||||
|
| Caminho | Conteúdo |
|
||||||
|
|---------|----------|
|
||||||
|
| `out/screencast/en/`, `out/screencast/pt/` | os vídeos finais |
|
||||||
|
| `out/screencast/caps/` | as capturas asciicast das sessões |
|
||||||
|
| `out/screencast/vo/` | o áudio gerado pelo MiniMax |
|
||||||
|
| `scripts/screencast/plans/` | o roteiro de comandos de cada fase |
|
||||||
|
| `scripts/screencast/narration/` | os textos da narração (EN e PT) |
|
||||||
|
|
||||||
|
## Texto de descrição (YouTube)
|
||||||
|
|
||||||
|
```
|
||||||
|
PF on Linux - o motor do OpenBSD PF 8.0 dentro do kernel Linux
|
||||||
|
|
||||||
|
Port completo do Packet Filter do OpenBSD 8.0 para o kernel Linux 7.3.4,
|
||||||
|
empacotado como módulo (pf.ko) e controlado pelo pfctl original do OpenBSD
|
||||||
|
sobre a ABI de ioctls do /dev/pf.
|
||||||
|
|
||||||
|
O lab é real: 4 VMs Alpine sob libvirt, kernel compilado do fonte com o
|
||||||
|
módulo, tráfego real entre cliente, firewall e servidor.
|
||||||
|
|
||||||
|
Fases:
|
||||||
|
0. O ambiente de testes - as 4 VMs e o diagrama de rede
|
||||||
|
1. Implementação no kernel - das fontes OpenBSD ao pf.ko
|
||||||
|
2. Ativação - insmod, /dev/pf, pfctl -e, pfctl -s info
|
||||||
|
3. Regras básicas - tabelas, anchors, keep state, block drop
|
||||||
|
4. Roteamento - route-to e reply-to, com o caminho real no diagrama
|
||||||
|
5. Balanceamento - pools round-robin, source-hash e dup-to
|
||||||
|
6. QOS - filas hfsc do OpenBSD 8.0 no caminho de dados
|
||||||
|
|
||||||
|
Suíte automatizada: 15 gates, todos verdes.
|
||||||
|
Código e documentação: https://git.labexposed.com/lgcosta/pf-linux-port
|
||||||
|
|
||||||
|
#linux #kernel #openbsd #pf #firewall #netfilter #screencast
|
||||||
|
```
|
||||||
+62
-22
@@ -65,6 +65,21 @@ 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)
|
ligadas. Os aliases de ssh do laptop (`pf0|cli|srv|gw2`, ProxyJump via deb)
|
||||||
são gerados em `~/.ssh/config.d/pflab`.
|
são gerados em `~/.ssh/config.d/pflab`.
|
||||||
|
|
||||||
|
**Do pf0 para as outras VMs.** O firewall tem seu próprio par de chaves e os
|
||||||
|
pares confiam nele, então uma sessão no pf0 pode pilotar o cliente e o
|
||||||
|
servidor diretamente — é isso que os screencasts fazem
|
||||||
|
(`ssh cli 'ping …'`):
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# uma vez por lab, a partir do hypervisor
|
||||||
|
ssh pf0 'ssh-keygen -t ed25519 -N "" -C pf0 -f /root/.ssh/id_ed25519'
|
||||||
|
# depois autorizar essa chave pública no cli, no srv e no gw2
|
||||||
|
```
|
||||||
|
|
||||||
|
O `/root/.ssh/config` do pf0 mapeia os nomes curtos `cli`, `srv` e `gw2`
|
||||||
|
para 10.0.1.10, 10.0.2.20 e 10.0.3.2, e o `/etc/hosts` traz o mesmo
|
||||||
|
mapeamento.
|
||||||
|
|
||||||
## 4. Rodando a suíte
|
## 4. Rodando a suíte
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
@@ -82,30 +97,55 @@ e sai com código diferente de zero se algo falhou.
|
|||||||
|
|
||||||
## 5. O que cada gate prova hoje
|
## 5. O que cada gate prova hoje
|
||||||
|
|
||||||
| Gate | Cenário (verificado com tráfego real, não com código de saída) |
|
Cada gate afirma **tráfego real e estado observável**, nunca apenas um código
|
||||||
|
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
|
||||||
|
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
|
||||||
|
guarda `out/test-logs/<gate>.sh.log`.
|
||||||
|
|
||||||
|
O que **não** tem gate (lacunas honestas): o caminho IPv6, o fingerprint de
|
||||||
|
OS, as opções `set` (`state-policy`, `allow-opts`) e os syncookies — o código
|
||||||
|
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.
|
||||||
|
|
||||||
|
## 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
|
||||||
|
de cada gate, reimplanta o módulo e as ferramentas, e termina com
|
||||||
|
`PASS=15 FAIL=0`. Os logs capturados ficam em
|
||||||
|
`out/test-logs/<gate>.sh.log`.
|
||||||
|
|
||||||
|
| Gate | O que prova hoje |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `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` |
|
| `01-state.sh` | `keep state` em um ping de trânsito; o estado aparece no `pfctl -ss` |
|
||||||
| `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 |
|
| `02-block.sh` | `block drop` mata o ping; `block return-rst` devolve RST ao cliente |
|
||||||
| `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 |
|
| `03-tables.sh` | uma tabela `pfr` bloqueia um host; um `-T delete` ao vivo libera o mesmo 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 |
|
| `04-anchors.sh` | a regra de um anchor é avaliada antes de um `block` posterior do ruleset principal |
|
||||||
|
| `05-nat.sh` | NAT de origem: o pacote sai com o endereço do pool e a resposta é des-NATeada |
|
||||||
|
| `06-rdr.sh` | redirect de porta de ponta a ponta, mais 2 MB passando com integridade byte a byte |
|
||||||
|
| `07-binat.sh` | `binat-to`: o par vê o endereço mapeado e o original nunca vaza |
|
||||||
|
| `08-routeto.sh` | `route-to` sai pelo link do gateway; `dup-to` copia e mantém o fluxo |
|
||||||
|
| `09-scrub.sh` | `min-ttl` e `max-mss` medidos no egresso do próprio firewall |
|
||||||
|
| `10-pools.sh` | seleção do pool de NAT (`round-robin`, `source-hash`) |
|
||||||
|
| `11-divert.sh` | `divert-to` para o relay `pdivert`: o pacote vai para o userspace e volta |
|
||||||
|
| `12-pflog.sh` | os pacotes logados chegam a `/dev/pflog` e viram um pcap DLT_PFLOG |
|
||||||
|
| `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 |
|
||||||
|
|
||||||
Gates planejados (documento de plano, T11–T18): 05-nat, 06-rdr, 07-binat,
|
Os screencasts ([screencast.md](screencast.md)) mostram boa parte desses
|
||||||
08-scrub (scapy), 09-routeto, 10-dup-fail, 11-divert, 12-queues (tolerância
|
gates sendo executados, com o diagrama de rede do lab desenhado sobre o
|
||||||
de ±15% no rate do iperf), 13-pflog, 14-soak (reload sob tráfego + ciclo de
|
terminal.
|
||||||
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
|
## 7. Inspeção manual
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1 +1 @@
|
|||||||
savedcmd_pf.mod := printf '%s\n' pfsrc/pfk_mod.o pfsrc/pfk_hooks.o compat/pfk_mbuf.o compat/pfk_kern.o compat/pfk_if.o compat/pfk_xmit.o compat/pfk_pcb.o compat/pfk_ip4.o compat/siphash.o compat/toeplitz.o compat/md5.o compat/sha2.o compat/radix.o compat/pfk_rtable.o compat/pfk_ip6.o compat/subr_tree.o pfsrc/pf_ruleset.o pfsrc/pf_table.o pfsrc/pf_norm.o pfsrc/pf_if.o pfsrc/pf_lb.o pfsrc/pf_osfp.o pfsrc/pf_syncookies.o pfsrc/pf_ioctl.o pfsrc/pf.o pfsrc/divert_sock.o pfsrc/pfk_pflog.o compat/pfk_ifq.o | awk '!x[$$0]++ { print("./"$$0) }' > pf.mod
|
savedcmd_pf.mod := printf '%s\n' pfsrc/pfk_mod.o pfsrc/pfk_hooks.o compat/pfk_mbuf.o compat/pfk_kern.o compat/pfk_if.o compat/pfk_xmit.o compat/pfk_pcb.o compat/pfk_ip4.o compat/siphash.o compat/toeplitz.o compat/md5.o compat/sha2.o compat/radix.o compat/pfk_rtable.o compat/pfk_ip6.o compat/subr_tree.o pfsrc/pf_ruleset.o pfsrc/pf_table.o pfsrc/pf_norm.o pfsrc/pf_if.o pfsrc/pf_lb.o pfsrc/pf_osfp.o pfsrc/pf_syncookies.o pfsrc/pf_ioctl.o pfsrc/pf.o pfsrc/divert_sock.o pfsrc/pfk_pflog.o pfsrc/fq_codel.o pfsrc/hfsc.o compat/pfk_ifq.o | awk '!x[$$0]++ { print("./"$$0) }' > pf.mod
|
||||||
+1
-1
@@ -20,7 +20,7 @@ pf-objs := pfsrc/pfk_mod.o pfsrc/pfk_hooks.o \
|
|||||||
# ported OpenBSD 8.0 engine
|
# ported OpenBSD 8.0 engine
|
||||||
pf-objs += pfsrc/pf_ruleset.o pfsrc/pf_table.o pfsrc/pf_norm.o pfsrc/pf_if.o \
|
pf-objs += pfsrc/pf_ruleset.o pfsrc/pf_table.o pfsrc/pf_norm.o pfsrc/pf_if.o \
|
||||||
pfsrc/pf_lb.o pfsrc/pf_osfp.o pfsrc/pf_syncookies.o \
|
pfsrc/pf_lb.o pfsrc/pf_osfp.o pfsrc/pf_syncookies.o \
|
||||||
pfsrc/pf_ioctl.o pfsrc/pf.o pfsrc/divert_sock.o pfsrc/pfk_pflog.o
|
pfsrc/pf_ioctl.o pfsrc/pf.o pfsrc/divert_sock.o pfsrc/pfk_pflog.o pfsrc/fq_codel.o pfsrc/hfsc.o
|
||||||
|
|
||||||
# queueing framework (compat/pfk_ifq.c implements the ifq API with priq
|
# queueing framework (compat/pfk_ifq.c implements the ifq API with priq
|
||||||
# semantics; the OpenBSD hfsc/fq_codel back ends land in milestone M5)
|
# semantics; the OpenBSD hfsc/fq_codel back ends land in milestone M5)
|
||||||
|
|||||||
@@ -1828,6 +1828,8 @@ deps_compat/md5.o := \
|
|||||||
include/compat/sys/rwlock.h \
|
include/compat/sys/rwlock.h \
|
||||||
include/compat/sys/mutex.h \
|
include/compat/sys/mutex.h \
|
||||||
include/compat/sys/pclock.h \
|
include/compat/sys/pclock.h \
|
||||||
|
include/net/hfsc.h \
|
||||||
|
include/net/fq_codel.h \
|
||||||
|
|
||||||
compat/md5.o: $(deps_compat/md5.o)
|
compat/md5.o: $(deps_compat/md5.o)
|
||||||
|
|
||||||
|
|||||||
@@ -1828,6 +1828,8 @@ deps_compat/pfk_if.o := \
|
|||||||
include/compat/sys/rwlock.h \
|
include/compat/sys/rwlock.h \
|
||||||
include/compat/sys/mutex.h \
|
include/compat/sys/mutex.h \
|
||||||
include/compat/sys/pclock.h \
|
include/compat/sys/pclock.h \
|
||||||
|
include/net/hfsc.h \
|
||||||
|
include/net/fq_codel.h \
|
||||||
|
|
||||||
compat/pfk_if.o: $(deps_compat/pfk_if.o)
|
compat/pfk_if.o: $(deps_compat/pfk_if.o)
|
||||||
|
|
||||||
|
|||||||
@@ -1828,6 +1828,8 @@ deps_compat/pfk_ifq.o := \
|
|||||||
include/compat/sys/rwlock.h \
|
include/compat/sys/rwlock.h \
|
||||||
include/compat/sys/mutex.h \
|
include/compat/sys/mutex.h \
|
||||||
include/compat/sys/pclock.h \
|
include/compat/sys/pclock.h \
|
||||||
|
include/net/hfsc.h \
|
||||||
|
include/net/fq_codel.h \
|
||||||
|
|
||||||
compat/pfk_ifq.o: $(deps_compat/pfk_ifq.o)
|
compat/pfk_ifq.o: $(deps_compat/pfk_ifq.o)
|
||||||
|
|
||||||
|
|||||||
@@ -1828,6 +1828,8 @@ deps_compat/pfk_ip4.o := \
|
|||||||
include/compat/sys/rwlock.h \
|
include/compat/sys/rwlock.h \
|
||||||
include/compat/sys/mutex.h \
|
include/compat/sys/mutex.h \
|
||||||
include/compat/sys/pclock.h \
|
include/compat/sys/pclock.h \
|
||||||
|
include/net/hfsc.h \
|
||||||
|
include/net/fq_codel.h \
|
||||||
|
|
||||||
compat/pfk_ip4.o: $(deps_compat/pfk_ip4.o)
|
compat/pfk_ip4.o: $(deps_compat/pfk_ip4.o)
|
||||||
|
|
||||||
|
|||||||
@@ -1828,6 +1828,8 @@ deps_compat/pfk_ip6.o := \
|
|||||||
include/compat/sys/rwlock.h \
|
include/compat/sys/rwlock.h \
|
||||||
include/compat/sys/mutex.h \
|
include/compat/sys/mutex.h \
|
||||||
include/compat/sys/pclock.h \
|
include/compat/sys/pclock.h \
|
||||||
|
include/net/hfsc.h \
|
||||||
|
include/net/fq_codel.h \
|
||||||
|
|
||||||
compat/pfk_ip6.o: $(deps_compat/pfk_ip6.o)
|
compat/pfk_ip6.o: $(deps_compat/pfk_ip6.o)
|
||||||
|
|
||||||
|
|||||||
@@ -1828,6 +1828,8 @@ deps_compat/pfk_kern.o := \
|
|||||||
include/compat/sys/rwlock.h \
|
include/compat/sys/rwlock.h \
|
||||||
include/compat/sys/mutex.h \
|
include/compat/sys/mutex.h \
|
||||||
include/compat/sys/pclock.h \
|
include/compat/sys/pclock.h \
|
||||||
|
include/net/hfsc.h \
|
||||||
|
include/net/fq_codel.h \
|
||||||
|
|
||||||
compat/pfk_kern.o: $(deps_compat/pfk_kern.o)
|
compat/pfk_kern.o: $(deps_compat/pfk_kern.o)
|
||||||
|
|
||||||
|
|||||||
@@ -1828,6 +1828,8 @@ deps_compat/pfk_mbuf.o := \
|
|||||||
include/compat/sys/rwlock.h \
|
include/compat/sys/rwlock.h \
|
||||||
include/compat/sys/mutex.h \
|
include/compat/sys/mutex.h \
|
||||||
include/compat/sys/pclock.h \
|
include/compat/sys/pclock.h \
|
||||||
|
include/net/hfsc.h \
|
||||||
|
include/net/fq_codel.h \
|
||||||
|
|
||||||
compat/pfk_mbuf.o: $(deps_compat/pfk_mbuf.o)
|
compat/pfk_mbuf.o: $(deps_compat/pfk_mbuf.o)
|
||||||
|
|
||||||
|
|||||||
@@ -1828,6 +1828,8 @@ deps_compat/pfk_pcb.o := \
|
|||||||
include/compat/sys/rwlock.h \
|
include/compat/sys/rwlock.h \
|
||||||
include/compat/sys/mutex.h \
|
include/compat/sys/mutex.h \
|
||||||
include/compat/sys/pclock.h \
|
include/compat/sys/pclock.h \
|
||||||
|
include/net/hfsc.h \
|
||||||
|
include/net/fq_codel.h \
|
||||||
/home/lgcosta/lab/linux_kernel/linux-7.3.4/include/net/inet6_hashtables.h \
|
/home/lgcosta/lab/linux_kernel/linux-7.3.4/include/net/inet6_hashtables.h \
|
||||||
|
|
||||||
compat/pfk_pcb.o: $(deps_compat/pfk_pcb.o)
|
compat/pfk_pcb.o: $(deps_compat/pfk_pcb.o)
|
||||||
|
|||||||
@@ -1828,6 +1828,8 @@ deps_compat/pfk_rtable.o := \
|
|||||||
include/compat/sys/rwlock.h \
|
include/compat/sys/rwlock.h \
|
||||||
include/compat/sys/mutex.h \
|
include/compat/sys/mutex.h \
|
||||||
include/compat/sys/pclock.h \
|
include/compat/sys/pclock.h \
|
||||||
|
include/net/hfsc.h \
|
||||||
|
include/net/fq_codel.h \
|
||||||
|
|
||||||
compat/pfk_rtable.o: $(deps_compat/pfk_rtable.o)
|
compat/pfk_rtable.o: $(deps_compat/pfk_rtable.o)
|
||||||
|
|
||||||
|
|||||||
@@ -1828,6 +1828,8 @@ deps_compat/pfk_xmit.o := \
|
|||||||
include/compat/sys/rwlock.h \
|
include/compat/sys/rwlock.h \
|
||||||
include/compat/sys/mutex.h \
|
include/compat/sys/mutex.h \
|
||||||
include/compat/sys/pclock.h \
|
include/compat/sys/pclock.h \
|
||||||
|
include/net/hfsc.h \
|
||||||
|
include/net/fq_codel.h \
|
||||||
include/pfk_api.h \
|
include/pfk_api.h \
|
||||||
|
|
||||||
compat/pfk_xmit.o: $(deps_compat/pfk_xmit.o)
|
compat/pfk_xmit.o: $(deps_compat/pfk_xmit.o)
|
||||||
|
|||||||
@@ -1828,6 +1828,8 @@ deps_compat/radix.o := \
|
|||||||
include/compat/sys/rwlock.h \
|
include/compat/sys/rwlock.h \
|
||||||
include/compat/sys/mutex.h \
|
include/compat/sys/mutex.h \
|
||||||
include/compat/sys/pclock.h \
|
include/compat/sys/pclock.h \
|
||||||
|
include/net/hfsc.h \
|
||||||
|
include/net/fq_codel.h \
|
||||||
|
|
||||||
compat/radix.o: $(deps_compat/radix.o)
|
compat/radix.o: $(deps_compat/radix.o)
|
||||||
|
|
||||||
|
|||||||
@@ -1828,6 +1828,8 @@ deps_compat/sha2.o := \
|
|||||||
include/compat/sys/rwlock.h \
|
include/compat/sys/rwlock.h \
|
||||||
include/compat/sys/mutex.h \
|
include/compat/sys/mutex.h \
|
||||||
include/compat/sys/pclock.h \
|
include/compat/sys/pclock.h \
|
||||||
|
include/net/hfsc.h \
|
||||||
|
include/net/fq_codel.h \
|
||||||
|
|
||||||
compat/sha2.o: $(deps_compat/sha2.o)
|
compat/sha2.o: $(deps_compat/sha2.o)
|
||||||
|
|
||||||
|
|||||||
@@ -1828,6 +1828,8 @@ deps_compat/siphash.o := \
|
|||||||
include/compat/sys/rwlock.h \
|
include/compat/sys/rwlock.h \
|
||||||
include/compat/sys/mutex.h \
|
include/compat/sys/mutex.h \
|
||||||
include/compat/sys/pclock.h \
|
include/compat/sys/pclock.h \
|
||||||
|
include/net/hfsc.h \
|
||||||
|
include/net/fq_codel.h \
|
||||||
|
|
||||||
compat/siphash.o: $(deps_compat/siphash.o)
|
compat/siphash.o: $(deps_compat/siphash.o)
|
||||||
|
|
||||||
|
|||||||
@@ -1828,6 +1828,8 @@ deps_compat/subr_tree.o := \
|
|||||||
include/compat/sys/rwlock.h \
|
include/compat/sys/rwlock.h \
|
||||||
include/compat/sys/mutex.h \
|
include/compat/sys/mutex.h \
|
||||||
include/compat/sys/pclock.h \
|
include/compat/sys/pclock.h \
|
||||||
|
include/net/hfsc.h \
|
||||||
|
include/net/fq_codel.h \
|
||||||
|
|
||||||
compat/subr_tree.o: $(deps_compat/subr_tree.o)
|
compat/subr_tree.o: $(deps_compat/subr_tree.o)
|
||||||
|
|
||||||
|
|||||||
@@ -1828,6 +1828,8 @@ deps_compat/toeplitz.o := \
|
|||||||
include/compat/sys/rwlock.h \
|
include/compat/sys/rwlock.h \
|
||||||
include/compat/sys/mutex.h \
|
include/compat/sys/mutex.h \
|
||||||
include/compat/sys/pclock.h \
|
include/compat/sys/pclock.h \
|
||||||
|
include/net/hfsc.h \
|
||||||
|
include/net/fq_codel.h \
|
||||||
/home/lgcosta/lab/linux_kernel/linux-7.3.4/include/crypto/hash.h \
|
/home/lgcosta/lab/linux_kernel/linux-7.3.4/include/crypto/hash.h \
|
||||||
/home/lgcosta/lab/linux_kernel/linux-7.3.4/include/linux/crypto.h \
|
/home/lgcosta/lab/linux_kernel/linux-7.3.4/include/linux/crypto.h \
|
||||||
|
|
||||||
|
|||||||
+103
-27
@@ -217,10 +217,14 @@ pfk_pfq_enqueue(void *arg, struct mbuf *m)
|
|||||||
struct pfk_pfq *pfq = arg;
|
struct pfk_pfq *pfq = arg;
|
||||||
|
|
||||||
if (!pfq)
|
if (!pfq)
|
||||||
return NULL;
|
return m; /* caller keeps ownership */
|
||||||
if (ifq_enqueue(&pfq->ifq, m) != 0)
|
/*
|
||||||
return NULL;
|
* ifq_enqueue takes ownership of m (the scheduler may drop it); we then
|
||||||
return m;
|
* drain immediately, so nothing is returned for the caller to send.
|
||||||
|
*/
|
||||||
|
ifq_enqueue(&pfq->ifq, m);
|
||||||
|
ifq_start(&pfq->ifq);
|
||||||
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct mbuf *
|
static struct mbuf *
|
||||||
@@ -273,10 +277,11 @@ static const struct pfq_ops pfk_priq_pfq_ops = {
|
|||||||
* packets are still queued, but the traffic shaping curves are not yet
|
* packets are still queued, but the traffic shaping curves are not yet
|
||||||
* applied — recorded in the feature matrix as pending M5).
|
* applied — recorded in the feature matrix as pending M5).
|
||||||
*/
|
*/
|
||||||
const struct ifq_ops * const ifq_hfsc_ops = &pfk_priq_ifq_ops;
|
/*
|
||||||
const struct ifq_ops * const ifq_fqcodel_ops = &pfk_priq_ifq_ops;
|
* M5: the real scheduler back ends (pfsrc/hfsc.c, pfsrc/fq_codel.c) define
|
||||||
const struct pfq_ops * const pfq_hfsc_ops = &pfk_priq_pfq_ops;
|
* ifq_hfsc_ops/pfq_hfsc_ops/ifq_fqcodel_ops/pfq_fqcodel_ops themselves; the
|
||||||
const struct pfq_ops * const pfq_fqcodel_ops = &pfk_priq_pfq_ops;
|
* priq shim above stays as the default for interfaces without a queue rule.
|
||||||
|
*/
|
||||||
|
|
||||||
/* ---------------- framework ---------------- */
|
/* ---------------- framework ---------------- */
|
||||||
void
|
void
|
||||||
@@ -302,7 +307,36 @@ ifq_attach(struct ifqueue *ifq, const struct ifq_ops *ops, void *opsarg)
|
|||||||
if (!opsarg && ops->ifqop_alloc)
|
if (!opsarg && ops->ifqop_alloc)
|
||||||
opsarg = ops->ifqop_alloc(0, NULL);
|
opsarg = ops->ifqop_alloc(0, NULL);
|
||||||
ifq->ifq_ops = ops;
|
ifq->ifq_ops = ops;
|
||||||
|
/*
|
||||||
|
* The port's own helpers use ifq_softc; the staged OpenBSD schedulers
|
||||||
|
* (hfsc/fq_codel) read ifq->ifq_q. Keep both in sync or the back ends
|
||||||
|
* dereference a NULL scheduler state.
|
||||||
|
*/
|
||||||
ifq->ifq_softc = opsarg;
|
ifq->ifq_softc = opsarg;
|
||||||
|
ifq->ifq_q = opsarg;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Scheduler hand-off used by the ported back ends (hfsc/fq_codel) when they
|
||||||
|
* need their per-interface state. PFLINUX: the port has no ifq mutex (the
|
||||||
|
* data path is a single ordered work queue), so "enter" is a plain ownership
|
||||||
|
* check and "leave" is a no-op - the pair exists to mirror OpenBSD's API.
|
||||||
|
*/
|
||||||
|
void *
|
||||||
|
ifq_q_enter(struct ifqueue *ifq, const struct ifq_ops *ops)
|
||||||
|
{
|
||||||
|
if (ifq == NULL || ops == NULL || ifq->ifq_ops != ops)
|
||||||
|
return NULL; /* another scheduler owns it */
|
||||||
|
if (ifq->ifq_softc == NULL && ops->ifqop_alloc)
|
||||||
|
ifq->ifq_softc = ops->ifqop_alloc(0, NULL);
|
||||||
|
return ifq->ifq_softc;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
ifq_q_leave(struct ifqueue *ifq, void *q)
|
||||||
|
{
|
||||||
|
(void)ifq;
|
||||||
|
(void)q;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -312,19 +346,35 @@ ifq_destroy(struct ifqueue *ifq)
|
|||||||
ifq->ifq_ops->ifqop_free(0, ifq->ifq_softc);
|
ifq->ifq_ops->ifqop_free(0, ifq->ifq_softc);
|
||||||
ifq->ifq_ops = NULL;
|
ifq->ifq_ops = NULL;
|
||||||
ifq->ifq_softc = NULL;
|
ifq->ifq_softc = NULL;
|
||||||
|
ifq->ifq_q = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ifqop_enq returns the packet the scheduler decided to DROP (possibly the one
|
||||||
|
* just offered), and NULL when the packet was accepted - it is NOT an error
|
||||||
|
* code. Freeing the returned mbuf is this layer's job.
|
||||||
|
*/
|
||||||
int
|
int
|
||||||
ifq_enqueue(struct ifqueue *ifq, struct mbuf *m)
|
ifq_enqueue(struct ifqueue *ifq, struct mbuf *m)
|
||||||
{
|
{
|
||||||
if (!ifq->ifq_ops || !ifq->ifq_ops->ifqop_enq)
|
struct mbuf *dm;
|
||||||
|
|
||||||
|
if (!ifq->ifq_ops || !ifq->ifq_ops->ifqop_enq) {
|
||||||
|
m_freem(m);
|
||||||
return EINVAL;
|
return EINVAL;
|
||||||
|
}
|
||||||
if (ifq->ifq_maxlen && ifq->ifq_len >= ifq->ifq_maxlen) {
|
if (ifq->ifq_maxlen && ifq->ifq_len >= ifq->ifq_maxlen) {
|
||||||
ifq->ifq_errors++;
|
ifq->ifq_errors++;
|
||||||
ifq->ifq_qdrops++;
|
ifq->ifq_qdrops++;
|
||||||
|
m_freem(m);
|
||||||
return ENOBUFS;
|
return ENOBUFS;
|
||||||
}
|
}
|
||||||
return ifq->ifq_ops->ifqop_enq(ifq, m) ? 0 : ENOMEM;
|
dm = ifq->ifq_ops->ifqop_enq(ifq, m);
|
||||||
|
if (dm) {
|
||||||
|
ifq->ifq_qdrops++;
|
||||||
|
m_freem(dm);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct mbuf *
|
struct mbuf *
|
||||||
@@ -336,14 +386,16 @@ ifq_deq_begin(struct ifqueue *ifq)
|
|||||||
if (!ifq->ifq_ops || !ifq->ifq_ops->ifqop_deq_begin)
|
if (!ifq->ifq_ops || !ifq->ifq_ops->ifqop_deq_begin)
|
||||||
return NULL;
|
return NULL;
|
||||||
m = ifq->ifq_ops->ifqop_deq_begin(ifq, &cookie);
|
m = ifq->ifq_ops->ifqop_deq_begin(ifq, &cookie);
|
||||||
|
ifq->ifq_dequeue = cookie;
|
||||||
return m;
|
return m;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
ifq_deq_commit(struct ifqueue *ifq, struct mbuf *m)
|
ifq_deq_commit(struct ifqueue *ifq, struct mbuf *m)
|
||||||
{
|
{
|
||||||
void *cookie = NULL;
|
void *cookie = ifq->ifq_dequeue;
|
||||||
|
|
||||||
|
ifq->ifq_dequeue = NULL;
|
||||||
if (ifq->ifq_ops && ifq->ifq_ops->ifqop_deq_commit)
|
if (ifq->ifq_ops && ifq->ifq_ops->ifqop_deq_commit)
|
||||||
ifq->ifq_ops->ifqop_deq_commit(ifq, m, cookie);
|
ifq->ifq_ops->ifqop_deq_commit(ifq, m, cookie);
|
||||||
}
|
}
|
||||||
@@ -365,12 +417,52 @@ ifq_dequeue(struct ifqueue *ifq)
|
|||||||
return m;
|
return m;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Drain the queue as far as the scheduler allows and hand each packet to the
|
||||||
|
* stack's output path. OpenBSD calls if_start from softint context; here it
|
||||||
|
* runs either from the pf work queue (enqueue side) or from the scheduler's
|
||||||
|
* deferred timeout (pacing side), which is what makes hfsc's bandwidth
|
||||||
|
* limits take effect.
|
||||||
|
*/
|
||||||
void
|
void
|
||||||
ifq_start(struct ifqueue *ifq)
|
ifq_start(struct ifqueue *ifq)
|
||||||
{
|
{
|
||||||
|
struct mbuf *m;
|
||||||
|
int n;
|
||||||
|
|
||||||
|
n = 0;
|
||||||
|
ifq->ifq_oactive = 1;
|
||||||
|
while ((m = ifq_dequeue(ifq)) != NULL) {
|
||||||
|
n++;
|
||||||
|
if (pfk_ifq_transmit(ifq, m))
|
||||||
|
break; /* device busy: packet kept */
|
||||||
|
}
|
||||||
ifq->ifq_oactive = 0;
|
ifq->ifq_oactive = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* pf data-path entry: hand a packet to the interface's scheduler when one is
|
||||||
|
* attached (a pf `queue' rule is configured). Returns 1 when the queue took
|
||||||
|
* ownership of m, 0 when the caller must keep going (priq/no scheduler).
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
pf_kifq_input(struct pfk_ifnet *ifp, struct mbuf *m)
|
||||||
|
{
|
||||||
|
struct ifqueue *ifq;
|
||||||
|
|
||||||
|
if (ifp == NULL || m == NULL)
|
||||||
|
return 0;
|
||||||
|
if (ifp == NULL)
|
||||||
|
return 0;
|
||||||
|
ifq = &ifp->if_snd;
|
||||||
|
if (ifq->ifq_ops == NULL || ifq->ifq_ops == ifq_priq_ops)
|
||||||
|
return 0; /* no scheduler: fast path */
|
||||||
|
if (ifq_enqueue(ifq, m) != 0)
|
||||||
|
return 1; /* dropped by the queue: owned */
|
||||||
|
ifq_start(ifq);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
unsigned int
|
unsigned int
|
||||||
ifq_purge(struct ifqueue *ifq)
|
ifq_purge(struct ifqueue *ifq)
|
||||||
{
|
{
|
||||||
@@ -423,19 +515,3 @@ ifq_hdatalen(struct ifqueue *ifq)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---------------- scheduler back ends (milestone M5) ---------------- */
|
|
||||||
/* The staged OpenBSD hfsc.c/fq_codel.c are not in the build yet; the priq
|
|
||||||
* ops above are the active scheduler. These wrappers keep pf_ioctl's
|
|
||||||
* initialisation calls valid until M5 replaces them with the ported
|
|
||||||
* schedulers (docs/porting-notes.md). */
|
|
||||||
void
|
|
||||||
hfsc_initialize(void)
|
|
||||||
{
|
|
||||||
pr_info("pfk: hfsc back end pending M5 (priq active)\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
fq_codel_initialize(void)
|
|
||||||
{
|
|
||||||
pr_info("pfk: fq_codel back end pending M5 (priq active)\n");
|
|
||||||
}
|
|
||||||
@@ -47,8 +47,21 @@ pfk_printk_compat(const char *fmt, ...)
|
|||||||
void
|
void
|
||||||
splassert_fail(int expected, int actual, const char *func)
|
splassert_fail(int expected, int actual, const char *func)
|
||||||
{
|
{
|
||||||
pr_err("splassert: %s: expected %d, got %d\n", func, expected, actual);
|
/*
|
||||||
BUG();
|
* PFLINUX: OpenBSD's splassert() checks a per-CPU spl level, so
|
||||||
|
* "assert unlocked" really means "this context did not take the lock".
|
||||||
|
* The port replaced spl with rw-semas plus work queues, where
|
||||||
|
* rw_status() is a global counter: an assert-unlocked in a work-queue
|
||||||
|
* task fires whenever *another* CPU happens to hold the lock, which is
|
||||||
|
* not a bug (the task never acquires pf_lock itself) and was turning a
|
||||||
|
* harmless race into a kernel BUG() that wedged the module in
|
||||||
|
* "Unloading". Report it once instead of killing the host; the
|
||||||
|
* structural invariant the BSD assert protects - purge never runs while
|
||||||
|
* holding pf_lock - is upheld by the call sites (pf_purge_* are only
|
||||||
|
* reached from systqmp tasks and timer work).
|
||||||
|
*/
|
||||||
|
pr_warn_ratelimited("pfk: splassert: %s: expected %d, got %d\n",
|
||||||
|
func, expected, actual);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---------------- addlog (OpenBSD kern.log) ---------------- */
|
/* ---------------- addlog (OpenBSD kern.log) ---------------- */
|
||||||
|
|||||||
@@ -607,3 +607,14 @@ pfk_mbuf_exit(void)
|
|||||||
{
|
{
|
||||||
kmem_cache_destroy(pfk_mbuf_cache);
|
kmem_cache_destroy(pfk_mbuf_cache);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
ml_purge(struct mbuf_list *ml)
|
||||||
|
{
|
||||||
|
struct mbuf *m;
|
||||||
|
|
||||||
|
while ((m = ml_dequeue(ml)) != NULL)
|
||||||
|
m_freem(m);
|
||||||
|
ml->ml_first = ml->ml_last = NULL;
|
||||||
|
ml->ml_len = 0;
|
||||||
|
}
|
||||||
@@ -270,3 +270,40 @@ pfk_if_output(struct pfk_ifnet *ifp, struct mbuf *m, void *dst,
|
|||||||
m_freem(mp);
|
m_freem(mp);
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Transmit one queued packet. Called from ifq_start() - i.e. from the pf
|
||||||
|
* work queue or from a scheduler's deferred timeout - and always consumes the
|
||||||
|
* mbuf. Returns 0 (keep draining); a scheduler that wants to defer does so by
|
||||||
|
* simply not returning a packet from ifqop_deq_begin().
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
pfk_ifq_transmit(struct ifqueue *ifq, struct mbuf *m)
|
||||||
|
{
|
||||||
|
struct sk_buff *skb = m_to_skb(m);
|
||||||
|
struct dst_entry *d;
|
||||||
|
|
||||||
|
(void)ifq;
|
||||||
|
if (skb == NULL) {
|
||||||
|
m_freem(m);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
d = skb_dst(skb);
|
||||||
|
if (d == NULL || IS_ERR(d) || d->dev == NULL || !netif_running(d->dev)) {
|
||||||
|
m_freem(m);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
skb->dev = d->dev;
|
||||||
|
skb_reset_network_header(skb);
|
||||||
|
if (skb->protocol == 0)
|
||||||
|
skb->protocol = htons(ip_hdr(skb)->protocol == IPPROTO_IPV6 ?
|
||||||
|
ETH_P_IPV6 : ETH_P_IP);
|
||||||
|
/* the stack owns skb->cb (IPCB) from here; pf already tested it */
|
||||||
|
memset(skb->cb, 0, sizeof(skb->cb));
|
||||||
|
skb->mark |= PFK_DONE_OUT;
|
||||||
|
skb_get(skb); /* the output path consumes one reference */
|
||||||
|
m->skb = NULL; /* the mbuf's reference moves to the stack */
|
||||||
|
m_freem(m);
|
||||||
|
d->output(&init_net, NULL, skb);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -27,6 +27,7 @@ struct ifqueue {
|
|||||||
struct mbuf_list *ifq_free; /* free list (unused in the port) */
|
struct mbuf_list *ifq_free; /* free list (unused in the port) */
|
||||||
unsigned int ifq_len;
|
unsigned int ifq_len;
|
||||||
unsigned int ifq_oactive;
|
unsigned int ifq_oactive;
|
||||||
|
void *ifq_dequeue; /* cookie between deq_begin/commit */
|
||||||
|
|
||||||
/* statistics */
|
/* statistics */
|
||||||
u_int64_t ifq_packets;
|
u_int64_t ifq_packets;
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
/* $OpenBSD: fq_codel.h,v 1.4 2020/06/18 23:29:59 dlg Exp $ */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2017 Mike Belopuhov
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _NET_FQ_CODEL_H_
|
||||||
|
#define _NET_FQ_CODEL_H_
|
||||||
|
|
||||||
|
/* compatible with hfsc_pktcntr */
|
||||||
|
struct fqcodel_pktcntr {
|
||||||
|
uint64_t packets;
|
||||||
|
uint64_t bytes;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct fqcodel_stats {
|
||||||
|
struct fqcodel_pktcntr xmit_cnt;
|
||||||
|
struct fqcodel_pktcntr drop_cnt;
|
||||||
|
|
||||||
|
uint32_t qlength;
|
||||||
|
uint32_t qlimit;
|
||||||
|
|
||||||
|
uint32_t flows;
|
||||||
|
uint32_t _unused; /* padding */
|
||||||
|
|
||||||
|
uint32_t target;
|
||||||
|
uint32_t interval;
|
||||||
|
|
||||||
|
/* the values below are used to calculate standard deviation */
|
||||||
|
uint64_t delaysum; /* sum of delays, us */
|
||||||
|
uint64_t delaysumsq;/* sum of squared delays, us */
|
||||||
|
};
|
||||||
|
|
||||||
|
#ifdef _KERNEL
|
||||||
|
extern const struct ifq_ops * const ifq_fqcodel_ops;
|
||||||
|
extern const struct pfq_ops * const pfq_fqcodel_ops;
|
||||||
|
#endif /* _KERNEL */
|
||||||
|
|
||||||
|
#endif /* _NET_FQ_CODEL_H_ */
|
||||||
@@ -0,0 +1,122 @@
|
|||||||
|
/* $OpenBSD: hfsc.h,v 1.15 2026/03/19 14:59:05 sthen Exp $ */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2012-2013 Henning Brauer <henning@openbsd.org>
|
||||||
|
* Copyright (c) 1997-1999 Carnegie Mellon University. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and distribute this software and
|
||||||
|
* its documentation is hereby granted (including for commercial or
|
||||||
|
* for-profit use), provided that both the copyright notice and this
|
||||||
|
* permission notice appear in all copies of the software, derivative
|
||||||
|
* works, or modified versions, and any portions thereof.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS EXPERIMENTAL AND IS KNOWN TO HAVE BUGS, SOME OF
|
||||||
|
* WHICH MAY HAVE SERIOUS CONSEQUENCES. CARNEGIE MELLON PROVIDES THIS
|
||||||
|
* SOFTWARE IN ITS ``AS IS'' CONDITION, AND ANY EXPRESS OR IMPLIED
|
||||||
|
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||||
|
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
|
||||||
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
|
||||||
|
* OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
|
||||||
|
* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||||
|
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
|
||||||
|
* USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
|
||||||
|
* DAMAGE.
|
||||||
|
*
|
||||||
|
* Carnegie Mellon encourages (but does not require) users of this
|
||||||
|
* software to return any improvements or extensions that they make,
|
||||||
|
* and to grant Carnegie Mellon the rights to redistribute these
|
||||||
|
* changes without encumbrance.
|
||||||
|
*/
|
||||||
|
#ifndef _HFSC_H_
|
||||||
|
#define _HFSC_H_
|
||||||
|
|
||||||
|
/* hfsc class flags */
|
||||||
|
#define HFSC_DEFAULTCLASS 0x1000 /* default class */
|
||||||
|
|
||||||
|
struct hfsc_pktcntr {
|
||||||
|
u_int64_t packets;
|
||||||
|
u_int64_t bytes;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define PKTCNTR_INC(cntr, len) \
|
||||||
|
do { (cntr)->packets++; (cntr)->bytes += len; } while (0)
|
||||||
|
|
||||||
|
struct hfsc_sc {
|
||||||
|
u_int64_t m1; /* slope of the first segment in bits/sec */
|
||||||
|
u_int64_t m2; /* slope of the second segment in bits/sec */
|
||||||
|
u_int d; /* the x-projection of the first segment in msec */
|
||||||
|
};
|
||||||
|
|
||||||
|
/* special class handles */
|
||||||
|
#define HFSC_ROOT_CLASS 0x10000
|
||||||
|
#define HFSC_DEFAULT_CLASSES 64
|
||||||
|
#define HFSC_MAX_CLASSES 65535
|
||||||
|
|
||||||
|
/* service curve types */
|
||||||
|
#define HFSC_REALTIMESC 1
|
||||||
|
#define HFSC_LINKSHARINGSC 2
|
||||||
|
#define HFSC_UPPERLIMITSC 4
|
||||||
|
#define HFSC_DEFAULTSC (HFSC_REALTIMESC|HFSC_LINKSHARINGSC)
|
||||||
|
|
||||||
|
struct hfsc_class_stats {
|
||||||
|
struct hfsc_pktcntr xmit_cnt;
|
||||||
|
struct hfsc_pktcntr drop_cnt;
|
||||||
|
|
||||||
|
u_int qlength;
|
||||||
|
u_int qlimit;
|
||||||
|
u_int period;
|
||||||
|
|
||||||
|
u_int class_id;
|
||||||
|
u_int32_t class_handle;
|
||||||
|
struct hfsc_sc rsc;
|
||||||
|
struct hfsc_sc fsc;
|
||||||
|
struct hfsc_sc usc; /* upper limit service curve */
|
||||||
|
|
||||||
|
u_int64_t total; /* total work in bytes */
|
||||||
|
u_int64_t cumul; /* cumulative work in bytes
|
||||||
|
done by real-time criteria */
|
||||||
|
u_int64_t d; /* deadline */
|
||||||
|
u_int64_t e; /* eligible time */
|
||||||
|
u_int64_t vt; /* virtual time */
|
||||||
|
u_int64_t f; /* fit time for upper-limit */
|
||||||
|
|
||||||
|
/* info helpful for debugging */
|
||||||
|
u_int64_t initvt; /* init virtual time */
|
||||||
|
u_int64_t vtoff; /* cl_vt_ipoff */
|
||||||
|
u_int64_t cvtmax; /* cl_maxvt */
|
||||||
|
u_int64_t myf; /* cl_myf */
|
||||||
|
u_int64_t cfmin; /* cl_mincf */
|
||||||
|
u_int64_t cvtmin; /* cl_mincvt */
|
||||||
|
u_int64_t myfadj; /* cl_myfadj */
|
||||||
|
u_int64_t vtadj; /* cl_vtadj */
|
||||||
|
u_int64_t cur_time;
|
||||||
|
u_int32_t machclk_freq;
|
||||||
|
|
||||||
|
u_int vtperiod; /* vt period sequence no */
|
||||||
|
u_int parentperiod; /* parent's vt period seqno */
|
||||||
|
int nactive; /* number of active children */
|
||||||
|
|
||||||
|
/* red and rio related info */
|
||||||
|
int qtype;
|
||||||
|
/* struct redstats red[3]; */
|
||||||
|
};
|
||||||
|
|
||||||
|
#ifdef _KERNEL
|
||||||
|
struct ifnet;
|
||||||
|
struct ifqueue;
|
||||||
|
struct pf_queuespec;
|
||||||
|
struct hfsc_if;
|
||||||
|
|
||||||
|
extern const struct ifq_ops * const ifq_hfsc_ops;
|
||||||
|
extern const struct pfq_ops * const pfq_hfsc_ops;
|
||||||
|
|
||||||
|
#define HFSC_ENABLED(ifq) ((ifq)->ifq_ops == ifq_hfsc_ops)
|
||||||
|
#define HFSC_DEFAULT_QLIMIT 50
|
||||||
|
|
||||||
|
void hfsc_initialize(void);
|
||||||
|
|
||||||
|
#endif /* _KERNEL */
|
||||||
|
#endif /* _HFSC_H_ */
|
||||||
@@ -1212,5 +1212,51 @@ struct pfk_pflog_stat {
|
|||||||
};
|
};
|
||||||
#define PFKIOCGETPFLOGSTAT _IOR('L', 1, struct pfk_pflog_stat)
|
#define PFKIOCGETPFLOGSTAT _IOR('L', 1, struct pfk_pflog_stat)
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Scheduler back-end ABI: the very headers OpenBSD ships (net/hfsc.h,
|
||||||
|
* net/fq_codel.h), vendored into the port so that pfctl and pf.ko agree on the
|
||||||
|
* stats layouts and on the class-handle/service-curve constants. (Hand-rolled
|
||||||
|
* approximations of these structs made DIOCGETQSTATS fail with EINVAL, because
|
||||||
|
* pfctl's sizeof(struct hfsc_class_stats) did not match the kernel's.)
|
||||||
|
*/
|
||||||
|
#ifndef nsecuptime
|
||||||
|
#define nsecuptime() getnsecuptime()
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <net/hfsc.h>
|
||||||
|
#include <net/fq_codel.h>
|
||||||
|
|
||||||
|
/* OpenBSD's global nanoseconds-per-clocktick (HFSC_CLK_PER_TICK). The port
|
||||||
|
* has no clock_rate machinery, so derive it from the kernel's HZ. */
|
||||||
|
#ifndef tick_nsec
|
||||||
|
#define tick_nsec (1000000000LL / HZ)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* ifq scheduler hand-off (see pfsrc/ifq.c): enter returns the scheduler's
|
||||||
|
* queue state when it owns the interface queue, NULL otherwise. */
|
||||||
|
void *ifq_q_enter(struct ifqueue *, const struct ifq_ops *);
|
||||||
|
void ifq_q_leave(struct ifqueue *, void *);
|
||||||
|
int pfk_ifq_transmit(struct ifqueue *, struct mbuf *);
|
||||||
|
int pf_kifq_input(struct pfk_ifnet *, struct mbuf *);
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* PFLINUX notes on the scheduler back ends:
|
||||||
|
*
|
||||||
|
* - splnet()/splx(): OpenBSD raises the software interrupt level around
|
||||||
|
* class-table surgery. This port runs the whole data path in one ordered
|
||||||
|
* work queue and guards the queue with ifq_mtx, so these are no-ops; they
|
||||||
|
* are kept as markers of the critical regions.
|
||||||
|
* - KERNEL_ASSERT_LOCKED(): the schedulers are only reached from ifq/pf paths
|
||||||
|
* that already hold the queue mutex; asserted structurally, not by spl.
|
||||||
|
* - HFSC_ENABLED(), PKTCNTR_INC(), the class-handle constants and the stats
|
||||||
|
* layouts all come from the vendored net/hfsc.h / net/fq_codel.h above, so
|
||||||
|
* kernel and pfctl cannot drift apart.
|
||||||
|
*/
|
||||||
|
#define splnet() 0
|
||||||
|
#define splx(s) ((void)(s))
|
||||||
|
#define KERNEL_ASSERT_LOCKED() do { } while (0)
|
||||||
|
|
||||||
#endif /* _PFK_H */
|
#endif /* _PFK_H */
|
||||||
|
|
||||||
@@ -74,6 +74,79 @@ ml_dequeue(struct mbuf_list *ml)
|
|||||||
return m;
|
return m;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* BSD <net/if_var.h> mbuf_list helpers (net/if_var.h ml_*) */
|
||||||
|
static inline void
|
||||||
|
ml_init(struct mbuf_list *ml)
|
||||||
|
{
|
||||||
|
ml->ml_first = ml->ml_last = NULL;
|
||||||
|
ml->ml_len = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void
|
||||||
|
ml_enqueue(struct mbuf_list *ml, struct mbuf *m)
|
||||||
|
{
|
||||||
|
m->m_next = NULL;
|
||||||
|
if (ml->ml_last == NULL)
|
||||||
|
ml->ml_first = m;
|
||||||
|
else
|
||||||
|
ml->ml_last->m_next = m;
|
||||||
|
ml->ml_last = m;
|
||||||
|
ml->ml_len++;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline struct mbuf *
|
||||||
|
ml_dequeue_tail(struct mbuf_list *ml)
|
||||||
|
{
|
||||||
|
struct mbuf *m, *prev = NULL;
|
||||||
|
|
||||||
|
if (ml->ml_first == NULL)
|
||||||
|
return NULL;
|
||||||
|
for (m = ml->ml_first; m->m_next; m = m->m_next)
|
||||||
|
prev = m;
|
||||||
|
if (prev == NULL) /* single element */
|
||||||
|
ml->ml_first = NULL;
|
||||||
|
else
|
||||||
|
prev->m_next = NULL;
|
||||||
|
ml->ml_last = prev;
|
||||||
|
ml->ml_len--;
|
||||||
|
m->m_next = NULL;
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* append every packet in m2 to m1 (m2 becomes empty) */
|
||||||
|
static inline void
|
||||||
|
ml_enlist(struct mbuf_list *m1, struct mbuf_list *m2)
|
||||||
|
{
|
||||||
|
if (m2->ml_first == NULL)
|
||||||
|
return;
|
||||||
|
if (m1->ml_last == NULL)
|
||||||
|
m1->ml_first = m2->ml_first;
|
||||||
|
else
|
||||||
|
m1->ml_last->m_next = m2->ml_first;
|
||||||
|
m1->ml_last = m2->ml_last;
|
||||||
|
m1->ml_len += m2->ml_len;
|
||||||
|
m2->ml_first = m2->ml_last = NULL;
|
||||||
|
m2->ml_len = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline unsigned int
|
||||||
|
ml_len(const struct mbuf_list *ml)
|
||||||
|
{
|
||||||
|
return (unsigned int)ml->ml_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int
|
||||||
|
ml_empty(const struct mbuf_list *ml)
|
||||||
|
{
|
||||||
|
return ml->ml_first == NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ml_purge(struct mbuf_list *ml); /* compat/pfk_mbuf.c (needs m_freem) */
|
||||||
|
|
||||||
|
#define MBUF_LIST_FIRST(_ml) ((_ml)->ml_first)
|
||||||
|
#define MBUF_LIST_INITIALIZER() { NULL, NULL, 0 }
|
||||||
|
|
||||||
/* egress interface (default-route dev), maintained on fib/net events */
|
/* egress interface (default-route dev), maintained on fib/net events */
|
||||||
struct pfk_ifnet *pfk_egress_if(void);
|
struct pfk_ifnet *pfk_egress_if(void);
|
||||||
void pfk_egress_refresh(void);
|
void pfk_egress_refresh(void);
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ struct pkthdr {
|
|||||||
u_int32_t ph_flowid;
|
u_int32_t ph_flowid;
|
||||||
u_int ph_ifidx;
|
u_int ph_ifidx;
|
||||||
u_int ph_rtableid;
|
u_int ph_rtableid;
|
||||||
struct pfk_timeval ph_timestamp;
|
int64_t ph_timestamp; /* OpenBSD 8.0: nanoseconds */
|
||||||
struct pfk_ifnet *ifp;
|
struct pfk_ifnet *ifp;
|
||||||
struct pfk_ifnet *rcvif;
|
struct pfk_ifnet *rcvif;
|
||||||
u_short ph; /* tso segs: unused, kept as pad */
|
u_short ph; /* tso segs: unused, kept as pad */
|
||||||
|
|||||||
@@ -1828,6 +1828,8 @@ deps_pfsrc/divert_sock.o := \
|
|||||||
include/compat/sys/rwlock.h \
|
include/compat/sys/rwlock.h \
|
||||||
include/compat/sys/mutex.h \
|
include/compat/sys/mutex.h \
|
||||||
include/compat/sys/pclock.h \
|
include/compat/sys/pclock.h \
|
||||||
|
include/net/hfsc.h \
|
||||||
|
include/net/fq_codel.h \
|
||||||
include/pfk_api.h \
|
include/pfk_api.h \
|
||||||
|
|
||||||
pfsrc/divert_sock.o: $(deps_pfsrc/divert_sock.o)
|
pfsrc/divert_sock.o: $(deps_pfsrc/divert_sock.o)
|
||||||
|
|||||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -1828,6 +1828,8 @@ deps_pfsrc/pf.o := \
|
|||||||
include/compat/sys/rwlock.h \
|
include/compat/sys/rwlock.h \
|
||||||
include/compat/sys/mutex.h \
|
include/compat/sys/mutex.h \
|
||||||
include/compat/sys/pclock.h \
|
include/compat/sys/pclock.h \
|
||||||
|
include/net/hfsc.h \
|
||||||
|
include/net/fq_codel.h \
|
||||||
|
|
||||||
pfsrc/pf.o: $(deps_pfsrc/pf.o)
|
pfsrc/pf.o: $(deps_pfsrc/pf.o)
|
||||||
|
|
||||||
|
|||||||
@@ -1828,6 +1828,8 @@ deps_pfsrc/pf_if.o := \
|
|||||||
include/compat/sys/rwlock.h \
|
include/compat/sys/rwlock.h \
|
||||||
include/compat/sys/mutex.h \
|
include/compat/sys/mutex.h \
|
||||||
include/compat/sys/pclock.h \
|
include/compat/sys/pclock.h \
|
||||||
|
include/net/hfsc.h \
|
||||||
|
include/net/fq_codel.h \
|
||||||
|
|
||||||
pfsrc/pf_if.o: $(deps_pfsrc/pf_if.o)
|
pfsrc/pf_if.o: $(deps_pfsrc/pf_if.o)
|
||||||
|
|
||||||
|
|||||||
@@ -1828,6 +1828,8 @@ deps_pfsrc/pf_ioctl.o := \
|
|||||||
include/compat/sys/rwlock.h \
|
include/compat/sys/rwlock.h \
|
||||||
include/compat/sys/mutex.h \
|
include/compat/sys/mutex.h \
|
||||||
include/compat/sys/pclock.h \
|
include/compat/sys/pclock.h \
|
||||||
|
include/net/hfsc.h \
|
||||||
|
include/net/fq_codel.h \
|
||||||
|
|
||||||
pfsrc/pf_ioctl.o: $(deps_pfsrc/pf_ioctl.o)
|
pfsrc/pf_ioctl.o: $(deps_pfsrc/pf_ioctl.o)
|
||||||
|
|
||||||
|
|||||||
@@ -1828,6 +1828,8 @@ deps_pfsrc/pf_lb.o := \
|
|||||||
include/compat/sys/rwlock.h \
|
include/compat/sys/rwlock.h \
|
||||||
include/compat/sys/mutex.h \
|
include/compat/sys/mutex.h \
|
||||||
include/compat/sys/pclock.h \
|
include/compat/sys/pclock.h \
|
||||||
|
include/net/hfsc.h \
|
||||||
|
include/net/fq_codel.h \
|
||||||
|
|
||||||
pfsrc/pf_lb.o: $(deps_pfsrc/pf_lb.o)
|
pfsrc/pf_lb.o: $(deps_pfsrc/pf_lb.o)
|
||||||
|
|
||||||
|
|||||||
@@ -1828,6 +1828,8 @@ deps_pfsrc/pf_norm.o := \
|
|||||||
include/compat/sys/rwlock.h \
|
include/compat/sys/rwlock.h \
|
||||||
include/compat/sys/mutex.h \
|
include/compat/sys/mutex.h \
|
||||||
include/compat/sys/pclock.h \
|
include/compat/sys/pclock.h \
|
||||||
|
include/net/hfsc.h \
|
||||||
|
include/net/fq_codel.h \
|
||||||
|
|
||||||
pfsrc/pf_norm.o: $(deps_pfsrc/pf_norm.o)
|
pfsrc/pf_norm.o: $(deps_pfsrc/pf_norm.o)
|
||||||
|
|
||||||
|
|||||||
@@ -1828,6 +1828,8 @@ deps_pfsrc/pf_osfp.o := \
|
|||||||
include/compat/sys/rwlock.h \
|
include/compat/sys/rwlock.h \
|
||||||
include/compat/sys/mutex.h \
|
include/compat/sys/mutex.h \
|
||||||
include/compat/sys/pclock.h \
|
include/compat/sys/pclock.h \
|
||||||
|
include/net/hfsc.h \
|
||||||
|
include/net/fq_codel.h \
|
||||||
|
|
||||||
pfsrc/pf_osfp.o: $(deps_pfsrc/pf_osfp.o)
|
pfsrc/pf_osfp.o: $(deps_pfsrc/pf_osfp.o)
|
||||||
|
|
||||||
|
|||||||
@@ -1828,6 +1828,8 @@ deps_pfsrc/pf_ruleset.o := \
|
|||||||
include/compat/sys/rwlock.h \
|
include/compat/sys/rwlock.h \
|
||||||
include/compat/sys/mutex.h \
|
include/compat/sys/mutex.h \
|
||||||
include/compat/sys/pclock.h \
|
include/compat/sys/pclock.h \
|
||||||
|
include/net/hfsc.h \
|
||||||
|
include/net/fq_codel.h \
|
||||||
|
|
||||||
pfsrc/pf_ruleset.o: $(deps_pfsrc/pf_ruleset.o)
|
pfsrc/pf_ruleset.o: $(deps_pfsrc/pf_ruleset.o)
|
||||||
|
|
||||||
|
|||||||
@@ -1828,6 +1828,8 @@ deps_pfsrc/pf_syncookies.o := \
|
|||||||
include/compat/sys/rwlock.h \
|
include/compat/sys/rwlock.h \
|
||||||
include/compat/sys/mutex.h \
|
include/compat/sys/mutex.h \
|
||||||
include/compat/sys/pclock.h \
|
include/compat/sys/pclock.h \
|
||||||
|
include/net/hfsc.h \
|
||||||
|
include/net/fq_codel.h \
|
||||||
|
|
||||||
pfsrc/pf_syncookies.o: $(deps_pfsrc/pf_syncookies.o)
|
pfsrc/pf_syncookies.o: $(deps_pfsrc/pf_syncookies.o)
|
||||||
|
|
||||||
|
|||||||
@@ -1828,6 +1828,8 @@ deps_pfsrc/pf_table.o := \
|
|||||||
include/compat/sys/rwlock.h \
|
include/compat/sys/rwlock.h \
|
||||||
include/compat/sys/mutex.h \
|
include/compat/sys/mutex.h \
|
||||||
include/compat/sys/pclock.h \
|
include/compat/sys/pclock.h \
|
||||||
|
include/net/hfsc.h \
|
||||||
|
include/net/fq_codel.h \
|
||||||
|
|
||||||
pfsrc/pf_table.o: $(deps_pfsrc/pf_table.o)
|
pfsrc/pf_table.o: $(deps_pfsrc/pf_table.o)
|
||||||
|
|
||||||
|
|||||||
@@ -1828,6 +1828,8 @@ deps_pfsrc/pfk_hooks.o := \
|
|||||||
include/compat/sys/rwlock.h \
|
include/compat/sys/rwlock.h \
|
||||||
include/compat/sys/mutex.h \
|
include/compat/sys/mutex.h \
|
||||||
include/compat/sys/pclock.h \
|
include/compat/sys/pclock.h \
|
||||||
|
include/net/hfsc.h \
|
||||||
|
include/net/fq_codel.h \
|
||||||
include/pfk_api.h \
|
include/pfk_api.h \
|
||||||
|
|
||||||
pfsrc/pfk_hooks.o: $(deps_pfsrc/pfk_hooks.o)
|
pfsrc/pfk_hooks.o: $(deps_pfsrc/pfk_hooks.o)
|
||||||
|
|||||||
@@ -1828,6 +1828,8 @@ deps_pfsrc/pfk_mod.o := \
|
|||||||
include/compat/sys/rwlock.h \
|
include/compat/sys/rwlock.h \
|
||||||
include/compat/sys/mutex.h \
|
include/compat/sys/mutex.h \
|
||||||
include/compat/sys/pclock.h \
|
include/compat/sys/pclock.h \
|
||||||
|
include/net/hfsc.h \
|
||||||
|
include/net/fq_codel.h \
|
||||||
include/pfk_api.h \
|
include/pfk_api.h \
|
||||||
|
|
||||||
pfsrc/pfk_mod.o: $(deps_pfsrc/pfk_mod.o)
|
pfsrc/pfk_mod.o: $(deps_pfsrc/pfk_mod.o)
|
||||||
|
|||||||
@@ -1828,6 +1828,8 @@ deps_pfsrc/pfk_pflog.o := \
|
|||||||
include/compat/sys/rwlock.h \
|
include/compat/sys/rwlock.h \
|
||||||
include/compat/sys/mutex.h \
|
include/compat/sys/mutex.h \
|
||||||
include/compat/sys/pclock.h \
|
include/compat/sys/pclock.h \
|
||||||
|
include/net/hfsc.h \
|
||||||
|
include/net/fq_codel.h \
|
||||||
include/pfk_api.h \
|
include/pfk_api.h \
|
||||||
|
|
||||||
pfsrc/pfk_pflog.o: $(deps_pfsrc/pfk_pflog.o)
|
pfsrc/pfk_pflog.o: $(deps_pfsrc/pfk_pflog.o)
|
||||||
|
|||||||
@@ -332,7 +332,9 @@ control_law(struct codel *cd, struct codel_params *cp, int64_t rts)
|
|||||||
unsigned int idx;
|
unsigned int idx;
|
||||||
|
|
||||||
KASSERT(cd->drops > 0);
|
KASSERT(cd->drops > 0);
|
||||||
idx = min(cd->drops - 1, nitems(codel_intervals) - 1);
|
/* PFLINUX: Linux' min() type-checks its arguments; nitems() is size_t */
|
||||||
|
idx = min_t(unsigned int, cd->drops - 1,
|
||||||
|
(unsigned int)(nitems(codel_intervals) - 1));
|
||||||
cd->next = rts + cp->intervals[idx];
|
cd->next = rts + cp->intervals[idx];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+12
-3
@@ -335,9 +335,9 @@ hfsc_class_purge(struct hfsc_class *cl, struct mbuf_list *ml)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static inline u_int
|
static inline u_int
|
||||||
hfsc_more_slots(u_int current)
|
hfsc_more_slots(u_int cur) /* PFLINUX: 'current' is a kernel macro */
|
||||||
{
|
{
|
||||||
u_int want = current * 2;
|
u_int want = cur * 2;
|
||||||
|
|
||||||
return (want > HFSC_MAX_CLASSES ? HFSC_MAX_CLASSES : want);
|
return (want > HFSC_MAX_CLASSES ? HFSC_MAX_CLASSES : want);
|
||||||
}
|
}
|
||||||
@@ -481,7 +481,16 @@ hfsc_pf_qstats(struct pf_queuespec *q, void *ubuf, int *nbytes)
|
|||||||
if (hif == NULL)
|
if (hif == NULL)
|
||||||
return (EBADF);
|
return (EBADF);
|
||||||
|
|
||||||
if ((cl = hfsc_clh2cph(hif, q->qid)) == NULL) {
|
/*
|
||||||
|
* A root queue's class is created with handle HFSC_ROOT_CLASS | qid
|
||||||
|
* (see hfsc_pf_addqueue), so looking the queue up by its plain pf qid
|
||||||
|
* only finds non-root classes: fall back to the root handle, otherwise
|
||||||
|
* "pfctl -s queue" fails with EINVAL on the first (root) queue.
|
||||||
|
*/
|
||||||
|
if ((cl = hfsc_clh2cph(hif, q->qid)) == NULL &&
|
||||||
|
(q->flags & PFQS_ROOTCLASS))
|
||||||
|
cl = hfsc_clh2cph(hif, HFSC_ROOT_CLASS | q->qid);
|
||||||
|
if (cl == NULL) {
|
||||||
ifq_q_leave(&ifp->if_snd, hif);
|
ifq_q_leave(&ifp->if_snd, hif);
|
||||||
return (EINVAL);
|
return (EINVAL);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -662,6 +662,11 @@ pf_create_queues(void)
|
|||||||
}
|
}
|
||||||
|
|
||||||
qif->disc = qif->pfqops->pfq_alloc(ifp);
|
qif->disc = qif->pfqops->pfq_alloc(ifp);
|
||||||
|
if (qif->disc == NULL) {
|
||||||
|
error = ENOBUFS;
|
||||||
|
free(qif, M_PF, sizeof(*qif));
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
qif->next = list;
|
qif->next = list;
|
||||||
list = qif;
|
list = qif;
|
||||||
|
|||||||
@@ -204,6 +204,22 @@ pfk_pkt_work(struct work_struct *w)
|
|||||||
|
|
||||||
|
|
||||||
if (m == m0) {
|
if (m == m0) {
|
||||||
|
/*
|
||||||
|
* Queueing: when a pf `queue' rule attached a scheduler to the
|
||||||
|
* egress interface, hand the packet to it instead of sending it
|
||||||
|
* straight out - this is what makes bandwidth limits real.
|
||||||
|
*/
|
||||||
|
if (action == PF_PASS && pk->dir == PF_OUT &&
|
||||||
|
pskb_may_pull(pk->skb, sizeof(struct iphdr)) &&
|
||||||
|
skb_dst(pk->skb) && !IS_ERR(skb_dst(pk->skb)) &&
|
||||||
|
pf_kifq_input(skb_dst(pk->skb)->dev ?
|
||||||
|
pfk_ifp_bydev(skb_dst(pk->skb)->dev) : NULL, m)) {
|
||||||
|
orig = pk->skb;
|
||||||
|
pk->skb = NULL; /* the queue owns the packet */
|
||||||
|
kfree_skb(orig);
|
||||||
|
kfree(pk);
|
||||||
|
return;
|
||||||
|
}
|
||||||
/* same buffer, possibly rewritten in place: our wrapper
|
/* same buffer, possibly rewritten in place: our wrapper
|
||||||
* held one extra skb reference — release it */
|
* held one extra skb reference — release it */
|
||||||
m_freem(m);
|
m_freem(m);
|
||||||
|
|||||||
@@ -115,11 +115,22 @@ pfk_pflog_pull(struct mbuf *m, char *buf, size_t want)
|
|||||||
{
|
{
|
||||||
struct sk_buff *skb = m ? m_to_skb(m) : NULL;
|
struct sk_buff *skb = m ? m_to_skb(m) : NULL;
|
||||||
size_t n;
|
size_t n;
|
||||||
|
int off;
|
||||||
|
|
||||||
if (skb == NULL)
|
if (skb == NULL)
|
||||||
return 0;
|
return 0;
|
||||||
n = min_t(size_t, want, skb->len);
|
/*
|
||||||
if (skb_copy_bits(skb, 0, buf, n))
|
* BSD logs from the network header; pf may already have advanced the
|
||||||
|
* mbuf/skb data pointer to the transport header while parsing, so
|
||||||
|
* anchor explicitly at the IP header instead of at skb->data.
|
||||||
|
*/
|
||||||
|
off = skb_network_offset(skb);
|
||||||
|
if (off < 0)
|
||||||
|
off = 0;
|
||||||
|
if (off > skb->len)
|
||||||
|
return 0;
|
||||||
|
n = min_t(size_t, want, skb->len - off);
|
||||||
|
if (skb_copy_bits(skb, off, buf, n))
|
||||||
return 0;
|
return 0;
|
||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,84 +0,0 @@
|
|||||||
{"version": 2, "width": 80, "height": 24, "timestamp": 1794449787, "env": {"SHELL": null, "TERM": "dumb"}}
|
|
||||||
[1.414508, "o", "\u001b[1m===============================================================\u001b[0m\r\n\u001b[1m PF on Linux - the OpenBSD PF 8.0 engine inside the Linux kernel\u001b[0m\r\n\u001b[1m===============================================================\u001b[0m\r\n\r\nLab: 3 Alpine VMs under libvirt.\r\n pftest-pf0 = firewall (kernel 7.3.4 built from source + pf.ko)\r\n pftest-cli = client 10.0.1.10 pftest-srv = server 10.0.2.20\r\n\r\n\u001b[1m--- 1. our kernel, the OpenBSD engine as a module ---\u001b[0m\r\n\u001b[1;36mpftest-pf0# uname -r\u001b[0m\r\n"]
|
|
||||||
[1.655866, "o", "7.3.4\r\n"]
|
|
||||||
[2.058621, "o", "\u001b[1;36mpftest-pf0# lsmod | grep '^pf ' || echo 'pf: not loaded yet'\u001b[0m\r\n"]
|
|
||||||
[2.276696, "o", "pf: not loaded yet\r\n"]
|
|
||||||
[2.680328, "o", "\u001b[1;36mpftest-pf0# insmod /opt/pf/pf.ko && dmesg | tail -3\u001b[0m\r\n"]
|
|
||||||
[2.917691, "o", "[ 146.252272] NET: Registered PF_IUCV protocol family\r\n[ 146.252362] pfk: netfilter hooks registered (10, netisr wq data path)\r\n[ 146.252423] pfk: PF-on-Linux engine loaded (OpenBSD 8.0 base)\r\n"]
|
|
||||||
[3.320802, "o", "\r\n\u001b[1m--- 2. pfctl talks to the module over the OpenBSD ioctl ABI ---\u001b[0m\r\n\u001b[1;36mpftest-pf0# /opt/pf/pfctl -s info | head -4\u001b[0m\r\n"]
|
|
||||||
[3.598837, "o", "Status: Disabled for 0 days 00:02:26 Debug: err\r\n\r\nState Table Total Rate\r\n current entries 0 \r\n"]
|
|
||||||
[4.00249, "o", "\u001b[1;36mpftest-pf0# /opt/pf/pfctl -e && /opt/pf/pfctl -s info | head -1\u001b[0m\r\n"]
|
|
||||||
[4.274878, "o", "Status: Enabled Debug: err\r\npf enabled\r\n"]
|
|
||||||
[4.678074, "o", "\r\n\u001b[1m--- 3. a real pf.conf: tables, stateful rules, an explicit drop ---\u001b[0m\r\n"]
|
|
||||||
[4.678128, "o", "\u001b[1;36mpftest-pf0# cat /tmp/demo-pf.conf\u001b[0m\r\n"]
|
|
||||||
[4.954849, "o", "# PF-on-Linux demo ruleset (pf.conf syntax, real OpenBSD pfctl)\r\nset block-policy return\r\ntable <trusted> { 10.0.1.0/24 }\r\n\r\n# transit traffic: stateful, table-driven\r\npass in quick on ingress proto icmp from <trusted> keep state\r\npass out quick on egress proto icmp keep state\r\n\r\n# deny a specific destination\r\nblock drop in quick proto icmp to 10.0.9.99\r\n\r\n# everything else\r\npass in quick\r\npass out quick\r\n"]
|
|
||||||
[5.358199, "o", "\u001b[1;36mpftest-pf0# /opt/pf/pfctl -f /tmp/demo-pf.conf && echo 'rules loaded'\u001b[0m\r\n"]
|
|
||||||
[5.630704, "o", "rules loaded\r\n"]
|
|
||||||
[6.034552, "o", "\u001b[1;36mpftest-pf0# /opt/pf/pfctl -sr\u001b[0m\r\n"]
|
|
||||||
[6.322794, "o", "pass in quick on ingress proto icmp from <trusted> to any\r\npass out quick on egress proto icmp all\r\nblock drop in quick inet proto icmp from any to 10.0.9.99\r\npass in quick all flags S/SA\r\npass out quick all flags S/SA\r\n"]
|
|
||||||
[6.725786, "o", "\r\n"]
|
|
||||||
[6.72584, "o", "\u001b[1m--- 4. transit traffic is allowed and stateful ---\u001b[0m\r\n\u001b[1;36mpftest-cli# ping -c2 -W2 10.0.2.20 | tail -3\u001b[0m\r\n"]
|
|
||||||
[7.977099, "o", "--- 10.0.2.20 ping statistics ---\r\n2 packets transmitted, 2 packets received, 0% packet loss\r\nround-trip min/avg/max = 0.339/0.351/0.363 ms\r\n"]
|
|
||||||
[8.379918, "o", "\u001b[1;36mpftest-pf0# /opt/pf/pfctl -ss | grep -E 'icmp' | head -4\u001b[0m\r\n"]
|
|
||||||
[8.634572, "o", "all icmp 10.0.2.20:8 <- 10.0.1.10:5449 0:0\r\nall icmp 10.0.1.10:5449 -> 10.0.2.20:8 0:0\r\n"]
|
|
||||||
[9.036709, "o", "\r\n\u001b[1m--- 5. tables are live objects ---\u001b[0m\r\n\u001b[1;36mpftest-pf0# /opt/pf/pfctl -t trusted -T show\u001b[0m\r\n"]
|
|
||||||
[9.298627, "o", " 10.0.1.0/24\r\n"]
|
|
||||||
[9.701487, "o", "\u001b[1;36mpftest-pf0# /opt/pf/pfctl -s Tables\u001b[0m\r\n"]
|
|
||||||
[9.974937, "o", "trusted\r\n"]
|
|
||||||
[10.378208, "o", "\r\n\u001b[1m--- 6. the drop rule actually drops ---\u001b[0m\r\n\u001b[1;36mpftest-pf0# cat > /tmp/demo-block.conf <<EOF\r\nblock drop in quick proto icmp to 10.0.2.20\r\npass in quick\r\npass out quick\r\nEOF\u001b[0m\r\n"]
|
|
||||||
[11.033956, "o", "\u001b[1;36mpftest-pf0# /opt/pf/pfctl -f /tmp/demo-block.conf && echo 'block rules loaded'\u001b[0m\r\n"]
|
|
||||||
[11.310724, "o", "block rules loaded\r\n"]
|
|
||||||
[11.714107, "o", "\u001b[1;36mpftest-cli# ping -c2 -W2 10.0.2.20 | tail -3\u001b[0m\r\n"]
|
|
||||||
[14.969152, "o", "\r\n--- 10.0.2.20 ping statistics ---\r\n2 packets transmitted, 0 packets received, 100% packet loss\r\n"]
|
|
||||||
[15.372543, "o", "\u001b[1;36mpftest-pf0# /opt/pf/pfctl -sr -vv | head -6\u001b[0m\r\n"]
|
|
||||||
[15.66699, "o", "@0 block drop in quick inet proto icmp from any to 10.0.2.20\r\n [ Evaluations: 6 Packets: 2 Bytes: 168 States: 0 ]\r\n [ Inserted: uid 0 pid 1687 State Creations: 0 ]\r\n@1 pass in quick all flags S/SA\r\n [ Evaluations: 1 Packets: 28 Bytes: 9296 States: 1 ]\r\n [ Inserted: uid 0 pid 1687 State Creations: 1 ]\r\n"]
|
|
||||||
[16.07016, "o", "\u001b[1;36mpftest-pf0# /opt/pf/pfctl -f /tmp/demo-pf.conf && echo 'back to the stateful ruleset'\u001b[0m\r\n"]
|
|
||||||
[16.338781, "o", "back to the stateful ruleset\r\n"]
|
|
||||||
[16.742479, "o", "\r\n\u001b[1m--- 7. NAT: the source is translated on the way out, restored on the way back ---\u001b[0m\r\n"]
|
|
||||||
[16.742537, "o", "\u001b[1;36mpftest-pf0# cat > /tmp/demo-nat.conf <<EOF\r\npass out quick on egress from 10.0.1.0/24 to any nat-to (eth2)\r\npass in quick on ingress proto icmp keep state\r\npass out quick on egress proto icmp keep state\r\nEOF\u001b[0m\r\n"]
|
|
||||||
[17.422636, "o", "\u001b[1;36mpftest-pf0# /opt/pf/pfctl -f /tmp/demo-nat.conf && echo 'nat rules loaded'\u001b[0m\r\n"]
|
|
||||||
[17.686794, "o", "nat rules loaded\r\n"]
|
|
||||||
[18.090339, "o", "\u001b[1;36mpftest-cli# ping -c2 -W2 10.0.2.20 | tail -3\u001b[0m\r\n"]
|
|
||||||
[19.341141, "o", "--- 10.0.2.20 ping statistics ---\r\n2 packets transmitted, 2 packets received, 0% packet loss\r\nround-trip min/avg/max = 0.276/0.317/0.359 ms\r\n"]
|
|
||||||
[19.744749, "o", "\u001b[1;36mpftest-pf0# /opt/pf/pfctl -ss | grep -E 'icmp' | head -4\u001b[0m\r\n"]
|
|
||||||
[20.010971, "o", "all icmp 10.0.2.20:8 <- 10.0.1.10:5461 0:0\r\nall icmp 10.0.2.1:43040 (10.0.1.10:5461) -> 10.0.2.20:8 0:0\r\n"]
|
|
||||||
[20.414328, "o", "\u001b[1;36mpftest-pf0# /opt/pf/pfctl -f /tmp/demo-pf.conf && echo 'back to the stateful ruleset'\u001b[0m\r\n"]
|
|
||||||
[20.690817, "o", "back to the stateful ruleset\r\n"]
|
|
||||||
[21.094035, "o", "\r\n\u001b[1m--- 8. pflog: logged packets reach userspace (/dev/pflog) ---\u001b[0m\r\n"]
|
|
||||||
[21.094088, "o", "\u001b[1;36mpftest-pf0# cat > /tmp/demo-log.conf <<EOF\r\npass in quick log proto icmp from 10.0.1.10 keep state\r\npass out quick\r\nEOF\u001b[0m\r\n"]
|
|
||||||
[21.762242, "o", "\u001b[1;36mpftest-pf0# /opt/pf/pfctl -f /tmp/demo-log.conf && echo 'log rule loaded'\u001b[0m\r\n"]
|
|
||||||
[22.034943, "o", "log rule loaded\r\n"]
|
|
||||||
[22.438523, "o", "\u001b[1;36mpftest-pf0# chmod +x /tmp/pflogd; rm -f /tmp/pflog.pcap; (setsid /tmp/pflogd -c 2 /tmp/pflog.pcap >/tmp/pflogd.log 2>&1 </dev/null &); sleep 1; echo 'pflogd collecting 2 records'\u001b[0m\r\n"]
|
|
||||||
[23.681454, "o", "pflogd collecting 2 records\r\n"]
|
|
||||||
[24.084864, "o", "\u001b[1;36mpftest-cli# ping -c2 -W2 10.0.2.20 | tail -2\u001b[0m\r\n"]
|
|
||||||
[25.333003, "o", "2 packets transmitted, 2 packets received, 0% packet loss\r\nround-trip min/avg/max = 0.352/0.364/0.376 ms\r\n"]
|
|
||||||
[27.737616, "o", "\u001b[1;36mpftest-pf0# cat /tmp/pflogd.log; ls -l /tmp/pflog.pcap; echo 'pcap header (magic d4c3b2a1, linktype 0x75=DLT_PFLOG):'; od -An -tx1 -N24 /tmp/pflog.pcap\u001b[0m\r\n"]
|
|
||||||
[28.026778, "o", "pflogd: 2 records\r\n-rw------- 1 root root 324 Nov 12 02:16 /tmp/pflog.pcap\r\npcap header (magic d4c3b2a1, linktype 0x75=DLT_PFLOG):\r\n d4 c3 b2 a1 02 00 04 00 00 00 00 00 00 00 00 00\r\n ff ff 00 00 75 00 00 00\r\n"]
|
|
||||||
[28.430241, "o", "\u001b[1;36mpftest-pf0# pkill pflogd 2>/dev/null; /opt/pf/pfctl -f /tmp/demo-pf.conf >/dev/null\u001b[0m\r\n"]
|
|
||||||
[29.114632, "o", "\r\n\u001b[1m--- 9. route-to: force the next hop away from the normal path ---\u001b[0m\r\n"]
|
|
||||||
[29.114687, "o", "\u001b[1;36mpftest-pf0# cat > /tmp/demo-route.conf <<EOF\r\npass in quick proto icmp from 10.0.1.10 to 10.0.2.20 route-to { 10.0.3.2 } keep state\r\npass out quick\r\nEOF\u001b[0m\r\n"]
|
|
||||||
[29.778259, "o", "\u001b[1;36mpftest-pf0# /opt/pf/pfctl -f /tmp/demo-route.conf && echo 'route-to rule loaded'\u001b[0m\r\n"]
|
|
||||||
[30.050898, "o", "route-to rule loaded\r\n"]
|
|
||||||
[30.45427, "o", "\u001b[1;36mpftest-pf0# (tcpdump -ni eth3 -c 2 -e icmp > /tmp/route.td 2>&1 &); (tcpdump -ni eth2 -c 2 -e icmp > /tmp/norm.td 2>&1 &); sleep 1\u001b[0m\r\n"]
|
|
||||||
[32.089063, "o", "\u001b[1;36mpftest-cli# ping -c1 -W2 10.0.2.20 | tail -2\u001b[0m\r\n"]
|
|
||||||
[34.31667, "o", "--- 10.0.2.20 ping statistics ---\r\n1 packets transmitted, 0 packets received, 100% packet loss\r\n"]
|
|
||||||
[36.72139, "o", "\u001b[1;36mpftest-pf0# echo 'forced egress (eth3, toward the gateway):'; grep -v '^tcpdump\\|^listening\\|^$' /tmp/route.td | head -2; echo 'normal egress (eth2):'; grep -c 'echo request' /tmp/norm.td\u001b[0m\r\n"]
|
|
||||||
[37.006878, "o", "forced egress (eth3, toward the gateway):\r\n02:16:59.958272 52:54:00:aa:00:04 > 52:54:00:dd:00:02, ethertype IPv4 (0x0800), length 98: 10.0.1.10 > 10.0.2.20: ICMP echo request, id 5473, seq 0, length 64\r\nnormal egress (eth2):\r\n0\r\n"]
|
|
||||||
[37.410476, "o", "\u001b[1;36mpftest-pf0# /opt/pf/pfctl -f /tmp/demo-pf.conf >/dev/null\u001b[0m\r\n"]
|
|
||||||
[38.094888, "o", "\r\n\u001b[1m--- 10. divert-to: the packet goes to userspace and comes back ---\u001b[0m\r\n"]
|
|
||||||
[38.095122, "o", "\u001b[1;36mpftest-pf0# cat /tmp/demo-divert.conf\u001b[0m\r\n"]
|
|
||||||
[38.35866, "o", "pass in quick proto icmp from 10.0.1.10 to 10.0.1.1 divert-to \"10.0.1.1\" port 10000 keep state\r\npass out quick\r\n"]
|
|
||||||
[38.763007, "o", "\u001b[1;36mpftest-pf0# /opt/pf/pfctl -f /tmp/demo-divert.conf && echo 'divert rule loaded'\u001b[0m\r\n"]
|
|
||||||
[39.022814, "o", "divert rule loaded\r\n"]
|
|
||||||
[39.42606, "o", "\u001b[1;36mpftest-pf0# chmod +x /tmp/pdivert; (setsid /tmp/pdivert -p 10000 -f >/tmp/pdivert.log 2>&1 </dev/null &); sleep 1; pgrep pdivert\u001b[0m\r\n"]
|
|
||||||
[40.654519, "o", "1794\r\n"]
|
|
||||||
[41.056785, "o", "\u001b[1;36mpftest-cli# ping -c2 -W2 10.0.1.1 | tail -3\u001b[0m\r\n"]
|
|
||||||
[42.284778, "o", "--- 10.0.1.1 ping statistics ---\r\n2 packets transmitted, 2 packets received, 0% packet loss\r\nround-trip min/avg/max = 0.177/0.224/0.271 ms\r\n"]
|
|
||||||
[42.691426, "o", "\u001b[1;36mpftest-pf0# head -3 /tmp/pdivert.log\u001b[0m\r\n"]
|
|
||||||
[42.942601, "o", "pdivert: listening on divert port 10000\r\npdivert: got 84 bytes 10.0.1.10 -> 10.0.1.1\r\npdivert: got 84 bytes 10.0.1.10 -> 10.0.1.1\r\n"]
|
|
||||||
[43.344696, "o", " (pdivert flipped the last payload byte and fixed the ICMP checksum)\r\n\r\n\u001b[1m--- 11. shutdown ---\u001b[0m\r\n\u001b[1;36mpftest-pf0# pkill pdivert; /opt/pf/pfctl -f /dev/null; /opt/pf/pfctl -d; echo 'pf disabled'\u001b[0m\r\n"]
|
|
||||||
[43.626606, "o", "pf disabled\r\npf disabled\r\n"]
|
|
||||||
[44.028882, "o", "\u001b[1;36mpftest-pf0# lsmod | grep '^pf '\u001b[0m\r\n"]
|
|
||||||
[44.286597, "o", "pf 552960 0\r\n"]
|
|
||||||
[44.688511, "o", "\r\n\u001b[1mdemo over - port, gates and docs:\u001b[0m\r\n\u001b[1m https://git.labexposed.com/lgcosta/pf-linux-port\u001b[0m\r\n"]
|
|
||||||
[44.688631, "o", "\r\n"]
|
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 2.8 MiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,161 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
"""
|
|
||||||
PF-on-Linux demo recorder -> asciicast v2 (.cast)
|
|
||||||
|
|
||||||
Every byte shown comes from the REAL lab (Alpine VMs under libvirt, kernel
|
|
||||||
7.3.4 built from source with our pf.ko). Only the pacing is scripted.
|
|
||||||
|
|
||||||
Usage:
|
|
||||||
./scripts/demo-record.py [--out out/demo-pf-linux.cast] [--quick]
|
|
||||||
|
|
||||||
Play back with: asciinema play out/demo-pf-linux.cast
|
|
||||||
Upload with: asciinema upload out/demo-pf-linux.cast
|
|
||||||
"""
|
|
||||||
import argparse
|
|
||||||
import json
|
|
||||||
import os
|
|
||||||
import shlex
|
|
||||||
import subprocess
|
|
||||||
import sys
|
|
||||||
import time
|
|
||||||
|
|
||||||
REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
||||||
DEB = os.environ.get("DEB", "root@192.168.1.242")
|
|
||||||
PF0 = os.environ.get("PF0_IP", "192.168.122.8")
|
|
||||||
CLI = os.environ.get("CLI_IP", "10.0.1.10")
|
|
||||||
SRV = os.environ.get("SRV_IP", "10.0.2.20")
|
|
||||||
KEY = os.path.join(os.environ.get("HOME", "/root"), ".ssh/pflab_ed")
|
|
||||||
|
|
||||||
CONF = """# PF-on-Linux demo ruleset (pf.conf syntax, real OpenBSD pfctl)
|
|
||||||
set block-policy return
|
|
||||||
table <trusted> { 10.0.1.0/24 }
|
|
||||||
|
|
||||||
# transit traffic: stateful, table-driven
|
|
||||||
pass in quick on ingress proto icmp from <trusted> keep state
|
|
||||||
pass out quick on egress proto icmp keep state
|
|
||||||
|
|
||||||
# deny a specific destination
|
|
||||||
block drop in quick proto icmp to 10.0.9.99
|
|
||||||
|
|
||||||
# everything else
|
|
||||||
pass in quick
|
|
||||||
pass out quick
|
|
||||||
"""
|
|
||||||
|
|
||||||
DIVERT_CONF = """pass in quick proto icmp from 10.0.1.10 to 10.0.1.1 divert-to "10.0.1.1" port 10000 keep state
|
|
||||||
pass out quick
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
def sh(cmd, timeout=40):
|
|
||||||
p = subprocess.run(cmd, shell=True, capture_output=True, text=True,
|
|
||||||
timeout=timeout)
|
|
||||||
return (p.stdout or "") + (p.stderr or "")
|
|
||||||
|
|
||||||
|
|
||||||
def on_pf0(cmd, timeout=40):
|
|
||||||
return sh(f"ssh -o ConnectTimeout=8 -o StrictHostKeyChecking=accept-new "
|
|
||||||
f"-i {KEY} root@{PF0} {shlex.quote(cmd)}", timeout)
|
|
||||||
|
|
||||||
|
|
||||||
def on_vm(ip, cmd, timeout=40):
|
|
||||||
return sh(f"ssh -o ConnectTimeout=8 -o StrictHostKeyChecking=accept-new "
|
|
||||||
f"-i {KEY} root@{ip} {shlex.quote(cmd)}", timeout)
|
|
||||||
|
|
||||||
|
|
||||||
def on_deb(cmd, timeout=40):
|
|
||||||
return sh(f"ssh -o ConnectTimeout=8 {DEB} {shlex.quote(cmd)}", timeout)
|
|
||||||
|
|
||||||
|
|
||||||
def stage():
|
|
||||||
"""put the demo ruleset + pdivert on pf0 (through the lab host)"""
|
|
||||||
with open("/tmp/demo-pf.conf", "w") as f:
|
|
||||||
f.write(CONF)
|
|
||||||
with open("/tmp/demo-divert.conf", "w") as f:
|
|
||||||
f.write(DIVERT_CONF)
|
|
||||||
sh(f"scp -q /tmp/demo-pf.conf /tmp/demo-divert.conf "
|
|
||||||
f"{os.path.join(REPO, 'tools/out/pdivert')} {DEB}:/tmp/", timeout=90)
|
|
||||||
on_deb(f"scp -q -o StrictHostKeyChecking=accept-new -i {KEY} "
|
|
||||||
f"/tmp/demo-pf.conf /tmp/demo-divert.conf /tmp/pdivert "
|
|
||||||
f"root@{PF0}:/tmp/ 2>&1", timeout=90)
|
|
||||||
|
|
||||||
|
|
||||||
SCENARIO = [
|
|
||||||
("local", "echo '=== PF on Linux — OpenBSD PF 8.0 engine running inside the Linux 7.3.4 kernel ==='"),
|
|
||||||
("local", "echo 'lab: 3 Alpine VMs under libvirt (pf0 = firewall, cli/srv = transit endpoints)'"),
|
|
||||||
("pf0", "uname -r"),
|
|
||||||
("pf0", "lsmod | grep '^pf ' || echo 'pf: not loaded'"),
|
|
||||||
("pf0", "insmod /opt/pf/pf.ko && dmesg | tail -3"),
|
|
||||||
("pf0", "/opt/pf/pfctl -s info | head -4"),
|
|
||||||
("pf0", "/opt/pf/pfctl -e && /opt/pf/pfctl -s info | head -1"),
|
|
||||||
("pf0", "cat /tmp/demo-pf.conf"),
|
|
||||||
("pf0", "/opt/pf/pfctl -f /tmp/demo-pf.conf && echo 'rules loaded' && /opt/pf/pfctl -sr"),
|
|
||||||
("pf0", "/opt/pf/pfctl -sr -vv | head -8"),
|
|
||||||
("cli", f"ping -c2 -W2 {SRV} | tail -3"),
|
|
||||||
("pf0", "/opt/pf/pfctl -ss"),
|
|
||||||
("pf0", "/opt/pf/pfctl -t trusted -T show"),
|
|
||||||
("pf0", "/opt/pf/pfctl -s Tables"),
|
|
||||||
("cli", "ping -c2 -W2 10.0.9.99 | tail -3"),
|
|
||||||
("pf0", "/opt/pf/pfctl -sr -vv | head -6"),
|
|
||||||
("pf0", "/opt/pf/pfctl -f /tmp/demo-divert.conf && echo 'divert rule loaded'"),
|
|
||||||
("pf0", "(setsid /tmp/pdivert -p 10000 -f >/tmp/pdivert.log 2>&1 </dev/null &); sleep 1; pgrep pdivert"),
|
|
||||||
("cli", "ping -c2 -W2 10.0.1.1 | tail -3"),
|
|
||||||
("pf0", "head -3 /tmp/pdivert.log"),
|
|
||||||
("pf0", "pkill pdivert; /opt/pf/pfctl -f /dev/null; /opt/pf/pfctl -d; echo 'pf disabled'"),
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def record(out_path, quick=False):
|
|
||||||
os.makedirs(os.path.dirname(out_path) or ".", exist_ok=True)
|
|
||||||
events = []
|
|
||||||
t = 0.0
|
|
||||||
|
|
||||||
def emit(text, delay):
|
|
||||||
nonlocal t
|
|
||||||
t += delay
|
|
||||||
events.append([round(t, 3), "o", text])
|
|
||||||
|
|
||||||
header = {
|
|
||||||
"version": 2, "width": 100, "height": 26,
|
|
||||||
"timestamp": int(time.time()),
|
|
||||||
"title": "PF on Linux — OpenBSD PF engine inside the Linux kernel (lab demo)",
|
|
||||||
"env": {"SHELL": "/bin/sh", "TERM": "xterm-256color"},
|
|
||||||
}
|
|
||||||
runners = {
|
|
||||||
"pf0": lambda c: on_pf0(c),
|
|
||||||
"cli": lambda c: on_vm(CLI, c),
|
|
||||||
"srv": lambda c: on_vm(SRV, c),
|
|
||||||
"local": lambda c: sh(c),
|
|
||||||
}
|
|
||||||
labels = {"pf0": "pftest-pf0", "cli": "pftest-cli", "srv": "pftest-srv",
|
|
||||||
"local": "lab-host"}
|
|
||||||
|
|
||||||
stage()
|
|
||||||
with open(out_path, "w") as f:
|
|
||||||
f.write(json.dumps(header) + "\n")
|
|
||||||
for kind, cmd in SCENARIO:
|
|
||||||
emit(f"\x1b[1;36m{labels[kind]}\x1b[0m:\x1b[1;37m {cmd}\x1b[0m\r\n",
|
|
||||||
0.35)
|
|
||||||
try:
|
|
||||||
out = runners[kind](cmd)
|
|
||||||
except subprocess.TimeoutExpired:
|
|
||||||
out = "[timeout]\r\n"
|
|
||||||
except Exception as e: # noqa: BLE001
|
|
||||||
out = f"[error: {e}]\r\n"
|
|
||||||
out = out.replace("\r\n", "\n").replace("\n", "\r\n")
|
|
||||||
emit(out or "(no output)\r\n", min(1.4, 0.3 + len(out) / 5000.0))
|
|
||||||
with open(out_path, "a") as f:
|
|
||||||
for ev in events:
|
|
||||||
f.write(json.dumps(ev) + "\n")
|
|
||||||
print(f"WROTE {out_path}: {len(events)} events, {t:.1f}s")
|
|
||||||
print("play: asciinema play " + out_path)
|
|
||||||
print("upload: asciinema upload " + out_path)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
ap = argparse.ArgumentParser()
|
|
||||||
ap.add_argument("--out", default=os.path.join(REPO, "out",
|
|
||||||
"demo-pf-linux.cast"))
|
|
||||||
ap.add_argument("--quick", action="store_true")
|
|
||||||
a = ap.parse_args()
|
|
||||||
record(a.out, a.quick)
|
|
||||||
@@ -1,182 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
# Demo session for PF-on-Linux: a scripted terminal tour of the lab.
|
|
||||||
# Recorded with: asciinema rec out/demo-pf-linux.cast -c "bash scripts/demo-session.sh"
|
|
||||||
#
|
|
||||||
# Everything printed is the REAL output of the lab (Alpine VMs under libvirt
|
|
||||||
# running the kernel built from source with our pf.ko). The VM management
|
|
||||||
# addresses are resolved from the hypervisor (they are DHCP), the lab
|
|
||||||
# topology (10.0.1.0/24, 10.0.2.0/24, ...) is fixed.
|
|
||||||
set -u
|
|
||||||
|
|
||||||
DEB=${DEB:-root@192.168.1.242}
|
|
||||||
CLI_IP=${CLI_IP:-10.0.1.10}
|
|
||||||
SRV_IP=${SRV_IP:-10.0.2.20}
|
|
||||||
KEY=${KEY:-$HOME/.ssh/pflab_ed}
|
|
||||||
|
|
||||||
bold() { printf '\033[1m%s\033[0m\n' "$*"; }
|
|
||||||
cyan() { printf '\033[1;36m%s\033[0m\n' "$*"; }
|
|
||||||
|
|
||||||
# management address of a lab VM (DHCP, so ask the hypervisor)
|
|
||||||
mgmt() {
|
|
||||||
ssh -o ConnectTimeout=8 "$DEB" "virsh domifaddr $1 | awk '/vnet/{print \$4}' | cut -d/ -f1" \
|
|
||||||
2>/dev/null | tr -d '\r' | head -1
|
|
||||||
}
|
|
||||||
|
|
||||||
PF0_MGMT=$(mgmt pftest-pf0)
|
|
||||||
CLI_MGMT=$(mgmt pftest-cli)
|
|
||||||
[ -n "$PF0_MGMT" ] || PF0_MGMT=192.168.122.8
|
|
||||||
[ -n "$CLI_MGMT" ] || CLI_MGMT=192.168.122.80
|
|
||||||
|
|
||||||
# run a command on a lab VM (through the hypervisor)
|
|
||||||
onvm() {
|
|
||||||
local ip=$1; shift
|
|
||||||
ssh -o ConnectTimeout=10 -o StrictHostKeyChecking=accept-new \
|
|
||||||
-o ProxyJump=root@192.168.1.242 -i "$KEY" root@"$ip" "$@" 2>&1
|
|
||||||
}
|
|
||||||
|
|
||||||
p0() {
|
|
||||||
cyan "pftest-pf0# $*"
|
|
||||||
onvm "$PF0_MGMT" "$@"
|
|
||||||
sleep 0.4
|
|
||||||
}
|
|
||||||
vmc() {
|
|
||||||
cyan "pftest-cli# $*"
|
|
||||||
onvm "$CLI_MGMT" "$@"
|
|
||||||
sleep 0.4
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
CONF=/tmp/demo-pf.conf
|
|
||||||
DIV=/tmp/demo-divert.conf
|
|
||||||
|
|
||||||
cat > "$CONF" <<'CONF'
|
|
||||||
# PF-on-Linux demo ruleset (pf.conf syntax, real OpenBSD pfctl)
|
|
||||||
set block-policy return
|
|
||||||
table <trusted> { 10.0.1.0/24 }
|
|
||||||
|
|
||||||
# transit traffic: stateful, table-driven
|
|
||||||
pass in quick on ingress proto icmp from <trusted> keep state
|
|
||||||
pass out quick on egress proto icmp keep state
|
|
||||||
|
|
||||||
# deny a specific destination
|
|
||||||
block drop in quick proto icmp to 10.0.9.99
|
|
||||||
|
|
||||||
# everything else
|
|
||||||
pass in quick
|
|
||||||
pass out quick
|
|
||||||
CONF
|
|
||||||
|
|
||||||
cat > "$DIV" <<CONF
|
|
||||||
pass in quick proto icmp from $CLI_IP to 10.0.1.1 divert-to "10.0.1.1" port 10000 keep state
|
|
||||||
pass out quick
|
|
||||||
CONF
|
|
||||||
|
|
||||||
# the pf0 interface towards the server (used by the NAT pool)
|
|
||||||
EGRESS_IF=$(onvm "$PF0_MGMT" "ip route get $SRV_IP | grep -o 'dev [a-z0-9]*' | cut -d' ' -f2" | tr -d '\r')
|
|
||||||
[ -n "$EGRESS_IF" ] || EGRESS_IF=eth2
|
|
||||||
|
|
||||||
scp -q -o ProxyJump=root@192.168.1.242 -i "$KEY" "$CONF" "$DIV" \
|
|
||||||
tools/out/pdivert tools/out/pflogd "root@$PF0_MGMT:/tmp/" 2>/dev/null
|
|
||||||
|
|
||||||
bold "==============================================================="
|
|
||||||
bold " PF on Linux - the OpenBSD PF 8.0 engine inside the Linux kernel"
|
|
||||||
bold "==============================================================="
|
|
||||||
echo
|
|
||||||
echo "Lab: 3 Alpine VMs under libvirt."
|
|
||||||
echo " pftest-pf0 = firewall (kernel 7.3.4 built from source + pf.ko)"
|
|
||||||
echo " pftest-cli = client 10.0.1.10 pftest-srv = server 10.0.2.20"
|
|
||||||
echo
|
|
||||||
|
|
||||||
bold "--- 1. our kernel, the OpenBSD engine as a module ---"
|
|
||||||
p0 "uname -r"
|
|
||||||
p0 "lsmod | grep '^pf ' || echo 'pf: not loaded yet'"
|
|
||||||
p0 "insmod /opt/pf/pf.ko && dmesg | tail -3"
|
|
||||||
echo
|
|
||||||
|
|
||||||
bold "--- 2. pfctl talks to the module over the OpenBSD ioctl ABI ---"
|
|
||||||
p0 "/opt/pf/pfctl -s info | head -4"
|
|
||||||
p0 "/opt/pf/pfctl -e && /opt/pf/pfctl -s info | head -1"
|
|
||||||
echo
|
|
||||||
|
|
||||||
bold "--- 3. a real pf.conf: tables, stateful rules, an explicit drop ---"
|
|
||||||
p0 "cat /tmp/demo-pf.conf"
|
|
||||||
p0 "/opt/pf/pfctl -f /tmp/demo-pf.conf && echo 'rules loaded'"
|
|
||||||
p0 "/opt/pf/pfctl -sr"
|
|
||||||
echo
|
|
||||||
|
|
||||||
bold "--- 4. transit traffic is allowed and stateful ---"
|
|
||||||
vmc "ping -c2 -W2 $SRV_IP | tail -3"
|
|
||||||
p0 "/opt/pf/pfctl -ss | grep -E 'icmp' | head -4"
|
|
||||||
echo
|
|
||||||
|
|
||||||
bold "--- 5. tables are live objects ---"
|
|
||||||
p0 "/opt/pf/pfctl -t trusted -T show"
|
|
||||||
p0 "/opt/pf/pfctl -s Tables"
|
|
||||||
echo
|
|
||||||
|
|
||||||
bold "--- 6. the drop rule actually drops ---"
|
|
||||||
p0 "cat > /tmp/demo-block.conf <<EOF
|
|
||||||
block drop in quick proto icmp to $SRV_IP
|
|
||||||
pass in quick
|
|
||||||
pass out quick
|
|
||||||
EOF"
|
|
||||||
p0 "/opt/pf/pfctl -f /tmp/demo-block.conf && echo 'block rules loaded'"
|
|
||||||
vmc "ping -c2 -W2 $SRV_IP | tail -3"
|
|
||||||
p0 "/opt/pf/pfctl -sr -vv | head -6"
|
|
||||||
p0 "/opt/pf/pfctl -f /tmp/demo-pf.conf && echo 'back to the stateful ruleset'"
|
|
||||||
echo
|
|
||||||
|
|
||||||
bold "--- 7. NAT: the source is translated on the way out, restored on the way back ---"
|
|
||||||
p0 "cat > /tmp/demo-nat.conf <<EOF
|
|
||||||
pass out quick on egress from 10.0.1.0/24 to any nat-to ($EGRESS_IF)
|
|
||||||
pass in quick on ingress proto icmp keep state
|
|
||||||
pass out quick on egress proto icmp keep state
|
|
||||||
EOF"
|
|
||||||
p0 "/opt/pf/pfctl -f /tmp/demo-nat.conf && echo 'nat rules loaded'"
|
|
||||||
vmc "ping -c2 -W2 $SRV_IP | tail -3"
|
|
||||||
p0 "/opt/pf/pfctl -ss | grep -E 'icmp' | head -4"
|
|
||||||
p0 "/opt/pf/pfctl -f /tmp/demo-pf.conf && echo 'back to the stateful ruleset'"
|
|
||||||
echo
|
|
||||||
|
|
||||||
bold "--- 8. pflog: logged packets reach userspace (/dev/pflog) ---"
|
|
||||||
p0 "cat > /tmp/demo-log.conf <<EOF
|
|
||||||
pass in quick log proto icmp from $CLI_IP keep state
|
|
||||||
pass out quick
|
|
||||||
EOF"
|
|
||||||
p0 "/opt/pf/pfctl -f /tmp/demo-log.conf && echo 'log rule loaded'"
|
|
||||||
p0 "chmod +x /tmp/pflogd; rm -f /tmp/pflog.pcap; (setsid /tmp/pflogd -c 2 /tmp/pflog.pcap >/tmp/pflogd.log 2>&1 </dev/null &); sleep 1; echo 'pflogd collecting 2 records'"
|
|
||||||
vmc "ping -c2 -W2 $SRV_IP | tail -2"
|
|
||||||
sleep 2
|
|
||||||
p0 "cat /tmp/pflogd.log; ls -l /tmp/pflog.pcap; echo 'pcap header (magic d4c3b2a1, linktype 0x75=DLT_PFLOG):'; od -An -tx1 -N24 /tmp/pflog.pcap"
|
|
||||||
p0 "pkill pflogd 2>/dev/null; /opt/pf/pfctl -f /tmp/demo-pf.conf >/dev/null"
|
|
||||||
echo
|
|
||||||
|
|
||||||
bold "--- 9. route-to: force the next hop away from the normal path ---"
|
|
||||||
p0 "cat > /tmp/demo-route.conf <<EOF
|
|
||||||
pass in quick proto icmp from $CLI_IP to $SRV_IP route-to { 10.0.3.2 } keep state
|
|
||||||
pass out quick
|
|
||||||
EOF"
|
|
||||||
p0 "/opt/pf/pfctl -f /tmp/demo-route.conf && echo 'route-to rule loaded'"
|
|
||||||
p0 "(tcpdump -ni eth3 -c 2 -e icmp > /tmp/route.td 2>&1 &); (tcpdump -ni eth2 -c 2 -e icmp > /tmp/norm.td 2>&1 &); sleep 1"
|
|
||||||
vmc "ping -c1 -W2 $SRV_IP | tail -2"
|
|
||||||
sleep 2
|
|
||||||
p0 "echo 'forced egress (eth3, toward the gateway):'; grep -v '^tcpdump\|^listening\|^$' /tmp/route.td | head -2; echo 'normal egress (eth2):'; grep -c 'echo request' /tmp/norm.td"
|
|
||||||
p0 "/opt/pf/pfctl -f /tmp/demo-pf.conf >/dev/null"
|
|
||||||
echo
|
|
||||||
|
|
||||||
bold "--- 10. divert-to: the packet goes to userspace and comes back ---"
|
|
||||||
p0 "cat /tmp/demo-divert.conf"
|
|
||||||
p0 "/opt/pf/pfctl -f /tmp/demo-divert.conf && echo 'divert rule loaded'"
|
|
||||||
p0 "chmod +x /tmp/pdivert; (setsid /tmp/pdivert -p 10000 -f >/tmp/pdivert.log 2>&1 </dev/null &); sleep 1; pgrep pdivert"
|
|
||||||
vmc "ping -c2 -W2 10.0.1.1 | tail -3"
|
|
||||||
p0 "head -3 /tmp/pdivert.log"
|
|
||||||
echo " (pdivert flipped the last payload byte and fixed the ICMP checksum)"
|
|
||||||
echo
|
|
||||||
|
|
||||||
bold "--- 11. shutdown ---"
|
|
||||||
p0 "pkill pdivert; /opt/pf/pfctl -f /dev/null; /opt/pf/pfctl -d; echo 'pf disabled'"
|
|
||||||
p0 "lsmod | grep '^pf '"
|
|
||||||
echo
|
|
||||||
bold "demo over - port, gates and docs:"
|
|
||||||
bold " https://git.labexposed.com/lgcosta/pf-linux-port"
|
|
||||||
echo
|
|
||||||
Executable
+149
@@ -0,0 +1,149 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Assemble the final MP4s: silent video track + MiniMax narration + subtitles.
|
||||||
|
|
||||||
|
Each narration segment was recorded as its own audio file, generated by
|
||||||
|
MiniMax with a female voice for the language of the track. The segments are
|
||||||
|
placed at the moment their beat starts on screen, so the voice, the caption
|
||||||
|
and the terminal action always agree.
|
||||||
|
|
||||||
|
./scripts/screencast/assemble.py --phase 04-roteamento --lang en
|
||||||
|
"""
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
|
||||||
|
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
REPO = os.path.dirname(os.path.dirname(HERE))
|
||||||
|
CAPS = os.path.join(REPO, "out", "screencast", "caps")
|
||||||
|
VO = os.path.join(REPO, "out", "screencast", "vo")
|
||||||
|
OUT = os.path.join(REPO, "out", "screencast")
|
||||||
|
ART = os.path.join(OUT, "art")
|
||||||
|
|
||||||
|
# which phases draw the lab diagram over the terminal
|
||||||
|
DIAGRAM_PHASES = {"00-ambiente", "04-roteamento", "05-balanceamento"}
|
||||||
|
# phases that show the module/host instead of the firewall VM
|
||||||
|
HOST_PHASES = {"01-implementacao"}
|
||||||
|
|
||||||
|
PHASE_ORDER = ["00-ambiente", "01-implementacao", "02-ativacao", "03-regras",
|
||||||
|
"04-roteamento", "05-balanceamento", "06-qos"]
|
||||||
|
|
||||||
|
|
||||||
|
def run(cmd):
|
||||||
|
subprocess.run(cmd, check=True)
|
||||||
|
|
||||||
|
|
||||||
|
def probe(path):
|
||||||
|
out = subprocess.run(["ffprobe", "-v", "error", "-show_entries",
|
||||||
|
"format=duration", "-of", "csv=p=0", path],
|
||||||
|
capture_output=True, text=True).stdout.strip()
|
||||||
|
return float(out or 0)
|
||||||
|
|
||||||
|
|
||||||
|
def audio_duration(path):
|
||||||
|
return probe(path)
|
||||||
|
|
||||||
|
|
||||||
|
def make_video(phase, lang):
|
||||||
|
"""Render the silent video (terminal + diagram + burned-in subtitles)."""
|
||||||
|
cast = os.path.join(CAPS, f"{phase}.cast")
|
||||||
|
nar = os.path.join(HERE, "narration", f"{phase}.{lang}.json")
|
||||||
|
if not os.path.exists(cast):
|
||||||
|
raise SystemExit(f"[assemble] missing capture: {cast}")
|
||||||
|
art = None
|
||||||
|
if os.path.isdir(ART):
|
||||||
|
imgs = sorted(f for f in os.listdir(ART) if f.endswith((".jpg", ".png")))
|
||||||
|
if imgs:
|
||||||
|
art = os.path.join(ART, imgs[0])
|
||||||
|
outdir = os.path.join(OUT, lang)
|
||||||
|
os.makedirs(outdir, exist_ok=True)
|
||||||
|
silent = os.path.join(outdir, f"{phase}.silent.mp4")
|
||||||
|
index = PHASE_ORDER.index(phase) + 1
|
||||||
|
cmd = [sys.executable, os.path.join(HERE, "render_video.py"),
|
||||||
|
"--cast", cast, "--narration", nar, "--out", silent,
|
||||||
|
"--lang", lang, "--phase-no", str(index),
|
||||||
|
"--phase-total", str(len(PHASE_ORDER))]
|
||||||
|
if phase in DIAGRAM_PHASES:
|
||||||
|
cmd.append("--diagram")
|
||||||
|
if art:
|
||||||
|
cmd += ["--art", art]
|
||||||
|
run(cmd)
|
||||||
|
return silent
|
||||||
|
|
||||||
|
|
||||||
|
def mux_audio(phase, lang, silent):
|
||||||
|
"""Lay every narration segment on the timeline and add it to the video."""
|
||||||
|
meta = json.load(open(os.path.join(CAPS, f"{phase}.json")))
|
||||||
|
beats = {b["id"]: b for b in meta["beats"]}
|
||||||
|
nar = json.load(open(os.path.join(HERE, "narration", f"{phase}.{lang}.json")))
|
||||||
|
vodir = os.path.join(VO, f"{phase}.{lang}")
|
||||||
|
|
||||||
|
segments = []
|
||||||
|
for i, seg in enumerate(nar["segments"]):
|
||||||
|
beat = beats.get(seg["beat"])
|
||||||
|
audio = os.path.join(vodir, f"{i:02d}-{seg['beat']}.mp3")
|
||||||
|
if beat is None or not os.path.exists(audio):
|
||||||
|
sys.stderr.write(f"[assemble] missing audio: {audio}\n")
|
||||||
|
continue
|
||||||
|
segments.append((beat["start"], audio))
|
||||||
|
|
||||||
|
tmp = os.path.join(OUT, "_tmp")
|
||||||
|
os.makedirs(tmp, exist_ok=True)
|
||||||
|
if not segments:
|
||||||
|
out = os.path.join(tmp, f"{phase}.{lang}.m4v")
|
||||||
|
run(["ffmpeg", "-y", "-loglevel", "error", "-i", silent,
|
||||||
|
"-c:v", "copy", "-an", out])
|
||||||
|
return out
|
||||||
|
|
||||||
|
vdur = probe(silent)
|
||||||
|
inputs = []
|
||||||
|
filters = []
|
||||||
|
# input 0 is the video; the narration files are inputs 1..N
|
||||||
|
for k, (start, audio) in enumerate(segments):
|
||||||
|
inputs += ["-i", audio]
|
||||||
|
delay = int(start * 1000)
|
||||||
|
# pad so every segment reaches the end of the video, then the mix is
|
||||||
|
# trimmed to the video length
|
||||||
|
filters.append(f"[{k + 1}:a]adelay={delay}|{delay},apad[aud{k}]")
|
||||||
|
mix_in = "".join(f"[aud{k}]" for k in range(len(segments)))
|
||||||
|
filters.append(f"{mix_in}amix=inputs={len(segments)}:normalize=0,"
|
||||||
|
f"atrim=0:{vdur:.3f},asetpts=N/SR/TB[mixed]")
|
||||||
|
cmd = ["ffmpeg", "-y", "-loglevel", "error", "-i", silent] + inputs + \
|
||||||
|
["-filter_complex", ";".join(filters),
|
||||||
|
"-map", "0:v", "-map", "[mixed]", "-c:v", "copy", "-c:a", "aac",
|
||||||
|
"-b:a", "160k", "-ar", "44100", "-ac", "2",
|
||||||
|
os.path.join(tmp, f"{phase}.{lang}.m4v")]
|
||||||
|
run(cmd)
|
||||||
|
return os.path.join(tmp, f"{phase}.{lang}.m4v")
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
ap = argparse.ArgumentParser()
|
||||||
|
ap.add_argument("--phase", required=True)
|
||||||
|
ap.add_argument("--lang", required=True, choices=["en", "pt"])
|
||||||
|
ap.add_argument("--keep-silent", action="store_true")
|
||||||
|
args = ap.parse_args()
|
||||||
|
|
||||||
|
phase, lang = args.phase, args.lang
|
||||||
|
silent = make_video(phase, lang)
|
||||||
|
muxed = mux_audio(phase, lang, silent)
|
||||||
|
|
||||||
|
outdir = os.path.join(OUT, lang)
|
||||||
|
os.makedirs(outdir, exist_ok=True)
|
||||||
|
final = os.path.join(outdir, f"{phase}.mp4")
|
||||||
|
run(["ffmpeg", "-y", "-loglevel", "error", "-i", muxed,
|
||||||
|
"-c", "copy", "-movflags", "+faststart", final])
|
||||||
|
if not args.keep_silent:
|
||||||
|
os.remove(silent)
|
||||||
|
os.remove(muxed)
|
||||||
|
|
||||||
|
dur = probe(final)
|
||||||
|
size = os.path.getsize(final) / 1e6
|
||||||
|
print(f"[assemble] {final} {dur:.1f}s {size:.1f}MB")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Executable
+48
@@ -0,0 +1,48 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Build every screencast: capture -> narration -> video -> full movie.
|
||||||
|
#
|
||||||
|
# ./scripts/screencast/build-all.sh # everything
|
||||||
|
# ./scripts/screencast/build-all.sh 04-roteamento
|
||||||
|
set -uo pipefail
|
||||||
|
cd "$(dirname "$0")/../.."
|
||||||
|
REPO=$(pwd)
|
||||||
|
|
||||||
|
PHASES=("$@")
|
||||||
|
[ ${#PHASES[@]} -eq 0 ] && PHASES=(00-ambiente 01-implementacao 02-ativacao 03-regras \
|
||||||
|
04-roteamento 05-balanceamento 06-qos)
|
||||||
|
|
||||||
|
echo "== narration audio =="
|
||||||
|
for lang in en pt; do
|
||||||
|
python3 scripts/screencast/make_audio.py --lang "$lang" | tail -1
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "== sessions =="
|
||||||
|
for p in "${PHASES[@]}"; do
|
||||||
|
# the activation phase shows the module being loaded, so it is the one
|
||||||
|
# that must not have it preloaded
|
||||||
|
extra=""
|
||||||
|
[ "$p" = "02-ativacao" ] || extra="--ensure-pf"
|
||||||
|
python3 scripts/screencast/record.py --plan "scripts/screencast/plans/$p.json" \
|
||||||
|
--out "out/screencast/caps/$p.cast" --pace $extra
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "== videos =="
|
||||||
|
for lang in en pt; do
|
||||||
|
for p in "${PHASES[@]}"; do
|
||||||
|
python3 scripts/screencast/assemble.py --phase "$p" --lang "$lang" || echo "FALHOU: $p $lang"
|
||||||
|
done
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "== full movie =="
|
||||||
|
for lang in en pt; do
|
||||||
|
python3 scripts/screencast/join.py --lang "$lang" || echo "FALHOU: join $lang"
|
||||||
|
done
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "== resultado =="
|
||||||
|
for f in out/screencast/en/*.mp4 out/screencast/pt/*.mp4; do
|
||||||
|
[ -e "$f" ] || continue
|
||||||
|
d=$(ffprobe -v error -show_entries format=duration -of csv=p=0 "$f")
|
||||||
|
a=$(ffprobe -v error -select_streams a -show_entries stream=codec_name -of csv=p=0 "$f")
|
||||||
|
printf '%-52s %6.1fs audio=%s\n' "$f" "$d" "${a:-none}"
|
||||||
|
done
|
||||||
Executable
+189
@@ -0,0 +1,189 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Join the phases into one continuous video per language, with transitions.
|
||||||
|
|
||||||
|
Each phase fades into a short "chapter" card that names it, and the card
|
||||||
|
fades into the next phase - one continuous stream, no hard cuts, so the
|
||||||
|
viewer always knows where they are.
|
||||||
|
|
||||||
|
./scripts/screencast/join.py --lang en
|
||||||
|
"""
|
||||||
|
import argparse
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
|
||||||
|
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
REPO = os.path.dirname(os.path.dirname(HERE))
|
||||||
|
OUT = os.path.join(REPO, "out", "screencast")
|
||||||
|
ART = os.path.join(OUT, "art")
|
||||||
|
|
||||||
|
PHASE_ORDER = ["00-ambiente", "01-implementacao", "02-ativacao", "03-regras",
|
||||||
|
"04-roteamento", "05-balanceamento", "06-qos"]
|
||||||
|
|
||||||
|
TITLES = {
|
||||||
|
"00-ambiente": ("The lab environment", "O ambiente de testes",
|
||||||
|
"Four Alpine VMs under libvirt", "Quatro VMs Alpine sob libvirt"),
|
||||||
|
"01-implementacao": ("Implementation in the kernel", "Implementacao no kernel",
|
||||||
|
"OpenBSD 8.0 sources + a Linux compatibility layer",
|
||||||
|
"Fontes do OpenBSD 8.0 + camada de compatibilidade Linux"),
|
||||||
|
"02-ativacao": ("Activation", "Ativacao",
|
||||||
|
"insmod pf.ko, then pfctl over the OpenBSD ioctl ABI",
|
||||||
|
"insmod pf.ko e o pfctl pela ABI de ioctls do OpenBSD"),
|
||||||
|
"03-regras": ("Basic rules", "Regras basicas",
|
||||||
|
"Tables, anchors, keep state, block drop",
|
||||||
|
"Tabelas, anchors, keep state, block drop"),
|
||||||
|
"04-roteamento": ("Routing", "Roteamento",
|
||||||
|
"route-to and reply-to", "route-to e reply-to"),
|
||||||
|
"05-balanceamento": ("Load balancing", "Balanceamento",
|
||||||
|
"Pools round-robin, source-hash, dup-to",
|
||||||
|
"Pools round-robin, source-hash, dup-to"),
|
||||||
|
"06-qos": ("Quality of service", "Qualidade de servico",
|
||||||
|
"hfsc queues in the data path", "Filas hfsc no caminho de dados"),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def run(cmd):
|
||||||
|
subprocess.run(cmd, check=True)
|
||||||
|
|
||||||
|
|
||||||
|
def probe(path):
|
||||||
|
out = subprocess.run(["ffprobe", "-v", "error", "-show_entries",
|
||||||
|
"format=duration", "-of", "csv=p=0", path],
|
||||||
|
capture_output=True, text=True).stdout.strip()
|
||||||
|
return float(out or 0)
|
||||||
|
|
||||||
|
|
||||||
|
def chapter_card(phase, lang, out_path, seconds=1.8):
|
||||||
|
"""A short card naming the phase; it is the pivot of the crossfade."""
|
||||||
|
title = TITLES[phase][0 if lang == "en" else 1]
|
||||||
|
sub = TITLES[phase][2 if lang == "en" else 3]
|
||||||
|
num = PHASE_ORDER.index(phase) + 1
|
||||||
|
art = None
|
||||||
|
if os.path.isdir(ART):
|
||||||
|
imgs = sorted(f for f in os.listdir(ART) if f.endswith((".jpg", ".png")))
|
||||||
|
if imgs:
|
||||||
|
art = os.path.join(ART, imgs[0])
|
||||||
|
|
||||||
|
script = os.path.join(OUT, "_chapter.py")
|
||||||
|
with open(script, "w") as f:
|
||||||
|
f.write(f"""
|
||||||
|
import os, sys
|
||||||
|
from PIL import Image, ImageDraw, ImageFont
|
||||||
|
W, H = 1920, 1080
|
||||||
|
BG = (12, 16, 22); ACCENT = (88, 168, 255); FG = (240, 246, 252); MUTED = (140, 150, 165)
|
||||||
|
mono = "/usr/share/fonts/dejavu-sans-mono-fonts/DejaVuSansMono.ttf"
|
||||||
|
sans = "/usr/share/fonts/dejavu-sans-fonts/DejaVuSans.ttf"
|
||||||
|
sansb = "/usr/share/fonts/dejavu-sans-fonts/DejaVuSans-Bold.ttf"
|
||||||
|
art = {art!r}
|
||||||
|
img = Image.new("RGB", (W, H), BG)
|
||||||
|
if art and os.path.exists(art):
|
||||||
|
img = Image.blend(img, Image.open(art).resize((W, H)).convert("RGB"), 0.5)
|
||||||
|
d = ImageDraw.Draw(img, "RGBA")
|
||||||
|
d.rectangle([0, 0, W, 8], fill=ACCENT)
|
||||||
|
d.text((140, 430), {title!r}, font=ImageFont.truetype(sansb, 62), fill=FG)
|
||||||
|
d.text((140, 520), {sub!r}, font=ImageFont.truetype(sans, 30), fill=MUTED)
|
||||||
|
d.text((140, 586), "PHASE %d OF %d" % ({num}, {len(PHASE_ORDER)}),
|
||||||
|
font=ImageFont.truetype(mono, 24), fill=ACCENT)
|
||||||
|
d.text((140, 640), "PF on Linux - OpenBSD PF 8.0 no kernel Linux",
|
||||||
|
font=ImageFont.truetype(mono, 24), fill=MUTED)
|
||||||
|
img.save(sys.argv[1])
|
||||||
|
""")
|
||||||
|
png = out_path + ".png"
|
||||||
|
run([sys.executable, script, png])
|
||||||
|
have = subprocess.run(["ffmpeg", "-hide_banner", "-encoders"],
|
||||||
|
capture_output=True, text=True).stdout
|
||||||
|
venc = (["-c:v", "libx264", "-preset", "veryfast", "-crf", "20", "-profile:v", "high"]
|
||||||
|
if "libx264" in have else
|
||||||
|
["-c:v", "libopenh264", "-b:v", "6M", "-profile:v", "high"])
|
||||||
|
run(["ffmpeg", "-y", "-loglevel", "error", "-loop", "1", "-i", png,
|
||||||
|
"-t", str(seconds), "-r", "30"] + venc + ["-pix_fmt", "yuv420p", out_path])
|
||||||
|
os.remove(png)
|
||||||
|
os.remove(script)
|
||||||
|
return out_path
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
ap = argparse.ArgumentParser()
|
||||||
|
ap.add_argument("--lang", required=True, choices=["en", "pt"])
|
||||||
|
ap.add_argument("--out")
|
||||||
|
ap.add_argument("--fade", type=float, default=0.7)
|
||||||
|
args = ap.parse_args()
|
||||||
|
|
||||||
|
lang = args.lang
|
||||||
|
tmp = os.path.join(OUT, "_tmp")
|
||||||
|
os.makedirs(tmp, exist_ok=True)
|
||||||
|
|
||||||
|
segs = [] # (path, duration)
|
||||||
|
for phase in PHASE_ORDER:
|
||||||
|
src = os.path.join(OUT, lang, f"{phase}.mp4")
|
||||||
|
if not os.path.exists(src):
|
||||||
|
sys.stderr.write(f"[join] missing {src}, skipping\n")
|
||||||
|
continue
|
||||||
|
segs.append((src, probe(src)))
|
||||||
|
card = chapter_card(phase, lang, os.path.join(tmp, f"card-{phase}.mp4"))
|
||||||
|
segs.append((card, probe(card)))
|
||||||
|
|
||||||
|
if not segs:
|
||||||
|
raise SystemExit("[join] nothing to join")
|
||||||
|
|
||||||
|
inputs = []
|
||||||
|
for path, _ in segs:
|
||||||
|
inputs += ["-i", path]
|
||||||
|
|
||||||
|
# sequential xfade: each segment fades into the next
|
||||||
|
filters = []
|
||||||
|
prev = "0:v"
|
||||||
|
offset = segs[0][1] - args.fade
|
||||||
|
for i in range(1, len(segs)):
|
||||||
|
filters.append(f"[{prev}][{i}:v]xfade=transition=fade:"
|
||||||
|
f"duration={args.fade}:offset={offset:.3f}[v{i}]")
|
||||||
|
prev = f"v{i}"
|
||||||
|
offset += segs[i][1] - args.fade
|
||||||
|
|
||||||
|
# The audio has to follow the same timeline: with xfade the segments
|
||||||
|
# overlap by the fade duration, so each one starts `fade` earlier than
|
||||||
|
# the plain sum would suggest.
|
||||||
|
audio_at = []
|
||||||
|
acc = 0.0
|
||||||
|
for i, (_, dur) in enumerate(segs):
|
||||||
|
audio_at.append(max(0.0, acc - args.fade * i))
|
||||||
|
acc += dur
|
||||||
|
|
||||||
|
# Only the phase videos carry narration; the chapter cards are silent.
|
||||||
|
# Their input indices are the even ones in `inputs`.
|
||||||
|
phase_idx = [i for i in range(len(segs)) if i % 2 == 0]
|
||||||
|
afilters = []
|
||||||
|
labels = []
|
||||||
|
for k, i in enumerate(phase_idx):
|
||||||
|
ms = int(audio_at[i] * 1000)
|
||||||
|
afilters.append(f"[{i}:a]adelay={ms}|{ms}[a{k}]")
|
||||||
|
labels.append(f"[a{k}]")
|
||||||
|
afilters.append("".join(labels) +
|
||||||
|
f"amix=inputs={len(phase_idx)}:normalize=0,"
|
||||||
|
f"atrim=0:{audio_at[-1] + segs[-1][1]:.3f},"
|
||||||
|
f"asetpts=N/SR/TB[amix]")
|
||||||
|
filters += afilters
|
||||||
|
amap = "[amix]"
|
||||||
|
|
||||||
|
out = args.out or os.path.join(OUT, lang, "pf-on-linux-full.mp4")
|
||||||
|
have = subprocess.run(["ffmpeg", "-hide_banner", "-encoders"],
|
||||||
|
capture_output=True, text=True).stdout
|
||||||
|
if "libx264" in have:
|
||||||
|
venc = ["-c:v", "libx264", "-preset", "veryfast", "-crf", "20",
|
||||||
|
"-profile:v", "high", "-level", "4.0"]
|
||||||
|
elif "libopenh264" in have:
|
||||||
|
venc = ["-c:v", "libopenh264", "-b:v", "6M", "-profile:v", "high"]
|
||||||
|
else:
|
||||||
|
venc = ["-c:v", "libvpx-vp9", "-b:v", "0", "-crf", "30", "-row-mt", "1",
|
||||||
|
"-cpu-used", "4"]
|
||||||
|
# the phases already carry their narration; the cards are silent
|
||||||
|
run(["ffmpeg", "-y", "-loglevel", "error"] + inputs +
|
||||||
|
["-filter_complex", ";".join(filters), "-map", f"[{prev}]",
|
||||||
|
"-map", amap] + venc +
|
||||||
|
["-pix_fmt", "yuv420p", "-movflags", "+faststart", out])
|
||||||
|
print(f"[join] {out} {probe(out):.1f}s")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Executable
+121
@@ -0,0 +1,121 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Generate the narration audio for every screencast phase with MiniMax TTS.
|
||||||
|
|
||||||
|
Female voices, one per language:
|
||||||
|
en -> English_ConfidentWoman
|
||||||
|
pt -> Portuguese_ConfidentWoman
|
||||||
|
|
||||||
|
Each *segment* of a narration script becomes its own audio file, so the
|
||||||
|
renderer can start the voice exactly when the matching beat appears on
|
||||||
|
screen and burn the same sentence as a subtitle.
|
||||||
|
|
||||||
|
./scripts/screencast/make_audio.py --lang en
|
||||||
|
./scripts/screencast/make_audio.py --lang pt --phase 04-roteamento
|
||||||
|
"""
|
||||||
|
import argparse
|
||||||
|
import base64
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
import urllib.error
|
||||||
|
import urllib.request
|
||||||
|
|
||||||
|
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
NARR = os.path.join(HERE, "narration")
|
||||||
|
VO_DIR = os.path.join(HERE, "..", "..", "out", "screencast", "vo")
|
||||||
|
|
||||||
|
VOICES = {
|
||||||
|
"en": {"voice_id": "English_ConfidentWoman", "language_boost": "English"},
|
||||||
|
"pt": {"voice_id": "Portuguese_ConfidentWoman", "language_boost": "Portuguese"},
|
||||||
|
}
|
||||||
|
|
||||||
|
# MiniMax occasionally stumbles on the punctuation of long sentences; keeping
|
||||||
|
# the text plain (no dashes, no slashes) makes the prosody predictable.
|
||||||
|
MODEL = "speech-02-hd"
|
||||||
|
API = "https://api.minimax.io/v1/t2a_v2"
|
||||||
|
|
||||||
|
|
||||||
|
def decode_audio(payload):
|
||||||
|
"""MiniMax returns the audio as a hex string (it starts with the ASCII
|
||||||
|
bytes of the ID3 header, 49443304). Older/other endpoints use base64,
|
||||||
|
so both are handled."""
|
||||||
|
if len(payload) > 8 and all(c in "0123456789abcdefABCDEF" for c in payload[:64]):
|
||||||
|
return bytes.fromhex(payload)
|
||||||
|
data = payload.replace("-", "+").replace("_", "/")
|
||||||
|
data += "=" * (-len(data) % 4)
|
||||||
|
return base64.b64decode(data, validate=False)
|
||||||
|
|
||||||
|
|
||||||
|
def tts(text, voice, lang, out_path, retries=3):
|
||||||
|
body = {
|
||||||
|
"model": MODEL,
|
||||||
|
"text": text,
|
||||||
|
"stream": False,
|
||||||
|
"voice_setting": {
|
||||||
|
"voice_id": voice["voice_id"],
|
||||||
|
"speed": 0.97, "vol": 1.0, "pitch": 0, "emotion": "neutral",
|
||||||
|
},
|
||||||
|
"audio_setting": {"sample_rate": 44100, "bitrate": 128000,
|
||||||
|
"format": "mp3", "channel": 1},
|
||||||
|
"language_boost": voice["language_boost"],
|
||||||
|
}
|
||||||
|
key = os.environ.get("MINIMAX_API_KEY")
|
||||||
|
if not key:
|
||||||
|
raise SystemExit("MINIMAX_API_KEY is not set")
|
||||||
|
for attempt in range(retries):
|
||||||
|
req = urllib.request.Request(
|
||||||
|
API, data=json.dumps(body).encode(),
|
||||||
|
headers={"Authorization": f"Bearer {key}", "Content-Type": "application/json"})
|
||||||
|
try:
|
||||||
|
with urllib.request.urlopen(req, timeout=120) as r:
|
||||||
|
data = json.loads(r.read())
|
||||||
|
if data.get("base_resp", {}).get("status_code") != 0:
|
||||||
|
raise RuntimeError(str(data.get("base_resp")))
|
||||||
|
audio = data.get("data", {}).get("audio")
|
||||||
|
if not audio:
|
||||||
|
raise RuntimeError("no audio in response")
|
||||||
|
with open(out_path, "wb") as f:
|
||||||
|
f.write(decode_audio(audio))
|
||||||
|
return True
|
||||||
|
except Exception as exc: # noqa: BLE001
|
||||||
|
sys.stderr.write(f"[tts] attempt {attempt + 1} failed: {exc}\n")
|
||||||
|
time.sleep(2 + attempt * 3)
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
ap = argparse.ArgumentParser()
|
||||||
|
ap.add_argument("--lang", required=True, choices=["en", "pt"])
|
||||||
|
ap.add_argument("--phase", help="only this phase")
|
||||||
|
ap.add_argument("--force", action="store_true")
|
||||||
|
args = ap.parse_args()
|
||||||
|
|
||||||
|
voice = VOICES[args.lang]
|
||||||
|
files = sorted(f for f in os.listdir(NARR) if f.endswith(f".{args.lang}.json"))
|
||||||
|
if args.phase:
|
||||||
|
files = [f for f in files if f.startswith(args.phase + ".")]
|
||||||
|
|
||||||
|
total = 0
|
||||||
|
for fn in files:
|
||||||
|
phase = fn.split(".")[0]
|
||||||
|
script = json.load(open(os.path.join(NARR, fn)))
|
||||||
|
outdir = os.path.abspath(os.path.join(VO_DIR, f"{phase}.{args.lang}"))
|
||||||
|
os.makedirs(outdir, exist_ok=True)
|
||||||
|
for i, seg in enumerate(script["segments"]):
|
||||||
|
out = os.path.join(outdir, f"{i:02d}-{seg['beat']}.mp3")
|
||||||
|
if os.path.exists(out) and not args.force:
|
||||||
|
print(f"skip {phase}.{args.lang} seg{i} ({seg['beat']})")
|
||||||
|
total += 1
|
||||||
|
continue
|
||||||
|
ok = tts(seg["text"], voice, args.lang, out)
|
||||||
|
size = os.path.getsize(out) if os.path.exists(out) else 0
|
||||||
|
print(f"{'ok ' if ok else 'FAIL'} {phase}.{args.lang} seg{i} "
|
||||||
|
f"({seg['beat']}) {size} bytes")
|
||||||
|
total += 1
|
||||||
|
print(f"[audio] {total} segments processed for lang={args.lang}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Executable
+227
@@ -0,0 +1,227 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Write the narration scripts for every screencast phase, in English and in
|
||||||
|
Brazilian Portuguese.
|
||||||
|
|
||||||
|
Each segment belongs to one *beat* of the recorded session (see
|
||||||
|
scripts/screencast/plans/*.json). The renderer places the segment's audio at
|
||||||
|
the moment that beat starts on screen, and burns the same text as a subtitle,
|
||||||
|
so the voice, the caption and the terminal action always agree.
|
||||||
|
|
||||||
|
A segment may also carry:
|
||||||
|
highlight - nodes of the lab diagram to light up while it is spoken
|
||||||
|
diagram_caption - one-line caption shown inside the diagram panel
|
||||||
|
"""
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
|
||||||
|
OUT = os.path.join(os.path.dirname(os.path.abspath(__file__)), "narration")
|
||||||
|
os.makedirs(OUT, exist_ok=True)
|
||||||
|
|
||||||
|
EN_VOICE = "English_ConfidentWoman"
|
||||||
|
PT_VOICE = "Portuguese_ConfidentWoman"
|
||||||
|
|
||||||
|
PHASES = {}
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------ 00 ----
|
||||||
|
PHASES["00-ambiente"] = {
|
||||||
|
"en": {
|
||||||
|
"title": "The lab environment",
|
||||||
|
"subtitle": "Four Alpine VMs under libvirt, running a kernel built from source",
|
||||||
|
"segments": [
|
||||||
|
{"beat": "b1", "text": "Welcome to the PF on Linux demonstration. Before we look at the firewall itself, let me show you the environment where everything runs. These are four virtual machines managed by libvirt, all of them Alpine Linux, and the machine you are looking at is the firewall: a kernel we compiled from source, version seven point three point four, with our port of the OpenBSD packet filter built in as a module."},
|
||||||
|
{"beat": "b2", "text": "The firewall has four interfaces. The management interface on the virtual network the hypervisor uses to reach us. Then three transit networks: ten point zero point one, where the client lives; ten point zero point two, where the server lives; and ten point zero point three, a second gateway we use to demonstrate routing decisions.", "highlight": ["cli", "pf0", "srv", "gw2"], "diagram_caption": "three transit networks, four interfaces"},
|
||||||
|
{"beat": "b3", "text": "The OpenBSD tools are installed on this machine exactly as they ship: the original pfctl, a pflog daemon and our divert tool. The module is not loaded yet, which is where we will start.", "highlight": ["pf0"], "diagram_caption": "pfctl, pflogd and pdivert, straight from OpenBSD"},
|
||||||
|
{"beat": "b4", "text": "And from the firewall we can reach the other machines directly. The client is on ten point zero point one point ten, the server on ten point zero point two point twenty, and the second gateway on ten point zero point three point two. Every command in this demonstration is real traffic crossing real interfaces.", "highlight": ["cli", "pf0", "srv", "gw2"], "diagram_caption": "cli, srv and gw2 reachable from pf0"},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"pt": {
|
||||||
|
"title": "O ambiente de testes",
|
||||||
|
"subtitle": "Quatro VMs Alpine sob libvirt, rodando um kernel compilado do fonte",
|
||||||
|
"segments": [
|
||||||
|
{"beat": "b1", "text": "Bem-vindo à demonstração do PF on Linux. Antes de falar do firewall em si, deixa eu mostrar o ambiente onde tudo acontece. São quatro máquinas virtuais administradas pelo libvirt, todas com Alpine Linux, e a máquina que você está vendo é o firewall: um kernel que compilamos do fonte, versão sete ponto três ponto quatro, com o nosso port do packet filter do OpenBSD compilado dentro como módulo."},
|
||||||
|
{"beat": "b2", "text": "O firewall tem quatro interfaces. A interface de gerência, na rede virtual que o hypervisor usa para falar com a gente. E depois três redes de trânsito: dez ponto zero ponto um, onde mora o cliente; dez ponto zero ponto dois, onde mora o servidor; e dez ponto zero ponto três, um segundo gateway que vamos usar para demonstrar decisões de roteamento.", "highlight": ["cli", "pf0", "srv", "gw2"], "diagram_caption": "três redes de transito, quatro interfaces"},
|
||||||
|
{"beat": "b3", "text": "As ferramentas do OpenBSD estão instaladas nesta máquina exatamente como são distribuídas: o pfctl original, um daemon de pflog e a nossa ferramenta de divert. O módulo ainda não está carregado, e é por aí que a gente começa.", "highlight": ["pf0"], "diagram_caption": "pfctl, pflogd e pdivert originais do OpenBSD"},
|
||||||
|
{"beat": "b4", "text": "E do firewall a gente alcança as outras máquinas diretamente. O cliente está em dez ponto zero ponto um ponto dez, o servidor em dez ponto zero ponto dois ponto vinte, e o segundo gateway em dez ponto zero ponto três ponto dois. Todo comando desta demonstração é tráfego real cruzando interfaces reais.", "highlight": ["cli", "pf0", "srv", "gw2"], "diagram_caption": "cli, srv e gw2 alcançáveis a partir do pf0"},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------ 01 ----
|
||||||
|
PHASES["01-implementacao"] = {
|
||||||
|
"en": {
|
||||||
|
"title": "Implementation in the kernel",
|
||||||
|
"subtitle": "OpenBSD 8.0 sources plus a Linux compatibility layer equals pf.ko",
|
||||||
|
"segments": [
|
||||||
|
{"beat": "b1", "text": "This is the heart of the work. We did not rewrite the firewall. We took the OpenBSD eight point zero sources, nearly twenty seven thousand lines of C, and we kept them almost intact. On top of that we wrote a compatibility layer, about seven thousand lines, that translates the BSD kernel interfaces into Linux ones."},
|
||||||
|
{"beat": "b2", "text": "The result is a normal Linux kernel module. Here modinfo shows us the license, the description and the vermagic, which tells us exactly which kernel this object was compiled against."},
|
||||||
|
{"beat": "b3", "text": "The vermagic line matters: it is the kernel version, the compiler flags and the module signature. Load the wrong one and the kernel refuses it. Everything you will see in the next minutes is running inside that module."},
|
||||||
|
{"beat": "b4", "text": "This file is what we load into the firewall. A single artifact, built out of tree, carrying the entire OpenBSD packet filter."},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"pt": {
|
||||||
|
"title": "Implementação no kernel",
|
||||||
|
"subtitle": "Fontes do OpenBSD 8.0 mais uma camada de compatibilidade Linux resultam em pf.ko",
|
||||||
|
"segments": [
|
||||||
|
{"beat": "b1", "text": "Aqui está o coração do trabalho. A gente não reescreveu o firewall. Pegamos as fontes do OpenBSD oito ponto zero, quase vinte e sete mil linhas de C, e mantivemos praticamente intactas. Em cima disso escrevemos uma camada de compatibilidade, cerca de sete mil linhas, que traduz as interfaces de kernel do BSD para as do Linux."},
|
||||||
|
{"beat": "b2", "text": "O resultado é um módulo de kernel Linux comum. Aqui o modinfo mostra a licença, a descrição e o vermagic, que nos diz exatamente com qual kernel este objeto foi compilado."},
|
||||||
|
{"beat": "b3", "text": "A linha do vermagic importa: é a versão do kernel, as flags do compilador e a assinatura do módulo. Carregar o arquivo errado e o kernel recusa. Tudo que você vai ver nos próximos minutos roda dentro desse módulo."},
|
||||||
|
{"beat": "b4", "text": "Este é o arquivo que carregamos no firewall. Um único artefato, compilado fora da árvore do kernel, carregando o packet filter inteiro do OpenBSD."},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------ 02 ----
|
||||||
|
PHASES["02-ativacao"] = {
|
||||||
|
"en": {
|
||||||
|
"title": "Activation",
|
||||||
|
"subtitle": "insmod pf.ko, then pfctl talks to it over the OpenBSD ioctl ABI",
|
||||||
|
"segments": [
|
||||||
|
{"beat": "b1", "text": "First we make sure nothing is loaded, so the activation is honest. No module, no firewall."},
|
||||||
|
{"beat": "b2", "text": "Now we insert it. The kernel accepts the module and the log shows our initialization message. This is the OpenBSD engine running inside Linux."},
|
||||||
|
{"beat": "b3", "text": "The device appears, owned by root with mode six hundred, exactly like the BSD original, and the module shows up in the list of loaded modules."},
|
||||||
|
{"beat": "b4", "text": "pfctl is the original OpenBSD tool. We enable the firewall and ask for its status. The ioctl interface, the statistics table, the rule counters, all of it is the OpenBSD ABI, unchanged."},
|
||||||
|
{"beat": "b5", "text": "The ruleset is still empty, and that is exactly right: with no rules, pf blocks everything and returns. The engine is armed and waiting for instructions."},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"pt": {
|
||||||
|
"title": "Ativação",
|
||||||
|
"subtitle": "insmod pf.ko e o pfctl conversa com ele pela ABI de ioctls do OpenBSD",
|
||||||
|
"segments": [
|
||||||
|
{"beat": "b1", "text": "Primeiro a gente garante que não tem nada carregado, para a ativação ser honesta. Sem módulo, sem firewall."},
|
||||||
|
{"beat": "b2", "text": "Agora inserimos. O kernel aceita o módulo e o log mostra a nossa mensagem de inicialização. Este é o motor do OpenBSD rodando dentro do Linux."},
|
||||||
|
{"beat": "b3", "text": "O dispositivo aparece, do root, com modo seiscentos, exatamente como no BSD original, e o módulo aparece na lista de módulos carregados."},
|
||||||
|
{"beat": "b4", "text": "O pfctl é a ferramenta original do OpenBSD. Habilitamos o firewall e pedimos o status. A interface de ioctl, a tabela de estatísticas, os contadores das regras: tudo é a ABI do OpenBSD, sem alteração."},
|
||||||
|
{"beat": "b5", "text": "O ruleset ainda está vazio, e isso está certo: sem regras, o pf bloqueia tudo e devolve. O motor está armado e esperando instruções."},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------ 03 ----
|
||||||
|
PHASES["03-regras"] = {
|
||||||
|
"en": {
|
||||||
|
"title": "Basic rules",
|
||||||
|
"subtitle": "Tables, anchors, keep state and block drop",
|
||||||
|
"segments": [
|
||||||
|
{"beat": "b1", "text": "Now we write a real pf dot conf. This is the original OpenBSD syntax: a table with the trusted network, an anchor, and the usual quick rules with keep state. pfctl parses it and the kernel accepts it."},
|
||||||
|
{"beat": "b2", "text": "pfctl shows us what the kernel understood: the rules as they were compiled, the list of tables, and the contents of the trusted table."},
|
||||||
|
{"beat": "b3", "text": "Let the traffic through. From the client we ping the server, and the firewall creates a state. You can see it in the state table: the five tuple, the timeout, the counters."},
|
||||||
|
{"beat": "b4", "text": "Now the same thing with a drop. We replace the pass rule with a block drop rule for the client network, and reload."},
|
||||||
|
{"beat": "b5", "text": "The ping is dead. One hundred percent packet loss. And the rule counters show the packets that were evaluated and the ones that were blocked. This is a stateful firewall behaving exactly like the original."},
|
||||||
|
{"beat": "b6", "text": "Finally, anchors. We load a ruleset into the anchor called demo, and pfctl lists the rules that live inside it, separately from the main ruleset."},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"pt": {
|
||||||
|
"title": "Regras básicas",
|
||||||
|
"subtitle": "Tabelas, anchors, keep state e block drop",
|
||||||
|
"segments": [
|
||||||
|
{"beat": "b1", "text": "Agora escrevemos um pf ponto conf de verdade. Esta é a sintaxe original do OpenBSD: uma tabela com a rede confiável, um anchor, e as regras quick de sempre com keep state. O pfctl faz o parsing e o kernel aceita."},
|
||||||
|
{"beat": "b2", "text": "O pfctl nos mostra o que o kernel entendeu: as regras como foram compiladas, a lista de tabelas e o conteúdo da tabela trusted."},
|
||||||
|
{"beat": "b3", "text": "Deixa o tráfego passar. Do cliente a gente dá ping no servidor, e o firewall cria um estado. Você vê isso na tabela de estados: a tupla de cinco campos, o timeout, os contadores."},
|
||||||
|
{"beat": "b4", "text": "Agora a mesma coisa com um drop. Trocamos a regra de pass por uma regra block drop para a rede do cliente, e recarregamos."},
|
||||||
|
{"beat": "b5", "text": "O ping morreu. Cem por cento de perda. E os contadores da regra mostram os pacotes avaliados e os bloqueados. Este é um firewall stateful se comportando exatamente como o original."},
|
||||||
|
{"beat": "b6", "text": "Por fim, os anchors. Carregamos um ruleset no anchor chamado demo, e o pfctl lista as regras que moram dentro dele, separadamente do ruleset principal."},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------ 04 ----
|
||||||
|
PHASES["04-roteamento"] = {
|
||||||
|
"en": {
|
||||||
|
"title": "Routing",
|
||||||
|
"subtitle": "route-to forces the next hop, reply-to forces the way back",
|
||||||
|
"segments": [
|
||||||
|
{"beat": "b1", "text": "Now the routing features. This rule matches traffic from the client to the server and sends it through the second gateway, ten point zero point three point two, instead of the normal path. pfctl shows the rule exactly as the kernel stored it.", "highlight": ["cli", "pf0", "gw2"], "diagram_caption": "route-to: normal path is eth2, forced path is eth3"},
|
||||||
|
{"beat": "b2", "text": "To prove where the packet actually went, we capture on both interfaces at the same time: eth three, towards the gateway, and eth two, towards the server."},
|
||||||
|
{"beat": "b3", "text": "And now the client pings the server.", "highlight": ["cli", "pf0", "gw2"], "diagram_caption": "the request leaves through eth3, to the gateway"},
|
||||||
|
{"beat": "b4", "text": "The evidence is in the captures. The request appears on eth three, on the way to the gateway, and it never appears on eth two. The firewall decided the next hop, not the routing table.", "highlight": ["cli", "pf0", "gw2"], "diagram_caption": "1 request on eth3, 0 on eth2"},
|
||||||
|
{"beat": "b5", "text": "The other half of the problem is the way back. reply to forces the reply of a flow to leave through a different gateway than the request came in, which is how you build asymmetric routing.", "highlight": ["cli", "pf0", "gw2"], "diagram_caption": "reply-to: the answer leaves through another gateway"},
|
||||||
|
{"beat": "b6", "text": "We capture eth three again and also eth one, the link where the client is."},
|
||||||
|
{"beat": "b7", "text": "The replies show up on eth three, on the gateway link, and not on the client link. The request and the reply now take different paths, exactly as commanded.", "highlight": ["cli", "pf0", "gw2"], "diagram_caption": "reply on eth3, nothing on the client link"},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"pt": {
|
||||||
|
"title": "Roteamento",
|
||||||
|
"subtitle": "route-to força o próximo salto, reply-to força o caminho de volta",
|
||||||
|
"segments": [
|
||||||
|
{"beat": "b1", "text": "Agora as funcionalidades de roteamento. Esta regra casa com o tráfego do cliente para o servidor e o manda pelo segundo gateway, dez ponto zero ponto três ponto dois, em vez do caminho normal. O pfctl mostra a regra exatamente como o kernel guardou.", "highlight": ["cli", "pf0", "gw2"], "diagram_caption": "route-to: o caminho normal e' eth2; o forcado, eth3"},
|
||||||
|
{"beat": "b2", "text": "Para provar por onde o pacote realmente passou, capturamos nas duas interfaces ao mesmo tempo: eth três, rumo ao gateway, e eth dois, rumo ao servidor."},
|
||||||
|
{"beat": "b3", "text": "E agora o cliente dá ping no servidor.", "highlight": ["cli", "pf0", "gw2"], "diagram_caption": "o pedido sai por eth3, rumo ao gateway"},
|
||||||
|
{"beat": "b4", "text": "A prova está nas capturas. O pedido aparece na eth três, indo para o gateway, e nunca aparece na eth dois. Quem decidiu o próximo salto foi o firewall, não a tabela de rotas.", "highlight": ["cli", "pf0", "gw2"], "diagram_caption": "1 pedido em eth3, 0 em eth2"},
|
||||||
|
{"beat": "b5", "text": "A outra metade do problema é o caminho de volta. O reply to força a resposta de um fluxo a sair por um gateway diferente do por onde o pedido entrou, que é assim que se constrói roteamento assimétrico.", "highlight": ["cli", "pf0", "gw2"], "diagram_caption": "reply-to: a resposta sai por um gateway diferente"},
|
||||||
|
{"beat": "b6", "text": "Capturamos a eth três de novo e também a eth um, o link onde está o cliente."},
|
||||||
|
{"beat": "b7", "text": "As respostas aparecem na eth três, no link do gateway, e não no link do cliente. O pedido e a resposta agora seguem caminhos diferentes, exatamente como foi pedido.", "highlight": ["cli", "pf0", "gw2"], "diagram_caption": "resposta em eth3, nada no link do cliente"},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------ 05 ----
|
||||||
|
PHASES["05-balanceamento"] = {
|
||||||
|
"en": {
|
||||||
|
"title": "Load balancing",
|
||||||
|
"subtitle": "NAT pools in round-robin and source-hash, plus dup-to",
|
||||||
|
"segments": [
|
||||||
|
{"beat": "b1", "text": "Load balancing in pf is done with pools. This rule translates the source of the client network into a pool of two addresses, the two gateways of the firewall, and selects between them round robin.", "highlight": ["cli", "pf0", "srv"], "diagram_caption": "pool { 10.0.2.1, 10.0.3.1 }: round-robin"},
|
||||||
|
{"beat": "b2", "text": "We capture on the interface towards the server to see which source address each packet carries."},
|
||||||
|
{"beat": "b3", "text": "Six packets from the client."},
|
||||||
|
{"beat": "b4", "text": "And the capture shows the truth: three packets went out with one address, three with the other. The pool is really rotating between its members, and the server sees a balanced load.", "highlight": ["cli", "pf0", "srv"], "diagram_caption": "3 packets with each source address"},
|
||||||
|
{"beat": "b5", "text": "Sometimes you do not want rotation. source hash keeps a given origin always mapped to the same pool member, which is what you want when a session has to be sticky.", "highlight": ["cli", "pf0", "srv"], "diagram_caption": "pool { 10.0.2.1, 10.0.3.1 }: source-hash"},
|
||||||
|
{"beat": "b6", "text": "Four more packets, and now every single one carries the same source address. The mapping is stable per origin.", "highlight": ["cli", "pf0", "srv"], "diagram_caption": "every packet with the same source"},
|
||||||
|
{"beat": "b7", "text": "And dup to is the mirror case: the packet keeps its normal path, and a copy is delivered to the second gateway. We count the requests on both interfaces: one on the normal path, one on the mirrored path. The packet was duplicated, not moved.", "highlight": ["cli", "pf0", "srv", "gw2"], "diagram_caption": "dup-to: the normal path and the mirrored copy"},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"pt": {
|
||||||
|
"title": "Balanceamento",
|
||||||
|
"subtitle": "Pools de NAT em round-robin e source-hash, mais o dup-to",
|
||||||
|
"segments": [
|
||||||
|
{"beat": "b1", "text": "O balanceamento no pf é feito com pools. Esta regra traduz a origem da rede do cliente para um pool de dois endereços, os dois gateways do firewall, e escolhe entre eles em round robin.", "highlight": ["cli", "pf0", "srv"], "diagram_caption": "pool { 10.0.2.1, 10.0.3.1 }: round-robin"},
|
||||||
|
{"beat": "b2", "text": "Capturamos na interface que vai para o servidor para ver qual endereço de origem cada pacote carrega."},
|
||||||
|
{"beat": "b3", "text": "Seis pacotes vindo do cliente."},
|
||||||
|
{"beat": "b4", "text": "E a captura mostra a verdade: três pacotes saíram com um endereço, três com o outro. O pool está realmente rodando entre os membros, e o servidor enxerga a carga balanceada.", "highlight": ["cli", "pf0", "srv"], "diagram_caption": "3 pacotes com cada endereço de origem"},
|
||||||
|
{"beat": "b5", "text": "Às vezes você não quer rotação. O source hash mantém uma origem sempre mapeada para o mesmo membro do pool, que é o que se quer quando a sessão precisa ser sticky.", "highlight": ["cli", "pf0", "srv"], "diagram_caption": "pool { 10.0.2.1, 10.0.3.1 }: source-hash"},
|
||||||
|
{"beat": "b6", "text": "Mais quatro pacotes, e agora todos carregam o mesmo endereço de origem. O mapeamento é estável por origem.", "highlight": ["cli", "pf0", "srv"], "diagram_caption": "todos os pacotes com a mesma origem"},
|
||||||
|
{"beat": "b7", "text": "E o dup to é o caso espelho: o pacote mantém o caminho normal e uma cópia é entregue no segundo gateway. Contamos os pedidos nas duas interfaces: um no caminho normal, um no caminho espelhado. O pacote foi duplicado, não movido.", "highlight": ["cli", "pf0", "srv", "gw2"], "diagram_caption": "dup-to: o caminho normal e a cópia espelhada"},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------ 06 ----
|
||||||
|
PHASES["06-qos"] = {
|
||||||
|
"en": {
|
||||||
|
"title": "Quality of service",
|
||||||
|
"subtitle": "OpenBSD 8.0 queues: hfsc classes attached to the interface",
|
||||||
|
"segments": [
|
||||||
|
{"beat": "b1", "text": "The last piece is quality of service. This is the OpenBSD eight point zero queue syntax: a root class with ten megabits of bandwidth, a leaf class with two megabits as the default, and a rule that steers the traffic from the client to that leaf class."},
|
||||||
|
{"beat": "b2", "text": "pfctl shows the queues with their statistics, and the rule set shows the queue attached to the rule."},
|
||||||
|
{"beat": "b3", "text": "Now we move real traffic through the queue. On the server we prepare two megabytes and listen on port ninety ninety five."},
|
||||||
|
{"beat": "b4", "text": "And the client downloads it, through the firewall, through the class."},
|
||||||
|
{"beat": "b5", "text": "Two million bytes transferred, and look at the queue counters: the hfsc class counted the packets and the bytes. The scheduler is in the data path, not just in the configuration."},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"pt": {
|
||||||
|
"title": "Qualidade de serviço",
|
||||||
|
"subtitle": "Filas do OpenBSD 8.0: classes hfsc anexadas à interface",
|
||||||
|
"segments": [
|
||||||
|
{"beat": "b1", "text": "A última peça é qualidade de serviço. Esta é a sintaxe de filas do OpenBSD oito ponto zero: uma classe raiz com dez megabits de banda, uma classe folha com dois megabits como default, e uma regra que direciona o tráfego do cliente para essa classe folha."},
|
||||||
|
{"beat": "b2", "text": "O pfctl mostra as filas com as estatísticas, e o ruleset mostra a fila anexada à regra."},
|
||||||
|
{"beat": "b3", "text": "Agora movemos tráfego de verdade pela fila. No servidor preparamos dois megabytes e escutamos na porta noventa e noventa e cinco."},
|
||||||
|
{"beat": "b4", "text": "E o cliente baixa isso, passando pelo firewall, passando pela classe."},
|
||||||
|
{"beat": "b5", "text": "Dois milhões de bytes transferidos, e olha os contadores das filas: a classe hfsc contou os pacotes e os bytes. O escalonador está no caminho de dados, não só na configuração."},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
# the diagram is drawn over these phases
|
||||||
|
DIAGRAM_PHASES = {"00-ambiente", "04-roteamento", "05-balanceamento"}
|
||||||
|
|
||||||
|
for phase, langs in PHASES.items():
|
||||||
|
for lang, data in langs.items():
|
||||||
|
out = {
|
||||||
|
"phase": phase, "lang": lang,
|
||||||
|
"voice": EN_VOICE if lang == "en" else PT_VOICE,
|
||||||
|
"title": data["title"], "subtitle": data["subtitle"],
|
||||||
|
"segments": data["segments"],
|
||||||
|
}
|
||||||
|
with open(os.path.join(OUT, f"{phase}.{lang}.json"), "w") as f:
|
||||||
|
json.dump(out, f, ensure_ascii=False, indent=2)
|
||||||
|
print(f"wrote {sum(len(v) for v in PHASES.values())} narration scripts to {OUT}")
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
{
|
||||||
|
"phase": "00-ambiente",
|
||||||
|
"lang": "en",
|
||||||
|
"voice": "English_ConfidentWoman",
|
||||||
|
"title": "The lab environment",
|
||||||
|
"subtitle": "Four Alpine VMs under libvirt, running a kernel built from source",
|
||||||
|
"segments": [
|
||||||
|
{
|
||||||
|
"beat": "b1",
|
||||||
|
"text": "Welcome to the PF on Linux demonstration. Before we look at the firewall itself, let me show you the environment where everything runs. These are four virtual machines managed by libvirt, all of them Alpine Linux, and the machine you are looking at is the firewall: a kernel we compiled from source, version seven point three point four, with our port of the OpenBSD packet filter built in as a module."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"beat": "b2",
|
||||||
|
"text": "The firewall has four interfaces. The management interface on the virtual network the hypervisor uses to reach us. Then three transit networks: ten point zero point one, where the client lives; ten point zero point two, where the server lives; and ten point zero point three, a second gateway we use to demonstrate routing decisions.",
|
||||||
|
"highlight": [
|
||||||
|
"cli",
|
||||||
|
"pf0",
|
||||||
|
"srv",
|
||||||
|
"gw2"
|
||||||
|
],
|
||||||
|
"diagram_caption": "three transit networks, four interfaces"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"beat": "b3",
|
||||||
|
"text": "The OpenBSD tools are installed on this machine exactly as they ship: the original pfctl, a pflog daemon and our divert tool. The module is not loaded yet, which is where we will start.",
|
||||||
|
"highlight": [
|
||||||
|
"pf0"
|
||||||
|
],
|
||||||
|
"diagram_caption": "pfctl, pflogd and pdivert, straight from OpenBSD"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"beat": "b4",
|
||||||
|
"text": "And from the firewall we can reach the other machines directly. The client is on ten point zero point one point ten, the server on ten point zero point two point twenty, and the second gateway on ten point zero point three point two. Every command in this demonstration is real traffic crossing real interfaces.",
|
||||||
|
"highlight": [
|
||||||
|
"cli",
|
||||||
|
"pf0",
|
||||||
|
"srv",
|
||||||
|
"gw2"
|
||||||
|
],
|
||||||
|
"diagram_caption": "cli, srv and gw2 reachable from pf0"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
{
|
||||||
|
"phase": "00-ambiente",
|
||||||
|
"lang": "pt",
|
||||||
|
"voice": "Portuguese_ConfidentWoman",
|
||||||
|
"title": "O ambiente de testes",
|
||||||
|
"subtitle": "Quatro VMs Alpine sob libvirt, rodando um kernel compilado do fonte",
|
||||||
|
"segments": [
|
||||||
|
{
|
||||||
|
"beat": "b1",
|
||||||
|
"text": "Bem-vindo à demonstração do PF on Linux. Antes de falar do firewall em si, deixa eu mostrar o ambiente onde tudo acontece. São quatro máquinas virtuais administradas pelo libvirt, todas com Alpine Linux, e a máquina que você está vendo é o firewall: um kernel que compilamos do fonte, versão sete ponto três ponto quatro, com o nosso port do packet filter do OpenBSD compilado dentro como módulo."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"beat": "b2",
|
||||||
|
"text": "O firewall tem quatro interfaces. A interface de gerência, na rede virtual que o hypervisor usa para falar com a gente. E depois três redes de trânsito: dez ponto zero ponto um, onde mora o cliente; dez ponto zero ponto dois, onde mora o servidor; e dez ponto zero ponto três, um segundo gateway que vamos usar para demonstrar decisões de roteamento.",
|
||||||
|
"highlight": [
|
||||||
|
"cli",
|
||||||
|
"pf0",
|
||||||
|
"srv",
|
||||||
|
"gw2"
|
||||||
|
],
|
||||||
|
"diagram_caption": "três redes de transito, quatro interfaces"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"beat": "b3",
|
||||||
|
"text": "As ferramentas do OpenBSD estão instaladas nesta máquina exatamente como são distribuídas: o pfctl original, um daemon de pflog e a nossa ferramenta de divert. O módulo ainda não está carregado, e é por aí que a gente começa.",
|
||||||
|
"highlight": [
|
||||||
|
"pf0"
|
||||||
|
],
|
||||||
|
"diagram_caption": "pfctl, pflogd e pdivert originais do OpenBSD"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"beat": "b4",
|
||||||
|
"text": "E do firewall a gente alcança as outras máquinas diretamente. O cliente está em dez ponto zero ponto um ponto dez, o servidor em dez ponto zero ponto dois ponto vinte, e o segundo gateway em dez ponto zero ponto três ponto dois. Todo comando desta demonstração é tráfego real cruzando interfaces reais.",
|
||||||
|
"highlight": [
|
||||||
|
"cli",
|
||||||
|
"pf0",
|
||||||
|
"srv",
|
||||||
|
"gw2"
|
||||||
|
],
|
||||||
|
"diagram_caption": "cli, srv e gw2 alcançáveis a partir do pf0"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Loaded 100 of 130 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user