From 12e9c1a2f3a8d985e6b888542fb3b169cdc49930 Mon Sep 17 00:00:00 2001 From: lgcosta Date: Wed, 11 Nov 2026 23:56:01 -0300 Subject: [PATCH] reply-to: gate 13 green (reply forced onto the gateway link); docs/README/CHANGELOG for 14 gates --- CHANGELOG.md | 6 +++++- README.md | 8 ++++---- docs/en/feature-matrix.md | 2 +- docs/en/pf-guide.md | 2 +- docs/pt-BR/pf-guide.md | 2 +- 5 files changed, 12 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 75301ca..4dbe516 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,11 @@ ## v0.6.0 — 2026-11-11 -Adds scrub and route-pool verification. Suite: 13 gates, all green. +Adds scrub, route pools and `reply-to` verification. Suite: 14 gates, all green. + +| Capability | Gate | Proof | +|---|---|---| +| `reply-to` (asymmetric reply path) | `13-replyto` | a flow's reply is forced onto the gateway's link and never uses the client's link (measured on the firewall's own interfaces) | | Capability | Gate | Proof | |---|---|---| diff --git a/README.md b/README.md index 3c32eeb..dd4dab5 100644 --- a/README.md +++ b/README.md @@ -8,8 +8,8 @@ real OpenBSD `pfctl` over the `/dev/pf` ioctl ABI. (A second module for the **Status:** the filter core, stateful filtering, tables, anchors, NAT (`nat-to`/`rdr-to`/`binat-to`), forced routing (`route-to`/`dup-to`), `divert` -and `pflog` are **verified in a real libvirt lab** (13/13 gates green). The -remaining features (ALTQ/pfq, af-to, `reply-to`) are still pending — honest per-feature status: +and `pflog` are **verified in a real libvirt lab** (14/14 gates green). The +remaining features (ALTQ/pfq, af-to) are still pending — honest per-feature status: [`docs/en/feature-matrix.md`](docs/en/feature-matrix.md). Demo (asciinema): [`docs/en/demo.md`](docs/en/demo.md). Design: [`docs/superpowers/specs/2026-11-11-pf-linux-port-design.md`](docs/superpowers/specs/2026-11-11-pf-linux-port-design.md) @@ -23,7 +23,7 @@ Design: [`docs/superpowers/specs/2026-11-11-pf-linux-port-design.md`](docs/super | 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 | | 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, divert e2e, pflog | **partial** — divert (11), pflog (12), route-to/dup-to (08), pools rr/source-hash (10) verified; `reply-to` pending | +| 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 | | M5 | queues: HFSC/FQ-CoDel back ends + `pfq` qdisc | pending | | M6 | docs complete, patchset tidy, full suite green, tagged release | **done** — docs EN+PT-BR, 11/11 suite green, tagged (v0.5.0) | @@ -37,7 +37,7 @@ Design: [`docs/superpowers/specs/2026-11-11-pf-linux-port-design.md`](docs/super | Scrub | fragment reassembly, overlap drop, `min-ttl`/`max-mss`/`df`/`no-df`, `set reassemble` | | QoS (ALTQ successor) | `queue`/`queue on` with OpenBSD 8.0 `ifq` schedulers: **default (priq), HFSC, FQ-CoDel**, attached via the `pfq` qdisc; `pfctl -s queue -v` stats | | Divert | `divert-to` / `divert` tag + `pdivert` relay over an in-module `PF_DIVERT` socket family (verified: packet to userspace and back) | -| Route | `route-to`/`dup-to` + pools (`round-robin`, `source-hash`) verified; `reply-to` pending (`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 | | Control | `/dev/pf` (0600), OpenBSD 8.0 ioctl ABI, Linux build of `pfctl` (single binary) | diff --git a/docs/en/feature-matrix.md b/docs/en/feature-matrix.md index 237db2e..4847fcf 100644 --- a/docs/en/feature-matrix.md +++ b/docs/en/feature-matrix.md @@ -58,7 +58,7 @@ As of 2026-11-11; source of per-row evidence: `out/test-logs/`, | Feature | Upstream syntax | Milestone | Status | |---|---|---|---| -| `route-to` / `dup-to` / `reply-to` | `pass in … route-to { 10.0.3.2 } keep state` | M4 | **`route-to` and `dup-to` VERIFIED in lab** (gate `08-routeto.sh`): the packet leaves through the gateway's link instead of the normal egress (proved on the firewall's own interfaces), and `dup-to` keeps the original path while emitting the copy. `reply-to` parses and is stored; gate pending. `fail-to` is **not a keyword in OpenBSD 8.0** (removed upstream), so it is out of scope here. 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). | +| `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. | | `divert-to` / `divert` tag | `pass in … divert-to port ` | 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). | | Queues, default scheduler (priq) | `queue root on wan`, `pfctl -s queue -v` | M5 | COMPILES-untested — `compat/pfk_ifq.c` implements the ifq API with **priq fallback semantics**; `queue` rules parse and load, but packets are not actually shunted through a classful qdisc until M5 | | HFSC | `queue on wan { hfsc … }` | M5 | COMPILES-untested — **priq fallback until M5**; `pfsrc/hfsc.c` staged (real OpenBSD 8.0 code) but **not in the module object list** | diff --git a/docs/en/pf-guide.md b/docs/en/pf-guide.md index 3c4d4ab..3cc1876 100644 --- a/docs/en/pf-guide.md +++ b/docs/en/pf-guide.md @@ -62,7 +62,7 @@ treat them as load-and-pray until the matrix moves them. |---|---|---| | `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 ()` 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`. | -| `route-to`, `dup-to` | — | **Verified in the lab** (gate `08-routeto.sh`): the forced gateway is used instead of the normal egress, and `dup-to` copies while keeping the flow. `reply-to` loads but has no gate yet. `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**. | | `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. | diff --git a/docs/pt-BR/pf-guide.md b/docs/pt-BR/pf-guide.md index fe52da4..a57c76a 100644 --- a/docs/pt-BR/pf-guide.md +++ b/docs/pt-BR/pf-guide.md @@ -65,7 +65,7 @@ matriz mudar. |---|---|---| | `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 ()`. 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. | -| `route-to`, `dup-to` | — | **Verificado em lab** (gate `08-routeto.sh`): o gateway forçado é usado em vez do egress normal, e `dup-to` copia mantendo o fluxo. `reply-to` carrega mas ainda sem gate. `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**. | | `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. |