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
+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));