Programs run as generators that yield what they wait for (threads.ts); the engine resumes them after every event. TCP connect, send, receive, accept and name resolution exist as such steps; the blocking calls wrap them. EchoRun records eight stories in Java FILIUS, frames, tables and each program's window lines; echoJava.test.ts plays them card by card. Not taken from Java: a connection without a route hangs for ever; a server switched off leaves its connections' last FIN unanswered. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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).
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.ladenentpackt 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).--networklädt nur die Konfiguration über die private MethodenetzwerkLaden(per Reflection), wie vorher. Der Unterschied am Korpus: Inanwendung_komplett.flsundmulticast_server.flssind dann die eigenen AnwendungenAccumulatorundBroadcastinstalliert. 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/NotebookuseIPAsName/useMACAsName), untersystemSSID, 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 ProgrammonItsSystem: ob es am Betriebssystem seines Knotens hängt) (Klassen, die Filius nicht mitbringt — eigene Anwendungen im Archiv unterprojekt/anwendungen— fehlen, weil das Werkzeug nurkonfiguration.xmllä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 JavasAddressEntry(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 Szenariodhcp-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 nachCLOSE_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, wennsendenscheitert.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),
sendenscheitert 8250 ms nach dem dritten. - Scheitert
senden, schließt Java ordentlich: FIN, ACK, FIN, ACK. Swift setzt den Socket ohne FIN auf CLOSED. TIME_WAITdauert 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
listenincloseund sendet sein FIN sofort (LAST_ACKrund 60 ms nach dem FIN), nicht erst, wenn die Anwendungschliessenaufruft. - Der Client sendet nur ein SYN: Nach dem ersten Zeitablauf setzt
connectClientModecloseSocket, und die Schleife über drei Versuche endet (verbindenscheitert 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_RCVDein FIN (close). In zwei von drei Läufen wartete er 6 RTT: Der Vergleichvergangen > defaultTimeout()liegt genau auf der Grenze. - Ein FIN geht einmal hinaus, nie wieder. Danach wartet
closehö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 bleibtESTABLISHED. - 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.
sendenkehrt sofort zurück, aber der Transport-Thread hält seine Warteschlange gesperrt, solange er wartet: das nächstesendenblockiert 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 (
EthernetThreadsortiert 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
sendendem UDP-Thread übergibt, geht oft erst nach einem Frame hinaus, den der Aufrufer gleich danach selbst schickt (der Thread wacht später auf).routedPingBesideUdpwartet 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() / 3rechnet Java infloat, in etwa einem von 10.000 Fällen eine Millisekunde anders als indouble. - 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.tsvergleichtexpirebis 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).
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.
Beispiele von Filius (Abnahme F2)
ExampleRun öffnet eine Beispieldatei aus corpus/java-examples so, wie
Filius eine Datei öffnet (ScenarioNetwork.fromFile:
SzenarioVerwaltung.laden in die Listen des Fensters), startet sie wie der
Aktionsmodus (jedes Gerät in der Reihenfolge der Datei), spielt einen Ablauf
und schreibt nach packages/fls/test/examples/<ablauf>.java.json die Frames
jeder Karte (unter ihrer MAC-Adresse), Javas Tabellen, die Zuordnung der
Karten (cards: MAC → Gerät/Index, ein wiederholter Name mit #2, #3, …),
die Aufrufe, die Adressen am Ende und jede Änderung der RIP-Tabellen. Abläufe:
routing (internetwork_komplett: Pings über drei Router, dann von einem
Notebook ohne Gateway), dhcp (einfaches_rechnernetz_komplett: vier
Clients), dns (dns_server: rekursiv über Wurzel, de., filius.de.),
nat (nat_with_mapped_port: Pings durch zwei Heimrouter), firewall
(Internet_Komplett_…_DE: acht DHCP-Clients, dann Pings durch die Firewall).
Gemessen (01.10.2026):
ipForwardingEnabledeines Rechners: Java speichert und beachtet den Schalter (meine-schule.deleitet die Pakete der iPads weiter); Leser, Modell, Kern undFlsOraclekannten ihn nicht.FlsOracleschreibt ihn jetzt für jeden Knoten mit IP-Stapel; Korpus, Schreiber und Export neu geprüft.- ARP-Lernregel (
ARPThread): Einen Eintrag lernt ein Gerät nur aus einer Anfrage nach seiner eigenen Adresse oder aus einer Antwort, eine Antwort gibt es nur auf eine Anfrage mit Ziel-MACff:ff:ff:ff:ff:ff, und ein Paket von der Netzadresse wird verworfen. Der Kern lernte aus jedem Paket. - Javas Firewall wendet ihre Regeln nur auf TCP und UDP an; ICMP lässt sie
durch, außer „ICMP verwerfen" ist an (
checkAcceptIcmp). Der Kern ebenso. - Ein Ping von einem Rechner ohne Gateway an ein fremdes Netz wirft in Java
eine
NullPointerException; der Kern meldet „Netz nicht erreichbar". - Gleich lange RIP-Wege: Hat ein Router zwei gleich lange Wege, nimmt
Java den, dessen Nachricht seine Threads zuerst verarbeiten; im
Firewall-Beispiel wechselte das zwischen Läufen (Router #3 zu 42.0.0.0
über Router
""oder über #2). Der Ablauf meidet solche Ziele. - Gleichzeitig startende DHCP-Clients bedient der Server in der Reihenfolge, in der Javas Threads senden; mit acht Clients stauen sich die Broadcasts, ein Frame erreicht die Karten bis zu rund 450 ms versetzt, und Javas Mitschnitt verliert eigene Frames eines Clients.
Echo-Server und einfacher Client (F3.1)
EchoRun installiert auf dem Netz von tcp-lan Filius' Echo-Server
(ServerBaustein) auf .10, eingeschaltet auf Port 55555, bevor die
Simulation startet (wie eine Datei ihn lädt), und den einfachen Client
(ClientBaustein) auf .20 und .30. Es macht die Aufrufe wie die Fenster:
verbinden und trennen gehen an den Thread des Programms, senden läuft
im Thread des Aufrufers, bis es zurückkehrt. Es schreibt nach
echo-java-<ablauf>.java.json die Frames jeder Karte, Javas Tabellen und
jede Zeile, die ein Programm in sein Fenster schreibt (seine Beobachter),
dazu die Aufrufe. Abläufe: exchange, closedPort, noRoute,
notConnected, serverOff, reconnect, twoClients, otherPort.
Gemessen (01.10.2026):
Anwendung.startenmeldet seinen Beobachternnull; das Fenster überspringt es.- Der Client merkt die Verbindung erst beim nächsten Blick
(
TCPSocket.verbindenschläft 100 ms), der Server ebenso eine neue Verbindung (ServerSocket.oeffnen) und dass er ausgeschaltet wurde („Server-Socket geschlossen“ rund 20 ms nach dem Ausschalten). - Ohne Route zum Ziel stirbt der Socket-Thread des Clients an der
RouteNotFoundException;verbindenwartet ewig, das Fenster bleibt stumm, kein Frame geht hinaus. - Ausgeschaltet schließt der Server seine Verbindungen mit FIN, gibt aber
zugleich den Port frei (
ServerSocket.schliessen); das FIN des Clients findet keinen Socket mehr und bleibt ohne ACK. - Aus- und sofort wieder eingeschaltet (ohne Pause) merkt Javas Schleife das
Ausschalten nicht und lauscht weiter auf dem alten Port, auch wenn er
geändert wurde. Wer im Fenster den Port wechselt, lässt Sekunden
dazwischen; so misst es
otherPort. - Der Client sendet nur ein SYN (wie bei
TcpRun,closedPort).