feat(fls): import DHCP, port forwarding and modem settings
importProject carries the DHCP client switch and server settings (hosts and home routers), a home router's port forwarding rows and a modem's pairing into the model; the core gains the types, ported from Swift. The Swift oracle writes them too, and they match on the 13 files Swift reads completely. Configuration text is now trimmed throughout, as Swift trims every value it reads (routes and firewall rules included). Outside those files Swift switches every DHCP server of the Internet examples off: their "aktiv" refers to a shared Boolean it cannot follow. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -20,11 +20,18 @@
|
||||
// - every router and home router has a firewall configuration, as every one
|
||||
// has a firewall in Java (Swift keeps one only when the file sets a field;
|
||||
// without one the core behaves as with an inactive one).
|
||||
// Not here yet: DHCP, NAT, remote links, file systems, applications.
|
||||
// - configuration text is trimmed, as Swift trims every value it reads;
|
||||
// names stay as Java has them;
|
||||
// - a Java modem becomes an in-project remote link pair keyed by its port,
|
||||
// as in Swift — two FILIUS instances on different hosts are not joined.
|
||||
// Not here yet: file systems and applications.
|
||||
|
||||
import {
|
||||
canonicalMacAddress,
|
||||
deepEqual,
|
||||
defaultSwitchConfiguration,
|
||||
dhcpServerConfiguration,
|
||||
DEFAULT_REMOTE_LINK_LATENCY_MILLISECONDS,
|
||||
deterministicDocumentationUuid,
|
||||
documentationColor,
|
||||
documentationItem,
|
||||
@@ -35,6 +42,10 @@ import {
|
||||
DOCUMENTATION_BLACK,
|
||||
DOCUMENTATION_PALE_YELLOW,
|
||||
type DeviceConfiguration,
|
||||
type DhcpClientConfiguration,
|
||||
type DhcpServerConfiguration,
|
||||
type GatewayPortForwardingRow,
|
||||
type RemoteLinkConfiguration,
|
||||
type FirewallAction,
|
||||
type FirewallConfiguration,
|
||||
type FirewallProtocol,
|
||||
@@ -67,6 +78,11 @@ export interface ImportedProject {
|
||||
/** Only routers with RIP switched on. */
|
||||
ripEnabledByNodeId: Map<Id, boolean>;
|
||||
firewallConfigurationsByNodeId: Map<Id, FirewallConfiguration>;
|
||||
dhcpClientConfigurations: Map<Id, DhcpClientConfiguration>;
|
||||
/** Only servers whose settings differ from Java's constructor. */
|
||||
dhcpServerConfigurations: Map<Id, DhcpServerConfiguration>;
|
||||
portForwardingRowsByNodeId: Map<Id, GatewayPortForwardingRow[]>;
|
||||
remoteLinkConfigurations: Map<Id, RemoteLinkConfiguration>;
|
||||
/** In render order: rectangles below text, then by order. */
|
||||
documentationItems: DocumentationItem[];
|
||||
/** The topology's warnings, then this step's. */
|
||||
@@ -98,11 +114,16 @@ export function importProject(topology: FlsTopology, newId: () => Id): ImportedP
|
||||
manualRoutesByNodeId: new Map(),
|
||||
ripEnabledByNodeId: new Map(),
|
||||
firewallConfigurationsByNodeId: new Map(),
|
||||
dhcpClientConfigurations: new Map(),
|
||||
dhcpServerConfigurations: new Map(),
|
||||
portForwardingRowsByNodeId: new Map(),
|
||||
remoteLinkConfigurations: new Map(),
|
||||
};
|
||||
const nodes = topology.nodes.map((source) => {
|
||||
const node = importNode(source, newId, warnings);
|
||||
registerConfigurations(source, node, deviceConfigurations, interfaceConfigurations);
|
||||
registerSystemConfigurations(source, node, system, warnings);
|
||||
registerServiceConfigurations(source, node, system, newId, warnings);
|
||||
return node;
|
||||
});
|
||||
|
||||
@@ -280,8 +301,87 @@ type SystemConfigurations = Pick<
|
||||
| 'manualRoutesByNodeId'
|
||||
| 'ripEnabledByNodeId'
|
||||
| 'firewallConfigurationsByNodeId'
|
||||
| 'dhcpClientConfigurations'
|
||||
| 'dhcpServerConfigurations'
|
||||
| 'portForwardingRowsByNodeId'
|
||||
| 'remoteLinkConfigurations'
|
||||
>;
|
||||
|
||||
/** A row of text cells, trimmed, or undefined unless it has exactly four. */
|
||||
function fourCells(row: (string | null)[]): [string, string, string, string] | undefined {
|
||||
if (row.length !== 4 || row.some((cell) => cell === null)) return undefined;
|
||||
return row.map((cell) => cell!.trim()) as [string, string, string, string];
|
||||
}
|
||||
|
||||
/**
|
||||
* The DHCP, NAT and modem parts of Swift's finalizeCurrentNodeCandidate and
|
||||
* registerRemoteLinkConfiguration.
|
||||
*/
|
||||
function registerServiceConfigurations(
|
||||
source: FlsNode,
|
||||
node: TopologyNode,
|
||||
system: SystemConfigurations,
|
||||
newId: () => Id,
|
||||
warnings: string[],
|
||||
): void {
|
||||
const where = `node '${node.displayName}'`;
|
||||
const dhcpCapable = node.kind === 'pc' || node.kind === 'notebook' || node.kind === 'gateway';
|
||||
if (source.system.dhcpClient === true) {
|
||||
if (dhcpCapable) system.dhcpClientConfigurations.set(node.id, { isEnabled: true });
|
||||
else warnings.push(`${where}: DHCP client on a ${node.kind}, ignored`);
|
||||
}
|
||||
const server = source.system.dhcpServer;
|
||||
if (server !== undefined) {
|
||||
const configuration = dhcpServerConfiguration({
|
||||
isActive: server.isActive,
|
||||
lowerBoundIPAddress: server.lowerBound?.trim() ?? '',
|
||||
upperBoundIPAddress: server.upperBound?.trim() ?? '',
|
||||
gatewayIPAddress: server.gateway?.trim() ?? '',
|
||||
dnsServerIPAddress: server.dnsServer?.trim() ?? '',
|
||||
useOwnSettings: server.useOwnSettings,
|
||||
staticAssignments: server.staticAssignedAddresses.map((entry) => ({
|
||||
id: newId(),
|
||||
macAddress: entry.mac,
|
||||
ipAddress: entry.ip,
|
||||
})),
|
||||
});
|
||||
if (!deepEqual(configuration, dhcpServerConfiguration())) {
|
||||
if (dhcpCapable) system.dhcpServerConfigurations.set(node.id, configuration);
|
||||
else warnings.push(`${where}: DHCP server settings on a ${node.kind}, ignored`);
|
||||
}
|
||||
}
|
||||
if (node.kind === 'gateway') {
|
||||
const rows: GatewayPortForwardingRow[] = [];
|
||||
for (const row of source.system.staticNAT ?? []) {
|
||||
const cells = fourCells(row);
|
||||
if (cells === undefined) {
|
||||
warnings.push(`${where}: a port forwarding row without four entries, left out`);
|
||||
continue;
|
||||
}
|
||||
const [protocolValue, publicPortValue, lanIPAddress, lanPortValue] = cells;
|
||||
rows.push({ protocolValue, publicPortValue, lanIPAddress, lanPortValue });
|
||||
}
|
||||
if (rows.length > 0) system.portForwardingRowsByNodeId.set(node.id, rows);
|
||||
}
|
||||
if (node.kind === 'remoteLink') {
|
||||
const modem = source.system.modem;
|
||||
let port = modem?.port ?? 12_345;
|
||||
if (port < 1 || port > 65_535) {
|
||||
warnings.push(`${where}: modem port ${port} is invalid, used 12345`);
|
||||
port = 12_345;
|
||||
}
|
||||
if (modem !== undefined && modem.mode !== 1 && modem.mode !== 2) {
|
||||
warnings.push(`${where}: modem mode ${modem.mode} unknown, treated as client`);
|
||||
}
|
||||
// Java pairs a server and a client modem over TCP on this port.
|
||||
system.remoteLinkConfigurations.set(node.id, {
|
||||
pairIdentifier: `java-modem-port-${port}`,
|
||||
latencyMilliseconds: DEFAULT_REMOTE_LINK_LATENCY_MILLISECONDS,
|
||||
isEnabled: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Swift's registerSwitchAndWirelessConfigurations, registerManualRoutes and
|
||||
* the RIP and firewall parts of finalizeCurrentNodeCandidate.
|
||||
@@ -306,17 +406,12 @@ function registerSystemConfigurations(
|
||||
}
|
||||
const routes: ManualRoute[] = [];
|
||||
for (const row of source.system.manualRoutes ?? []) {
|
||||
const [destinationNetwork, subnetMask, gateway, interfaceIPAddress] = row;
|
||||
if (
|
||||
row.length !== 4 ||
|
||||
destinationNetwork == null ||
|
||||
subnetMask == null ||
|
||||
gateway == null ||
|
||||
interfaceIPAddress == null
|
||||
) {
|
||||
const cells = fourCells(row);
|
||||
if (cells === undefined) {
|
||||
warnings.push(`${where}: a manual route without four entries, left out`);
|
||||
continue;
|
||||
}
|
||||
const [destinationNetwork, subnetMask, gateway, interfaceIPAddress] = cells;
|
||||
routes.push({ destinationNetwork, subnetMask, gateway, interfaceIPAddress });
|
||||
}
|
||||
if (routes.length > 0) system.manualRoutesByNodeId.set(node.id, routes);
|
||||
@@ -340,10 +435,10 @@ function firewallConfigurationOf(firewall: FlsFirewall, where: string, warnings:
|
||||
const protocol = PROTOCOLS[rule.protocol];
|
||||
if (protocol === undefined) warnings.push(`${where}: firewall protocol ${rule.protocol} unknown, used TCP`);
|
||||
return {
|
||||
sourceIPAddress: rule.srcIP ?? '',
|
||||
sourceSubnetMask: rule.srcMask ?? '',
|
||||
destinationIPAddress: rule.destIP ?? '',
|
||||
destinationSubnetMask: rule.destMask ?? '',
|
||||
sourceIPAddress: rule.srcIP?.trim() ?? '',
|
||||
sourceSubnetMask: rule.srcMask?.trim() ?? '',
|
||||
destinationIPAddress: rule.destIP?.trim() ?? '',
|
||||
destinationSubnetMask: rule.destMask?.trim() ?? '',
|
||||
port: rule.port,
|
||||
protocol: protocol ?? 'tcp',
|
||||
action: action(rule.action, 'action'),
|
||||
|
||||
Reference in New Issue
Block a user