// The pages of FILIUS' routers and home routers (filius.software.system: // VermittlungsrechnerBetriebssystem, GatewayFirmware; the web server's // plug-ins FirewallWebLog and VermittlungWeb), written as Java writes them: // from its templates (tmpl/vermittlung_index, gateway_index, routing) and the // texts of its message bundle, in the project's language. The pages go over // the simulated network as they are, so they are written here, not by the // editor. // // Departure from Java: the German log page closes its title with // (Java's bundle writes ). import type { FirewallRule } from '../config.js'; import type { FirewallLogEntry } from '../engine.js'; import { formatIPv4, maskAddress, parseIPv4 } from '../ipv4.js'; import type { RouteRow } from '../routeTable.js'; import { DIRECTLY_CONNECTED_SOURCE_MARKER } from '../config.js'; /** The languages FILIUS' pages come in (Java also has French). */ export type PageLanguage = 'de' | 'en'; /** Java's templates as its WebServer and WebServerPlugIn read them: each line ended by "\n". */ const TEMPLATES: Record<'routerIndex' | 'gatewayIndex' | 'routing', Record> = { routerIndex: { de: "\n \n Administration des Vermittlungsrechners\n \n \n\n

Firewall-Administration

\n

Konfiguration

\n

Log-Einträge anzeigen

\n\n

Weiterleitung/Routing

\n

Anzeige des aktuellen Status der Weiterleitungs-/Routing-Tabelle

\n \n\n", en: "\n \n Router Administration\n \n \n

Firewall Administration

\n

Configuration

\n

Show Log

\n\n

Routing/Forwarding

\n

Show current routing/forwarding table

\n \n \n\n", }, gatewayIndex: { de: "\n \n Administration des Gateways\n \n \n

Firewall-Administration

\n

Konfiguration

\n

Log-Einträge anzeigen

\n \n\n", en: "\n \n Gateway Administration\n \n \n

Firewall Administration

\n

Configuration

\n

Show Log

\n \n\n", }, routing: { de: "\n \n \n:title:\n \n \n \n

\n:title:\n

\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n:routing_entries:\n\n
RechnernetzHopsGültigkeitNächster Hop
AdresseNetzmaske(sec)ZielÖffentliche Adresse
\n

\n:hint:\n

\n \n\n", en: "\n \n \n:title:\n \n \n \n

\n:title:\n

\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n:routing_entries:\n\n
NetworkHopsValidityNext Hop
AddressNetmask(sec)TargetPublic Address
\n

\n:hint:\n

\n \n\n", }, }; /** The texts of Java's message bundle the pages use. */ interface PageTexts { readonly logHead: string; readonly logBody: string; readonly ruleMatched: string; readonly icmpDropped: string; readonly accept: string; readonly drop: string; readonly ripHint: string; readonly unlimited: string; readonly forwardingHint: string; readonly forwardingTitle: string; readonly ripTitle: string; } const TEXTS: Record = { de: { /** sw_firewallweblog_msg1, msg2 (, where Java writes ). */ logHead: '\n\t\n\t\tLog-Einträge der Firewall', logBody: '\n\t\n\t\n\t\t

Log-Einträge der Firewall

', /** sw_firewall_msg8, firewallthread_msg1, jfirewalldialog_msg33, msg34. */ ruleMatched: 'Firewall-Regel hat gegriffen:', icmpDropped: 'ICMP-Paket verworfen:', accept: 'akzeptieren', drop: 'verwerfen', /** sw_vermittlungweb_msg1, msg3, msg4, msg5, msg6. */ ripHint: 'Diese Tabelle wird durch das Routing Information Protocol (RIP) aufgebaut und aktualisiert.', unlimited: 'unbegrenzt', forwardingHint: 'Diese Tabelle wurde aus der IP-Konfiguration des Vermittlungsrechners und manuellen Einträgen erstellt.', forwardingTitle: 'Routing-/Weiterleitungs-Tabelle', ripTitle: 'Routing-Tabelle', }, en: { logHead: '\n\t\n\t\tLog-data set entry of the firewall', logBody: '\n\t\n\t\n\t\t

Log-data set entry of the firewall

