Java's RIPTable.check marks an expired route only at the router's next beacon, up to some 35 s late. The engine schedules each router's first expiry, marks the route with 16 hops at that time and sends its table at once, as after any change (better than Java where Java only samples, decided 01.10.2026). ripJava's expire story is compared up to the expiry and then checks that R2 marks the route at its time and R1 a cable later. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
479 lines
27 KiB
Markdown
479 lines
27 KiB
Markdown
# Java-Orakel
|
||
|
||
Das Orakel misst Javas **echtes** Verhalten, Fehler eingeschlossen. Der
|
||
TypeScript-Leser folgt Java, übernimmt aber keine Java-Fehler (Datenverlust,
|
||
Absturz, eigene Ausgabe nicht wieder lesbar, Tippfehler, Verstoß gegen den
|
||
Standard); jede solche Abweichung steht in den Tests namentlich mit Grund
|
||
(`JAVA_BUGS`). Was Java selbst schreibt — der Korpus — wird exakt wie in
|
||
Java gelesen.
|
||
|
||
Java-Filius selbst liest den Korpus (`corpus/`) und schreibt, was es gefunden
|
||
hat, nach `corpus/corpus.java.json`. **Java ist der Schiedsrichter für das
|
||
Dateiformat** — der Swift-Leser verliert in 10 von 23 Dateien Geräte (Plan
|
||
§13, M2).
|
||
|
||
```bash
|
||
npm run oracle:java # = tools/java-oracle/run.sh
|
||
```
|
||
|
||
Braucht JDK 17+, Maven und `xvfb-run`. Filius öffnet beim Laden sein
|
||
Hauptfenster (`Information.reset` → `JMainFrame`), deshalb läuft es an einem
|
||
virtuellen Bildschirm statt „headless".
|
||
|
||
* Gelesen wird so, wie Filius eine Datei öffnet: `SzenarioVerwaltung.laden`
|
||
entpackt das Archiv, kopiert die eigenen Anwendungen
|
||
(`projekt/anwendungen`) dorthin, wo Filius' Klassenlader sucht, und lädt
|
||
dann die Konfiguration — Javas Weg, kein Nachbau (seit 30.09.2026, M8).
|
||
`--network` lädt nur die Konfiguration über die private Methode
|
||
`netzwerkLaden` (per Reflection), wie vorher. Der Unterschied am Korpus:
|
||
In `anwendung_komplett.fls` und `multicast_server.fls` sind dann die
|
||
eigenen Anwendungen `Accumulator` und `Broadcast` installiert. Filius'
|
||
Klassenlader ist ein Singleton: Eine einmal geladene eigene Klasse bleibt
|
||
für die übrigen Dateien desselben Laufs geladen. Am Korpus stört das
|
||
nicht, denn keine Datei nutzt eine Klasse, die sie nicht mitbringt.
|
||
* Stand: `gitlab.com/filius1/filius` @ `6640354` (2.14.0), wird beim ersten
|
||
Lauf nach `.upstream/` geholt und gebaut.
|
||
* **Gewürfeltes:** Was nicht in der Datei steht, würfelt Filius zum Teil
|
||
(MAC-Adressen, die SSID eines Switches). Jede Datei wird deshalb **zweimal**
|
||
geladen; jeder Wert, der sich zwischen beiden Läufen unterscheidet,
|
||
erscheint als `null`. Zwei Läufe des Werkzeugs sind byte-gleich. (Bis
|
||
29.09.2026 suchte es die MAC im Dateitext und hielt dabei acht
|
||
kleingeschriebene für gewürfelt.)
|
||
* Bisher ausgelesen: Geräte (Klasse, Name, Beschriftung, Position,
|
||
Schnittstellen samt Funk-Eigenschaft `wireless`, bei Rechner/Notebook `useIPAsName`/`useMACAsName`), unter
|
||
`system` SSID, Lernzeit des Switches, RIP, manuelle Routen, die Firewall
|
||
von Router und Heimrouter, DHCP-Client und -Server, die statische
|
||
NAT-Tabelle des Heimrouters, die Verbindung eines Modems und das
|
||
Dateisystem (Inhalte als Länge und SHA-256, damit die Referenz klein
|
||
bleibt; bei Router und Heimrouter stehen vorn die Dateien, die Javas
|
||
Konstruktor aus eigenen Vorlagen erzeugt, dahinter die aus der Datei) und
|
||
die installierten Programme eines Rechners mit ihren Einstellungen (die
|
||
persönliche Firewall samt Regeln; je Programm `onItsSystem`: ob es am
|
||
Betriebssystem seines Knotens hängt) (Klassen, die Filius
|
||
nicht mitbringt — eigene Anwendungen im Archiv unter `projekt/anwendungen`
|
||
— fehlen, weil das Werkzeug nur `konfiguration.xml` lädt); E-Mail: Konten des Servers aus der Bean-Liste und aus `/mailserver/konten.txt` (`kontenLaden`), Ordner und Adressbuch des Clients und sein Konto aus `/konten.txt` (`laden`); ein Empfänger, den eine ältere Datei als Zeichenkette hält, samt dem, was Javas `AddressEntry(String)` daraus macht; Kabel (Enden als Gerät/Schnittstelle),
|
||
Doku-Elemente.
|
||
|
||
## Getter-Standardwerte
|
||
|
||
XMLEncoder teilt gleiche `Integer`/`Boolean`-Objekte. Ein Wert, der einmal als
|
||
Ergebnis eines Getters geschrieben wurde (`<void id="Integer0"
|
||
property="iconTextGap"/>`), taucht später nur noch als Verweis auf — etwa als
|
||
x-Koordinate eines Geräts. Daran scheitert der Swift-Leser (Plan §13, M2).
|
||
|
||
`GetterDefaults` erzeugt die betroffenen Klassen wie XMLDecoder (ohne
|
||
Argumente) und ruft den Getter auf; das Ergebnis steht in
|
||
`packages/fls/src/javaDefaults.json`. Gemessen, nicht geraten: Filius' Knopf
|
||
setzt `verticalTextPosition` auf 3 (BOTTOM), nicht auf Swings 0 (CENTER).
|
||
|
||
Dasselbe gilt für **statische Konstanten**: Die erste `0` einer Datei kann als
|
||
`<object class="javax.swing.SwingConstants" field="CENTER"/>` stehen, und jede
|
||
spätere `0` — etwa die Rot-, Grün- und Blauwerte einer schwarzen Doku-Farbe —
|
||
verweist darauf. Schreibweise im Aufruf: `klasse::FELD`, Ergebnis
|
||
`"SwingConstants.CENTER": 0`.
|
||
|
||
## `IP.ipCheck`
|
||
|
||
Java-Filius schickt jede IP-Adresse und Maske beim Laden durch
|
||
`IP.ipCheck`. `IpCheckCases` ruft es für jede Zeile von
|
||
`packages/fls/test/ip-check.cases.txt` auf (ASCII, Nicht-ASCII als
|
||
`\uXXXX`) und schreibt `ip-check.java.json`. Gemessen: leere Abschnitte
|
||
zählen nicht (`10..0.0.1`), Vorzeichen und führende Nullen gehen, ebenso
|
||
Ziffern jeder Schrift in der BMP (Thai, Devanagari, Vollbreite) — aber kein
|
||
Leerzeichen und kein 𝟏 (Ersatzpaar).
|
||
|
||
## `/dns/hosts`
|
||
|
||
Javas DNS-Server liest seine Einträge bei jeder Anfrage aus der Textdatei
|
||
`/dns/hosts`. `DnsHostsCases` legt für jede Zeile von
|
||
`packages/fls/test/dns-hosts.cases.txt` (eine ganze Datei je Zeile, ASCII,
|
||
Zeilenumbruch als `\u000A`) einen Rechner mit dieser Datei an und schreibt,
|
||
was `DNSServer.holeResourceRecords()` liefert, nach `dns-hosts.java.json`.
|
||
Gemessen: Felder trennt nur ein einzelnes Leerzeichen (Tab und NBSP nicht),
|
||
Zeilen nur LF; ein Name, den Javas Muster ablehnt (`1und1.de`, `müller.de`),
|
||
wird leer, der Eintrag bleibt trotzdem; Typen außer A/MX/NS (auch `a`,
|
||
`CNAME`, eine Kommentarzeile) gelten als gültig; eine TTL, die kein `int`
|
||
ist, wird still 3600.
|
||
|
||
## Was Java aus unseren Dateien lädt
|
||
|
||
`run.sh` lädt auch die Dateien unter `packages/fls/test/written/`, die
|
||
`topologyWriter.test.ts` mit `UPDATE_WRITTEN=1` schreibt, und unter
|
||
`packages/fls/test/exported/`, die `exportProject.test.ts` mit
|
||
`UPDATE_EXPORTED=1` schreibt (beide nicht eingecheckt), mit derselben
|
||
Lademethode wie den Korpus, und schreibt `written.java.json` bzw.
|
||
`exported.java.json` daneben, samt SHA-256 jeder Datei
|
||
(`--sha256`), damit der Test merkt, wenn der Befund nicht mehr zur Ausgabe
|
||
passt. Je Kabel steht auch `panel`: die Geräte, zwischen denen die
|
||
Oberfläche es zeichnet (`JCablePanel.ziel1/ziel2`) — im Korpus bei allen 183
|
||
Kabeln die Geräte, die es verbindet.
|
||
|
||
## Wie Java Base64 schreibt
|
||
|
||
`Base64Cases` ruft Filius' eigenes `Base64.encodeBytes` (iHarder 2.x) auf
|
||
Byte-Folgen von 19 Längen rund um die Zeilenlänge auf
|
||
(`packages/fls/test/base64.java.json`). Befund: nach je 76 Zeichen ein
|
||
`\n`, auch nach der letzten vollen Zeile, dann der aufgefüllte Rest ohne
|
||
Umbruch. So stehen Bilder in den Dateien, auch die, die Javas Konstruktoren
|
||
selbst anlegen.
|
||
|
||
## Was Java aus unserem XML macht
|
||
|
||
`BeansDump` liest jede `*.xml` unter `packages/fls/test/beans/` mit
|
||
`XMLDecoder` und beschreibt, was dabei herauskommt (Beans über ihre
|
||
Eigenschaften mit Lese- und Schreibmethode, Listen, Maps, Arrays,
|
||
`Rectangle`, `Color`; ein zweites Mal gesehenes Objekt als Verweis) —
|
||
`beans.java.json`. Fehler, die der Decoder sonst nur ins Log schreibt,
|
||
sammelt ein `ExceptionListener`. Zeichenketten stehen als ASCII mit
|
||
`\uXXXX`, weil ein einsames Surrogat in UTF-8 sonst zu „?“ würde.
|
||
|
||
## E-Mail-Kontodateien
|
||
|
||
`EmailFileCases server|client` legt für jede Zeile von
|
||
`packages/fls/test/email-server.cases.txt` bzw. `email-client.cases.txt`
|
||
einen Rechner mit dieser Datei an (`/mailserver/konten.txt` bzw.
|
||
`/konten.txt`) und schreibt, welche Konten `EmailServer.kontenLaden()` bzw.
|
||
`EmailAnwendung.laden()` daraus machen. Gemessen: Eine einzige Serverzeile
|
||
mit weniger als fünf Feldern lässt Java die ganze Datei verwerfen
|
||
(`NoSuchElementException` — die Simulation startet nicht); leere Felder
|
||
überspringt Java, statt sie zu zählen; eine Nachricht mit leerem Text geht
|
||
verloren, weil `split` leere Felder am Ende abschneidet. Beim Client gewinnt
|
||
die letzte Zeile, auch eine kaputte, die das Konto leert.
|
||
|
||
## DHCP zur Laufzeit
|
||
|
||
`DhcpRun` baut das Netz eines Kern-Szenarios (`packages/core/test/scenarios/*.scenario.json`:
|
||
Knoten, Kabel, Geräte, DHCP-Server und -Clients; die Schritte bleiben
|
||
unbeachtet) in Java-Filius nach und startet es wie der Aktionsmodus
|
||
(`starten()` jeder Systemsoftware). Nach der angegebenen Echtzeit schreibt es
|
||
nach `<name>.java.json`, was jede Netzwerkkarte gesehen hat (Filius' eigener
|
||
Mitschnitt, der `Lauscher`: Zeit, MACs, ARP-Operation und -Adressen,
|
||
IP/UDP-Adressen und -Ports samt DHCP-Text), und die Adressen jedes Rechners
|
||
am Ende. Die MAC eines Ports ist dieselbe wie im Kern (`mac`, sonst
|
||
`stableGeneratedMacAddress`).
|
||
|
||
* Switches und DHCP-Server starten zuerst: Javas Client wartet auf jeden
|
||
aktiven Server, findet sie aber über die Knotenliste der Oberfläche, die das
|
||
Werkzeug nicht hat.
|
||
* `GUIContainer.getGUIContainer()` wird vor dem Start einmal aufgerufen. Im
|
||
echten Filius steht das Fenster schon; ohne das legen zwei Clients den
|
||
Singleton gleichzeitig an (nicht synchronisiert), und einer von ihnen
|
||
sendet nie etwas (gemessen am Szenario `dhcp-lan`).
|
||
* Die Zeiten schwanken von Lauf zu Lauf um einige zehn Millisekunden (echte
|
||
Threads), die Link-Local-Adresse würfelt Java mit `Math.random`. Die
|
||
Reihenfolge der Nachrichten eines Clients und ihr Inhalt schwanken nicht.
|
||
|
||
Gemessen (30.09.2026), was Swift anders macht: Je Versuch sendet Java genau
|
||
ein DISCOVER bzw. REQUEST und wartet dann bis zu einer RTT (2,5 s) auf die
|
||
Antwort; eine unpassende Antwort (etwa das Angebot des zweiten Servers) liest
|
||
es und verwirft sie. Vor dem REQUEST prüft es die angebotene Adresse per ARP
|
||
und wartet dabei eine RTT; antwortet ein anderer Rechner, lehnt es mit
|
||
DHCPDECLINE ab. Javas Server übergeht das DECLINE und hält die Adresse
|
||
weiter als Angebot fest (4 RTT). Nach dem fünften Fehlversuch (Zeitablauf)
|
||
nimmt der Client eine Link-Local-Adresse `169.254.1.x/16`: ARP-Probe, eine
|
||
RTT warten, dann ein ARP-Reply an alle.
|
||
|
||
Die RTT ist in Java nicht fest: `Verbindung.holeRTT` = 50 Kabel ×
|
||
Verzögerung je Kabel × RTT-Faktor (Startoption, 1). Beim Anlegen des
|
||
Fensters setzt das Hauptmenü den Regler auf 90 %, also 55 ms je Kabel und
|
||
2750 ms RTT — so laufen die `DhcpRun`-Referenzen; gemessen sind die
|
||
Wartezeiten 2745 bis 2751 ms. Außerdem befördert ein Java-Kabel einen Frame
|
||
nach dem anderen (`SimplexVerbindung` schläft die Verzögerung mit gesperrtem
|
||
Puffer): Sendet eine Karte zwei Frames direkt nacheinander, kommt der zweite
|
||
eine Kabellänge später.
|
||
|
||
Filius' Mitschnitt selbst ist dabei nicht ganz zuverlässig: Starten zwei
|
||
Clients zugleich, fehlt auf der Karte des zweiten dessen eigenes DISCOVER
|
||
(vier von vier Läufen von `dhcp-lan`; der Server empfängt es).
|
||
|
||
## TCP zur Laufzeit
|
||
|
||
`TcpRun` spielt auf dem Netz von `tcp-lan` (Server 192.168.0.10, Clients .20
|
||
und .30) einen festen Ablauf von Socket-Aufrufen ab, wie Filius' Anwendungen
|
||
sie machen, den Server in einem eigenen Thread. Es schreibt nach
|
||
`tcp-java-<ablauf>.java.json` die Frames jeder Karte (mit TCP-Flags,
|
||
Sequenz- und Bestätigungsnummer), wann jeder Aufruf begann, zurückkam oder
|
||
scheiterte, und jeden Zustand jedes Sockets (alle 2 ms abgefragt). Netzbau
|
||
und Mitschnitt teilen `DhcpRun` und `TcpRun` in `ScenarioNetwork`.
|
||
|
||
* `exchange`: Verbindung zu Port 80, `GET /`, Antwort 3000 × `x` (drei
|
||
Segmente), der Client schließt, der Server eine Sekunde nach
|
||
`CLOSE_WAIT`.
|
||
* `closedPort`: Verbindung zu Port 81, auf dem niemand lauscht.
|
||
* `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:
|
||
|
||
* Jeder Versuch wartet 3 RTT (8250 ms): Daten gehen dreimal hinaus (1720,
|
||
9970, 18220 ms), `senden` scheitert 8250 ms nach dem dritten.
|
||
* Scheitert `senden`, schließt Java ordentlich: FIN, ACK, FIN, ACK. Swift
|
||
setzt den Socket ohne FIN auf CLOSED.
|
||
* `TIME_WAIT` dauert 3 RTT (8250 ms; oder bis zum nächsten Segment), nicht
|
||
eine RTT.
|
||
* Die Gegenseite schließt von selbst: Kommt ein FIN, geht Javas
|
||
Socket-Thread aus `listen` in `close` und sendet sein FIN sofort
|
||
(`LAST_ACK` rund 60 ms nach dem FIN), nicht erst, wenn die Anwendung
|
||
`schliessen` aufruft.
|
||
* Der Client sendet nur **ein** SYN: Nach dem ersten Zeitablauf setzt
|
||
`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.
|
||
|
||
## DNS zur Laufzeit
|
||
|
||
`DnsRun` ruft auf dem Netz von `dns-lan` den Resolver eines Clients auf, wie
|
||
Filius' Programme es tun, und schreibt nach `dns-java-<ablauf>.java.json` die
|
||
Frames jeder Karte und das Ergebnis des Aufrufs. `ScenarioNetwork` richtet
|
||
dazu die `dnsServers` des Szenarios ein: installiert, Einträge über
|
||
`hinzuRecord` in `/dns/hosts` (Zeilen `name TYP ziel`, TTL 3600),
|
||
eingeschaltet. Server 192.168.0.10 kennt `web.filius.de`, den Mailserver
|
||
von `filius.de` und verweist `schule.de` an 192.168.0.11, der
|
||
`www.schule.de` kennt.
|
||
|
||
* `address`, `mail`, `referral`, `unknown`: A, MX (samt A des Mailservers),
|
||
Verweis per NS, unbekannter Name.
|
||
* `noServer`: der DNS-Server des Clients (192.168.0.99) fehlt.
|
||
|
||
Gemessen (30.09.2026), Javas Format, das Swift durch ein eigenes ersetzt:
|
||
Jede Nachricht ist Text, `ID=… QR=… RCODE=… QDCOUNT=… ANCOUNT=… NSCOUNT=…
|
||
ARCOUNT=… ` und dann je Zeile eine Frage `name. TYP IN` oder ein Eintrag
|
||
`name. TYP ttl ziel`. Die ID würfelt Java (`Math.random`). Die Antwort
|
||
wiederholt die Frage nicht (`QDCOUNT=0`), RCODE ist immer 0, auch für einen
|
||
unbekannten Namen (leere Antwort), und ein Verweis steht mit NS und A des
|
||
Nameservers im Antwortteil. Der Resolver folgt dem Verweis selbst
|
||
(iterativ) und wartet je Anfrage 5 RTT (13750 ms bei 90 %); ohne Server
|
||
fragt ARP zweimal im Abstand einer RTT, dann scheitert der Aufruf.
|
||
|
||
Auf dem Netz von `dns-recursive` (sonst wie `dns-lan`) löst 192.168.0.10
|
||
rekursiv auf, sein eigener DNS-Server ist .11, und er führt die Zone
|
||
`filius.de` mit sich selbst als Nameserver. Abläufe
|
||
(`dns-recursive-java-<ablauf>.java.json`): `address` (eigener Eintrag),
|
||
`referral` (`www.schule.de` über den NS von `schule.de`), `unknown`
|
||
(`x.unbekannt.org`), `unknownInZone` (`nix.schule.de`, das .11 nicht kennt),
|
||
`unknownInOwnZone` (`nix.filius.de`).
|
||
|
||
Gemessen (30.09.2026): Findet der Server nichts in seinen Einträgen, fragt
|
||
er selbst, mit seinem eigenen Resolver (iterativ, 5 RTT je Anfrage): zuerst
|
||
den Nameserver der längsten passenden Zone aus seinen Einträgen, bringt das
|
||
nichts, seinen eigenen DNS-Server (nur wenn das nicht er selbst ist). Die
|
||
Antwort an den Client enthält, was er gelernt hat, sonst den Verweis (NS und
|
||
A). Bei `unknownInZone` fragt er .11 also zweimal. Bei `unknownInOwnZone`
|
||
fragt er als Nameserver der Zone sich selbst, ohne Ende: Jede Ebene erzeugt
|
||
die nächste (ohne Frames, die Anfragen bleiben im Rechner), der Client gibt
|
||
nach 5 RTT auf, dann laufen die inneren Ebenen nacheinander ab und fragen
|
||
ersatzweise .11, eine Anfrage alle 55 ms. Ein Java-Fehler; der Kern fragt
|
||
sich weder als Nameserver noch als DNS-Server selbst.
|
||
|
||
## ARP zur Laufzeit
|
||
|
||
`ArpRun` sendet auf dem Netz von `arp-routed` (10.0.0.2 über einen Switch am
|
||
Router 10.0.0.1, 20.0.0.2 am Router 20.0.0.1; `ScenarioNetwork` baut dazu
|
||
Router mit `setzeAnzahlAnschluesse` und den `interfaces` des Szenarios) an
|
||
Adressen, die keine Karte hat, und schreibt nach `arp-java-<ablauf>.java.json`
|
||
die Frames und die Aufrufe. Abläufe: `pingLan` (Ping an 10.0.0.9,
|
||
`ICMP.sendProbe`), `pingRouted` (Ping an 20.0.0.9), `udpTwice` (zwei
|
||
Datagramme an 10.0.0.9), `udpBehind` (eines an 10.0.0.9, dann eines an
|
||
20.0.0.2), `routedUdpBehind` (eines an 20.0.0.9, dann eines an 20.0.0.2:
|
||
der Router leitet beide weiter), `routedPingBesideUdp` (Ping an 20.0.0.2,
|
||
damit der Router es kennt, ein Datagramm an 20.0.0.9, 100 ms später wieder
|
||
ein Ping an 20.0.0.2) und `routedPingBehindPing` (ein Echo-Request an
|
||
20.0.0.9 ohne Warten auf Antwort, dann ein Ping an 20.0.0.2). ICMP-Frames
|
||
schreibt `ScenarioNetwork` mit Typ, Code, Adressen und Sequenznummer.
|
||
|
||
Gemessen (30.09.2026), was der Kern (nach Swift) anders macht:
|
||
|
||
* ARP fragt bis zu zweimal und wartet je eine RTT (`holeARPTabellenEintrag(ziel,
|
||
2, …)`); dann „Host unreachable" (ICMP 3/1). Einen Negativ-Cache gibt es
|
||
nicht: das nächste Paket fragt wieder zweimal.
|
||
* Wer wartet, ist ein Thread: beim Ping der Aufrufer (5,5 s, dann ICMP 3/1
|
||
vom eigenen Rechner, Absender 127.0.0.1), beim Weiterleiten der IP-Thread
|
||
des Routers, bei UDP und TCP der Transport-Thread je Protokoll und Knoten.
|
||
`senden` kehrt sofort zurück, aber der Transport-Thread hält seine
|
||
Warteschlange gesperrt, solange er wartet: das nächste `senden` blockiert
|
||
bis dahin (5,5 s), und ein Datagramm an einen erreichbaren Rechner geht
|
||
erst danach.
|
||
* Ein Ping wartet eine RTT auf die Antwort; das „Host unreachable" des
|
||
Routers (nach dessen 5,5 s) kommt zu spät.
|
||
|
||
Gemessen (30.09.2026) an den Router-Abläufen:
|
||
|
||
* Der Router leitet UDP und TCP in seinem IP-Thread weiter, ICMP in seinem
|
||
ICMP-Thread (`EthernetThread` sortiert nach Protokoll in zwei Puffer).
|
||
Beide halten ihren Puffer, solange ARP wartet: Ein Datagramm an 20.0.0.2
|
||
wartet 5,5 s hinter einem an 20.0.0.9, ein Echo-Request ebenso hinter
|
||
einem Echo-Request (der Ping kehrt nach einer RTT ohne Antwort zurück, die
|
||
Antwort kommt später). Ein Ping neben einem hängenden Datagramm geht sofort
|
||
durch. Auch das Echo-Reply eines Rechners schickt sein ICMP-Thread.
|
||
* Javas ARP weckt mit `arpTabelle.notify()` nur **einen** Wartenden, nicht
|
||
den, dessen Antwort kam. Warten zwei Threads (im Rechner der UDP-Thread und
|
||
der Ping-Aufrufer, im Router IP- und ICMP-Thread), wartet der andere bis
|
||
zum Ende seiner RTT; einer, der fälschlich geweckt wurde, verbraucht einen
|
||
Versuch und fragt sofort wieder (gemessen: das zweite ARP nach 371 ms
|
||
statt 2750, der ICMP-Thread schickte den Echo-Request eine RTT zu spät).
|
||
Ein Java-Fehler, den der Kern nicht übernimmt: Er weckt alle, die auf die
|
||
Adresse warten. Die Abläufe meiden ihn (nur ein Wartender zur Zeit).
|
||
* Ein Datagramm, das `senden` dem UDP-Thread übergibt, geht oft erst nach
|
||
einem Frame hinaus, den der Aufrufer gleich danach selbst schickt (der
|
||
Thread wacht später auf). `routedPingBesideUdp` wartet deshalb 100 ms.
|
||
|
||
Der Kern spielt alle sieben Abläufe wie Java (`packages/core/test/arpJava.test.ts`:
|
||
Frames, Abstände und die Zeiten, zu denen die Aufrufe zurückkehren). Die UDP-Abläufe
|
||
seit F2.4b-2: ein Senden kehrt sofort zurück wie in Java (Swifts Senden wartete, bis
|
||
alle Frames angekommen waren), und je Knoten und Protokoll sendet ein Transport-Thread
|
||
ein Segment nach dem anderen.
|
||
|
||
## RIP zur Laufzeit
|
||
|
||
`RipRun` startet das Netz von `rip-chain` (PC 10.0.1.2 – R1 – R2 – R3 – PC
|
||
10.0.3.2, jeder Router mit RIP; `ScenarioNetwork` schaltet RIP für die im
|
||
Feld `rip` genannten Router ein) und schreibt nach `rip-java-<ablauf>.java.json`
|
||
die Frames, jede Änderung jeder RIP-Tabelle (alle 2 ms abgefragt, Zeile
|
||
`netz/maske gateway schnittstelle hops; …`) und die Aufrufe. Java würfelt die
|
||
Pausen eines Routers aus einem neuen `Random`; `RipRun` setzt jedem Router
|
||
per Reflection (`VermittlungsrechnerBetriebssystem.ripbeacon`,
|
||
`RIPBeacon.rand`) den Startwert, den der Kern ihm gibt (`ripSeed`: Startwert
|
||
des Szenarios, `0x52495000` und FNV-1a der Router-ID). Dann würfeln beide
|
||
dieselben Pausen.
|
||
|
||
* `converge`: die Tabellen füllen sich, bei 45 s pingt 10.0.1.2 10.0.3.2.
|
||
* `expire`: bei 10 s schweigt R3 (`RIPTable.setNextBeacon(Long.MAX_VALUE)`),
|
||
bei 125 s der Ping noch einmal.
|
||
|
||
Gemessen (30.09.2026, dazu die Pausenformel am JDK):
|
||
|
||
* Die Pause ist `(int) (30000 * (rand.nextFloat() / 3 + 0.84))`; `nextFloat() / 3`
|
||
rechnet Java in `float`, in etwa einem von 10.000 Fällen eine Millisekunde
|
||
anders als in `double`.
|
||
* Eine gelernte Route bekommt 16 Hops erst beim nächsten Beacon ihres Routers
|
||
nach dem Verfallen (`RIPTable.check`): R3s Netz verfiel bei R2 nach rund
|
||
76,4 s, R2 setzte es bei seinem Beacon um 86,7 s auf 16; R1 hörte die 16 vom
|
||
selben Router und meldete sofort. Der Ping bekommt dann ICMP 3/0 von R1.
|
||
Der Kern setzt die Route seit 01.10.2026 genau beim Verfallen auf 16 und
|
||
meldet sofort (besser als Java, wo Java nur bei seinem Takt nachsieht);
|
||
`ripJava.test.ts` vergleicht `expire` bis dahin und prüft danach, dass R2
|
||
zur Zeit und R1 ein Kabel später auf 16 steht (`BETTER_THAN_JAVA_FROM`).
|
||
* Die Router starten nacheinander, rund 20 ms auseinander; wer einen
|
||
Nachbarn vor seinem ersten Beacon hört, sendet die gelernte Route gleich
|
||
mit. Senden mehrere zugleich, fehlt im Lauscher mancher eigene Frame.
|
||
|
||
## NAT zur Laufzeit
|
||
|
||
`NatRun` startet das Netz von `nat-home` (PC 192.168.0.10 am LAN-Port des
|
||
Heimrouters 192.168.0.1; sein WAN-Port 42.0.0.10/8 an einem Switch mit
|
||
42.0.0.20 und 42.0.0.30; Portweiterleitung TCP 8080 an 192.168.0.10:80;
|
||
`ScenarioNetwork` baut Heimrouter und das Feld `portForwarding` über
|
||
`GatewayFirmware.setStaticNAT`) und schreibt nach `nat-java-<ablauf>.java.json`
|
||
die Frames, die Aufrufe und am Ende die NAT-Tabelle des Heimrouters. Abläufe:
|
||
`pingOut`, `udpOut` (Antwort vom Server), `udpTwoRemotes` (danach vom selben
|
||
Port an 42.0.0.30), `tcpOut`, `forward` (von 42.0.0.20 an 42.0.0.10:8080),
|
||
`pingWan` (42.0.0.20 pingt die WAN-Adresse, dann 192.168.0.10).
|
||
|
||
Ein UDP-Server im Werkzeug ist ein aktiver Socket auf Port 7: ein passiver
|
||
trägt seinen Port erst ein, wenn Daten da sind, die vorher nicht ankommen.
|
||
|
||
`ScenarioNetwork` schreibt seit F2.6 die Frames **jeder** Karte eines Knotens
|
||
(die erste unter der Knoten-ID, weitere unter `id#index`); vorher nur die
|
||
erste, so dass bei Routern und Heimroutern die zweite Karte fehlte. Die
|
||
ARP-, RIP- und NAT-Läufe sind damit neu aufgenommen; die übrigen Knoten haben
|
||
nur eine Karte.
|
||
|
||
Gemessen (30.09.2026), was der Heimrouter tut (`NatGateway`, dessen
|
||
Threads zwischen Karte und Ethernet-Schicht sitzen):
|
||
|
||
* Ausgehend (Ziel außerhalb des LAN, nicht die WAN-Adresse) wird die
|
||
Absenderadresse zur WAN-Adresse, der Port zu einem freien Port des
|
||
Heimrouters (nach Javas Zufall); ICMP behält seine Kennung, die als Port
|
||
des Eintrags dient. Einen Eintrag legt ein TCP-SYN, jedes UDP-Datagramm
|
||
und jedes ICMP-Paket an, aber nur, wenn der LAN-Endpunkt noch keinen hat.
|
||
* Eingehend an die WAN-Adresse sucht der Heimrouter den Eintrag nach
|
||
Gegenstelle, WAN-Port und Protokoll, sonst nach der Portweiterleitung, die
|
||
dann einen dynamischen Eintrag für die Gegenstelle anlegt
|
||
(`DynamicEnryFromStatic`).
|
||
* Ein zweiter Server, an den derselbe LAN-Endpunkt sendet, bekommt das
|
||
Datagramm mit demselben WAN-Port, seine Antwort aber findet keinen Eintrag
|
||
(der gilt nur für die erste Gegenstelle) und kommt nicht ins LAN.
|
||
* Ein Ping an die WAN-Adresse beantwortet der Heimrouter, einer an eine
|
||
LAN-Adresse von außen verschwindet.
|
||
* Dynamische Einträge verfallen nach 300 s mal Javas Verzögerungsfaktor
|
||
(bei 90 % 55 Minuten), geprüft jede Sekunde, aufgefrischt nur von
|
||
eingehenden Paketen (aus dem Code, nicht gemessen). Der Kern verfällt sie
|
||
seit 01.10.2026 genau in diesem Alter statt beim nächsten Sekundentakt.
|
||
|
||
|
||
## WLAN zur Laufzeit
|
||
|
||
`WlanRun` startet das Netz von `wlan-lan` (Notebooks 192.168.1.11 und .12
|
||
mit Funkkarte, je über ein Funkkabel am Switch mit der SSID „schule";
|
||
PC 192.168.1.13 an einem Kabel; Notebook 192.168.1.14 mit Funkkarte und
|
||
SSID „schule", aber ohne Kabel) und schreibt nach `wlan-java-<ablauf>.java.json`
|
||
die Frames und die Aufrufe. `ScenarioNetwork` setzt dazu `Kabel.setWireless`
|
||
nach dem Feld `wireless` eines Kabels, die SSID des Switches (`switches`)
|
||
und Funkkarte und SSID der Rechner (`wireless`). Abläufe: `pingWired` (.11
|
||
pingt .13), `pingWireless` (.11 pingt .12), `pingWithoutCable` (.11 pingt
|
||
.14), `pingFromWithoutCable` (.14 pingt .11).
|
||
|
||
Gemessen (01.10.2026), wie im Code (`Kabel.wireless`, `Betriebssystem.ssid`
|
||
liest nur die Oberfläche):
|
||
|
||
* Ein Funkkabel ist zur Laufzeit ein Kabel wie jedes andere: dieselben
|
||
Frames zu denselben Zeiten über Funk wie über Kabel (Ping 470 ms).
|
||
* Eine SSID verbindet nichts. Das Notebook ohne Kabel bleibt unerreichbar
|
||
(zweimal ARP, nach 5,5 s „Host unreachable" beim Absender), und es selbst
|
||
erreicht niemanden. Das Funkkabel legt nur Javas Editor
|
||
(`JHostKonfiguration.updateWifiConnection`: das alte entfernen, an den
|
||
Switch mit der SSID anschließen); ändert sich die SSID eines Switches,
|
||
entfernt er dessen Funkkabel, ohne neu zu verbinden. So entsteht in einer
|
||
Datei ein Rechner mit SSID ohne Kabel.
|
||
|
||
Swift verbindet zur Laufzeit jeden Rechner mit eingeschaltetem WLAN mit
|
||
einem freien Port des Switches seiner SSID, auch ohne Kabel
|
||
(`wirelessAssociations`). Der Kern folgt Java: Er kennt nur die Kabel.
|
||
|
||
## Mitschnitt je Karte
|
||
|
||
Was `ScenarioNetwork.writeFrames` schreibt, ist Javas Mitschnitt selbst
|
||
(`Lauscher.datenEinheiten`): je Karte jeder Frame, den sie gesendet oder
|
||
empfangen hat. Seit F2.8a vergleicht der Kern seinen Mitschnitt Karte für
|
||
Karte damit (`expectSameCaptures` in `packages/core/test/javaRun.ts`), nicht
|
||
mehr nur jeden Frame dort, wo er zuerst auftaucht.
|
||
|
||
Dabei fiel auf (01.10.2026): `ScenarioNetwork` übernahm die Lernzeit eines
|
||
Switches (`switches.<id>.retention`) nicht; Java lief mit 300 s, der Kern
|
||
mit den 5 s der Szenarien `tcp-lan` und `dhcp-*`. Der alte Vergleich sah den
|
||
Unterschied nicht: Ein gefluteter Frame erscheint nur auf zusätzlichen
|
||
Karten. Seit F2.8a setzt das Werkzeug die Lernzeit
|
||
(`SwitchFirmware.setRetentionTime`), die TCP- und DHCP-Läufe sind neu
|
||
aufgenommen. Mit 5 s flutet auch Javas Switch in `lostData` den FIN nach
|
||
26 s an alle Karten, wie der Kern. `dhcp-lan` behält die alte Aufnahme: Die
|
||
neue unterscheidet sich nur darin, welcher der beiden Clients zuerst sendet;
|
||
nach 5 s gehen dort nur noch Broadcasts.
|
||
|
||
Javas Switch prüft seine Tabelle einmal je Sekunde ab dem Start
|
||
(`SwitchFirmware.checkSAT`), ein Eintrag verfällt also bis zu einer Sekunde
|
||
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).
|