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>
255 lines
14 KiB
Java
255 lines
14 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
|
|
|
|
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);
|
|
}
|
|
}
|