feat(core): Java's IP and ICMP threads, measured with Java FILIUS
Every packet that comes in goes through its node's IP thread (UDP, TCP) or ICMP thread (ICMP), as Java's IPThread and ICMPThread: deliver, answer an echo request or forward, one after the other; while one waits for ARP, the next wait behind it. A packet a node sends to itself is delivered at once, as Java's IP.senden does. The transport send threads and these are one mechanism now (handToProtocolThread). ArpRun has three stories through the router (routedUdpBehind, routedPingBesideUdp, routedPingBehindPing); the oracle writes ICMP frames with type, code, addresses and sequence number. All seven ArpRun stories match Java FILIUS. Measured and not taken: Java's ARP wakes one waiter with notify, not the one whose answer came; the core wakes all that wait for the address. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -276,7 +276,12 @@ 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).
|
||||
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:
|
||||
|
||||
@@ -293,7 +298,28 @@ Gemessen (30.09.2026), was der Kern (nach Swift) anders macht:
|
||||
* Ein Ping wartet eine RTT auf die Antwort; das „Host unreachable" des
|
||||
Routers (nach dessen 5,5 s) kommt zu spät.
|
||||
|
||||
Der Kern spielt alle vier Abläufe wie Java (`packages/core/test/arpJava.test.ts`:
|
||||
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
|
||||
|
||||
@@ -144,7 +144,7 @@ done
|
||||
|
||||
# ARP as Java FILIUS runs it: sends to addresses no card has on arp-routed's
|
||||
# network (ArpRun); frames and the calls, for the core's ARP comparison.
|
||||
for story in pingLan pingRouted udpTwice udpBehind; do
|
||||
for story in pingLan pingRouted udpTwice udpBehind routedUdpBehind routedPingBesideUdp routedPingBehindPing; do
|
||||
output="$root/packages/core/test/scenarios/arp-java-$story.java.json"
|
||||
rm -f "$output"
|
||||
HOME="$here/.build/home" xvfb-run -a java -cp "$classpath:$here/.build" ArpRun \
|
||||
|
||||
@@ -10,8 +10,19 @@
|
||||
// pingRouted 10.0.0.2 pings 20.0.0.9: the router's ARP fails
|
||||
// udpTwice 10.0.0.2 sends two UDP datagrams to 10.0.0.9 at once
|
||||
// udpBehind 10.0.0.2 sends a UDP datagram to 10.0.0.9, then one to 20.0.0.2
|
||||
// routedUdpBehind 10.0.0.2 sends a UDP datagram to 20.0.0.9, then one
|
||||
// to 20.0.0.2: the router forwards both
|
||||
// routedPingBesideUdp a ping to 20.0.0.2 (the router learns it), a UDP
|
||||
// datagram to 20.0.0.9, 100 ms later (the UDP thread
|
||||
// sends first) a ping to 20.0.0.2 again: only
|
||||
// one thread waits for ARP at a time. Two would meet
|
||||
// ARP's notify, which wakes one waiter, not the one
|
||||
// whose answer came (a Java bug the core does not take)
|
||||
// routedPingBehindPing an echo request to 20.0.0.9 (sent, not waited for),
|
||||
// then a ping to 20.0.0.2
|
||||
|
||||
import filius.software.transportschicht.UDPSocket;
|
||||
import filius.software.vermittlungsschicht.ICMP;
|
||||
import filius.software.vermittlungsschicht.IcmpPaket;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -83,6 +94,32 @@ public class ArpRun {
|
||||
return "";
|
||||
});
|
||||
break;
|
||||
case "routedUdpBehind":
|
||||
call("senden verloren", () -> {
|
||||
send("20.0.0.9", "verloren");
|
||||
return "";
|
||||
});
|
||||
call("senden dahinter", () -> {
|
||||
send("20.0.0.2", "dahinter");
|
||||
return "";
|
||||
});
|
||||
break;
|
||||
case "routedPingBesideUdp":
|
||||
call("sendProbe 20.0.0.2", () -> icmp(network.system(HOST).holeICMP().sendProbe("20.0.0.2", 64, 1, 1)));
|
||||
call("senden verloren", () -> {
|
||||
send("20.0.0.9", "verloren");
|
||||
return "";
|
||||
});
|
||||
Thread.sleep(100);
|
||||
call("sendProbe 20.0.0.2", () -> icmp(network.system(HOST).holeICMP().sendProbe("20.0.0.2", 64, 1, 2)));
|
||||
break;
|
||||
case "routedPingBehindPing":
|
||||
call("sendeICMP 20.0.0.9", () -> {
|
||||
network.system(HOST).holeICMP().sendeICMP(ICMP.TYPE_ECHO_REQUEST, ICMP.CODE_ECHO, 2, 2, null, "20.0.0.9");
|
||||
return "";
|
||||
});
|
||||
call("sendProbe 20.0.0.2", () -> icmp(network.system(HOST).holeICMP().sendProbe("20.0.0.2", 64, 1, 1)));
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("unknown story " + story);
|
||||
}
|
||||
|
||||
@@ -237,7 +237,10 @@ public class ScenarioNetwork {
|
||||
out.key("arp").number(arp.getOperation()).key("senderIp").string(arp.getSenderIP()).key("targetIp")
|
||||
.string(arp.getTargetIP());
|
||||
} else if (data instanceof IcmpPaket) {
|
||||
out.key("icmp").number(((IcmpPaket) data).getIcmpType());
|
||||
IcmpPaket icmp = (IcmpPaket) data;
|
||||
out.key("icmp").number(icmp.getIcmpType()).key("icmpCode").number(icmp.getIcmpCode()).key("sourceIp")
|
||||
.string(icmp.getSender()).key("destinationIp").string(icmp.getEmpfaenger()).key("sequence")
|
||||
.number(icmp.getSeqNr());
|
||||
} else if (data instanceof IpPaket) {
|
||||
IpPaket ip = (IpPaket) data;
|
||||
Segment segment = ip.getSegment();
|
||||
|
||||
Reference in New Issue
Block a user