feat: F4.6g3 a computer's firewall window, measured against Java's with traffic from two networks
The firewall-pc scenario and FirewallWindowRun use Java's window as a person does and send a ping, a TCP connection and a DNS question from the server's network and from behind the router after each step. The core adds and removes port rules (personalFirewall.ts) and writes both into the firewall's log, which now carries each line's time; the room takes the window's actions and sends its view and the log as lines; the editor shows Java's window with its two tabs. Not adopted from Java: any integer as a port, a port after a space refused without a word, a log one can type into. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -17,6 +17,7 @@ Swift-Pfade relativ zu `filius-ipad/ios/FiliusPad/TopologyEditor/` (Stand
|
|||||||
| `src/runtime/engine.ts` (ganze Millisekunden) | Java `System.currentTimeMillis` (long) | behoben (F4.6b): `start` und `advance` runden auf ganze Millisekunden ab, wie Javas Uhr zählt. Gefunden im Browser: der Raum gab `performance.now()` mit Bruchteilen weiter, und ein Ping blieb nach der ersten Zeitüberschreitung hängen; alle Tests liefen zuvor mit glatten Takten |
|
| `src/runtime/engine.ts` (ganze Millisekunden) | Java `System.currentTimeMillis` (long) | behoben (F4.6b): `start` und `advance` runden auf ganze Millisekunden ab, wie Javas Uhr zählt. Gefunden im Browser: der Raum gab `performance.now()` mit Bruchteilen weiter, und ein Ping blieb nach der ersten Zeitüberschreitung hängen; alle Tests liefen zuvor mit glatten Takten |
|
||||||
| `src/runtime/engine.ts` (leere Nachricht), `test/apps.test.ts` | Java `TCPSocket.erstelleSegmente`, `TCPSocket.run` (gemessen mit `EchoWindowRun`) | behoben (F4.6g1): ein leeres Segment mit PSH an der erwarteten Stelle ist eine leere Nachricht und wird ausgeliefert (vorher galt es als Wiederholung, weil seine Nummer die nächste nicht weiterzählt; Javas Echo beantwortet sie). Java beendet eine Nachricht mit jedem PSH, auch dem eines wiederholten Segments (eine leere Nachricht mehr, wenn ein ACK verloren geht); hier nur mit einem neuen — ein wiederholtes leeres ist an seiner Nummer nicht von einem neuen zu unterscheiden (benannt) |
|
| `src/runtime/engine.ts` (leere Nachricht), `test/apps.test.ts` | Java `TCPSocket.erstelleSegmente`, `TCPSocket.run` (gemessen mit `EchoWindowRun`) | behoben (F4.6g1): ein leeres Segment mit PSH an der erwarteten Stelle ist eine leere Nachricht und wird ausgeliefert (vorher galt es als Wiederholung, weil seine Nummer die nächste nicht weiterzählt; Javas Echo beantwortet sie). Java beendet eine Nachricht mit jedem PSH, auch dem eines wiederholten Segments (eine leere Nachricht mehr, wenn ein ACK verloren geht); hier nur mit einem neuen — ein wiederholtes leeres ist an seiner Nummer nicht von einem neuen zu unterscheiden (benannt) |
|
||||||
| `src/runtime/apps/dnsServer.ts`, `src/runtime/dnsHosts.ts`, `src/runtime/engine.ts` (DNS-Server), `src/runtime/snapshot.ts` (`DnsServerSnapshot`), `test/dnsWindow.test.ts`, `test/dnsWindow.java.json` | Java `DNSServer` (`hinzuRecord`, `loescheResourceRecord`, `changeSingleEntry`, `leseRecordListe`, `schreibeRecordListe`), `ResourceRecord`, `GUIApplicationDNSServerWindow`, `DNSConfigTable` (gemessen mit `DnsWindowRun`) | neu (F4.6g2): der DNS-Server als Programm, das sein Fenster ändert — an und aus (sein Socket auf Port 53), rekursive Auflösung, und die Einträge, die `/dns/hosts` sind und bei jeder Anfrage gelesen werden wie in Java (ein anderes Programm, der Texteditor, ändert sie sofort); ohne Datei die der Einstellungen. Der Schnappschuss nimmt jetzt jeden installierten DNS-Server mit, ob er läuft (`isActive`). Der Leser von `/dns/hosts` zieht aus `packages/fls` hierher (`isHostsDomainName` statt `isDomainName`), dazu `newHostsRecord`, `dnsHostsText`, `hostsRecordsOf`. Gegen Javas Fenster geprüft, auch die Antworten des Resolvers. Abweichend von Java: ein hinzugefügter Eintrag behält, was ein anderes Programm inzwischen in die Datei schrieb (Java schreibt seine alte Liste zurück); eine Zelle ändert nur ihren Eintrag (Java auch die folgenden Einträge anderer Art); eine entfernte Zeile ist dieser Eintrag (Java der erste des Namens); ein Eintrag braucht einen Namen (Java nimmt den leeren) |
|
| `src/runtime/apps/dnsServer.ts`, `src/runtime/dnsHosts.ts`, `src/runtime/engine.ts` (DNS-Server), `src/runtime/snapshot.ts` (`DnsServerSnapshot`), `test/dnsWindow.test.ts`, `test/dnsWindow.java.json` | Java `DNSServer` (`hinzuRecord`, `loescheResourceRecord`, `changeSingleEntry`, `leseRecordListe`, `schreibeRecordListe`), `ResourceRecord`, `GUIApplicationDNSServerWindow`, `DNSConfigTable` (gemessen mit `DnsWindowRun`) | neu (F4.6g2): der DNS-Server als Programm, das sein Fenster ändert — an und aus (sein Socket auf Port 53), rekursive Auflösung, und die Einträge, die `/dns/hosts` sind und bei jeder Anfrage gelesen werden wie in Java (ein anderes Programm, der Texteditor, ändert sie sofort); ohne Datei die der Einstellungen. Der Schnappschuss nimmt jetzt jeden installierten DNS-Server mit, ob er läuft (`isActive`). Der Leser von `/dns/hosts` zieht aus `packages/fls` hierher (`isHostsDomainName` statt `isDomainName`), dazu `newHostsRecord`, `dnsHostsText`, `hostsRecordsOf`. Gegen Javas Fenster geprüft, auch die Antworten des Resolvers. Abweichend von Java: ein hinzugefügter Eintrag behält, was ein anderes Programm inzwischen in die Datei schrieb (Java schreibt seine alte Liste zurück); eine Zelle ändert nur ihren Eintrag (Java auch die folgenden Einträge anderer Art); eine entfernte Zeile ist dieser Eintrag (Java der erste des Namens); ein Eintrag braucht einen Namen (Java nimmt den leeren) |
|
||||||
|
| `src/runtime/apps/personalFirewall.ts`, `src/runtime/engine.ts` (`personalFirewall`, `addFirewallPortRule`, `removeFirewallRule`, Firewall-Protokoll mit Zeit), `src/runtime/apps/routerPages.ts` (`portRuleAdded`, `portRuleRemoved`), `test/firewallWindow.test.ts`, `test/firewallWindow.java.json`, `test/scenarios/firewall-pc.scenario.json` | Java `GUIApplicationFirewallWindow` (`hinzuRegel`, `loescheRegel`, `ruleToVector`), `Firewall` (`addRule`, `deleteRule`), `FirewallRule` (gemessen mit `FirewallWindowRun`) | neu (F4.6g3): die Firewall eines Rechners, wie ihr Fenster sie ändert — eine Portregel hinzugefügt (TCP, UDP oder beide; alle Absender oder nur die der eigenen Netze; akzeptieren, hinten angefügt), eine Regel entfernt; beides steht im Protokoll der Firewall (Java schreibt es nur ins Fenster). Das Protokoll trägt jetzt die Zeit jeder Zeile. Gegen Javas Fenster geprüft, mit Ping, TCP-Verbindung und DNS-Frage aus dem eigenen und einem fremden Netz nach jedem Schritt. Abweichend von Java: ein Port ist 1 bis 65535, Leerzeichen drumherum erlaubt (Java nimmt jede Zahl: −1 öffnet jeden Port beider Protokolle, 0 und 70000 ergeben Regeln, die nie greifen; ein Leerzeichen davor lehnt Java still ab) |
|
||||||
| `src/runtime/engine.ts` (Installation), `src/runtime/apps/installation.ts`, `test/installation.test.ts` | Java `InternetKnotenBetriebssystem.installAndStartAppIfAvailable/entferneSoftware`, `WebServer.erzeugeStandardVerzeichnis`, `PeerToPeerAnwendung`/`EmailServer`/`DNSServer.setSystemSoftware/starten`, `tmpl/webserver_index_*`, `gfx/allgemein/splashscreen-mini.png` | neu (F4.6c): was ein Programm bei der Installation in die Dateien legt (Javas Startseite je Sprache, Bild, `/peer2peer`, `/mailserver/konten.txt`, `/dns/hosts`); `installProgram`/`removeProgram` im laufenden Betrieb — Einstellungen und fehlende Dateien aus dem Schnappschuss des Dokuments, Programm sofort gestartet; entfernt wird ein Server abgeschaltet, die Dateien bleiben. Die Programmerzeugung beim Start läuft über dieselbe `startProgram` in der bisherigen Reihenfolge |
|
| `src/runtime/engine.ts` (Installation), `src/runtime/apps/installation.ts`, `test/installation.test.ts` | Java `InternetKnotenBetriebssystem.installAndStartAppIfAvailable/entferneSoftware`, `WebServer.erzeugeStandardVerzeichnis`, `PeerToPeerAnwendung`/`EmailServer`/`DNSServer.setSystemSoftware/starten`, `tmpl/webserver_index_*`, `gfx/allgemein/splashscreen-mini.png` | neu (F4.6c): was ein Programm bei der Installation in die Dateien legt (Javas Startseite je Sprache, Bild, `/peer2peer`, `/mailserver/konten.txt`, `/dns/hosts`); `installProgram`/`removeProgram` im laufenden Betrieb — Einstellungen und fehlende Dateien aus dem Schnappschuss des Dokuments, Programm sofort gestartet; entfernt wird ein Server abgeschaltet, die Dateien bleiben. Die Programmerzeugung beim Start läuft über dieselbe `startProgram` in der bisherigen Reihenfolge |
|
||||||
| `src/runtime/apps/web.ts` (F4.6d), `test/web.test.ts` | Java `WebBrowser.retrieveImages`, `GUIApplicationWebBrowserWindow.hyperLinkListener`, `WebServer.changeVHostTable/saveVHosts/setUseVHost` (gemessen mit `BrowserWindowRun`) | erweitert (F4.6d): die Zeile einer Seite nennt Host und Pfad, die eines Bildes trägt seine Daten (für das Fenster); `pageReference` und `linkTarget` — relative Bilder und Links zählen vom Ordner der Seite (Java vom Wurzelordner des Servers; für eine Seite im Wurzelordner sind die Anfragen Javas), eine Marke (`#…`) und andere Schemata führen nirgends hin; der Webserver schaltet seine virtuellen Hosts um und schreibt ihre Tabelle, nur mit vollständigen Zeilen (Java schreibt jede Zeile, und beim Lesen rückt eine Zelle einer unvollständigen in die Zeile davor) |
|
| `src/runtime/apps/web.ts` (F4.6d), `test/web.test.ts` | Java `WebBrowser.retrieveImages`, `GUIApplicationWebBrowserWindow.hyperLinkListener`, `WebServer.changeVHostTable/saveVHosts/setUseVHost` (gemessen mit `BrowserWindowRun`) | erweitert (F4.6d): die Zeile einer Seite nennt Host und Pfad, die eines Bildes trägt seine Daten (für das Fenster); `pageReference` und `linkTarget` — relative Bilder und Links zählen vom Ordner der Seite (Java vom Wurzelordner des Servers; für eine Seite im Wurzelordner sind die Anfragen Javas), eine Marke (`#…`) und andere Schemata führen nirgends hin; der Webserver schaltet seine virtuellen Hosts um und schreibt ihre Tabelle, nur mit vollständigen Zeilen (Java schreibt jede Zeile, und beim Lesen rückt eine Zelle einer unvollständigen in die Zeile davor) |
|
||||||
| `src/runtime/apps/emailClient.ts`, `src/runtime/apps/emailServer.ts`, `src/runtime/emailAccountsFile.ts`, `src/runtime/engine.ts` (F4.6e) | Java `GUIApplicationEmailAnwendungWindow.kontoSpeichern/emailLoeschen`, `EmailAnwendung.setzeKonto/speichern/removeReceivedMail/removeSentMail`, `GUIApplicationEmailServerWindow`, `EmailServer.setMailDomain/benutzerHinzufuegen/kontoLoeschen` (gemessen mit `MailWindowRun`) | erweitert (F4.6e): das E-Mail-Programm setzt sein Konto (`accountOfForm` mit Javas Mustern für Ports und Benutzernamen, `splitName` wie Javas Dialog: letztes Wort Nachname) und schreibt es nach `/konten.txt` (`clientAccountFileText`, jetzt im Kern statt im Export), löscht eine Mail eines Ordners, prüft ein Formular wie Javas Fenster (`mailFormProblem`) und sendet mit dem Konto, mit dem die Mail geschrieben wurde; der Server setzt seine Domain (nicht leer, Javas Muster), legt ein Konto an (einen vorhandenen Namen in jeder Schreibweise nicht — Javas Fenster meldet es als angelegt) und entfernt eines, sein Startknopf schreibt die eigene Zeile des Fensters; die neue Domain steht sofort in Schnappschuss und Kontendatei (Java erst bei der nächsten Kontenänderung) |
|
| `src/runtime/apps/emailClient.ts`, `src/runtime/apps/emailServer.ts`, `src/runtime/emailAccountsFile.ts`, `src/runtime/engine.ts` (F4.6e) | Java `GUIApplicationEmailAnwendungWindow.kontoSpeichern/emailLoeschen`, `EmailAnwendung.setzeKonto/speichern/removeReceivedMail/removeSentMail`, `GUIApplicationEmailServerWindow`, `EmailServer.setMailDomain/benutzerHinzufuegen/kontoLoeschen` (gemessen mit `MailWindowRun`) | erweitert (F4.6e): das E-Mail-Programm setzt sein Konto (`accountOfForm` mit Javas Mustern für Ports und Benutzernamen, `splitName` wie Javas Dialog: letztes Wort Nachname) und schreibt es nach `/konten.txt` (`clientAccountFileText`, jetzt im Kern statt im Export), löscht eine Mail eines Ordners, prüft ein Formular wie Javas Fenster (`mailFormProblem`) und sendet mit dem Konto, mit dem die Mail geschrieben wurde; der Server setzt seine Domain (nicht leer, Javas Muster), legt ein Konto an (einen vorhandenen Namen in jeder Schreibweise nicht — Javas Fenster meldet es als angelegt) und entfernt eines, sein Startknopf schreibt die eigene Zeile des Fensters; die neue Domain steht sofort in Schnappschuss und Kontendatei (Java erst bei der nächsten Kontenänderung) |
|
||||||
@@ -164,6 +165,7 @@ Swift-Pfade relativ zu `filius-ipad/ios/FiliusPad/TopologyEditor/` (Stand
|
|||||||
| `src/mail.ts` | Java `GUIApplicationEmailAnwendungWindow` (`posteingangAktualisieren`, `gesendeteAktualisieren`, `replyLayout`), `GUIApplicationEmailServerWindow` (gemessen mit `MailWindowRun`) | neu (F4.6e): die Bildschirmstände von E-Mail-Programm (Konto, Posteingang, Gesendete) und E-Mail-Server (Knopf, Domain, Konten mit Zahl der Mails); die Zeilen der Ordner und die Antwort (an den Absender, „RE:“, zitierter Text) wie Javas Fenster |
|
| `src/mail.ts` | Java `GUIApplicationEmailAnwendungWindow` (`posteingangAktualisieren`, `gesendeteAktualisieren`, `replyLayout`), `GUIApplicationEmailServerWindow` (gemessen mit `MailWindowRun`) | neu (F4.6e): die Bildschirmstände von E-Mail-Programm (Konto, Posteingang, Gesendete) und E-Mail-Server (Knopf, Domain, Konten mit Zahl der Mails); die Zeilen der Ordner und die Antwort (an den Absender, „RE:“, zitierter Text) wie Javas Fenster |
|
||||||
| `src/echo.ts` | Java `GUIApplicationServerBausteinWindow`, `GUIApplicationClientBausteinWindow` (gemessen mit `EchoWindowRun`) | neu (F4.6g1): die Bildschirmstände von Echo-Server (läuft, Port) und Einfachem Client (verbunden, Adresse, Port); `portOfText`: ein Port, wie ein Socket ihn haben kann (Javas `Integer.parseInt` nimmt jede Zahl und wirft bei anderem Text) |
|
| `src/echo.ts` | Java `GUIApplicationServerBausteinWindow`, `GUIApplicationClientBausteinWindow` (gemessen mit `EchoWindowRun`) | neu (F4.6g1): die Bildschirmstände von Echo-Server (läuft, Port) und Einfachem Client (verbunden, Adresse, Port); `portOfText`: ein Port, wie ein Socket ihn haben kann (Javas `Integer.parseInt` nimmt jede Zahl und wirft bei anderem Text) |
|
||||||
| `src/dns.ts`, `src/sim.ts` (`dnsServer`) | Java `GUIApplicationDNSServerWindow` (gemessen mit `DnsWindowRun`) | neu (F4.6g2): der Bildschirmstand des DNS-Servers — Knopf, Kästchen, die Zeilen der drei Tabellen, wie `/dns/hosts` sie hält |
|
| `src/dns.ts`, `src/sim.ts` (`dnsServer`) | Java `GUIApplicationDNSServerWindow` (gemessen mit `DnsWindowRun`) | neu (F4.6g2): der Bildschirmstand des DNS-Servers — Knopf, Kästchen, die Zeilen der drei Tabellen, wie `/dns/hosts` sie hält |
|
||||||
|
| `src/firewall.ts`, `src/sim.ts` (`firewall`) | Java `GUIApplicationFirewallWindow` (gemessen mit `FirewallWindowRun`) | neu (F4.6g3): der Bildschirmstand der Firewall eines Rechners — die drei Kästchen und die Regeln, wie die Tabelle sie zeigt (mit dem ganzen Text einer Regel, die das Fenster nicht machen kann); das Protokoll kommt als Zeilen des Fensters |
|
||||||
|
|
||||||
## packages/sim
|
## packages/sim
|
||||||
|
|
||||||
@@ -182,6 +184,7 @@ Swift-Pfade relativ zu `filius-ipad/ios/FiliusPad/TopologyEditor/` (Stand
|
|||||||
| `src/room.ts` (Dateien in beide Richtungen), `test/writeBack.test.ts` | Plan §7.3 | erweitert (F4.6f2): was eine Person ins Dokument schreibt (der Texteditor), geht im Lauf sofort in die Dateien der Engine; was die Engine an ihren Dateien ändert, geht im Takt der Änderung ins Dokument (nicht erst nach einer Sekunde), damit das Dokument jede Änderung der Engine hat, bevor die nächste einer Person kommt — so behält ein Text, den beide zugleich ändern, beides. Ein Rechner, dessen Programme keine Dateien benutzen, liest seine beim nächsten Start |
|
| `src/room.ts` (Dateien in beide Richtungen), `test/writeBack.test.ts` | Plan §7.3 | erweitert (F4.6f2): was eine Person ins Dokument schreibt (der Texteditor), geht im Lauf sofort in die Dateien der Engine; was die Engine an ihren Dateien ändert, geht im Takt der Änderung ins Dokument (nicht erst nach einer Sekunde), damit das Dokument jede Änderung der Engine hat, bevor die nächste einer Person kommt — so behält ein Text, den beide zugleich ändern, beides. Ein Rechner, dessen Programme keine Dateien benutzen, liest seine beim nächsten Start |
|
||||||
| `src/room.ts` (Echo), `test/echoWindow.test.ts`, `test/echoWindow.java.json`, `test/fixtures/after-run-echo.fls`, `test/room.test.ts` | Java `GUIApplicationServerBausteinWindow`, `GUIApplicationClientBausteinWindow` (gemessen mit `EchoWindowRun`), `SaveAfterRunRun echo` | erweitert (F4.6g1): der Raum nimmt `setActive` mit Port beim Echo-Server und `connect`/`disconnect`/`send` beim Einfachen Client an, schickt ihre Bildschirmstände und schreibt nach dem Lauf Port und Ziel zurück, wie Javas Projekt sie behält. Gegen Javas Fenster und Javas gespeichertes Projekt geprüft; benannt: drei SYN statt einem (F2.3c) — ein Verbindungsversuch dauert länger |
|
| `src/room.ts` (Echo), `test/echoWindow.test.ts`, `test/echoWindow.java.json`, `test/fixtures/after-run-echo.fls`, `test/room.test.ts` | Java `GUIApplicationServerBausteinWindow`, `GUIApplicationClientBausteinWindow` (gemessen mit `EchoWindowRun`), `SaveAfterRunRun echo` | erweitert (F4.6g1): der Raum nimmt `setActive` mit Port beim Echo-Server und `connect`/`disconnect`/`send` beim Einfachen Client an, schickt ihre Bildschirmstände und schreibt nach dem Lauf Port und Ziel zurück, wie Javas Projekt sie behält. Gegen Javas Fenster und Javas gespeichertes Projekt geprüft; benannt: drei SYN statt einem (F2.3c) — ein Verbindungsversuch dauert länger |
|
||||||
| `src/room.ts` (DNS-Server), `test/dnsWindow.test.ts` | Java `GUIApplicationDNSServerWindow`, `DNSConfigTable` | erweitert (F4.6g2): der Raum nimmt `setActive`, `setRecursive`, `addRecord`, `removeRecord`, `changeRecord` an, schickt den Bildschirmstand (ohne Zeilen; Javas Fenster hat keine) und schreibt nach dem Lauf zurück, ob der Server läuft, die rekursive Auflösung und die Einträge der Datei in seine Einstellungen |
|
| `src/room.ts` (DNS-Server), `test/dnsWindow.test.ts` | Java `GUIApplicationDNSServerWindow`, `DNSConfigTable` | erweitert (F4.6g2): der Raum nimmt `setActive`, `setRecursive`, `addRecord`, `removeRecord`, `changeRecord` an, schickt den Bildschirmstand (ohne Zeilen; Javas Fenster hat keine) und schreibt nach dem Lauf zurück, ob der Server läuft, die rekursive Auflösung und die Einträge der Datei in seine Einstellungen |
|
||||||
|
| `src/room.ts` (Firewall), `test/firewallWindow.test.ts` | Java `GUIApplicationFirewallWindow` | erweitert (F4.6g3): der Raum nimmt `setActive`, `setIcmpAllowed`, `setFilterUdp`, `addRule`, `removeRule` an, schickt den Bildschirmstand und das Protokoll der Firewall als Zeilen (über Stopps behalten, wie die Engine es hält); zurück ins Projekt geht die Firewall wie schon bei Routern |
|
||||||
| `browser/m9.spec.ts`, `browser/traceDigest.ts`, `browser/*Entry.ts`, `/playwright.config.ts` | Plan §13 M9 | neu (F4.2): M9 in Chromium — 17 Beispiele, je 30 s mit Eingaben, ergeben Zeichen für Zeichen denselben Mitschnitt wie in Node; der Raum in einem echten Worker |
|
| `browser/m9.spec.ts`, `browser/traceDigest.ts`, `browser/*Entry.ts`, `/playwright.config.ts` | Plan §13 M9 | neu (F4.2): M9 in Chromium — 17 Beispiele, je 30 s mit Eingaben, ergeben Zeichen für Zeichen denselben Mitschnitt wie in Node; der Raum in einem echten Worker |
|
||||||
|
|
||||||
## packages/editor
|
## packages/editor
|
||||||
@@ -215,6 +218,7 @@ Swift-Pfade relativ zu `filius-ipad/ios/FiliusPad/TopologyEditor/` (Stand
|
|||||||
| `src/desktop/fileExplorer.ts`, `src/desktop/windowState.ts`, `src/desktop/FileExplorerPanel.tsx`, `src/desktop/ImageViewerPanel.tsx` | Java `GUIApplicationFileExplorerWindow` (`fileexplorer_msg2`–`19`, `sw_fileexplorer_msg1`–`3`, `sw_fileimporter_msg3`), `GUIApplicationImageViewerWindow` (`imageviewer_msg2`), gemessen mit `FileExplorerWindowRun` | neu (F4.6f3): der Datei-Explorer — Baum aus Ordnern und Dateien, die Einträge des gewählten Ordners mit Javas Symbolen, Javas Menü per Rechtsklick (auf einem Eintrag Löschen, Ausschneiden, Kopieren, Umbenennen; auf der freien Fläche Neuer Ordner, Einfügen), Javas Fragen und das Importfenster; Kopiertes bleibt zum nächsten Einfügen, Ausgeschnittenes nicht. Der Bildbetrachter — „Öffnen“ über Javas Dateidialog, das Bild, sein Name als Titel. Die Dateien sind die des Dokuments, geändert über die Operationen des Kerns. Besser als Java: Baum und Liste folgen den Dateien sofort („Aktualisieren“ entfällt); die Aktionen des Menüs sind auch Knöpfe, ein Finger braucht keinen Rechtsklick; ein Ordner der Liste öffnet mit Doppelklick; eine im Baum gewählte Datei wird in ihrem Ordner gewählt (Java hält sie für einen Ordner, ein neuer Ordner landete unter ihr); was eine Aktion ablehnt, wird gesagt; der neue Name beginnt mit dem alten; der Bildbetrachter zeigt ein Bild statt alle übereinander und sagt, was kein Bild ist. `windowState.ts`: was die Fenster der lokalen Programme teilen (auch der Texteditor) |
|
| `src/desktop/fileExplorer.ts`, `src/desktop/windowState.ts`, `src/desktop/FileExplorerPanel.tsx`, `src/desktop/ImageViewerPanel.tsx` | Java `GUIApplicationFileExplorerWindow` (`fileexplorer_msg2`–`19`, `sw_fileexplorer_msg1`–`3`, `sw_fileimporter_msg3`), `GUIApplicationImageViewerWindow` (`imageviewer_msg2`), gemessen mit `FileExplorerWindowRun` | neu (F4.6f3): der Datei-Explorer — Baum aus Ordnern und Dateien, die Einträge des gewählten Ordners mit Javas Symbolen, Javas Menü per Rechtsklick (auf einem Eintrag Löschen, Ausschneiden, Kopieren, Umbenennen; auf der freien Fläche Neuer Ordner, Einfügen), Javas Fragen und das Importfenster; Kopiertes bleibt zum nächsten Einfügen, Ausgeschnittenes nicht. Der Bildbetrachter — „Öffnen“ über Javas Dateidialog, das Bild, sein Name als Titel. Die Dateien sind die des Dokuments, geändert über die Operationen des Kerns. Besser als Java: Baum und Liste folgen den Dateien sofort („Aktualisieren“ entfällt); die Aktionen des Menüs sind auch Knöpfe, ein Finger braucht keinen Rechtsklick; ein Ordner der Liste öffnet mit Doppelklick; eine im Baum gewählte Datei wird in ihrem Ordner gewählt (Java hält sie für einen Ordner, ein neuer Ordner landete unter ihr); was eine Aktion ablehnt, wird gesagt; der neue Name beginnt mit dem alten; der Bildbetrachter zeigt ein Bild statt alle übereinander und sagt, was kein Bild ist. `windowState.ts`: was die Fenster der lokalen Programme teilen (auch der Texteditor) |
|
||||||
| `src/desktop/EchoPanels.tsx`, `src/desktop/serverLines.ts` (`simpleClientLine`) | Java `GUIApplicationServerBausteinWindow` (`serverbaustein_msg1`–`3`), `GUIApplicationClientBausteinWindow` (`clientbaustein_msg1`–`6`), `ClientBaustein` (`sw_clientbaustein_msg2`–`6`), `Socket`/`TCPSocket` (`sw_socket_msg1`, `sw_tcpsocket_msg2`, `3`, `9`) | neu (F4.6g1): Echo-Server — Starten/Anhalten mit Port (nur angehalten beschreibbar) und seine Zeilen; Einfacher Client — Server-Adresse und -Port (verbunden gesperrt, nach einer Änderung des Programms wieder dessen Werte), Verbinden/Trennen, Nachricht und Senden (nur verbunden, danach leer), seine Zeilen in Javas Worten. Besser als Java: ein Port, der keiner ist, wird rot markiert und nicht übergeben (Javas Knopf wirft und tut nichts) |
|
| `src/desktop/EchoPanels.tsx`, `src/desktop/serverLines.ts` (`simpleClientLine`) | Java `GUIApplicationServerBausteinWindow` (`serverbaustein_msg1`–`3`), `GUIApplicationClientBausteinWindow` (`clientbaustein_msg1`–`6`), `ClientBaustein` (`sw_clientbaustein_msg2`–`6`), `Socket`/`TCPSocket` (`sw_socket_msg1`, `sw_tcpsocket_msg2`, `3`, `9`) | neu (F4.6g1): Echo-Server — Starten/Anhalten mit Port (nur angehalten beschreibbar) und seine Zeilen; Einfacher Client — Server-Adresse und -Port (verbunden gesperrt, nach einer Änderung des Programms wieder dessen Werte), Verbinden/Trennen, Nachricht und Senden (nur verbunden, danach leer), seine Zeilen in Javas Worten. Besser als Java: ein Port, der keiner ist, wird rot markiert und nicht übergeben (Javas Knopf wirft und tut nichts) |
|
||||||
| `src/desktop/DnsServerPanel.tsx`, `src/desktop/WebServerPanel.tsx` (Zelle) | Java `GUIApplicationDNSServerWindow` (`dnsserver_msg1`–`23`), gemessen mit `DnsWindowRun` | neu (F4.6g2): der DNS-Server — Starten/Beenden, das Kästchen der rekursiven Auflösung, drei Reiter mit je zwei Feldern (das Adressfeld beginnt mit der eigenen Adresse), rot, solange die Prüfung sie nicht nimmt, „Hinzufügen“ (sonst Javas gültiges Beispiel), „Auswahl entfernen“ und die Tabelle, deren Zellen beim Verlassen geschrieben werden. Besser als Java: eine Zelle, die die Prüfung nicht nimmt, wird markiert, bevor sie zurückgeht. **Behoben, auch im Webserver (F4.6d):** eine Zelle übernahm den neuen Wert des Raums, der kurz nach einem Enter kommt, auch über das inzwischen Getippte; jetzt nur, solange seit Enter, Escape oder Verlassen nichts getippt ist |
|
| `src/desktop/DnsServerPanel.tsx`, `src/desktop/WebServerPanel.tsx` (Zelle) | Java `GUIApplicationDNSServerWindow` (`dnsserver_msg1`–`23`), gemessen mit `DnsWindowRun` | neu (F4.6g2): der DNS-Server — Starten/Beenden, das Kästchen der rekursiven Auflösung, drei Reiter mit je zwei Feldern (das Adressfeld beginnt mit der eigenen Adresse), rot, solange die Prüfung sie nicht nimmt, „Hinzufügen“ (sonst Javas gültiges Beispiel), „Auswahl entfernen“ und die Tabelle, deren Zellen beim Verlassen geschrieben werden. Besser als Java: eine Zelle, die die Prüfung nicht nimmt, wird markiert, bevor sie zurückgeht. **Behoben, auch im Webserver (F4.6d):** eine Zelle übernahm den neuen Wert des Raums, der kurz nach einem Enter kommt, auch über das inzwischen Getippte; jetzt nur, solange seit Enter, Escape oder Verlassen nichts getippt ist |
|
||||||
|
| `src/desktop/PersonalFirewallPanel.tsx`, `src/desktop/serverLines.ts` (`firewallLine`), `src/desktop/DesktopWindow.tsx` | Java `GUIApplicationFirewallWindow` (`firewall_msg1`–`14`), `Firewall`/`FirewallThread` (`sw_firewall_msg5`, `msg7`, `msg8`, `firewallthread_msg1`), gemessen mit `FirewallWindowRun` | neu (F4.6g3): die Firewall eines Rechners — Reiter „Konfiguration“ und „Log“, die drei Kästchen (ICMP und UDP nur bei eingeschalteter Firewall), Port mit Protokoll und Absendern, „Hinzufügen“ (das Feld wird danach geleert), „Löschen“ der gewählten Zeile, die Tabelle; das Protokoll in Javas Worten. Besser als Java: ein Port, der keiner ist, wird rot markiert und nicht übergeben; Javas Erklärtext, den Java schreibt, aber nie zeigt, steht unter den Kästchen; eine Regel, die das Fenster nicht machen kann (aus einer Datei: verwerfend oder mit Adresse), zeigt in der Absenderspalte, was sie ist (Java zeigt sie als offenen Port für alle Absender); ins Protokoll kann man nicht tippen |
|
||||||
| `src/Editor.tsx`, `src/canvas/Canvas.tsx` (Aktionsmodus) | Java `GUIContainer.setActiveSite/showDesktop/closeDialogs/reopenDialogs`, `GUIKnotenItem` (Klick im Aktionsmodus), `GUIEvents.kontextMenueAktionsmodus` (`guievents_msg3`), `JCablePanel` (`farbeBlinken`) | erweitert (F4.6a): im Aktionsmodus ist der Entwurf gesperrt wie bei Java — keine Palette, kein Blatt, nichts bewegt sich, kein Rückgängig, kein Löschen —, Kabel mit Rahmen leuchten grün, ein Tipp auf einen Rechner öffnet seinen Desktop (Langdruck: „Desktop anzeigen“); offene Desktops überstehen den Entwurf und erscheinen beim nächsten Start wieder, die eines entfernten Rechners nicht |
|
| `src/Editor.tsx`, `src/canvas/Canvas.tsx` (Aktionsmodus) | Java `GUIContainer.setActiveSite/showDesktop/closeDialogs/reopenDialogs`, `GUIKnotenItem` (Klick im Aktionsmodus), `GUIEvents.kontextMenueAktionsmodus` (`guievents_msg3`), `JCablePanel` (`farbeBlinken`) | erweitert (F4.6a): im Aktionsmodus ist der Entwurf gesperrt wie bei Java — keine Palette, kein Blatt, nichts bewegt sich, kein Rückgängig, kein Löschen —, Kabel mit Rahmen leuchten grün, ein Tipp auf einen Rechner öffnet seinen Desktop (Langdruck: „Desktop anzeigen“); offene Desktops überstehen den Entwurf und erscheinen beim nächsten Start wieder, die eines entfernten Rechners nicht |
|
||||||
| `browser/simulation.spec.ts`, `test/programs.test.ts` | — | neu (F4.6a): Moduswechsel, Sperren, Geschwindigkeit, Kabelaktivität, Desktop mit Titel, Programmen, Netzwerk-Info, Verschieben und Stapeln, Handy, Englisch; die Programmliste gegen Javas `Desktop_*.txt`; seit F4.6b die Befehlszeile (Ping über drei Router, Verlauf, Chips, Strg+C, Behalten, exit) und der Fehlerfall des Raums; seit F4.6c die Software-Installation (sofort lauffähig, Doppelklick, Abwählen) |
|
| `browser/simulation.spec.ts`, `test/programs.test.ts` | — | neu (F4.6a): Moduswechsel, Sperren, Geschwindigkeit, Kabelaktivität, Desktop mit Titel, Programmen, Netzwerk-Info, Verschieben und Stapeln, Handy, Englisch; die Programmliste gegen Javas `Desktop_*.txt`; seit F4.6b die Befehlszeile (Ping über drei Router, Verlauf, Chips, Strg+C, Behalten, exit) und der Fehlerfall des Raums; seit F4.6c die Software-Installation (sofort lauffähig, Doppelklick, Abwählen) |
|
||||||
| `browser/web.spec.ts`, `browser/fixtures/web-pages.fls`, `test/webPagesFixture.test.ts`, `test/serverLines.test.ts`, `src/simulation/useSimulation.ts` (`views`), `src/Editor.tsx` (Sprache beim Start), `src/desktop/DesktopWindow.tsx` | — | neu (F4.6d): Browser und Webserver in Chromium auf einer eigenen Projektdatei (mit den Befehlen des Dokuments gebaut und bei jedem Lauf verglichen): Adresszeile, Titel, Sandbox, Bilder, Marke, Links im Ordner, beide Formulare bis in die Zeilen des Servers, Fehlerseite, nicht erreichbarer Server, Knopf, Kästchen und Tabelle, die Routerseiten auf Englisch; die Zeilen des Servers gegen Javas Fenster |
|
| `browser/web.spec.ts`, `browser/fixtures/web-pages.fls`, `test/webPagesFixture.test.ts`, `test/serverLines.test.ts`, `src/simulation/useSimulation.ts` (`views`), `src/Editor.tsx` (Sprache beim Start), `src/desktop/DesktopWindow.tsx` | — | neu (F4.6d): Browser und Webserver in Chromium auf einer eigenen Projektdatei (mit den Befehlen des Dokuments gebaut und bei jedem Lauf verglichen): Adresszeile, Titel, Sandbox, Bilder, Marke, Links im Ordner, beide Formulare bis in die Zeilen des Servers, Fehlerseite, nicht erreichbarer Server, Knopf, Kästchen und Tabelle, die Routerseiten auf Englisch; die Zeilen des Servers gegen Javas Fenster |
|
||||||
@@ -224,6 +228,7 @@ Swift-Pfade relativ zu `filius-ipad/ios/FiliusPad/TopologyEditor/` (Stand
|
|||||||
| `browser/files.spec.ts`, `test/fileExplorer.test.ts` | — | neu (F4.6f3): Javas Schritte an Explorer und Bildbetrachter nachgespielt (gleich, wo Java recht hat, benannt, wo es irrt); in Chromium: im Lauf installiert, Ordner angelegt, über Javas Rechtsklick-Menü kopiert und eingefügt, Javas Meldung beim Einfügen in sich selbst, per Knopf umbenannt und nach „Sicher?“ gelöscht, was die Befehlszeile anlegt sofort sichtbar, ein lokales Bild importiert und im Bildbetrachter gezeigt, eine Textdatei dort abgelehnt |
|
| `browser/files.spec.ts`, `test/fileExplorer.test.ts` | — | neu (F4.6f3): Javas Schritte an Explorer und Bildbetrachter nachgespielt (gleich, wo Java recht hat, benannt, wo es irrt); in Chromium: im Lauf installiert, Ordner angelegt, über Javas Rechtsklick-Menü kopiert und eingefügt, Javas Meldung beim Einfügen in sich selbst, per Knopf umbenannt und nach „Sicher?“ gelöscht, was die Befehlszeile anlegt sofort sichtbar, ein lokales Bild importiert und im Bildbetrachter gezeigt, eine Textdatei dort abgelehnt |
|
||||||
| `browser/echo.spec.ts`, `test/serverLines.test.ts` (Echo) | — | neu (F4.6g1): Echo in Chromium — im Lauf installiert, ein Port, der keiner ist, markiert, Server gestartet, Client verbunden, Felder gesperrt, Nachricht gesendet und zurückbekommen, Trennen; die Zeilen beider Fenster gegen Javas |
|
| `browser/echo.spec.ts`, `test/serverLines.test.ts` (Echo) | — | neu (F4.6g1): Echo in Chromium — im Lauf installiert, ein Port, der keiner ist, markiert, Server gestartet, Client verbunden, Felder gesperrt, Nachricht gesendet und zurückbekommen, Trennen; die Zeilen beider Fenster gegen Javas |
|
||||||
| `browser/dns.spec.ts` | — | neu (F4.6g2): der DNS-Server in Chromium auf `email_komplett` — Knopf, Reiter, eigene Adresse im Feld, Javas Beispiel bei Ungültigem, Eintrag hinzugefügt, Zelle geschrieben und eine abgelehnt, Zeile entfernt, MX, Kästchen, Beenden; die Befehlszeile des Web-Servers löst den neuen Namen auf |
|
| `browser/dns.spec.ts` | — | neu (F4.6g2): der DNS-Server in Chromium auf `email_komplett` — Knopf, Reiter, eigene Adresse im Feld, Javas Beispiel bei Ungültigem, Eintrag hinzugefügt, Zelle geschrieben und eine abgelehnt, Zeile entfernt, MX, Kästchen, Beenden; die Befehlszeile des Web-Servers löst den neuen Namen auf |
|
||||||
|
| `browser/firewall.spec.ts`, `test/serverLines.test.ts` (Firewall) | — | neu (F4.6g3): die Firewall in Chromium auf `email_komplett`, im Lauf auf dem Web-Server installiert — Javas Kästchen, ein Port abgelehnt und einer hinzugefügt, der Browser eines Notebooks durch die Regel gelassen, ICMP verboten und dessen Ping verworfen (beides im Protokoll), die Firewall aus (die Kästchen darunter gesperrt), die Regel gewählt und gelöscht; die Zeilen des Protokolls und der Tabelle Wort für Wort wie Javas |
|
||||||
| `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 |
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ export * from './runtime/apps/dnsServer.js';
|
|||||||
export * from './runtime/apps/web.js';
|
export * from './runtime/apps/web.js';
|
||||||
export * from './runtime/apps/routerPages.js';
|
export * from './runtime/apps/routerPages.js';
|
||||||
export * from './runtime/apps/firewallSettings.js';
|
export * from './runtime/apps/firewallSettings.js';
|
||||||
|
export * from './runtime/apps/personalFirewall.js';
|
||||||
export * from './runtime/apps/emailServer.js';
|
export * from './runtime/apps/emailServer.js';
|
||||||
export * from './runtime/apps/emailClient.js';
|
export * from './runtime/apps/emailClient.js';
|
||||||
export * from './runtime/apps/filePrograms.js';
|
export * from './runtime/apps/filePrograms.js';
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
// A computer's firewall as its window changes it, after Java's
|
||||||
|
// GUIApplicationFirewallWindow (measured with tools/java-oracle's
|
||||||
|
// FirewallWindowRun): a port rule added — the port opened for TCP, UDP or
|
||||||
|
// both, for all senders or only those of the computer's networks — and a
|
||||||
|
// rule removed. The three boxes are the configuration's fields; what the
|
||||||
|
// firewall does with a packet is the engine's (evaluateFirewall), as for a
|
||||||
|
// router.
|
||||||
|
//
|
||||||
|
// Departures from Java (firewallWindow.test.ts names each):
|
||||||
|
// - a port is 1 to 65535, spaces around it allowed. Java takes any int:
|
||||||
|
// -1 opens every port of both protocols (the table shows no port), 0 or
|
||||||
|
// 70000 make a rule that never matches; and refuses " 80" without a word.
|
||||||
|
|
||||||
|
import { DIRECTLY_CONNECTED_SOURCE_MARKER, firewallRule, type FirewallConfiguration, type FirewallProtocol, type FirewallRule } from '../config.js';
|
||||||
|
|
||||||
|
/** The protocols of the window's list: TCP, UDP, "*" for both. */
|
||||||
|
export type PortRuleProtocol = Exclude<FirewallProtocol, 'icmp'>;
|
||||||
|
|
||||||
|
/** The port typed, as the window takes it: 1 to 65535; undefined for anything else. */
|
||||||
|
export function firewallPortOfText(text: string): number | undefined {
|
||||||
|
const trimmed = text.trim();
|
||||||
|
if (!/^[0-9]+$/.test(trimmed)) return undefined;
|
||||||
|
const port = Number(trimmed);
|
||||||
|
return port >= 1 && port <= 65_535 ? port : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** "Hinzufügen" (hinzuRegel): a rule accepting the port, at the end; Java's FirewallRule("", "", "", "", port, protocol, ACCEPT). */
|
||||||
|
export function withPortRule(configuration: FirewallConfiguration, port: number, protocol: PortRuleProtocol, sameNetworkOnly: boolean): FirewallConfiguration {
|
||||||
|
const rule = firewallRule({ port, protocol, action: 'accept', sourceIPAddress: sameNetworkOnly ? DIRECTLY_CONNECTED_SOURCE_MARKER : '' });
|
||||||
|
return { ...configuration, rules: [...configuration.rules, rule] };
|
||||||
|
}
|
||||||
|
|
||||||
|
/** "Löschen" (loescheRegel): the rule chosen; undefined when there is none at that row. */
|
||||||
|
export function withoutRule(configuration: FirewallConfiguration, index: number): FirewallConfiguration | undefined {
|
||||||
|
if (!Number.isInteger(index) || index < 0 || index >= configuration.rules.length) return undefined;
|
||||||
|
return { ...configuration, rules: configuration.rules.filter((_, each) => each !== index) };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* How the window's table shows a rule: Java's sender column, or none for a
|
||||||
|
* rule the window cannot make (one from a file that drops, or names an
|
||||||
|
* address; a mask without its address counts for nothing). Java shows such a
|
||||||
|
* rule as a port opened for all senders.
|
||||||
|
*/
|
||||||
|
export function portRuleSender(rule: FirewallRule): 'all' | 'sameNetwork' | undefined {
|
||||||
|
if (rule.action !== 'accept' || rule.destinationIPAddress !== '') return undefined;
|
||||||
|
if (rule.sourceIPAddress === '') return 'all';
|
||||||
|
return rule.sourceIPAddress === DIRECTLY_CONNECTED_SOURCE_MARKER ? 'sameNetwork' : undefined;
|
||||||
|
}
|
||||||
@@ -11,6 +11,7 @@
|
|||||||
|
|
||||||
import type { FirewallRule } from '../config.js';
|
import type { FirewallRule } from '../config.js';
|
||||||
import type { FirewallLogEntry } from '../engine.js';
|
import type { FirewallLogEntry } from '../engine.js';
|
||||||
|
import type { PortRuleProtocol } from './personalFirewall.js';
|
||||||
import { formatIPv4, maskAddress, parseIPv4 } from '../ipv4.js';
|
import { formatIPv4, maskAddress, parseIPv4 } from '../ipv4.js';
|
||||||
import type { RouteRow } from '../routeTable.js';
|
import type { RouteRow } from '../routeTable.js';
|
||||||
import { DIRECTLY_CONNECTED_SOURCE_MARKER } from '../config.js';
|
import { DIRECTLY_CONNECTED_SOURCE_MARKER } from '../config.js';
|
||||||
@@ -40,6 +41,8 @@ interface PageTexts {
|
|||||||
readonly logBody: string;
|
readonly logBody: string;
|
||||||
readonly ruleMatched: string;
|
readonly ruleMatched: string;
|
||||||
readonly icmpDropped: string;
|
readonly icmpDropped: string;
|
||||||
|
readonly portRuleAdded: string;
|
||||||
|
readonly portRuleRemoved: string;
|
||||||
readonly accept: string;
|
readonly accept: string;
|
||||||
readonly drop: string;
|
readonly drop: string;
|
||||||
readonly ripHint: string;
|
readonly ripHint: string;
|
||||||
@@ -57,6 +60,9 @@ const TEXTS: Record<PageLanguage, PageTexts> = {
|
|||||||
/** sw_firewall_msg8, firewallthread_msg1, jfirewalldialog_msg33, msg34. */
|
/** sw_firewall_msg8, firewallthread_msg1, jfirewalldialog_msg33, msg34. */
|
||||||
ruleMatched: 'Firewall-Regel hat gegriffen:',
|
ruleMatched: 'Firewall-Regel hat gegriffen:',
|
||||||
icmpDropped: 'ICMP-Paket verworfen:',
|
icmpDropped: 'ICMP-Paket verworfen:',
|
||||||
|
/** sw_firewall_msg5, msg7: a computer's window writes them (no router has one). */
|
||||||
|
portRuleAdded: 'Portregel wurde hinzugefügt:',
|
||||||
|
portRuleRemoved: 'Portregel wurde entfernt.',
|
||||||
accept: 'akzeptieren',
|
accept: 'akzeptieren',
|
||||||
drop: 'verwerfen',
|
drop: 'verwerfen',
|
||||||
/** sw_vermittlungweb_msg1, msg3, msg4, msg5, msg6. */
|
/** sw_vermittlungweb_msg1, msg3, msg4, msg5, msg6. */
|
||||||
@@ -71,6 +77,8 @@ const TEXTS: Record<PageLanguage, PageTexts> = {
|
|||||||
logBody: '\n\t</head>\n\t<body>\n\t\t<h2>Log-data set entry of the firewall</h2>',
|
logBody: '\n\t</head>\n\t<body>\n\t\t<h2>Log-data set entry of the firewall</h2>',
|
||||||
ruleMatched: 'Firewall rule matched:',
|
ruleMatched: 'Firewall rule matched:',
|
||||||
icmpDropped: 'Dropped ICMP packet:',
|
icmpDropped: 'Dropped ICMP packet:',
|
||||||
|
portRuleAdded: 'Port rule is added:',
|
||||||
|
portRuleRemoved: 'Delete port rule.',
|
||||||
accept: 'accept',
|
accept: 'accept',
|
||||||
drop: 'drop',
|
drop: 'drop',
|
||||||
ripHint: 'This table is generated and updated by the Routing Information Protocol (RIP).',
|
ripHint: 'This table is generated and updated by the Routing Information Protocol (RIP).',
|
||||||
@@ -113,11 +121,19 @@ export function firewallRuleText(rule: FirewallRule, firstInterface: { readonly
|
|||||||
return text + (rule.action === 'accept' ? 'ACCEPT' : 'DROP');
|
return text + (rule.action === 'accept' ? 'ACCEPT' : 'DROP');
|
||||||
}
|
}
|
||||||
|
|
||||||
/** A line of the firewall's log, as Firewall tells its observers. */
|
/** The window's protocol list (GUIApplicationFirewallWindow): its items name a port rule in the log. */
|
||||||
|
export const PORT_RULE_PROTOCOL_NAMES: Readonly<Record<PortRuleProtocol, string>> = { tcp: 'TCP', udp: 'UDP', all: '*' };
|
||||||
|
|
||||||
|
/** A line of the firewall's log, as Firewall tells its observers (and a computer's window writes). */
|
||||||
export function firewallLogText(entry: FirewallLogEntry, language: PageLanguage): string {
|
export function firewallLogText(entry: FirewallLogEntry, language: PageLanguage): string {
|
||||||
const texts = TEXTS[language];
|
const texts = TEXTS[language];
|
||||||
if (entry.kind === 'icmpDropped') {
|
switch (entry.kind) {
|
||||||
|
case 'icmpDropped':
|
||||||
return `${texts.icmpDropped} ${entry.senderIPAddress} -> ${entry.receiverIPAddress} (code: ${entry.code}, type: ${entry.type})`;
|
return `${texts.icmpDropped} ${entry.senderIPAddress} -> ${entry.receiverIPAddress} (code: ${entry.code}, type: ${entry.type})`;
|
||||||
|
case 'portRuleAdded':
|
||||||
|
return `${texts.portRuleAdded} ${entry.port}/${PORT_RULE_PROTOCOL_NAMES[entry.protocol]}`;
|
||||||
|
case 'portRuleRemoved':
|
||||||
|
return texts.portRuleRemoved;
|
||||||
}
|
}
|
||||||
return `${texts.ruleMatched} #${entry.ruleNumber} (${firewallRuleText(entry.rule, entry.firstInterface)}) -> ${firewallActionText(entry.accepted ? 'accept' : 'drop', language)}`;
|
return `${texts.ruleMatched} #${entry.ruleNumber} (${firewallRuleText(entry.rule, entry.firstInterface)}) -> ${firewallActionText(entry.accepted ? 'accept' : 'drop', language)}`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -124,6 +124,8 @@ import { EmailServer } from './apps/emailServer.js';
|
|||||||
import { Terminal } from './apps/terminal.js';
|
import { Terminal } from './apps/terminal.js';
|
||||||
import { firewallLogPage, forwardingTablePage, ripTablePage, routerIndexPage } from './apps/routerPages.js';
|
import { firewallLogPage, forwardingTablePage, ripTablePage, routerIndexPage } from './apps/routerPages.js';
|
||||||
import { applyFirewallSettings, firewallSettingsPage } from './apps/firewallSettings.js';
|
import { applyFirewallSettings, firewallSettingsPage } from './apps/firewallSettings.js';
|
||||||
|
import { withoutRule, withPortRule, type PortRuleProtocol } from './apps/personalFirewall.js';
|
||||||
|
import type { LogLine } from './apps/program.js';
|
||||||
import { parentPath, VirtualFileSystem } from '../model/virtualFileSystem.js';
|
import { parentPath, VirtualFileSystem } from '../model/virtualFileSystem.js';
|
||||||
import { clientAccountFileText, EMAIL_CLIENT_ACCOUNT_FILE, EMAIL_SERVER_ACCOUNTS_FILE, serverAccountsFileText } from './emailAccountsFile.js';
|
import { clientAccountFileText, EMAIL_CLIENT_ACCOUNT_FILE, EMAIL_SERVER_ACCOUNTS_FILE, serverAccountsFileText } from './emailAccountsFile.js';
|
||||||
import { GNUTELLA_FOLDER, GnutellaPeer } from './apps/gnutella.js';
|
import { GNUTELLA_FOLDER, GnutellaPeer } from './apps/gnutella.js';
|
||||||
@@ -320,10 +322,11 @@ export interface FirewallDecision {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* A line of a firewall's log (Firewall.benachrichtigeBeobachter, shown by
|
* A line of a firewall's log (Firewall.benachrichtigeBeobachter, shown by
|
||||||
* the router's page "log"): a rule that matched a TCP or UDP packet, with
|
* the router's page "log" and a computer's firewall window): a rule that
|
||||||
* the node's first interface (the rule's text names its network for "any
|
* matched a TCP or UDP packet, with the node's first interface (the rule's
|
||||||
* directly connected network"), or an ICMP packet dropped by the general
|
* text names its network for "any directly connected network"), or an ICMP
|
||||||
* setting.
|
* packet dropped by the general setting; and, from a computer's window
|
||||||
|
* (Java writes them into the window only), a port rule added or removed.
|
||||||
*/
|
*/
|
||||||
export type FirewallLogEntry =
|
export type FirewallLogEntry =
|
||||||
| {
|
| {
|
||||||
@@ -333,7 +336,9 @@ export type FirewallLogEntry =
|
|||||||
readonly firstInterface: { readonly ipAddress: string; readonly subnetMask: string } | undefined;
|
readonly firstInterface: { readonly ipAddress: string; readonly subnetMask: string } | undefined;
|
||||||
readonly accepted: boolean;
|
readonly accepted: boolean;
|
||||||
}
|
}
|
||||||
| { readonly kind: 'icmpDropped'; readonly senderIPAddress: string; readonly receiverIPAddress: string; readonly type: number; readonly code: number };
|
| { readonly kind: 'icmpDropped'; readonly senderIPAddress: string; readonly receiverIPAddress: string; readonly type: number; readonly code: number }
|
||||||
|
| { readonly kind: 'portRuleAdded'; readonly port: number; readonly protocol: PortRuleProtocol }
|
||||||
|
| { readonly kind: 'portRuleRemoved' };
|
||||||
|
|
||||||
export type RuntimePhase = 'stopped' | 'running';
|
export type RuntimePhase = 'stopped' | 'running';
|
||||||
|
|
||||||
@@ -945,7 +950,7 @@ export class NetworkRuntimeEngine {
|
|||||||
/** The time of day a program writes into a mail (Java's new Date()). */
|
/** The time of day a program writes into a mail (Java's new Date()). */
|
||||||
private readonly wallClock: () => number;
|
private readonly wallClock: () => number;
|
||||||
/** Each node's firewall log; it outlives a stop and a new start, as Java's log page does. */
|
/** Each node's firewall log; it outlives a stop and a new start, as Java's log page does. */
|
||||||
private readonly firewallLogsByNodeId = new Map<Id, FirewallLogEntry[]>();
|
private readonly firewallLogsByNodeId = new Map<Id, LogLine<FirewallLogEntry>[]>();
|
||||||
/** Accepted connections a program has taken (acceptTcpSteps). */
|
/** Accepted connections a program has taken (acceptTcpSteps). */
|
||||||
private readonly handedOutTcpSocketIds = new Set<Id>();
|
private readonly handedOutTcpSocketIds = new Set<Id>();
|
||||||
private nextApplicationWakeId = 1;
|
private nextApplicationWakeId = 1;
|
||||||
@@ -1127,12 +1132,12 @@ export class NetworkRuntimeEngine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** The node's firewall log, since the engine was made. */
|
/** The node's firewall log, since the engine was made. */
|
||||||
firewallLog(nodeId: Id): readonly FirewallLogEntry[] {
|
firewallLog(nodeId: Id): readonly LogLine<FirewallLogEntry>[] {
|
||||||
return this.firewallLogsByNodeId.get(nodeId) ?? [];
|
return this.firewallLogsByNodeId.get(nodeId) ?? [];
|
||||||
}
|
}
|
||||||
|
|
||||||
private logFirewall(nodeId: Id, entry: FirewallLogEntry): void {
|
private logFirewall(nodeId: Id, entry: FirewallLogEntry): void {
|
||||||
getOrCreate(this.firewallLogsByNodeId, nodeId, () => []).push(entry);
|
getOrCreate(this.firewallLogsByNodeId, nodeId, () => []).push({ ...entry, timeMilliseconds: this.stateValue.currentTimeMilliseconds });
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Firewall.notifyRuleApplication. */
|
/** Firewall.notifyRuleApplication. */
|
||||||
@@ -5615,6 +5620,31 @@ export class NetworkRuntimeEngine {
|
|||||||
return this.stateValue.topologySnapshot.firewallConfigurationsByNodeId.get(nodeId) ?? firewallConfiguration();
|
return this.stateValue.topologySnapshot.firewallConfigurationsByNodeId.get(nodeId) ?? firewallConfiguration();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** A computer's firewall, when it has one installed (a router's is no program; its page changes it). */
|
||||||
|
personalFirewall(nodeId: Id): FirewallConfiguration | undefined {
|
||||||
|
const node = this.nodeSnapshot(nodeId);
|
||||||
|
return node !== undefined && isPcClassEndpoint(node.kind) ? this.stateValue.topologySnapshot.firewallConfigurationsByNodeId.get(nodeId) : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The window's "Hinzufügen": the port opened, written into its log; false for a computer without a firewall. */
|
||||||
|
addFirewallPortRule(nodeId: Id, port: number, protocol: PortRuleProtocol, sameNetworkOnly: boolean): boolean {
|
||||||
|
const configuration = this.personalFirewall(nodeId);
|
||||||
|
if (configuration === undefined) return false;
|
||||||
|
this.setFirewallConfiguration(nodeId, withPortRule(configuration, port, protocol, sameNetworkOnly));
|
||||||
|
this.logFirewall(nodeId, { kind: 'portRuleAdded', port, protocol });
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The window's "Löschen": the rule of that row removed, written into its log; false when there is none. */
|
||||||
|
removeFirewallRule(nodeId: Id, index: number): boolean {
|
||||||
|
const configuration = this.personalFirewall(nodeId);
|
||||||
|
const changed = configuration === undefined ? undefined : withoutRule(configuration, index);
|
||||||
|
if (changed === undefined) return false;
|
||||||
|
this.setFirewallConfiguration(nodeId, changed);
|
||||||
|
this.logFirewall(nodeId, { kind: 'portRuleRemoved' });
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
setFirewallConfiguration(nodeId: Id, configuration: FirewallConfiguration): void {
|
setFirewallConfiguration(nodeId: Id, configuration: FirewallConfiguration): void {
|
||||||
const configurations = new Map(this.stateValue.topologySnapshot.firewallConfigurationsByNodeId);
|
const configurations = new Map(this.stateValue.topologySnapshot.firewallConfigurationsByNodeId);
|
||||||
configurations.set(nodeId, configuration);
|
configurations.set(nodeId, configuration);
|
||||||
|
|||||||
@@ -0,0 +1,620 @@
|
|||||||
|
{
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"step": "open",
|
||||||
|
"traffic": {
|
||||||
|
"local": {
|
||||||
|
"ping": "ICMP 0/0 from 10.0.0.10",
|
||||||
|
"tcp": "TimeOutException: Zeitüberschreitung beim Verbindungsaufbau",
|
||||||
|
"dns": "TimeOutException"
|
||||||
|
},
|
||||||
|
"remote": {
|
||||||
|
"ping": "ICMP 0/0 from 10.0.0.10",
|
||||||
|
"tcp": "TimeOutException: Zeitüberschreitung beim Verbindungsaufbau",
|
||||||
|
"dns": "TimeOutException"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"active": {
|
||||||
|
"selected": true,
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
"icmp": {
|
||||||
|
"selected": true,
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
"udp": {
|
||||||
|
"selected": true,
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
"port": "",
|
||||||
|
"protocol": "TCP",
|
||||||
|
"sender": "Alle Absender",
|
||||||
|
"rows": [],
|
||||||
|
"log": "",
|
||||||
|
"logEditable": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step": "add port abc",
|
||||||
|
"active": {
|
||||||
|
"selected": true,
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
"icmp": {
|
||||||
|
"selected": true,
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
"udp": {
|
||||||
|
"selected": true,
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
"port": "abc",
|
||||||
|
"protocol": "TCP",
|
||||||
|
"sender": "Alle Absender",
|
||||||
|
"rows": [],
|
||||||
|
"log": "",
|
||||||
|
"logEditable": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step": "add an empty port",
|
||||||
|
"active": {
|
||||||
|
"selected": true,
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
"icmp": {
|
||||||
|
"selected": true,
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
"udp": {
|
||||||
|
"selected": true,
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
"port": "",
|
||||||
|
"protocol": "TCP",
|
||||||
|
"sender": "Alle Absender",
|
||||||
|
"rows": [],
|
||||||
|
"log": "",
|
||||||
|
"logEditable": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step": "add 55555 TCP for all senders",
|
||||||
|
"traffic": {
|
||||||
|
"local": {
|
||||||
|
"ping": "ICMP 0/0 from 10.0.0.10",
|
||||||
|
"tcp": "connected",
|
||||||
|
"dns": "TimeOutException"
|
||||||
|
},
|
||||||
|
"remote": {
|
||||||
|
"ping": "ICMP 0/0 from 10.0.0.10",
|
||||||
|
"tcp": "connected",
|
||||||
|
"dns": "TimeOutException"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"active": {
|
||||||
|
"selected": true,
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
"icmp": {
|
||||||
|
"selected": true,
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
"udp": {
|
||||||
|
"selected": true,
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
"port": "",
|
||||||
|
"protocol": "TCP",
|
||||||
|
"sender": "Alle Absender",
|
||||||
|
"rows": [
|
||||||
|
[
|
||||||
|
"TCP",
|
||||||
|
"55555",
|
||||||
|
"Alle Absender"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"log": "Portregel wurde hinzugefügt: 55555/TCP\nFirewall-Regel hat gegriffen: #1 (*/* -> */*; TCP:55555 => ACCEPT) -> akzeptieren\nFirewall-Regel hat gegriffen: #1 (*/* -> */*; TCP:55555 => ACCEPT) -> akzeptieren\n",
|
||||||
|
"logEditable": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step": "add 53 UDP for the same network",
|
||||||
|
"traffic": {
|
||||||
|
"local": {
|
||||||
|
"ping": "ICMP 0/0 from 10.0.0.10",
|
||||||
|
"tcp": "connected",
|
||||||
|
"dns": "10.0.0.10"
|
||||||
|
},
|
||||||
|
"remote": {
|
||||||
|
"ping": "ICMP 0/0 from 10.0.0.10",
|
||||||
|
"tcp": "connected",
|
||||||
|
"dns": "TimeOutException"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"active": {
|
||||||
|
"selected": true,
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
"icmp": {
|
||||||
|
"selected": true,
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
"udp": {
|
||||||
|
"selected": true,
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
"port": "",
|
||||||
|
"protocol": "UDP",
|
||||||
|
"sender": "Absender im gleichen Rechnernetz",
|
||||||
|
"rows": [
|
||||||
|
[
|
||||||
|
"TCP",
|
||||||
|
"55555",
|
||||||
|
"Alle Absender"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"UDP",
|
||||||
|
"53",
|
||||||
|
"Nur lokales Rechnernetz"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"log": "Portregel wurde hinzugefügt: 55555/TCP\nFirewall-Regel hat gegriffen: #1 (*/* -> */*; TCP:55555 => ACCEPT) -> akzeptieren\nFirewall-Regel hat gegriffen: #1 (*/* -> */*; TCP:55555 => ACCEPT) -> akzeptieren\nPortregel wurde hinzugefügt: 53/UDP\nFirewall-Regel hat gegriffen: #1 (*/* -> */*; TCP:55555 => ACCEPT) -> akzeptieren\nFirewall-Regel hat gegriffen: #2 (10.0.0.0/255.255.255.0 -> */*; UDP:53 => ACCEPT) -> akzeptieren\nFirewall-Regel hat gegriffen: #1 (*/* -> */*; TCP:55555 => ACCEPT) -> akzeptieren\n",
|
||||||
|
"logEditable": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step": "ICMP no longer allowed",
|
||||||
|
"traffic": {
|
||||||
|
"local": {
|
||||||
|
"ping": "null",
|
||||||
|
"tcp": "connected",
|
||||||
|
"dns": "10.0.0.10"
|
||||||
|
},
|
||||||
|
"remote": {
|
||||||
|
"ping": "null",
|
||||||
|
"tcp": "connected",
|
||||||
|
"dns": "TimeOutException"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"active": {
|
||||||
|
"selected": true,
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
"icmp": {
|
||||||
|
"selected": false,
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
"udp": {
|
||||||
|
"selected": true,
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
"port": "",
|
||||||
|
"protocol": "UDP",
|
||||||
|
"sender": "Absender im gleichen Rechnernetz",
|
||||||
|
"rows": [
|
||||||
|
[
|
||||||
|
"TCP",
|
||||||
|
"55555",
|
||||||
|
"Alle Absender"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"UDP",
|
||||||
|
"53",
|
||||||
|
"Nur lokales Rechnernetz"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"log": "Portregel wurde hinzugefügt: 55555/TCP\nFirewall-Regel hat gegriffen: #1 (*/* -> */*; TCP:55555 => ACCEPT) -> akzeptieren\nFirewall-Regel hat gegriffen: #1 (*/* -> */*; TCP:55555 => ACCEPT) -> akzeptieren\nPortregel wurde hinzugefügt: 53/UDP\nFirewall-Regel hat gegriffen: #1 (*/* -> */*; TCP:55555 => ACCEPT) -> akzeptieren\nFirewall-Regel hat gegriffen: #2 (10.0.0.0/255.255.255.0 -> */*; UDP:53 => ACCEPT) -> akzeptieren\nFirewall-Regel hat gegriffen: #1 (*/* -> */*; TCP:55555 => ACCEPT) -> akzeptieren\nICMP-Paket verworfen: 10.0.0.2 -> 10.0.0.10 (code: 0, type: 8)\nFirewall-Regel hat gegriffen: #1 (*/* -> */*; TCP:55555 => ACCEPT) -> akzeptieren\nFirewall-Regel hat gegriffen: #2 (10.0.0.0/255.255.255.0 -> */*; UDP:53 => ACCEPT) -> akzeptieren\nICMP-Paket verworfen: 20.0.0.2 -> 10.0.0.10 (code: 0, type: 8)\nFirewall-Regel hat gegriffen: #1 (*/* -> */*; TCP:55555 => ACCEPT) -> akzeptieren\n",
|
||||||
|
"logEditable": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step": "UDP filter off",
|
||||||
|
"traffic": {
|
||||||
|
"local": {
|
||||||
|
"ping": "null",
|
||||||
|
"tcp": "connected",
|
||||||
|
"dns": "10.0.0.10"
|
||||||
|
},
|
||||||
|
"remote": {
|
||||||
|
"ping": "null",
|
||||||
|
"tcp": "connected",
|
||||||
|
"dns": "10.0.0.10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"active": {
|
||||||
|
"selected": true,
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
"icmp": {
|
||||||
|
"selected": false,
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
"udp": {
|
||||||
|
"selected": false,
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
"port": "",
|
||||||
|
"protocol": "UDP",
|
||||||
|
"sender": "Absender im gleichen Rechnernetz",
|
||||||
|
"rows": [
|
||||||
|
[
|
||||||
|
"TCP",
|
||||||
|
"55555",
|
||||||
|
"Alle Absender"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"UDP",
|
||||||
|
"53",
|
||||||
|
"Nur lokales Rechnernetz"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"log": "Portregel wurde hinzugefügt: 55555/TCP\nFirewall-Regel hat gegriffen: #1 (*/* -> */*; TCP:55555 => ACCEPT) -> akzeptieren\nFirewall-Regel hat gegriffen: #1 (*/* -> */*; TCP:55555 => ACCEPT) -> akzeptieren\nPortregel wurde hinzugefügt: 53/UDP\nFirewall-Regel hat gegriffen: #1 (*/* -> */*; TCP:55555 => ACCEPT) -> akzeptieren\nFirewall-Regel hat gegriffen: #2 (10.0.0.0/255.255.255.0 -> */*; UDP:53 => ACCEPT) -> akzeptieren\nFirewall-Regel hat gegriffen: #1 (*/* -> */*; TCP:55555 => ACCEPT) -> akzeptieren\nICMP-Paket verworfen: 10.0.0.2 -> 10.0.0.10 (code: 0, type: 8)\nFirewall-Regel hat gegriffen: #1 (*/* -> */*; TCP:55555 => ACCEPT) -> akzeptieren\nFirewall-Regel hat gegriffen: #2 (10.0.0.0/255.255.255.0 -> */*; UDP:53 => ACCEPT) -> akzeptieren\nICMP-Paket verworfen: 20.0.0.2 -> 10.0.0.10 (code: 0, type: 8)\nFirewall-Regel hat gegriffen: #1 (*/* -> */*; TCP:55555 => ACCEPT) -> akzeptieren\nICMP-Paket verworfen: 10.0.0.2 -> 10.0.0.10 (code: 0, type: 8)\nFirewall-Regel hat gegriffen: #1 (*/* -> */*; TCP:55555 => ACCEPT) -> akzeptieren\nICMP-Paket verworfen: 20.0.0.2 -> 10.0.0.10 (code: 0, type: 8)\nFirewall-Regel hat gegriffen: #1 (*/* -> */*; TCP:55555 => ACCEPT) -> akzeptieren\n",
|
||||||
|
"logEditable": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step": "add -1 for all protocols",
|
||||||
|
"active": {
|
||||||
|
"selected": true,
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
"icmp": {
|
||||||
|
"selected": false,
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
"udp": {
|
||||||
|
"selected": false,
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
"port": "",
|
||||||
|
"protocol": "*",
|
||||||
|
"sender": "Alle Absender",
|
||||||
|
"rows": [
|
||||||
|
[
|
||||||
|
"TCP",
|
||||||
|
"55555",
|
||||||
|
"Alle Absender"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"UDP",
|
||||||
|
"53",
|
||||||
|
"Nur lokales Rechnernetz"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"*",
|
||||||
|
"",
|
||||||
|
"Alle Absender"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"log": "Portregel wurde hinzugefügt: 55555/TCP\nFirewall-Regel hat gegriffen: #1 (*/* -> */*; TCP:55555 => ACCEPT) -> akzeptieren\nFirewall-Regel hat gegriffen: #1 (*/* -> */*; TCP:55555 => ACCEPT) -> akzeptieren\nPortregel wurde hinzugefügt: 53/UDP\nFirewall-Regel hat gegriffen: #1 (*/* -> */*; TCP:55555 => ACCEPT) -> akzeptieren\nFirewall-Regel hat gegriffen: #2 (10.0.0.0/255.255.255.0 -> */*; UDP:53 => ACCEPT) -> akzeptieren\nFirewall-Regel hat gegriffen: #1 (*/* -> */*; TCP:55555 => ACCEPT) -> akzeptieren\nICMP-Paket verworfen: 10.0.0.2 -> 10.0.0.10 (code: 0, type: 8)\nFirewall-Regel hat gegriffen: #1 (*/* -> */*; TCP:55555 => ACCEPT) -> akzeptieren\nFirewall-Regel hat gegriffen: #2 (10.0.0.0/255.255.255.0 -> */*; UDP:53 => ACCEPT) -> akzeptieren\nICMP-Paket verworfen: 20.0.0.2 -> 10.0.0.10 (code: 0, type: 8)\nFirewall-Regel hat gegriffen: #1 (*/* -> */*; TCP:55555 => ACCEPT) -> akzeptieren\nICMP-Paket verworfen: 10.0.0.2 -> 10.0.0.10 (code: 0, type: 8)\nFirewall-Regel hat gegriffen: #1 (*/* -> */*; TCP:55555 => ACCEPT) -> akzeptieren\nICMP-Paket verworfen: 20.0.0.2 -> 10.0.0.10 (code: 0, type: 8)\nFirewall-Regel hat gegriffen: #1 (*/* -> */*; TCP:55555 => ACCEPT) -> akzeptieren\nPortregel wurde hinzugefügt: -1/*\n",
|
||||||
|
"logEditable": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step": "add 0 TCP",
|
||||||
|
"active": {
|
||||||
|
"selected": true,
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
"icmp": {
|
||||||
|
"selected": false,
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
"udp": {
|
||||||
|
"selected": false,
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
"port": "",
|
||||||
|
"protocol": "TCP",
|
||||||
|
"sender": "Alle Absender",
|
||||||
|
"rows": [
|
||||||
|
[
|
||||||
|
"TCP",
|
||||||
|
"55555",
|
||||||
|
"Alle Absender"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"UDP",
|
||||||
|
"53",
|
||||||
|
"Nur lokales Rechnernetz"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"*",
|
||||||
|
"",
|
||||||
|
"Alle Absender"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"TCP",
|
||||||
|
"0",
|
||||||
|
"Alle Absender"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"log": "Portregel wurde hinzugefügt: 55555/TCP\nFirewall-Regel hat gegriffen: #1 (*/* -> */*; TCP:55555 => ACCEPT) -> akzeptieren\nFirewall-Regel hat gegriffen: #1 (*/* -> */*; TCP:55555 => ACCEPT) -> akzeptieren\nPortregel wurde hinzugefügt: 53/UDP\nFirewall-Regel hat gegriffen: #1 (*/* -> */*; TCP:55555 => ACCEPT) -> akzeptieren\nFirewall-Regel hat gegriffen: #2 (10.0.0.0/255.255.255.0 -> */*; UDP:53 => ACCEPT) -> akzeptieren\nFirewall-Regel hat gegriffen: #1 (*/* -> */*; TCP:55555 => ACCEPT) -> akzeptieren\nICMP-Paket verworfen: 10.0.0.2 -> 10.0.0.10 (code: 0, type: 8)\nFirewall-Regel hat gegriffen: #1 (*/* -> */*; TCP:55555 => ACCEPT) -> akzeptieren\nFirewall-Regel hat gegriffen: #2 (10.0.0.0/255.255.255.0 -> */*; UDP:53 => ACCEPT) -> akzeptieren\nICMP-Paket verworfen: 20.0.0.2 -> 10.0.0.10 (code: 0, type: 8)\nFirewall-Regel hat gegriffen: #1 (*/* -> */*; TCP:55555 => ACCEPT) -> akzeptieren\nICMP-Paket verworfen: 10.0.0.2 -> 10.0.0.10 (code: 0, type: 8)\nFirewall-Regel hat gegriffen: #1 (*/* -> */*; TCP:55555 => ACCEPT) -> akzeptieren\nICMP-Paket verworfen: 20.0.0.2 -> 10.0.0.10 (code: 0, type: 8)\nFirewall-Regel hat gegriffen: #1 (*/* -> */*; TCP:55555 => ACCEPT) -> akzeptieren\nPortregel wurde hinzugefügt: -1/*\nPortregel wurde hinzugefügt: 0/TCP\n",
|
||||||
|
"logEditable": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step": "add 70000 TCP for the same network",
|
||||||
|
"active": {
|
||||||
|
"selected": true,
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
"icmp": {
|
||||||
|
"selected": false,
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
"udp": {
|
||||||
|
"selected": false,
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
"port": "",
|
||||||
|
"protocol": "TCP",
|
||||||
|
"sender": "Absender im gleichen Rechnernetz",
|
||||||
|
"rows": [
|
||||||
|
[
|
||||||
|
"TCP",
|
||||||
|
"55555",
|
||||||
|
"Alle Absender"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"UDP",
|
||||||
|
"53",
|
||||||
|
"Nur lokales Rechnernetz"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"*",
|
||||||
|
"",
|
||||||
|
"Alle Absender"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"TCP",
|
||||||
|
"0",
|
||||||
|
"Alle Absender"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"TCP",
|
||||||
|
"70000",
|
||||||
|
"Nur lokales Rechnernetz"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"log": "Portregel wurde hinzugefügt: 55555/TCP\nFirewall-Regel hat gegriffen: #1 (*/* -> */*; TCP:55555 => ACCEPT) -> akzeptieren\nFirewall-Regel hat gegriffen: #1 (*/* -> */*; TCP:55555 => ACCEPT) -> akzeptieren\nPortregel wurde hinzugefügt: 53/UDP\nFirewall-Regel hat gegriffen: #1 (*/* -> */*; TCP:55555 => ACCEPT) -> akzeptieren\nFirewall-Regel hat gegriffen: #2 (10.0.0.0/255.255.255.0 -> */*; UDP:53 => ACCEPT) -> akzeptieren\nFirewall-Regel hat gegriffen: #1 (*/* -> */*; TCP:55555 => ACCEPT) -> akzeptieren\nICMP-Paket verworfen: 10.0.0.2 -> 10.0.0.10 (code: 0, type: 8)\nFirewall-Regel hat gegriffen: #1 (*/* -> */*; TCP:55555 => ACCEPT) -> akzeptieren\nFirewall-Regel hat gegriffen: #2 (10.0.0.0/255.255.255.0 -> */*; UDP:53 => ACCEPT) -> akzeptieren\nICMP-Paket verworfen: 20.0.0.2 -> 10.0.0.10 (code: 0, type: 8)\nFirewall-Regel hat gegriffen: #1 (*/* -> */*; TCP:55555 => ACCEPT) -> akzeptieren\nICMP-Paket verworfen: 10.0.0.2 -> 10.0.0.10 (code: 0, type: 8)\nFirewall-Regel hat gegriffen: #1 (*/* -> */*; TCP:55555 => ACCEPT) -> akzeptieren\nICMP-Paket verworfen: 20.0.0.2 -> 10.0.0.10 (code: 0, type: 8)\nFirewall-Regel hat gegriffen: #1 (*/* -> */*; TCP:55555 => ACCEPT) -> akzeptieren\nPortregel wurde hinzugefügt: -1/*\nPortregel wurde hinzugefügt: 0/TCP\nPortregel wurde hinzugefügt: 70000/TCP\n",
|
||||||
|
"logEditable": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step": "add a port after a space",
|
||||||
|
"active": {
|
||||||
|
"selected": true,
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
"icmp": {
|
||||||
|
"selected": false,
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
"udp": {
|
||||||
|
"selected": false,
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
"port": " 80",
|
||||||
|
"protocol": "TCP",
|
||||||
|
"sender": "Alle Absender",
|
||||||
|
"rows": [
|
||||||
|
[
|
||||||
|
"TCP",
|
||||||
|
"55555",
|
||||||
|
"Alle Absender"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"UDP",
|
||||||
|
"53",
|
||||||
|
"Nur lokales Rechnernetz"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"*",
|
||||||
|
"",
|
||||||
|
"Alle Absender"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"TCP",
|
||||||
|
"0",
|
||||||
|
"Alle Absender"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"TCP",
|
||||||
|
"70000",
|
||||||
|
"Nur lokales Rechnernetz"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"log": "Portregel wurde hinzugefügt: 55555/TCP\nFirewall-Regel hat gegriffen: #1 (*/* -> */*; TCP:55555 => ACCEPT) -> akzeptieren\nFirewall-Regel hat gegriffen: #1 (*/* -> */*; TCP:55555 => ACCEPT) -> akzeptieren\nPortregel wurde hinzugefügt: 53/UDP\nFirewall-Regel hat gegriffen: #1 (*/* -> */*; TCP:55555 => ACCEPT) -> akzeptieren\nFirewall-Regel hat gegriffen: #2 (10.0.0.0/255.255.255.0 -> */*; UDP:53 => ACCEPT) -> akzeptieren\nFirewall-Regel hat gegriffen: #1 (*/* -> */*; TCP:55555 => ACCEPT) -> akzeptieren\nICMP-Paket verworfen: 10.0.0.2 -> 10.0.0.10 (code: 0, type: 8)\nFirewall-Regel hat gegriffen: #1 (*/* -> */*; TCP:55555 => ACCEPT) -> akzeptieren\nFirewall-Regel hat gegriffen: #2 (10.0.0.0/255.255.255.0 -> */*; UDP:53 => ACCEPT) -> akzeptieren\nICMP-Paket verworfen: 20.0.0.2 -> 10.0.0.10 (code: 0, type: 8)\nFirewall-Regel hat gegriffen: #1 (*/* -> */*; TCP:55555 => ACCEPT) -> akzeptieren\nICMP-Paket verworfen: 10.0.0.2 -> 10.0.0.10 (code: 0, type: 8)\nFirewall-Regel hat gegriffen: #1 (*/* -> */*; TCP:55555 => ACCEPT) -> akzeptieren\nICMP-Paket verworfen: 20.0.0.2 -> 10.0.0.10 (code: 0, type: 8)\nFirewall-Regel hat gegriffen: #1 (*/* -> */*; TCP:55555 => ACCEPT) -> akzeptieren\nPortregel wurde hinzugefügt: -1/*\nPortregel wurde hinzugefügt: 0/TCP\nPortregel wurde hinzugefügt: 70000/TCP\n",
|
||||||
|
"logEditable": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step": "delete with no row chosen",
|
||||||
|
"active": {
|
||||||
|
"selected": true,
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
"icmp": {
|
||||||
|
"selected": false,
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
"udp": {
|
||||||
|
"selected": false,
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
"port": " 80",
|
||||||
|
"protocol": "TCP",
|
||||||
|
"sender": "Alle Absender",
|
||||||
|
"rows": [
|
||||||
|
[
|
||||||
|
"TCP",
|
||||||
|
"55555",
|
||||||
|
"Alle Absender"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"UDP",
|
||||||
|
"53",
|
||||||
|
"Nur lokales Rechnernetz"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"*",
|
||||||
|
"",
|
||||||
|
"Alle Absender"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"TCP",
|
||||||
|
"0",
|
||||||
|
"Alle Absender"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"TCP",
|
||||||
|
"70000",
|
||||||
|
"Nur lokales Rechnernetz"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"log": "Portregel wurde hinzugefügt: 55555/TCP\nFirewall-Regel hat gegriffen: #1 (*/* -> */*; TCP:55555 => ACCEPT) -> akzeptieren\nFirewall-Regel hat gegriffen: #1 (*/* -> */*; TCP:55555 => ACCEPT) -> akzeptieren\nPortregel wurde hinzugefügt: 53/UDP\nFirewall-Regel hat gegriffen: #1 (*/* -> */*; TCP:55555 => ACCEPT) -> akzeptieren\nFirewall-Regel hat gegriffen: #2 (10.0.0.0/255.255.255.0 -> */*; UDP:53 => ACCEPT) -> akzeptieren\nFirewall-Regel hat gegriffen: #1 (*/* -> */*; TCP:55555 => ACCEPT) -> akzeptieren\nICMP-Paket verworfen: 10.0.0.2 -> 10.0.0.10 (code: 0, type: 8)\nFirewall-Regel hat gegriffen: #1 (*/* -> */*; TCP:55555 => ACCEPT) -> akzeptieren\nFirewall-Regel hat gegriffen: #2 (10.0.0.0/255.255.255.0 -> */*; UDP:53 => ACCEPT) -> akzeptieren\nICMP-Paket verworfen: 20.0.0.2 -> 10.0.0.10 (code: 0, type: 8)\nFirewall-Regel hat gegriffen: #1 (*/* -> */*; TCP:55555 => ACCEPT) -> akzeptieren\nICMP-Paket verworfen: 10.0.0.2 -> 10.0.0.10 (code: 0, type: 8)\nFirewall-Regel hat gegriffen: #1 (*/* -> */*; TCP:55555 => ACCEPT) -> akzeptieren\nICMP-Paket verworfen: 20.0.0.2 -> 10.0.0.10 (code: 0, type: 8)\nFirewall-Regel hat gegriffen: #1 (*/* -> */*; TCP:55555 => ACCEPT) -> akzeptieren\nPortregel wurde hinzugefügt: -1/*\nPortregel wurde hinzugefügt: 0/TCP\nPortregel wurde hinzugefügt: 70000/TCP\n",
|
||||||
|
"logEditable": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step": "delete row 0",
|
||||||
|
"traffic": {
|
||||||
|
"local": {
|
||||||
|
"ping": "null",
|
||||||
|
"tcp": "connected",
|
||||||
|
"dns": "10.0.0.10"
|
||||||
|
},
|
||||||
|
"remote": {
|
||||||
|
"ping": "null",
|
||||||
|
"tcp": "connected",
|
||||||
|
"dns": "10.0.0.10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"active": {
|
||||||
|
"selected": true,
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
"icmp": {
|
||||||
|
"selected": false,
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
"udp": {
|
||||||
|
"selected": false,
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
"port": " 80",
|
||||||
|
"protocol": "TCP",
|
||||||
|
"sender": "Alle Absender",
|
||||||
|
"rows": [
|
||||||
|
[
|
||||||
|
"UDP",
|
||||||
|
"53",
|
||||||
|
"Nur lokales Rechnernetz"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"*",
|
||||||
|
"",
|
||||||
|
"Alle Absender"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"TCP",
|
||||||
|
"0",
|
||||||
|
"Alle Absender"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"TCP",
|
||||||
|
"70000",
|
||||||
|
"Nur lokales Rechnernetz"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"log": "Portregel wurde hinzugefügt: 55555/TCP\nFirewall-Regel hat gegriffen: #1 (*/* -> */*; TCP:55555 => ACCEPT) -> akzeptieren\nFirewall-Regel hat gegriffen: #1 (*/* -> */*; TCP:55555 => ACCEPT) -> akzeptieren\nPortregel wurde hinzugefügt: 53/UDP\nFirewall-Regel hat gegriffen: #1 (*/* -> */*; TCP:55555 => ACCEPT) -> akzeptieren\nFirewall-Regel hat gegriffen: #2 (10.0.0.0/255.255.255.0 -> */*; UDP:53 => ACCEPT) -> akzeptieren\nFirewall-Regel hat gegriffen: #1 (*/* -> */*; TCP:55555 => ACCEPT) -> akzeptieren\nICMP-Paket verworfen: 10.0.0.2 -> 10.0.0.10 (code: 0, type: 8)\nFirewall-Regel hat gegriffen: #1 (*/* -> */*; TCP:55555 => ACCEPT) -> akzeptieren\nFirewall-Regel hat gegriffen: #2 (10.0.0.0/255.255.255.0 -> */*; UDP:53 => ACCEPT) -> akzeptieren\nICMP-Paket verworfen: 20.0.0.2 -> 10.0.0.10 (code: 0, type: 8)\nFirewall-Regel hat gegriffen: #1 (*/* -> */*; TCP:55555 => ACCEPT) -> akzeptieren\nICMP-Paket verworfen: 10.0.0.2 -> 10.0.0.10 (code: 0, type: 8)\nFirewall-Regel hat gegriffen: #1 (*/* -> */*; TCP:55555 => ACCEPT) -> akzeptieren\nICMP-Paket verworfen: 20.0.0.2 -> 10.0.0.10 (code: 0, type: 8)\nFirewall-Regel hat gegriffen: #1 (*/* -> */*; TCP:55555 => ACCEPT) -> akzeptieren\nPortregel wurde hinzugefügt: -1/*\nPortregel wurde hinzugefügt: 0/TCP\nPortregel wurde hinzugefügt: 70000/TCP\nPortregel wurde entfernt.\nICMP-Paket verworfen: 10.0.0.2 -> 10.0.0.10 (code: 0, type: 8)\nFirewall-Regel hat gegriffen: #2 (*/* -> */*; *:* => ACCEPT) -> akzeptieren\nICMP-Paket verworfen: 20.0.0.2 -> 10.0.0.10 (code: 0, type: 8)\nFirewall-Regel hat gegriffen: #2 (*/* -> */*; *:* => ACCEPT) -> akzeptieren\n",
|
||||||
|
"logEditable": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step": "firewall off",
|
||||||
|
"traffic": {
|
||||||
|
"local": {
|
||||||
|
"ping": "ICMP 0/0 from 10.0.0.10",
|
||||||
|
"tcp": "connected",
|
||||||
|
"dns": "10.0.0.10"
|
||||||
|
},
|
||||||
|
"remote": {
|
||||||
|
"ping": "ICMP 0/0 from 10.0.0.10",
|
||||||
|
"tcp": "connected",
|
||||||
|
"dns": "10.0.0.10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"active": {
|
||||||
|
"selected": false,
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
"icmp": {
|
||||||
|
"selected": false,
|
||||||
|
"enabled": false
|
||||||
|
},
|
||||||
|
"udp": {
|
||||||
|
"selected": false,
|
||||||
|
"enabled": false
|
||||||
|
},
|
||||||
|
"port": " 80",
|
||||||
|
"protocol": "TCP",
|
||||||
|
"sender": "Alle Absender",
|
||||||
|
"rows": [
|
||||||
|
[
|
||||||
|
"UDP",
|
||||||
|
"53",
|
||||||
|
"Nur lokales Rechnernetz"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"*",
|
||||||
|
"",
|
||||||
|
"Alle Absender"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"TCP",
|
||||||
|
"0",
|
||||||
|
"Alle Absender"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"TCP",
|
||||||
|
"70000",
|
||||||
|
"Nur lokales Rechnernetz"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"log": "Portregel wurde hinzugefügt: 55555/TCP\nFirewall-Regel hat gegriffen: #1 (*/* -> */*; TCP:55555 => ACCEPT) -> akzeptieren\nFirewall-Regel hat gegriffen: #1 (*/* -> */*; TCP:55555 => ACCEPT) -> akzeptieren\nPortregel wurde hinzugefügt: 53/UDP\nFirewall-Regel hat gegriffen: #1 (*/* -> */*; TCP:55555 => ACCEPT) -> akzeptieren\nFirewall-Regel hat gegriffen: #2 (10.0.0.0/255.255.255.0 -> */*; UDP:53 => ACCEPT) -> akzeptieren\nFirewall-Regel hat gegriffen: #1 (*/* -> */*; TCP:55555 => ACCEPT) -> akzeptieren\nICMP-Paket verworfen: 10.0.0.2 -> 10.0.0.10 (code: 0, type: 8)\nFirewall-Regel hat gegriffen: #1 (*/* -> */*; TCP:55555 => ACCEPT) -> akzeptieren\nFirewall-Regel hat gegriffen: #2 (10.0.0.0/255.255.255.0 -> */*; UDP:53 => ACCEPT) -> akzeptieren\nICMP-Paket verworfen: 20.0.0.2 -> 10.0.0.10 (code: 0, type: 8)\nFirewall-Regel hat gegriffen: #1 (*/* -> */*; TCP:55555 => ACCEPT) -> akzeptieren\nICMP-Paket verworfen: 10.0.0.2 -> 10.0.0.10 (code: 0, type: 8)\nFirewall-Regel hat gegriffen: #1 (*/* -> */*; TCP:55555 => ACCEPT) -> akzeptieren\nICMP-Paket verworfen: 20.0.0.2 -> 10.0.0.10 (code: 0, type: 8)\nFirewall-Regel hat gegriffen: #1 (*/* -> */*; TCP:55555 => ACCEPT) -> akzeptieren\nPortregel wurde hinzugefügt: -1/*\nPortregel wurde hinzugefügt: 0/TCP\nPortregel wurde hinzugefügt: 70000/TCP\nPortregel wurde entfernt.\nICMP-Paket verworfen: 10.0.0.2 -> 10.0.0.10 (code: 0, type: 8)\nFirewall-Regel hat gegriffen: #2 (*/* -> */*; *:* => ACCEPT) -> akzeptieren\nICMP-Paket verworfen: 20.0.0.2 -> 10.0.0.10 (code: 0, type: 8)\nFirewall-Regel hat gegriffen: #2 (*/* -> */*; *:* => ACCEPT) -> akzeptieren\n",
|
||||||
|
"logEditable": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step": "firewall on again",
|
||||||
|
"active": {
|
||||||
|
"selected": true,
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
"icmp": {
|
||||||
|
"selected": false,
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
"udp": {
|
||||||
|
"selected": false,
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
"port": " 80",
|
||||||
|
"protocol": "TCP",
|
||||||
|
"sender": "Alle Absender",
|
||||||
|
"rows": [
|
||||||
|
[
|
||||||
|
"UDP",
|
||||||
|
"53",
|
||||||
|
"Nur lokales Rechnernetz"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"*",
|
||||||
|
"",
|
||||||
|
"Alle Absender"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"TCP",
|
||||||
|
"0",
|
||||||
|
"Alle Absender"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"TCP",
|
||||||
|
"70000",
|
||||||
|
"Nur lokales Rechnernetz"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"log": "Portregel wurde hinzugefügt: 55555/TCP\nFirewall-Regel hat gegriffen: #1 (*/* -> */*; TCP:55555 => ACCEPT) -> akzeptieren\nFirewall-Regel hat gegriffen: #1 (*/* -> */*; TCP:55555 => ACCEPT) -> akzeptieren\nPortregel wurde hinzugefügt: 53/UDP\nFirewall-Regel hat gegriffen: #1 (*/* -> */*; TCP:55555 => ACCEPT) -> akzeptieren\nFirewall-Regel hat gegriffen: #2 (10.0.0.0/255.255.255.0 -> */*; UDP:53 => ACCEPT) -> akzeptieren\nFirewall-Regel hat gegriffen: #1 (*/* -> */*; TCP:55555 => ACCEPT) -> akzeptieren\nICMP-Paket verworfen: 10.0.0.2 -> 10.0.0.10 (code: 0, type: 8)\nFirewall-Regel hat gegriffen: #1 (*/* -> */*; TCP:55555 => ACCEPT) -> akzeptieren\nFirewall-Regel hat gegriffen: #2 (10.0.0.0/255.255.255.0 -> */*; UDP:53 => ACCEPT) -> akzeptieren\nICMP-Paket verworfen: 20.0.0.2 -> 10.0.0.10 (code: 0, type: 8)\nFirewall-Regel hat gegriffen: #1 (*/* -> */*; TCP:55555 => ACCEPT) -> akzeptieren\nICMP-Paket verworfen: 10.0.0.2 -> 10.0.0.10 (code: 0, type: 8)\nFirewall-Regel hat gegriffen: #1 (*/* -> */*; TCP:55555 => ACCEPT) -> akzeptieren\nICMP-Paket verworfen: 20.0.0.2 -> 10.0.0.10 (code: 0, type: 8)\nFirewall-Regel hat gegriffen: #1 (*/* -> */*; TCP:55555 => ACCEPT) -> akzeptieren\nPortregel wurde hinzugefügt: -1/*\nPortregel wurde hinzugefügt: 0/TCP\nPortregel wurde hinzugefügt: 70000/TCP\nPortregel wurde entfernt.\nICMP-Paket verworfen: 10.0.0.2 -> 10.0.0.10 (code: 0, type: 8)\nFirewall-Regel hat gegriffen: #2 (*/* -> */*; *:* => ACCEPT) -> akzeptieren\nICMP-Paket verworfen: 20.0.0.2 -> 10.0.0.10 (code: 0, type: 8)\nFirewall-Regel hat gegriffen: #2 (*/* -> */*; *:* => ACCEPT) -> akzeptieren\n",
|
||||||
|
"logEditable": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,238 @@
|
|||||||
|
// A computer's firewall as Java's window changes it, against Java's
|
||||||
|
// (firewallWindow.java.json, FirewallWindowRun) on firewall-pc's server:
|
||||||
|
// Java's steps played on the engine — the boxes, the rules as the window's
|
||||||
|
// table shows them, the log, and after the steps that ask, what reaches the
|
||||||
|
// server from a computer of its network and one behind the router (a ping,
|
||||||
|
// a TCP connection to its echo server, its DNS server asked) — and where
|
||||||
|
// Java errs, what the firewall does instead (apps/personalFirewall.ts names
|
||||||
|
// each): only a port is taken.
|
||||||
|
|
||||||
|
import { readFileSync } from 'node:fs';
|
||||||
|
import { dirname, join } from 'node:path';
|
||||||
|
import { fileURLToPath } from 'node:url';
|
||||||
|
import { describe, expect, it } from 'vitest';
|
||||||
|
import {
|
||||||
|
DIRECTLY_CONNECTED_SOURCE_MARKER,
|
||||||
|
firewallLogText,
|
||||||
|
firewallPortOfText,
|
||||||
|
firewallRule,
|
||||||
|
JAVA_PERSONAL_FIREWALL_DEFAULTS,
|
||||||
|
NetworkRuntimeEngine,
|
||||||
|
portRuleSender,
|
||||||
|
VirtualFileSystem,
|
||||||
|
withoutRule,
|
||||||
|
withPortRule,
|
||||||
|
withSnapshot,
|
||||||
|
type FirewallRule,
|
||||||
|
type PortRuleProtocol,
|
||||||
|
type Steps,
|
||||||
|
} from '../src/index.js';
|
||||||
|
import { snapshotOf, type Scenario } from './scenario.js';
|
||||||
|
|
||||||
|
interface Reached {
|
||||||
|
readonly ping: string;
|
||||||
|
readonly tcp: string;
|
||||||
|
readonly dns: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface JavaStep {
|
||||||
|
readonly step: string;
|
||||||
|
readonly traffic?: { readonly local: Reached; readonly remote: Reached };
|
||||||
|
readonly active: { readonly selected: boolean; readonly enabled: boolean };
|
||||||
|
readonly icmp: { readonly selected: boolean; readonly enabled: boolean };
|
||||||
|
readonly udp: { readonly selected: boolean; readonly enabled: boolean };
|
||||||
|
readonly rows: readonly (readonly string[])[];
|
||||||
|
readonly log: string;
|
||||||
|
readonly logEditable: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const here = dirname(fileURLToPath(import.meta.url));
|
||||||
|
const java = JSON.parse(readFileSync(join(here, 'firewallWindow.java.json'), 'utf8')) as { readonly steps: readonly JavaStep[] };
|
||||||
|
const scenario = JSON.parse(readFileSync(join(here, 'scenarios/firewall-pc.scenario.json'), 'utf8')) as Scenario;
|
||||||
|
const LOCAL = '00000000-0000-0000-0000-000000000201';
|
||||||
|
const REMOTE = '00000000-0000-0000-0000-000000000203';
|
||||||
|
const SERVER = '00000000-0000-0000-0000-000000000204';
|
||||||
|
const SERVER_ADDRESS = '10.0.0.10';
|
||||||
|
const TIMED_OUT = 'TimeOutException: Zeitüberschreitung beim Verbindungsaufbau';
|
||||||
|
|
||||||
|
/** Java's ruleToVector: protocol, port, sender. */
|
||||||
|
function javaRow(rule: FirewallRule): string[] {
|
||||||
|
const protocol = rule.protocol === 'tcp' ? 'TCP' : rule.protocol === 'udp' ? 'UDP' : '*';
|
||||||
|
return [protocol, rule.port >= 0 ? String(rule.port) : '', rule.sourceIPAddress === DIRECTLY_CONNECTED_SOURCE_MARKER ? 'Nur lokales Rechnernetz' : 'Alle Absender'];
|
||||||
|
}
|
||||||
|
|
||||||
|
function setUp() {
|
||||||
|
const engine = new NetworkRuntimeEngine(scenario.seed);
|
||||||
|
engine.handle({
|
||||||
|
type: 'start',
|
||||||
|
snapshot: withSnapshot(snapshotOf(scenario), {
|
||||||
|
fileSystemsByNodeId: new Map([[SERVER, new VirtualFileSystem()]]),
|
||||||
|
echoServersByNodeId: new Map([[SERVER, { port: 55555, isActive: true }]]),
|
||||||
|
firewallConfigurationsByNodeId: new Map([[SERVER, JAVA_PERSONAL_FIREWALL_DEFAULTS]]),
|
||||||
|
}),
|
||||||
|
seed: scenario.seed,
|
||||||
|
initialTimeMilliseconds: 0,
|
||||||
|
});
|
||||||
|
const run = (milliseconds: number) => engine.handle({ type: 'advance', toMilliseconds: engine.state.currentTimeMilliseconds + milliseconds });
|
||||||
|
const blocking = <T>(steps: Steps<T>, milliseconds: number): T | undefined => {
|
||||||
|
let result: T | undefined;
|
||||||
|
engine.startThread(
|
||||||
|
(function* (): Steps {
|
||||||
|
result = yield* steps;
|
||||||
|
})(),
|
||||||
|
);
|
||||||
|
run(milliseconds);
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
let sequenceNumber = 1;
|
||||||
|
const reached = (from: string): Reached => {
|
||||||
|
const answer = blocking(engine.icmpEchoSteps(from, SERVER_ADDRESS, { identifier: 1, sequenceNumber: sequenceNumber++ }), 30_000);
|
||||||
|
const ping = answer?.message.kind === 'echoReply' ? `ICMP 0/0 from ${answer.packet.senderIPAddress}` : 'null';
|
||||||
|
const socket = engine.openTcpClientSocket(from, SERVER_ADDRESS, 55555)!;
|
||||||
|
const connected = blocking(engine.connectTcpSteps(socket), 60_000);
|
||||||
|
// Held a moment, as the oracle holds it.
|
||||||
|
run(500);
|
||||||
|
engine.closeSocket(socket);
|
||||||
|
run(1000);
|
||||||
|
const tcp = connected === 'connected' ? 'connected' : connected === 'timedOut' ? TIMED_OUT : String(connected);
|
||||||
|
const resolution = blocking(engine.resolveDnsAddressSteps(from, 'www.filius.de'), 30_000);
|
||||||
|
const dns = resolution === undefined || resolution.type === 'timedOut' ? 'TimeOutException' : String(resolution.address ?? null);
|
||||||
|
return { ping, tcp, dns };
|
||||||
|
};
|
||||||
|
return { engine, reached };
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("a computer's firewall window, against Java's", () => {
|
||||||
|
it("plays Java's steps: the same where Java is right, as named where it errs", () => {
|
||||||
|
const { engine, reached } = setUp();
|
||||||
|
const configuration = () => engine.personalFirewall(SERVER)!;
|
||||||
|
const log = () => engine.firewallLog(SERVER).map((entry) => `${firewallLogText(entry, 'de')}\n`);
|
||||||
|
// Java's log lines, less those of what Java takes and the firewall does not (and with those the firewall takes instead).
|
||||||
|
let javaLines = (step: JavaStep) => step.log.split(/(?<=\n)/).filter((line) => line !== '');
|
||||||
|
const at = (name: string) => java.steps.find((step) => step.step === name)!;
|
||||||
|
const check = (name: string, traffic: { readonly local: Reached; readonly remote: Reached } | undefined = at(name).traffic) => {
|
||||||
|
const step = at(name);
|
||||||
|
if (traffic !== undefined) expect({ local: reached(LOCAL), remote: reached(REMOTE) }, name).toEqual(traffic);
|
||||||
|
expect({ active: configuration().isActive, icmp: !configuration().dropIcmp, udp: configuration().filterUdp, rows: configuration().rules.map(javaRow), log: log() }, name).toEqual({
|
||||||
|
active: step.active.selected,
|
||||||
|
icmp: step.icmp.selected,
|
||||||
|
udp: step.udp.selected,
|
||||||
|
rows: step.rows,
|
||||||
|
log: javaLines(step),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const add = (port: string, protocol: PortRuleProtocol, sameNetworkOnly: boolean) => {
|
||||||
|
const number = firewallPortOfText(port);
|
||||||
|
return number !== undefined && engine.addFirewallPortRule(SERVER, number, protocol, sameNetworkOnly);
|
||||||
|
};
|
||||||
|
const set = (fields: Partial<ReturnType<typeof configuration>>) => engine.setFirewallConfiguration(SERVER, { ...configuration(), ...fields });
|
||||||
|
|
||||||
|
// Active as Java installs it: a ping goes through, TCP and UDP only by a rule.
|
||||||
|
check('open');
|
||||||
|
expect(at('open').logEditable).toBe(true);
|
||||||
|
expect([add('abc', 'tcp', false), add('', 'tcp', false)]).toEqual([false, false]);
|
||||||
|
check('add port abc');
|
||||||
|
check('add an empty port');
|
||||||
|
|
||||||
|
expect(add('55555', 'tcp', false)).toBe(true);
|
||||||
|
check('add 55555 TCP for all senders');
|
||||||
|
expect(add('53', 'udp', true)).toBe(true);
|
||||||
|
check('add 53 UDP for the same network');
|
||||||
|
set({ dropIcmp: true });
|
||||||
|
check('ICMP no longer allowed');
|
||||||
|
set({ filterUdp: false });
|
||||||
|
check('UDP filter off');
|
||||||
|
|
||||||
|
// No port, none taken (Java takes each: -1 a rule for every port of both protocols, 0 and 70000 rules that never match).
|
||||||
|
expect([add('-1', 'all', false), add('0', 'tcp', false), add('70000', 'tcp', true)]).toEqual([false, false, false]);
|
||||||
|
const notTaken = ['Portregel wurde hinzugefügt: -1/*\n', 'Portregel wurde hinzugefügt: 0/TCP\n', 'Portregel wurde hinzugefügt: 70000/TCP\n'];
|
||||||
|
let javaRows = (step: JavaStep) => step.rows.filter(([, port]) => port !== '' && port !== '0' && port !== '70000');
|
||||||
|
const javaAll = javaLines;
|
||||||
|
javaLines = (step) => javaAll(step).filter((line) => !notTaken.includes(line));
|
||||||
|
const sameAsJava = (name: string) => expect({ rows: configuration().rules.map(javaRow), log: log() }, name).toEqual({ rows: javaRows(at(name)), log: javaLines(at(name)) });
|
||||||
|
for (const name of ['add -1 for all protocols', 'add 0 TCP', 'add 70000 TCP for the same network']) sameAsJava(name);
|
||||||
|
// A port after a space is taken (Java refuses it without a word).
|
||||||
|
expect(add(' 80', 'tcp', false)).toBe(true);
|
||||||
|
const tookPort = javaLines(at('add 70000 TCP for the same network')).length;
|
||||||
|
const javaTaken = javaLines;
|
||||||
|
javaLines = (step) => javaTaken(step).toSpliced(tookPort, 0, 'Portregel wurde hinzugefügt: 80/TCP\n');
|
||||||
|
const javaListed = javaRows;
|
||||||
|
javaRows = (step) => [...javaListed(step), ['TCP', '80', 'Alle Absender']];
|
||||||
|
sameAsJava('add a port after a space');
|
||||||
|
|
||||||
|
// "Löschen" with no row chosen does nothing; row 0 goes. Without Java's rule for every port, TCP is no longer let through.
|
||||||
|
sameAsJava('delete with no row chosen');
|
||||||
|
expect(engine.removeFirewallRule(SERVER, 0)).toBe(true);
|
||||||
|
const javaKept = javaLines;
|
||||||
|
javaLines = (step) => javaKept(step).filter((line) => !line.includes('*/* -> */*; *:*'));
|
||||||
|
const javaDelete = at('delete row 0');
|
||||||
|
const closed = (reach: Reached): Reached => ({ ...reach, tcp: TIMED_OUT });
|
||||||
|
expect({ local: reached(LOCAL), remote: reached(REMOTE) }).toEqual({ local: closed(javaDelete.traffic!.local), remote: closed(javaDelete.traffic!.remote) });
|
||||||
|
sameAsJava('delete row 0');
|
||||||
|
|
||||||
|
// Off: everything goes through, nothing is written; on again.
|
||||||
|
set({ isActive: false });
|
||||||
|
const javaOff = at('firewall off');
|
||||||
|
expect({ local: reached(LOCAL), remote: reached(REMOTE) }).toEqual(javaOff.traffic);
|
||||||
|
sameAsJava('firewall off');
|
||||||
|
expect([javaOff.icmp.enabled, javaOff.udp.enabled, at('firewall on again').icmp.enabled]).toEqual([false, false, true]);
|
||||||
|
set({ isActive: true });
|
||||||
|
expect(configuration()).toMatchObject({ isActive: true, dropIcmp: true, filterUdp: false });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("a computer's firewall beyond the steps", () => {
|
||||||
|
it('takes a port of 1 to 65535 only, spaces around it allowed', () => {
|
||||||
|
expect(['1', '65535', ' 80 ', '+80', '80a', '0', '65536', '-1', '', ' '].map(firewallPortOfText)).toEqual([
|
||||||
|
1,
|
||||||
|
65535,
|
||||||
|
80,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('adds Java’s rule and removes the row chosen only', () => {
|
||||||
|
const added = withPortRule(withPortRule(JAVA_PERSONAL_FIREWALL_DEFAULTS, 80, 'tcp', false), 53, 'udp', true);
|
||||||
|
expect(added.rules).toEqual([
|
||||||
|
firewallRule({ port: 80, protocol: 'tcp', action: 'accept' }),
|
||||||
|
firewallRule({ port: 53, protocol: 'udp', action: 'accept', sourceIPAddress: DIRECTLY_CONNECTED_SOURCE_MARKER }),
|
||||||
|
]);
|
||||||
|
expect(withoutRule(added, 1)!.rules).toEqual([added.rules[0]]);
|
||||||
|
expect([withoutRule(added, 2), withoutRule(added, -1), withoutRule(added, 0.5)]).toEqual([undefined, undefined, undefined]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("names the window's sender for the rules it makes only", () => {
|
||||||
|
expect(
|
||||||
|
[
|
||||||
|
firewallRule({ action: 'accept' }),
|
||||||
|
firewallRule({ action: 'accept', sourceIPAddress: DIRECTLY_CONNECTED_SOURCE_MARKER, sourceSubnetMask: '255.0.0.0' }),
|
||||||
|
firewallRule({ action: 'accept', sourceSubnetMask: '255.0.0.0' }),
|
||||||
|
firewallRule({ action: 'drop' }),
|
||||||
|
firewallRule({ action: 'accept', sourceIPAddress: '10.0.0.0', sourceSubnetMask: '255.0.0.0' }),
|
||||||
|
firewallRule({ action: 'accept', destinationIPAddress: '10.0.0.10' }),
|
||||||
|
].map(portRuleSender),
|
||||||
|
).toEqual(['all', 'sameNetwork', 'all', undefined, undefined, undefined]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('has nothing for a computer without a firewall, nor a router', () => {
|
||||||
|
const engine = new NetworkRuntimeEngine(scenario.seed);
|
||||||
|
// The router has a firewall, as every one has in Java; it is no program of a computer.
|
||||||
|
const ROUTER = '00000000-0000-0000-0000-000000000202';
|
||||||
|
engine.handle({
|
||||||
|
type: 'start',
|
||||||
|
snapshot: withSnapshot(snapshotOf(scenario), { firewallConfigurationsByNodeId: new Map([[ROUTER, JAVA_PERSONAL_FIREWALL_DEFAULTS]]) }),
|
||||||
|
seed: scenario.seed,
|
||||||
|
initialTimeMilliseconds: 0,
|
||||||
|
});
|
||||||
|
expect([engine.personalFirewall(SERVER), engine.personalFirewall(ROUTER)]).toEqual([undefined, undefined]);
|
||||||
|
expect(engine.addFirewallPortRule(ROUTER, 80, 'tcp', false)).toBe(false);
|
||||||
|
expect([engine.addFirewallPortRule(SERVER, 80, 'tcp', false), engine.removeFirewallRule(SERVER, 0)]).toEqual([false, false]);
|
||||||
|
expect(engine.firewallLog(SERVER)).toEqual([]);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,146 @@
|
|||||||
|
{
|
||||||
|
"seed": 21,
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"id": "00000000-0000-0000-0000-000000000201",
|
||||||
|
"kind": "pc",
|
||||||
|
"ports": [
|
||||||
|
{
|
||||||
|
"id": "00000000-0000-0000-0000-000000000211",
|
||||||
|
"label": "eth0"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "00000000-0000-0000-0000-000000000202",
|
||||||
|
"kind": "router",
|
||||||
|
"ports": [
|
||||||
|
{
|
||||||
|
"id": "00000000-0000-0000-0000-000000000221",
|
||||||
|
"label": "rt1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "00000000-0000-0000-0000-000000000222",
|
||||||
|
"label": "rt2"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "00000000-0000-0000-0000-000000000203",
|
||||||
|
"kind": "pc",
|
||||||
|
"ports": [
|
||||||
|
{
|
||||||
|
"id": "00000000-0000-0000-0000-000000000213",
|
||||||
|
"label": "eth0"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "00000000-0000-0000-0000-000000000204",
|
||||||
|
"kind": "pc",
|
||||||
|
"ports": [
|
||||||
|
{
|
||||||
|
"id": "00000000-0000-0000-0000-000000000214",
|
||||||
|
"label": "eth0"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "00000000-0000-0000-0000-000000000205",
|
||||||
|
"kind": "networkSwitch",
|
||||||
|
"ports": [
|
||||||
|
{
|
||||||
|
"id": "00000000-0000-0000-0000-000000000251",
|
||||||
|
"label": "sw1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "00000000-0000-0000-0000-000000000252",
|
||||||
|
"label": "sw2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "00000000-0000-0000-0000-000000000253",
|
||||||
|
"label": "sw3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "00000000-0000-0000-0000-000000000254",
|
||||||
|
"label": "sw4"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"links": [
|
||||||
|
{
|
||||||
|
"id": "00000000-0000-0000-0000-00000000F301",
|
||||||
|
"sourceNode": "00000000-0000-0000-0000-000000000201",
|
||||||
|
"sourcePort": "00000000-0000-0000-0000-000000000211",
|
||||||
|
"targetNode": "00000000-0000-0000-0000-000000000205",
|
||||||
|
"targetPort": "00000000-0000-0000-0000-000000000251"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "00000000-0000-0000-0000-00000000F302",
|
||||||
|
"sourceNode": "00000000-0000-0000-0000-000000000204",
|
||||||
|
"sourcePort": "00000000-0000-0000-0000-000000000214",
|
||||||
|
"targetNode": "00000000-0000-0000-0000-000000000205",
|
||||||
|
"targetPort": "00000000-0000-0000-0000-000000000252"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "00000000-0000-0000-0000-00000000F303",
|
||||||
|
"sourceNode": "00000000-0000-0000-0000-000000000202",
|
||||||
|
"sourcePort": "00000000-0000-0000-0000-000000000221",
|
||||||
|
"targetNode": "00000000-0000-0000-0000-000000000205",
|
||||||
|
"targetPort": "00000000-0000-0000-0000-000000000253"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "00000000-0000-0000-0000-00000000F304",
|
||||||
|
"sourceNode": "00000000-0000-0000-0000-000000000202",
|
||||||
|
"sourcePort": "00000000-0000-0000-0000-000000000222",
|
||||||
|
"targetNode": "00000000-0000-0000-0000-000000000203",
|
||||||
|
"targetPort": "00000000-0000-0000-0000-000000000213"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"devices": {
|
||||||
|
"00000000-0000-0000-0000-000000000201": {
|
||||||
|
"ip": "10.0.0.2",
|
||||||
|
"mask": "255.255.255.0",
|
||||||
|
"gateway": "10.0.0.1",
|
||||||
|
"dns": "10.0.0.10"
|
||||||
|
},
|
||||||
|
"00000000-0000-0000-0000-000000000202": {
|
||||||
|
"ip": "10.0.0.1",
|
||||||
|
"mask": "255.255.255.0"
|
||||||
|
},
|
||||||
|
"00000000-0000-0000-0000-000000000203": {
|
||||||
|
"ip": "20.0.0.2",
|
||||||
|
"mask": "255.255.255.0",
|
||||||
|
"gateway": "20.0.0.1",
|
||||||
|
"dns": "10.0.0.10"
|
||||||
|
},
|
||||||
|
"00000000-0000-0000-0000-000000000204": {
|
||||||
|
"ip": "10.0.0.10",
|
||||||
|
"mask": "255.255.255.0",
|
||||||
|
"gateway": "10.0.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"interfaces": [
|
||||||
|
{
|
||||||
|
"node": "00000000-0000-0000-0000-000000000202",
|
||||||
|
"port": "00000000-0000-0000-0000-000000000221",
|
||||||
|
"ip": "10.0.0.1",
|
||||||
|
"mask": "255.255.255.0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"node": "00000000-0000-0000-0000-000000000202",
|
||||||
|
"port": "00000000-0000-0000-0000-000000000222",
|
||||||
|
"ip": "20.0.0.1",
|
||||||
|
"mask": "255.255.255.0"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"dnsServers": {
|
||||||
|
"00000000-0000-0000-0000-000000000204": {
|
||||||
|
"records": [
|
||||||
|
"www.filius.de A 10.0.0.10"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
,"steps": []
|
||||||
|
}
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
// A computer's firewall on its desktop in Chromium (F4.6g3), on FILIUS'
|
||||||
|
// example email_komplett with the firewall installed on its web server:
|
||||||
|
// Java's boxes, a port refused and one added, a notebook's browser let
|
||||||
|
// through by the rule, ICMP no longer allowed and a ping dropped (both in the
|
||||||
|
// log), the firewall switched off (the boxes beneath it can no longer be
|
||||||
|
// changed), the rule chosen and removed.
|
||||||
|
|
||||||
|
import { expect, test, type Page } from '@playwright/test';
|
||||||
|
import { drawing, onDevice, openEditor, serveEditor } from './editorPage.js';
|
||||||
|
|
||||||
|
let server: { readonly base: string; readonly close: () => Promise<void> };
|
||||||
|
|
||||||
|
test.beforeAll(async () => {
|
||||||
|
server = await serveEditor();
|
||||||
|
});
|
||||||
|
|
||||||
|
test.afterAll(async () => {
|
||||||
|
await server.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
async function desktopOf(page: Page, name: string) {
|
||||||
|
const id = (await drawing(page)).nodes.find((node) => node.name === name)!.id;
|
||||||
|
const at = await onDevice(page, id);
|
||||||
|
await page.mouse.click(at.x, at.y);
|
||||||
|
return page.locator(`section[data-desktop="${id}"]`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function install(page: Page, desktop: ReturnType<Page['locator']>, program: string) {
|
||||||
|
await desktop.locator('[data-program="installation"]').click();
|
||||||
|
const installation = desktop.locator('[data-program-window="installation"]');
|
||||||
|
await installation.locator('[data-list="available"] [role="option"]').filter({ hasText: program }).click();
|
||||||
|
await installation.getByRole('button', { name: 'Installieren' }).click();
|
||||||
|
await installation.getByRole('button', { name: 'Änderungen annehmen' }).click();
|
||||||
|
}
|
||||||
|
|
||||||
|
test('keeps a computer’s firewall as Java’s window does, and the firewall lets through what its rules open', async ({ page }) => {
|
||||||
|
test.setTimeout(120_000);
|
||||||
|
await openEditor(page, server.base, 'email_komplett.fls');
|
||||||
|
await page.getByRole('button', { name: 'Wechselt in den Aktionsmodus' }).click();
|
||||||
|
await expect(page.locator('svg.filius-canvas')).toHaveAttribute('data-mode', 'simulation');
|
||||||
|
const desktop = await desktopOf(page, 'Web-Server');
|
||||||
|
await install(page, desktop, 'Firewall');
|
||||||
|
await desktop.locator('[data-program="firewall"]').click();
|
||||||
|
const window = desktop.locator('[data-program-window="firewall"]');
|
||||||
|
await expect(window.locator('.program-titlebar h3')).toHaveText('Firewall');
|
||||||
|
await expect(window.getByRole('tab')).toHaveText(['Konfiguration', 'Log']);
|
||||||
|
const active = window.getByRole('checkbox', { name: 'Firewall aktivieren' });
|
||||||
|
const icmp = window.getByRole('checkbox', { name: 'Internet Control Message Protocol (Ping etc.) erlauben' });
|
||||||
|
await expect(active).toBeChecked();
|
||||||
|
await expect(icmp).toBeChecked();
|
||||||
|
await expect(window.getByRole('checkbox', { name: 'Aktiviere UDP-Datenverkehrfilter' })).toBeChecked();
|
||||||
|
await expect(window.getByText('Die Firewall blockiert alle eingehenden IP-Pakete')).toBeVisible();
|
||||||
|
|
||||||
|
// A port that is none is marked and not handed over; 80 is added and the field emptied.
|
||||||
|
const port = window.getByRole('textbox', { name: 'Zulässiger Port/Protokoll:' });
|
||||||
|
const add = window.getByRole('button', { name: 'Hinzufügen' });
|
||||||
|
await port.fill('70000');
|
||||||
|
await expect(port).toHaveAttribute('aria-invalid', 'true');
|
||||||
|
await expect(add).toBeDisabled();
|
||||||
|
await port.fill('80');
|
||||||
|
await add.click();
|
||||||
|
const rows = window.getByRole('table', { name: 'Regeln' }).locator('tbody tr');
|
||||||
|
await expect(rows).toHaveCount(1);
|
||||||
|
await expect(rows.first().locator('td')).toHaveText(['TCP', '80', 'Alle Absender']);
|
||||||
|
await expect(port).toHaveValue('');
|
||||||
|
|
||||||
|
// A notebook's browser is let through by the rule; the log says so.
|
||||||
|
await desktop.locator('.desktop-close').click();
|
||||||
|
const notebook = await desktopOf(page, 'Rechner 1');
|
||||||
|
await notebook.locator('[data-program="webBrowser"]').click();
|
||||||
|
const browser = notebook.locator('[data-program-window="webBrowser"]');
|
||||||
|
const address = browser.getByRole('textbox', { name: 'Adresse' });
|
||||||
|
await address.fill('141.99.50.231');
|
||||||
|
await address.press('Enter');
|
||||||
|
await expect(browser.frameLocator('iframe[title="Seite"]').locator('body')).not.toBeEmpty({ timeout: 30_000 });
|
||||||
|
|
||||||
|
// ICMP no longer allowed: the notebook's ping is dropped.
|
||||||
|
await notebook.locator('.desktop-close').click();
|
||||||
|
await desktopOf(page, 'Web-Server');
|
||||||
|
await desktop.locator('[data-program="firewall"]').click();
|
||||||
|
await icmp.click();
|
||||||
|
await expect(icmp).not.toBeChecked();
|
||||||
|
await desktop.locator('.desktop-close').click();
|
||||||
|
await desktopOf(page, 'Rechner 1');
|
||||||
|
await install(page, notebook, 'Befehlszeile');
|
||||||
|
await notebook.locator('[data-program="commandPrompt"]').click();
|
||||||
|
const input = notebook.locator('input[name="terminalInput"]');
|
||||||
|
await input.fill('ping 141.99.50.231');
|
||||||
|
await input.press('Enter');
|
||||||
|
await expect(notebook.locator('.terminal-text')).toContainText('0 Paket(e) empfangen', { timeout: 60_000 });
|
||||||
|
await notebook.locator('.desktop-close').click();
|
||||||
|
await desktopOf(page, 'Web-Server');
|
||||||
|
await desktop.locator('[data-program="firewall"]').click();
|
||||||
|
await window.getByRole('tab', { name: 'Log' }).click();
|
||||||
|
const log = window.getByRole('tabpanel', { name: 'Log' });
|
||||||
|
await expect(log).toContainText('Portregel wurde hinzugefügt: 80/TCP\nFirewall-Regel hat gegriffen: #1 (*/* -> */*; TCP:80 => ACCEPT) -> akzeptieren');
|
||||||
|
await expect(log).toContainText('ICMP-Paket verworfen: 141.99.50.181 -> 141.99.50.231 (code: 0, type: 8)');
|
||||||
|
|
||||||
|
// Off: the two boxes beneath it can no longer be changed; the rule chosen and removed.
|
||||||
|
await window.getByRole('tab', { name: 'Konfiguration' }).click();
|
||||||
|
await active.click();
|
||||||
|
await expect(active).not.toBeChecked();
|
||||||
|
await expect(icmp).toBeDisabled();
|
||||||
|
await expect(window.getByRole('button', { name: 'Löschen' })).toBeDisabled();
|
||||||
|
await rows.first().dispatchEvent('pointerdown');
|
||||||
|
await window.getByRole('button', { name: 'Löschen' }).click();
|
||||||
|
await expect(rows).toHaveCount(0);
|
||||||
|
await window.getByRole('tab', { name: 'Log' }).click();
|
||||||
|
await expect(log).toContainText('Portregel wurde entfernt.');
|
||||||
|
});
|
||||||
@@ -41,6 +41,7 @@ import { TextEditorPanel, useTextEditorWindow } from './TextEditorPanel.js';
|
|||||||
import { FileExplorerPanel } from './FileExplorerPanel.js';
|
import { FileExplorerPanel } from './FileExplorerPanel.js';
|
||||||
import { EchoServerPanel, SimpleClientPanel } from './EchoPanels.js';
|
import { EchoServerPanel, SimpleClientPanel } from './EchoPanels.js';
|
||||||
import { DnsServerPanel } from './DnsServerPanel.js';
|
import { DnsServerPanel } from './DnsServerPanel.js';
|
||||||
|
import { PersonalFirewallPanel } from './PersonalFirewallPanel.js';
|
||||||
import { ImageViewerPanel, useImageViewer } from './ImageViewerPanel.js';
|
import { ImageViewerPanel, useImageViewer } from './ImageViewerPanel.js';
|
||||||
import { WebServerPanel } from './WebServerPanel.js';
|
import { WebServerPanel } from './WebServerPanel.js';
|
||||||
import './desktop.css';
|
import './desktop.css';
|
||||||
@@ -71,7 +72,7 @@ export interface DesktopWindowProps {
|
|||||||
export type DesktopWindowName = DesktopProgram | 'installation';
|
export type DesktopWindowName = DesktopProgram | 'installation';
|
||||||
|
|
||||||
/** The programs whose windows are built so far. */
|
/** The programs whose windows are built so far. */
|
||||||
const WITH_WINDOW: ReadonlySet<DesktopProgram> = new Set(['commandPrompt', 'webBrowser', 'webServer', 'emailClient', 'emailServer', 'textEditor', 'fileExplorer', 'imageViewer', 'echoServer', 'simpleClient', 'dnsServer']);
|
const WITH_WINDOW: ReadonlySet<DesktopProgram> = new Set(['commandPrompt', 'webBrowser', 'webServer', 'emailClient', 'emailServer', 'textEditor', 'fileExplorer', 'imageViewer', 'echoServer', 'simpleClient', 'dnsServer', 'firewall']);
|
||||||
|
|
||||||
/** The document's desktop of a computer, read anew after every change (a program installed, the label changed). */
|
/** The document's desktop of a computer, read anew after every change (a program installed, the label changed). */
|
||||||
function useDesktopView(doc: Y.Doc, id: string) {
|
function useDesktopView(doc: Y.Doc, id: string) {
|
||||||
@@ -189,6 +190,7 @@ export function DesktopWindow({ doc, id, simulation, active, x, y, z, program: o
|
|||||||
{shown === 'echoServer' ? <EchoServerPanel node={id} simulation={simulation} /> : null}
|
{shown === 'echoServer' ? <EchoServerPanel node={id} simulation={simulation} /> : null}
|
||||||
{shown === 'simpleClient' ? <SimpleClientPanel node={id} simulation={simulation} /> : null}
|
{shown === 'simpleClient' ? <SimpleClientPanel node={id} simulation={simulation} /> : null}
|
||||||
{shown === 'dnsServer' ? <DnsServerPanel node={id} simulation={simulation} /> : null}
|
{shown === 'dnsServer' ? <DnsServerPanel node={id} simulation={simulation} /> : null}
|
||||||
|
{shown === 'firewall' ? <PersonalFirewallPanel node={id} simulation={simulation} /> : null}
|
||||||
</ProgramFrame>
|
</ProgramFrame>
|
||||||
)}
|
)}
|
||||||
{info ? (
|
{info ? (
|
||||||
|
|||||||
@@ -0,0 +1,159 @@
|
|||||||
|
// A computer's firewall, after Java's GUIApplicationFirewallWindow (measured
|
||||||
|
// with tools/java-oracle's FirewallWindowRun): two tabs. The first holds the
|
||||||
|
// three boxes — the firewall on, ICMP allowed, UDP filtered; the last two
|
||||||
|
// only while it is on —, the port with its protocol (TCP, UDP, "*") and its
|
||||||
|
// senders (all, or those of the same network), "Hinzufügen" (the field is
|
||||||
|
// emptied after it), "Löschen" of the row chosen, and the table of rules.
|
||||||
|
// The second holds the log: the firewall's lines and the rules added or
|
||||||
|
// removed. The firewall is the room's: the panel shows its view and lines
|
||||||
|
// and hands over what is done.
|
||||||
|
//
|
||||||
|
// Better than Java:
|
||||||
|
// - a port that is none (Java takes any number, and refuses one after a
|
||||||
|
// space without a word) is marked red and not handed over.
|
||||||
|
// - Java writes the firewall's explanation but never shows it; here it
|
||||||
|
// stands under the boxes.
|
||||||
|
// - a rule the window cannot make (from a file: one that drops, or names an
|
||||||
|
// address) shows what it is in the sender column; Java shows it as a port
|
||||||
|
// opened for all senders.
|
||||||
|
// - the log cannot be typed into (Java's can).
|
||||||
|
|
||||||
|
import { useEffect, useLayoutEffect, useRef, useState, type KeyboardEvent } from 'react';
|
||||||
|
import { firewallPortOfText, type FirewallLogEntry, type LogLine, type PortRuleProtocol } from '@mdg-filius/core';
|
||||||
|
import type { FirewallView, FirewallViewRule } from '@mdg-filius/protocol';
|
||||||
|
import { useTranslate, type Translate } from '../i18n/i18n.js';
|
||||||
|
import type { Simulation } from '../simulation/useSimulation.js';
|
||||||
|
import { firewallLine } from './serverLines.js';
|
||||||
|
|
||||||
|
const NO_LINES: readonly LogLine<FirewallLogEntry>[] = [];
|
||||||
|
const PROTOCOL_NAMES: Readonly<Record<FirewallViewRule['protocol'], string>> = { tcp: 'TCP', udp: 'UDP', all: '*', icmp: 'ICMP' };
|
||||||
|
/** Java's list: TCP, UDP, "*". */
|
||||||
|
const PROTOCOLS: readonly PortRuleProtocol[] = ['tcp', 'udp', 'all'];
|
||||||
|
|
||||||
|
/** A rule's cells as Java's ruleToVector writes them: protocol, port (none for every port), sender. */
|
||||||
|
export function firewallRow(rule: FirewallViewRule, t: Translate): readonly [string, string, string] {
|
||||||
|
const sender = rule.sender === 'all' ? t('personalFirewall.allSenders') : rule.sender === 'sameNetwork' ? t('personalFirewall.localNetwork') : rule.text;
|
||||||
|
return [PROTOCOL_NAMES[rule.protocol], rule.port >= 0 ? String(rule.port) : '', sender];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function PersonalFirewallPanel({ node, simulation }: { readonly node: string; readonly simulation: Simulation }) {
|
||||||
|
const t = useTranslate();
|
||||||
|
const topic = `app:${node}/firewall` as const;
|
||||||
|
const { watch, act } = simulation;
|
||||||
|
useEffect(() => watch([topic]), [watch, topic]);
|
||||||
|
const view = simulation.views.get(topic) as FirewallView | null | undefined;
|
||||||
|
const [tab, setTab] = useState<'setup' | 'log'>('setup');
|
||||||
|
const [port, setPort] = useState('');
|
||||||
|
const [protocol, setProtocol] = useState<PortRuleProtocol>('tcp');
|
||||||
|
const [sameNetworkOnly, setSameNetworkOnly] = useState(false);
|
||||||
|
const [chosen, setChosen] = useState<number | null>(null);
|
||||||
|
const lines = (simulation.apps.get(topic as never)?.lines as readonly LogLine<FirewallLogEntry>[] | undefined) ?? NO_LINES;
|
||||||
|
const log = lines.map((each) => `${firewallLine(each, t)}\n`).join('');
|
||||||
|
const logElement = useRef<HTMLPreElement>(null);
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
if (logElement.current !== null) logElement.current.scrollTop = logElement.current.scrollHeight;
|
||||||
|
}, [log, tab]);
|
||||||
|
if (view === null || view === undefined) return <div className="web-server" />;
|
||||||
|
const typed = firewallPortOfText(port);
|
||||||
|
const chosenRow = chosen !== null && chosen < view.rules.length ? chosen : null;
|
||||||
|
|
||||||
|
const add = () => {
|
||||||
|
if (typed === undefined) return;
|
||||||
|
act(node, 'firewall', 'addRule', { port: typed, protocol, sameNetworkOnly });
|
||||||
|
setPort('');
|
||||||
|
};
|
||||||
|
const remove = () => {
|
||||||
|
if (chosenRow === null) return;
|
||||||
|
act(node, 'firewall', 'removeRule', { row: chosenRow });
|
||||||
|
setChosen(null);
|
||||||
|
};
|
||||||
|
const choose = (row: number) => (event: KeyboardEvent<HTMLTableRowElement>) => {
|
||||||
|
if (event.key !== 'Enter' && event.key !== ' ') return;
|
||||||
|
event.preventDefault();
|
||||||
|
setChosen(row);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="web-server personal-firewall">
|
||||||
|
<div className="mail-tabs" role="tablist">
|
||||||
|
{(['setup', 'log'] as const).map((each) => (
|
||||||
|
<button key={each} type="button" role="tab" aria-selected={tab === each} onClick={() => setTab(each)}>
|
||||||
|
{t(`personalFirewall.${each}`)}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
{tab === 'log' ? (
|
||||||
|
<pre className="web-server-log" ref={logElement} role="tabpanel" aria-label={t('personalFirewall.log')} tabIndex={0}>
|
||||||
|
{log}
|
||||||
|
</pre>
|
||||||
|
) : (
|
||||||
|
<div className="personal-firewall-page" role="tabpanel" aria-label={t('personalFirewall.setup')}>
|
||||||
|
<div className="personal-firewall-boxes">
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" checked={view.active} onChange={(event) => act(node, 'firewall', 'setActive', { active: event.target.checked })} />
|
||||||
|
{t('firewall.active')}
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" checked={view.icmpAllowed} disabled={!view.active} onChange={(event) => act(node, 'firewall', 'setIcmpAllowed', { allowed: event.target.checked })} />
|
||||||
|
{t('personalFirewall.allowIcmp')}
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" checked={view.filterUdp} disabled={!view.active} onChange={(event) => act(node, 'firewall', 'setFilterUdp', { enabled: event.target.checked })} />
|
||||||
|
{t('personalFirewall.filterUdp')}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<p className="personal-firewall-hint">{t('personalFirewall.hint')}</p>
|
||||||
|
<div className="web-server-bar">
|
||||||
|
<label>
|
||||||
|
{t('personalFirewall.port')}
|
||||||
|
<input
|
||||||
|
value={port}
|
||||||
|
inputMode="numeric"
|
||||||
|
aria-invalid={port !== '' && typed === undefined ? 'true' : undefined}
|
||||||
|
onChange={(event) => setPort(event.target.value)}
|
||||||
|
onKeyDown={(event) => event.key === 'Enter' && add()}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<select aria-label={t('firewall.protocol')} value={protocol} onChange={(event) => setProtocol(event.target.value as PortRuleProtocol)}>
|
||||||
|
{PROTOCOLS.map((each) => (
|
||||||
|
<option key={each} value={each}>
|
||||||
|
{PROTOCOL_NAMES[each]}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
<select aria-label={t('personalFirewall.sender')} value={sameNetworkOnly ? 'sameNetwork' : 'all'} onChange={(event) => setSameNetworkOnly(event.target.value === 'sameNetwork')}>
|
||||||
|
<option value="all">{t('personalFirewall.allSenders')}</option>
|
||||||
|
<option value="sameNetwork">{t('personalFirewall.sameNetwork')}</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div className="web-server-bar">
|
||||||
|
<button type="button" disabled={typed === undefined} onClick={add}>
|
||||||
|
{t('personalFirewall.add')}
|
||||||
|
</button>
|
||||||
|
<button type="button" disabled={chosenRow === null} onClick={remove}>
|
||||||
|
{t('personalFirewall.delete')}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<table className="dns-server-table personal-firewall-table" aria-label={t('personalFirewall.rules')}>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>{t('firewall.protocol')}</th>
|
||||||
|
<th>{t('personalFirewall.portColumn')}</th>
|
||||||
|
<th>{t('personalFirewall.sender')}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{view.rules.map((rule, row) => (
|
||||||
|
<tr key={row} tabIndex={0} aria-selected={row === chosenRow} onPointerDown={() => setChosen(row)} onKeyDown={choose(row)}>
|
||||||
|
{firewallRow(rule, t).map((cell, column) => (
|
||||||
|
<td key={column}>{cell}</td>
|
||||||
|
))}
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1140,3 +1140,65 @@
|
|||||||
.dns-server-table tr[aria-selected='true'] input {
|
.dns-server-table tr[aria-selected='true'] input {
|
||||||
background: #b8cfe5;
|
background: #b8cfe5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* A computer's firewall (GUIApplicationFirewallWindow): two tabs, the boxes,
|
||||||
|
* the port with its two lists, two buttons and the table of rules; the log. */
|
||||||
|
.personal-firewall-page {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
overflow: auto;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.personal-firewall-boxes {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.personal-firewall-boxes label {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
min-height: 44px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.personal-firewall-boxes input {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.personal-firewall-hint {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.personal-firewall input:not([type='checkbox']),
|
||||||
|
.personal-firewall select {
|
||||||
|
min-height: 32px;
|
||||||
|
padding: 0 6px;
|
||||||
|
border: 1px solid #a0a0a0;
|
||||||
|
background: #ffffff;
|
||||||
|
color: #000000;
|
||||||
|
font: 16px Dialog, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.personal-firewall input:not([type='checkbox']) {
|
||||||
|
width: 6em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.personal-firewall [aria-invalid='true'] {
|
||||||
|
border-color: #ff0000;
|
||||||
|
color: #ff0000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.personal-firewall-table td {
|
||||||
|
min-height: 32px;
|
||||||
|
padding: 6px;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
}
|
||||||
|
|
||||||
|
.personal-firewall-table tr {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,9 +2,10 @@
|
|||||||
// ServerMitarbeiter, TCPSocket's failures), and the generic client's as its
|
// ServerMitarbeiter, TCPSocket's failures), and the generic client's as its
|
||||||
// window writes them (ClientBaustein, Socket, TCPSocket), in the person's
|
// window writes them (ClientBaustein, Socket, TCPSocket), in the person's
|
||||||
// language: one line each (a failed connection two), as Java appends them to
|
// language: one line each (a failed connection two), as Java appends them to
|
||||||
// its window's text area.
|
// its window's text area. And a computer's firewall's log as its window
|
||||||
|
// writes it (Firewall.benachrichtigeBeobachter, GUIApplicationFirewallWindow).
|
||||||
|
|
||||||
import type { ServerLogEntry, SimpleClientLogEntry } from '@mdg-filius/core';
|
import { firewallRuleText, PORT_RULE_PROTOCOL_NAMES, type FirewallLogEntry, type ServerLogEntry, type SimpleClientLogEntry } from '@mdg-filius/core';
|
||||||
import type { Translate } from '../i18n/i18n.js';
|
import type { Translate } from '../i18n/i18n.js';
|
||||||
|
|
||||||
export function serverLine(entry: ServerLogEntry, t: Translate): string {
|
export function serverLine(entry: ServerLogEntry, t: Translate): string {
|
||||||
@@ -45,3 +46,20 @@ export function simpleClientLine(entry: SimpleClientLogEntry, t: Translate): str
|
|||||||
return t(`server.sendFailed.${entry.failure}`);
|
return t(`server.sendFailed.${entry.failure}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function firewallLine(entry: FirewallLogEntry, t: Translate): string {
|
||||||
|
switch (entry.kind) {
|
||||||
|
case 'ruleMatched':
|
||||||
|
return t('personalFirewall.ruleMatched', {
|
||||||
|
number: entry.ruleNumber,
|
||||||
|
rule: firewallRuleText(entry.rule, entry.firstInterface),
|
||||||
|
action: t(entry.accepted ? 'firewall.accept' : 'firewall.drop'),
|
||||||
|
});
|
||||||
|
case 'icmpDropped':
|
||||||
|
return t('personalFirewall.icmpDropped', { sender: entry.senderIPAddress, receiver: entry.receiverIPAddress, code: entry.code, type: entry.type });
|
||||||
|
case 'portRuleAdded':
|
||||||
|
return t('personalFirewall.portRuleAdded', { port: entry.port, protocol: PORT_RULE_PROTOCOL_NAMES[entry.protocol] });
|
||||||
|
case 'portRuleRemoved':
|
||||||
|
return t('personalFirewall.portRuleRemoved');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -248,6 +248,24 @@
|
|||||||
"network.ipAddress": "IP-Adresse:",
|
"network.ipAddress": "IP-Adresse:",
|
||||||
"network.macAddress": "MAC-Adresse:",
|
"network.macAddress": "MAC-Adresse:",
|
||||||
"network.subnetMask": "Netzmaske:",
|
"network.subnetMask": "Netzmaske:",
|
||||||
|
"personalFirewall.add": "Hinzufügen",
|
||||||
|
"personalFirewall.allSenders": "Alle Absender",
|
||||||
|
"personalFirewall.allowIcmp": "Internet Control Message Protocol (Ping etc.) erlauben",
|
||||||
|
"personalFirewall.delete": "Löschen",
|
||||||
|
"personalFirewall.filterUdp": "Aktiviere UDP-Datenverkehrfilter",
|
||||||
|
"personalFirewall.hint": "Die Firewall blockiert alle eingehenden IP-Pakete, wenn sie aktiviert wurde. Ausnahmen müssen explizit angegeben werden. Regeln bestimmen TCP-/UDP-Ports, für die Daten zugelassen werden und ob die Ausnahme nur für alle Absender oder nur für Absender im lokalen Rechnernetz gilt.",
|
||||||
|
"personalFirewall.icmpDropped": "ICMP-Paket verworfen: {sender} -> {receiver} (code: {code}, type: {type})",
|
||||||
|
"personalFirewall.localNetwork": "Nur lokales Rechnernetz",
|
||||||
|
"personalFirewall.log": "Log",
|
||||||
|
"personalFirewall.port": "Zulässiger Port/Protokoll:",
|
||||||
|
"personalFirewall.portColumn": "Port",
|
||||||
|
"personalFirewall.portRuleAdded": "Portregel wurde hinzugefügt: {port}/{protocol}",
|
||||||
|
"personalFirewall.portRuleRemoved": "Portregel wurde entfernt.",
|
||||||
|
"personalFirewall.ruleMatched": "Firewall-Regel hat gegriffen: #{number} ({rule}) -> {action}",
|
||||||
|
"personalFirewall.rules": "Regeln",
|
||||||
|
"personalFirewall.sameNetwork": "Absender im gleichen Rechnernetz",
|
||||||
|
"personalFirewall.sender": "Absender",
|
||||||
|
"personalFirewall.setup": "Konfiguration",
|
||||||
"program.commandPrompt": "Befehlszeile",
|
"program.commandPrompt": "Befehlszeile",
|
||||||
"program.dnsServer": "DNS-Server",
|
"program.dnsServer": "DNS-Server",
|
||||||
"program.echoServer": "Echo-Server",
|
"program.echoServer": "Echo-Server",
|
||||||
|
|||||||
@@ -248,6 +248,24 @@
|
|||||||
"network.ipAddress": "IP address:",
|
"network.ipAddress": "IP address:",
|
||||||
"network.macAddress": "MAC address:",
|
"network.macAddress": "MAC address:",
|
||||||
"network.subnetMask": "Netmask:",
|
"network.subnetMask": "Netmask:",
|
||||||
|
"personalFirewall.add": "Add",
|
||||||
|
"personalFirewall.allSenders": "All sender",
|
||||||
|
"personalFirewall.allowIcmp": "Allow Internet Control Message Protocol (Ping etc.)",
|
||||||
|
"personalFirewall.delete": "Delete",
|
||||||
|
"personalFirewall.filterUdp": "Activate UDP traffic filter",
|
||||||
|
"personalFirewall.hint": "The firewall blocks incoming IP packets, if it is activated. Exceptions must be declared. Rules define opened ports and whether they are opened for global or local sender.",
|
||||||
|
"personalFirewall.icmpDropped": "Dropped ICMP packet: {sender} -> {receiver} (code: {code}, type: {type})",
|
||||||
|
"personalFirewall.localNetwork": "Local network",
|
||||||
|
"personalFirewall.log": "Log",
|
||||||
|
"personalFirewall.port": "Opened port/protocol:",
|
||||||
|
"personalFirewall.portColumn": "Port",
|
||||||
|
"personalFirewall.portRuleAdded": "Port rule is added: {port}/{protocol}",
|
||||||
|
"personalFirewall.portRuleRemoved": "Delete port rule.",
|
||||||
|
"personalFirewall.ruleMatched": "Firewall rule matched: #{number} ({rule}) -> {action}",
|
||||||
|
"personalFirewall.rules": "Rules",
|
||||||
|
"personalFirewall.sameNetwork": "Local sender",
|
||||||
|
"personalFirewall.sender": "Sender",
|
||||||
|
"personalFirewall.setup": "Setup",
|
||||||
"program.commandPrompt": "Command Line",
|
"program.commandPrompt": "Command Line",
|
||||||
"program.dnsServer": "DNS server",
|
"program.dnsServer": "DNS server",
|
||||||
"program.echoServer": "Echo server",
|
"program.echoServer": "Echo server",
|
||||||
|
|||||||
@@ -175,6 +175,20 @@ const FROM_JAVA: Readonly<Record<string, string>> = {
|
|||||||
'firewall.dropIcmp': 'jfirewalldialog_msg40',
|
'firewall.dropIcmp': 'jfirewalldialog_msg40',
|
||||||
'firewall.dropIcmpHint': 'jfirewalldialog_msg41',
|
'firewall.dropIcmpHint': 'jfirewalldialog_msg41',
|
||||||
'firewall.filterSyn': 'jfirewalldialog_msg42',
|
'firewall.filterSyn': 'jfirewalldialog_msg42',
|
||||||
|
'personalFirewall.hint': 'firewall_msg2',
|
||||||
|
'personalFirewall.port': 'firewall_msg3',
|
||||||
|
'personalFirewall.allSenders': 'firewall_msg4',
|
||||||
|
'personalFirewall.sameNetwork': 'firewall_msg5',
|
||||||
|
'personalFirewall.add': 'firewall_msg6',
|
||||||
|
'personalFirewall.delete': 'firewall_msg7',
|
||||||
|
'personalFirewall.portColumn': 'firewall_msg8',
|
||||||
|
'personalFirewall.sender': 'firewall_msg9',
|
||||||
|
'personalFirewall.setup': 'firewall_msg10',
|
||||||
|
'personalFirewall.log': 'firewall_msg11',
|
||||||
|
'personalFirewall.localNetwork': 'firewall_msg12',
|
||||||
|
'personalFirewall.allowIcmp': 'firewall_msg13',
|
||||||
|
'personalFirewall.filterUdp': 'firewall_msg14',
|
||||||
|
'personalFirewall.portRuleRemoved': 'sw_firewall_msg7',
|
||||||
'sheet.connections': 'jvermittlungsrechnerkonfiguration_msg23',
|
'sheet.connections': 'jvermittlungsrechnerkonfiguration_msg23',
|
||||||
'connections.local': 'jvermittlungsrechnerkonfiguration_msg22',
|
'connections.local': 'jvermittlungsrechnerkonfiguration_msg22',
|
||||||
'connections.add': 'jvermittlungsrechnerkonfiguration_msg24',
|
'connections.add': 'jvermittlungsrechnerkonfiguration_msg24',
|
||||||
@@ -319,6 +333,9 @@ const FROM_JAVA_JOINED: Readonly<Record<string, readonly [string, string, string
|
|||||||
|
|
||||||
/** Java's words before a value, joined as its programs join them (SMTPMitarbeiter, SMTPClient, the server window's question). */
|
/** Java's words before a value, joined as its programs join them (SMTPMitarbeiter, SMTPClient, the server window's question). */
|
||||||
const FROM_JAVA_BEFORE: Readonly<Record<string, readonly [string, string, string?]>> = {
|
const FROM_JAVA_BEFORE: Readonly<Record<string, readonly [string, string, string?]>> = {
|
||||||
|
'personalFirewall.ruleMatched': ['sw_firewall_msg8', ' #{number} ({rule}) -> {action}'],
|
||||||
|
'personalFirewall.icmpDropped': ['firewallthread_msg1', ' {sender} -> {receiver} (code: {code}, type: {type})'],
|
||||||
|
'personalFirewall.portRuleAdded': ['sw_firewall_msg5', ' {port}/{protocol}'],
|
||||||
'mail.sendFailed': ['sw_smtpmitarbeiter_msg14', ' {address}'],
|
'mail.sendFailed': ['sw_smtpmitarbeiter_msg14', ' {address}'],
|
||||||
'mail.mailSent': ['sw_smtpclient_msg4', ' {recipients}'],
|
'mail.mailSent': ['sw_smtpclient_msg4', ' {recipients}'],
|
||||||
// "'" + name + "'", the question's two halves written against it.
|
// "'" + name + "'", the question's two halves written against it.
|
||||||
|
|||||||
@@ -1,13 +1,17 @@
|
|||||||
// A server's lines as Java's web server window wrote them
|
// A server's lines as Java's web server window wrote them
|
||||||
// (packages/sim/test/browserWindow.java.json, BrowserWindowRun): each kind
|
// (packages/sim/test/browserWindow.java.json, BrowserWindowRun): each kind
|
||||||
// the window got there, in Java's words.
|
// the window got there, in Java's words. And a computer's firewall's log and
|
||||||
|
// table as Java's firewall window wrote them
|
||||||
|
// (packages/core/test/firewallWindow.java.json, FirewallWindowRun).
|
||||||
|
|
||||||
import { readFileSync } from 'node:fs';
|
import { readFileSync } from 'node:fs';
|
||||||
import { dirname, join } from 'node:path';
|
import { dirname, join } from 'node:path';
|
||||||
import { fileURLToPath } from 'node:url';
|
import { fileURLToPath } from 'node:url';
|
||||||
import { describe, expect, it } from 'vitest';
|
import { describe, expect, it } from 'vitest';
|
||||||
import type { ServerLogEntry, SimpleClientLogEntry } from '@mdg-filius/core';
|
import { DIRECTLY_CONNECTED_SOURCE_MARKER, firewallRule, type FirewallLogEntry, type ServerLogEntry, type SimpleClientLogEntry } from '@mdg-filius/core';
|
||||||
import { serverLine, simpleClientLine } from '../src/desktop/serverLines.js';
|
import type { FirewallViewRule } from '@mdg-filius/protocol';
|
||||||
|
import { firewallRow } from '../src/desktop/PersonalFirewallPanel.js';
|
||||||
|
import { firewallLine, serverLine, simpleClientLine } from '../src/desktop/serverLines.js';
|
||||||
import { translate } from '../src/i18n/i18n.js';
|
import { translate } from '../src/i18n/i18n.js';
|
||||||
|
|
||||||
const java = JSON.parse(readFileSync(join(dirname(fileURLToPath(import.meta.url)), '../../sim/test/browserWindow.java.json'), 'utf8')) as { steps: { step: string; log?: string }[] };
|
const java = JSON.parse(readFileSync(join(dirname(fileURLToPath(import.meta.url)), '../../sim/test/browserWindow.java.json'), 'utf8')) as { steps: { step: string; log?: string }[] };
|
||||||
@@ -78,3 +82,43 @@ describe("the echo windows' lines", () => {
|
|||||||
]).toEqual(['Fehler beim Verbindungsaufbau', 'Fehler beim Verbindungsaufbau\nAktives Öffnen des Sockets fehlgeschlagen', 'Keine Verbindung hergestellt!', 'Verbindung nicht aufgebaut!']);
|
]).toEqual(['Fehler beim Verbindungsaufbau', 'Fehler beim Verbindungsaufbau\nAktives Öffnen des Sockets fehlgeschlagen', 'Keine Verbindung hergestellt!', 'Verbindung nicht aufgebaut!']);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("a computer's firewall's lines", () => {
|
||||||
|
const firewallJava = JSON.parse(readFileSync(join(dirname(fileURLToPath(import.meta.url)), '../../core/test/firewallWindow.java.json'), 'utf8')) as { steps: { step: string; log: string }[] };
|
||||||
|
const javaLog = firewallJava.steps.find((step) => step.step === 'delete row 0')!.log.split('\n');
|
||||||
|
|
||||||
|
it("are Java's, line by line", () => {
|
||||||
|
const network = { ipAddress: '10.0.0.10', subnetMask: '255.255.255.0' };
|
||||||
|
const entries: FirewallLogEntry[] = [
|
||||||
|
{ kind: 'portRuleAdded', port: 55555, protocol: 'tcp' },
|
||||||
|
{ kind: 'ruleMatched', ruleNumber: 1, rule: firewallRule({ port: 55555, protocol: 'tcp', action: 'accept' }), firstInterface: network, accepted: true },
|
||||||
|
{ kind: 'ruleMatched', ruleNumber: 2, rule: firewallRule({ port: 53, protocol: 'udp', action: 'accept', sourceIPAddress: DIRECTLY_CONNECTED_SOURCE_MARKER }), firstInterface: network, accepted: true },
|
||||||
|
{ kind: 'icmpDropped', senderIPAddress: '10.0.0.2', receiverIPAddress: '10.0.0.10', type: 8, code: 0 },
|
||||||
|
{ kind: 'portRuleAdded', port: -1, protocol: 'all' },
|
||||||
|
{ kind: 'portRuleRemoved' },
|
||||||
|
];
|
||||||
|
const lines = entries.map((entry) => firewallLine(entry, t));
|
||||||
|
for (const line of lines) expect(javaLog, line).toContain(line);
|
||||||
|
expect(firewallLine({ kind: 'ruleMatched', ruleNumber: 3, rule: firewallRule({ port: 80 }), firstInterface: undefined, accepted: false }, t)).toBe(
|
||||||
|
'Firewall-Regel hat gegriffen: #3 (*/* -> */*; TCP:80 => DROP) -> verwerfen',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("are the table's rows as Java's ruleToVector writes them, and name a rule the window cannot make", () => {
|
||||||
|
expect(
|
||||||
|
[
|
||||||
|
{ protocol: 'tcp', port: 55555, sender: 'all', text: '' },
|
||||||
|
{ protocol: 'udp', port: 53, sender: 'sameNetwork', text: '' },
|
||||||
|
{ protocol: 'all', port: -1, sender: 'all', text: '' },
|
||||||
|
{ protocol: 'tcp', port: 0, sender: 'all', text: '' },
|
||||||
|
{ protocol: 'icmp', port: -1, sender: null, text: '10.0.0.0/255.0.0.0 -> */*; ?:* => DROP' },
|
||||||
|
].map((rule) => firewallRow(rule as FirewallViewRule, t)),
|
||||||
|
).toEqual([
|
||||||
|
['TCP', '55555', 'Alle Absender'],
|
||||||
|
['UDP', '53', 'Nur lokales Rechnernetz'],
|
||||||
|
['*', '', 'Alle Absender'],
|
||||||
|
['TCP', '0', 'Alle Absender'],
|
||||||
|
['ICMP', '', '10.0.0.0/255.0.0.0 -> */*; ?:* => DROP'],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
// What a computer's firewall window shows besides its log, as the room keeps
|
||||||
|
// it and every client shows it (plan §7.3), after
|
||||||
|
// GUIApplicationFirewallWindow (measured with tools/java-oracle's
|
||||||
|
// FirewallWindowRun): its three boxes and the rules of its table. Its log
|
||||||
|
// comes as the window's lines: the firewall's log entries.
|
||||||
|
|
||||||
|
/** A rule as the window's table shows it. */
|
||||||
|
export interface FirewallViewRule {
|
||||||
|
readonly protocol: 'all' | 'tcp' | 'udp' | 'icmp';
|
||||||
|
/** The port, or -1 for every port. */
|
||||||
|
readonly port: number;
|
||||||
|
/** Java's sender column; null for a rule the window cannot make (one from a file that drops, or names an address). */
|
||||||
|
readonly sender: 'all' | 'sameNetwork' | null;
|
||||||
|
/** The rule whole, as the log names it (FirewallRule.toString). */
|
||||||
|
readonly text: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FirewallView {
|
||||||
|
readonly active: boolean;
|
||||||
|
/** "Internet Control Message Protocol (Ping etc.) erlauben": the firewall does not drop ICMP. */
|
||||||
|
readonly icmpAllowed: boolean;
|
||||||
|
readonly filterUdp: boolean;
|
||||||
|
readonly rules: readonly FirewallViewRule[];
|
||||||
|
}
|
||||||
@@ -4,5 +4,6 @@ export * from './sim.js';
|
|||||||
export * from './terminal.js';
|
export * from './terminal.js';
|
||||||
export * from './dns.js';
|
export * from './dns.js';
|
||||||
export * from './echo.js';
|
export * from './echo.js';
|
||||||
|
export * from './firewall.js';
|
||||||
export * from './mail.js';
|
export * from './mail.js';
|
||||||
export * from './web.js';
|
export * from './web.js';
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import type { CaptureRow, PageLanguage } from '@mdg-filius/core';
|
|||||||
export const SIM_PROTOCOL_VERSION = 1;
|
export const SIM_PROTOCOL_VERSION = 1;
|
||||||
|
|
||||||
/** The programs whose windows a client can watch and act in. */
|
/** The programs whose windows a client can watch and act in. */
|
||||||
export const APP_KINDS = ['terminal', 'webBrowser', 'webServer', 'emailClient', 'emailServer', 'echoServer', 'simpleClient', 'gnutella', 'dnsServer'] as const;
|
export const APP_KINDS = ['terminal', 'webBrowser', 'webServer', 'emailClient', 'emailServer', 'echoServer', 'simpleClient', 'gnutella', 'dnsServer', 'firewall'] as const;
|
||||||
export type AppKind = (typeof APP_KINDS)[number];
|
export type AppKind = (typeof APP_KINDS)[number];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -48,7 +48,9 @@ import {
|
|||||||
DEFAULT_SPEED_PERCENT,
|
DEFAULT_SPEED_PERCENT,
|
||||||
NetworkRuntimeEngine,
|
NetworkRuntimeEngine,
|
||||||
captureRows,
|
captureRows,
|
||||||
|
firewallRuleText,
|
||||||
interfaceKey,
|
interfaceKey,
|
||||||
|
portRuleSender,
|
||||||
stableGeneratedMacAddress,
|
stableGeneratedMacAddress,
|
||||||
type CapturedFrame,
|
type CapturedFrame,
|
||||||
type CaptureRow,
|
type CaptureRow,
|
||||||
@@ -73,6 +75,7 @@ import {
|
|||||||
type EmailServerView,
|
type EmailServerView,
|
||||||
type DnsServerView,
|
type DnsServerView,
|
||||||
type EchoServerView,
|
type EchoServerView,
|
||||||
|
type FirewallView,
|
||||||
type SimpleClientView,
|
type SimpleClientView,
|
||||||
type TraceFrame,
|
type TraceFrame,
|
||||||
type WebServerView,
|
type WebServerView,
|
||||||
@@ -463,6 +466,8 @@ export class SimulationRoom {
|
|||||||
return this.actInSimpleClient(input);
|
return this.actInSimpleClient(input);
|
||||||
case 'dnsServer':
|
case 'dnsServer':
|
||||||
return this.actInDnsServer(input);
|
return this.actInDnsServer(input);
|
||||||
|
case 'firewall':
|
||||||
|
return this.actInFirewall(input);
|
||||||
default:
|
default:
|
||||||
return 'unknownAction';
|
return 'unknownAction';
|
||||||
}
|
}
|
||||||
@@ -498,6 +503,36 @@ export class SimulationRoom {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** A computer's firewall window: its three boxes, "Hinzufügen" with the port, the protocol and the senders chosen, "Löschen" of the row chosen. */
|
||||||
|
private actInFirewall(input: Extract<ClientMessage, { kind: 'input' }>): ErrorCode | undefined {
|
||||||
|
const configuration = this.engine.personalFirewall(input.node);
|
||||||
|
if (configuration === undefined) return 'unknownApp';
|
||||||
|
const { args } = input;
|
||||||
|
const set = (fields: Partial<typeof configuration>) => this.engine.setFirewallConfiguration(input.node, { ...configuration, ...fields });
|
||||||
|
switch (input.action) {
|
||||||
|
case 'setActive':
|
||||||
|
if (typeof args.active !== 'boolean') return 'badArgs';
|
||||||
|
set({ isActive: args.active });
|
||||||
|
return undefined;
|
||||||
|
case 'setIcmpAllowed':
|
||||||
|
if (typeof args.allowed !== 'boolean') return 'badArgs';
|
||||||
|
set({ dropIcmp: !args.allowed });
|
||||||
|
return undefined;
|
||||||
|
case 'setFilterUdp':
|
||||||
|
if (typeof args.enabled !== 'boolean') return 'badArgs';
|
||||||
|
set({ filterUdp: args.enabled });
|
||||||
|
return undefined;
|
||||||
|
case 'addRule':
|
||||||
|
if (!isPort(args.port) || args.port === 0 || (args.protocol !== 'tcp' && args.protocol !== 'udp' && args.protocol !== 'all') || typeof args.sameNetworkOnly !== 'boolean') return 'badArgs';
|
||||||
|
this.engine.addFirewallPortRule(input.node, args.port, args.protocol, args.sameNetworkOnly);
|
||||||
|
return undefined;
|
||||||
|
case 'removeRule':
|
||||||
|
return Number.isInteger(args.row) && this.engine.removeFirewallRule(input.node, args.row as number) ? undefined : 'badArgs';
|
||||||
|
default:
|
||||||
|
return 'unknownAction';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** The start button with the port typed (Java's window sets the port before and after it starts the server). */
|
/** The start button with the port typed (Java's window sets the port before and after it starts the server). */
|
||||||
private actInEchoServer(input: Extract<ClientMessage, { kind: 'input' }>): ErrorCode | undefined {
|
private actInEchoServer(input: Extract<ClientMessage, { kind: 'input' }>): ErrorCode | undefined {
|
||||||
const server = this.engine.echoServer(input.node);
|
const server = this.engine.echoServer(input.node);
|
||||||
@@ -680,6 +715,14 @@ export class SimulationRoom {
|
|||||||
const view: DnsServerView = { active: server.isActive, recursive: server.recursiveResolutionEnabled, rows: { A: rows('A'), MX: rows('MX'), NS: rows('NS') } };
|
const view: DnsServerView = { active: server.isActive, recursive: server.recursiveResolutionEnabled, rows: { A: rows('A'), MX: rows('MX'), NS: rows('NS') } };
|
||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
case 'firewall': {
|
||||||
|
const configuration = this.engine.personalFirewall(node);
|
||||||
|
if (configuration === undefined) return null;
|
||||||
|
const first = this.engine.networkInterfaces(node)[0];
|
||||||
|
const rules = configuration.rules.map((rule) => ({ protocol: rule.protocol, port: rule.port, sender: portRuleSender(rule) ?? null, text: firewallRuleText(rule, first) }));
|
||||||
|
const view: FirewallView = { active: configuration.isActive, icmpAllowed: !configuration.dropIcmp, filterUdp: configuration.filterUdp, rules };
|
||||||
|
return view;
|
||||||
|
}
|
||||||
case 'echoServer': {
|
case 'echoServer': {
|
||||||
const server = this.engine.echoServer(node);
|
const server = this.engine.echoServer(node);
|
||||||
if (server === undefined) return null;
|
if (server === undefined) return null;
|
||||||
@@ -725,6 +768,9 @@ export class SimulationRoom {
|
|||||||
return this.engine.simpleClient(node);
|
return this.engine.simpleClient(node);
|
||||||
case 'gnutella':
|
case 'gnutella':
|
||||||
return this.engine.gnutella(node);
|
return this.engine.gnutella(node);
|
||||||
|
case 'firewall':
|
||||||
|
// Its log outlives a stop, as the engine keeps it.
|
||||||
|
return this.engine.personalFirewall(node) === undefined ? undefined : { log: { all: this.engine.firewallLog(node) } };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,129 @@
|
|||||||
|
// A computer's firewall window in the room, on FILIUS' example
|
||||||
|
// email_komplett with a firewall installed on its web server: its view (the
|
||||||
|
// three boxes, the rules as the table shows them), its actions, its log as
|
||||||
|
// the window's lines (also over a stop), and what a run leaves in the
|
||||||
|
// project. The firewall's steps against Java's window are
|
||||||
|
// packages/core/test/firewallWindow.test.ts.
|
||||||
|
|
||||||
|
import { readFileSync } from 'node:fs';
|
||||||
|
import { dirname, join } from 'node:path';
|
||||||
|
import { fileURLToPath } from 'node:url';
|
||||||
|
import { describe, expect, it } from 'vitest';
|
||||||
|
import * as Y from 'yjs';
|
||||||
|
import { DIRECTLY_CONNECTED_SOURCE_MARKER, firewallRule } from '@mdg-filius/core';
|
||||||
|
import { addFirewallRule, readProject, setSoftware, writeProject } from '@mdg-filius/doc';
|
||||||
|
import { openProjectArchive } from '@mdg-filius/fls';
|
||||||
|
import type { FirewallView, HostMessage } from '@mdg-filius/protocol';
|
||||||
|
import { SimulationRoom, TICK_MILLISECONDS } from '../src/index.js';
|
||||||
|
|
||||||
|
const here = dirname(fileURLToPath(import.meta.url));
|
||||||
|
const corpus = join(here, '../../../corpus/java-examples');
|
||||||
|
|
||||||
|
function setUp(name = 'Web-Server', fromFile = false) {
|
||||||
|
let ids = 0;
|
||||||
|
const doc = new Y.Doc();
|
||||||
|
writeProject(
|
||||||
|
doc,
|
||||||
|
openProjectArchive(new Uint8Array(readFileSync(join(corpus, 'email_komplett.fls'))), () => `00000000-0000-0000-0000-${(++ids).toString(16).padStart(12, '0')}`),
|
||||||
|
);
|
||||||
|
const idOf = (displayName: string) => readProject(doc).graph.nodes.find((node) => node.displayName === displayName)!.id;
|
||||||
|
setSoftware(doc, idOf('Web-Server'), { install: ['firewall'], remove: [] }, 'de', 'me');
|
||||||
|
// A rule as a file may hold it and the window cannot make: TCP, every port, dropped.
|
||||||
|
if (fromFile) addFirewallRule(doc, idOf('Web-Server'), 'me');
|
||||||
|
const id = idOf(name);
|
||||||
|
let now = 0;
|
||||||
|
const room = new SimulationRoom(doc, { now: () => now, seed: () => 5 });
|
||||||
|
let view: FirewallView | null | undefined;
|
||||||
|
const errors: string[] = [];
|
||||||
|
const lines: unknown[] = [];
|
||||||
|
const client = room.connect((message: HostMessage) => {
|
||||||
|
if (message.kind === 'app' && message.app === 'firewall') {
|
||||||
|
lines.length = message.from;
|
||||||
|
lines.push(...message.lines);
|
||||||
|
}
|
||||||
|
if (message.kind === 'view' && message.app === 'firewall') view = message.view as FirewallView | null;
|
||||||
|
if (message.kind === 'error') errors.push(message.code);
|
||||||
|
});
|
||||||
|
const send = (fields: object) => room.receive(client, { v: 1, ...fields });
|
||||||
|
send({ kind: 'sub', topics: [`app:${id}/firewall`] });
|
||||||
|
send({ kind: 'start' });
|
||||||
|
const run = (milliseconds: number) => {
|
||||||
|
for (let elapsed = 0; elapsed < milliseconds; elapsed += TICK_MILLISECONDS) {
|
||||||
|
now += TICK_MILLISECONDS;
|
||||||
|
room.tick();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
run(TICK_MILLISECONDS);
|
||||||
|
const act = (action: string, args: object = {}) => send({ kind: 'input', node: id, app: 'firewall', action, args, reqId: action });
|
||||||
|
return { doc, id, view: () => view, errors, lines, act, run, send, now: () => now };
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("a computer's firewall window in the room", () => {
|
||||||
|
it('shows the boxes and the rules, takes its actions, and sends its log as lines', () => {
|
||||||
|
const { view, act, errors, lines, send, run, now } = setUp();
|
||||||
|
// Active as Java installs it.
|
||||||
|
expect(view()).toEqual({ active: true, icmpAllowed: true, filterUdp: true, rules: [] });
|
||||||
|
run(1000);
|
||||||
|
act('addRule', { port: 80, protocol: 'tcp', sameNetworkOnly: false });
|
||||||
|
// A line carries the time of the simulation it was written at.
|
||||||
|
expect(lines).toEqual([{ kind: 'portRuleAdded', port: 80, protocol: 'tcp', timeMilliseconds: now() }]);
|
||||||
|
act('addRule', { port: 53, protocol: 'all', sameNetworkOnly: true });
|
||||||
|
expect(view()!.rules).toEqual([
|
||||||
|
{ protocol: 'tcp', port: 80, sender: 'all', text: '*/* -> */*; TCP:80 => ACCEPT' },
|
||||||
|
{ protocol: 'all', port: 53, sender: 'sameNetwork', text: '141.99.50.128/255.255.255.128 -> */*; *:53 => ACCEPT' },
|
||||||
|
]);
|
||||||
|
act('setIcmpAllowed', { allowed: false });
|
||||||
|
act('setFilterUdp', { enabled: false });
|
||||||
|
act('setActive', { active: false });
|
||||||
|
expect(view()).toMatchObject({ active: false, icmpAllowed: false, filterUdp: false });
|
||||||
|
act('removeRule', { row: 0 });
|
||||||
|
expect(view()!.rules.map((rule) => rule.port)).toEqual([53]);
|
||||||
|
expect(errors).toEqual([]);
|
||||||
|
expect(lines).toMatchObject([{ kind: 'portRuleAdded', port: 80, protocol: 'tcp' }, { kind: 'portRuleAdded', port: 53, protocol: 'all' }, { kind: 'portRuleRemoved' }]);
|
||||||
|
// The log outlives a stop and a new start, as the engine keeps it.
|
||||||
|
send({ kind: 'stop' });
|
||||||
|
send({ kind: 'start' });
|
||||||
|
act('addRule', { port: 25, protocol: 'tcp', sameNetworkOnly: false });
|
||||||
|
expect(lines.map((line) => (line as { kind: string }).kind)).toEqual(['portRuleAdded', 'portRuleAdded', 'portRuleRemoved', 'portRuleAdded']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shows a rule the window cannot make as what it is', () => {
|
||||||
|
const { view } = setUp('Web-Server', true);
|
||||||
|
expect(view()!.rules).toEqual([{ protocol: 'tcp', port: -1, sender: null, text: '*/* -> */*; TCP:* => DROP' }]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('leaves in the project what the run made of the firewall', () => {
|
||||||
|
const { doc, id, act, send } = setUp();
|
||||||
|
act('addRule', { port: 80, protocol: 'udp', sameNetworkOnly: true });
|
||||||
|
act('setIcmpAllowed', { allowed: false });
|
||||||
|
send({ kind: 'stop' });
|
||||||
|
expect(readProject(doc).firewallConfigurationsByNodeId.get(id)).toMatchObject({
|
||||||
|
isActive: true,
|
||||||
|
dropIcmp: true,
|
||||||
|
filterUdp: true,
|
||||||
|
rules: [firewallRule({ port: 80, protocol: 'udp', action: 'accept', sourceIPAddress: DIRECTLY_CONNECTED_SOURCE_MARKER })],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('refuses what the window never hands over', () => {
|
||||||
|
const { act, errors } = setUp();
|
||||||
|
act('setActive', { active: 'no' });
|
||||||
|
act('setIcmpAllowed', {});
|
||||||
|
act('setFilterUdp', { enabled: 1 });
|
||||||
|
act('addRule', { port: 0, protocol: 'tcp', sameNetworkOnly: false });
|
||||||
|
act('addRule', { port: 65_536, protocol: 'tcp', sameNetworkOnly: false });
|
||||||
|
act('addRule', { port: 80, protocol: 'icmp', sameNetworkOnly: false });
|
||||||
|
act('addRule', { port: 80, protocol: 'tcp' });
|
||||||
|
act('removeRule', { row: 0 });
|
||||||
|
act('removeRule', { row: 'x' });
|
||||||
|
act('restart');
|
||||||
|
expect(errors).toEqual(['badArgs', 'badArgs', 'badArgs', 'badArgs', 'badArgs', 'badArgs', 'badArgs', 'badArgs', 'badArgs', 'unknownAction']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('has no window on a computer without a firewall', () => {
|
||||||
|
const { view, act, errors } = setUp('DNS-Server');
|
||||||
|
expect(view()).toBeNull();
|
||||||
|
act('setActive', { active: false });
|
||||||
|
expect(errors).toEqual(['unknownApp']);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1527,3 +1527,41 @@ Gemessen (03.10.2026):
|
|||||||
* Eine leere Maildomain wird genommen (`. MX 3600 x.`).
|
* Eine leere Maildomain wird genommen (`. MX 3600 x.`).
|
||||||
* „Auswahl entfernen“ entfernt den ersten Eintrag des Namens, bei zwei
|
* „Auswahl entfernen“ entfernt den ersten Eintrag des Namens, bei zwei
|
||||||
gleichen Namen also womöglich den anderen.
|
gleichen Namen also womöglich den anderen.
|
||||||
|
|
||||||
|
### Firewall eines Rechners (F4.6g3)
|
||||||
|
|
||||||
|
`FirewallWindowRun` installiert auf dem Server von `firewall-pc` (zwei Netze
|
||||||
|
hinter einem Router, der Server mit DNS- und Echo-Server, je ein Rechner im
|
||||||
|
eigenen und im fremden Netz) Javas Firewall, öffnet ihr
|
||||||
|
`GUIApplicationFirewallWindow` und benutzt es wie eine Person: die drei
|
||||||
|
Kästchen, Port mit Protokoll- und Absenderliste und „Hinzufügen“ (auch mit
|
||||||
|
dem, was keine Zahl oder kein Port ist), eine Zeile gewählt und „Löschen“.
|
||||||
|
Je Schritt: die Kästchen (und ob sie sich ändern lassen), das Portfeld, die
|
||||||
|
Tabelle, das Protokoll (und ob man hineintippen kann); nach den Schritten,
|
||||||
|
die danach fragen, was vom eigenen und vom fremden Rechner beim Server
|
||||||
|
ankommt: ein Ping, eine TCP-Verbindung zum Echo-Server, eine DNS-Frage
|
||||||
|
(`packages/core/test/firewallWindow.java.json`). Ein Durchlauf dauert etwa
|
||||||
|
sieben Minuten (Javas Zeitüberschreitungen).
|
||||||
|
|
||||||
|
Gemessen (03.10.2026):
|
||||||
|
|
||||||
|
* Frisch installiert ist die Firewall aktiv, ICMP erlaubt, der UDP-Filter
|
||||||
|
an: Ein Ping geht durch, TCP und DNS nicht (Standardaktion verwerfen).
|
||||||
|
* Eine Regel für alle Absender lässt beide Rechner durch, „Absender im
|
||||||
|
gleichen Rechnernetz“ nur den eigenen. Jede greifende Regel steht im
|
||||||
|
Protokoll („Firewall-Regel hat gegriffen: #1 (…) -> akzeptieren“), auch
|
||||||
|
„Portregel wurde hinzugefügt: 55555/TCP“ und „Portregel wurde entfernt.“
|
||||||
|
* Ohne ICMP-Haken wird ein Ping verworfen und protokolliert, ohne
|
||||||
|
UDP-Filter geht DNS ohne Regel durch (ohne Zeile). Ist die Firewall aus,
|
||||||
|
lassen sich die beiden Kästchen nicht ändern, und alles geht durch.
|
||||||
|
* Javas Ungenauigkeiten:
|
||||||
|
* Jede ganze Zahl ist ein Port: −1 ergibt eine Regel für jeden Port
|
||||||
|
beider Protokolle (die Tabelle zeigt keinen Port), 0 und 70000 Regeln,
|
||||||
|
die nie greifen. Eine Zahl nach einem Leerzeichen lehnt Java still ab.
|
||||||
|
* Ins Protokoll kann man tippen.
|
||||||
|
* Den Erklärtext (`firewall_msg2`) baut Javas Fenster, zeigt ihn aber nie.
|
||||||
|
* Fallen der Sonde: Javas `ClientBaustein` hängt nach dem Trennen noch im
|
||||||
|
Empfangen, sein nächstes „Verbinden“ läuft dahinter nie — die Sonde nimmt
|
||||||
|
je Verbindung einen eigenen `TCPSocket`. Javas Echo-Server verträgt keine
|
||||||
|
sofort wieder geschlossene Verbindung (die folgenden laufen in die
|
||||||
|
Zeitüberschreitung) — die Sonde hält sie eine halbe Sekunde.
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ if [ ! -f "$upstream/target/filius.jar" ]; then
|
|||||||
fi
|
fi
|
||||||
classpath="$upstream/target/filius.jar:$upstream/target/lib/*"
|
classpath="$upstream/target/filius.jar:$upstream/target/lib/*"
|
||||||
mkdir -p "$here/.build"
|
mkdir -p "$here/.build"
|
||||||
javac -encoding UTF-8 -d "$here/.build" -cp "$classpath" "$here/src/FlsOracle.java" "$here/src/GetterDefaults.java" "$here/src/IpCheckCases.java" "$here/src/DnsHostsCases.java" "$here/src/EmailFileCases.java" "$here/src/BeansDump.java" "$here/src/Base64Cases.java" "$here/src/ScenarioNetwork.java" "$here/src/DhcpRun.java" "$here/src/TcpRun.java" "$here/src/DnsRun.java" "$here/src/ArpRun.java" "$here/src/RipRun.java" "$here/src/NatRun.java" "$here/src/WlanRun.java" "$here/src/ExampleRun.java" "$here/src/EchoRun.java" "$here/src/WebRun.java" "$here/src/RouterWebRun.java" "$here/src/KonfigCases.java" "$here/src/EmailRun.java" "$here/src/MailCases.java" "$here/src/EmailClientRun.java" "$here/src/TerminalCases.java" "$here/src/TerminalRun.java" "$here/src/FileCases.java" "$here/src/GnutellaRun.java" "$here/src/NewDeviceRun.java" "$here/src/SheetRun.java" "$here/src/TerminalWindowRun.java" "$here/src/InstallRun.java" "$here/src/BrowserWindowRun.java" "$here/src/MailWindowRun.java" "$here/src/SaveAfterRunRun.java" "$here/src/TextEditorWindowRun.java" "$here/src/FileExplorerWindowRun.java" "$here/src/EchoWindowRun.java" "$here/src/DnsWindowRun.java"
|
javac -encoding UTF-8 -d "$here/.build" -cp "$classpath" "$here/src/FlsOracle.java" "$here/src/GetterDefaults.java" "$here/src/IpCheckCases.java" "$here/src/DnsHostsCases.java" "$here/src/EmailFileCases.java" "$here/src/BeansDump.java" "$here/src/Base64Cases.java" "$here/src/ScenarioNetwork.java" "$here/src/DhcpRun.java" "$here/src/TcpRun.java" "$here/src/DnsRun.java" "$here/src/ArpRun.java" "$here/src/RipRun.java" "$here/src/NatRun.java" "$here/src/WlanRun.java" "$here/src/ExampleRun.java" "$here/src/EchoRun.java" "$here/src/WebRun.java" "$here/src/RouterWebRun.java" "$here/src/KonfigCases.java" "$here/src/EmailRun.java" "$here/src/MailCases.java" "$here/src/EmailClientRun.java" "$here/src/TerminalCases.java" "$here/src/TerminalRun.java" "$here/src/FileCases.java" "$here/src/GnutellaRun.java" "$here/src/NewDeviceRun.java" "$here/src/SheetRun.java" "$here/src/TerminalWindowRun.java" "$here/src/InstallRun.java" "$here/src/BrowserWindowRun.java" "$here/src/MailWindowRun.java" "$here/src/SaveAfterRunRun.java" "$here/src/TextEditorWindowRun.java" "$here/src/FileExplorerWindowRun.java" "$here/src/EchoWindowRun.java" "$here/src/DnsWindowRun.java" "$here/src/FirewallWindowRun.java"
|
||||||
# FILIUS writes its settings below $HOME; keep them out of the real one.
|
# FILIUS writes its settings below $HOME; keep them out of the real one.
|
||||||
mkdir -p "$here/.build/home"
|
mkdir -p "$here/.build/home"
|
||||||
# Removed first: a crashed run must not leave the previous result standing.
|
# Removed first: a crashed run must not leave the previous result standing.
|
||||||
@@ -442,6 +442,17 @@ HOME="$here/.build/home" xvfb-run -a java -Duser.language=de -Duser.country=DE -
|
|||||||
"$root/packages/core/test/scenarios/mail-lan.scenario.json" "$output" 2>&1 | grep -v 'logback\|^$\|^\s*at ' || true
|
"$root/packages/core/test/scenarios/mail-lan.scenario.json" "$output" 2>&1 | grep -v 'logback\|^$\|^\s*at ' || true
|
||||||
test -s "$output"
|
test -s "$output"
|
||||||
|
|
||||||
|
# FILIUS' firewall window of a computer used as a person does — the three
|
||||||
|
# boxes, the port field with its lists and "Hinzufügen" (also what is no
|
||||||
|
# number or no port), "Löschen" — and after it a ping, a TCP connection and a
|
||||||
|
# DNS question from the server's network and from behind the router
|
||||||
|
# (FirewallWindowRun): the firewall's window (packages/core/test/firewallWindow.test.ts).
|
||||||
|
output="$root/packages/core/test/firewallWindow.java.json"
|
||||||
|
rm -f "$output"
|
||||||
|
HOME="$here/.build/home" xvfb-run -a java -Duser.language=de -Duser.country=DE -cp "$classpath:$here/.build" FirewallWindowRun \
|
||||||
|
"$root/packages/core/test/scenarios/firewall-pc.scenario.json" "$output" 2>&1 | grep -v 'logback\|^$\|^\s*at ' || true
|
||||||
|
test -s "$output"
|
||||||
|
|
||||||
# FILIUS' own examples as Java FILIUS runs them, opened and started as FILIUS
|
# FILIUS' own examples as Java FILIUS runs them, opened and started as FILIUS
|
||||||
# does (ExampleRun): the acceptance of F2 (packages/fls/test/examplesJava.test.ts)
|
# does (ExampleRun): the acceptance of F2 (packages/fls/test/examplesJava.test.ts)
|
||||||
# and the classics of F3 (web, email, gnutella; packages/fls/test/classicsJava.test.ts).
|
# and the classics of F3 (web, email, gnutella; packages/fls/test/classicsJava.test.ts).
|
||||||
|
|||||||
@@ -0,0 +1,271 @@
|
|||||||
|
// Uses FILIUS' own firewall window of a computer (GUIApplicationFirewallWindow
|
||||||
|
// on a GUIDesktopPanel) on firewall-pc's server as a person does: the three
|
||||||
|
// boxes, the port field with its two lists and "Hinzufügen" (also with what
|
||||||
|
// is no number, and numbers no port is), a row chosen and "Löschen". After
|
||||||
|
// each step it writes what the window shows (the boxes and whether they can
|
||||||
|
// be changed, the port field, the table, the log and whether it can be typed
|
||||||
|
// into) and, where traffic is asked, what reaches the server from a computer
|
||||||
|
// of its network and one behind the router: a ping, a TCP connection to its
|
||||||
|
// echo server (a socket of the client's own), its DNS server asked for a name. The reference for the room's
|
||||||
|
// firewall window (packages/core/test/firewallWindow.test.ts).
|
||||||
|
//
|
||||||
|
// Usage: java -cp filius.jar:lib/*:classes FirewallWindowRun <scenario.json> <output json>
|
||||||
|
|
||||||
|
import filius.gui.anwendungssicht.GUIApplicationFirewallWindow;
|
||||||
|
import filius.gui.anwendungssicht.GUIDesktopPanel;
|
||||||
|
import filius.software.clientserver.ServerBaustein;
|
||||||
|
import filius.software.system.Betriebssystem;
|
||||||
|
import filius.software.system.InternetKnotenBetriebssystem;
|
||||||
|
import filius.software.transportschicht.TCPSocket;
|
||||||
|
import filius.software.vermittlungsschicht.IcmpPaket;
|
||||||
|
|
||||||
|
import java.awt.Component;
|
||||||
|
import java.awt.Container;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
import javax.swing.JButton;
|
||||||
|
import javax.swing.JCheckBox;
|
||||||
|
import javax.swing.JComboBox;
|
||||||
|
import javax.swing.JFrame;
|
||||||
|
import javax.swing.JTable;
|
||||||
|
import javax.swing.JTextArea;
|
||||||
|
import javax.swing.JTextField;
|
||||||
|
import javax.swing.SwingUtilities;
|
||||||
|
import javax.swing.table.TableModel;
|
||||||
|
|
||||||
|
public class FirewallWindowRun {
|
||||||
|
static final String LOCAL = "00000000-0000-0000-0000-000000000201";
|
||||||
|
static final String REMOTE = "00000000-0000-0000-0000-000000000203";
|
||||||
|
static final String SERVER = "00000000-0000-0000-0000-000000000204";
|
||||||
|
static final String SERVER_ADDRESS = "10.0.0.10";
|
||||||
|
static final String SERVER_CLASS = "filius.software.clientserver.ServerBaustein";
|
||||||
|
|
||||||
|
static final List<Object> steps = new ArrayList<>();
|
||||||
|
static GUIApplicationFirewallWindow window;
|
||||||
|
static ScenarioNetwork network;
|
||||||
|
static int sequence = 1;
|
||||||
|
|
||||||
|
static Object field(Object object, String name) throws Exception {
|
||||||
|
Field field = GUIApplicationFirewallWindow.class.getDeclaredField(name);
|
||||||
|
field.setAccessible(true);
|
||||||
|
return field.get(object);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void onEdt(Runnable action) throws Exception {
|
||||||
|
SwingUtilities.invokeLater(action);
|
||||||
|
Thread.sleep(500);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Map<String, Object> box(String name) throws Exception {
|
||||||
|
JCheckBox box = (JCheckBox) field(window, name);
|
||||||
|
Map<String, Object> shown = new LinkedHashMap<>();
|
||||||
|
shown.put("selected", box.isSelected());
|
||||||
|
shown.put("enabled", box.isEnabled());
|
||||||
|
return shown;
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<List<String>> rows(JTable table) {
|
||||||
|
List<List<String>> rows = new ArrayList<>();
|
||||||
|
TableModel model = table.getModel();
|
||||||
|
for (int row = 0; row < model.getRowCount(); row++) {
|
||||||
|
List<String> cells = new ArrayList<>();
|
||||||
|
for (int column = 0; column < model.getColumnCount(); column++) cells.add(String.valueOf(model.getValueAt(row, column)));
|
||||||
|
rows.add(cells);
|
||||||
|
}
|
||||||
|
return rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
static InternetKnotenBetriebssystem system(String id) {
|
||||||
|
return (InternetKnotenBetriebssystem) network.nodes.get(id).getSystemSoftware();
|
||||||
|
}
|
||||||
|
|
||||||
|
static String ping(String from) {
|
||||||
|
IcmpPaket answer = system(from).holeICMP().sendProbe(SERVER_ADDRESS, 64, 1, sequence++);
|
||||||
|
return answer == null ? "null" : "ICMP " + answer.getIcmpType() + "/" + answer.getIcmpCode() + " from " + answer.getSender();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** A TCP connection to the server's echo server, as its client opens one (a socket of its own, closed again): "connected" or Java's exception. */
|
||||||
|
static String connect(String from) throws Exception {
|
||||||
|
AtomicReference<String> result = new AtomicReference<>("(no answer)");
|
||||||
|
Thread thread = new Thread(() -> {
|
||||||
|
try {
|
||||||
|
TCPSocket socket = new TCPSocket(system(from), SERVER_ADDRESS, 55555);
|
||||||
|
socket.verbinden();
|
||||||
|
result.set("connected");
|
||||||
|
// Held a moment, as a person's client would: Java's echo server takes no connection closed at once (the next ones then time out).
|
||||||
|
Thread.sleep(500);
|
||||||
|
socket.schliessen();
|
||||||
|
} catch (Exception error) {
|
||||||
|
result.set(error.getClass().getSimpleName() + ": " + error.getMessage());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
thread.start();
|
||||||
|
thread.join(60000);
|
||||||
|
Thread.sleep(1000);
|
||||||
|
return result.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
static String resolve(String from) throws Exception {
|
||||||
|
AtomicReference<String> result = new AtomicReference<>("(no answer)");
|
||||||
|
Thread thread = new Thread(() -> {
|
||||||
|
try {
|
||||||
|
result.set(String.valueOf(system(from).holeDNSClient().holeIPAdresse("www.filius.de")));
|
||||||
|
} catch (Exception error) {
|
||||||
|
result.set(error.getClass().getSimpleName());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
thread.start();
|
||||||
|
thread.join(30000);
|
||||||
|
return result.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void step(String name, boolean traffic) throws Exception {
|
||||||
|
Map<String, Object> result = new LinkedHashMap<>();
|
||||||
|
result.put("step", name);
|
||||||
|
if (traffic) {
|
||||||
|
Map<String, Object> reached = new LinkedHashMap<>();
|
||||||
|
for (String[] each : new String[][] { { "local", LOCAL }, { "remote", REMOTE } }) {
|
||||||
|
Map<String, Object> from = new LinkedHashMap<>();
|
||||||
|
from.put("ping", ping(each[1]));
|
||||||
|
from.put("tcp", connect(each[1]));
|
||||||
|
from.put("dns", resolve(each[1]));
|
||||||
|
reached.put(each[0], from);
|
||||||
|
}
|
||||||
|
result.put("traffic", reached);
|
||||||
|
}
|
||||||
|
result.put("active", box("cbEinAus"));
|
||||||
|
result.put("icmp", box("cbIcmp"));
|
||||||
|
result.put("udp", box("cbUdp"));
|
||||||
|
result.put("port", ((JTextField) field(window, "tfPort")).getText());
|
||||||
|
result.put("protocol", String.valueOf(((JComboBox<?>) field(window, "cbProtocol")).getSelectedItem()));
|
||||||
|
result.put("sender", String.valueOf(((JComboBox<?>) field(window, "cbAlleAbsender")).getSelectedItem()));
|
||||||
|
result.put("rows", rows((JTable) field(window, "tTabellePort")));
|
||||||
|
JTextArea log = (JTextArea) field(window, "log");
|
||||||
|
result.put("log", log.getText());
|
||||||
|
result.put("logEditable", log.isEditable());
|
||||||
|
steps.add(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void press(String text) throws Exception {
|
||||||
|
List<JButton> found = new ArrayList<>();
|
||||||
|
collect(window, text, found);
|
||||||
|
JButton button = found.get(0);
|
||||||
|
onEdt(button::doClick);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void collect(Container container, String text, List<JButton> found) {
|
||||||
|
for (Component component : container.getComponents()) {
|
||||||
|
if (component instanceof JButton && text.equals(((JButton) component).getText())) found.add((JButton) component);
|
||||||
|
if (component instanceof Container) collect((Container) component, text, found);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void click(String box) throws Exception {
|
||||||
|
JCheckBox each = (JCheckBox) field(window, box);
|
||||||
|
onEdt(each::doClick);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The port typed, the protocol (0 TCP, 1 UDP, 2 *) and the sender (0 all, 1 the same network) chosen, "Hinzufügen". */
|
||||||
|
static void add(String port, int protocol, int sender) throws Exception {
|
||||||
|
JTextField field = (JTextField) field(window, "tfPort");
|
||||||
|
JComboBox<?> protocols = (JComboBox<?>) field(window, "cbProtocol");
|
||||||
|
JComboBox<?> senders = (JComboBox<?>) field(window, "cbAlleAbsender");
|
||||||
|
onEdt(() -> {
|
||||||
|
field.setText(port);
|
||||||
|
protocols.setSelectedIndex(protocol);
|
||||||
|
senders.setSelectedIndex(sender);
|
||||||
|
});
|
||||||
|
press("Hinzufügen");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void choose(int row) throws Exception {
|
||||||
|
JTable table = (JTable) field(window, "tTabellePort");
|
||||||
|
onEdt(() -> table.setRowSelectionInterval(row, row));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) throws Exception {
|
||||||
|
try {
|
||||||
|
run(args);
|
||||||
|
} catch (Throwable error) {
|
||||||
|
error.printStackTrace();
|
||||||
|
System.exit(1);
|
||||||
|
}
|
||||||
|
System.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void run(String[] args) throws Exception {
|
||||||
|
network = ScenarioNetwork.read(args[0]);
|
||||||
|
Betriebssystem server = (Betriebssystem) network.nodes.get(SERVER).getSystemSoftware();
|
||||||
|
server.installApp("filius.software.firewall.Firewall", false);
|
||||||
|
server.installApp(SERVER_CLASS, false);
|
||||||
|
ServerBaustein echo = (ServerBaustein) server.holeSoftware(SERVER_CLASS);
|
||||||
|
echo.setPort(55555);
|
||||||
|
echo.setAktiv(true);
|
||||||
|
network.start();
|
||||||
|
Thread.sleep(500);
|
||||||
|
|
||||||
|
JFrame frame = new JFrame();
|
||||||
|
GUIDesktopPanel desktop = new GUIDesktopPanel(server);
|
||||||
|
frame.getContentPane().add(desktop);
|
||||||
|
window = (GUIApplicationFirewallWindow) desktop.starteAnwendung("filius.software.firewall.Firewall");
|
||||||
|
SwingUtilities.invokeAndWait(() -> {
|
||||||
|
frame.pack();
|
||||||
|
frame.setVisible(true);
|
||||||
|
});
|
||||||
|
Thread.sleep(500);
|
||||||
|
step("open", true);
|
||||||
|
|
||||||
|
// What Java's field does not take: nothing happens.
|
||||||
|
add("abc", 0, 0);
|
||||||
|
step("add port abc", false);
|
||||||
|
add("", 0, 0);
|
||||||
|
step("add an empty port", false);
|
||||||
|
|
||||||
|
add("55555", 0, 0);
|
||||||
|
step("add 55555 TCP for all senders", true);
|
||||||
|
add("53", 1, 1);
|
||||||
|
step("add 53 UDP for the same network", true);
|
||||||
|
|
||||||
|
click("cbIcmp");
|
||||||
|
step("ICMP no longer allowed", true);
|
||||||
|
click("cbUdp");
|
||||||
|
step("UDP filter off", true);
|
||||||
|
|
||||||
|
// Numbers that are no port.
|
||||||
|
add("-1", 2, 0);
|
||||||
|
step("add -1 for all protocols", false);
|
||||||
|
add("0", 0, 0);
|
||||||
|
step("add 0 TCP", false);
|
||||||
|
add("70000", 0, 1);
|
||||||
|
step("add 70000 TCP for the same network", false);
|
||||||
|
add(" 80", 0, 0);
|
||||||
|
step("add a port after a space", false);
|
||||||
|
|
||||||
|
// "Löschen" with no row chosen, then the first row.
|
||||||
|
press("Löschen");
|
||||||
|
step("delete with no row chosen", false);
|
||||||
|
choose(0);
|
||||||
|
press("Löschen");
|
||||||
|
step("delete row 0", true);
|
||||||
|
|
||||||
|
// Off: the boxes cannot be changed; on again.
|
||||||
|
click("cbEinAus");
|
||||||
|
step("firewall off", true);
|
||||||
|
click("cbEinAus");
|
||||||
|
step("firewall on again", false);
|
||||||
|
|
||||||
|
Map<String, Object> output = new LinkedHashMap<>();
|
||||||
|
output.put("steps", steps);
|
||||||
|
try (FileOutputStream out = new FileOutputStream(args[1])) {
|
||||||
|
out.write((FlsOracle.Json.write(output, "") + "\n").getBytes(StandardCharsets.UTF_8));
|
||||||
|
}
|
||||||
|
network.stop();
|
||||||
|
System.out.println("firewall window steps: " + steps.size());
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user