', ruleMatched: 'Firewall rule matched:', icmpDropped: 'Dropped ICMP packet:', accept: 'accept', drop: 'drop', ripHint: 'This table is generated and updated by the Routing Information Protocol (RIP).', unlimited: 'unlimited', forwardingHint: 'This table was created from the IP configuration of the router and manually added entries of the forwarding table.', forwardingTitle: 'Routing/Forwarding Table', ripTitle: 'Routing Table', }, }; /** The index page a router or home router keeps in /webserver (WebServer.erzeugeIndexDatei). */ export function routerIndexPage(kind: 'router' | 'gateway', language: PageLanguage): string { return TEMPLATES[kind === 'router' ? 'routerIndex' : 'gatewayIndex'][language]; } /** FirewallRule.toString(nics): the rule as the log names it. */ export function firewallRuleText(rule: FirewallRule, firstInterface: { readonly ipAddress: string; readonly subnetMask: string } | undefined): string { const sameNetwork = rule.sourceIPAddress === DIRECTLY_CONNECTED_SOURCE_MARKER && firstInterface !== undefined; let text = ''; if (rule.sourceIPAddress === '') { text += '*/'; } else if (sameNetwork) { const address = parseIPv4(firstInterface.ipAddress); const mask = parseIPv4(firstInterface.subnetMask); const network = address === undefined || mask === undefined ? firstInterface.ipAddress : formatIPv4(maskAddress(address, mask)); text += `${network}/${firstInterface.subnetMask} -> `; } else { text += `${rule.sourceIPAddress}/`; } if (!sameNetwork) text += rule.sourceSubnetMask === '' ? '* -> ' : `${rule.sourceSubnetMask} -> `; text += rule.destinationIPAddress === '' ? '*/' : `${rule.destinationIPAddress}/`; text += rule.destinationSubnetMask === '' ? '*; ' : `${rule.destinationSubnetMask}; `; text += rule.protocol === 'all' ? '*:' : rule.protocol === 'tcp' ? 'TCP:' : rule.protocol === 'udp' ? 'UDP:' : '?:'; text += rule.port >= 0 ? `${rule.port} => ` : '* => '; return text + (rule.action === 'accept' ? 'ACCEPT' : 'DROP'); } /** A line of the firewall's log, as Firewall tells its observers. */ 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})`; } return `${texts.ruleMatched} #${entry.ruleNumber} (${firewallRuleText(entry.rule, entry.firstInterface)}) -> ${entry.accepted ? texts.accept : texts.drop}`; } /** FirewallWebLog.holeHtmlSeite: the log since the engine was made. */ export function firewallLogPage(entries: readonly FirewallLogEntry[], language: PageLanguage): string { const texts = TEXTS[language]; const lines = entries.map((entry) => `
\n${firewallLogText(entry, language)}`).join(''); return `${texts.logHead}${texts.logBody}\n\n${lines}\n\n\t\n`; } function routingPage(title: string, entries: string, hint: string, language: PageLanguage): string { return TEMPLATES.routing[language].replaceAll(':title:', title).replaceAll(':routing_entries:', entries).replaceAll(':hint:', hint); } /** VermittlungWeb.createForwardingTablePage: a router without RIP. */ export function forwardingTablePage(rows: readonly RouteRow[], language: PageLanguage): string { const texts = TEXTS[language]; const entries = rows .map((row) => { const direct = row.nextHop === row.interfaceIPAddress; const localhost = row.interfaceIPAddress === '127.0.0.1'; const cells = `${row.destinationNetwork}${row.subnetMask}` + (direct || localhost ? ' 0 ' : ' ? ') + `${texts.unlimited}` + `${row.nextHop} - `; if (direct || localhost) return `${cells}`; if (row.subnetMask === '0.0.0.0') return `${cells}`; return `${cells}`; }) .join(''); return routingPage(texts.forwardingTitle, entries, texts.forwardingHint, language); } /** VermittlungWeb.createRoutingTablePage: a router with RIP; how long each learned route has left, in whole seconds. */ export function ripTablePage(rows: readonly RouteRow[], nowMilliseconds: number, language: PageLanguage): string { const texts = TEXTS[language]; const entries = rows .map((row) => { const hops = row.metric ?? 16; const validity = row.expiresAtMilliseconds === undefined ? texts.unlimited : String(Math.trunc((row.expiresAtMilliseconds - nowMilliseconds) / 1_000)); const publicAddress = row.hopPublicIPAddress ?? ''; const cells = `${row.destinationNetwork}${row.subnetMask}${hops}${validity}${row.nextHop}` + `${publicAddress}`; if (hops === 0) return `${cells}`; if (hops === 16) return `${cells}`; return `${cells}`; }) .join(''); return routingPage(texts.ripTitle, entries, texts.ripHint, language); }