feat(core): SYN+ACK and FIN go once, as in Java, measured with Java FILIUS

TcpRun loses exactly one frame (loseFirst: the card's output buffer drops
it; the sender's packet log still shows it, as with setDrop) in three new
stories: lostSynAck, lostFin, lostLastFin.

Measured: Java never repeats a SYN+ACK or a FIN. The server gives a
handshake up after one timeout (3 RTT) and closes with a FIN from SYN_RCVD
(its boundary check sometimes lets it wait twice; it means once). After its
FIN a socket waits at most five times, each wait ended by a segment or the
timeout; then it is gone. The core does the same (TCP_CLOSING_WAITS, with
TIME_WAIT in the loop) and shows CLOSED where Java leaves the state
standing (a named Java bug).

One departure: a repeated SYN in SYN_RCVD gets the SYN+ACK once more and
the wait starts anew; Java closes there, but its client never repeats the
SYN. lostSynAck is compared up to the second SYN (JAVA_UNTIL); the rest is
an engine test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-30 18:08:18 +00:00
co-authored by Claude Opus 5.5
parent a88fa1512c
commit 7e0348636c
11 changed files with 1056 additions and 40 deletions
+19
View File
@@ -203,6 +203,17 @@ und Mitschnitt teilen `DhcpRun` und `TcpRun` in `ScenarioNetwork`.
* `lostData`: verbunden, dann gehen alle Frames verloren
(`Verbindung.setDrop`, wie der Paketverlust des Kerns), während der Client
sendet; der Verlust endet, wenn `senden` scheitert.
* `lostSynAck`: das erste SYN+ACK des Servers geht verloren.
* `lostFin`: verbunden, der Client schließt; sein FIN geht verloren.
* `lostLastFin`: verbunden, der Client schließt; das FIN des Servers geht
verloren.
Einen einzelnen Frame verliert `loseFirst`: Per Reflection ersetzt es den
Ausgangspuffer der Karte (`Port.ausgangsPuffer`) durch einen, der den ersten
passenden Frame verwirft, statt ihn einzureihen. Das Kabel wartet am alten
Puffer und wird dort einmal geweckt. Der Lauscher des Absenders sieht den
Frame wie bei `setDrop` (er schreibt nach dem Einreihen); anders als dort
belegt der Frame das Kabel nicht.
Gemessen (30.09.2026, 90 %, RTT 2750 ms), was Swift anders macht:
@@ -220,6 +231,14 @@ Gemessen (30.09.2026, 90 %, RTT 2750 ms), was Swift anders macht:
`connectClientMode` `closeSocket`, und die Schleife über drei Versuche
endet (`verbinden` scheitert nach 3 RTT). Der Server-Socket wiederholt sein
SYN+ACK nie.
* Nach einem verlorenen SYN+ACK gibt der Server nach 3 RTT auf und schickt aus
`SYN_RCVD` ein FIN (`close`). In zwei von drei Läufen wartete er 6 RTT: Der
Vergleich `vergangen > defaultTimeout()` liegt genau auf der Grenze.
* Ein FIN geht einmal hinaus, nie wieder. Danach wartet `close` höchstens
fünfmal 3 RTT; jedes ankommende Segment beendet eine Wartezeit. Dann ist
der Socket weg, sein Zustand bleibt aber stehen (`FIN_WAIT_1`,
`FIN_WAIT_2`, `LAST_ACK`). Die Gegenseite eines verlorenen FIN bleibt
`ESTABLISHED`.
* Startnummern aus einem Zähler über alle TCP-Sockets (1000000, 2000000, …),
wie im Kern; der freie Port ist zufällig.