feat(core): packet capture per card after Java's Lauscher, measured card by card
The engine keeps Java's packet capture (capture.ts): per network card every frame it sent (when it leaves on the cable) or received (also one for another card), once, with time and number; switches log nothing; frames the packet loss dropped are kept apart; the capture outlives a stop and a new start, as in Java, until clearCapture. Per port instead of per MAC, at most 10,000 frames a card. expectSameCaptures compares each card's capture with Java's list of that card in the ARP, WLAN, DNS, TCP, NAT, RIP and single-client DHCP runs. It found that ScenarioNetwork ignored a switch's learning time (Java ran 300 s, the core the scenarios' 5 s); the oracle sets it now, and the TCP and DHCP runs are recorded again. A core test found that a new start numbers frames from 1 again and the capture took them as seen. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -39,6 +39,8 @@
|
||||
// DNS after Java (filius.software.dns: DNSServer, DNSServerMitarbeiter,
|
||||
// Resolver, DNSQueryAgent), not Swift's typed format; a recursive server
|
||||
// asks on without asking itself (Java does, without end).
|
||||
// The packet capture after Java's Lauscher (capture.ts): a card logs a frame
|
||||
// when it leaves on the cable and when it arrives, a switch logs nothing.
|
||||
// Java counterparts: filius.hardware.Verbindung, filius.software.netzzugangsschicht,
|
||||
// filius.software.vermittlungsschicht (ARP, IP, ICMP), system.SwitchFirmware,
|
||||
// filius.software.transportschicht (UDP, TCP), filius.software.dhcp.
|
||||
@@ -103,6 +105,7 @@ import {
|
||||
type DnsMessageRecord,
|
||||
type DnsQuestion,
|
||||
} from './dns.js';
|
||||
import { PacketCapture, PRODUCTION_CAPTURE_RETENTION, type CaptureRetentionPolicy } from './capture.js';
|
||||
import { JavaCompatibleRandom } from './random.js';
|
||||
import { routeTableRows, type RouteRow } from './routeTable.js';
|
||||
import { EMPTY_TOPOLOGY_SNAPSHOT, withSnapshot, type NodeSnapshot, type TopologySnapshot } from './snapshot.js';
|
||||
@@ -845,6 +848,7 @@ export class NetworkRuntimeEngine {
|
||||
private readonly cablesCarryOneFrameAtATime: boolean;
|
||||
private readonly swiftArp: boolean;
|
||||
private readonly swiftSends: boolean;
|
||||
private readonly packetCapture: PacketCapture;
|
||||
|
||||
/**
|
||||
* `swiftCables`: a cable carries all frames at once, as in the Swift
|
||||
@@ -855,12 +859,20 @@ export class NetworkRuntimeEngine {
|
||||
*
|
||||
* `swiftArp`, `swiftSends`: Swift's ARP and Swift's send that returns
|
||||
* when the network is quiet, likewise only for the comparison.
|
||||
*
|
||||
* `captureRetention`: how many frames each card's packet capture keeps.
|
||||
*/
|
||||
constructor(
|
||||
seed: bigint | number = DEFAULT_SEED,
|
||||
packetTraceRetentionPolicy: TraceRetentionPolicy = PRODUCTION_TRACE_RETENTION,
|
||||
options: { readonly swiftCables?: boolean; readonly swiftArp?: boolean; readonly swiftSends?: boolean } = {},
|
||||
options: {
|
||||
readonly swiftCables?: boolean;
|
||||
readonly swiftArp?: boolean;
|
||||
readonly swiftSends?: boolean;
|
||||
readonly captureRetention?: CaptureRetentionPolicy;
|
||||
} = {},
|
||||
) {
|
||||
this.packetCapture = new PacketCapture(options.captureRetention ?? PRODUCTION_CAPTURE_RETENTION);
|
||||
this.cablesCarryOneFrameAtATime = options.swiftCables !== true;
|
||||
this.swiftArp = options.swiftArp === true;
|
||||
this.swiftSends = options.swiftSends === true;
|
||||
@@ -876,6 +888,20 @@ export class NetworkRuntimeEngine {
|
||||
this.stateValue.simulationSpeedPercent = this.runtimeSpeed.percent;
|
||||
}
|
||||
|
||||
/**
|
||||
* The packet capture (Java's Lauscher): per card every frame it sent or
|
||||
* received. It outlives a stop and a new start, as in Java; clearCapture
|
||||
* empties it.
|
||||
*/
|
||||
get capture(): PacketCapture {
|
||||
return this.packetCapture;
|
||||
}
|
||||
|
||||
/** Empties the packet capture (Java: the data exchange window's reset, a new or loaded project). */
|
||||
clearCapture(): void {
|
||||
this.packetCapture.clear();
|
||||
}
|
||||
|
||||
/** The runtime state. Read it; change it only through the engine's methods. */
|
||||
get state(): RuntimeState {
|
||||
return this.stateValue;
|
||||
@@ -900,6 +926,7 @@ export class NetworkRuntimeEngine {
|
||||
if (this.stateValue.phase !== 'stopped') return [this.output({ type: 'startIgnoredAlreadyRunning' })];
|
||||
const seed = toSeed(input.seed);
|
||||
this.stateValue = initialState(seed);
|
||||
this.packetCapture.beginRun();
|
||||
this.stateValue.phase = 'running';
|
||||
this.stateValue.currentTimeMilliseconds = input.initialTimeMilliseconds;
|
||||
this.stateValue.simulationSpeedPercent = this.runtimeSpeed.percent;
|
||||
@@ -1143,6 +1170,7 @@ export class NetworkRuntimeEngine {
|
||||
layer: TraceLayer,
|
||||
): boolean {
|
||||
if (!this.stateValue.globalPacketLossEnabled) return false;
|
||||
this.packetCapture.recordDropped(this.stateValue.currentTimeMilliseconds, frame);
|
||||
this.recordTrace({
|
||||
frameIdentity: frame.identity,
|
||||
packetIdentity: packetIdentityInFrame(frame),
|
||||
@@ -3730,6 +3758,7 @@ export class NetworkRuntimeEngine {
|
||||
|
||||
/** The frame leaves on the cable (and shows in the packet log); it arrives one cable's delay later. */
|
||||
private departEthernetFrame(fromNodeId: Id, outgoingPortId: Id, endpoint: { nodeId: Id; portId: Id } | undefined, frame: EthernetFrame): void {
|
||||
this.captureOnCard(fromNodeId, outgoingPortId, frame);
|
||||
this.recordTrace({
|
||||
frameIdentity: frame.identity,
|
||||
packetIdentity: packetIdentityInFrame(frame),
|
||||
@@ -3804,7 +3833,14 @@ export class NetworkRuntimeEngine {
|
||||
return this.stateValue.topologySnapshot.nodes.find((node) => node.id === nodeId);
|
||||
}
|
||||
|
||||
/** Java logs a frame on a card; a switch has none. */
|
||||
private captureOnCard(nodeId: Id, portId: Id, frame: EthernetFrame): void {
|
||||
if (this.nodeSnapshot(nodeId)?.kind === 'networkSwitch') return;
|
||||
this.packetCapture.record(portId, this.stateValue.currentTimeMilliseconds, frame);
|
||||
}
|
||||
|
||||
private receiveEthernetFrame(frame: EthernetFrame, nodeId: Id, incomingPortId: Id): void {
|
||||
this.captureOnCard(nodeId, incomingPortId, frame);
|
||||
this.recordTrace({
|
||||
frameIdentity: frame.identity,
|
||||
packetIdentity: packetIdentityInFrame(frame),
|
||||
|
||||
Reference in New Issue
Block a user