feat(core): event queue, Ethernet, switch, ARP, IPv4, ICMP, routing and firewall
First slice of the TypeScript port of the filius-ipad runtime engine, with 29 tests ported from the Swift suite. Parts not ported yet throw NotPortedError instead of guessing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,150 @@
|
||||
// Ported from filius-ipad (GPLv2/GPLv3):
|
||||
// State/TopologyEditorState.swift (runtime configuration value types)
|
||||
|
||||
import type { Id } from '../ids.js';
|
||||
import { maskAddress, parseIPv4 } from './ipv4.js';
|
||||
|
||||
/** The IP settings of a PC or notebook (its single interface) — and a router's gateway. */
|
||||
export interface DeviceConfiguration {
|
||||
readonly ipAddress: string;
|
||||
readonly subnetMask: string;
|
||||
readonly defaultGateway: string;
|
||||
readonly dnsServer: string;
|
||||
}
|
||||
|
||||
export function deviceConfiguration(
|
||||
ipAddress: string,
|
||||
subnetMask: string,
|
||||
defaultGateway = '',
|
||||
dnsServer = '',
|
||||
): DeviceConfiguration {
|
||||
return { ipAddress, subnetMask, defaultGateway, dnsServer };
|
||||
}
|
||||
|
||||
/** One interface of a router or gateway. */
|
||||
export interface InterfaceConfiguration {
|
||||
readonly ipAddress: string;
|
||||
readonly subnetMask: string;
|
||||
}
|
||||
|
||||
/** Map key for an interface: node and port. */
|
||||
export type InterfaceKey = string;
|
||||
|
||||
export function interfaceKey(nodeId: Id, portId: Id): InterfaceKey {
|
||||
return `${nodeId}/${portId}`;
|
||||
}
|
||||
|
||||
/** A row of the manual routing table (Java `Weiterleitungstabelle`). */
|
||||
export interface ManualRoute {
|
||||
readonly destinationNetwork: string;
|
||||
readonly subnetMask: string;
|
||||
readonly gateway: string;
|
||||
readonly interfaceIPAddress: string;
|
||||
}
|
||||
|
||||
/** Longest numeric mask wins; on a tie the first row stays. */
|
||||
export function bestMatchingManualRoute(
|
||||
targetIPAddress: string,
|
||||
routes: readonly ManualRoute[],
|
||||
): ManualRoute | undefined {
|
||||
const target = parseIPv4(targetIPAddress);
|
||||
if (target === undefined) return undefined;
|
||||
let best: ManualRoute | undefined;
|
||||
let bestMask: number | undefined;
|
||||
for (const route of routes) {
|
||||
const destination = parseIPv4(route.destinationNetwork);
|
||||
const mask = parseIPv4(route.subnetMask);
|
||||
if (destination === undefined || mask === undefined) continue;
|
||||
if (destination !== maskAddress(target, mask)) continue;
|
||||
if (bestMask !== undefined && mask <= bestMask) continue;
|
||||
bestMask = mask;
|
||||
best = route;
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
export interface SwitchConfiguration {
|
||||
readonly ssid: string;
|
||||
readonly retentionTimeMilliseconds: number;
|
||||
}
|
||||
|
||||
/** Java SwitchFirmware keeps a learned MAC address for five minutes. */
|
||||
export const DEFAULT_SWITCH_RETENTION_MILLISECONDS = 300_000;
|
||||
|
||||
export function switchConfiguration(
|
||||
ssid: string,
|
||||
retentionTimeMilliseconds = DEFAULT_SWITCH_RETENTION_MILLISECONDS,
|
||||
): SwitchConfiguration {
|
||||
return { ssid, retentionTimeMilliseconds };
|
||||
}
|
||||
|
||||
/** The first six hex digits of the node's UUID, lower case. */
|
||||
export function defaultSwitchConfiguration(nodeId: Id): SwitchConfiguration {
|
||||
const compact = nodeId.replaceAll('-', '').toLowerCase();
|
||||
return switchConfiguration(compact.slice(0, 6));
|
||||
}
|
||||
|
||||
// ── Firewall (Java filius.software.firewall.Firewall, FirewallRule) ─────────
|
||||
|
||||
export const FIREWALL_PROTOCOL = { all: -1, icmp: 1, tcp: 6, udp: 17 } as const;
|
||||
export type FirewallProtocol = keyof typeof FIREWALL_PROTOCOL;
|
||||
|
||||
export type FirewallAction = 'drop' | 'accept';
|
||||
|
||||
/** Java writes this as the source of a rule that means "any directly connected network". */
|
||||
export const DIRECTLY_CONNECTED_SOURCE_MARKER = '999.999.999.999';
|
||||
export const ALL_PORTS = -1;
|
||||
|
||||
export interface FirewallRule {
|
||||
readonly sourceIPAddress: string;
|
||||
readonly sourceSubnetMask: string;
|
||||
readonly destinationIPAddress: string;
|
||||
readonly destinationSubnetMask: string;
|
||||
/** A port number or ALL_PORTS. */
|
||||
readonly port: number;
|
||||
readonly protocol: FirewallProtocol;
|
||||
readonly action: FirewallAction;
|
||||
}
|
||||
|
||||
export function firewallRule(fields: Partial<FirewallRule> = {}): FirewallRule {
|
||||
return {
|
||||
sourceIPAddress: fields.sourceIPAddress ?? '',
|
||||
sourceSubnetMask: fields.sourceSubnetMask ?? '',
|
||||
destinationIPAddress: fields.destinationIPAddress ?? '',
|
||||
destinationSubnetMask: fields.destinationSubnetMask ?? '',
|
||||
port: fields.port ?? ALL_PORTS,
|
||||
protocol: fields.protocol ?? 'tcp',
|
||||
action: fields.action ?? 'drop',
|
||||
};
|
||||
}
|
||||
|
||||
export interface FirewallConfiguration {
|
||||
readonly isActive: boolean;
|
||||
readonly defaultPolicy: FirewallAction;
|
||||
readonly dropIcmp: boolean;
|
||||
readonly filterSynSegmentsOnly: boolean;
|
||||
readonly filterUdp: boolean;
|
||||
readonly rules: readonly FirewallRule[];
|
||||
}
|
||||
|
||||
/** What a router or gateway has without a firewall configured: inactive. */
|
||||
export function firewallConfiguration(fields: Partial<FirewallConfiguration> = {}): FirewallConfiguration {
|
||||
return {
|
||||
isActive: fields.isActive ?? false,
|
||||
defaultPolicy: fields.defaultPolicy ?? 'drop',
|
||||
dropIcmp: fields.dropIcmp ?? false,
|
||||
filterSynSegmentsOnly: fields.filterSynSegmentsOnly ?? true,
|
||||
filterUdp: fields.filterUdp ?? true,
|
||||
rules: fields.rules ?? [],
|
||||
};
|
||||
}
|
||||
|
||||
/** What Java installs as the personal firewall of a PC: active, drop by default. */
|
||||
export const JAVA_PERSONAL_FIREWALL_DEFAULTS: FirewallConfiguration = firewallConfiguration({
|
||||
isActive: true,
|
||||
defaultPolicy: 'drop',
|
||||
dropIcmp: false,
|
||||
filterSynSegmentsOnly: true,
|
||||
filterUdp: true,
|
||||
rules: [],
|
||||
});
|
||||
Reference in New Issue
Block a user