feat(fls): system software settings as Java loads them
readSystemSoftware adds to each node what the topology needs from its system software: a switch's SSID and learning time, a host's wireless SSID, a router's RIP switch, the manual routes and the firewall of a router or home router. All 23 corpus files match Java. - A router's firewall is one object its constructor hangs in two places; the corpus writes it under the web server's "konfig" plug-in. The places are merged. - The decoder keeps entries a constructor made in a map, reached by get(key) (installed applications), like the elements of a list. - The Java oracle loads every file twice and writes values that differ as null: what FILIUS rolls (MACs, a switch's SSID) without text search. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,198 @@
|
||||
// The settings of a node's system software that the topology needs, read as
|
||||
// Java FILIUS holds them after loading: a switch's SSID and learning time, a
|
||||
// host's wireless SSID, a router's RIP switch, the manual routes, and the
|
||||
// firewall of a router or home router.
|
||||
// Java counterparts: filius.software.system.SwitchFirmware, Betriebssystem,
|
||||
// VermittlungsrechnerBetriebssystem, GatewayFirmware, InternetKnotenBetriebssystem,
|
||||
// filius.software.vermittlungsschicht.Weiterleitungstabelle,
|
||||
// filius.software.firewall.Firewall and FirewallRule.
|
||||
//
|
||||
// Checked against Java (corpus.java.json, `system`). DHCP, NAT, the modem,
|
||||
// file systems and applications are later layers.
|
||||
//
|
||||
// A router's firewall is one object its constructor makes and hangs in two
|
||||
// places: the installed applications and the configuration plug-ins of its
|
||||
// web server. XMLEncoder writes its settings wherever it meets it first —
|
||||
// in the corpus, under the web server's `konfig` plug-in. The decoder does
|
||||
// not know Java's constructors and sees a placeholder per path; they are
|
||||
// merged here.
|
||||
|
||||
import { isJavaObject, isUnresolved, listItems, mapEntries, type JavaObject, type JavaValue } from './javabeans.js';
|
||||
import type { FlsNodeClass } from './topology.js';
|
||||
|
||||
export interface FlsFirewallRule {
|
||||
srcIP: string | null;
|
||||
srcMask: string | null;
|
||||
destIP: string | null;
|
||||
destMask: string | null;
|
||||
/** A port, or -1 for all. */
|
||||
port: number;
|
||||
/** -1 all, 1 ICMP, 6 TCP, 17 UDP. */
|
||||
protocol: number;
|
||||
/** 0 drop, 1 accept. */
|
||||
action: number;
|
||||
}
|
||||
|
||||
export interface FlsFirewall {
|
||||
activated: boolean;
|
||||
/** 0 drop, 1 accept. */
|
||||
defaultPolicy: number;
|
||||
dropICMP: boolean;
|
||||
filterSYNSegmentsOnly: boolean;
|
||||
filterUdp: boolean;
|
||||
rules: FlsFirewallRule[];
|
||||
}
|
||||
|
||||
export interface FlsSystemSoftware {
|
||||
/**
|
||||
* Switch: its SSID, undefined when the file has none (Java rolls one).
|
||||
* Rechner, Notebook: the wireless network it joins, null for none.
|
||||
*/
|
||||
ssid?: string | null | undefined;
|
||||
/** Switch: how long it keeps a learned MAC address, in milliseconds. */
|
||||
retentionTime?: number;
|
||||
/** Vermittlungsrechner. */
|
||||
ripEnabled?: boolean;
|
||||
/** Every node with an IP stack: rows of destination, mask, gateway, interface address — as the file has them. */
|
||||
manualRoutes?: (string | null)[][];
|
||||
/** Vermittlungsrechner, Gateway. */
|
||||
firewall?: FlsFirewall;
|
||||
}
|
||||
|
||||
type Warn = (message: string) => void;
|
||||
|
||||
const FIREWALL_CLASS = 'filius.software.firewall.Firewall';
|
||||
const WEB_SERVER_CLASS = 'filius.software.www.WebServer';
|
||||
|
||||
export function readSystemSoftware(device: JavaObject, javaClass: FlsNodeClass, warn: Warn): FlsSystemSoftware {
|
||||
const system = device.properties.get('systemSoftware');
|
||||
const software = isJavaObject(system) ? system : undefined;
|
||||
const value = (property: string) => software?.properties.get(property);
|
||||
const result: FlsSystemSoftware = {};
|
||||
|
||||
switch (javaClass) {
|
||||
case 'Switch':
|
||||
result.ssid = optionalString(value('SSID'), 'SSID', warn);
|
||||
result.retentionTime = integer(value('retentionTime'), 300_000, 'retentionTime', warn);
|
||||
break;
|
||||
case 'Rechner':
|
||||
case 'Notebook':
|
||||
result.ssid = optionalString(value('ssid'), 'ssid', warn) ?? null;
|
||||
break;
|
||||
case 'Vermittlungsrechner':
|
||||
result.ripEnabled = boolean(value('ripEnabled'), false, 'ripEnabled', warn);
|
||||
break;
|
||||
case 'Gateway':
|
||||
case 'Modem':
|
||||
break;
|
||||
}
|
||||
if (javaClass !== 'Switch' && javaClass !== 'Modem') {
|
||||
result.manualRoutes = manualRoutes(value('weiterleitungstabelle'), warn);
|
||||
}
|
||||
if (javaClass === 'Vermittlungsrechner' || javaClass === 'Gateway') {
|
||||
result.firewall = firewall(software, warn);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function manualRoutes(table: JavaValue | undefined, warn: Warn): (string | null)[][] {
|
||||
if (table === undefined || table === null) return [];
|
||||
if (!isJavaObject(table)) {
|
||||
warn(`weiterleitungstabelle is ${describe(table)}, ignored`);
|
||||
return [];
|
||||
}
|
||||
return listItems(table.properties.get('manuelleTabelle')).map((row) =>
|
||||
listItems(row).map((cell) => (typeof cell === 'string' || cell === null ? cell : null)),
|
||||
);
|
||||
}
|
||||
|
||||
/** Every object the file reaches the router's one firewall by. */
|
||||
function firewallObjects(software: JavaObject | undefined): JavaObject[] {
|
||||
const found: JavaObject[] = [];
|
||||
const applications = new Map(mapEntries(software?.properties.get('installierteAnwendungen')));
|
||||
const installed = applications.get(FIREWALL_CLASS);
|
||||
if (isJavaObject(installed)) found.push(installed);
|
||||
const webServer = applications.get(WEB_SERVER_CLASS);
|
||||
if (isJavaObject(webServer)) {
|
||||
for (const [, plugin] of mapEntries(webServer.properties.get('plugins'))) {
|
||||
const pluginFirewall = isJavaObject(plugin) ? plugin.properties.get('firewall') : undefined;
|
||||
if (isJavaObject(pluginFirewall) && !found.includes(pluginFirewall)) found.push(pluginFirewall);
|
||||
}
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
/** The firewall as the router's constructor leaves it (off, drop), then the file's settings. */
|
||||
function firewall(software: JavaObject | undefined, warn: Warn): FlsFirewall {
|
||||
const properties = new Map<string, JavaValue>();
|
||||
for (const object of firewallObjects(software)) {
|
||||
for (const [property, value] of object.properties) {
|
||||
if (properties.has(property) && properties.get(property) !== value) {
|
||||
warn(`firewall: ${property} is set in two places, the later one is used`);
|
||||
}
|
||||
properties.set(property, value);
|
||||
}
|
||||
}
|
||||
const at = (property: string) => properties.get(property);
|
||||
// setAllowRelatedPackets is the old name of setFilterSYNSegmentsOnly.
|
||||
const filterSyn = at('filterSYNSegmentsOnly') ?? at('allowRelatedPackets');
|
||||
return {
|
||||
activated: boolean(at('activated'), false, 'firewall activated', warn),
|
||||
defaultPolicy: integer(at('defaultPolicy'), 0, 'firewall defaultPolicy', warn),
|
||||
dropICMP: boolean(at('dropICMP'), false, 'firewall dropICMP', warn),
|
||||
filterSYNSegmentsOnly: boolean(filterSyn, true, 'firewall filterSYNSegmentsOnly', warn),
|
||||
filterUdp: boolean(at('filterUdp'), true, 'firewall filterUdp', warn),
|
||||
rules: listItems(at('ruleset')).flatMap((rule) => (isJavaObject(rule) ? [firewallRule(rule, warn)] : [])),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* FirewallRule: public fields set one by one (`getField(name).set`), or the
|
||||
* seven-argument constructor. Defaults: all addresses empty, all ports, TCP,
|
||||
* drop.
|
||||
*/
|
||||
function firewallRule(rule: JavaObject, warn: Warn): FlsFirewallRule {
|
||||
const [srcIP, srcMask, destIP, destMask, port, protocol, action] = rule.args.length === 7 ? rule.args : [];
|
||||
const field = (name: string, fromConstructor: JavaValue | undefined) => rule.properties.get(name) ?? fromConstructor;
|
||||
const text = (name: string, fromConstructor: JavaValue | undefined) => {
|
||||
const value = field(name, fromConstructor) ?? '';
|
||||
if (typeof value === 'string' || value === null) return value;
|
||||
warn(`firewall rule: ${name} is ${describe(value)}, used ""`);
|
||||
return '';
|
||||
};
|
||||
return {
|
||||
srcIP: text('srcIP', srcIP),
|
||||
srcMask: text('srcMask', srcMask),
|
||||
destIP: text('destIP', destIP),
|
||||
destMask: text('destMask', destMask),
|
||||
port: integer(field('port', port), -1, 'firewall rule port', warn),
|
||||
protocol: integer(field('protocol', protocol), 6, 'firewall rule protocol', warn),
|
||||
action: integer(field('action', action), 0, 'firewall rule action', warn),
|
||||
};
|
||||
}
|
||||
|
||||
function optionalString(value: JavaValue | undefined, name: string, warn: Warn): string | null | undefined {
|
||||
if (value === undefined || value === null || typeof value === 'string') return value;
|
||||
warn(`${name} is ${describe(value)}, ignored`);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function boolean(value: JavaValue | undefined, fallback: boolean, name: string, warn: Warn): boolean {
|
||||
if (value === undefined) return fallback;
|
||||
if (typeof value === 'boolean') return value;
|
||||
warn(`${name} is ${describe(value)}, used ${fallback}`);
|
||||
return fallback;
|
||||
}
|
||||
|
||||
function integer(value: JavaValue | undefined, fallback: number, name: string, warn: Warn): number {
|
||||
if (value === undefined) return fallback;
|
||||
if (typeof value === 'number' && Number.isInteger(value)) return value;
|
||||
warn(`${name} is ${describe(value)}, used ${fallback}`);
|
||||
return fallback;
|
||||
}
|
||||
|
||||
function describe(value: JavaValue): string {
|
||||
if (isUnresolved(value)) return `unresolved (${value.reason})`;
|
||||
if (isJavaObject(value)) return `an object of ${value.className}`;
|
||||
return typeof value === 'bigint' ? `${value}` : JSON.stringify(value);
|
||||
}
|
||||
Reference in New Issue
Block a user