feat(editor): F4.5d the firewall in the device sheet
Measured first: SheetRun drives FILIUS' firewall dialog down to its table editor (Swing's own end of an edit). Java's table writes a cell into the rule before it checks it, so that the firewall holds "abc", a mask of 255.0.255.0 or port 0 while the table shows the old value, and reads a port that is no number as all ports. Here nothing the check refuses reaches the rule. - doc: the three switches, the default action, rules (new as Java's, cells by Java's checks: masks of ones then zeros, the directly connected marker as a source only, an empty port for all ports; protocol, action, moved up or down, removed) - editor: the firewall as a page of the router's and home router's sheet, with Java's explanations; the general tab named so (Java: "network interfaces") Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+3
-1
@@ -126,6 +126,7 @@ Swift-Pfade relativ zu `filius-ipad/ios/FiliusPad/TopologyEditor/` (Stand
|
|||||||
| `src/sheet.ts` | Java `JHostKonfiguration.updateAttribute/updateSsid`, `JSwitchKonfiguration.updateAttribute`, `JModemKonfiguration.updateAttribute` | neu (F4.5a): was das Blatt zeigt — je Art die Felder des Java-Panels, die Netze der Switches je einmal, WLAN nur bei freiem Port oder schon drahtlos, Haltezeit in ganzen Sekunden |
|
| `src/sheet.ts` | Java `JHostKonfiguration.updateAttribute/updateSsid`, `JSwitchKonfiguration.updateAttribute`, `JModemKonfiguration.updateAttribute` | neu (F4.5a): was das Blatt zeigt — je Art die Felder des Java-Panels, die Netze der Switches je einmal, WLAN nur bei freiem Port oder schon drahtlos, Haltezeit in ganzen Sekunden |
|
||||||
| `src/settings.ts`, `src/sheet.ts` (Router, Heimrouter) | Java `JVermittlungsrechnerKonfiguration`, `JGatewayConfiguration`, `JWeiterleitungsTabelle`, `Weiterleitungstabelle.holeTabelle/validate/addManuellenEintrag` | erweitert (F4.5b): Adresse und Maske je Karte (die erste gespiegelt in den Einstellungen des Geräts, wie das Projekt sie hält), Standard-Gateway, RIP, die manuellen Zeilen der Weiterleitungstabelle (neue Zeile erst mit vier gültigen Zellen, Zelle, Löschen), DHCP am Heimrouter (lässt seinen DHCP-Server laufen, wie Java); das Blatt liest Karten mit dem Gerät am anderen Kabelende und die ganze Tabelle über `routeTableRows` des Kerns (gegen fünf von Java gemessene Tabellen). Abweichend von Java: Eine Kartenadresse, die das Muster, aber nicht `IP.ipCheck` besteht („.0.0.1“), wird nicht `null` (Javas Router-Panel wirft danach); eine abgelehnte Tabellenzelle wird nicht als `null` gespeichert; ein leeres Gateway ist nicht rot (Javas Router-Panel prüft es mit `musterIpAdresse`, übernimmt es aber) |
|
| `src/settings.ts`, `src/sheet.ts` (Router, Heimrouter) | Java `JVermittlungsrechnerKonfiguration`, `JGatewayConfiguration`, `JWeiterleitungsTabelle`, `Weiterleitungstabelle.holeTabelle/validate/addManuellenEintrag` | erweitert (F4.5b): Adresse und Maske je Karte (die erste gespiegelt in den Einstellungen des Geräts, wie das Projekt sie hält), Standard-Gateway, RIP, die manuellen Zeilen der Weiterleitungstabelle (neue Zeile erst mit vier gültigen Zellen, Zelle, Löschen), DHCP am Heimrouter (lässt seinen DHCP-Server laufen, wie Java); das Blatt liest Karten mit dem Gerät am anderen Kabelende und die ganze Tabelle über `routeTableRows` des Kerns (gegen fünf von Java gemessene Tabellen). Abweichend von Java: Eine Kartenadresse, die das Muster, aber nicht `IP.ipCheck` besteht („.0.0.1“), wird nicht `null` (Javas Router-Panel wirft danach); eine abgelehnte Tabellenzelle wird nicht als `null` gespeichert; ein leeres Gateway ist nicht rot (Javas Router-Panel prüft es mit `musterIpAdresse`, übernimmt es aber) |
|
||||||
| `src/settings.ts`, `src/sheet.ts`, `src/fields.ts` (DHCP-Server, Portweiterleitung) | Java `JDHCPKonfiguration`, `DHCPServer.determineGatewayip/determineDnsserverip/addStaticAssignment`, `JPortForwardingDialog`, `GatewayFirmware.validateData`, `EingabenUeberpruefung.musterMacAddress` | erweitert (F4.5c): Grenzen, Gateway und DNS des DHCP-Servers (wie Javas Dialog nie leer), „Manuelle Einstellungen“ und „DHCP aktivieren“, feste Zuordnungen (MAC und Adresse, eine MAC nur einmal, ohne Rücksicht auf Groß-/Kleinschreibung), die Zeilen der Portweiterleitung (TCP oder UDP, Ports nach `musterPort`, LAN-Adresse); das Blatt zeigt, was der Server ausgibt, wie Java es bestimmt (eigene Einstellung, sonst Gateway und DNS des Geräts oder 0.0.0.0; beim Heimrouter die LAN-Adresse) und welche Zeile Javas Firmware weiterleitet (`parsePortForwardingRow` des Kerns, jetzt exportiert). Abweichend von Java: Grenzen in `IP.ipCheck`-Form statt wie getippt („.0.0.1“ nimmt Java), MAC-Adressen groß und nur aus Hexziffern (Javas Klasse `[0-9a-fA-f]` lässt „_“ durch), eine zweite Zuordnung derselben MAC wird mit Meldung abgelehnt statt still verworfen, ungültige Portweiterleitungen werden nicht gespeichert (Java speichert jede, leitet aber nur gültige weiter) |
|
| `src/settings.ts`, `src/sheet.ts`, `src/fields.ts` (DHCP-Server, Portweiterleitung) | Java `JDHCPKonfiguration`, `DHCPServer.determineGatewayip/determineDnsserverip/addStaticAssignment`, `JPortForwardingDialog`, `GatewayFirmware.validateData`, `EingabenUeberpruefung.musterMacAddress` | erweitert (F4.5c): Grenzen, Gateway und DNS des DHCP-Servers (wie Javas Dialog nie leer), „Manuelle Einstellungen“ und „DHCP aktivieren“, feste Zuordnungen (MAC und Adresse, eine MAC nur einmal, ohne Rücksicht auf Groß-/Kleinschreibung), die Zeilen der Portweiterleitung (TCP oder UDP, Ports nach `musterPort`, LAN-Adresse); das Blatt zeigt, was der Server ausgibt, wie Java es bestimmt (eigene Einstellung, sonst Gateway und DNS des Geräts oder 0.0.0.0; beim Heimrouter die LAN-Adresse) und welche Zeile Javas Firmware weiterleitet (`parsePortForwardingRow` des Kerns, jetzt exportiert). Abweichend von Java: Grenzen in `IP.ipCheck`-Form statt wie getippt („.0.0.1“ nimmt Java), MAC-Adressen groß und nur aus Hexziffern (Javas Klasse `[0-9a-fA-f]` lässt „_“ durch), eine zweite Zuordnung derselben MAC wird mit Meldung abgelehnt statt still verworfen, ungültige Portweiterleitungen werden nicht gespeichert (Java speichert jede, leitet aber nur gültige weiter) |
|
||||||
|
| `src/settings.ts`, `src/sheet.ts`, `src/fields.ts` (Firewall) | Java `JFirewallDialog`, `GatewayFirewallConfigTable.closeEditor`, `Firewall.addRule/moveUp/moveDown/deleteRule`, `FirewallRule.validateExistingAttributes`, `EingabenUeberpruefung.isValidSubnetmask` | erweitert (F4.5d): Firewall an/aus, ICMP filtern, nur SYN verwerfen, Standardaktion, Regeln (neu wie Javas: leer, TCP, alle Ports, verwerfen; Zelle, Protokoll, Aktion, rauf/runter, löschen); Quelle leer, Adresse oder Javas Marke „999.999.999.999“, Ziel leer oder Adresse, Masken leer oder durchgehende Einsen, Port leer (alle) oder `musterPort`. Abweichend von Java: Javas Tabelle schreibt eine Zelle vor der Prüfung in die Regel, sodass die Firewall „abc“, Maske „255.0.255.0“ oder Port 0 hält, während die Tabelle den alten Wert zeigt — hier wird nichts übernommen, was die Prüfung ablehnt; ein Port, der keine Zahl ist, wird nicht still zu „alle Ports“; „.0.0.1“ und „080“ gelten nicht |
|
||||||
| `src/view.ts` (`nodeLabel`) | Java `Knoten.holeAnzeigeName` | herausgelöst (F4.5b): die Beschriftung eines Geräts, auch für „Verbunden mit …“ |
|
| `src/view.ts` (`nodeLabel`) | Java `Knoten.holeAnzeigeName` | herausgelöst (F4.5b): die Beschriftung eines Geräts, auch für „Verbunden mit …“ |
|
||||||
| `test/sheet.test.ts`, `test/sheet.cases.txt`, `test/sheet.java.json` | — | neu (F4.5a): 77 in Javas Felder getippte Texte, jede Regel dagegen samt der benannten Abweichungen; die Befehle, die Funkverbindungen, Rückgängig je Änderung, alles über eine gespeicherte `.fls` |
|
| `test/sheet.test.ts`, `test/sheet.cases.txt`, `test/sheet.java.json` | — | neu (F4.5a): 77 in Javas Felder getippte Texte, jede Regel dagegen samt der benannten Abweichungen; die Befehle, die Funkverbindungen, Rückgängig je Änderung, alles über eine gespeicherte `.fls` |
|
||||||
| `test/devices.test.ts`, `test/fixtures/new-devices.fls` | — | neu (F4.4a): neue Geräte jeder Art gegen Javas gespeicherte (Zufall maskiert), Kabelregeln, ein selbst gebautes Netz pingt im Kern, Rückgängig nur der eigenen Änderungen über zwei Replikate |
|
| `test/devices.test.ts`, `test/fixtures/new-devices.fls` | — | neu (F4.4a): neue Geräte jeder Art gegen Javas gespeicherte (Zufall maskiert), Kabelregeln, ein selbst gebautes Netz pingt im Kern, Rückgängig nur der eigenen Änderungen über zwei Replikate |
|
||||||
@@ -165,7 +166,8 @@ Swift-Pfade relativ zu `filius-ipad/ios/FiliusPad/TopologyEditor/` (Stand
|
|||||||
| `src/Editor.tsx`, `src/canvas/Canvas.tsx` (Werkzeuge) | Java `GUIEvents` (Kabel ziehen, Heimrouter-Hälften, Meldungen `guievents_msg1/12/22–26`) | neu (F4.4b): Auswählen, Gerät ziehen, Langdruck, Kabel ziehen oder zweimal antippen mit Gummiband und WAN/LAN-Hinweis, Javas Meldungen, Entf-Taste, Rückgängig/Wiederholen per Knopf und Strg+Z/Strg+Y; jede Änderung mit dem eigenen Ursprung des Editors |
|
| `src/Editor.tsx`, `src/canvas/Canvas.tsx` (Werkzeuge) | Java `GUIEvents` (Kabel ziehen, Heimrouter-Hälften, Meldungen `guievents_msg1/12/22–26`) | neu (F4.4b): Auswählen, Gerät ziehen, Langdruck, Kabel ziehen oder zweimal antippen mit Gummiband und WAN/LAN-Hinweis, Javas Meldungen, Entf-Taste, Rückgängig/Wiederholen per Knopf und Strg+Z/Strg+Y; jede Änderung mit dem eigenen Ursprung des Editors |
|
||||||
| `browser/design.spec.ts`, `browser/editorPage.ts` | — | neu (F4.4b): die Werkzeuge in Chromium mit Maus und Berührung (CDP) |
|
| `browser/design.spec.ts`, `browser/editorPage.ts` | — | neu (F4.4b): die Werkzeuge in Chromium mit Maus und Berührung (CDP) |
|
||||||
| `src/sheet/DeviceSheet.tsx`, `src/sheet/SheetField.tsx`, `src/sheet/sheet.css` | Java `JKonfiguration` und die Panels je Art (Reihenfolge und Wörter `jhostkonfiguration_msg1`–`16`, `jswitchkonfiguration_msg1`–`5`, `jmodemkonfiguration_msg4`) | neu (F4.5a): das Geräte-Blatt — öffnet mit dem gewählten Gerät wie Javas Panel, neben der Fläche (340 px) oder auf dem Handy darunter; ein Feld wird beim Verlassen oder mit Enter übernommen, abgelehnter Text bleibt rot stehen mit dem, was weiter gilt (Java setzt still den alten Wert zurück), Escape verwirft ihn; Adressfelder bei DHCP gesperrt; jede Änderung ein Schritt im Rückgängig. Vermittlungsrechner und Heimrouter vorerst nur mit Namen |
|
| `src/sheet/DeviceSheet.tsx`, `src/sheet/SheetField.tsx`, `src/sheet/sheet.css` | Java `JKonfiguration` und die Panels je Art (Reihenfolge und Wörter `jhostkonfiguration_msg1`–`16`, `jswitchkonfiguration_msg1`–`5`, `jmodemkonfiguration_msg4`) | neu (F4.5a): das Geräte-Blatt — öffnet mit dem gewählten Gerät wie Javas Panel, neben der Fläche (340 px) oder auf dem Handy darunter; ein Feld wird beim Verlassen oder mit Enter übernommen, abgelehnter Text bleibt rot stehen mit dem, was weiter gilt (Java setzt still den alten Wert zurück), Escape verwirft ihn; Adressfelder bei DHCP gesperrt; jede Änderung ein Schritt im Rückgängig. Vermittlungsrechner und Heimrouter vorerst nur mit Namen |
|
||||||
| `browser/sheet.spec.ts` | — | neu (F4.5a): das Blatt in Chromium — Felder wie Java, Markieren und Behalten, Gerätewechsel, Rückgängig, WLAN, Handy mit Berührung, Englisch; seit F4.5b Router-Reiter, aktives Kabel, Weiterleitungstabelle, RIP, Heimrouter; seit F4.5c DHCP-Server und Portfreigaben |
|
| `browser/sheet.spec.ts` | — | neu (F4.5a): das Blatt in Chromium — Felder wie Java, Markieren und Behalten, Gerätewechsel, Rückgängig, WLAN, Handy mit Berührung, Englisch; seit F4.5b Router-Reiter, aktives Kabel, Weiterleitungstabelle, RIP, Heimrouter; seit F4.5c DHCP-Server und Portfreigaben; seit F4.5d Firewall |
|
||||||
|
| `src/sheet/FirewallPanel.tsx` | Java `JFirewallDialog` (`jfirewalldialog_msg21`–`43`, `jvermittlungsrechnerkonfiguration_msg4`) | neu (F4.5d): die Firewall als Seite des Blatts (bei Vermittlungsrechner und Heimrouter unter „Allgemein“): Reiter Allgemein mit den drei Schaltern und Javas Erklärungen, Reiter Firewall-Regeln mit Javas Einleitung, Standardaktion, Regeltabelle, rauf/runter (an den Enden gesperrt), Neue Regel, Regel löschen (die Nachfolgerin bleibt gewählt, wie bei Java). Javas erster Reiter heißt „Netzwerkschnittstellen“, enthält aber die allgemeinen Schalter — hier „Allgemein“; Javas „Aktion?“ ohne Fragezeichen, „über die Standard-Aktion“ groß |
|
||||||
| `src/sheet/ServicePanels.tsx`, `src/sheet/RowsEditor.tsx`, `src/sheet/SheetTabs.tsx`, `src/sheet/DeviceSheet.tsx` (Seiten) | Java `JDHCPKonfiguration` (`jdhcpkonfiguration_msg1`–`15`), `JPortForwardingDialog` (`jportforwarding_msg4`–`7`, `jgatewayconfiguration_msg19`) | neu (F4.5c): Javas Dialoge als Seiten des Blatts mit „Zurück“ — DHCP-Server (Grundeinstellungen, statische Adresszuweisung; beim Rechner, gesperrt während DHCP die Karte einrichtet, und auf dem LAN-Reiter des Heimrouters), Portfreigaben (Heimrouter); jedes Feld beim Verlassen übernommen statt mit Javas OK-Knopf. Die Zeilentabellen (Weiterleitung, Zuordnungen, Portfreigaben) teilen sich `RowsEditor`: eine neue Zeile, sobald alle Felder gültig sind, Zeile wählen, bearbeiten, löschen. Javas Tooltip zu „Manuelle Einstellungen“ als Text unter dem Kästchen (ein Finger hat kein Überfahren) |
|
| `src/sheet/ServicePanels.tsx`, `src/sheet/RowsEditor.tsx`, `src/sheet/SheetTabs.tsx`, `src/sheet/DeviceSheet.tsx` (Seiten) | Java `JDHCPKonfiguration` (`jdhcpkonfiguration_msg1`–`15`), `JPortForwardingDialog` (`jportforwarding_msg4`–`7`, `jgatewayconfiguration_msg19`) | neu (F4.5c): Javas Dialoge als Seiten des Blatts mit „Zurück“ — DHCP-Server (Grundeinstellungen, statische Adresszuweisung; beim Rechner, gesperrt während DHCP die Karte einrichtet, und auf dem LAN-Reiter des Heimrouters), Portfreigaben (Heimrouter); jedes Feld beim Verlassen übernommen statt mit Javas OK-Knopf. Die Zeilentabellen (Weiterleitung, Zuordnungen, Portfreigaben) teilen sich `RowsEditor`: eine neue Zeile, sobald alle Felder gültig sind, Zeile wählen, bearbeiten, löschen. Javas Tooltip zu „Manuelle Einstellungen“ als Text unter dem Kästchen (ein Finger hat kein Überfahren) |
|
||||||
| `src/sheet/RouterSheets.tsx`, `src/sheet/NameField.tsx`, `src/canvas/Canvas.tsx` (`highlightedLink`), `src/theme/tokens.css` (`--canvas-cable-active`) | Java `JVermittlungsrechnerKonfiguration`, `JGatewayConfiguration`, `JWeiterleitungsTabelle` (Wörter `jvermittlungsrechnerkonfiguration_msg6`–`26`, `jweiterleitungstabelle_msg3`–`6`), `JCablePanel.farbeBlinken`, `Kabel.setAktiv` | neu (F4.5b): Blatt des Vermittlungsrechners — Reiter Allgemein (Name, Gateway, Automatisches Routing, IP-Weiterleitung nur angezeigt), je Karte (Reiter mit ihrer Adresse, „Verbunden mit …“, Adresse, Maske, MAC) und Weiterleitungstabelle (alle Einträge oder nur die manuellen, Javas Zeilen grau, eine neue Zeile, die mit vier gültigen Feldern übernommen wird, Zelle bearbeiten, Eintrag löschen; bei RIP gesperrt); Blatt des Heimrouters mit Allgemein, LAN, WAN (DHCP sperrt die WAN-Adresse). Das Kabel der offenen Karte leuchtet grün wie bei Java. Javas „Eintrag Löschen“ klein geschrieben |
|
| `src/sheet/RouterSheets.tsx`, `src/sheet/NameField.tsx`, `src/canvas/Canvas.tsx` (`highlightedLink`), `src/theme/tokens.css` (`--canvas-cable-active`) | Java `JVermittlungsrechnerKonfiguration`, `JGatewayConfiguration`, `JWeiterleitungsTabelle` (Wörter `jvermittlungsrechnerkonfiguration_msg6`–`26`, `jweiterleitungstabelle_msg3`–`6`), `JCablePanel.farbeBlinken`, `Kabel.setAktiv` | neu (F4.5b): Blatt des Vermittlungsrechners — Reiter Allgemein (Name, Gateway, Automatisches Routing, IP-Weiterleitung nur angezeigt), je Karte (Reiter mit ihrer Adresse, „Verbunden mit …“, Adresse, Maske, MAC) und Weiterleitungstabelle (alle Einträge oder nur die manuellen, Javas Zeilen grau, eine neue Zeile, die mit vier gültigen Feldern übernommen wird, Zelle bearbeiten, Eintrag löschen; bei RIP gesperrt); Blatt des Heimrouters mit Allgemein, LAN, WAN (DHCP sperrt die WAN-Adresse). Das Kabel der offenen Karte leuchtet grün wie bei Java. Javas „Eintrag Löschen“ klein geschrieben |
|
||||||
|
|
||||||
|
|||||||
@@ -18,9 +18,23 @@
|
|||||||
// " 10.0.0.1"; a phone's keyboard puts a space after a word). A name and an
|
// " 10.0.0.1"; a phone's keyboard puts a space after a word). A name and an
|
||||||
// SSID are taken as typed, spaces included, as Java takes them.
|
// SSID are taken as typed, spaces included, as Java takes them.
|
||||||
|
|
||||||
import { IP_ADDRESS_PATTERN, javaIpCheck } from '@mdg-filius/core';
|
import { DIRECTLY_CONNECTED_SOURCE_MARKER, IP_ADDRESS_PATTERN, javaIpCheck, parseIPv4 } from '@mdg-filius/core';
|
||||||
|
|
||||||
export type FieldRule = 'name' | 'ipAddress' | 'ipAddressOrEmpty' | 'subnetMask' | 'ssid' | 'seconds' | 'port' | 'macAddress';
|
export type FieldRule =
|
||||||
|
| 'name'
|
||||||
|
| 'ipAddress'
|
||||||
|
| 'ipAddressOrEmpty'
|
||||||
|
| 'subnetMask'
|
||||||
|
| 'ssid'
|
||||||
|
| 'seconds'
|
||||||
|
| 'port'
|
||||||
|
| 'macAddress'
|
||||||
|
/** A firewall rule's source: empty, an address, or Java's marker for "a directly connected network". */
|
||||||
|
| 'firewallSource'
|
||||||
|
/** A firewall rule's mask: empty, or ones then zeros (EingabenUeberpruefung.isValidSubnetmask). */
|
||||||
|
| 'firewallMask'
|
||||||
|
/** A firewall rule's port: empty for all ports, or musterPort. */
|
||||||
|
| 'portOrEmpty';
|
||||||
|
|
||||||
/** What a field's text comes to: the value the device takes, or that it takes none. */
|
/** What a field's text comes to: the value the device takes, or that it takes none. */
|
||||||
export type CheckedField = { readonly value: string } | { readonly invalid: true };
|
export type CheckedField = { readonly value: string } | { readonly invalid: true };
|
||||||
@@ -71,6 +85,18 @@ export function checkField(rule: FieldRule, text: string): CheckedField {
|
|||||||
}
|
}
|
||||||
case 'port':
|
case 'port':
|
||||||
return PORT_PATTERN.test(text.trim()) ? { value: text.trim() } : INVALID;
|
return PORT_PATTERN.test(text.trim()) ? { value: text.trim() } : INVALID;
|
||||||
|
case 'firewallSource':
|
||||||
|
if (text.trim() === DIRECTLY_CONNECTED_SOURCE_MARKER) return { value: DIRECTLY_CONNECTED_SOURCE_MARKER };
|
||||||
|
return checkField('ipAddressOrEmpty', text);
|
||||||
|
case 'firewallMask': {
|
||||||
|
if (text.trim() === '') return { value: '' };
|
||||||
|
const checked = address(text.trim(), SUBNET_MASK_PATTERN);
|
||||||
|
if ('invalid' in checked) return checked;
|
||||||
|
// Ones, then zeros, at least one one (Java: the 32 bits match "^11*0*$").
|
||||||
|
return /^1+0*$/.test(parseIPv4(checked.value)!.toString(2).padStart(32, '0')) ? checked : INVALID;
|
||||||
|
}
|
||||||
|
case 'portOrEmpty':
|
||||||
|
return text.trim() === '' ? { value: '' } : checkField('port', text);
|
||||||
case 'macAddress':
|
case 'macAddress':
|
||||||
// Upper case, as the model keeps MAC addresses (Java keeps them as typed and compares ignoring case).
|
// Upper case, as the model keeps MAC addresses (Java keeps them as typed and compares ignoring case).
|
||||||
return MAC_ADDRESS_PATTERN.test(text.trim()) ? { value: text.trim().toUpperCase() } : INVALID;
|
return MAC_ADDRESS_PATTERN.test(text.trim()) ? { value: text.trim().toUpperCase() } : INVALID;
|
||||||
|
|||||||
@@ -24,14 +24,22 @@
|
|||||||
// second static assignment of a MAC address without a word, and that a home
|
// second static assignment of a MAC address without a word, and that a home
|
||||||
// router keeps any port forwarding row, valid or not (its firmware then
|
// router keeps any port forwarding row, valid or not (its firmware then
|
||||||
// forwards only the valid ones): here a value passes its rule or is not
|
// forwards only the valid ones): here a value passes its rule or is not
|
||||||
// taken, and the sheet says why.
|
// taken, and the sheet says why. Nor that Java's firewall table writes a
|
||||||
|
// cell into the rule before it checks it, so that a rule holds "abc" or a
|
||||||
|
// mask of 255.0.255.0 while the table shows the old value, and reads a port
|
||||||
|
// that is no number as "all ports".
|
||||||
|
|
||||||
import * as Y from 'yjs';
|
import * as Y from 'yjs';
|
||||||
import {
|
import {
|
||||||
DEFAULT_REMOTE_LINK_LATENCY_MILLISECONDS,
|
DEFAULT_REMOTE_LINK_LATENCY_MILLISECONDS,
|
||||||
defaultSwitchConfiguration,
|
defaultSwitchConfiguration,
|
||||||
|
ALL_PORTS,
|
||||||
dhcpServerConfiguration,
|
dhcpServerConfiguration,
|
||||||
|
firewallConfiguration,
|
||||||
|
firewallRule,
|
||||||
forwardsIPv4ByDefault,
|
forwardsIPv4ByDefault,
|
||||||
|
type FirewallAction,
|
||||||
|
type FirewallProtocol,
|
||||||
type DhcpStaticAssignment,
|
type DhcpStaticAssignment,
|
||||||
type GatewayPortForwardingRow,
|
type GatewayPortForwardingRow,
|
||||||
type Id,
|
type Id,
|
||||||
@@ -42,7 +50,7 @@ import { modemPair } from '@mdg-filius/fls';
|
|||||||
import { connect, type ConnectError } from './commands.js';
|
import { connect, type ConnectError } from './commands.js';
|
||||||
import { checkField, type CheckedField, type FieldRule } from './fields.js';
|
import { checkField, type CheckedField, type FieldRule } from './fields.js';
|
||||||
import { inOrder, ROOTS } from './projectDoc.js';
|
import { inOrder, ROOTS } from './projectDoc.js';
|
||||||
import { toY } from './values.js';
|
import { fromY, toY, type PlainValue } from './values.js';
|
||||||
|
|
||||||
const nodes = (doc: Y.Doc) => doc.getMap<Y.Map<unknown>>(ROOTS.nodes);
|
const nodes = (doc: Y.Doc) => doc.getMap<Y.Map<unknown>>(ROOTS.nodes);
|
||||||
const links = (doc: Y.Doc) => doc.getMap<Y.Map<unknown>>(ROOTS.links);
|
const links = (doc: Y.Doc) => doc.getMap<Y.Map<unknown>>(ROOTS.links);
|
||||||
@@ -481,3 +489,98 @@ export function removePortForwarding(doc: Y.Doc, id: Id, index: number, origin:
|
|||||||
if (list.length === 0) settingsOf(node).delete('portForwarding');
|
if (list.length === 0) settingsOf(node).delete('portForwarding');
|
||||||
}, origin);
|
}, origin);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** A router's or home router's firewall, made as Java's constructor leaves it when the device has none in the document. */
|
||||||
|
function firewallOf(node: Y.Map<unknown>): Y.Map<unknown> {
|
||||||
|
let firewall = settingsOf(node).get('firewall') as Y.Map<unknown> | undefined;
|
||||||
|
if (firewall === undefined) {
|
||||||
|
firewall = toY({ ...firewallConfiguration(), rules: [] }) as Y.Map<unknown>;
|
||||||
|
settingsOf(node).set('firewall', firewall);
|
||||||
|
}
|
||||||
|
return firewall;
|
||||||
|
}
|
||||||
|
|
||||||
|
const rulesOf = (node: Y.Map<unknown>) => {
|
||||||
|
const firewall = firewallOf(node);
|
||||||
|
let rules = firewall.get('rules') as Y.Array<Y.Map<unknown>> | undefined;
|
||||||
|
if (rules === undefined) {
|
||||||
|
rules = new Y.Array<Y.Map<unknown>>();
|
||||||
|
firewall.set('rules', rules);
|
||||||
|
}
|
||||||
|
return rules;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** The firewall switched on, dropping ICMP, or dropping only SYN segments (JFirewallDialog's general tab). */
|
||||||
|
export function setFirewallFlag(doc: Y.Doc, id: Id, flag: 'isActive' | 'dropIcmp' | 'filterSynSegmentsOnly', on: boolean, origin: unknown): void {
|
||||||
|
const node = nodes(doc).get(id);
|
||||||
|
if (node !== undefined) doc.transact(() => firewallOf(node).set(flag, on), origin);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** What the firewall does when no rule matches. */
|
||||||
|
export function setFirewallDefaultPolicy(doc: Y.Doc, id: Id, policy: FirewallAction, origin: unknown): void {
|
||||||
|
const node = nodes(doc).get(id);
|
||||||
|
if (node !== undefined) doc.transact(() => firewallOf(node).set('defaultPolicy', policy), origin);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** A new rule at the end, as Java's makes it (Firewall.addRule: every field empty, TCP, all ports, drop); its place. */
|
||||||
|
export function addFirewallRule(doc: Y.Doc, id: Id, origin: unknown): number | undefined {
|
||||||
|
const node = nodes(doc).get(id);
|
||||||
|
if (node === undefined) return undefined;
|
||||||
|
let place = 0;
|
||||||
|
doc.transact(() => {
|
||||||
|
const rules = rulesOf(node);
|
||||||
|
rules.push([toY({ ...firewallRule() }) as Y.Map<unknown>]);
|
||||||
|
place = rules.length - 1;
|
||||||
|
}, origin);
|
||||||
|
return place;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type FirewallRuleColumn = 'sourceIPAddress' | 'sourceSubnetMask' | 'destinationIPAddress' | 'destinationSubnetMask' | 'port';
|
||||||
|
export const FIREWALL_RULE_COLUMNS: readonly FirewallRuleColumn[] = ['sourceIPAddress', 'sourceSubnetMask', 'destinationIPAddress', 'destinationSubnetMask', 'port'];
|
||||||
|
|
||||||
|
/** Each text column's rule (FirewallRule.validateExistingAttributes; the marker only as a source). */
|
||||||
|
export const firewallCellRule = (column: FirewallRuleColumn): FieldRule =>
|
||||||
|
column === 'sourceIPAddress' ? 'firewallSource' : column === 'destinationIPAddress' ? 'ipAddressOrEmpty' : column === 'port' ? 'portOrEmpty' : 'firewallMask';
|
||||||
|
|
||||||
|
const ruleAt = (node: Y.Map<unknown>, index: number) => ((settingsOf(node).get('firewall') as Y.Map<unknown> | undefined)?.get('rules') as Y.Array<Y.Map<unknown>> | undefined)?.get(index);
|
||||||
|
|
||||||
|
/** A text cell of a rule; an empty port means all ports. */
|
||||||
|
export function setFirewallRuleCell(doc: Y.Doc, id: Id, index: number, column: FirewallRuleColumn, text: string, origin: unknown): CheckedField {
|
||||||
|
return setChecked(
|
||||||
|
doc,
|
||||||
|
id,
|
||||||
|
checkField(firewallCellRule(column), text),
|
||||||
|
(node, value) => ruleAt(node, index)?.set(column, column === 'port' ? (value === '' ? ALL_PORTS : Number(value)) : value),
|
||||||
|
origin,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** A rule's protocol or action (Java's lists: *, TCP, UDP; accept, drop). */
|
||||||
|
export function setFirewallRuleChoice(doc: Y.Doc, id: Id, index: number, choice: { readonly protocol: FirewallProtocol } | { readonly action: FirewallAction }, origin: unknown): void {
|
||||||
|
const node = nodes(doc).get(id);
|
||||||
|
const rule = node === undefined ? undefined : ruleAt(node, index);
|
||||||
|
if (rule === undefined) return;
|
||||||
|
doc.transact(() => ('protocol' in choice ? rule.set('protocol', choice.protocol) : rule.set('action', choice.action)), origin);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Moves a rule one place up (-1) or down (+1); the first goes no higher, the last no lower (Firewall.moveUp, moveDown). */
|
||||||
|
export function moveFirewallRule(doc: Y.Doc, id: Id, index: number, step: -1 | 1, origin: unknown): boolean {
|
||||||
|
const node = nodes(doc).get(id);
|
||||||
|
const rules = node === undefined ? undefined : ((settingsOf(node).get('firewall') as Y.Map<unknown> | undefined)?.get('rules') as Y.Array<Y.Map<unknown>> | undefined);
|
||||||
|
const target = index + step;
|
||||||
|
if (rules === undefined || index < 0 || index >= rules.length || target < 0 || target >= rules.length) return false;
|
||||||
|
doc.transact(() => {
|
||||||
|
// A Yjs type cannot be inserted twice: the rule moves as a copy of its fields.
|
||||||
|
const moved = fromY(rules.get(index)) as PlainValue;
|
||||||
|
rules.delete(index, 1);
|
||||||
|
rules.insert(target, [toY(moved) as Y.Map<unknown>]);
|
||||||
|
}, origin);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function removeFirewallRule(doc: Y.Doc, id: Id, index: number, origin: unknown): void {
|
||||||
|
const node = nodes(doc).get(id);
|
||||||
|
const rules = node === undefined ? undefined : ((settingsOf(node).get('firewall') as Y.Map<unknown> | undefined)?.get('rules') as Y.Array<Y.Map<unknown>> | undefined);
|
||||||
|
if (rules === undefined || index < 0 || index >= rules.length) return;
|
||||||
|
doc.transact(() => rules.delete(index, 1), origin);
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,7 +8,9 @@ import {
|
|||||||
defaultSwitchConfiguration,
|
defaultSwitchConfiguration,
|
||||||
DEFAULT_SWITCH_RETENTION_MILLISECONDS,
|
DEFAULT_SWITCH_RETENTION_MILLISECONDS,
|
||||||
dhcpServerConfiguration,
|
dhcpServerConfiguration,
|
||||||
|
firewallConfiguration,
|
||||||
forwardsIPv4ByDefault,
|
forwardsIPv4ByDefault,
|
||||||
|
type FirewallConfiguration,
|
||||||
parsePortForwardingRow,
|
parsePortForwardingRow,
|
||||||
routeTableRows,
|
routeTableRows,
|
||||||
type DhcpServerConfiguration,
|
type DhcpServerConfiguration,
|
||||||
@@ -90,6 +92,14 @@ export interface InterfaceSheet {
|
|||||||
readonly connected: { readonly device: string; readonly link: string } | null;
|
readonly connected: { readonly device: string; readonly link: string } | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** A router's or home router's firewall as Java's dialog shows it (JFirewallDialog). */
|
||||||
|
export type FirewallSheet = Pick<FirewallConfiguration, 'isActive' | 'dropIcmp' | 'filterSynSegmentsOnly' | 'defaultPolicy' | 'rules'>;
|
||||||
|
|
||||||
|
function firewallSheet(node: Y.Map<unknown>): FirewallSheet {
|
||||||
|
const { isActive, dropIcmp, filterSynSegmentsOnly, defaultPolicy, rules } = firewallConfiguration((fromY(settingsOf(node).get('firewall')) ?? {}) as Partial<FirewallConfiguration>);
|
||||||
|
return { isActive, dropIcmp, filterSynSegmentsOnly, defaultPolicy, rules };
|
||||||
|
}
|
||||||
|
|
||||||
export interface RouterSheet {
|
export interface RouterSheet {
|
||||||
readonly kind: 'router';
|
readonly kind: 'router';
|
||||||
readonly name: string;
|
readonly name: string;
|
||||||
@@ -100,6 +110,7 @@ export interface RouterSheet {
|
|||||||
readonly interfaces: readonly InterfaceSheet[];
|
readonly interfaces: readonly InterfaceSheet[];
|
||||||
/** The manual rows, in their order. */
|
/** The manual rows, in their order. */
|
||||||
readonly routes: readonly ManualRoute[];
|
readonly routes: readonly ManualRoute[];
|
||||||
|
readonly firewall: FirewallSheet;
|
||||||
/** The whole table as Java shows it (Weiterleitungstabelle.holeTabelle): own addresses and networks, localhost, the manual rows, the gateway. */
|
/** The whole table as Java shows it (Weiterleitungstabelle.holeTabelle): own addresses and networks, localhost, the manual rows, the gateway. */
|
||||||
readonly table: readonly RouteRow[];
|
readonly table: readonly RouteRow[];
|
||||||
}
|
}
|
||||||
@@ -114,6 +125,7 @@ export interface HomeRouterSheet {
|
|||||||
readonly wan: InterfaceSheet;
|
readonly wan: InterfaceSheet;
|
||||||
readonly lan: InterfaceSheet;
|
readonly lan: InterfaceSheet;
|
||||||
readonly dhcpServer: DhcpServerSheet;
|
readonly dhcpServer: DhcpServerSheet;
|
||||||
|
readonly firewall: FirewallSheet;
|
||||||
/** The port forwarding rows, each with whether Java's firmware forwards it (GatewayFirmware.validateData). */
|
/** The port forwarding rows, each with whether Java's firmware forwards it (GatewayFirmware.validateData). */
|
||||||
readonly portForwarding: readonly (GatewayPortForwardingRow & { readonly forwarded: boolean })[];
|
readonly portForwarding: readonly (GatewayPortForwardingRow & { readonly forwarded: boolean })[];
|
||||||
}
|
}
|
||||||
@@ -237,6 +249,7 @@ export function deviceSheet(doc: Y.Doc, id: string): DeviceSheet | undefined {
|
|||||||
interfaces,
|
interfaces,
|
||||||
routes,
|
routes,
|
||||||
table: routeTableRows(interfaces, routes, gateway),
|
table: routeTableRows(interfaces, routes, gateway),
|
||||||
|
firewall: firewallSheet(node),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
case 'gateway': {
|
case 'gateway': {
|
||||||
@@ -252,6 +265,7 @@ export function deviceSheet(doc: Y.Doc, id: string): DeviceSheet | undefined {
|
|||||||
lan: lan!,
|
lan: lan!,
|
||||||
// Java's home router hands out its LAN address as the gateway, with the LAN card's mask.
|
// Java's home router hands out its LAN address as the gateway, with the LAN card's mask.
|
||||||
dhcpServer: dhcpServerSheet(node, { subnetMask: lan!.subnetMask, gateway: lan!.ipAddress, dnsServer: '' }, true),
|
dhcpServer: dhcpServerSheet(node, { subnetMask: lan!.subnetMask, gateway: lan!.ipAddress, dnsServer: '' }, true),
|
||||||
|
firewall: firewallSheet(node),
|
||||||
portForwarding: rows.map((row) => ({ ...row, forwarded: parsePortForwardingRow(row) !== undefined })),
|
portForwarding: rows.map((row) => ({ ...row, forwarded: parsePortForwardingRow(row) !== undefined })),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1209,7 +1209,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"flows": {
|
"flows": {
|
||||||
"useMacAsNameThenOff": "26:0A:1B:EE:FE:4B",
|
"useMacAsNameThenOff": "5E:C4:F7:30:10:4A",
|
||||||
"useIpAsNameThenOff": "PC-A",
|
"useIpAsNameThenOff": "PC-A",
|
||||||
"dhcpClientStopsServer": true,
|
"dhcpClientStopsServer": true,
|
||||||
"dhcpClient": true,
|
"dhcpClient": true,
|
||||||
@@ -1674,6 +1674,615 @@
|
|||||||
false
|
false
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"firewall": {
|
||||||
|
"newRule": {
|
||||||
|
"srcIP": "",
|
||||||
|
"srcMask": "",
|
||||||
|
"destIP": "",
|
||||||
|
"destMask": "",
|
||||||
|
"port": -1,
|
||||||
|
"protocol": 6,
|
||||||
|
"action": 0
|
||||||
|
},
|
||||||
|
"newRuleShown": [
|
||||||
|
"1",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"TCP",
|
||||||
|
"",
|
||||||
|
"verwerfen"
|
||||||
|
],
|
||||||
|
"newFirewall": [
|
||||||
|
false,
|
||||||
|
0,
|
||||||
|
false,
|
||||||
|
true
|
||||||
|
],
|
||||||
|
"cells": [
|
||||||
|
{
|
||||||
|
"column": 1,
|
||||||
|
"input": "10.0.0.0",
|
||||||
|
"rule": {
|
||||||
|
"srcIP": "10.0.0.0",
|
||||||
|
"srcMask": "",
|
||||||
|
"destIP": "",
|
||||||
|
"destMask": "",
|
||||||
|
"port": -1,
|
||||||
|
"protocol": 6,
|
||||||
|
"action": 0
|
||||||
|
},
|
||||||
|
"valid": true,
|
||||||
|
"shown": [
|
||||||
|
"10.0.0.0",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"TCP",
|
||||||
|
"",
|
||||||
|
"verwerfen"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"column": 1,
|
||||||
|
"input": "abc",
|
||||||
|
"rule": {
|
||||||
|
"srcIP": "abc",
|
||||||
|
"srcMask": "",
|
||||||
|
"destIP": "",
|
||||||
|
"destMask": "",
|
||||||
|
"port": -1,
|
||||||
|
"protocol": 6,
|
||||||
|
"action": 0
|
||||||
|
},
|
||||||
|
"valid": false,
|
||||||
|
"shown": [
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"TCP",
|
||||||
|
"",
|
||||||
|
"verwerfen"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"column": 1,
|
||||||
|
"input": ".0.0.1",
|
||||||
|
"rule": {
|
||||||
|
"srcIP": ".0.0.1",
|
||||||
|
"srcMask": "",
|
||||||
|
"destIP": "",
|
||||||
|
"destMask": "",
|
||||||
|
"port": -1,
|
||||||
|
"protocol": 6,
|
||||||
|
"action": 0
|
||||||
|
},
|
||||||
|
"valid": true,
|
||||||
|
"shown": [
|
||||||
|
".0.0.1",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"TCP",
|
||||||
|
"",
|
||||||
|
"verwerfen"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"column": 1,
|
||||||
|
"input": "999.999.999.999",
|
||||||
|
"rule": {
|
||||||
|
"srcIP": "999.999.999.999",
|
||||||
|
"srcMask": "",
|
||||||
|
"destIP": "",
|
||||||
|
"destMask": "",
|
||||||
|
"port": -1,
|
||||||
|
"protocol": 6,
|
||||||
|
"action": 0
|
||||||
|
},
|
||||||
|
"valid": true,
|
||||||
|
"shown": [
|
||||||
|
"999.999.999.999",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"TCP",
|
||||||
|
"",
|
||||||
|
"verwerfen"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"column": 1,
|
||||||
|
"input": "010.0.0.0",
|
||||||
|
"rule": {
|
||||||
|
"srcIP": "010.0.0.0",
|
||||||
|
"srcMask": "",
|
||||||
|
"destIP": "",
|
||||||
|
"destMask": "",
|
||||||
|
"port": -1,
|
||||||
|
"protocol": 6,
|
||||||
|
"action": 0
|
||||||
|
},
|
||||||
|
"valid": true,
|
||||||
|
"shown": [
|
||||||
|
"010.0.0.0",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"TCP",
|
||||||
|
"",
|
||||||
|
"verwerfen"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"column": 1,
|
||||||
|
"input": " ",
|
||||||
|
"rule": {
|
||||||
|
"srcIP": " ",
|
||||||
|
"srcMask": "",
|
||||||
|
"destIP": "",
|
||||||
|
"destMask": "",
|
||||||
|
"port": -1,
|
||||||
|
"protocol": 6,
|
||||||
|
"action": 0
|
||||||
|
},
|
||||||
|
"valid": true,
|
||||||
|
"shown": [
|
||||||
|
" ",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"TCP",
|
||||||
|
"",
|
||||||
|
"verwerfen"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"column": 2,
|
||||||
|
"input": "255.255.0.0",
|
||||||
|
"rule": {
|
||||||
|
"srcIP": "",
|
||||||
|
"srcMask": "255.255.0.0",
|
||||||
|
"destIP": "",
|
||||||
|
"destMask": "",
|
||||||
|
"port": -1,
|
||||||
|
"protocol": 6,
|
||||||
|
"action": 0
|
||||||
|
},
|
||||||
|
"valid": true,
|
||||||
|
"shown": [
|
||||||
|
"",
|
||||||
|
"255.255.0.0",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"TCP",
|
||||||
|
"",
|
||||||
|
"verwerfen"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"column": 2,
|
||||||
|
"input": "255.0.255.0",
|
||||||
|
"rule": {
|
||||||
|
"srcIP": "",
|
||||||
|
"srcMask": "255.0.255.0",
|
||||||
|
"destIP": "",
|
||||||
|
"destMask": "",
|
||||||
|
"port": -1,
|
||||||
|
"protocol": 6,
|
||||||
|
"action": 0
|
||||||
|
},
|
||||||
|
"valid": false,
|
||||||
|
"shown": [
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"TCP",
|
||||||
|
"",
|
||||||
|
"verwerfen"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"column": 2,
|
||||||
|
"input": "0.0.0.0",
|
||||||
|
"rule": {
|
||||||
|
"srcIP": "",
|
||||||
|
"srcMask": "0.0.0.0",
|
||||||
|
"destIP": "",
|
||||||
|
"destMask": "",
|
||||||
|
"port": -1,
|
||||||
|
"protocol": 6,
|
||||||
|
"action": 0
|
||||||
|
},
|
||||||
|
"valid": false,
|
||||||
|
"shown": [
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"TCP",
|
||||||
|
"",
|
||||||
|
"verwerfen"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"column": 2,
|
||||||
|
"input": "255.255.255.255",
|
||||||
|
"rule": {
|
||||||
|
"srcIP": "",
|
||||||
|
"srcMask": "255.255.255.255",
|
||||||
|
"destIP": "",
|
||||||
|
"destMask": "",
|
||||||
|
"port": -1,
|
||||||
|
"protocol": 6,
|
||||||
|
"action": 0
|
||||||
|
},
|
||||||
|
"valid": true,
|
||||||
|
"shown": [
|
||||||
|
"",
|
||||||
|
"255.255.255.255",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"TCP",
|
||||||
|
"",
|
||||||
|
"verwerfen"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"column": 2,
|
||||||
|
"input": "0255.255.0.0",
|
||||||
|
"rule": {
|
||||||
|
"srcIP": "",
|
||||||
|
"srcMask": "0255.255.0.0",
|
||||||
|
"destIP": "",
|
||||||
|
"destMask": "",
|
||||||
|
"port": -1,
|
||||||
|
"protocol": 6,
|
||||||
|
"action": 0
|
||||||
|
},
|
||||||
|
"valid": true,
|
||||||
|
"shown": [
|
||||||
|
"",
|
||||||
|
"0255.255.0.0",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"TCP",
|
||||||
|
"",
|
||||||
|
"verwerfen"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"column": 3,
|
||||||
|
"input": "192.168.0.1",
|
||||||
|
"rule": {
|
||||||
|
"srcIP": "",
|
||||||
|
"srcMask": "",
|
||||||
|
"destIP": "192.168.0.1",
|
||||||
|
"destMask": "",
|
||||||
|
"port": -1,
|
||||||
|
"protocol": 6,
|
||||||
|
"action": 0
|
||||||
|
},
|
||||||
|
"valid": true,
|
||||||
|
"shown": [
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"192.168.0.1",
|
||||||
|
"",
|
||||||
|
"TCP",
|
||||||
|
"",
|
||||||
|
"verwerfen"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"column": 3,
|
||||||
|
"input": "999.999.999.999",
|
||||||
|
"rule": {
|
||||||
|
"srcIP": "",
|
||||||
|
"srcMask": "",
|
||||||
|
"destIP": "999.999.999.999",
|
||||||
|
"destMask": "",
|
||||||
|
"port": -1,
|
||||||
|
"protocol": 6,
|
||||||
|
"action": 0
|
||||||
|
},
|
||||||
|
"valid": false,
|
||||||
|
"shown": [
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"TCP",
|
||||||
|
"",
|
||||||
|
"verwerfen"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"column": 4,
|
||||||
|
"input": "255.255.255.0",
|
||||||
|
"rule": {
|
||||||
|
"srcIP": "",
|
||||||
|
"srcMask": "",
|
||||||
|
"destIP": "",
|
||||||
|
"destMask": "255.255.255.0",
|
||||||
|
"port": -1,
|
||||||
|
"protocol": 6,
|
||||||
|
"action": 0
|
||||||
|
},
|
||||||
|
"valid": true,
|
||||||
|
"shown": [
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"255.255.255.0",
|
||||||
|
"TCP",
|
||||||
|
"",
|
||||||
|
"verwerfen"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"column": 6,
|
||||||
|
"input": "80",
|
||||||
|
"rule": {
|
||||||
|
"srcIP": "",
|
||||||
|
"srcMask": "",
|
||||||
|
"destIP": "",
|
||||||
|
"destMask": "",
|
||||||
|
"port": 80,
|
||||||
|
"protocol": 6,
|
||||||
|
"action": 0
|
||||||
|
},
|
||||||
|
"valid": true,
|
||||||
|
"shown": [
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"TCP",
|
||||||
|
"80",
|
||||||
|
"verwerfen"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"column": 6,
|
||||||
|
"input": "abc",
|
||||||
|
"rule": {
|
||||||
|
"srcIP": "",
|
||||||
|
"srcMask": "",
|
||||||
|
"destIP": "",
|
||||||
|
"destMask": "",
|
||||||
|
"port": -1,
|
||||||
|
"protocol": 6,
|
||||||
|
"action": 0
|
||||||
|
},
|
||||||
|
"valid": true,
|
||||||
|
"shown": [
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"TCP",
|
||||||
|
"",
|
||||||
|
"verwerfen"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"column": 6,
|
||||||
|
"input": "0",
|
||||||
|
"rule": {
|
||||||
|
"srcIP": "",
|
||||||
|
"srcMask": "",
|
||||||
|
"destIP": "",
|
||||||
|
"destMask": "",
|
||||||
|
"port": 0,
|
||||||
|
"protocol": 6,
|
||||||
|
"action": 0
|
||||||
|
},
|
||||||
|
"valid": false,
|
||||||
|
"shown": [
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"TCP",
|
||||||
|
"",
|
||||||
|
"verwerfen"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"column": 6,
|
||||||
|
"input": "080",
|
||||||
|
"rule": {
|
||||||
|
"srcIP": "",
|
||||||
|
"srcMask": "",
|
||||||
|
"destIP": "",
|
||||||
|
"destMask": "",
|
||||||
|
"port": 80,
|
||||||
|
"protocol": 6,
|
||||||
|
"action": 0
|
||||||
|
},
|
||||||
|
"valid": true,
|
||||||
|
"shown": [
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"TCP",
|
||||||
|
"80",
|
||||||
|
"verwerfen"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"column": 6,
|
||||||
|
"input": "",
|
||||||
|
"rule": {
|
||||||
|
"srcIP": "",
|
||||||
|
"srcMask": "",
|
||||||
|
"destIP": "",
|
||||||
|
"destMask": "",
|
||||||
|
"port": -1,
|
||||||
|
"protocol": 6,
|
||||||
|
"action": 0
|
||||||
|
},
|
||||||
|
"valid": true,
|
||||||
|
"shown": [
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"TCP",
|
||||||
|
"",
|
||||||
|
"verwerfen"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"column": 6,
|
||||||
|
"input": "65536",
|
||||||
|
"rule": {
|
||||||
|
"srcIP": "",
|
||||||
|
"srcMask": "",
|
||||||
|
"destIP": "",
|
||||||
|
"destMask": "",
|
||||||
|
"port": 65536,
|
||||||
|
"protocol": 6,
|
||||||
|
"action": 0
|
||||||
|
},
|
||||||
|
"valid": false,
|
||||||
|
"shown": [
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"TCP",
|
||||||
|
"",
|
||||||
|
"verwerfen"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"column": 5,
|
||||||
|
"input": "UDP",
|
||||||
|
"rule": {
|
||||||
|
"srcIP": "",
|
||||||
|
"srcMask": "",
|
||||||
|
"destIP": "",
|
||||||
|
"destMask": "",
|
||||||
|
"port": -1,
|
||||||
|
"protocol": 17,
|
||||||
|
"action": 0
|
||||||
|
},
|
||||||
|
"valid": true,
|
||||||
|
"shown": [
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"UDP",
|
||||||
|
"",
|
||||||
|
"verwerfen"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"column": 5,
|
||||||
|
"input": "*",
|
||||||
|
"rule": {
|
||||||
|
"srcIP": "",
|
||||||
|
"srcMask": "",
|
||||||
|
"destIP": "",
|
||||||
|
"destMask": "",
|
||||||
|
"port": -1,
|
||||||
|
"protocol": -1,
|
||||||
|
"action": 0
|
||||||
|
},
|
||||||
|
"valid": true,
|
||||||
|
"shown": [
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"*",
|
||||||
|
"",
|
||||||
|
"verwerfen"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"column": 7,
|
||||||
|
"input": "#0",
|
||||||
|
"rule": {
|
||||||
|
"srcIP": "",
|
||||||
|
"srcMask": "",
|
||||||
|
"destIP": "",
|
||||||
|
"destMask": "",
|
||||||
|
"port": -1,
|
||||||
|
"protocol": 6,
|
||||||
|
"action": 1
|
||||||
|
},
|
||||||
|
"valid": true,
|
||||||
|
"shown": [
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"TCP",
|
||||||
|
"",
|
||||||
|
"akzeptieren"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"moves": [
|
||||||
|
[
|
||||||
|
"up",
|
||||||
|
1,
|
||||||
|
false,
|
||||||
|
[
|
||||||
|
"1.1.1.1",
|
||||||
|
"2.2.2.2",
|
||||||
|
"3.3.3.3"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"up",
|
||||||
|
3,
|
||||||
|
true,
|
||||||
|
[
|
||||||
|
"1.1.1.1",
|
||||||
|
"3.3.3.3",
|
||||||
|
"2.2.2.2"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"down",
|
||||||
|
3,
|
||||||
|
false,
|
||||||
|
[
|
||||||
|
"1.1.1.1",
|
||||||
|
"3.3.3.3",
|
||||||
|
"2.2.2.2"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"down",
|
||||||
|
1,
|
||||||
|
true,
|
||||||
|
[
|
||||||
|
"3.3.3.3",
|
||||||
|
"1.1.1.1",
|
||||||
|
"2.2.2.2"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"delete",
|
||||||
|
2,
|
||||||
|
null,
|
||||||
|
[
|
||||||
|
"3.3.3.3",
|
||||||
|
"2.2.2.2"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
"edits": {
|
"edits": {
|
||||||
"invalidCell": {
|
"invalidCell": {
|
||||||
"manual": [
|
"manual": [
|
||||||
|
|||||||
@@ -31,6 +31,14 @@ import {
|
|||||||
setSwitchRetention,
|
setSwitchRetention,
|
||||||
setSwitchSsid,
|
setSwitchSsid,
|
||||||
addRoute,
|
addRoute,
|
||||||
|
addFirewallRule,
|
||||||
|
moveFirewallRule,
|
||||||
|
removeFirewallRule,
|
||||||
|
setFirewallDefaultPolicy,
|
||||||
|
setFirewallFlag,
|
||||||
|
setFirewallRuleCell,
|
||||||
|
setFirewallRuleChoice,
|
||||||
|
type FirewallRuleColumn,
|
||||||
addDhcpStaticAssignment,
|
addDhcpStaticAssignment,
|
||||||
addPortForwarding,
|
addPortForwarding,
|
||||||
removeDhcpStaticAssignment,
|
removeDhcpStaticAssignment,
|
||||||
@@ -90,7 +98,23 @@ const java = JSON.parse(readFileSync(join(dirname(fileURLToPath(import.meta.url)
|
|||||||
readonly addable: readonly boolean[];
|
readonly addable: readonly boolean[];
|
||||||
};
|
};
|
||||||
readonly forwarding: { readonly rows: readonly (readonly [string, string, string, string])[]; readonly valid: readonly boolean[] };
|
readonly forwarding: { readonly rows: readonly (readonly [string, string, string, string])[]; readonly valid: readonly boolean[] };
|
||||||
|
readonly firewall: {
|
||||||
|
readonly newRule: JavaFirewallRule;
|
||||||
|
readonly newFirewall: readonly [boolean, number, boolean, boolean];
|
||||||
|
readonly cells: readonly { readonly column: number; readonly input: string; readonly rule: JavaFirewallRule; readonly valid: boolean }[];
|
||||||
|
readonly moves: readonly (readonly [string, number, boolean | null, readonly string[]])[];
|
||||||
};
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
interface JavaFirewallRule {
|
||||||
|
readonly srcIP: string;
|
||||||
|
readonly srcMask: string;
|
||||||
|
readonly destIP: string;
|
||||||
|
readonly destMask: string;
|
||||||
|
readonly port: number;
|
||||||
|
readonly protocol: number;
|
||||||
|
readonly action: number;
|
||||||
|
}
|
||||||
|
|
||||||
const RULES: Readonly<Record<string, FieldRule>> = {
|
const RULES: Readonly<Record<string, FieldRule>> = {
|
||||||
'host name': 'name',
|
'host name': 'name',
|
||||||
@@ -921,3 +945,147 @@ describe("a home router's port forwarding", () => {
|
|||||||
expect(reopened.portForwardingRowsByNodeId.get(again)).toEqual([JAVA_ROWS()[0]]);
|
expect(reopened.portForwardingRowsByNodeId.get(again)).toEqual([JAVA_ROWS()[0]]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("a router's firewall", () => {
|
||||||
|
const JAVA_PROTOCOLS: Readonly<Record<number, string>> = { [-1]: 'all', 1: 'icmp', 6: 'tcp', 17: 'udp' };
|
||||||
|
const COLUMNS: Readonly<Record<number, FirewallRuleColumn>> = { 1: 'sourceIPAddress', 2: 'sourceSubnetMask', 3: 'destinationIPAddress', 4: 'destinationSubnetMask', 6: 'port' };
|
||||||
|
const ruleOfJava = (rule: JavaFirewallRule) => ({
|
||||||
|
sourceIPAddress: rule.srcIP,
|
||||||
|
sourceSubnetMask: rule.srcMask,
|
||||||
|
destinationIPAddress: rule.destIP,
|
||||||
|
destinationSubnetMask: rule.destMask,
|
||||||
|
port: rule.port,
|
||||||
|
protocol: JAVA_PROTOCOLS[rule.protocol],
|
||||||
|
action: rule.action === 1 ? 'accept' : 'drop',
|
||||||
|
});
|
||||||
|
|
||||||
|
function firewalled() {
|
||||||
|
const doc = new Y.Doc();
|
||||||
|
const id = add(doc, 'router');
|
||||||
|
const rules = () => router(doc, id).firewall.rules;
|
||||||
|
return { doc, id, rules };
|
||||||
|
}
|
||||||
|
|
||||||
|
it("starts as Java's, and makes a new rule as Java's does", () => {
|
||||||
|
const { doc, id, rules } = firewalled();
|
||||||
|
const [isActive, policy, dropIcmp, filterSyn] = java.firewall.newFirewall;
|
||||||
|
expect(router(doc, id).firewall).toEqual({ isActive, defaultPolicy: policy === 1 ? 'accept' : 'drop', dropIcmp, filterSynSegmentsOnly: filterSyn, rules: [] });
|
||||||
|
expect(addFirewallRule(doc, id, 'me')).toBe(0);
|
||||||
|
expect(rules()).toEqual([ruleOfJava(java.firewall.newRule)]);
|
||||||
|
expect(addFirewallRule(doc, 'nobody', 'me')).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("takes a cell by Java's checks, never a value they refuse (Java's table keeps it in the rule)", () => {
|
||||||
|
// Java: what its rule holds after the cell is taken, and whether the rule then passes its own check.
|
||||||
|
expect(java.firewall.cells.filter((each) => each.column in COLUMNS).map((each) => [each.column, each.input, ruleOfJava(each.rule)[COLUMNS[each.column]!], each.valid])).toEqual([
|
||||||
|
[1, '10.0.0.0', '10.0.0.0', true],
|
||||||
|
[1, 'abc', 'abc', false],
|
||||||
|
[1, '.0.0.1', '.0.0.1', true],
|
||||||
|
[1, '999.999.999.999', '999.999.999.999', true],
|
||||||
|
[1, '010.0.0.0', '010.0.0.0', true],
|
||||||
|
[1, ' ', ' ', true],
|
||||||
|
[2, '255.255.0.0', '255.255.0.0', true],
|
||||||
|
[2, '255.0.255.0', '255.0.255.0', false],
|
||||||
|
[2, '0.0.0.0', '0.0.0.0', false],
|
||||||
|
[2, '255.255.255.255', '255.255.255.255', true],
|
||||||
|
[2, '0255.255.0.0', '0255.255.0.0', true],
|
||||||
|
[3, '192.168.0.1', '192.168.0.1', true],
|
||||||
|
[3, '999.999.999.999', '999.999.999.999', false],
|
||||||
|
[4, '255.255.255.0', '255.255.255.0', true],
|
||||||
|
[6, '80', 80, true],
|
||||||
|
[6, 'abc', -1, true],
|
||||||
|
[6, '0', 0, false],
|
||||||
|
[6, '080', 80, true],
|
||||||
|
[6, '', -1, true],
|
||||||
|
[6, '65536', 65_536, false],
|
||||||
|
]);
|
||||||
|
// Here: what the rule takes, or that it takes nothing. Not adopted: ".0.0.1" (no address), "abc" as all ports, "080" (musterPort).
|
||||||
|
const ours = java.firewall.cells
|
||||||
|
.filter((each) => each.column in COLUMNS)
|
||||||
|
.map((each) => {
|
||||||
|
const { doc, id, rules } = firewalled();
|
||||||
|
addFirewallRule(doc, id, 'me');
|
||||||
|
const result = setFirewallRuleCell(doc, id, 0, COLUMNS[each.column]!, each.input, 'me');
|
||||||
|
return 'invalid' in result ? 'refused' : rules()[0]![COLUMNS[each.column]!];
|
||||||
|
});
|
||||||
|
expect(ours).toEqual([
|
||||||
|
'10.0.0.0',
|
||||||
|
'refused',
|
||||||
|
'refused',
|
||||||
|
'999.999.999.999',
|
||||||
|
'10.0.0.0',
|
||||||
|
'',
|
||||||
|
'255.255.0.0',
|
||||||
|
'refused',
|
||||||
|
'refused',
|
||||||
|
'255.255.255.255',
|
||||||
|
'255.255.0.0',
|
||||||
|
'192.168.0.1',
|
||||||
|
'refused',
|
||||||
|
'255.255.255.0',
|
||||||
|
80,
|
||||||
|
'refused',
|
||||||
|
'refused',
|
||||||
|
'refused',
|
||||||
|
-1,
|
||||||
|
'refused',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("chooses a rule's protocol and action as Java's lists do", () => {
|
||||||
|
const choices = java.firewall.cells.filter((each) => !(each.column in COLUMNS)).map((each) => ruleOfJava(each.rule));
|
||||||
|
expect(choices.map((each) => [each.protocol, each.action])).toEqual([
|
||||||
|
['udp', 'drop'],
|
||||||
|
['all', 'drop'],
|
||||||
|
['tcp', 'accept'],
|
||||||
|
]);
|
||||||
|
const { doc, id, rules } = firewalled();
|
||||||
|
addFirewallRule(doc, id, 'me');
|
||||||
|
setFirewallRuleChoice(doc, id, 0, { protocol: 'udp' }, 'me');
|
||||||
|
setFirewallRuleChoice(doc, id, 0, { action: 'accept' }, 'me');
|
||||||
|
expect(rules()[0]).toMatchObject({ protocol: 'udp', action: 'accept' });
|
||||||
|
setFirewallRuleChoice(doc, id, 5, { protocol: 'all' }, 'me');
|
||||||
|
expect(rules()).toHaveLength(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("moves and removes rules as Java's buttons do, the first going no higher and the last no lower", () => {
|
||||||
|
const { doc, id, rules } = firewalled();
|
||||||
|
for (const source of ['1.1.1.1', '2.2.2.2', '3.3.3.3']) setFirewallRuleCell(doc, id, addFirewallRule(doc, id, 'me')!, 'sourceIPAddress', source, 'me');
|
||||||
|
// Java's buttons pass the row's number (1 for the first); here its place.
|
||||||
|
const ours = java.firewall.moves.map(([step, number]) => {
|
||||||
|
const answer = step === 'delete' ? (removeFirewallRule(doc, id, number - 1, 'me'), null) : moveFirewallRule(doc, id, number - 1, step === 'up' ? -1 : 1, 'me');
|
||||||
|
return [step, number, answer, rules().map((rule) => rule.sourceIPAddress)];
|
||||||
|
});
|
||||||
|
expect(ours).toEqual(java.firewall.moves);
|
||||||
|
expect(moveFirewallRule(doc, id, 7, 1, 'me')).toBe(false);
|
||||||
|
removeFirewallRule(doc, id, 7, 'me');
|
||||||
|
expect(rules()).toHaveLength(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('switches its flags and its default, and keeps all through a saved file', () => {
|
||||||
|
const { doc, id } = firewalled();
|
||||||
|
const homeId = add(doc, 'gateway');
|
||||||
|
for (const device of [id, homeId]) {
|
||||||
|
setFirewallFlag(doc, device, 'isActive', true, 'me');
|
||||||
|
setFirewallFlag(doc, device, 'dropIcmp', true, 'me');
|
||||||
|
setFirewallFlag(doc, device, 'filterSynSegmentsOnly', false, 'me');
|
||||||
|
setFirewallDefaultPolicy(doc, device, 'accept', 'me');
|
||||||
|
addFirewallRule(doc, device, 'me');
|
||||||
|
setFirewallRuleCell(doc, device, 0, 'destinationIPAddress', '10.0.0.0', 'me');
|
||||||
|
setFirewallRuleCell(doc, device, 0, 'destinationSubnetMask', '255.0.0.0', 'me');
|
||||||
|
setFirewallRuleCell(doc, device, 0, 'port', '80', 'me');
|
||||||
|
}
|
||||||
|
expect(home(doc, homeId).firewall).toMatchObject({ isActive: true, dropIcmp: true, filterSynSegmentsOnly: false, defaultPolicy: 'accept' });
|
||||||
|
let counter = 0;
|
||||||
|
const reopened = openProjectArchive(saveProjectArchive(readProject(doc)).archive, () => `60000000-0000-0000-0000-${(++counter).toString(16).padStart(12, '0')}`);
|
||||||
|
for (const node of reopened.graph.nodes) {
|
||||||
|
expect(reopened.firewallConfigurationsByNodeId.get(node.id)).toMatchObject({
|
||||||
|
isActive: true,
|
||||||
|
dropIcmp: true,
|
||||||
|
filterSynSegmentsOnly: false,
|
||||||
|
defaultPolicy: 'accept',
|
||||||
|
rules: [{ sourceIPAddress: '', destinationIPAddress: '10.0.0.0', destinationSubnetMask: '255.0.0.0', port: 80, protocol: 'tcp', action: 'drop' }],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -348,6 +348,66 @@ test("keeps a home router's port forwarding on its own page, and shows its DHCP
|
|||||||
await expect(sheet(page).getByRole('tab', { name: 'LAN: 192.168.0.10' })).toHaveAttribute('aria-selected', 'true');
|
await expect(sheet(page).getByRole('tab', { name: 'LAN: 192.168.0.10' })).toHaveAttribute('aria-selected', 'true');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("sets up a router's firewall on its own page: the switches, the default, rules edited, moved and removed", async ({ page }) => {
|
||||||
|
await openEditor(page, server.base, 'new');
|
||||||
|
await page.locator('[data-palette="router"]').click();
|
||||||
|
await page.getByRole('dialog').getByRole('button', { name: '2', exact: true }).click();
|
||||||
|
const id = (await drawing(page)).nodes[0]!.id;
|
||||||
|
const firewall = async () => ((await sheetOf(page, id)) as RouterSheet).firewall;
|
||||||
|
await sheet(page).getByRole('button', { name: 'Firewall einrichten' }).click();
|
||||||
|
await expect(sheet(page).getByRole('heading')).toHaveText('Firewall einrichten');
|
||||||
|
await expect(sheet(page).getByRole('tab')).toHaveText(['Allgemein', 'Firewall-Regeln']);
|
||||||
|
await sheet(page).getByLabel('Firewall aktivieren').check();
|
||||||
|
await sheet(page).getByLabel('nur SYN-Pakete verwerfen').uncheck();
|
||||||
|
await expect.poll(firewall).toMatchObject({ isActive: true, filterSynSegmentsOnly: false });
|
||||||
|
|
||||||
|
await sheet(page).getByRole('tab', { name: 'Firewall-Regeln' }).click();
|
||||||
|
await sheet(page).locator('select[name="defaultPolicy"]').selectOption('accept');
|
||||||
|
await sheet(page).getByRole('button', { name: 'Neue Regel' }).click();
|
||||||
|
const rows = sheet(page).locator('table[data-rows="firewall"] tbody tr');
|
||||||
|
await expect(rows.first().locator('td')).toHaveText(['1', '', '', '', '', 'TCP', '', 'verwerfen']);
|
||||||
|
await expect(sheet(page).getByRole('button', { name: 'rauf' })).toBeDisabled();
|
||||||
|
await field(page, 'firewall-sourceIPAddress').fill('abc');
|
||||||
|
await field(page, 'firewall-sourceIPAddress').press('Enter');
|
||||||
|
await expect(sheet(page).getByRole('status')).toHaveText('Nicht übernommen – es gilt weiterhin (leer).');
|
||||||
|
await field(page, 'firewall-sourceIPAddress').fill('10.0.0.0');
|
||||||
|
await field(page, 'firewall-sourceIPAddress').press('Enter');
|
||||||
|
await field(page, 'firewall-sourceSubnetMask').fill('255.0.255.0');
|
||||||
|
await field(page, 'firewall-sourceSubnetMask').press('Enter');
|
||||||
|
await expect(field(page, 'firewall-sourceSubnetMask')).toHaveAttribute('aria-invalid', 'true');
|
||||||
|
await field(page, 'firewall-sourceSubnetMask').fill('255.0.0.0');
|
||||||
|
await field(page, 'firewall-sourceSubnetMask').press('Enter');
|
||||||
|
await field(page, 'firewall-port').fill('80');
|
||||||
|
await field(page, 'firewall-port').press('Enter');
|
||||||
|
await sheet(page).locator('select[name="firewall-protocol"]').selectOption('udp');
|
||||||
|
await sheet(page).locator('select[name="firewall-action"]').selectOption('accept');
|
||||||
|
await expect(rows.first().locator('td')).toHaveText(['1', '10.0.0.0', '255.0.0.0', '', '', 'UDP', '80', 'akzeptieren']);
|
||||||
|
|
||||||
|
await sheet(page).getByRole('button', { name: 'Neue Regel' }).click();
|
||||||
|
await field(page, 'firewall-destinationIPAddress').fill('192.168.0.1');
|
||||||
|
await field(page, 'firewall-destinationIPAddress').press('Enter');
|
||||||
|
await expect(sheet(page).getByRole('button', { name: 'runter' })).toBeDisabled();
|
||||||
|
await sheet(page).getByRole('button', { name: 'rauf' }).click();
|
||||||
|
await expect.poll(async () => (await firewall()).rules.map((rule) => rule.destinationIPAddress)).toEqual(['192.168.0.1', '']);
|
||||||
|
await expect(rows.first()).toHaveAttribute('aria-selected', 'true');
|
||||||
|
await sheet(page).getByRole('button', { name: 'Regel löschen' }).click();
|
||||||
|
await expect.poll(async () => (await firewall()).rules.map((rule) => rule.sourceIPAddress)).toEqual(['10.0.0.0']);
|
||||||
|
// Java keeps a row chosen: the one that took the removed one's place.
|
||||||
|
await expect(rows.first()).toHaveAttribute('aria-selected', 'true');
|
||||||
|
await expect(field(page, 'firewall-sourceIPAddress')).toHaveValue('10.0.0.0');
|
||||||
|
await expect(await firewall()).toMatchObject({ defaultPolicy: 'accept' });
|
||||||
|
await sheet(page).getByRole('button', { name: 'Zurück' }).click();
|
||||||
|
await expect(sheet(page).getByRole('heading')).toHaveText('Vermittlungsrechner');
|
||||||
|
});
|
||||||
|
|
||||||
|
test("opens a home router's firewall from its general tab", async ({ page }) => {
|
||||||
|
await openEditor(page, server.base, 'new');
|
||||||
|
await page.locator('[data-palette="gateway"]').click();
|
||||||
|
await sheet(page).getByRole('button', { name: 'Firewall einrichten' }).click();
|
||||||
|
await expect(sheet(page).getByRole('heading')).toHaveText('Firewall einrichten');
|
||||||
|
await expect(sheet(page).getByLabel('Firewall aktivieren')).not.toBeChecked();
|
||||||
|
});
|
||||||
|
|
||||||
test.describe('on a phone', () => {
|
test.describe('on a phone', () => {
|
||||||
test.use({ viewport: { width: 390, height: 844 }, hasTouch: true });
|
test.use({ viewport: { width: 390, height: 844 }, hasTouch: true });
|
||||||
|
|
||||||
|
|||||||
@@ -42,6 +42,29 @@
|
|||||||
"dhcp.static": "Statische Adresszuweisung",
|
"dhcp.static": "Statische Adresszuweisung",
|
||||||
"dhcp.subnetMask": "Netzmaske",
|
"dhcp.subnetMask": "Netzmaske",
|
||||||
"dhcp.upperBound": "Adress-Obergrenze",
|
"dhcp.upperBound": "Adress-Obergrenze",
|
||||||
|
"firewall.accept": "akzeptieren",
|
||||||
|
"firewall.action": "Aktion",
|
||||||
|
"firewall.active": "Firewall aktivieren",
|
||||||
|
"firewall.activeHint": "Erst durch Aktivieren der Firewall werden die Firewall-Regeln angewendet. Bei deaktivierter Firewall findet keine Filterung statt.",
|
||||||
|
"firewall.add": "Neue Regel",
|
||||||
|
"firewall.defaultPolicy": "Standardaktion, wenn keine Regel greift",
|
||||||
|
"firewall.delete": "Regel löschen",
|
||||||
|
"firewall.destinationIPAddress": "Ziel-IP",
|
||||||
|
"firewall.destinationSubnetMask": "Netzmaske",
|
||||||
|
"firewall.down": "runter",
|
||||||
|
"firewall.drop": "verwerfen",
|
||||||
|
"firewall.dropIcmp": "ICMP-Pakete filtern",
|
||||||
|
"firewall.dropIcmpHint": "Alle ICMP Pakete, z.B. Ping-Anfragen, werden von der Firewall verworfen.",
|
||||||
|
"firewall.filterSyn": "nur SYN-Pakete verwerfen",
|
||||||
|
"firewall.filterSynHint": "Neue Verbindungsanfragen zu gesperrten Ports werden verworfen.\nDer Rückkanal für erlaubte Verbindungsanfragen wird akzeptiert.",
|
||||||
|
"firewall.intro": "Die folgenden Regeln werden von oben nach unten durchlaufen. Sobald eine Regel greift, wird die gewählte Aktion ausgeführt.\nAlle Angaben (außer Protokoll und Aktion) in einer Regel sind optional. Leere Felder werden zur Filterung nicht ausgewertet.\nWenn eine IP-Adresse angegeben wird, muss auch die Netzwerkmaske angegeben sein! Mit einer Maske ungleich 255.255.255.255 lassen sich ganze Netzbereiche adressieren.\nÜber die Standard-Aktion kann zwischen Blacklisting- und Whitelisting-Ansatz gewechselt werden.",
|
||||||
|
"firewall.number": "ID",
|
||||||
|
"firewall.port": "Port",
|
||||||
|
"firewall.protocol": "Protokoll",
|
||||||
|
"firewall.rules": "Firewall-Regeln",
|
||||||
|
"firewall.sourceIPAddress": "Quell-IP",
|
||||||
|
"firewall.sourceSubnetMask": "Netzmaske",
|
||||||
|
"firewall.up": "rauf",
|
||||||
"forwarding.lanIPAddress": "LAN-Adresse",
|
"forwarding.lanIPAddress": "LAN-Adresse",
|
||||||
"forwarding.lanPortValue": "LAN-Port",
|
"forwarding.lanPortValue": "LAN-Port",
|
||||||
"forwarding.protocolValue": "Protokoll",
|
"forwarding.protocolValue": "Protokoll",
|
||||||
@@ -71,6 +94,7 @@
|
|||||||
"sheet.dhcpServer": "DHCP-Server einrichten",
|
"sheet.dhcpServer": "DHCP-Server einrichten",
|
||||||
"sheet.dnsServer": "Domain Name Server",
|
"sheet.dnsServer": "Domain Name Server",
|
||||||
"sheet.empty": "(leer)",
|
"sheet.empty": "(leer)",
|
||||||
|
"sheet.firewall": "Firewall einrichten",
|
||||||
"sheet.forwardingTable": "Weiterleitungstabelle",
|
"sheet.forwardingTable": "Weiterleitungstabelle",
|
||||||
"sheet.general": "Allgemein",
|
"sheet.general": "Allgemein",
|
||||||
"sheet.invalidValue": "Dieser Wert ist ungültig.",
|
"sheet.invalidValue": "Dieser Wert ist ungültig.",
|
||||||
|
|||||||
@@ -42,6 +42,29 @@
|
|||||||
"dhcp.static": "Static Address Assignment",
|
"dhcp.static": "Static Address Assignment",
|
||||||
"dhcp.subnetMask": "Netmask",
|
"dhcp.subnetMask": "Netmask",
|
||||||
"dhcp.upperBound": "Upper bound of address",
|
"dhcp.upperBound": "Upper bound of address",
|
||||||
|
"firewall.accept": "accept",
|
||||||
|
"firewall.action": "Action",
|
||||||
|
"firewall.active": "Activate firewall",
|
||||||
|
"firewall.activeHint": "Firewall rules will only be applied if the firewall is activated. In the case of a not activated firewall no packet filtering will take place.",
|
||||||
|
"firewall.add": "add new rule",
|
||||||
|
"firewall.defaultPolicy": "Default action, if no rule matches",
|
||||||
|
"firewall.delete": "delete rule",
|
||||||
|
"firewall.destinationIPAddress": "Dest. IP",
|
||||||
|
"firewall.destinationSubnetMask": "Netmask",
|
||||||
|
"firewall.down": "move down",
|
||||||
|
"firewall.drop": "drop",
|
||||||
|
"firewall.dropIcmp": "Filter ICMP packets",
|
||||||
|
"firewall.dropIcmpHint": "All ICMP packets, e.g., ping requests, will be dropped.",
|
||||||
|
"firewall.filterSyn": "only drop SYN packets",
|
||||||
|
"firewall.filterSynHint": "New connection requests to blocked ports will be dropped.\nBack channels for allowed connection requests will be accepted.",
|
||||||
|
"firewall.intro": "The following rules are evaluated from top to bottom. For the first matching rule the given action will be used.\nAll fields (except protocol and action) are optional. Empty fields will not be considered for packet filtering.\nIf an IP address is used, then the subnet mask has to be filled in as well! A subnet mask different to 255.255.255.255 can be used to address whole subnet ranges.\nUsing the default policy switch the approach can be toggled from blacklisting to whitelisting and vice versa.",
|
||||||
|
"firewall.number": "ID",
|
||||||
|
"firewall.port": "Port",
|
||||||
|
"firewall.protocol": "Protocol",
|
||||||
|
"firewall.rules": "Firewall Rules",
|
||||||
|
"firewall.sourceIPAddress": "Source IP",
|
||||||
|
"firewall.sourceSubnetMask": "Netmask",
|
||||||
|
"firewall.up": "move up",
|
||||||
"forwarding.lanIPAddress": "LAN Address",
|
"forwarding.lanIPAddress": "LAN Address",
|
||||||
"forwarding.lanPortValue": "LAN Port",
|
"forwarding.lanPortValue": "LAN Port",
|
||||||
"forwarding.protocolValue": "Protocol",
|
"forwarding.protocolValue": "Protocol",
|
||||||
@@ -71,6 +94,7 @@
|
|||||||
"sheet.dhcpServer": "DHCP server setup",
|
"sheet.dhcpServer": "DHCP server setup",
|
||||||
"sheet.dnsServer": "Domain Name Server",
|
"sheet.dnsServer": "Domain Name Server",
|
||||||
"sheet.empty": "(empty)",
|
"sheet.empty": "(empty)",
|
||||||
|
"sheet.firewall": "Firewall settings",
|
||||||
"sheet.forwardingTable": "Forwarding table",
|
"sheet.forwardingTable": "Forwarding table",
|
||||||
"sheet.general": "General",
|
"sheet.general": "General",
|
||||||
"sheet.invalidValue": "This value is invalid.",
|
"sheet.invalidValue": "This value is invalid.",
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import { ICON_URLS } from '../canvas/icons.js';
|
|||||||
import { useTranslate } from '../i18n/i18n.js';
|
import { useTranslate } from '../i18n/i18n.js';
|
||||||
import { NameField, type Change } from './NameField.js';
|
import { NameField, type Change } from './NameField.js';
|
||||||
import { HomeRouterFields, RouterFields } from './RouterSheets.js';
|
import { HomeRouterFields, RouterFields } from './RouterSheets.js';
|
||||||
|
import { FirewallPanel } from './FirewallPanel.js';
|
||||||
import { DhcpServerPanel, PortForwardingPanel } from './ServicePanels.js';
|
import { DhcpServerPanel, PortForwardingPanel } from './ServicePanels.js';
|
||||||
import { SheetCheck, SheetField } from './SheetField.js';
|
import { SheetCheck, SheetField } from './SheetField.js';
|
||||||
import './sheet.css';
|
import './sheet.css';
|
||||||
@@ -61,7 +62,7 @@ export interface DeviceSheetProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** A page of the sheet that Java opens as a dialog. */
|
/** A page of the sheet that Java opens as a dialog. */
|
||||||
export type SheetPage = 'dhcp' | 'forwarding';
|
export type SheetPage = 'dhcp' | 'forwarding' | 'firewall';
|
||||||
|
|
||||||
export function DeviceSheet({ doc, id, label, origin, onChanged, onMessage, onClose, onHighlight }: DeviceSheetProps) {
|
export function DeviceSheet({ doc, id, label, origin, onChanged, onMessage, onClose, onHighlight }: DeviceSheetProps) {
|
||||||
const t = useTranslate();
|
const t = useTranslate();
|
||||||
@@ -91,7 +92,7 @@ export function DeviceSheet({ doc, id, label, origin, onChanged, onMessage, onCl
|
|||||||
‹
|
‹
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
<h2>{open === 'dhcp' ? t('sheet.dhcpServer') : open === 'forwarding' ? t('sheet.portForwarding') : title}</h2>
|
<h2>{open === 'dhcp' ? t('sheet.dhcpServer') : open === 'forwarding' ? t('sheet.portForwarding') : open === 'firewall' ? t('sheet.firewall') : title}</h2>
|
||||||
<button type="button" className="sheet-close" onClick={onClose} aria-label={t('sheet.close')} title={t('sheet.close')}>
|
<button type="button" className="sheet-close" onClick={onClose} aria-label={t('sheet.close')} title={t('sheet.close')}>
|
||||||
×
|
×
|
||||||
</button>
|
</button>
|
||||||
@@ -105,7 +106,7 @@ export function DeviceSheet({ doc, id, label, origin, onChanged, onMessage, onCl
|
|||||||
) : sheet.kind === 'remoteLink' ? (
|
) : sheet.kind === 'remoteLink' ? (
|
||||||
<ModemFields sheet={sheet} change={change} />
|
<ModemFields sheet={sheet} change={change} />
|
||||||
) : sheet.kind === 'router' ? (
|
) : sheet.kind === 'router' ? (
|
||||||
<RouterFields sheet={sheet} change={change} onHighlight={onHighlight} />
|
<RouterFields sheet={sheet} change={change} onHighlight={onHighlight} onOpen={onOpen} />
|
||||||
) : sheet.kind === 'gateway' ? (
|
) : sheet.kind === 'gateway' ? (
|
||||||
<HomeRouterFields sheet={sheet} change={change} onHighlight={onHighlight} onOpen={onOpen} />
|
<HomeRouterFields sheet={sheet} change={change} onHighlight={onHighlight} onOpen={onOpen} />
|
||||||
) : (
|
) : (
|
||||||
@@ -116,6 +117,10 @@ export function DeviceSheet({ doc, id, label, origin, onChanged, onMessage, onCl
|
|||||||
<div key={`${id}-dhcp`}>
|
<div key={`${id}-dhcp`}>
|
||||||
<DhcpServerPanel server={sheet.dhcpServer} change={change} />
|
<DhcpServerPanel server={sheet.dhcpServer} change={change} />
|
||||||
</div>
|
</div>
|
||||||
|
) : open === 'firewall' && (sheet.kind === 'router' || sheet.kind === 'gateway') ? (
|
||||||
|
<div key={`${id}-firewall`}>
|
||||||
|
<FirewallPanel firewall={sheet.firewall} change={change} />
|
||||||
|
</div>
|
||||||
) : open === 'forwarding' && sheet.kind === 'gateway' ? (
|
) : open === 'forwarding' && sheet.kind === 'gateway' ? (
|
||||||
<div key={`${id}-forwarding`}>
|
<div key={`${id}-forwarding`}>
|
||||||
<PortForwardingPanel sheet={sheet} change={change} />
|
<PortForwardingPanel sheet={sheet} change={change} />
|
||||||
|
|||||||
@@ -0,0 +1,193 @@
|
|||||||
|
// A router's or home router's firewall as a page of the device sheet, after
|
||||||
|
// Java's dialog (JFirewallDialog): a general tab with the three switches and
|
||||||
|
// their explanations, a tab with the default action and the rules — a rule
|
||||||
|
// chosen to edit its cells, moved up or down, a new rule at the end as
|
||||||
|
// Java's (TCP, all ports, drop), a rule removed. Java's first tab says
|
||||||
|
// "network interfaces" though it holds the general switches; here it says so.
|
||||||
|
|
||||||
|
import { useState } from 'react';
|
||||||
|
import {
|
||||||
|
addFirewallRule,
|
||||||
|
FIREWALL_RULE_COLUMNS,
|
||||||
|
firewallCellRule,
|
||||||
|
moveFirewallRule,
|
||||||
|
removeFirewallRule,
|
||||||
|
setFirewallDefaultPolicy,
|
||||||
|
setFirewallFlag,
|
||||||
|
setFirewallRuleCell,
|
||||||
|
setFirewallRuleChoice,
|
||||||
|
checkField,
|
||||||
|
type FirewallSheet,
|
||||||
|
} from '@mdg-filius/doc';
|
||||||
|
import type { FirewallAction, FirewallProtocol, FirewallRule } from '@mdg-filius/core';
|
||||||
|
import { useTranslate } from '../i18n/i18n.js';
|
||||||
|
import type { Change } from './NameField.js';
|
||||||
|
import { SheetCheck, SheetField } from './SheetField.js';
|
||||||
|
import { SheetTabs } from './SheetTabs.js';
|
||||||
|
|
||||||
|
/** Java's protocol names in the rule table (ruleAsVector): "*" for all. */
|
||||||
|
const PROTOCOL_NAMES: Readonly<Record<FirewallProtocol, string>> = { all: '*', tcp: 'TCP', udp: 'UDP', icmp: 'ICMP' };
|
||||||
|
/** Java's list offers all, TCP and UDP; ICMP comes only from a file, and is offered where a rule has it. */
|
||||||
|
const OFFERED: readonly FirewallProtocol[] = ['all', 'tcp', 'udp'];
|
||||||
|
|
||||||
|
const cellText = (rule: FirewallRule, column: (typeof FIREWALL_RULE_COLUMNS)[number]) => (column === 'port' ? (rule.port < 0 ? '' : String(rule.port)) : rule[column]);
|
||||||
|
|
||||||
|
/** A text with Java's line breaks as paragraphs. */
|
||||||
|
function Note({ text }: { readonly text: string }) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{text.split('\n').map((line, index) => (
|
||||||
|
<p key={index} className="sheet-note">
|
||||||
|
{line}
|
||||||
|
</p>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function FirewallPanel({ firewall, change }: { readonly firewall: FirewallSheet; readonly change: Change }) {
|
||||||
|
const t = useTranslate();
|
||||||
|
const [tab, setTab] = useState('general');
|
||||||
|
const [chosen, setChosen] = useState<number | null>(null);
|
||||||
|
const rule = chosen === null ? undefined : firewall.rules[chosen];
|
||||||
|
const flag = (name: 'isActive' | 'dropIcmp' | 'filterSynSegmentsOnly') => (on: boolean) => change.run(() => setFirewallFlag(change.doc, change.id, name, on, change.origin));
|
||||||
|
const move = (step: -1 | 1) => {
|
||||||
|
if (chosen !== null && change.run(() => moveFirewallRule(change.doc, change.id, chosen, step, change.origin))) setChosen(chosen + step);
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<SheetTabs
|
||||||
|
tabs={[
|
||||||
|
{ key: 'general', label: t('sheet.general') },
|
||||||
|
{ key: 'rules', label: t('firewall.rules') },
|
||||||
|
]}
|
||||||
|
selected={tab}
|
||||||
|
onSelect={setTab}
|
||||||
|
/>
|
||||||
|
<div role="tabpanel" key={tab}>
|
||||||
|
{tab === 'general' ? (
|
||||||
|
<>
|
||||||
|
<SheetCheck name="firewallActive" label={t('firewall.active')} checked={firewall.isActive} onChange={flag('isActive')} />
|
||||||
|
<Note text={t('firewall.activeHint')} />
|
||||||
|
<SheetCheck name="dropIcmp" label={t('firewall.dropIcmp')} checked={firewall.dropIcmp} onChange={flag('dropIcmp')} />
|
||||||
|
<Note text={t('firewall.dropIcmpHint')} />
|
||||||
|
<SheetCheck name="filterSyn" label={t('firewall.filterSyn')} checked={firewall.filterSynSegmentsOnly} onChange={flag('filterSynSegmentsOnly')} />
|
||||||
|
<Note text={t('firewall.filterSynHint')} />
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Note text={t('firewall.intro')} />
|
||||||
|
<label className="sheet-field">
|
||||||
|
<span>{t('firewall.defaultPolicy')}</span>
|
||||||
|
<select
|
||||||
|
name="defaultPolicy"
|
||||||
|
value={firewall.defaultPolicy}
|
||||||
|
onChange={(event) => change.run(() => setFirewallDefaultPolicy(change.doc, change.id, event.target.value as FirewallAction, change.origin))}
|
||||||
|
>
|
||||||
|
<option value="accept">{t('firewall.accept')}</option>
|
||||||
|
<option value="drop">{t('firewall.drop')}</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
<div className="route-table-frame">
|
||||||
|
<table className="route-table" data-rows="firewall">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col">{t('firewall.number')}</th>
|
||||||
|
{FIREWALL_RULE_COLUMNS.slice(0, 4).map((column) => (
|
||||||
|
<th key={column} scope="col">
|
||||||
|
{t(`firewall.${column}`)}
|
||||||
|
</th>
|
||||||
|
))}
|
||||||
|
<th scope="col">{t('firewall.protocol')}</th>
|
||||||
|
<th scope="col">{t('firewall.port')}</th>
|
||||||
|
<th scope="col">{t('firewall.action')}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{firewall.rules.map((each, index) => (
|
||||||
|
<tr key={index} data-manual={String(index)} aria-selected={chosen === index} onClick={() => setChosen(index)}>
|
||||||
|
<td>{index + 1}</td>
|
||||||
|
{FIREWALL_RULE_COLUMNS.slice(0, 4).map((column) => (
|
||||||
|
// A cell its rule refuses (from a file) is marked, not mended.
|
||||||
|
<td key={column} data-invalid={'invalid' in checkField(firewallCellRule(column), cellText(each, column)) ? 'true' : undefined}>
|
||||||
|
{cellText(each, column)}
|
||||||
|
</td>
|
||||||
|
))}
|
||||||
|
<td>{PROTOCOL_NAMES[each.protocol]}</td>
|
||||||
|
<td data-invalid={'invalid' in checkField('portOrEmpty', cellText(each, 'port')) ? 'true' : undefined}>{cellText(each, 'port')}</td>
|
||||||
|
<td>{t(each.action === 'accept' ? 'firewall.accept' : 'firewall.drop')}</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div className="sheet-buttons">
|
||||||
|
<button type="button" disabled={chosen === null || chosen === 0} onClick={() => move(-1)}>
|
||||||
|
{t('firewall.up')}
|
||||||
|
</button>
|
||||||
|
<button type="button" disabled={chosen === null || chosen === firewall.rules.length - 1} onClick={() => move(1)}>
|
||||||
|
{t('firewall.down')}
|
||||||
|
</button>
|
||||||
|
<button type="button" onClick={() => setChosen(change.run(() => addFirewallRule(change.doc, change.id, change.origin)) ?? null)}>
|
||||||
|
{t('firewall.add')}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
disabled={chosen === null}
|
||||||
|
onClick={() => {
|
||||||
|
if (chosen === null) return;
|
||||||
|
change.run(() => removeFirewallRule(change.doc, change.id, chosen, change.origin));
|
||||||
|
// Java keeps the row chosen, or the last one when the last went.
|
||||||
|
const left = firewall.rules.length - 1;
|
||||||
|
setChosen(left === 0 ? null : Math.min(chosen, left - 1));
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('firewall.delete')}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{rule === undefined ? null : (
|
||||||
|
<fieldset className="sheet-group" key={`rule-${String(chosen)}`}>
|
||||||
|
{FIREWALL_RULE_COLUMNS.map((column) => (
|
||||||
|
<SheetField
|
||||||
|
key={column}
|
||||||
|
name={`firewall-${column}`}
|
||||||
|
label={t(`firewall.${column}`)}
|
||||||
|
value={cellText(rule, column)}
|
||||||
|
rule={firewallCellRule(column)}
|
||||||
|
inputMode={column === 'port' ? 'numeric' : 'decimal'}
|
||||||
|
onCommit={(text) => change.run(() => setFirewallRuleCell(change.doc, change.id, chosen!, column, text, change.origin))}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
<label className="sheet-field">
|
||||||
|
<span>{t('firewall.protocol')}</span>
|
||||||
|
<select
|
||||||
|
name="firewall-protocol"
|
||||||
|
value={rule.protocol}
|
||||||
|
onChange={(event) => change.run(() => setFirewallRuleChoice(change.doc, change.id, chosen!, { protocol: event.target.value as FirewallProtocol }, change.origin))}
|
||||||
|
>
|
||||||
|
{(OFFERED.includes(rule.protocol) ? OFFERED : [...OFFERED, rule.protocol]).map((protocol) => (
|
||||||
|
<option key={protocol} value={protocol}>
|
||||||
|
{PROTOCOL_NAMES[protocol]}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
<label className="sheet-field">
|
||||||
|
<span>{t('firewall.action')}</span>
|
||||||
|
<select
|
||||||
|
name="firewall-action"
|
||||||
|
value={rule.action}
|
||||||
|
onChange={(event) => change.run(() => setFirewallRuleChoice(change.doc, change.id, chosen!, { action: event.target.value as FirewallAction }, change.origin))}
|
||||||
|
>
|
||||||
|
<option value="accept">{t('firewall.accept')}</option>
|
||||||
|
<option value="drop">{t('firewall.drop')}</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
</fieldset>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -65,7 +65,17 @@ function GatewayField({ value, change }: { readonly value: string; readonly chan
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function RouterFields({ sheet, change, onHighlight }: { readonly sheet: RouterSheet; readonly change: Change; readonly onHighlight: (link: string | null) => void }) {
|
export function RouterFields({
|
||||||
|
sheet,
|
||||||
|
change,
|
||||||
|
onHighlight,
|
||||||
|
onOpen,
|
||||||
|
}: {
|
||||||
|
readonly sheet: RouterSheet;
|
||||||
|
readonly change: Change;
|
||||||
|
readonly onHighlight: (link: string | null) => void;
|
||||||
|
readonly onOpen: (page: 'firewall') => void;
|
||||||
|
}) {
|
||||||
const t = useTranslate();
|
const t = useTranslate();
|
||||||
const [tab, setTab] = useState('general');
|
const [tab, setTab] = useState('general');
|
||||||
// Java greys the table out while RIP runs; an open table goes back to the general tab.
|
// Java greys the table out while RIP runs; an open table goes back to the general tab.
|
||||||
@@ -89,6 +99,11 @@ export function RouterFields({ sheet, change, onHighlight }: { readonly sheet: R
|
|||||||
<GatewayField value={sheet.gateway} change={change} />
|
<GatewayField value={sheet.gateway} change={change} />
|
||||||
<SheetCheck name="rip" label={t('sheet.rip')} checked={sheet.rip} onChange={(on) => change.run(() => setRip(change.doc, change.id, on, change.origin))} />
|
<SheetCheck name="rip" label={t('sheet.rip')} checked={sheet.rip} onChange={(on) => change.run(() => setRip(change.doc, change.id, on, change.origin))} />
|
||||||
<SheetCheck name="ipForwarding" label={t('sheet.ipForwarding')} checked={sheet.ipForwarding} disabled onChange={() => undefined} />
|
<SheetCheck name="ipForwarding" label={t('sheet.ipForwarding')} checked={sheet.ipForwarding} disabled onChange={() => undefined} />
|
||||||
|
<div className="sheet-buttons">
|
||||||
|
<button type="button" onClick={() => onOpen('firewall')}>
|
||||||
|
{t('sheet.firewall')}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</>
|
</>
|
||||||
) : card !== undefined ? (
|
) : card !== undefined ? (
|
||||||
<InterfaceFields card={card} change={change} />
|
<InterfaceFields card={card} change={change} />
|
||||||
@@ -109,7 +124,7 @@ export function HomeRouterFields({
|
|||||||
readonly sheet: HomeRouterSheet;
|
readonly sheet: HomeRouterSheet;
|
||||||
readonly change: Change;
|
readonly change: Change;
|
||||||
readonly onHighlight: (link: string | null) => void;
|
readonly onHighlight: (link: string | null) => void;
|
||||||
readonly onOpen: (page: 'dhcp' | 'forwarding') => void;
|
readonly onOpen: (page: 'dhcp' | 'forwarding' | 'firewall') => void;
|
||||||
}) {
|
}) {
|
||||||
const t = useTranslate();
|
const t = useTranslate();
|
||||||
const [tab, setTab] = useState('general');
|
const [tab, setTab] = useState('general');
|
||||||
@@ -131,6 +146,9 @@ export function HomeRouterFields({
|
|||||||
<GatewayField value={sheet.gateway} change={change} />
|
<GatewayField value={sheet.gateway} change={change} />
|
||||||
<SheetCheck name="ipForwarding" label={t('sheet.ipForwarding')} checked={sheet.ipForwarding} disabled onChange={() => undefined} />
|
<SheetCheck name="ipForwarding" label={t('sheet.ipForwarding')} checked={sheet.ipForwarding} disabled onChange={() => undefined} />
|
||||||
<div className="sheet-buttons">
|
<div className="sheet-buttons">
|
||||||
|
<button type="button" onClick={() => onOpen('firewall')}>
|
||||||
|
{t('sheet.firewall')}
|
||||||
|
</button>
|
||||||
<button type="button" onClick={() => onOpen('forwarding')}>
|
<button type="button" onClick={() => onOpen('forwarding')}>
|
||||||
{t('sheet.portForwarding')}
|
{t('sheet.portForwarding')}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -85,6 +85,26 @@ const FROM_JAVA: Readonly<Record<string, string>> = {
|
|||||||
'forwarding.publicPortValue': 'jportforwarding_msg5',
|
'forwarding.publicPortValue': 'jportforwarding_msg5',
|
||||||
'forwarding.lanIPAddress': 'jportforwarding_msg6',
|
'forwarding.lanIPAddress': 'jportforwarding_msg6',
|
||||||
'forwarding.lanPortValue': 'jportforwarding_msg7',
|
'forwarding.lanPortValue': 'jportforwarding_msg7',
|
||||||
|
'sheet.firewall': 'jvermittlungsrechnerkonfiguration_msg4',
|
||||||
|
'firewall.rules': 'jfirewalldialog_msg21',
|
||||||
|
'firewall.up': 'jfirewalldialog_msg22',
|
||||||
|
'firewall.down': 'jfirewalldialog_msg23',
|
||||||
|
'firewall.add': 'jfirewalldialog_msg24',
|
||||||
|
'firewall.delete': 'jfirewalldialog_msg25',
|
||||||
|
'firewall.number': 'jfirewalldialog_msg26',
|
||||||
|
'firewall.sourceIPAddress': 'jfirewalldialog_msg27',
|
||||||
|
'firewall.sourceSubnetMask': 'jfirewalldialog_msg28',
|
||||||
|
'firewall.destinationIPAddress': 'jfirewalldialog_msg29',
|
||||||
|
'firewall.destinationSubnetMask': 'jfirewalldialog_msg30',
|
||||||
|
'firewall.protocol': 'jfirewalldialog_msg31',
|
||||||
|
'firewall.accept': 'jfirewalldialog_msg33',
|
||||||
|
'firewall.drop': 'jfirewalldialog_msg34',
|
||||||
|
'firewall.port': 'jfirewalldialog_msg35',
|
||||||
|
'firewall.active': 'jfirewalldialog_msg38',
|
||||||
|
'firewall.activeHint': 'jfirewalldialog_msg39',
|
||||||
|
'firewall.dropIcmp': 'jfirewalldialog_msg40',
|
||||||
|
'firewall.dropIcmpHint': 'jfirewalldialog_msg41',
|
||||||
|
'firewall.filterSyn': 'jfirewalldialog_msg42',
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Java's words, mended: the prompt says "Vermittlungsrechnerns" (guicontainer_msg1). */
|
/** Java's words, mended: the prompt says "Vermittlungsrechnerns" (guicontainer_msg1). */
|
||||||
@@ -97,6 +117,13 @@ const FROM_JAVA_MENDED: Readonly<Record<string, readonly [string, (java: string)
|
|||||||
// Java's word, then what it joins or which card.
|
// Java's word, then what it joins or which card.
|
||||||
'sheet.connectedTo': ['jvermittlungsrechnerkonfiguration_msg6', (java) => `${java} {device}`],
|
'sheet.connectedTo': ['jvermittlungsrechnerkonfiguration_msg6', (java) => `${java} {device}`],
|
||||||
'sheet.nic': ['jvermittlungsrechnerkonfiguration_msg10', (java) => `${java} {number}`],
|
'sheet.nic': ['jvermittlungsrechnerkonfiguration_msg10', (java) => `${java} {number}`],
|
||||||
|
// A column's title without Java's question mark ("Aktion?").
|
||||||
|
'firewall.action': ['jfirewalldialog_msg32', (java) => java.replace(/\?$/, '')],
|
||||||
|
// A label above its list, without the colon.
|
||||||
|
'firewall.defaultPolicy': ['jfirewalldialog_msg36', (java) => java.replace(/:$/, '')],
|
||||||
|
// Java's "\n" as line breaks; the last sentence of the German starts with a small letter there.
|
||||||
|
'firewall.intro': ['jfirewalldialog_msg37', (java) => java.replaceAll('\\n', '\n').replace('\nüber', '\nÜber')],
|
||||||
|
'firewall.filterSynHint': ['jfirewalldialog_msg43', (java) => java.replaceAll('\\n', '\n')],
|
||||||
};
|
};
|
||||||
|
|
||||||
describe('the catalogs', () => {
|
describe('the catalogs', () => {
|
||||||
|
|||||||
@@ -1185,3 +1185,29 @@ Gemessen (03.10.2026):
|
|||||||
* Portweiterleitung: Der Dialog speichert jede Zeile roh, auch Port „0“,
|
* Portweiterleitung: Der Dialog speichert jede Zeile roh, auch Port „0“,
|
||||||
„abc“, „05353“; die Firmware leitet nur gültige weiter
|
„abc“, „05353“; die Firmware leitet nur gültige weiter
|
||||||
(`validateData`: `musterProtocol`, `musterPort`, `musterIpAdresse`).
|
(`validateData`: `musterProtocol`, `musterPort`, `musterIpAdresse`).
|
||||||
|
|
||||||
|
### Firewall (F4.5d)
|
||||||
|
|
||||||
|
`SheetRun` bedient auch `JFirewallDialog` eines Vermittlungsrechners
|
||||||
|
(`firewall`): eine neue Regel, Zellen der Regeltabelle über Swings eigenes
|
||||||
|
Bearbeitungsende (`stopCellEditing` → `editingStopped` → `closeEditor`),
|
||||||
|
Verschieben und Löschen mit den Nummern, die Javas Knöpfe übergeben.
|
||||||
|
|
||||||
|
Gemessen (03.10.2026):
|
||||||
|
|
||||||
|
* Neue Firewall: aus, Standardaktion verwerfen, ICMP nicht filtern, nur
|
||||||
|
SYN verwerfen an. Neue Regel: alle Felder leer, TCP, alle Ports (−1),
|
||||||
|
verwerfen.
|
||||||
|
* `closeEditor` schreibt die Zelle in das Regelobjekt der Firewall, bevor
|
||||||
|
`validateExistingAttributes` prüft; besteht die Regel nicht, wird nur die
|
||||||
|
Tabelle nicht aufgefrischt. Die Firewall hält dann „abc“ als Quelle,
|
||||||
|
„255.0.255.0“ oder „0.0.0.0“ als Maske, „999.999.999.999“ als Ziel, Port 0
|
||||||
|
oder 65536.
|
||||||
|
* Masken: `isValidSubnetmask` (Einsen, dann Nullen, mindestens eine Eins).
|
||||||
|
Quelle und Ziel: `musterIpAdresse` ohne `ipCheck` („.0.0.1“ und
|
||||||
|
„010.0.0.0“ bleiben, wie getippt); die Marke „999.999.999.999“ gilt nur als
|
||||||
|
Quelle. Leerzeichen gelten als leer.
|
||||||
|
* Port: `Integer.valueOf`, was keine Zahl ist („abc“, „“), wird zu „alle
|
||||||
|
Ports“; „080“ wird 80.
|
||||||
|
* rauf/runter: Die erste Regel geht nicht höher, die letzte nicht tiefer
|
||||||
|
(`moveUp`/`moveDown` antworten `false`).
|
||||||
|
|||||||
@@ -27,13 +27,17 @@
|
|||||||
// a host's (gateway and DNS with its own settings on) or a home router's;
|
// 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
|
// then what the dialog shows without own settings, the static assignments
|
||||||
// ("dhcp") and a home router's port forwarding as its dialog keeps it
|
// ("dhcp") and a home router's port forwarding as its dialog keeps it
|
||||||
// (JPortForwardingDialog, "forwarding").
|
// (JPortForwardingDialog, "forwarding"). Last, a router's firewall dialog
|
||||||
|
// (JFirewallDialog, "firewall"): a new rule, a cell of a rule edited in its
|
||||||
|
// table as the table takes it (GatewayFirewallConfigTable.closeEditor), the
|
||||||
|
// rules moved and removed with the dialog's buttons.
|
||||||
//
|
//
|
||||||
// Usage: java -cp filius.jar:lib/*:classes SheetRun <cases txt> <output json>
|
// Usage: java -cp filius.jar:lib/*:classes SheetRun <cases txt> <output json>
|
||||||
|
|
||||||
import filius.gui.GUIContainer;
|
import filius.gui.GUIContainer;
|
||||||
import filius.gui.JMainFrame;
|
import filius.gui.JMainFrame;
|
||||||
import filius.gui.netzwerksicht.JDHCPKonfiguration;
|
import filius.gui.netzwerksicht.JDHCPKonfiguration;
|
||||||
|
import filius.gui.netzwerksicht.JFirewallDialog;
|
||||||
import filius.gui.netzwerksicht.JKonfiguration;
|
import filius.gui.netzwerksicht.JKonfiguration;
|
||||||
import filius.gui.netzwerksicht.JPortForwardingDialog;
|
import filius.gui.netzwerksicht.JPortForwardingDialog;
|
||||||
import filius.gui.netzwerksicht.JWeiterleitungsTabelle;
|
import filius.gui.netzwerksicht.JWeiterleitungsTabelle;
|
||||||
@@ -52,6 +56,8 @@ import filius.software.system.ModemFirmware;
|
|||||||
import filius.software.system.SwitchFirmware;
|
import filius.software.system.SwitchFirmware;
|
||||||
import filius.software.system.VermittlungsrechnerBetriebssystem;
|
import filius.software.system.VermittlungsrechnerBetriebssystem;
|
||||||
import filius.software.dhcp.DHCPAddressAssignment;
|
import filius.software.dhcp.DHCPAddressAssignment;
|
||||||
|
import filius.software.firewall.Firewall;
|
||||||
|
import filius.software.firewall.FirewallRule;
|
||||||
import filius.software.dhcp.DHCPServer;
|
import filius.software.dhcp.DHCPServer;
|
||||||
import filius.software.vermittlungsschicht.Weiterleitungstabelle;
|
import filius.software.vermittlungsschicht.Weiterleitungstabelle;
|
||||||
|
|
||||||
@@ -384,6 +390,87 @@ public class SheetRun {
|
|||||||
return flows;
|
return flows;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Map<String, Object> rule(FirewallRule rule) {
|
||||||
|
Map<String, Object> fields = new LinkedHashMap<>();
|
||||||
|
fields.put("srcIP", rule.srcIP);
|
||||||
|
fields.put("srcMask", rule.srcMask);
|
||||||
|
fields.put("destIP", rule.destIP);
|
||||||
|
fields.put("destMask", rule.destMask);
|
||||||
|
fields.put("port", rule.port);
|
||||||
|
fields.put("protocol", (int) rule.protocol);
|
||||||
|
fields.put("action", (int) rule.action);
|
||||||
|
return fields;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Firewall routerFirewall() {
|
||||||
|
Vermittlungsrechner router = new Vermittlungsrechner();
|
||||||
|
router.setzeAnzahlAnschluesse(2);
|
||||||
|
return ((VermittlungsrechnerBetriebssystem) router.getSystemSoftware()).holeFirewall();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** A cell of the dialog's only rule, typed into the table's editor and taken as the table takes it ("#i": the i-th choice of a list). */
|
||||||
|
static Map<String, Object> firewallCell(int column, String text) throws Exception {
|
||||||
|
Firewall firewall = routerFirewall();
|
||||||
|
firewall.addRule();
|
||||||
|
JFirewallDialog dialog = new JFirewallDialog(firewall, JMainFrame.getJMainFrame());
|
||||||
|
JTable table = (JTable) get(dialog, "ruleTable");
|
||||||
|
table.editCellAt(0, column);
|
||||||
|
java.awt.Component editor = table.getEditorComponent();
|
||||||
|
if (editor instanceof JTextField) ((JTextField) editor).setText(text);
|
||||||
|
else if (text.startsWith("#")) ((javax.swing.JComboBox<?>) editor).setSelectedIndex(Integer.parseInt(text.substring(1)));
|
||||||
|
else ((javax.swing.JComboBox<?>) editor).setSelectedItem(text);
|
||||||
|
// As Swing ends an edit: the editor stops, the table hears it (editingStopped → closeEditor).
|
||||||
|
table.getCellEditor().stopCellEditing();
|
||||||
|
Map<String, Object> result = new LinkedHashMap<>();
|
||||||
|
result.put("column", column);
|
||||||
|
result.put("input", text);
|
||||||
|
FirewallRule rule = firewall.getRuleset().get(0);
|
||||||
|
result.put("rule", rule(rule));
|
||||||
|
result.put("valid", rule.validateExistingAttributes());
|
||||||
|
List<Object> shown = new ArrayList<>();
|
||||||
|
for (int i = 1; i < 8; i++) shown.add(table.getValueAt(0, i));
|
||||||
|
result.put("shown", shown);
|
||||||
|
dialog.dispose();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Map<String, Object> firewallFlows() throws Exception {
|
||||||
|
Map<String, Object> flows = new LinkedHashMap<>();
|
||||||
|
{
|
||||||
|
Firewall firewall = routerFirewall();
|
||||||
|
JFirewallDialog dialog = new JFirewallDialog(firewall, JMainFrame.getJMainFrame());
|
||||||
|
firewall.addRule();
|
||||||
|
dialog.updateRuleTable();
|
||||||
|
flows.put("newRule", rule(firewall.getRuleset().get(0)));
|
||||||
|
flows.put("newRuleShown", dialog.ruleAsVector(1, firewall.getRuleset().get(0)));
|
||||||
|
flows.put("newFirewall", Arrays.asList(firewall.isActivated(), (int) firewall.getDefaultPolicy(), firewall.getDropICMP(), firewall.getFilterSYNSegmentsOnly()));
|
||||||
|
dialog.dispose();
|
||||||
|
}
|
||||||
|
String[][] cells = { { "1", "10.0.0.0" }, { "1", "abc" }, { "1", ".0.0.1" }, { "1", "999.999.999.999" }, { "1", "010.0.0.0" }, { "1", " " }, { "2", "255.255.0.0" }, { "2", "255.0.255.0" },
|
||||||
|
{ "2", "0.0.0.0" }, { "2", "255.255.255.255" }, { "2", "0255.255.0.0" }, { "3", "192.168.0.1" }, { "3", "999.999.999.999" }, { "4", "255.255.255.0" }, { "6", "80" }, { "6", "abc" },
|
||||||
|
{ "6", "0" }, { "6", "080" }, { "6", "" }, { "6", "65536" }, { "5", "UDP" }, { "5", "*" }, { "7", "#0" } };
|
||||||
|
List<Object> edits = new ArrayList<>();
|
||||||
|
for (String[] cell : cells) edits.add(firewallCell(Integer.parseInt(cell[0]), cell[1]));
|
||||||
|
flows.put("cells", edits);
|
||||||
|
// Moving with the ids the dialog passes (1 for the first row), and removing.
|
||||||
|
Firewall firewall = routerFirewall();
|
||||||
|
for (String source : new String[] { "1.1.1.1", "2.2.2.2", "3.3.3.3" }) firewall.addRule(new FirewallRule(source, "", "", "", FirewallRule.ALL_PORTS, FirewallRule.TCP, FirewallRule.DROP));
|
||||||
|
List<Object> moves = new ArrayList<>();
|
||||||
|
Object[][] steps = { { "up", 1 }, { "up", 3 }, { "down", 3 }, { "down", 1 }, { "delete", 2 } };
|
||||||
|
for (Object[] step : steps) {
|
||||||
|
int id = (Integer) step[1];
|
||||||
|
Object answer = null;
|
||||||
|
if (step[0].equals("up")) answer = firewall.moveUp(id);
|
||||||
|
else if (step[0].equals("down")) answer = firewall.moveDown(id);
|
||||||
|
else firewall.deleteRule(id - 1);
|
||||||
|
List<Object> order = new ArrayList<>();
|
||||||
|
for (FirewallRule each : firewall.getRuleset()) order.add(each.srcIP);
|
||||||
|
moves.add(Arrays.asList(step[0], id, answer, order));
|
||||||
|
}
|
||||||
|
flows.put("moves", moves);
|
||||||
|
return flows;
|
||||||
|
}
|
||||||
|
|
||||||
public static void main(String[] args) throws Exception {
|
public static void main(String[] args) throws Exception {
|
||||||
// FILIUS' window keeps the JVM alive: end it on any failure too.
|
// FILIUS' window keeps the JVM alive: end it on any failure too.
|
||||||
try {
|
try {
|
||||||
@@ -508,6 +595,7 @@ public class SheetRun {
|
|||||||
output.put("tables", tables);
|
output.put("tables", tables);
|
||||||
output.put("dhcp", dhcpFlows());
|
output.put("dhcp", dhcpFlows());
|
||||||
output.put("forwarding", forwardingFlows());
|
output.put("forwarding", forwardingFlows());
|
||||||
|
output.put("firewall", firewallFlows());
|
||||||
output.put("edits", edits);
|
output.put("edits", edits);
|
||||||
try (FileOutputStream out = new FileOutputStream(args[1])) {
|
try (FileOutputStream out = new FileOutputStream(args[1])) {
|
||||||
out.write((FlsOracle.Json.write(output, "") + "\n").getBytes(StandardCharsets.UTF_8));
|
out.write((FlsOracle.Json.write(output, "") + "\n").getBytes(StandardCharsets.UTF_8));
|
||||||
|
|||||||
Reference in New Issue
Block a user