test: F2 acceptance — five of FILIUS' examples run as Java FILIUS runs them
ExampleRun opens an example as FILIUS opens it (SzenarioVerwaltung.laden into the window's lists), starts it as the action mode does, plays a story and writes every card's frames and tables, the calls, the addresses and the RIP tables' changes. examplesJava.test.ts reads the same file with this package's reader and runs it in the core (projectSnapshot, new): routing (internetwork_komplett), DHCP (einfaches_rechnernetz_komplett), DNS (dns_server), NAT (nat_with_mapped_port) and firewall (Internet_Komplett_..._DE) match card by card. Found and fixed: a host's ipForwardingEnabled, which Java saves and obeys, was unknown to the reader, the model, the core, the writer, the export and FlsOracle; and Java's ARP learns only from a request for its own address or a reply. Named Java bugs: a ping without a gateway throws a NullPointerException; a home router drops the answer of a second remote end. Open: Java picks between equally long RIP routes by thread timing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -506,3 +506,43 @@ RIP: Der Anlauf entscheidet, wie viele Zufallspausen jeder Router zieht
|
||||
(jede Nachricht zieht eine), und der hängt an Javas Startreihenfolge. Eine
|
||||
Aufnahme, deren Anlauf von dem des Kerns (alle Router zugleich) abweicht,
|
||||
liegt danach in allen Zeiten daneben; das Werkzeug wird dann wiederholt.
|
||||
|
||||
## Beispiele von Filius (Abnahme F2)
|
||||
|
||||
`ExampleRun` öffnet eine Beispieldatei aus `corpus/java-examples` so, wie
|
||||
Filius eine Datei öffnet (`ScenarioNetwork.fromFile`:
|
||||
`SzenarioVerwaltung.laden` in die Listen des Fensters), startet sie wie der
|
||||
Aktionsmodus (jedes Gerät in der Reihenfolge der Datei), spielt einen Ablauf
|
||||
und schreibt nach `packages/fls/test/examples/<ablauf>.java.json` die Frames
|
||||
jeder Karte (unter ihrer MAC-Adresse), Javas Tabellen, die Zuordnung der
|
||||
Karten (`cards`: MAC → `Gerät/Index`, ein wiederholter Name mit `#2`, `#3`, …),
|
||||
die Aufrufe, die Adressen am Ende und jede Änderung der RIP-Tabellen. Abläufe:
|
||||
`routing` (internetwork_komplett: Pings über drei Router, dann von einem
|
||||
Notebook ohne Gateway), `dhcp` (einfaches_rechnernetz_komplett: vier
|
||||
Clients), `dns` (dns_server: rekursiv über Wurzel, `de.`, `filius.de.`),
|
||||
`nat` (nat_with_mapped_port: Pings durch zwei Heimrouter), `firewall`
|
||||
(Internet_Komplett_…_DE: acht DHCP-Clients, dann Pings durch die Firewall).
|
||||
|
||||
Gemessen (01.10.2026):
|
||||
|
||||
* **`ipForwardingEnabled`** eines Rechners: Java speichert und beachtet den
|
||||
Schalter (`meine-schule.de` leitet die Pakete der iPads weiter); Leser,
|
||||
Modell, Kern und `FlsOracle` kannten ihn nicht. `FlsOracle` schreibt ihn
|
||||
jetzt für jeden Knoten mit IP-Stapel; Korpus, Schreiber und Export neu
|
||||
geprüft.
|
||||
* **ARP-Lernregel** (`ARPThread`): Einen Eintrag lernt ein Gerät nur aus einer
|
||||
Anfrage nach seiner eigenen Adresse oder aus einer Antwort, eine Antwort
|
||||
gibt es nur auf eine Anfrage mit Ziel-MAC `ff:ff:ff:ff:ff:ff`, und ein
|
||||
Paket von der Netzadresse wird verworfen. Der Kern lernte aus jedem Paket.
|
||||
* Javas Firewall wendet ihre Regeln nur auf TCP und UDP an; ICMP lässt sie
|
||||
durch, außer „ICMP verwerfen" ist an (`checkAcceptIcmp`). Der Kern ebenso.
|
||||
* Ein Ping von einem Rechner ohne Gateway an ein fremdes Netz wirft in Java
|
||||
eine `NullPointerException`; der Kern meldet „Netz nicht erreichbar".
|
||||
* **Gleich lange RIP-Wege**: Hat ein Router zwei gleich lange Wege, nimmt
|
||||
Java den, dessen Nachricht seine Threads zuerst verarbeiten; im
|
||||
Firewall-Beispiel wechselte das zwischen Läufen (Router #3 zu 42.0.0.0
|
||||
über Router `""` oder über #2). Der Ablauf meidet solche Ziele.
|
||||
* Gleichzeitig startende DHCP-Clients bedient der Server in der Reihenfolge,
|
||||
in der Javas Threads senden; mit acht Clients stauen sich die Broadcasts,
|
||||
ein Frame erreicht die Karten bis zu rund 450 ms versetzt, und Javas
|
||||
Mitschnitt verliert eigene Frames eines Clients.
|
||||
|
||||
@@ -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"
|
||||
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"
|
||||
# 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.
|
||||
@@ -193,3 +193,15 @@ for story in pingWired pingWireless pingWithoutCable pingFromWithoutCable; do
|
||||
"$root/packages/core/test/scenarios/wlan-lan.scenario.json" "$story" 12000 "$output" 2>&1 | grep -v 'logback\|^$' || 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 \
|
||||
nat_with_mapped_port:nat:15000 Internet_Komplett_mit_eMail_Webserver_Intranet_Portforwarding_Firewall_DHCP_DE:firewall:16000; do
|
||||
file="${run%%:*}"; rest="${run#*:}"; story="${rest%%:*}"
|
||||
output="$root/packages/fls/test/examples/$story.java.json"
|
||||
rm -f "$output"
|
||||
HOME="$here/.build/home" xvfb-run -a java -cp "$classpath:$here/.build" ExampleRun \
|
||||
"$root/corpus/java-examples/$file.fls" "$story" "${rest##*:}" "$output" 2>&1 | grep -v 'logback\|^$' || true
|
||||
test -s "$output"
|
||||
done
|
||||
|
||||
@@ -0,0 +1,165 @@
|
||||
// Runs one of FILIUS' own example files (corpus/java-examples) in Java FILIUS,
|
||||
// loaded and started as FILIUS opens and starts it (ScenarioNetwork.fromFile),
|
||||
// plays a story on it and records every frame each card saw, the capture's
|
||||
// tables, the story's calls and each host's addresses at the end.
|
||||
//
|
||||
// ExampleRun <file.fls> <story> <milliseconds> <out.json>
|
||||
//
|
||||
// The stories:
|
||||
// routing internetwork_komplett: Notebook 1.10 pings Webserver 5.10 across
|
||||
// three routers; then Notebook 1.11, which has no gateway
|
||||
// dhcp einfaches_rechnernetz_komplett: four notebooks get addresses
|
||||
// from the server
|
||||
// dns dns_server: the notebook asks its DNS server (recursive) for
|
||||
// www.filius.de, then for the mail server of filius.de
|
||||
// nat nat_with_mapped_port: Notebook 1.10 (behind Heimrouter Paul)
|
||||
// pings Webserver www.jupiter.de, then Heimrouter Vincent's WAN
|
||||
// address
|
||||
// firewall Internet_Komplett_…_DE: when the DHCP clients have their
|
||||
// addresses (8 s), iPad 1 (its network dropped by the firewall,
|
||||
// which leaves ICMP to its own switch) pings mail.gmx.de, then
|
||||
// the first Notebook (accepted), then mail.gmx.de pings the
|
||||
// firewall's WAN address. Targets with one shortest RIP path
|
||||
// each: where two are equal, Java takes whichever its threads
|
||||
// learn first
|
||||
|
||||
import filius.hardware.knoten.Knoten;
|
||||
import filius.software.dns.Resolver;
|
||||
import filius.software.rip.RIPRoute;
|
||||
import filius.software.rip.RIPTable;
|
||||
import filius.software.system.VermittlungsrechnerBetriebssystem;
|
||||
import filius.software.system.InternetKnotenBetriebssystem;
|
||||
import filius.software.vermittlungsschicht.IcmpPaket;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class ExampleRun {
|
||||
static ScenarioNetwork network;
|
||||
static final List<Object[]> events = new ArrayList<>();
|
||||
/** Every change of a RIP router's table: time, router, table. */
|
||||
static final List<Object[]> ripChanges = new ArrayList<>();
|
||||
static final Map<String, String> lastTables = new java.util.HashMap<>();
|
||||
|
||||
/** A RIP router's table as one line: `network/mask gateway interface hops; …`, in its order. */
|
||||
static String tableText(RIPTable table) {
|
||||
StringBuilder text = new StringBuilder();
|
||||
synchronized (table) {
|
||||
for (RIPRoute route : table.routes) {
|
||||
if (text.length() > 0) text.append("; ");
|
||||
text.append(route.getNetAddress()).append('/').append(route.getNetMask()).append(' ').append(route.getGateway())
|
||||
.append(' ').append(route.getInterfaceIpAddress()).append(' ').append(route.hops);
|
||||
}
|
||||
}
|
||||
return text.toString();
|
||||
}
|
||||
|
||||
static synchronized void pollTables() {
|
||||
for (Map.Entry<String, Knoten> entry : network.nodes.entrySet()) {
|
||||
if (!(entry.getValue().getSystemSoftware() instanceof VermittlungsrechnerBetriebssystem)) continue;
|
||||
RIPTable table = ((VermittlungsrechnerBetriebssystem) entry.getValue().getSystemSoftware()).getRIPTable();
|
||||
if (table == null) continue;
|
||||
String text = tableText(table);
|
||||
if (!text.equals(lastTables.get(entry.getKey()))) {
|
||||
lastTables.put(entry.getKey(), text);
|
||||
ripChanges.add(new Object[] { System.currentTimeMillis() - network.start, entry.getKey(), text });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static synchronized void event(String what) {
|
||||
events.add(new Object[] { System.currentTimeMillis() - network.start, what });
|
||||
}
|
||||
|
||||
interface Call {
|
||||
String run() throws Exception;
|
||||
}
|
||||
|
||||
static void call(String label, Call call) {
|
||||
event(label + " called");
|
||||
try {
|
||||
event(label + " returned " + call.run());
|
||||
} catch (Exception e) {
|
||||
event(label + " failed " + e.getClass().getSimpleName());
|
||||
}
|
||||
}
|
||||
|
||||
static String icmp(IcmpPaket packet) {
|
||||
return packet == null ? "null" : "ICMP " + packet.getIcmpType() + "/" + packet.getIcmpCode() + " from " + packet.getSender();
|
||||
}
|
||||
|
||||
static void ping(String node, String target, int sequence) {
|
||||
call("sendProbe " + target, () -> icmp(network.system(node).holeICMP().sendProbe(target, 64, 1, sequence)));
|
||||
}
|
||||
|
||||
static Resolver resolver(String node) {
|
||||
return network.system(node).holeDNSClient();
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
network = ScenarioNetwork.fromFile(args[0]);
|
||||
String story = args[1];
|
||||
long end = Long.parseLong(args[2]);
|
||||
network.start();
|
||||
Thread poller = new Thread(() -> {
|
||||
while (true) {
|
||||
pollTables();
|
||||
try {
|
||||
Thread.sleep(2);
|
||||
} catch (InterruptedException e) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
poller.setDaemon(true);
|
||||
poller.start();
|
||||
Thread.sleep(500);
|
||||
switch (story) {
|
||||
case "routing":
|
||||
ping("Notebook 1.10", "141.99.5.10", 1);
|
||||
ping("Notebook 1.11", "141.99.5.10", 2);
|
||||
break;
|
||||
case "dhcp":
|
||||
break;
|
||||
case "dns":
|
||||
call("holeIPAdresse www.filius.de", () -> resolver("Notebook").holeIPAdresse("www.filius.de"));
|
||||
call("holeIPAdresseMailServer filius.de", () -> resolver("Notebook").holeIPAdresseMailServer("filius.de"));
|
||||
break;
|
||||
case "nat":
|
||||
ping("Notebook 1.10", "141.99.4.10", 1);
|
||||
ping("Notebook 1.10", "141.99.5.10", 2);
|
||||
break;
|
||||
case "firewall":
|
||||
Thread.sleep(7500);
|
||||
ping("iPad 1", "130.100.200.25", 1);
|
||||
ping("Notebook", "130.100.200.25", 2);
|
||||
ping("mail.gmx.de (130.100.200.25)", "42.0.0.10", 3);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("unknown story " + story);
|
||||
}
|
||||
long rest = network.start + end - System.currentTimeMillis();
|
||||
if (rest > 0) Thread.sleep(rest);
|
||||
|
||||
ScenarioNetwork.Json out = new ScenarioNetwork.Json();
|
||||
out.open('{');
|
||||
network.writeFrames(out);
|
||||
network.writeDevices(out);
|
||||
// Every change of each RIP router's table (polled every 2 ms), as RipRun writes them.
|
||||
synchronized (ExampleRun.class) {
|
||||
out.key("ripTables").open('[');
|
||||
for (Object[] each : ripChanges) out.open('[').number((Long) each[0]).string((String) each[1]).string((String) each[2]).close(']');
|
||||
out.close(']');
|
||||
}
|
||||
synchronized (ExampleRun.class) {
|
||||
out.key("events").open('[');
|
||||
for (Object[] each : events) out.open('[').number((Long) each[0]).string((String) each[1]).close(']');
|
||||
out.close(']');
|
||||
}
|
||||
out.close('}');
|
||||
network.stop();
|
||||
ScenarioNetwork.write(args[3], out);
|
||||
System.exit(0);
|
||||
}
|
||||
}
|
||||
@@ -292,6 +292,7 @@ public class FlsOracle {
|
||||
json.put("fileSystem", system.getDateisystem() == null ? null : fileTree(system.getDateisystem().getRoot()));
|
||||
if (system instanceof Betriebssystem) json.put("applications", applications(system));
|
||||
json.put("dhcpClient", system.isDHCPKonfiguration());
|
||||
json.put("ipForwardingEnabled", system.isIpForwardingEnabled());
|
||||
DHCPServer server = system.getDHCPServer();
|
||||
if (server != null) {
|
||||
Map<String, Object> dhcp = new LinkedHashMap<>();
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
// DhcpRun and TcpRun.
|
||||
|
||||
import filius.gui.GUIContainer;
|
||||
import filius.gui.netzwerksicht.GUIKnotenItem;
|
||||
import filius.rahmenprogramm.SzenarioVerwaltung;
|
||||
import filius.hardware.Kabel;
|
||||
import filius.hardware.NetzwerkInterface;
|
||||
import filius.hardware.Port;
|
||||
@@ -54,8 +56,40 @@ public class ScenarioNetwork {
|
||||
/** Each card's MAC address, under its node's id (the first card) or `id#index` (the others). */
|
||||
final Map<String, String> macByNode = new LinkedHashMap<>();
|
||||
final List<String> servers = new ArrayList<>();
|
||||
/** Loaded from a .fls file (fromFile): started as the action mode starts it, in the file's order. */
|
||||
boolean fromFile;
|
||||
/** fromFile: each card's MAC address and its node's name with the card's index, `name/index`. */
|
||||
final Map<String, String> cardByMac = new LinkedHashMap<>();
|
||||
long start;
|
||||
|
||||
/**
|
||||
* A .fls file loaded as FILIUS opens it (SzenarioVerwaltung.laden into the
|
||||
* window's lists): the nodes under their names (a repeated name gets
|
||||
* `#2`, `#3`, …), each card's frames under its MAC address.
|
||||
*/
|
||||
static ScenarioNetwork fromFile(String path) throws Exception {
|
||||
GUIContainer gui = GUIContainer.getGUIContainer();
|
||||
if (!SzenarioVerwaltung.getInstance().laden(path, gui.getKnotenItems(), gui.getCableItems(), gui.getDocuItems())) {
|
||||
throw new IllegalStateException("FILIUS could not load " + path);
|
||||
}
|
||||
ScenarioNetwork network = new ScenarioNetwork();
|
||||
network.fromFile = true;
|
||||
for (GUIKnotenItem item : gui.getKnotenItems()) {
|
||||
Knoten knoten = item.getKnoten();
|
||||
String name = knoten.getName();
|
||||
for (int count = 2; network.nodes.containsKey(name); count++) name = knoten.getName() + "#" + count;
|
||||
network.nodes.put(name, knoten);
|
||||
if (knoten instanceof InternetKnoten) {
|
||||
List<NetzwerkInterface> nics = ((InternetKnoten) knoten).getNetzwerkInterfaces();
|
||||
for (int index = 0; index < nics.size(); index++) {
|
||||
network.macByNode.put(nics.get(index).getMac(), nics.get(index).getMac());
|
||||
network.cardByMac.put(nics.get(index).getMac(), name + "/" + index);
|
||||
}
|
||||
}
|
||||
}
|
||||
return network;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
static ScenarioNetwork read(String path) throws Exception {
|
||||
Map<String, Object> scenario = (Map<String, Object>) new JsonReader(
|
||||
@@ -204,6 +238,12 @@ public class ScenarioNetwork {
|
||||
|
||||
/** Starts the simulation; times in the log count from here. */
|
||||
void start() throws InterruptedException {
|
||||
if (fromFile) {
|
||||
// GUIMainMenu.selectMode(MODUS_AKTION): every node in the window's order.
|
||||
start = System.currentTimeMillis();
|
||||
for (GUIKnotenItem item : GUIContainer.getGUIContainer().getKnotenItems()) item.getKnoten().getSystemSoftware().starten();
|
||||
return;
|
||||
}
|
||||
// Switches and DHCP servers first: FILIUS' client waits for every active
|
||||
// server to have started (waitUntilDhcpServerStarted, through the GUI's
|
||||
// node list, which this tool has not got).
|
||||
@@ -244,6 +284,11 @@ public class ScenarioNetwork {
|
||||
out.close('}');
|
||||
writeRows(out, "rows", true);
|
||||
writeRows(out, "layeredRows", false);
|
||||
if (fromFile) {
|
||||
out.key("cards").open('{');
|
||||
for (Map.Entry<String, String> entry : cardByMac.entrySet()) out.key(entry.getKey()).string(entry.getValue());
|
||||
out.close('}');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user