feat(editor): F4.5c the DHCP server and the port forwarding in the device sheet

Measured first: SheetRun now types into FILIUS' DHCP dialog and keeps rows
in its port forwarding dialog. Java's DHCP server keeps its bounds as typed
(".0.0.1" too), never takes an empty gateway or DNS server, hands out the
device's gateway and DNS server unless its own (a home router always its
LAN address, where "manual settings" do nothing), drops a second static
assignment of a MAC address silently and lets "_" through its MAC pattern;
the port forwarding dialog keeps any row, its firmware forwards only the
valid ones. Here a value passes its rule or is not taken, with a word why.

- doc: DHCP server addresses and flags, static assignments (a MAC address
  once, ignoring case), port forwarding rows (TCP or UDP, musterPort, the
  LAN address); the sheet shows what the server hands out and which rows
  Java's firmware forwards (the core's parsePortForwardingRow, exported)
- editor: Java's dialogs as pages of the sheet with a back button, the
  three row tables on one RowsEditor (the forwarding table rebuilt on it);
  a sheet per device, so an open page closes when another device is chosen

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-03 12:16:04 +00:00
co-authored by Claude Opus 5.5
parent eff9433673
commit c8edb87210
22 changed files with 1525 additions and 143 deletions
+28
View File
@@ -1157,3 +1157,31 @@ Gemessen (03.10.2026):
* Tabelle bearbeiten: Eine Zelle, die das Muster nicht besteht („abc“, „“,
„10..0.0“), wird als `null` gespeichert; eine neue Zeile erst, wenn alle
vier Zellen gültig sind; „Neuer Eintrag“ zweimal ergibt eine offene Zeile.
### DHCP-Server und Portweiterleitung (F4.5c)
`SheetRun` tippt auch in `JDHCPKonfiguration` (eines Rechners mit Gateway
und DNS oder eines Heimrouters; der Server mit Grenzen 100–200 und eigenem
Gateway 10.0.0.1, DNS 10.0.0.2) und übernimmt mit dem Knopf (`speichern`).
Dazu, was der Dialog ohne eigene Einstellungen zeigt, feste Zuordnungen aus
seiner Tabelle (`dhcp`) und Zeilen des Portweiterleitungs-Dialogs
(`forwarding`).
Gemessen (03.10.2026):
* Grenzen, Gateway, DNS: Muster `musterIpAdresse`, also nie leer. Der Server
speichert den Text wie getippt: „010.000.000.001“ bleibt so, „.0.0.1“
wird unmarkiert gespeichert.
* Ohne eigene Einstellungen zeigt der Dialog Gateway und DNS des Rechners
(gesperrt), ohne solche 0.0.0.0. Beim Heimrouter ist das Gateway immer
seine LAN-Adresse (gesperrt), der DNS-Server immer frei; das Kästchen
„Manuelle Einstellungen“ bewirkt dort nichts. Die Netzmaske ist die der
ersten Karte (beim Heimrouter LAN), gesperrt.
* Feste Zuordnungen: Javas Hinzufügen-Knopf prüft MAC (`musterMacAddress`,
dessen Klasse `[0-9a-fA-f]` auch „_“ durchlässt) und Adresse; beim
Speichern verwirft der Server eine zweite Zuordnung derselben MAC (ohne
Rücksicht auf Groß-/Kleinschreibung) und ungültige Zeilen still. Die MAC
bleibt, wie getippt.
* Portweiterleitung: Der Dialog speichert jede Zeile roh, auch Port „0“,
„abc“, „05353“; die Firmware leitet nur gültige weiter
(`validateData`: `musterProtocol`, `musterPort`, `musterIpAdresse`).
+149 -1
View File
@@ -19,12 +19,23 @@
// shows its address or MAC address, DHCP switched on while the device's own
// DHCP server runs; then a router's forwarding table as Java shows it
// (Weiterleitungstabelle.holeTabelle, "tables") and its rows edited in the
// panel's table (JWeiterleitungsTabelle, "edits").
// panel's table (JWeiterleitungsTabelle, "edits"). The DHCP dialog
// (JDHCPKonfiguration) takes its fields with its button (speichern):
//
// <dhcpHost|dhcpHome> <lower|upper|gateway|dns> <text>
//
// a host's (gateway and DNS with its own settings on) or a home router's;
// then what the dialog shows without own settings, the static assignments
// ("dhcp") and a home router's port forwarding as its dialog keeps it
// (JPortForwardingDialog, "forwarding").
//
// Usage: java -cp filius.jar:lib/*:classes SheetRun <cases txt> <output json>
import filius.gui.GUIContainer;
import filius.gui.JMainFrame;
import filius.gui.netzwerksicht.JDHCPKonfiguration;
import filius.gui.netzwerksicht.JKonfiguration;
import filius.gui.netzwerksicht.JPortForwardingDialog;
import filius.gui.netzwerksicht.JWeiterleitungsTabelle;
import filius.hardware.NetzwerkInterface;
import filius.hardware.knoten.Gateway;
@@ -40,6 +51,8 @@ import filius.software.system.InternetKnotenBetriebssystem;
import filius.software.system.ModemFirmware;
import filius.software.system.SwitchFirmware;
import filius.software.system.VermittlungsrechnerBetriebssystem;
import filius.software.dhcp.DHCPAddressAssignment;
import filius.software.dhcp.DHCPServer;
import filius.software.vermittlungsschicht.Weiterleitungstabelle;
import java.io.FileOutputStream;
@@ -55,6 +68,9 @@ import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import javax.swing.JCheckBox;
import javax.swing.JTable;
import javax.swing.table.DefaultTableModel;
import java.util.LinkedList;
import javax.swing.JTextField;
public class SheetRun {
@@ -242,6 +258,132 @@ public class SheetRun {
return result;
}
/** A device whose DHCP server the dialog edits: a host 192.168.0.10/24 with gateway and DNS, or a home router; the server with known settings. */
static InternetKnotenBetriebssystem dhcpDevice(String kind) {
InternetKnotenBetriebssystem os;
if (kind.equals("dhcpHost")) {
Rechner host = new Rechner();
os = (InternetKnotenBetriebssystem) host.getSystemSoftware();
os.setStandardGateway("192.168.0.254");
os.setDNSServer("192.168.0.253");
} else {
os = (InternetKnotenBetriebssystem) new Gateway().getSystemSoftware();
}
DHCPServer server = os.getDHCPServer();
server.setUntergrenze("192.168.0.100");
server.setObergrenze("192.168.0.200");
server.setGatewayip("10.0.0.1");
server.setDnsserverip("10.0.0.2");
return os;
}
static JDHCPKonfiguration dhcpDialog(InternetKnotenBetriebssystem os) {
return new JDHCPKonfiguration(JMainFrame.getJMainFrame(), "DHCP", os);
}
static String dhcpValue(DHCPServer server, String field) {
switch (field) {
case "lower": return server.getUntergrenze();
case "upper": return server.getObergrenze();
case "gateway": return server.getGatewayip();
case "dns": return server.getDnsserverip();
}
throw new IllegalArgumentException(field);
}
/** A field of the DHCP dialog typed and taken with its button. */
static Map<String, Object> dhcpCase(String kind, String field, String text) throws Exception {
InternetKnotenBetriebssystem os = dhcpDevice(kind);
DHCPServer server = os.getDHCPServer();
String start = dhcpValue(server, field);
JDHCPKonfiguration dialog = dhcpDialog(os);
if (kind.equals("dhcpHost")) ((JCheckBox) get(dialog, "cbUseInternal")).setSelected(true);
String name = field.equals("lower") ? "tfUntergrenze" : field.equals("upper") ? "tfObergrenze" : field.equals("gateway") ? "tfGateway" : "tfDNSServer";
JTextField input = (JTextField) get(dialog, name);
input.setText(text);
boolean marked = !dialog.ueberpruefen(EingabenUeberpruefung.musterIpAdresse, input);
call(dialog, "speichern");
dialog.dispose();
Map<String, Object> result = new LinkedHashMap<>();
result.put("device", kind);
result.put("field", field);
result.put("input", text);
result.put("start", start);
result.put("marked", marked);
result.put("stored", dhcpValue(server, field));
result.put("shown", dhcpValue(server, field));
return result;
}
/** What the DHCP dialog shows without own settings, and its static assignments taken with its button. */
static Map<String, Object> dhcpFlows() throws Exception {
Map<String, Object> flows = new LinkedHashMap<>();
for (String kind : new String[] { "dhcpHost", "dhcpHome" }) {
InternetKnotenBetriebssystem os = dhcpDevice(kind);
JDHCPKonfiguration dialog = dhcpDialog(os);
Map<String, Object> shown = new LinkedHashMap<>();
for (String name : new String[] { "tfUntergrenze", "tfObergrenze", "tfNetzmaske", "tfGateway", "tfDNSServer" }) {
JTextField input = (JTextField) get(dialog, name);
shown.put(name, Arrays.asList(input.getText(), input.isEditable()));
}
dialog.dispose();
flows.put(kind + "Shown", shown);
}
{
Rechner host = new Rechner();
InternetKnotenBetriebssystem os = (InternetKnotenBetriebssystem) host.getSystemSoftware();
JDHCPKonfiguration dialog = dhcpDialog(os);
flows.put("hostWithoutGatewayShown", Arrays.asList(((JTextField) get(dialog, "tfGateway")).getText(), ((JTextField) get(dialog, "tfDNSServer")).getText()));
dialog.dispose();
}
// Static assignments: rows of the dialog's table, taken with its button.
String[][] rows = { { "aa:bb:cc:dd:ee:01", "192.168.0.50" }, { "AA:BB:CC:DD:EE:01", "192.168.0.51" }, { "AA:BB:CC:DD:EE:_F", "192.168.0.52" }, { "AA:BB:CC:DD:EE:02", "192.168.0.256" },
{ "AA:BB:CC:DD:EE:03", "010.0.0.1" }, { "AABBCCDDEE04", "192.168.0.54" } };
InternetKnotenBetriebssystem os = dhcpDevice("dhcpHost");
JDHCPKonfiguration dialog = dhcpDialog(os);
DefaultTableModel model = (DefaultTableModel) ((JTable) get(dialog, "staticAddressTable")).getModel();
for (String[] row : rows) model.addRow(new Object[] { row[0], row[1] });
call(dialog, "speichern");
dialog.dispose();
List<Object> kept = new ArrayList<>();
for (DHCPAddressAssignment entry : os.getDHCPServer().holeStaticAssignedAddresses()) kept.add(Arrays.asList(entry.getMAC(), entry.getIp()));
flows.put("staticAssignments", kept);
// The add button's own check, per row: MAC and address patterns.
List<Object> addable = new ArrayList<>();
for (String[] row : rows) addable.add(EingabenUeberpruefung.isGueltig(row[0], EingabenUeberpruefung.musterMacAddress) && EingabenUeberpruefung.isGueltig(row[1], EingabenUeberpruefung.musterIpAdresse));
flows.put("addable", addable);
return flows;
}
/** A home router's port forwarding rows as its dialog keeps them, and which of them its firmware would forward (musterProtocol, musterPort, musterIpAdresse). */
static Map<String, Object> forwardingFlows() throws Exception {
Gateway home = new Gateway();
GatewayFirmware firmware = (GatewayFirmware) home.getSystemSoftware();
JPortForwardingDialog dialog = new JPortForwardingDialog(firmware.getStaticNAT(), JMainFrame.getJMainFrame());
dialog.createStaticTable();
String[][] rows = { { "TCP", "80", "192.168.0.20", "8080" }, { "UDP", "0", "192.168.0.21", "53" }, { "TCP", "abc", "192.168.0.22", "80" }, { "tcp", "443", "192.168.0.300", "443" },
{ "17", "5353", "192.168.0.23", "05353" } };
for (String[] row : rows) {
dialog.neuerEintrag();
JTable table = (JTable) get(dialog, "staticTable");
int last = table.getRowCount() - 1;
for (int column = 0; column < 4; column++) table.setValueAt(row[column], last, column);
}
dialog.aenderungenAnnehmen();
dialog.dispose();
Map<String, Object> flows = new LinkedHashMap<>();
List<Object> kept = new ArrayList<>();
List<Object> valid = new ArrayList<>();
for (String[] row : firmware.getStaticNAT()) {
kept.add(Arrays.asList(row));
valid.add(EingabenUeberpruefung.isGueltig(row[0], EingabenUeberpruefung.musterProtocol) && EingabenUeberpruefung.isGueltig(row[1], EingabenUeberpruefung.musterPort)
&& EingabenUeberpruefung.isGueltig(row[2], EingabenUeberpruefung.musterIpAdresse) && EingabenUeberpruefung.isGueltig(row[3], EingabenUeberpruefung.musterPort));
}
flows.put("rows", kept);
flows.put("valid", valid);
return flows;
}
public static void main(String[] args) throws Exception {
// FILIUS' window keeps the JVM alive: end it on any failure too.
try {
@@ -261,6 +403,10 @@ public class SheetRun {
String[] words = line.split(" ", 3);
String key = words[0] + " " + words[1];
String text = words.length < 3 ? "" : IpCheckCases.unescape(words[2]);
if (words[0].startsWith("dhcp")) {
cases.add(dhcpCase(words[0], words[1], text));
continue;
}
String[] names = FIELDS.get(key);
if (names == null) throw new IllegalArgumentException(line);
Object device = device(words[0]);
@@ -360,6 +506,8 @@ public class SheetRun {
output.put("cases", cases);
output.put("flows", flows);
output.put("tables", tables);
output.put("dhcp", dhcpFlows());
output.put("forwarding", forwardingFlows());
output.put("edits", edits);
try (FileOutputStream out = new FileOutputStream(args[1])) {
out.write((FlsOracle.Json.write(output, "") + "\n").getBytes(StandardCharsets.UTF_8));