Java's GatewayFirmware.checkNAT and SwitchFirmware.checkSAT look every second from the start, so an entry goes up to a second late. The engine expires both exactly at their age: better than Java where Java only samples, as long as saved files stay compatible (decided 01.10.2026). The switch did so already; its departure is now named in the code. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
475 lines
27 KiB
Markdown
475 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.
|
||
* 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).
|