feat(core): the firewall settings page of routers after Java, measured against Java

The page konfig changes the firewall at run time (into the snapshot, back
into the project through projectWithSimulationChanges). KonfigCases feeds
Java's plug-in directly (25 cases, de and en); RouterWebRun plays two
stories over the network. Ten Java bugs named, not adopted. The web server
answers a method not written GET or POST with 501, as Java does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-01 18:13:31 +00:00
co-authored by Claude Opus 5.5
parent 7dac57cbbe
commit 8438b42018
23 changed files with 13229 additions and 24 deletions
+38
View File
@@ -646,3 +646,41 @@ Gemessen (01.10.2026):
* Java zählt TCP-Sequenznummern und Segmente in Zeichen seiner Strings: Ein
„ä“ auf der Routen-Seite ist eine Stelle, nicht zwei Bytes.
## Firewall-Einstellungen der Router (F3.2c)
`KonfigCases` füttert Javas Seite `konfig` (`FirewallWebKonfig`) direkt mit
den Schritten aus `packages/core/test/firewall-settings.cases.txt` (eine
Anfrage ohne Daten oder die Daten eines Formulars) und schreibt je Schritt
die Seite und die Firewall danach, auf Deutsch und auf Englisch
(`firewall-settings.{de,en}.java.json`; das Gebietsschema wird vor dem
ersten Lesen des Nachrichtenbündels gesetzt). Jeder Fall beginnt mit einem
neuen Router, ohne Regeln, mit drei oder mit der einen Regel, die ein Fall
anlegt (die Seite, die Java zeigen würde, hätte es sie angenommen).
`RouterWebRun` spielt dazu auf `router-web` `konfigIcmp` und `konfigBlock`
über das Netz.
Gemessen (01.10.2026):
* Javas Browserfenster (Swing, `FormSubmitEvent.getData`) schickt jedes Feld
des Formulars in seiner Reihenfolge, UTF-8-kodiert, angehakte Kästchen und
die gewählte Option, dazu nur den gedrückten Knopf.
* Java prüft die Felder beim Lesen: die Quell-IP gegen die Maske, die noch
nicht gelesen ist (also leer), die Maske gegen die IP, ebenso. Eine Regel
mit Quell- oder Zieladresse nimmt die Seite deshalb nie an, in keiner
Reihenfolge der Felder.
* Ein Fehler bleibt im Plug-in stehen und erscheint auf jeder späteren Seite,
auch ohne Daten. Mit zwei Knöpfen zugleich (Speichern und Hinzufügen)
speichert Java und schreibt die Fehler als nackte Liste.
* Java dekodiert die Daten, bevor es sie an `&` und `=` zerlegt: Ein
getipptes `&` zerreißt das Formular. Eine kaputte Kodierung (`%zz`, `%` am
Ende) wirft im Dekodierer, Regel 0 nach unten wirft
(`ArrayIndexOutOfBoundsException`), ein `$` im wieder gefüllten Formular
lässt `replaceAll` scheitern (die Seite ist `null`, also 500).
* Die Maske prüft `isValidSubnetmask`: vier Zahlen (eine leere am Ende fällt
bei Javas `split` weg), `parseInt` mit Vorzeichen, die Bits Einsen, dann
Nullen, mindestens eine Eins (0.0.0.0 ist keine Maske).
* Über das Netz: „ICMP filtern“ aus, und der nächste Ping kommt durch; eine
Regel „TCP 80 verwerfen“ über die erlaubende geschoben, und das SYN des
Browsers bleibt an der Firewall hängen. Javas Router-Server lauschte auch
hier in einem Lauf nie (`run.sh` wiederholt).
+12 -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"
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"
# 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.
@@ -224,6 +224,7 @@ done
# sometimes never listens (a race in setAktiv): a run without a page is
# repeated.
for run in router-web:routerIndex:6000 router-web:routerRoutes:6000 router-web:routerLog:12000 router-web:routerKonfig:6000 \
router-web:konfigIcmp:12000 router-web:konfigBlock:30000 \
nat-home:gatewayIndex:9000 nat-home:gatewayFromWan:15000 rip-chain:ripRoutes:46000; do
network="${run%%:*}"; rest="${run#*:}"; story="${rest%%:*}"
output="$root/packages/core/test/scenarios/routerweb-java-$story.java.json"
@@ -236,6 +237,16 @@ for run in router-web:routerIndex:6000 router-web:routerRoutes:6000 router-web:r
test -s "$output"
done
# The firewall settings page (konfig) fed with what a browser posts, in both
# languages (KonfigCases; packages/core/test/firewallSettings.test.ts).
for language in de en; do
output="$root/packages/core/test/firewall-settings.$language.java.json"
rm -f "$output"
HOME="$here/.build/home" xvfb-run -a java -cp "$classpath:$here/.build" KonfigCases "$language" \
"$root/packages/core/test/firewall-settings.cases.txt" "$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 \
+105
View File
@@ -0,0 +1,105 @@
// Feeds FILIUS' firewall settings page (FirewallWebKonfig, the router's web
// server's plug-in konfig) with what a browser posts, step after step, and
// writes each page and the firewall after it: the reference for the core's
// konfig page (packages/core/src/runtime/apps/firewallSettings.ts). Each case
// starts from a new router; its firewall holds no rules ("empty"), three
// ("three"), one only a file can bring ("portZero": port 0, ICMP), or the
// one rule a case adds ("sourceRule", "destinationRule":
// the page Java would answer, had it taken the rule). The cases file is
// ASCII, with Java-style unicode escapes (backslash, u, four hex digits) for
// anything else:
//
// case <name> <start>
// GET the page without data (Java: holeHtmlSeite(null))
// POST <data> the page with the data a form posts ("POST" alone: empty)
//
// Usage: java -cp filius.jar:lib/*:classes KonfigCases <de|en> <cases txt> <output json>
import filius.hardware.knoten.Vermittlungsrechner;
import filius.rahmenprogramm.Information;
import filius.software.firewall.Firewall;
import filius.software.firewall.FirewallRule;
import filius.software.system.VermittlungsrechnerBetriebssystem;
import filius.software.www.WebServerPlugIn;
import java.io.FileOutputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
public class KonfigCases {
public static void main(String[] args) throws Exception {
// Before any class reads the message bundle (I18n.messages is read once).
Information.getInformation().setLocale(args[0].equals("en") ? new Locale("en", "GB") : Locale.GERMANY);
List<Object> cases = new ArrayList<>();
Map<String, Object> current = null;
List<Object> steps = null;
WebServerPlugIn page = null;
Firewall firewall = null;
for (String line : Files.readAllLines(Path.of(args[1]), StandardCharsets.US_ASCII)) {
if (line.isEmpty() || line.startsWith("#")) continue;
if (line.startsWith("case ")) {
String[] words = line.split(" ");
current = new LinkedHashMap<>();
steps = new ArrayList<>();
current.put("name", words[1]);
current.put("start", words[2]);
current.put("steps", steps);
cases.add(current);
VermittlungsrechnerBetriebssystem os = (VermittlungsrechnerBetriebssystem) new Vermittlungsrechner().getSystemSoftware();
firewall = os.holeFirewall();
page = os.holeWebServer().holePlugin("konfig");
if (words[2].equals("three")) {
firewall.setActivated(true);
firewall.addRule(new FirewallRule("", "", "", "", 80, FirewallRule.TCP, FirewallRule.ACCEPT));
firewall.addRule(new FirewallRule("999.999.999.999", "", "20.0.0.0", "255.0.0.0", FirewallRule.ALL_PORTS, FirewallRule.UDP, FirewallRule.DROP));
firewall.addRule(new FirewallRule("10.0.0.0", "255.0.0.0", "", "", FirewallRule.ALL_PORTS, FirewallRule.ALL_PROTOCOLS, FirewallRule.DROP));
} else if (words[2].equals("portZero")) {
firewall.addRule(new FirewallRule("", "", "", "", 0, FirewallRule.ICMP, FirewallRule.ACCEPT));
} else if (words[2].equals("sourceRule")) {
firewall.addRule(new FirewallRule("10.0.0.0", "255.255.255.0", "", "", 80, FirewallRule.TCP, FirewallRule.DROP));
} else if (words[2].equals("destinationRule")) {
firewall.addRule(new FirewallRule("", "", "20.0.0.0", "255.0.0.0", FirewallRule.ALL_PORTS, FirewallRule.UDP, FirewallRule.ACCEPT));
}
continue;
}
String post = line.equals("GET") ? null : line.equals("POST") ? "" : IpCheckCases.unescape(line.substring("POST ".length()));
Map<String, Object> step = new LinkedHashMap<>();
step.put("post", post);
try {
step.put("page", page.holeHtmlSeite(post));
} catch (RuntimeException error) {
step.put("page", "!" + error.getClass().getSimpleName());
}
step.put("firewall", state(firewall));
steps.add(step);
}
try (FileOutputStream out = new FileOutputStream(args[2])) {
out.write((FlsOracle.Json.write(cases, "") + "\n").getBytes(StandardCharsets.UTF_8));
}
System.out.println("konfig cases (" + args[0] + "): " + cases.size());
System.exit(0);
}
static Map<String, Object> state(Firewall firewall) {
Map<String, Object> state = new LinkedHashMap<>();
state.put("isActive", firewall.isActivated());
state.put("dropIcmp", firewall.getDropICMP());
state.put("filterSynSegmentsOnly", firewall.getFilterSYNSegmentsOnly());
state.put("defaultPolicy", firewall.getDefaultPolicy() == FirewallRule.ACCEPT ? "accept" : "drop");
List<Object> rules = new ArrayList<>();
for (FirewallRule rule : firewall.getRuleset()) {
String protocol = rule.protocol == FirewallRule.TCP ? "tcp" : rule.protocol == FirewallRule.UDP ? "udp" : rule.protocol == FirewallRule.ICMP ? "icmp" : rule.protocol == FirewallRule.ALL_PROTOCOLS ? "all" : String.valueOf(rule.protocol);
rules.add(Arrays.asList(rule.srcIP, rule.srcMask, rule.destIP, rule.destMask, rule.port, protocol, rule.action == FirewallRule.ACCEPT ? "accept" : "drop"));
}
state.put("rules", rules);
return state;
}
}
+36 -6
View File
@@ -15,6 +15,11 @@
// routerLog a ping to 20.0.0.2 (dropped), the index, then
// 10.0.0.1/log
// routerKonfig 10.0.0.1/konfig
// konfigIcmp a ping to 20.0.0.2 (dropped), the settings saved
// without "drop ICMP" (the form as Java's browser window
// posts it), the ping again
// konfigBlock a rule added (TCP 80: drop), moved up above the rule
// that accepts the browser, then the index again
// nat-home (home router 42.0.0.10/192.168.0.1):
// gatewayIndex from the LAN PC 192.168.0.10: 192.168.0.1, then
// 192.168.0.1/routes (no such page on a home router)
@@ -61,6 +66,22 @@ public class RouterWebRun {
browser.holeWebseite(new URL("http", parts[0], path.isEmpty() ? "/" : path));
}
/** What the browser window posts when a button of the settings form is pressed (Swing: every field, the button). */
static void post(String typed, String data) throws Exception {
event("browser", "call holeWebseite " + typed + " " + data);
String[] parts = typed.split("/", 2);
browser.holeWebseite(new URL("http", parts[0], "/" + parts[1]), data);
}
static void ping(InternetKnotenBetriebssystem host) {
Thread ping = new Thread(() -> {
event("ping", "call sendProbe 20.0.0.2");
Object answer = host.holeICMP().sendProbe("20.0.0.2", 64, 1, 1);
event("ping", "returned " + (answer == null ? "null" : "answer"));
});
ping.start();
}
static String id(int suffix) {
return String.format("00000000-0000-0000-0000-%012d", suffix);
}
@@ -124,12 +145,7 @@ public class RouterWebRun {
open("10.0.0.1/routes");
break;
case "routerLog": {
Thread ping = new Thread(() -> {
event("ping", "call sendProbe 20.0.0.2");
Object answer = host.holeICMP().sendProbe("20.0.0.2", 64, 1, 1);
event("ping", "returned " + (answer == null ? "null" : "answer"));
});
ping.start();
ping(host);
sleepUntil(3500);
open("10.0.0.1");
sleepUntil(7000);
@@ -139,6 +155,20 @@ public class RouterWebRun {
case "routerKonfig":
open("10.0.0.1/konfig");
break;
case "konfigIcmp":
ping(host);
sleepUntil(3500);
post("10.0.0.1/konfig", "firewallActivate=1&onlySYN=1&btnSave=speichern&defaultPolicy=accept&srcIP=&srcMask=&destIP=&destMask=&protocol=*&port=&action=drop");
sleepUntil(7000);
ping(host);
break;
case "konfigBlock":
post("10.0.0.1/konfig", "firewallActivate=1&dropICMP=1&onlySYN=1&defaultPolicy=accept&srcIP=&srcMask=&destIP=&destMask=&protocol=TCP&port=80&action=drop&addrule=hinzuf%C3%BCgen");
sleepUntil(4000);
post("10.0.0.1/konfig", "firewallActivate=1&dropICMP=1&onlySYN=1&defaultPolicy=accept&radioRule=2&moveup=rauf&srcIP=&srcMask=&destIP=&destMask=&protocol=*&port=&action=drop");
sleepUntil(8000);
open("10.0.0.1");
break;
case "gatewayIndex":
open("192.168.0.1");
sleepUntil(4000);