engine: downstream MTU and downstream train in the measurement document

Three facts the client cannot produce alone, kept deliberately separate:
mtu.pmtud_down (largest datagram that arrives unfragmented — meaningful only
because the server sets DF), mtu.frag_delivery (whether larger ones arrive once
fragmentation is allowed), and train.udp_downstream (loss, reordering and
arrival spacing in the download direction, which a round trip cannot separate
from upstream loss).

ServerMeasurement now runs them on the same ProbeSession as the echo train. It
had to: a fresh session restarts client-side sequence numbers and the server's
anti-replay window discards the lot, so the re-primed source is never recorded
and every granted send goes to a socket that has already closed. That produced
four confidently-wrong FAILED tests and a RED verdict on a healthy network.

Live against fmr: path MTU 1500, fragments to 4000, 100/100 downstream, GREEN.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
mrambossek
2026-08-01 10:54:38 +02:00
co-authored by Claude Fable 5
parent ce1aaa332a
commit 14e5fad1b2
18 changed files with 911 additions and 127 deletions
@@ -12,6 +12,11 @@ import java.util.Base64
* A data-plane session (probe-protocol.md §3): derives the session key, then sends signed ELT1
* packets to the server's UDP endpoint and reads back verified responses. One session ↔ one
* server target. Blocking; the caller owns threading.
*
* One instance per server session, for its whole lifetime. Sequence numbers start at zero here
* while the server's anti-replay window (§3.2) keeps counting, so a second instance sharing a
* session id has all its packets discarded as replays — and, because the server then never
* records the new source, any granted send still targets the socket that was closed.
*/
class ProbeSession(
private val credential: String,