feat(core): the command line's network commands ping, host, nslookup, arp and netstat after Java, measured against Java

TerminalRun types lines into Java's command line on terminal-net at set
times; the engine types the same lines. New in the engine: an ICMP echo
in a program's thread (the first ICMP message after the send, whatever
it is), Java's route choice, the ARP table with Java's broadcast row, DNS
in a program's thread, ICMP broadcasts of a node's own network, and an
ARP wait that ends after what else happens at that millisecond.

Departures from Java: an echo is answered from the address asked
(RFC 792), network unreachable carries the packet with its sender, ping
names the host and takes only -b before it, arp and netstat list in the
order learned, netstat lists a UDP server's listening socket only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-02 08:03:10 +00:00
co-authored by Claude Opus 5.5
parent 016f9461df
commit 495c449ef2
22 changed files with 33994 additions and 42 deletions
+68
View File
@@ -793,3 +793,71 @@ Gemessen (01.10.2026):
Pfad an den Arbeitsordner, `rm /` löscht den Arbeitsordner und meldet
Erfolg; `mv` auf die Datei selbst (auch in anderer Schreibweise) löscht
sie.
## Befehlszeile, Netzbefehle (F3.4b)
`TerminalRun` tippt Zeilen in Javas `Terminal` auf den Rechnern von
`terminal-net` (LAN 192.168.0.0/24 mit Client .20, DNS-Server .10 ohne
Gateway, .30, .31, Router .1/10.0.0.1, dahinter 10.0.0.2 mit einem
DNS-Server, den es nicht gibt), zu festen Zeiten und so, wie das Fenster sie
übergibt (`terminalEingabeAuswerten`); `^C` ist Strg+C (`setInterrupt`).
Geschrieben werden je Karte die Frames samt Javas Tabellen, je Rechner was
die Befehlszeile sagt (Texte und Laufmarken) mit der Zeit, und die getippten
Zeilen mit ihren Zeiten, die der Test des Kerns ebenso tippt. Die JVM läuft
deutsch.
Gemessen (02.10.2026):
* `ping`: vier Echos, je eines pro Sekunde ab dem Senden des vorigen; die
Zeit zählt ab vor dem ARP (473 ms im LAN mit ARP, dann 223 ms; über den
Router 673 und 334 ms, TTL 63). Gewartet wird nach dem Senden eine RTT
(hier 2750 ms) auf die *erste* ICMP-Nachricht, gleich welche; alles außer
der Antwort mit der Sequenznummer ist „Timeout!“, und das nächste Echo geht
sofort. Ohne ARP-Antwort legt sich der Rechner selbst „Host Unreachable“
hin (je Echo zwei ARP-Anfragen, 5,5 s); hinter dem Router fragt dieser
seine ARP-Anfragen nacheinander ab, so beantwortet das dritte Echo die
Meldung zum ersten. Ohne Route am Router kommt „Network Unreachable“
sofort, viermal in 900 ms. Strg+C wirkt vor dem nächsten Echo, die
Statistik zählt die gesendeten; ein danach getippter bekannter Befehl
hebt es auf (`setInterrupt(false)` beim Übergeben), ein unbekannter nicht.
Einen unbekannten Befehl meldet das Fenster sofort, auch während ein
anderer läuft.
* Broadcast: ohne `-b` „nicht unterstützt“ (auch 255.255.255.255 über die
Standardroute, Maske 0.0.0.0); mit `-b` geht das Echo mit TTL 1 an
FF:FF:FF:FF:FF:FF, alle im Netz antworten (auch der Router), jeder fragt
dazu per ARP nach dem Absender. Welche Antwort zuerst kommt, ist ein
Wettlauf der Threads. Ein gerichteter Broadcast aus einem anderen Netz
(10.0.0.2 an 192.168.0.255) wird am Router verworfen.
* Eigene Adresse: Javas Route geht über 127.0.0.1, die Antwort kommt „From
127.0.0.1“; ebenso für `localhost` und 127.0.0.1, ohne Frame.
* Ferne Adresse eines Routers: die Antwort kommt von der nahen Karte
(„From 192.168.0.1“ für `ping 10.0.0.1`).
* Fehler Javas: in der Statistik und bei „Timeout!“ steht der erste Parameter
statt des Hosts (`--- -b Paketstatistik`); jedes erste Wort gilt als `-b`
(`ping -x 192.168.0.31` pingt). „Network Unreachable“ vom Router bettet ein
halb gebautes Paket ein (`sender=null`, TTL 63).
* `host`: A, dann MX, Namen mit Punkt am Ende; „nicht aufgelöst“ erst nach
beiden Anfragen (auch MX allein zählt); `localhost` ohne Anfrage. Ohne erreichbaren DNS-Server
nach fünf RTT „Zeitüberschreitung“, bei `ping` mit „ (DNS)“ und ohne
Laufmarken. Ohne eingetragenen DNS-Server sofort „nicht aufgelöst“.
* `nslookup`: Server und Adresse (127.0.0.1 für `localhost`), dann nur der
Antwortteil; ohne Treffer nur der Kopf. Ohne Parameter die Hilfe ohne
Laufmarken (`host` mit).
* `arp`: Javas Tabelle ist eine `HashMap` mit der Zeile 255.255.255.255,
die jedes Zurücksetzen (`-d`) neu anlegt; `-a` zeigt eine Zeile, `-d` mit
Adresse löscht sie und zeigt dann die Tabelle; unbekannte Optionen und
`-a`/`-d` mit etwas anderem als einer Adresse zeigen die Tabelle. `arp -d
255.255.255.255` entfernt auch die Broadcast-Zeile, bis `arp -d`.
* `netstat`: TCP, dann UDP, je Socket die lokale Adresse nach der Route zur
Gegenstelle; ein Server mit `0.0.0.0` und `-:-` in LISTEN, darunter seine
Verbindungen; eine getrennte Verbindung steht beim Client in `TIME_WAIT`,
beim Server ist sie ausgetragen. Javas UDP-Server behält für jede je beantwortete Adresse und
jeden Port einen Socket (`CONNECTED`), dessen Arbeiter für immer wartet.
Die Reihenfolge ist die einer `HashMap` bzw. `Hashtable`.
* Javas `waitForIcmpResponse` verpasst sein Aufwecken, wenn die Antwort
schon da ist, bevor es wartet (ein Wettlauf der Threads: Echo an sich
selbst, das „Host Unreachable“, das sich ein Rechner selbst hinlegt), und
wartet dann eine volle RTT. Eine Aufnahme damit (ein Ping an sich selbst
über 1 s, mehr als 6 s zwischen zwei Timeouts ins Leere) wird wiederholt;
ebenso eine unter Last (andere Sitzungen auf dem Rechner: die erste
ARP-Runde dauert dann knapp 300 statt 225 ms).
+13 -1
View File
@@ -21,7 +21,7 @@ if [ ! -f "$upstream/target/filius.jar" ]; then
fi
classpath="$upstream/target/filius.jar:$upstream/target/lib/*"
mkdir -p "$here/.build"
javac -encoding UTF-8 -d "$here/.build" -cp "$classpath" "$here/src/FlsOracle.java" "$here/src/GetterDefaults.java" "$here/src/IpCheckCases.java" "$here/src/DnsHostsCases.java" "$here/src/EmailFileCases.java" "$here/src/BeansDump.java" "$here/src/Base64Cases.java" "$here/src/ScenarioNetwork.java" "$here/src/DhcpRun.java" "$here/src/TcpRun.java" "$here/src/DnsRun.java" "$here/src/ArpRun.java" "$here/src/RipRun.java" "$here/src/NatRun.java" "$here/src/WlanRun.java" "$here/src/ExampleRun.java" "$here/src/EchoRun.java" "$here/src/WebRun.java" "$here/src/RouterWebRun.java" "$here/src/KonfigCases.java" "$here/src/EmailRun.java" "$here/src/MailCases.java" "$here/src/EmailClientRun.java" "$here/src/TerminalCases.java"
javac -encoding UTF-8 -d "$here/.build" -cp "$classpath" "$here/src/FlsOracle.java" "$here/src/GetterDefaults.java" "$here/src/IpCheckCases.java" "$here/src/DnsHostsCases.java" "$here/src/EmailFileCases.java" "$here/src/BeansDump.java" "$here/src/Base64Cases.java" "$here/src/ScenarioNetwork.java" "$here/src/DhcpRun.java" "$here/src/TcpRun.java" "$here/src/DnsRun.java" "$here/src/ArpRun.java" "$here/src/RipRun.java" "$here/src/NatRun.java" "$here/src/WlanRun.java" "$here/src/ExampleRun.java" "$here/src/EchoRun.java" "$here/src/WebRun.java" "$here/src/RouterWebRun.java" "$here/src/KonfigCases.java" "$here/src/EmailRun.java" "$here/src/MailCases.java" "$here/src/EmailClientRun.java" "$here/src/TerminalCases.java" "$here/src/TerminalRun.java"
# FILIUS writes its settings below $HOME; keep them out of the real one.
mkdir -p "$here/.build/home"
# Removed first: a crashed run must not leave the previous result standing.
@@ -286,6 +286,18 @@ for language in de en; do
test -s "$output"
done
# The command line's network commands on terminal-net, typed at set times
# (TerminalRun; packages/core/test/terminalJava.test.ts), in the German locale.
for run in pingLan:8000 pingRouted:13000 pingNobody:49000 pingUnreachable:45000 pingBroadcast:27000 pingSelf:17000 \
pingErrors:14000 interrupt:16000 dns:15000 dnsTimeout:80000 arp:11000 netstat:10000; do
story="${run%%:*}"
output="$root/packages/core/test/scenarios/terminal-java-$story.java.json"
rm -f "$output"
HOME="$here/.build/home" xvfb-run -a java $german -cp "$classpath:$here/.build" TerminalRun \
"$root/packages/core/test/scenarios/terminal-net.scenario.json" "$story" "${run##*:}" "$output" 2>&1 | grep -v 'logback\|^$\|^\s*at ' || true
test -s "$output"
done
# FILIUS' own examples as Java FILIUS runs them, opened and started as FILIUS
# does (ExampleRun): the acceptance of F2 (packages/fls/test/examplesJava.test.ts).
for run in internetwork_komplett:routing:12000 einfaches_rechnernetz_komplett:dhcp:20000 dns_server:dns:12000 \
+254
View File
@@ -0,0 +1,254 @@
// Runs a story of FILIUS' command line (Terminal) on terminal-net in Java
// FILIUS and records what really happens: every frame each card saw, Java's
// tables of them, and everything each host's command line told its window
// (each text and each Boolean "busy" mark), with the time.
//
// TerminalRun <scenario.json> <story> <milliseconds> <out.json>
//
// terminal-net: the client 192.168.0.20 (gateway .1, DNS server .10), the
// DNS server .10 (no gateway, no DNS server), .30, .31 (the echo server on
// 55555 in netstat, as on the DNS server), the router .1 / 10.0.0.1, and 10.0.0.2 behind it
// (DNS server 192.168.0.53, which is not there). The DNS server knows
// www.filius.de, filius.de (A and MX), mail.filius.de, multi.filius.de (two
// A), fern.filius.de (10.0.0.2), weg.filius.de (.99, no one) and post.filius.de
// (MX only). A line is typed as the window hands it over
// (Terminal.terminalEingabeAuswerten, the
// first word the command, the others its parameters, then three empty ones):
// the program's thread runs it, one after the other; "^C" is the window's
// Ctrl+C (setInterrupt). Each story's lines with their times are in the
// output (acts), for the core's test to type them at the same times.
// pingLan ping .31, arp typed while it runs
// pingRouted ping fern.filius.de (behind the router), arp on both ends, ping the router's far address
// pingNobody ping .99, which no one has, then by name (weg.filius.de)
// pingUnreachable ping 10.0.0.99 (no one behind the router), 172.16.0.1 (no route at the router)
// pingBroadcast ping .255, ping -b .255, arp, ping (-b) 255.255.255.255, also without a route;
// ping -b .255 from behind the router
// pingSelf ping its own address, localhost, 127.0.0.1
// pingErrors usage, unknown names, no route, no DNS server, a first word other than -b
// interrupt ping .31, Ctrl+C after 1.8 s; again, then arp typed at once; again, then an unknown command
// dns host and nslookup on known and unknown names, localhost, usage, a name with MX only
// dnsTimeout host, nslookup and ping by name where the DNS server is not there
// arp arp after a ping and a lookup, -a, -d, unknown options, no address, the broadcast row removed
// netstat netstat on the client connected to the echo server, on the echo server, on the DNS server;
// the connection closed, on both again
import filius.software.clientserver.ClientBaustein;
import filius.software.clientserver.ServerBaustein;
import filius.software.lokal.Terminal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
public class TerminalRun {
static final String CLIENT = "00000000-0000-0000-0000-000000000401";
static final String DNS = "00000000-0000-0000-0000-000000000402";
static final String WEB = "00000000-0000-0000-0000-000000000403";
static final String ECHO = "00000000-0000-0000-0000-000000000404";
static final String FAR = "00000000-0000-0000-0000-000000000406";
static final String TERMINAL_CLASS = "filius.software.lokal.Terminal";
static ScenarioNetwork network;
static final List<Object[]> events = new ArrayList<>();
static synchronized void event(String host, String kind, String text) {
events.add(new Object[] { System.currentTimeMillis() - network.start, host, kind, text });
}
static void sleepUntil(long milliseconds) throws InterruptedException {
long rest = network.start + milliseconds - System.currentTimeMillis();
if (rest > 0) Thread.sleep(rest);
}
/** The story's lines: time, host, line. */
static List<Object[]> acts(String story) {
List<Object[]> acts = new ArrayList<>();
switch (story) {
case "pingLan":
acts.add(new Object[] { 500L, CLIENT, "ping 192.168.0.31" });
acts.add(new Object[] { 1500L, CLIENT, "arp" });
break;
case "pingRouted":
acts.add(new Object[] { 500L, CLIENT, "ping fern.filius.de" });
acts.add(new Object[] { 6500L, CLIENT, "arp" });
acts.add(new Object[] { 6700L, FAR, "arp" });
acts.add(new Object[] { 7000L, CLIENT, "ping 10.0.0.1" });
break;
case "pingNobody":
acts.add(new Object[] { 500L, CLIENT, "ping 192.168.0.99" });
acts.add(new Object[] { 23500L, CLIENT, "ping weg.filius.de" });
break;
case "pingUnreachable":
acts.add(new Object[] { 500L, CLIENT, "ping 10.0.0.99" });
acts.add(new Object[] { 40000L, CLIENT, "ping 172.16.0.1" });
break;
case "pingBroadcast":
acts.add(new Object[] { 500L, CLIENT, "ping 192.168.0.255" });
acts.add(new Object[] { 1000L, CLIENT, "ping -b 192.168.0.255" });
acts.add(new Object[] { 7000L, CLIENT, "arp" });
acts.add(new Object[] { 7200L, CLIENT, "ping 255.255.255.255" });
acts.add(new Object[] { 7400L, CLIENT, "ping -b 255.255.255.255" });
acts.add(new Object[] { 12500L, DNS, "ping 255.255.255.255" });
acts.add(new Object[] { 13000L, FAR, "ping -b 192.168.0.255" });
break;
case "pingSelf":
acts.add(new Object[] { 500L, CLIENT, "ping 192.168.0.20" });
acts.add(new Object[] { 6000L, CLIENT, "ping localhost" });
acts.add(new Object[] { 11500L, CLIENT, "ping 127.0.0.1" });
break;
case "pingErrors":
acts.add(new Object[] { 500L, CLIENT, "ping" });
acts.add(new Object[] { 700L, CLIENT, "ping a b c" });
acts.add(new Object[] { 900L, CLIENT, "ping gibtsnicht.filius.de" });
acts.add(new Object[] { 2500L, CLIENT, "ping 1.2.3" });
acts.add(new Object[] { 4000L, DNS, "ping 10.0.0.2" });
acts.add(new Object[] { 4200L, DNS, "ping www.filius.de" });
acts.add(new Object[] { 8000L, CLIENT, "ping -x 192.168.0.31" });
break;
case "interrupt":
acts.add(new Object[] { 500L, CLIENT, "ping 192.168.0.31" });
acts.add(new Object[] { 2300L, CLIENT, "^C" });
acts.add(new Object[] { 5000L, CLIENT, "ping 192.168.0.31" });
acts.add(new Object[] { 5800L, CLIENT, "^C" });
acts.add(new Object[] { 5900L, CLIENT, "arp" });
acts.add(new Object[] { 10000L, CLIENT, "ping 192.168.0.31" });
acts.add(new Object[] { 10800L, CLIENT, "^C" });
acts.add(new Object[] { 10900L, CLIENT, "gibtsnicht" });
break;
case "dns":
acts.add(new Object[] { 500L, CLIENT, "host www.filius.de" });
acts.add(new Object[] { 2000L, CLIENT, "host filius.de" });
acts.add(new Object[] { 3500L, CLIENT, "host multi.filius.de" });
acts.add(new Object[] { 5000L, CLIENT, "host gibtsnicht.filius.de" });
acts.add(new Object[] { 6500L, CLIENT, "nslookup www.filius.de" });
acts.add(new Object[] { 8000L, CLIENT, "nslookup multi.filius.de" });
acts.add(new Object[] { 9500L, CLIENT, "nslookup localhost" });
acts.add(new Object[] { 9700L, CLIENT, "nslookup gibtsnicht.filius.de" });
acts.add(new Object[] { 11500L, CLIENT, "host" });
acts.add(new Object[] { 11600L, CLIENT, "nslookup" });
acts.add(new Object[] { 11700L, CLIENT, "host localhost" });
acts.add(new Object[] { 11900L, CLIENT, "nslookup filius.de" });
acts.add(new Object[] { 12100L, CLIENT, "host post.filius.de" });
break;
case "dnsTimeout":
acts.add(new Object[] { 500L, FAR, "host www.filius.de" });
acts.add(new Object[] { 30000L, FAR, "nslookup www.filius.de" });
acts.add(new Object[] { 60000L, FAR, "ping www.filius.de" });
break;
case "arp":
acts.add(new Object[] { 500L, CLIENT, "ping 192.168.0.31" });
acts.add(new Object[] { 5500L, CLIENT, "host www.filius.de" });
acts.add(new Object[] { 7000L, CLIENT, "arp" });
acts.add(new Object[] { 7200L, CLIENT, "arp -a 192.168.0.31" });
acts.add(new Object[] { 7400L, CLIENT, "arp -a 192.168.0.99" });
acts.add(new Object[] { 7600L, CLIENT, "arp -d 192.168.0.31" });
acts.add(new Object[] { 7800L, CLIENT, "arp" });
acts.add(new Object[] { 8000L, CLIENT, "arp -x 1.2.3.4" });
acts.add(new Object[] { 8200L, CLIENT, "arp foo" });
acts.add(new Object[] { 8250L, CLIENT, "arp -a foo" });
acts.add(new Object[] { 8300L, CLIENT, "arp -d foo" });
acts.add(new Object[] { 8400L, CLIENT, "arp -d" });
acts.add(new Object[] { 8600L, CLIENT, "arp" });
acts.add(new Object[] { 8800L, CLIENT, "arp -d 255.255.255.255" });
acts.add(new Object[] { 9000L, CLIENT, "arp" });
acts.add(new Object[] { 9200L, CLIENT, "arp -d" });
break;
case "netstat":
acts.add(new Object[] { 2500L, CLIENT, "netstat" });
acts.add(new Object[] { 2700L, ECHO, "netstat" });
acts.add(new Object[] { 2900L, CLIENT, "host www.filius.de" });
acts.add(new Object[] { 4500L, DNS, "netstat" });
acts.add(new Object[] { 4700L, WEB, "netstat" });
acts.add(new Object[] { 4900L, CLIENT, "netstat x" });
acts.add(new Object[] { 5000L, CLIENT, "disconnect" });
acts.add(new Object[] { 8000L, CLIENT, "netstat" });
acts.add(new Object[] { 8200L, ECHO, "netstat" });
break;
default:
throw new IllegalArgumentException("unknown story " + story);
}
return acts;
}
/** GUIApplicationTerminalWindow on Enter (or Ctrl+C): the line handed to the program. */
static void type(Terminal terminal, String host, String line) {
event(host, "call", line);
if (line.equals("^C")) {
terminal.setInterrupt(true);
return;
}
StringTokenizer tokens = new StringTokenizer(line, " ");
String command = tokens.nextToken();
String[] parameters = new String[3 + tokens.countTokens()];
Arrays.fill(parameters, "");
for (int index = 0; tokens.hasMoreTokens(); index++) parameters[index] = tokens.nextToken();
terminal.terminalEingabeAuswerten(command, parameters);
}
public static void main(String[] args) throws Exception {
network = ScenarioNetwork.read(args[0]);
String story = args[1];
long end = Long.parseLong(args[2]);
List<Object[]> acts = acts(story);
Map<String, Terminal> terminals = new LinkedHashMap<>();
for (String host : new String[] { CLIENT, DNS, WEB, ECHO, FAR }) {
network.system(host).installApp(TERMINAL_CLASS, false);
Terminal terminal = (Terminal) network.system(host).holeSoftware(TERMINAL_CLASS);
terminal.hinzuBeobachter((observable, data) -> {
if (data instanceof Boolean) event(host, "busy", data.toString());
else if (data != null) event(host, "text", data.toString());
});
terminals.put(host, terminal);
}
ClientBaustein client = null;
if (story.equals("netstat")) {
for (String host : new String[] { ECHO, DNS }) {
network.system(host).installApp("filius.software.clientserver.ServerBaustein", false);
ServerBaustein server = (ServerBaustein) network.system(host).holeSoftware("filius.software.clientserver.ServerBaustein");
server.setPort(55555);
server.setAktiv(true);
}
network.system(CLIENT).installApp("filius.software.clientserver.ClientBaustein", false);
client = (ClientBaustein) network.system(CLIENT).holeSoftware("filius.software.clientserver.ClientBaustein");
}
network.start();
if (client != null) {
sleepUntil(500);
event(CLIENT, "call", "connect 192.168.0.31:55555");
client.setZielIPAdresse("192.168.0.31");
client.setZielPort(55555);
client.verbinden();
}
for (Object[] act : acts) {
sleepUntil((Long) act[0]);
if (act[2].equals("disconnect")) {
// The echo client's window: its connection closed (ClientBaustein.trennen).
event(CLIENT, "call", "disconnect");
client.trennen();
continue;
}
type(terminals.get((String) act[1]), (String) act[1], (String) act[2]);
}
sleepUntil(end);
ScenarioNetwork.Json out = new ScenarioNetwork.Json();
out.open('{');
network.writeFrames(out);
out.key("acts").open('[');
for (Object[] act : acts) out.open('[').number((Long) act[0]).string((String) act[1]).string((String) act[2]).close(']');
out.close(']');
synchronized (TerminalRun.class) {
out.key("events").open('[');
for (Object[] each : events) out.open('[').number((Long) each[0]).string((String) each[1]).string((String) each[2]).string((String) each[3]).close(']');
out.close(']');
}
out.close('}');
network.stop();
ScenarioNetwork.write(args[3], out);
System.exit(0);
}
}