feat(core): DHCP in the simulation core, as in Swift

engine.ts ports Swift's "DHCP server and automatic client": an active
server listens on port 67 from the start and offers a static assignment
before its range, round from the address after the last one offered;
offers last 10 s, dynamic leases a day, and a server whose offer another
server beat blacklists that address for a while. A client starts as an
event: DISCOVER, OFFER, a check by ARP, DECLINE, REQUEST, ACK or NAK,
2.5 s per wait, and after ten failures its old address back. The snapshot
carries the DHCP settings of clients and servers.

Four scenarios match the Swift engine. One Swift error is named in the
oracle test: a client run directly is advanced a second time, to FINISH,
by the server's answer within its own DISCOVER, and the outer run then
writes back its stale copy of the status (Swift's value types); the
address and every trace agree, only the reported status is wrong in
Swift. Swift's three DHCP engine tests are ported, with tests for
stopping and the message format. 15 mutation probes, 14 caught; the other
concerns the home router, which runs only once NAT is ported.

Two departures from Java remain, to be fixed next: from the event queue
the client's ARP check does not wait, so an address conflict is never
found, and each answer that wakes the client makes it send its state's
message again (a second DISCOVER and REQUEST, the second answered with
NAK). Java waits a round trip and sends once per attempt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-30 10:17:36 +00:00
co-authored by Claude Opus 5.5
parent 12849b1535
commit 396dbacfc5
26 changed files with 43646 additions and 23 deletions
+9 -1
View File
@@ -2,13 +2,15 @@
// State/TopologyNetworkRuntimeEngine.swift (TopologyNetworkRuntimeTopologySnapshot)
// What the runtime starts from: the topology and every configuration, frozen
// at the moment the simulation starts. Later protocol slices add their
// configuration maps here (DHCP, NAT, remote link, wireless).
// configuration maps here (NAT, remote link, wireless).
import { compareIds, type Id } from '../ids.js';
import type { NodeKind } from '../model/node.js';
import { canonicalMacAddress } from '../model/node.js';
import type {
DeviceConfiguration,
DhcpClientConfiguration,
DhcpServerConfiguration,
FirewallConfiguration,
InterfaceConfiguration,
InterfaceKey,
@@ -51,6 +53,8 @@ export interface TopologySnapshot {
readonly interfaceConfigurations: ReadonlyMap<InterfaceKey, InterfaceConfiguration>;
readonly manualRoutesByNodeId: ReadonlyMap<Id, readonly ManualRoute[]>;
readonly ripEnabledByNodeId: ReadonlyMap<Id, boolean>;
readonly dhcpClientConfigurationsByNodeId: ReadonlyMap<Id, DhcpClientConfiguration>;
readonly dhcpServerConfigurationsByNodeId: ReadonlyMap<Id, DhcpServerConfiguration>;
readonly firewallConfigurationsByNodeId: ReadonlyMap<Id, FirewallConfiguration>;
readonly switchConfigurationsByNodeId: ReadonlyMap<Id, SwitchConfiguration>;
}
@@ -62,6 +66,8 @@ export interface TopologySnapshotInput {
interfaceConfigurations?: ReadonlyMap<InterfaceKey, InterfaceConfiguration>;
manualRoutesByNodeId?: ReadonlyMap<Id, readonly ManualRoute[]>;
ripEnabledByNodeId?: ReadonlyMap<Id, boolean>;
dhcpClientConfigurationsByNodeId?: ReadonlyMap<Id, DhcpClientConfiguration>;
dhcpServerConfigurationsByNodeId?: ReadonlyMap<Id, DhcpServerConfiguration>;
firewallConfigurationsByNodeId?: ReadonlyMap<Id, FirewallConfiguration>;
switchConfigurationsByNodeId?: ReadonlyMap<Id, SwitchConfiguration>;
}
@@ -74,6 +80,8 @@ export function topologySnapshot(input: TopologySnapshotInput): TopologySnapshot
interfaceConfigurations: input.interfaceConfigurations ?? new Map(),
manualRoutesByNodeId: input.manualRoutesByNodeId ?? new Map(),
ripEnabledByNodeId: input.ripEnabledByNodeId ?? new Map(),
dhcpClientConfigurationsByNodeId: input.dhcpClientConfigurationsByNodeId ?? new Map(),
dhcpServerConfigurationsByNodeId: input.dhcpServerConfigurationsByNodeId ?? new Map(),
firewallConfigurationsByNodeId: input.firewallConfigurationsByNodeId ?? new Map(),
switchConfigurationsByNodeId: input.switchConfigurationsByNodeId ?? new Map(),
};