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
+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);
}
}