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:
@@ -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).
|
||||
|
||||
@@ -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 \
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user