From 0eb040fcc8c62e4b59ebc3a89f0f0e96429cadfc Mon Sep 17 00:00:00 2001 From: lgcosta Date: Wed, 11 Nov 2026 21:27:32 -0300 Subject: [PATCH] docs: rdr-to verified; porting notes on checksum strategy across translation paths (GSO regression) --- docs/en/feature-matrix.md | 4 ++-- docs/porting-notes.md | 17 +++++++++++++++++ docs/pt-BR/feature-matrix.md | 4 ++-- 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/docs/en/feature-matrix.md b/docs/en/feature-matrix.md index e815907..13e69e6 100644 --- a/docs/en/feature-matrix.md +++ b/docs/en/feature-matrix.md @@ -49,8 +49,8 @@ As of 2026-11-11; source of per-row evidence: `out/test-logs/`, | 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`. | -| `rdr-to` | `rdr pass on wan proto tcp from any to any port 8080 -> srv port 80` | M3 | PENDING-M3 | -| `binat-to` | `binat-to …` | M3 | PENDING-M3 | +| `rdr-to` | `pass in … to port 8080 rdr-to 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). | +| `binat-to` | `binat-to ` (no direction) | M3 | PENDING-M3 — parser accepts the 8.0 form (`binat-to` is a rule option, not a standalone rule); VM gate not written yet. | | Scrub/normalize: reassembly, `fragment drop-overlap`, `min-ttl`, `max-mss`, `df`/`no-df`, `random-id`, `set reassemble yes` | `match on all scrub (max-mss 1380 …)` | M3 | COMPILES-untested (`pf_norm.c` compiled into pf.ko and reached from `pf_test`; scapy gate 08 pending) | | OS fingerprinting in states | `pass … set os A` + `/etc/pf.os` | bonus | COMPILES-untested (`pf_osfp.c` compiled; no fingerprint packaging yet — missing `/etc/pf.os` is tolerated) | diff --git a/docs/porting-notes.md b/docs/porting-notes.md index 611be3f..5a00516 100644 --- a/docs/porting-notes.md +++ b/docs/porting-notes.md @@ -904,3 +904,20 @@ demonstrates the BSD "receive -> rewrite -> reinject" cycle. Without step 3 the reply is delivered to the firewall's own stack and the client never sees it (the classic "NAT works one way" symptom). + +## Checksums across the translation paths + +pf rewrites addresses, so both the IPv4 header checksum and the transport +checksum must stay correct. Three cases matter in the lab: + +| Case | What Linux gives us | What the port does | +|---|---|---| +| transit packet, no translation | `CHECKSUM_UNNECESSARY` (vhost-net did not materialize the field) | leave it alone — the peer's stack trusts the marker | +| translated packet, plain skb | same | recompute the transport checksum in software over the new pseudo-header, then the IP header checksum | +| translated packet, GSO/TSO skb (bulk, locally generated) | `CHECKSUM_PARTIAL` | **do not** compute in software: one computation covers only the first segment. Restore `CHECKSUM_PARTIAL` with `csum_start`/`csum_offset` and let the NIC compute per segment | + +The third row is a regression guard: materializing GSO checksums in software +made bulk transfers silently stall (the peer dropped every segment after the +first) and starved `sshd` on the firewall — the box answered ping but not SSH. +`tests/vm/06-rdr.sh` pulls 2 MB through a redirect and compares the size to +catch it. diff --git a/docs/pt-BR/feature-matrix.md b/docs/pt-BR/feature-matrix.md index 7afb3eb..1ba12cd 100644 --- a/docs/pt-BR/feature-matrix.md +++ b/docs/pt-BR/feature-matrix.md @@ -49,8 +49,8 @@ Estado em 2026-11-11; evidência por linha: `out/test-logs/`, | Feature | Sintaxe de 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`. | -| `rdr-to` | `rdr pass on wan proto tcp from any to any port 8080 -> srv port 80` | M3 | PENDING-M3 | -| `binat-to` | `binat-to …` | M3 | PENDING-M3 | +| `rdr-to` | `pass in … to port 8080 rdr-to 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). | +| `binat-to` | `binat-to ` (sem direção) | M3 | PENDING-M3 — o parser aceita a forma 8.0 (`binat-to` é opção de regra, não regra própria); gate em VM ainda não escrito. | | Scrub/normalize: remontagem, `fragment drop-overlap`, `min-ttl`, `max-mss`, `df`/`no-df`, `random-id`, `set reassemble yes` | `match on all scrub (max-mss 1380 …)` | M3 | COMPILES-untested (`pf_norm.c` compilado no pf.ko e alcançado pelo `pf_test`; gate scapy 08 pendente) | | Fingerprint de SO em estados | `pass … set os A` + `/etc/pf.os` | bônus | COMPILES-untested (`pf_osfp.c` compilado; sem empacotamento de fingerprints ainda — ausência de `/etc/pf.os` é tolerada) |