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:
@@ -26,6 +26,7 @@ export * from './runtime/apps/dnsServer.js';
|
||||
export * from './runtime/apps/web.js';
|
||||
export * from './runtime/apps/routerPages.js';
|
||||
export * from './runtime/apps/firewallSettings.js';
|
||||
export * from './runtime/apps/personalFirewall.js';
|
||||
export * from './runtime/apps/emailServer.js';
|
||||
export * from './runtime/apps/emailClient.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 { FirewallLogEntry } from '../engine.js';
|
||||
import type { PortRuleProtocol } from './personalFirewall.js';
|
||||
import { formatIPv4, maskAddress, parseIPv4 } from '../ipv4.js';
|
||||
import type { RouteRow } from '../routeTable.js';
|
||||
import { DIRECTLY_CONNECTED_SOURCE_MARKER } from '../config.js';
|
||||
@@ -40,6 +41,8 @@ interface PageTexts {
|
||||
readonly logBody: string;
|
||||
readonly ruleMatched: string;
|
||||
readonly icmpDropped: string;
|
||||
readonly portRuleAdded: string;
|
||||
readonly portRuleRemoved: string;
|
||||
readonly accept: string;
|
||||
readonly drop: string;
|
||||
readonly ripHint: string;
|
||||
@@ -57,6 +60,9 @@ const TEXTS: Record<PageLanguage, PageTexts> = {
|
||||
/** sw_firewall_msg8, firewallthread_msg1, jfirewalldialog_msg33, msg34. */
|
||||
ruleMatched: 'Firewall-Regel hat gegriffen:',
|
||||
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',
|
||||
drop: 'verwerfen',
|
||||
/** 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>',
|
||||
ruleMatched: 'Firewall rule matched:',
|
||||
icmpDropped: 'Dropped ICMP packet:',
|
||||
portRuleAdded: 'Port rule is added:',
|
||||
portRuleRemoved: 'Delete port rule.',
|
||||
accept: 'accept',
|
||||
drop: 'drop',
|
||||
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');
|
||||
}
|
||||
|
||||
/** 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 {
|
||||
const texts = TEXTS[language];
|
||||
if (entry.kind === 'icmpDropped') {
|
||||
return `${texts.icmpDropped} ${entry.senderIPAddress} -> ${entry.receiverIPAddress} (code: ${entry.code}, type: ${entry.type})`;
|
||||
switch (entry.kind) {
|
||||
case 'icmpDropped':
|
||||
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)}`;
|
||||
}
|
||||
|
||||
@@ -124,6 +124,8 @@ import { EmailServer } from './apps/emailServer.js';
|
||||
import { Terminal } from './apps/terminal.js';
|
||||
import { firewallLogPage, forwardingTablePage, ripTablePage, routerIndexPage } from './apps/routerPages.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 { clientAccountFileText, EMAIL_CLIENT_ACCOUNT_FILE, EMAIL_SERVER_ACCOUNTS_FILE, serverAccountsFileText } from './emailAccountsFile.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
|
||||
* the router's page "log"): a rule that matched a TCP or UDP packet, with
|
||||
* the node's first interface (the rule's text names its network for "any
|
||||
* directly connected network"), or an ICMP packet dropped by the general
|
||||
* setting.
|
||||
* the router's page "log" and a computer's firewall window): a rule that
|
||||
* matched a TCP or UDP packet, with the node's first interface (the rule's
|
||||
* text names its network for "any directly connected network"), or an ICMP
|
||||
* 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 =
|
||||
| {
|
||||
@@ -333,7 +336,9 @@ export type FirewallLogEntry =
|
||||
readonly firstInterface: { readonly ipAddress: string; readonly subnetMask: string } | undefined;
|
||||
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';
|
||||
|
||||
@@ -945,7 +950,7 @@ export class NetworkRuntimeEngine {
|
||||
/** The time of day a program writes into a mail (Java's new Date()). */
|
||||
private readonly wallClock: () => number;
|
||||
/** 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). */
|
||||
private readonly handedOutTcpSocketIds = new Set<Id>();
|
||||
private nextApplicationWakeId = 1;
|
||||
@@ -1127,12 +1132,12 @@ export class NetworkRuntimeEngine {
|
||||
}
|
||||
|
||||
/** 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) ?? [];
|
||||
}
|
||||
|
||||
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. */
|
||||
@@ -5615,6 +5620,31 @@ export class NetworkRuntimeEngine {
|
||||
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 {
|
||||
const configurations = new Map(this.stateValue.topologySnapshot.firewallConfigurationsByNodeId);
|
||||
configurations.set(nodeId, configuration);
|
||||
|
||||
Reference in New Issue
Block a user