Files
mdg-filius/tools/java-oracle/src/TerminalRun.java
T
julian.kiedaischandClaude Opus 5.5 a867c33d64 feat(core): traceroute and tcpdump after Java, measured against Java
TerminalRun adds nine stories: traceroute in the LAN, through the router,
unreachable, without a route, to no one, to the host itself, errors and
Ctrl+C; tcpdump on two hosts. traceroute counts failures across hops as
Java does and takes an error that carries its probe; tcpdump tells each
frame's highest capture row with its time of day, at once.

Departures from Java: traceroute tells its summary before the end mark
(Java after it, where its window shows no text) and calls a host at the
20th hop reached. arpsend is not built.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:46:18 +00:00

313 lines
17 KiB
Java

// 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
// traceLan traceroute .31 (one hop)
// traceRouted traceroute fern.filius.de (two hops), the router's far address
// traceUnreachable traceroute 10.0.0.99 (no one behind the router)
// traceNoRoute traceroute 172.16.0.1 (no route at the router)
// traceNobody traceroute .99 (no one in the LAN)
// traceSelf traceroute its own address, localhost
// traceErrors usage, an unknown name, no DNS server
// traceInterrupt traceroute 10.0.0.99, Ctrl+C after 3.5 s
// tcpdump tcpdump on .31 and on the DNS server while the client pings .31, looks up a name
// and connects to .31's echo server; arp typed on .31 meanwhile; Ctrl+C on both
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;
case "traceLan":
acts.add(new Object[] { 500L, CLIENT, "traceroute 192.168.0.31" });
break;
case "traceRouted":
acts.add(new Object[] { 500L, CLIENT, "traceroute fern.filius.de" });
acts.add(new Object[] { 3000L, CLIENT, "traceroute 10.0.0.1" });
break;
case "traceUnreachable":
acts.add(new Object[] { 500L, CLIENT, "traceroute 10.0.0.99" });
break;
case "traceNoRoute":
acts.add(new Object[] { 500L, CLIENT, "traceroute 172.16.0.1" });
break;
case "traceNobody":
acts.add(new Object[] { 500L, CLIENT, "traceroute 192.168.0.99" });
break;
case "traceSelf":
acts.add(new Object[] { 500L, CLIENT, "traceroute 192.168.0.20" });
acts.add(new Object[] { 1500L, CLIENT, "traceroute localhost" });
break;
case "traceErrors":
acts.add(new Object[] { 500L, CLIENT, "traceroute" });
acts.add(new Object[] { 700L, CLIENT, "traceroute a b" });
acts.add(new Object[] { 900L, CLIENT, "traceroute gibtsnicht.filius.de" });
acts.add(new Object[] { 3000L, FAR, "traceroute www.filius.de" });
break;
case "traceInterrupt":
acts.add(new Object[] { 500L, CLIENT, "traceroute 10.0.0.99" });
acts.add(new Object[] { 4000L, CLIENT, "^C" });
break;
case "tcpdump":
acts.add(new Object[] { 500L, ECHO, "tcpdump" });
acts.add(new Object[] { 600L, DNS, "tcpdump" });
acts.add(new Object[] { 1000L, CLIENT, "ping 192.168.0.31" });
acts.add(new Object[] { 3000L, ECHO, "arp" });
acts.add(new Object[] { 5500L, CLIENT, "host www.filius.de" });
acts.add(new Object[] { 6500L, CLIENT, "connect" });
acts.add(new Object[] { 8000L, ECHO, "^C" });
acts.add(new Object[] { 8100L, DNS, "^C" });
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") || story.equals("tcpdump")) {
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 (story.equals("netstat")) {
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("connect")) {
// The echo client's window: connect to the echo server (ClientBaustein.verbinden).
event(CLIENT, "call", "connect 192.168.0.31:55555");
client.setZielIPAdresse("192.168.0.31");
client.setZielPort(55555);
client.verbinden();
continue;
}
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);
}
}