feat(core): the packet capture's table after Java's Lauscher, measured against Java's own tables

captureRows.ts builds Java's rows per frame (Lauscher.datenVorbereiten):
Ethernet; ARP, ICMP or IP; TCP or UDP; the data with Java's protocol
recognition; with addresses and ports or ports only. The comment stays
structured; captureCommentText says it with given words and numbers.

ScenarioNetwork writes Java's finished tables per card (Lauscher.getDaten,
German locale); all runtime runs are recorded again, NatRun has ttlOut.
expectSameCaptures compares both views for every frame. Fixed where the
engine differed: an ARP request's target MAC is the broadcast address,
an ICMP error's identifier and sequence number are 0 (RFC 792), and host
unreachable from a router carries the packet as forwarded. Not taken:
Java's lower-case broadcast address in a reply to 0.0.0.0, and the typo
'Exeeded'. RIP keeps its recordings: Java's staggered router start now
gives a different startup on this machine.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-01 15:11:05 +00:00
co-authored by Claude Opus 5.5
parent 0a34f3a4c4
commit 21a404693c
55 changed files with 37423 additions and 1321 deletions
+30
View File
@@ -476,3 +476,33 @@ Javas Switch prüft seine Tabelle einmal je Sekunde ab dem Start
nach der Lernzeit; der Kern genau bei ihr. Das ist gewollt und wird nicht
gemessen: Wo Java nur in einem Takt nachsieht, macht der Kern es genauer
(Entscheidung 01.10.2026; dasselbe für die NAT-Einträge des Heimrouters).
## Tabelle des Mitschnitts
Seit F2.8b schreibt `ScenarioNetwork.writeFrames` neben den Frames Javas
fertige Tabelle jeder Karte (`Lauscher.getDaten`): `rows` mit Adresse:Port
(das Fenster „Datenaustausch"), `layeredRows` nur mit Ports (die
Schichtenansicht); je Zeile Nummer, Quelle, Ziel, Protokoll, Schicht,
Bemerkung, ohne die Uhrzeit. Java läuft dabei mit `Locale.GERMANY`
(`Information.getLocaleOrDefault`): deutsche Wörter, Tausenderpunkte. Alle
Laufzeit-Läufe sind dafür neu aufgenommen (bis auf `dhcp-lan`, s. oben);
`NatRun` hat den Ablauf `ttlOut` (Ping mit TTL 1 aus dem LAN: der Heimrouter
meldet „Zeit überschritten").
Gemessen (01.10.2026), wo der Kern abwich:
* Eine ARP-Anfrage trägt als Ziel-MAC `FF:FF:FF:FF:FF:FF`
(`ARP.sendeARPBroadcast`), der Kern trug Nullen (wie Swift).
* Eine ICMP-Fehlermeldung trägt Identifier und Sequenznummer 0, der Kern
kopierte die des Echos (wie Swift).
* „Host nicht erreichbar" vom Router trägt das Paket mit gesenkter TTL (wie
er es weitergeleitet hat), der Kern das angekommene.
Bewusst nicht übernommen: Javas Antwort an `0.0.0.0` schreibt die
Broadcast-Adresse klein (`ARP.sendArpReply`), der Kern immer groß; „ICMP
Time Exeeded" ist ein Tippfehler.
RIP: Der Anlauf entscheidet, wie viele Zufallspausen jeder Router zieht
(jede Nachricht zieht eine), und der hängt an Javas Startreihenfolge. Eine
Aufnahme, deren Anlauf von dem des Kerns (alle Router zugleich) abweicht,
liegt danach in allen Zeiten daneben; das Werkzeug wird dann wiederholt.
+1 -1
View File
@@ -154,7 +154,7 @@ done
# NAT as Java FILIUS runs it: nat-home's home router between a LAN PC and two
# WAN hosts (NatRun); frames, calls and the router's NAT table at the end.
for run in pingOut:10000 udpOut:10000 udpTwoRemotes:25000 tcpOut:20000 forward:20000 pingWan:15000; do
for run in pingOut:10000 udpOut:10000 udpTwoRemotes:25000 tcpOut:20000 forward:20000 pingWan:15000 ttlOut:10000; do
story="${run%%:*}"
output="$root/packages/core/test/scenarios/nat-java-$story.java.json"
rm -f "$output"
+4
View File
@@ -15,6 +15,7 @@
// forward 42.0.0.20 connects to 42.0.0.10:8080, forwarded to
// 192.168.0.10:80; sends, receives, closes
// pingWan 42.0.0.20 pings 42.0.0.10, then 192.168.0.10
// ttlOut 192.168.0.10 pings 42.0.0.20 with TTL 1: time exceeded
import filius.software.nat.NatEntry;
import filius.software.nat.NatGateway;
@@ -194,6 +195,9 @@ public class NatRun {
Thread.sleep(200);
tcpClient(SERVER, "42.0.0.10", 8080);
break;
case "ttlOut":
call("sendProbe 42.0.0.20", () -> icmp(os(LAN_PC).holeICMP().sendProbe("42.0.0.20", 1, 1, 1)));
break;
case "pingWan":
call("sendProbe 42.0.0.10", () -> icmp(os(SERVER).holeICMP().sendProbe("42.0.0.10", 64, 1, 1)));
call("sendProbe 192.168.0.10", () -> icmp(os(SERVER).holeICMP().sendProbe("192.168.0.10", 64, 1, 2)));
@@ -242,6 +242,32 @@ public class ScenarioNetwork {
out.close(']');
}
out.close('}');
writeRows(out, "rows", true);
writeRows(out, "layeredRows", false);
}
/**
* The packet capture's table of each card as FILIUS shows it
* (Lauscher.getDaten): with addresses and ports (`rows`, the data exchange
* window) or ports only (`layeredRows`, the layered view). Each row: number,
* source, destination, protocol, layer, comment — its time (the wall clock)
* left out.
*/
void writeRows(Json out, String key, boolean withAddresses) {
out.key(key).open('{');
for (Map.Entry<String, String> entry : macByNode.entrySet()) {
out.key(entry.getKey()).open('[');
for (Object[] row : Lauscher.getLauscher().getDaten(entry.getValue(), withAddresses, 1)) {
out.open('[');
for (int column = 0; column < row.length; column++) {
if (column == 1) continue;
out.string(row[column] == null ? null : String.valueOf(row[column]));
}
out.close(']');
}
out.close(']');
}
out.close('}');
}
/** Each host's address, mask, gateway and DNS server. */