feat(core): modems run — the pair in the project, and a modem of another project hands its frames on (K0–K2)
Measured first: tools/java-oracle's ModemRun joins two networks by a pair of Java modems over TCP on localhost. The modem has no card and adds no delay of its own — a frame takes the cables on both sides, nothing more. The core pairs the two enabled modems on a port (Swift's inProject, with its conditions and cycle suppression) on Java's timing: no 20 ms. The modem example of FILIUS now pings through instead of failing the core. A modem of another project (relay) gives its frames to whoever runs the engine and puts on its cable what comes back, with this engine's frame and packet numbers; a frame dies after MAXIMUM_RELAY_HOPS trips. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -51,7 +51,7 @@ export function uuid(suffix: number): Id {
|
||||
return `00000000-0000-0000-0000-${suffix.toString(16).toUpperCase().padStart(12, '0')}`;
|
||||
}
|
||||
|
||||
const FRAME_EVENTS = new Set(['ethernetLinkDeparture', 'ethernetLinkDelivery', 'remoteLinkDelivery', 'arpAnswered']);
|
||||
const FRAME_EVENTS = new Set(['ethernetLinkDeparture', 'ethernetLinkDelivery', 'arpAnswered']);
|
||||
|
||||
/**
|
||||
* Advances until every frame in flight has arrived — what Swift's send did
|
||||
|
||||
@@ -135,7 +135,7 @@ export function watchedEngine(scenario: Scenario, snapshot: TopologySnapshot = s
|
||||
|
||||
/** Every frame when it first shows on a host's card (sent, or received). */
|
||||
const frames = (): SeenFrame[] => {
|
||||
const hosts = new Set(scenario.nodes.filter((node) => node.kind !== 'networkSwitch').map((node) => node.id));
|
||||
const hosts = new Set(scenario.nodes.filter((node) => node.kind !== 'networkSwitch' && node.kind !== 'remoteLink').map((node) => node.id));
|
||||
const firstSeen = new Map<number, SeenFrame>();
|
||||
for (const event of engine.state.packetTraces) {
|
||||
if (event.layer !== 'physical' || !hosts.has(event.nodeId) || event.frameIdentity === undefined) continue;
|
||||
@@ -185,10 +185,10 @@ export interface CaptureCard {
|
||||
readonly portId: string;
|
||||
}
|
||||
|
||||
/** A scenario's cards: its hosts' ports, under the node's id for the first card and `id#index` for the others (ScenarioNetwork). */
|
||||
/** A scenario's cards: its hosts' ports, under the node's id for the first card and `id#index` for the others (ScenarioNetwork); switches and modems have none. */
|
||||
export function scenarioCards(scenario: Scenario): CaptureCard[] {
|
||||
return scenario.nodes
|
||||
.filter((node) => node.kind !== 'networkSwitch')
|
||||
.filter((node) => node.kind !== 'networkSwitch' && node.kind !== 'remoteLink')
|
||||
.flatMap((node) => node.ports.map((port, index) => ({ key: index === 0 ? node.id : `${node.id}#${index}`, portId: port.id })));
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,164 @@
|
||||
// Modems as Java FILIUS runs them. tools/java-oracle's ModemRun joined two
|
||||
// networks by a pair of modems (a server and a client over TCP on localhost)
|
||||
// and pinged across (modem-java-<story>.java.json): the modem has no card,
|
||||
// so no capture of its own, and adds no delay — a frame takes the cables on
|
||||
// both sides, nothing more. The engine pairs the two modems on a port and must
|
||||
// show every card the same frames at about the same times.
|
||||
//
|
||||
// Beside Java: the pair's cycle suppression, three modems on a port, a
|
||||
// partner without its cable (Swift's conditions; Java's modems have no pair
|
||||
// in the project).
|
||||
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { dirname, join } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { withSnapshot, type NetworkRuntimeEngine } from '../src/index.js';
|
||||
import { expectSameCaptures, GAP_TOLERANCE_MILLISECONDS, watchedEngine, type JavaFrame, type JavaTables } from './javaRun.js';
|
||||
import { snapshotOf, type Scenario } from './scenario.js';
|
||||
|
||||
const scenarioDirectory = join(dirname(fileURLToPath(import.meta.url)), 'scenarios');
|
||||
const id = (number: number) => `00000000-0000-0000-0000-000000000${number}`;
|
||||
const HOST = id(401);
|
||||
const SERVER_MODEM = id(405);
|
||||
const CLIENT_MODEM = id(406);
|
||||
|
||||
interface JavaRun extends JavaTables {
|
||||
frames: Record<string, JavaFrame[]>;
|
||||
events: [number, string][];
|
||||
}
|
||||
|
||||
/** A ping from HOST in Java's words: the ICMP answer's type and code, or null when none came in time. */
|
||||
function ping(engine: NetworkRuntimeEngine, target: string, sequenceNumber: number): string {
|
||||
const result = engine.sendIcmpEcho(HOST, target, { sequenceNumber });
|
||||
if (result.type === 'icmpError' && result.kind === 'destinationHostUnreachable') return 'ICMP 3/1';
|
||||
const answered = (engine.state.icmpObservationsByNodeId.get(HOST) ?? []).some((observation) => observation.message.kind === 'echoReply' && observation.message.sequenceNumber === sequenceNumber);
|
||||
return answered ? 'ICMP 0/0' : 'null';
|
||||
}
|
||||
|
||||
const scenario = JSON.parse(readFileSync(join(scenarioDirectory, 'modem-lan.scenario.json'), 'utf8')) as Scenario;
|
||||
|
||||
/** Without the client modem's configuration: the server modem has no partner, as Java's that is not connected. */
|
||||
const unpaired = () => {
|
||||
const snapshot = snapshotOf(scenario);
|
||||
return withSnapshot(snapshot, { remoteLinkConfigurationsByNodeId: new Map([...snapshot.remoteLinkConfigurationsByNodeId].filter(([node]) => node !== CLIENT_MODEM)) });
|
||||
};
|
||||
|
||||
const STORIES: Record<string, { readonly pings: number; readonly snapshot?: () => ReturnType<typeof snapshotOf> }> = {
|
||||
ping: { pings: 2 },
|
||||
unconnected: { pings: 1, snapshot: unpaired },
|
||||
};
|
||||
|
||||
/** Java's events without its answer's sender ("from …"), which the engine's results do not name. */
|
||||
const javaEvent = (event: string) => event.replace(/ from .*$/, '');
|
||||
|
||||
describe('modems as Java FILIUS runs them', () => {
|
||||
for (const [name, story] of Object.entries(STORIES)) {
|
||||
it(name, () => {
|
||||
const java = JSON.parse(readFileSync(join(scenarioDirectory, `modem-java-${name}.java.json`), 'utf8')) as JavaRun;
|
||||
const { engine } = watchedEngine(scenario, story.snapshot?.());
|
||||
engine.handle({ type: 'advance', toMilliseconds: 500 });
|
||||
const events: [number, string][] = [];
|
||||
for (let sequence = 1; sequence <= story.pings; sequence += 1) {
|
||||
events.push([engine.state.currentTimeMilliseconds, 'sendProbe 10.0.0.2 called']);
|
||||
const result = ping(engine, '10.0.0.2', sequence);
|
||||
events.push([engine.state.currentTimeMilliseconds, `sendProbe 10.0.0.2 returned ${result}`]);
|
||||
}
|
||||
engine.handle({ type: 'advance', toMilliseconds: 10_000 });
|
||||
|
||||
expect(events.map(([, event]) => event)).toEqual(java.events.map(([, event]) => javaEvent(event)));
|
||||
for (const [index, [time]] of events.entries()) {
|
||||
expect(Math.abs(time - events[0]![0] - (java.events[index]![0] - java.events[0]![0])), `time of ${events[index]![1]}`).toBeLessThanOrEqual(GAP_TOLERANCE_MILLISECONDS);
|
||||
}
|
||||
expectSameCaptures(engine, scenario, java.frames, { tables: java });
|
||||
// A modem has no card: nothing in a capture of its port.
|
||||
expect(engine.capture.frames(id(451))).toEqual([]);
|
||||
expect(engine.capture.frames(id(461))).toEqual([]);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
describe('a pair of modems in the project', () => {
|
||||
it('stands active while running, stopped otherwise', () => {
|
||||
const { engine } = watchedEngine(scenario);
|
||||
expect(engine.remoteLinkCondition(SERVER_MODEM)).toEqual({ kind: 'active', partnerNodeId: CLIENT_MODEM });
|
||||
expect(engine.remoteLinkCondition(CLIENT_MODEM)).toEqual({ kind: 'active', partnerNodeId: SERVER_MODEM });
|
||||
expect(engine.remoteLinkCondition(HOST)).toBeUndefined();
|
||||
engine.handle({ type: 'stop' });
|
||||
expect(engine.remoteLinkCondition(SERVER_MODEM)).toEqual({ kind: 'stopped' });
|
||||
});
|
||||
|
||||
it('adds no delay of its own: across the pair a frame takes three cables, as two take two', () => {
|
||||
const { engine } = watchedEngine(scenario);
|
||||
engine.handle({ type: 'advance', toMilliseconds: 500 });
|
||||
ping(engine, '10.0.0.2', 1);
|
||||
const sent = engine.capture.frames(id(411))[0]!.timeMilliseconds;
|
||||
// The ARP request: 10.0.0.3 behind the switch (two cables), 10.0.0.2 behind the pair (three).
|
||||
const overSwitch = engine.capture.frames(id(417))[0]!.timeMilliseconds - sent;
|
||||
const overPair = engine.capture.frames(id(413))[0]!.timeMilliseconds - sent;
|
||||
expect(overSwitch).toBeGreaterThan(0);
|
||||
expect(overPair).toBe((overSwitch / 2) * 3);
|
||||
});
|
||||
|
||||
it('pairs only enabled modems', () => {
|
||||
const snapshot = snapshotOf(scenario);
|
||||
const off = withSnapshot(snapshot, {
|
||||
remoteLinkConfigurationsByNodeId: new Map(
|
||||
[...snapshot.remoteLinkConfigurationsByNodeId].map(([node, configuration]) => [node, node === CLIENT_MODEM ? { ...configuration, isEnabled: false } : configuration]),
|
||||
),
|
||||
});
|
||||
const { engine } = watchedEngine(scenario, off);
|
||||
expect(engine.remoteLinkCondition(SERVER_MODEM)).toEqual({ kind: 'unpaired' });
|
||||
expect(engine.remoteLinkCondition(CLIENT_MODEM)).toEqual({ kind: 'disabled' });
|
||||
});
|
||||
|
||||
it('has no partner when it is alone on its port, and drops what comes', () => {
|
||||
const { engine } = watchedEngine(scenario, unpaired());
|
||||
expect(engine.remoteLinkCondition(SERVER_MODEM)).toEqual({ kind: 'unpaired' });
|
||||
engine.handle({ type: 'advance', toMilliseconds: 500 });
|
||||
ping(engine, '10.0.0.2', 1);
|
||||
expect(engine.state.packetTraces.some((event) => event.nodeId === SERVER_MODEM && event.operation === 'dropped' && event.detail === 'remote link unpaired')).toBe(true);
|
||||
expect(engine.capture.frames(id(413))).toEqual([]);
|
||||
});
|
||||
|
||||
it('is ambiguous with a third modem on the port, and passes nothing', () => {
|
||||
const third = id(408);
|
||||
const snapshot = snapshotOf(scenario);
|
||||
const crowded = withSnapshot(snapshot, {
|
||||
nodes: [...snapshot.nodes, { id: third, kind: 'remoteLink', ports: [{ id: id(481), label: 'remote0', macAddress: '02:00:00:00:04:81' }] }],
|
||||
remoteLinkConfigurationsByNodeId: new Map([...snapshot.remoteLinkConfigurationsByNodeId, [third, { pairIdentifier: 'java-modem-port-12345', latencyMilliseconds: 20, isEnabled: true }]]),
|
||||
});
|
||||
const { engine } = watchedEngine(scenario, crowded);
|
||||
expect(engine.remoteLinkCondition(SERVER_MODEM)).toEqual({ kind: 'ambiguous', enabledNodeCount: 3 });
|
||||
engine.handle({ type: 'advance', toMilliseconds: 500 });
|
||||
expect(ping(engine, '10.0.0.2', 1)).toBe('ICMP 3/1');
|
||||
});
|
||||
|
||||
it('is detached when its partner has no cable', () => {
|
||||
const snapshot = snapshotOf(scenario);
|
||||
const { engine } = watchedEngine(scenario, withSnapshot(snapshot, { links: snapshot.links.filter((link) => link.sourceNodeId !== CLIENT_MODEM) }));
|
||||
expect(engine.remoteLinkCondition(SERVER_MODEM)).toEqual({ kind: 'detached', partnerNodeId: CLIENT_MODEM });
|
||||
engine.handle({ type: 'advance', toMilliseconds: 500 });
|
||||
expect(ping(engine, '10.0.0.2', 1)).toBe('ICMP 3/1');
|
||||
});
|
||||
|
||||
it('passes a frame once: both modems on one switch make a circle that ends at the modems', () => {
|
||||
// The client modem on the switch's third port instead of at 10.0.0.2: a broadcast goes into the server modem, out of the client modem, back into the switch.
|
||||
const snapshot = snapshotOf(scenario);
|
||||
const circle = withSnapshot(snapshot, {
|
||||
nodes: snapshot.nodes.map((node) => (node.id === id(404) ? { ...node, ports: [...node.ports, { id: id(444), label: 'sw4', macAddress: '02:00:00:00:04:44' }] } : node)),
|
||||
links: [
|
||||
...snapshot.links.filter((link) => link.targetNodeId !== id(403)),
|
||||
{ id: '00000000-0000-0000-0000-00000000F405', sourceNodeId: CLIENT_MODEM, sourcePortId: id(461), targetNodeId: id(404), targetPortId: id(444) },
|
||||
],
|
||||
});
|
||||
const { engine } = watchedEngine(scenario, circle);
|
||||
engine.handle({ type: 'advance', toMilliseconds: 500 });
|
||||
expect(ping(engine, '10.0.0.3', 1)).toBe('ICMP 0/0');
|
||||
engine.handle({ type: 'advance', toMilliseconds: 5_000 });
|
||||
// The switch floods the request to both modems: the client modem already passed it on as the server modem's partner.
|
||||
expect(engine.state.packetTraces.some((event) => event.nodeId === CLIENT_MODEM && event.detail === 'remote link cycle suppression')).toBe(true);
|
||||
// 10.0.0.3 saw the request once, not again and again.
|
||||
expect(engine.capture.frames(id(417)).filter((each) => each.frame.payload.kind === 'arp' && each.frame.payload.packet.operation === 'request')).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,266 @@
|
||||
// A modem of another project (server F's relay): the engine hands what
|
||||
// reaches such a modem to `relay` and puts on the modem's cable what comes
|
||||
// back through receiveRelayedFrame. Here two engines stand for two projects,
|
||||
// joined by a queue that delivers between their steps, as F's threads do.
|
||||
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import {
|
||||
deviceConfiguration,
|
||||
MAXIMUM_RELAY_HOPS,
|
||||
NetworkRuntimeEngine,
|
||||
portSnapshot,
|
||||
topologySnapshot,
|
||||
type EthernetFrame,
|
||||
type Id,
|
||||
type NodeKind,
|
||||
type RemoteLinkRelay,
|
||||
type TopologySnapshot,
|
||||
} from '../src/index.js';
|
||||
|
||||
const id = (number: number) => `00000000-0000-0000-0000-${String(number).padStart(12, '0')}`;
|
||||
|
||||
interface Device {
|
||||
readonly id: number;
|
||||
readonly kind: NodeKind;
|
||||
readonly ports: number;
|
||||
readonly ip?: string;
|
||||
readonly relay?: RemoteLinkRelay;
|
||||
}
|
||||
|
||||
/** A project: its devices (port n of device d is d * 10 + n) and cables between ports. */
|
||||
function project(devices: readonly Device[], cables: readonly [number, number][]): TopologySnapshot {
|
||||
const owner = new Map(devices.flatMap((device) => Array.from({ length: device.ports }, (_, index) => [device.id * 10 + index, device.id] as const)));
|
||||
return topologySnapshot({
|
||||
nodes: devices.map((device) => ({
|
||||
id: id(device.id),
|
||||
kind: device.kind,
|
||||
ports: Array.from({ length: device.ports }, (_, index) => portSnapshot(id(device.id * 10 + index), `p${index}`)),
|
||||
})),
|
||||
links: cables.map(([source, target], index) => ({
|
||||
id: id(900_000 + source * 1000 + target + index),
|
||||
sourceNodeId: id(owner.get(source)!),
|
||||
sourcePortId: id(source),
|
||||
targetNodeId: id(owner.get(target)!),
|
||||
targetPortId: id(target),
|
||||
})),
|
||||
deviceConfigurations: new Map(devices.filter((device) => device.ip !== undefined).map((device) => [id(device.id), deviceConfiguration(device.ip!, '255.255.255.0', '', '')])),
|
||||
remoteLinkConfigurationsByNodeId: new Map(
|
||||
devices
|
||||
.filter((device) => device.kind === 'remoteLink')
|
||||
.map((device) => [id(device.id), { pairIdentifier: 'java-modem-port-12345', latencyMilliseconds: 20, isEnabled: true, ...(device.relay === undefined ? {} : { relay: device.relay }) }]),
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Two engines whose relay modems are paired as `pairs` says (A's modem →
|
||||
* B's); what one relays reaches the other at its next step. `relayed`
|
||||
* counts what went across.
|
||||
*/
|
||||
function joined(a: TopologySnapshot, b: TopologySnapshot, pairs: readonly [number, number][]) {
|
||||
const queue: { to: 'a' | 'b'; node: Id; frame: EthernetFrame }[] = [];
|
||||
const toB = new Map(pairs.map(([from, to]) => [id(from), id(to)]));
|
||||
const toA = new Map(pairs.map(([from, to]) => [id(to), id(from)]));
|
||||
let relayed = 0;
|
||||
const engineA = new NetworkRuntimeEngine(7, undefined, {
|
||||
relay: (node, frame) => {
|
||||
relayed += 1;
|
||||
queue.push({ to: 'b', node: toB.get(node)!, frame });
|
||||
},
|
||||
});
|
||||
const engineB = new NetworkRuntimeEngine(8, undefined, {
|
||||
relay: (node, frame) => {
|
||||
relayed += 1;
|
||||
queue.push({ to: 'a', node: toA.get(node)!, frame });
|
||||
},
|
||||
});
|
||||
engineA.handle({ type: 'start', snapshot: a, seed: 7, initialTimeMilliseconds: 0 });
|
||||
engineB.handle({ type: 'start', snapshot: b, seed: 8, initialTimeMilliseconds: 0 });
|
||||
/** Both engines a millisecond further, then what is queued delivered. */
|
||||
const step = () => {
|
||||
for (const engine of [engineA, engineB]) engine.handle({ type: 'advance', toMilliseconds: engine.state.currentTimeMilliseconds + 1 });
|
||||
for (const { to, node, frame } of queue.splice(0)) (to === 'a' ? engineA : engineB).receiveRelayedFrame(node, frame);
|
||||
};
|
||||
const run = (milliseconds: number) => {
|
||||
for (let index = 0; index < milliseconds; index += 1) step();
|
||||
};
|
||||
return { engineA, engineB, run, relayed: () => relayed };
|
||||
}
|
||||
|
||||
/** A UDP datagram from `from` to `target` (a send returns at once, as in Java; ARP runs while the engines step). */
|
||||
function sendUdp(engine: NetworkRuntimeEngine, from: number, target: string): void {
|
||||
const socket = engine.bindUdpSocket(id(from), { remoteIPAddress: target, remotePort: 9 })!;
|
||||
engine.sendUdp(socket, new TextEncoder().encode('hallo'));
|
||||
}
|
||||
|
||||
/** Whether a card captured a frame of `kind` from `sender`'s address. */
|
||||
function captured(engine: NetworkRuntimeEngine, port: number, kind: 'arp reply' | 'udp', sender: string): boolean {
|
||||
return engine.capture
|
||||
.frames(id(port))
|
||||
.some(({ frame }) =>
|
||||
kind === 'arp reply'
|
||||
? frame.payload.kind === 'arp' && frame.payload.packet.operation === 'reply' && frame.payload.packet.senderIPAddress === sender
|
||||
: frame.payload.kind === 'ipv4' && frame.payload.packet.protocol === 'udp' && frame.payload.packet.senderIPAddress === sender,
|
||||
);
|
||||
}
|
||||
|
||||
const A = project(
|
||||
[
|
||||
{ id: 1, kind: 'pc', ports: 1, ip: '10.0.0.1' },
|
||||
{ id: 2, kind: 'remoteLink', ports: 1, relay: { role: 'offer' } },
|
||||
],
|
||||
[[10, 20]],
|
||||
);
|
||||
const B = project(
|
||||
[
|
||||
{ id: 3, kind: 'remoteLink', ports: 1, relay: { role: 'join', code: 'K7F3QX' } },
|
||||
{ id: 4, kind: 'networkSwitch', ports: 3 },
|
||||
{ id: 5, kind: 'pc', ports: 1, ip: '10.0.0.2' },
|
||||
],
|
||||
[
|
||||
[30, 40],
|
||||
[41, 50],
|
||||
],
|
||||
);
|
||||
|
||||
describe('a modem of another project', () => {
|
||||
it('stands as relay on its cable, detached without', () => {
|
||||
const { engineA } = joined(A, B, [[2, 3]]);
|
||||
expect(engineA.remoteLinkCondition(id(2))).toEqual({ kind: 'relay' });
|
||||
const alone = new NetworkRuntimeEngine(7);
|
||||
alone.handle({ type: 'start', snapshot: project([{ id: 2, kind: 'remoteLink', ports: 1, relay: { role: 'offer' } }], []), seed: 7, initialTimeMilliseconds: 0 });
|
||||
expect(alone.remoteLinkCondition(id(2))).toEqual({ kind: 'detached' });
|
||||
});
|
||||
|
||||
it('joins two projects: ARP goes across and back, then the datagram', () => {
|
||||
const { engineA, engineB, run } = joined(A, B, [[2, 3]]);
|
||||
sendUdp(engineA, 1, '10.0.0.2');
|
||||
run(3_000);
|
||||
expect(captured(engineA, 10, 'arp reply', '10.0.0.2')).toBe(true);
|
||||
expect(captured(engineB, 50, 'udp', '10.0.0.1')).toBe(true);
|
||||
// A modem has no card; each side captures only its own cards.
|
||||
expect(engineA.capture.frames(id(20))).toEqual([]);
|
||||
});
|
||||
|
||||
it('numbers a frame from the other project anew, so that a switch does not take it for one it saw', () => {
|
||||
// B with a second computer on the switch: their datagrams go between them, unicast, not across.
|
||||
const busyB = project(
|
||||
[
|
||||
{ id: 3, kind: 'remoteLink', ports: 1, relay: { role: 'join', code: 'K7F3QX' } },
|
||||
{ id: 4, kind: 'networkSwitch', ports: 3 },
|
||||
{ id: 5, kind: 'pc', ports: 1, ip: '10.0.0.2' },
|
||||
{ id: 9, kind: 'pc', ports: 1, ip: '10.0.0.3' },
|
||||
],
|
||||
[
|
||||
[30, 40],
|
||||
[41, 50],
|
||||
[42, 90],
|
||||
],
|
||||
);
|
||||
const { engineA, engineB, run } = joined(A, busyB, [[2, 3]]);
|
||||
for (let index = 0; index < 5; index += 1) {
|
||||
sendUdp(engineB, 5, '10.0.0.3');
|
||||
run(500);
|
||||
}
|
||||
// B's switch has seen the number A's next frame will carry.
|
||||
expect(engineB.state.switchSeenFrameIdentitiesByNodeId.get(id(4))!.has(engineA.state.nextFrameIdentity)).toBe(true);
|
||||
sendUdp(engineA, 1, '10.0.0.2');
|
||||
run(3_000);
|
||||
expect(captured(engineB, 50, 'udp', '10.0.0.1')).toBe(true);
|
||||
expect(engineB.state.packetTraces.some((event) => event.detail === 'switch cycle suppression')).toBe(false);
|
||||
});
|
||||
|
||||
it(`drops a frame after ${MAXIMUM_RELAY_HOPS} trips between projects: two pairs between the same networks make no endless circle`, () => {
|
||||
const a = project(
|
||||
[
|
||||
{ id: 1, kind: 'pc', ports: 1, ip: '10.0.0.1' },
|
||||
{ id: 6, kind: 'networkSwitch', ports: 3 },
|
||||
{ id: 2, kind: 'remoteLink', ports: 1, relay: { role: 'offer' } },
|
||||
{ id: 7, kind: 'remoteLink', ports: 1, relay: { role: 'offer' } },
|
||||
],
|
||||
[
|
||||
[10, 60],
|
||||
[61, 20],
|
||||
[62, 70],
|
||||
],
|
||||
);
|
||||
const b = project(
|
||||
[
|
||||
{ id: 3, kind: 'remoteLink', ports: 1, relay: { role: 'join', code: 'AAAAAA' } },
|
||||
{ id: 8, kind: 'remoteLink', ports: 1, relay: { role: 'join', code: 'BBBBBB' } },
|
||||
{ id: 4, kind: 'networkSwitch', ports: 2 },
|
||||
],
|
||||
[
|
||||
[30, 40],
|
||||
[80, 41],
|
||||
],
|
||||
);
|
||||
const { engineA, engineB, run, relayed } = joined(a, b, [
|
||||
[2, 3],
|
||||
[7, 8],
|
||||
]);
|
||||
sendUdp(engineA, 1, '10.0.0.9');
|
||||
run(20_000);
|
||||
const dropped = [engineA, engineB].flatMap((engine) => engine.state.packetTraces).filter((event) => event.detail === 'remote link relay hop limit');
|
||||
expect(dropped.length).toBeGreaterThan(0);
|
||||
// Two ARP requests (Java asks twice), each going round until its trips are used up.
|
||||
expect(relayed()).toBeLessThanOrEqual(2 * 2 * MAXIMUM_RELAY_HOPS);
|
||||
});
|
||||
|
||||
it('is no partner for a modem of this project on the same port', () => {
|
||||
const engine = new NetworkRuntimeEngine(7);
|
||||
const mixed = project(
|
||||
[
|
||||
{ id: 1, kind: 'pc', ports: 1, ip: '10.0.0.1' },
|
||||
{ id: 2, kind: 'remoteLink', ports: 1 },
|
||||
{ id: 3, kind: 'remoteLink', ports: 1, relay: { role: 'offer' } },
|
||||
{ id: 5, kind: 'pc', ports: 1, ip: '10.0.0.2' },
|
||||
],
|
||||
[
|
||||
[10, 20],
|
||||
[30, 50],
|
||||
],
|
||||
);
|
||||
engine.handle({ type: 'start', snapshot: mixed, seed: 7, initialTimeMilliseconds: 0 });
|
||||
expect(engine.remoteLinkCondition(id(2))).toEqual({ kind: 'unpaired' });
|
||||
});
|
||||
|
||||
it('loses what reaches it without a relay, as an unconnected modem does', () => {
|
||||
const engine = new NetworkRuntimeEngine(7);
|
||||
engine.handle({ type: 'start', snapshot: A, seed: 7, initialTimeMilliseconds: 0 });
|
||||
sendUdp(engine, 1, '10.0.0.2');
|
||||
engine.handle({ type: 'advance', toMilliseconds: 1_000 });
|
||||
expect(engine.state.packetTraces.some((event) => event.nodeId === id(2) && event.detail === 'remote link relay unavailable')).toBe(true);
|
||||
});
|
||||
|
||||
it('takes nothing from the relay while stopped, nor at a modem of this project', () => {
|
||||
const { engineA, engineB } = joined(A, B, [[2, 3]]);
|
||||
const frame: EthernetFrame = {
|
||||
identity: 1,
|
||||
sourceMacAddress: '02:00:00:00:00:01',
|
||||
destinationMacAddress: 'FF:FF:FF:FF:FF:FF',
|
||||
payload: {
|
||||
kind: 'arp',
|
||||
packet: { operation: 'request', senderMacAddress: '02:00:00:00:00:01', senderIPAddress: '10.0.0.1', targetMacAddress: '00:00:00:00:00:00', targetIPAddress: '10.0.0.2' },
|
||||
},
|
||||
};
|
||||
expect(engineB.receiveRelayedFrame(id(5), frame)).toBe(false);
|
||||
expect(engineB.receiveRelayedFrame(id(3), frame)).toBe(true);
|
||||
engineA.handle({ type: 'stop' });
|
||||
expect(engineA.receiveRelayedFrame(id(2), frame)).toBe(false);
|
||||
const inProject = new NetworkRuntimeEngine(7);
|
||||
inProject.handle({
|
||||
type: 'start',
|
||||
snapshot: project(
|
||||
[
|
||||
{ id: 2, kind: 'remoteLink', ports: 1 },
|
||||
{ id: 1, kind: 'pc', ports: 1, ip: '10.0.0.1' },
|
||||
],
|
||||
[[10, 20]],
|
||||
),
|
||||
seed: 7,
|
||||
initialTimeMilliseconds: 0,
|
||||
});
|
||||
expect(inProject.receiveRelayedFrame(id(2), frame)).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -58,6 +58,11 @@ export interface Scenario {
|
||||
portForwarding?: Record<string, [string, string, string, string][]>;
|
||||
/** Routers with RIP switched on. */
|
||||
rip?: string[];
|
||||
/**
|
||||
* Modems: Java pairs a server and a client on a port over TCP; the core
|
||||
* pairs the two modems on a port (the mode is Java's only).
|
||||
*/
|
||||
modems?: Record<string, { mode: 'server' | 'client'; port: number }>;
|
||||
/** Records as `name TYPE target` (Java's hinzuRecord: TTL 3600). */
|
||||
dnsServers?: Record<string, { records: string[]; recursive?: boolean }>;
|
||||
steps: {
|
||||
@@ -124,6 +129,9 @@ export function snapshotOf(scenario: Scenario): TopologySnapshot {
|
||||
targetNodeId: link.targetNode,
|
||||
targetPortId: link.targetPort,
|
||||
})),
|
||||
remoteLinkConfigurationsByNodeId: new Map(
|
||||
Object.entries(scenario.modems ?? {}).map(([node, modem]) => [node, { pairIdentifier: `java-modem-port-${modem.port}`, latencyMilliseconds: 20, isEnabled: true }]),
|
||||
),
|
||||
deviceConfigurations: new Map(
|
||||
Object.entries(scenario.devices ?? {}).map(([node, device]) => [
|
||||
node,
|
||||
|
||||
@@ -0,0 +1,580 @@
|
||||
{
|
||||
"frames": {
|
||||
"00000000-0000-0000-0000-000000000401": [
|
||||
{
|
||||
"time": 734,
|
||||
"source": "02:00:00:00:04:11",
|
||||
"destination": "FF:FF:FF:FF:FF:FF",
|
||||
"arp": 1,
|
||||
"senderIp": "10.0.0.1",
|
||||
"targetIp": "10.0.0.2"
|
||||
},
|
||||
{
|
||||
"time": 1124,
|
||||
"source": "02:00:00:00:04:13",
|
||||
"destination": "02:00:00:00:04:11",
|
||||
"arp": 2,
|
||||
"senderIp": "10.0.0.2",
|
||||
"targetIp": "10.0.0.1"
|
||||
},
|
||||
{
|
||||
"time": 1129,
|
||||
"source": "02:00:00:00:04:11",
|
||||
"destination": "02:00:00:00:04:13",
|
||||
"icmp": 8,
|
||||
"icmpCode": 0,
|
||||
"sourceIp": "10.0.0.1",
|
||||
"destinationIp": "10.0.0.2",
|
||||
"sequence": 1
|
||||
},
|
||||
{
|
||||
"time": 1466,
|
||||
"source": "02:00:00:00:04:13",
|
||||
"destination": "02:00:00:00:04:11",
|
||||
"icmp": 0,
|
||||
"icmpCode": 0,
|
||||
"sourceIp": "10.0.0.2",
|
||||
"destinationIp": "10.0.0.1",
|
||||
"sequence": 1
|
||||
},
|
||||
{
|
||||
"time": 1469,
|
||||
"source": "02:00:00:00:04:11",
|
||||
"destination": "02:00:00:00:04:13",
|
||||
"icmp": 8,
|
||||
"icmpCode": 0,
|
||||
"sourceIp": "10.0.0.1",
|
||||
"destinationIp": "10.0.0.2",
|
||||
"sequence": 2
|
||||
},
|
||||
{
|
||||
"time": 1803,
|
||||
"source": "02:00:00:00:04:13",
|
||||
"destination": "02:00:00:00:04:11",
|
||||
"icmp": 0,
|
||||
"icmpCode": 0,
|
||||
"sourceIp": "10.0.0.2",
|
||||
"destinationIp": "10.0.0.1",
|
||||
"sequence": 2
|
||||
}
|
||||
],
|
||||
"00000000-0000-0000-0000-000000000403": [
|
||||
{
|
||||
"time": 915,
|
||||
"source": "02:00:00:00:04:11",
|
||||
"destination": "FF:FF:FF:FF:FF:FF",
|
||||
"arp": 1,
|
||||
"senderIp": "10.0.0.1",
|
||||
"targetIp": "10.0.0.2"
|
||||
},
|
||||
{
|
||||
"time": 915,
|
||||
"source": "02:00:00:00:04:13",
|
||||
"destination": "02:00:00:00:04:11",
|
||||
"arp": 2,
|
||||
"senderIp": "10.0.0.2",
|
||||
"targetIp": "10.0.0.1"
|
||||
},
|
||||
{
|
||||
"time": 1298,
|
||||
"source": "02:00:00:00:04:11",
|
||||
"destination": "02:00:00:00:04:13",
|
||||
"icmp": 8,
|
||||
"icmpCode": 0,
|
||||
"sourceIp": "10.0.0.1",
|
||||
"destinationIp": "10.0.0.2",
|
||||
"sequence": 1
|
||||
},
|
||||
{
|
||||
"time": 1299,
|
||||
"source": "02:00:00:00:04:13",
|
||||
"destination": "02:00:00:00:04:11",
|
||||
"icmp": 0,
|
||||
"icmpCode": 0,
|
||||
"sourceIp": "10.0.0.2",
|
||||
"destinationIp": "10.0.0.1",
|
||||
"sequence": 1
|
||||
},
|
||||
{
|
||||
"time": 1636,
|
||||
"source": "02:00:00:00:04:11",
|
||||
"destination": "02:00:00:00:04:13",
|
||||
"icmp": 8,
|
||||
"icmpCode": 0,
|
||||
"sourceIp": "10.0.0.1",
|
||||
"destinationIp": "10.0.0.2",
|
||||
"sequence": 2
|
||||
},
|
||||
{
|
||||
"time": 1636,
|
||||
"source": "02:00:00:00:04:13",
|
||||
"destination": "02:00:00:00:04:11",
|
||||
"icmp": 0,
|
||||
"icmpCode": 0,
|
||||
"sourceIp": "10.0.0.2",
|
||||
"destinationIp": "10.0.0.1",
|
||||
"sequence": 2
|
||||
}
|
||||
],
|
||||
"00000000-0000-0000-0000-000000000407": [
|
||||
{
|
||||
"time": 858,
|
||||
"source": "02:00:00:00:04:11",
|
||||
"destination": "FF:FF:FF:FF:FF:FF",
|
||||
"arp": 1,
|
||||
"senderIp": "10.0.0.1",
|
||||
"targetIp": "10.0.0.2"
|
||||
}
|
||||
]
|
||||
},
|
||||
"rows": {
|
||||
"00000000-0000-0000-0000-000000000401": [
|
||||
[
|
||||
"1",
|
||||
"02:00:00:00:04:11",
|
||||
"FF:FF:FF:FF:FF:FF",
|
||||
"",
|
||||
"Netzzugang",
|
||||
"0x806"
|
||||
],
|
||||
[
|
||||
"1",
|
||||
"10.0.0.1",
|
||||
"10.0.0.2",
|
||||
"ARP",
|
||||
"Vermittlung",
|
||||
"Suche nach MAC für 10.0.0.2 [op=REQUEST, sender=02:00:00:00:04:11|10.0.0.1, target=FF:FF:FF:FF:FF:FF|10.0.0.2]"
|
||||
],
|
||||
[
|
||||
"2",
|
||||
"02:00:00:00:04:13",
|
||||
"02:00:00:00:04:11",
|
||||
"",
|
||||
"Netzzugang",
|
||||
"0x806"
|
||||
],
|
||||
[
|
||||
"2",
|
||||
"10.0.0.2",
|
||||
"10.0.0.1",
|
||||
"ARP",
|
||||
"Vermittlung",
|
||||
"MAC ist 02:00:00:00:04:13 [op=REPLY, sender=02:00:00:00:04:13|10.0.0.2, target=02:00:00:00:04:11|10.0.0.1]"
|
||||
],
|
||||
[
|
||||
"3",
|
||||
"02:00:00:00:04:11",
|
||||
"02:00:00:00:04:13",
|
||||
"",
|
||||
"Netzzugang",
|
||||
"0x800"
|
||||
],
|
||||
[
|
||||
"3",
|
||||
"10.0.0.1",
|
||||
"10.0.0.2",
|
||||
"ICMP",
|
||||
"Vermittlung",
|
||||
"ICMP Echo Request (ping), TTL: 64, Identifier: 1, Seq.-No.: 1"
|
||||
],
|
||||
[
|
||||
"4",
|
||||
"02:00:00:00:04:13",
|
||||
"02:00:00:00:04:11",
|
||||
"",
|
||||
"Netzzugang",
|
||||
"0x800"
|
||||
],
|
||||
[
|
||||
"4",
|
||||
"10.0.0.2",
|
||||
"10.0.0.1",
|
||||
"ICMP",
|
||||
"Vermittlung",
|
||||
"ICMP Echo Reply (pong), TTL: 64, Identifier: 1, Seq.-No.: 1"
|
||||
],
|
||||
[
|
||||
"5",
|
||||
"02:00:00:00:04:11",
|
||||
"02:00:00:00:04:13",
|
||||
"",
|
||||
"Netzzugang",
|
||||
"0x800"
|
||||
],
|
||||
[
|
||||
"5",
|
||||
"10.0.0.1",
|
||||
"10.0.0.2",
|
||||
"ICMP",
|
||||
"Vermittlung",
|
||||
"ICMP Echo Request (ping), TTL: 64, Identifier: 1, Seq.-No.: 2"
|
||||
],
|
||||
[
|
||||
"6",
|
||||
"02:00:00:00:04:13",
|
||||
"02:00:00:00:04:11",
|
||||
"",
|
||||
"Netzzugang",
|
||||
"0x800"
|
||||
],
|
||||
[
|
||||
"6",
|
||||
"10.0.0.2",
|
||||
"10.0.0.1",
|
||||
"ICMP",
|
||||
"Vermittlung",
|
||||
"ICMP Echo Reply (pong), TTL: 64, Identifier: 1, Seq.-No.: 2"
|
||||
]
|
||||
],
|
||||
"00000000-0000-0000-0000-000000000403": [
|
||||
[
|
||||
"1",
|
||||
"02:00:00:00:04:11",
|
||||
"FF:FF:FF:FF:FF:FF",
|
||||
"",
|
||||
"Netzzugang",
|
||||
"0x806"
|
||||
],
|
||||
[
|
||||
"1",
|
||||
"10.0.0.1",
|
||||
"10.0.0.2",
|
||||
"ARP",
|
||||
"Vermittlung",
|
||||
"Suche nach MAC für 10.0.0.2 [op=REQUEST, sender=02:00:00:00:04:11|10.0.0.1, target=FF:FF:FF:FF:FF:FF|10.0.0.2]"
|
||||
],
|
||||
[
|
||||
"2",
|
||||
"02:00:00:00:04:13",
|
||||
"02:00:00:00:04:11",
|
||||
"",
|
||||
"Netzzugang",
|
||||
"0x806"
|
||||
],
|
||||
[
|
||||
"2",
|
||||
"10.0.0.2",
|
||||
"10.0.0.1",
|
||||
"ARP",
|
||||
"Vermittlung",
|
||||
"MAC ist 02:00:00:00:04:13 [op=REPLY, sender=02:00:00:00:04:13|10.0.0.2, target=02:00:00:00:04:11|10.0.0.1]"
|
||||
],
|
||||
[
|
||||
"3",
|
||||
"02:00:00:00:04:11",
|
||||
"02:00:00:00:04:13",
|
||||
"",
|
||||
"Netzzugang",
|
||||
"0x800"
|
||||
],
|
||||
[
|
||||
"3",
|
||||
"10.0.0.1",
|
||||
"10.0.0.2",
|
||||
"ICMP",
|
||||
"Vermittlung",
|
||||
"ICMP Echo Request (ping), TTL: 64, Identifier: 1, Seq.-No.: 1"
|
||||
],
|
||||
[
|
||||
"4",
|
||||
"02:00:00:00:04:13",
|
||||
"02:00:00:00:04:11",
|
||||
"",
|
||||
"Netzzugang",
|
||||
"0x800"
|
||||
],
|
||||
[
|
||||
"4",
|
||||
"10.0.0.2",
|
||||
"10.0.0.1",
|
||||
"ICMP",
|
||||
"Vermittlung",
|
||||
"ICMP Echo Reply (pong), TTL: 64, Identifier: 1, Seq.-No.: 1"
|
||||
],
|
||||
[
|
||||
"5",
|
||||
"02:00:00:00:04:11",
|
||||
"02:00:00:00:04:13",
|
||||
"",
|
||||
"Netzzugang",
|
||||
"0x800"
|
||||
],
|
||||
[
|
||||
"5",
|
||||
"10.0.0.1",
|
||||
"10.0.0.2",
|
||||
"ICMP",
|
||||
"Vermittlung",
|
||||
"ICMP Echo Request (ping), TTL: 64, Identifier: 1, Seq.-No.: 2"
|
||||
],
|
||||
[
|
||||
"6",
|
||||
"02:00:00:00:04:13",
|
||||
"02:00:00:00:04:11",
|
||||
"",
|
||||
"Netzzugang",
|
||||
"0x800"
|
||||
],
|
||||
[
|
||||
"6",
|
||||
"10.0.0.2",
|
||||
"10.0.0.1",
|
||||
"ICMP",
|
||||
"Vermittlung",
|
||||
"ICMP Echo Reply (pong), TTL: 64, Identifier: 1, Seq.-No.: 2"
|
||||
]
|
||||
],
|
||||
"00000000-0000-0000-0000-000000000407": [
|
||||
[
|
||||
"1",
|
||||
"02:00:00:00:04:11",
|
||||
"FF:FF:FF:FF:FF:FF",
|
||||
"",
|
||||
"Netzzugang",
|
||||
"0x806"
|
||||
],
|
||||
[
|
||||
"1",
|
||||
"10.0.0.1",
|
||||
"10.0.0.2",
|
||||
"ARP",
|
||||
"Vermittlung",
|
||||
"Suche nach MAC für 10.0.0.2 [op=REQUEST, sender=02:00:00:00:04:11|10.0.0.1, target=FF:FF:FF:FF:FF:FF|10.0.0.2]"
|
||||
]
|
||||
]
|
||||
},
|
||||
"layeredRows": {
|
||||
"00000000-0000-0000-0000-000000000401": [
|
||||
[
|
||||
"1",
|
||||
"02:00:00:00:04:11",
|
||||
"FF:FF:FF:FF:FF:FF",
|
||||
"",
|
||||
"Netzzugang",
|
||||
"0x806"
|
||||
],
|
||||
[
|
||||
"1",
|
||||
"10.0.0.1",
|
||||
"10.0.0.2",
|
||||
"ARP",
|
||||
"Vermittlung",
|
||||
"Suche nach MAC für 10.0.0.2 [op=REQUEST, sender=02:00:00:00:04:11|10.0.0.1, target=FF:FF:FF:FF:FF:FF|10.0.0.2]"
|
||||
],
|
||||
[
|
||||
"2",
|
||||
"02:00:00:00:04:13",
|
||||
"02:00:00:00:04:11",
|
||||
"",
|
||||
"Netzzugang",
|
||||
"0x806"
|
||||
],
|
||||
[
|
||||
"2",
|
||||
"10.0.0.2",
|
||||
"10.0.0.1",
|
||||
"ARP",
|
||||
"Vermittlung",
|
||||
"MAC ist 02:00:00:00:04:13 [op=REPLY, sender=02:00:00:00:04:13|10.0.0.2, target=02:00:00:00:04:11|10.0.0.1]"
|
||||
],
|
||||
[
|
||||
"3",
|
||||
"02:00:00:00:04:11",
|
||||
"02:00:00:00:04:13",
|
||||
"",
|
||||
"Netzzugang",
|
||||
"0x800"
|
||||
],
|
||||
[
|
||||
"3",
|
||||
"10.0.0.1",
|
||||
"10.0.0.2",
|
||||
"ICMP",
|
||||
"Vermittlung",
|
||||
"ICMP Echo Request (ping), TTL: 64, Identifier: 1, Seq.-No.: 1"
|
||||
],
|
||||
[
|
||||
"4",
|
||||
"02:00:00:00:04:13",
|
||||
"02:00:00:00:04:11",
|
||||
"",
|
||||
"Netzzugang",
|
||||
"0x800"
|
||||
],
|
||||
[
|
||||
"4",
|
||||
"10.0.0.2",
|
||||
"10.0.0.1",
|
||||
"ICMP",
|
||||
"Vermittlung",
|
||||
"ICMP Echo Reply (pong), TTL: 64, Identifier: 1, Seq.-No.: 1"
|
||||
],
|
||||
[
|
||||
"5",
|
||||
"02:00:00:00:04:11",
|
||||
"02:00:00:00:04:13",
|
||||
"",
|
||||
"Netzzugang",
|
||||
"0x800"
|
||||
],
|
||||
[
|
||||
"5",
|
||||
"10.0.0.1",
|
||||
"10.0.0.2",
|
||||
"ICMP",
|
||||
"Vermittlung",
|
||||
"ICMP Echo Request (ping), TTL: 64, Identifier: 1, Seq.-No.: 2"
|
||||
],
|
||||
[
|
||||
"6",
|
||||
"02:00:00:00:04:13",
|
||||
"02:00:00:00:04:11",
|
||||
"",
|
||||
"Netzzugang",
|
||||
"0x800"
|
||||
],
|
||||
[
|
||||
"6",
|
||||
"10.0.0.2",
|
||||
"10.0.0.1",
|
||||
"ICMP",
|
||||
"Vermittlung",
|
||||
"ICMP Echo Reply (pong), TTL: 64, Identifier: 1, Seq.-No.: 2"
|
||||
]
|
||||
],
|
||||
"00000000-0000-0000-0000-000000000403": [
|
||||
[
|
||||
"1",
|
||||
"02:00:00:00:04:11",
|
||||
"FF:FF:FF:FF:FF:FF",
|
||||
"",
|
||||
"Netzzugang",
|
||||
"0x806"
|
||||
],
|
||||
[
|
||||
"1",
|
||||
"10.0.0.1",
|
||||
"10.0.0.2",
|
||||
"ARP",
|
||||
"Vermittlung",
|
||||
"Suche nach MAC für 10.0.0.2 [op=REQUEST, sender=02:00:00:00:04:11|10.0.0.1, target=FF:FF:FF:FF:FF:FF|10.0.0.2]"
|
||||
],
|
||||
[
|
||||
"2",
|
||||
"02:00:00:00:04:13",
|
||||
"02:00:00:00:04:11",
|
||||
"",
|
||||
"Netzzugang",
|
||||
"0x806"
|
||||
],
|
||||
[
|
||||
"2",
|
||||
"10.0.0.2",
|
||||
"10.0.0.1",
|
||||
"ARP",
|
||||
"Vermittlung",
|
||||
"MAC ist 02:00:00:00:04:13 [op=REPLY, sender=02:00:00:00:04:13|10.0.0.2, target=02:00:00:00:04:11|10.0.0.1]"
|
||||
],
|
||||
[
|
||||
"3",
|
||||
"02:00:00:00:04:11",
|
||||
"02:00:00:00:04:13",
|
||||
"",
|
||||
"Netzzugang",
|
||||
"0x800"
|
||||
],
|
||||
[
|
||||
"3",
|
||||
"10.0.0.1",
|
||||
"10.0.0.2",
|
||||
"ICMP",
|
||||
"Vermittlung",
|
||||
"ICMP Echo Request (ping), TTL: 64, Identifier: 1, Seq.-No.: 1"
|
||||
],
|
||||
[
|
||||
"4",
|
||||
"02:00:00:00:04:13",
|
||||
"02:00:00:00:04:11",
|
||||
"",
|
||||
"Netzzugang",
|
||||
"0x800"
|
||||
],
|
||||
[
|
||||
"4",
|
||||
"10.0.0.2",
|
||||
"10.0.0.1",
|
||||
"ICMP",
|
||||
"Vermittlung",
|
||||
"ICMP Echo Reply (pong), TTL: 64, Identifier: 1, Seq.-No.: 1"
|
||||
],
|
||||
[
|
||||
"5",
|
||||
"02:00:00:00:04:11",
|
||||
"02:00:00:00:04:13",
|
||||
"",
|
||||
"Netzzugang",
|
||||
"0x800"
|
||||
],
|
||||
[
|
||||
"5",
|
||||
"10.0.0.1",
|
||||
"10.0.0.2",
|
||||
"ICMP",
|
||||
"Vermittlung",
|
||||
"ICMP Echo Request (ping), TTL: 64, Identifier: 1, Seq.-No.: 2"
|
||||
],
|
||||
[
|
||||
"6",
|
||||
"02:00:00:00:04:13",
|
||||
"02:00:00:00:04:11",
|
||||
"",
|
||||
"Netzzugang",
|
||||
"0x800"
|
||||
],
|
||||
[
|
||||
"6",
|
||||
"10.0.0.2",
|
||||
"10.0.0.1",
|
||||
"ICMP",
|
||||
"Vermittlung",
|
||||
"ICMP Echo Reply (pong), TTL: 64, Identifier: 1, Seq.-No.: 2"
|
||||
]
|
||||
],
|
||||
"00000000-0000-0000-0000-000000000407": [
|
||||
[
|
||||
"1",
|
||||
"02:00:00:00:04:11",
|
||||
"FF:FF:FF:FF:FF:FF",
|
||||
"",
|
||||
"Netzzugang",
|
||||
"0x806"
|
||||
],
|
||||
[
|
||||
"1",
|
||||
"10.0.0.1",
|
||||
"10.0.0.2",
|
||||
"ARP",
|
||||
"Vermittlung",
|
||||
"Suche nach MAC für 10.0.0.2 [op=REQUEST, sender=02:00:00:00:04:11|10.0.0.1, target=FF:FF:FF:FF:FF:FF|10.0.0.2]"
|
||||
]
|
||||
]
|
||||
},
|
||||
"events": [
|
||||
[
|
||||
728,
|
||||
"sendProbe 10.0.0.2 called"
|
||||
],
|
||||
[
|
||||
1468,
|
||||
"sendProbe 10.0.0.2 returned ICMP 0/0 from 10.0.0.2"
|
||||
],
|
||||
[
|
||||
1468,
|
||||
"sendProbe 10.0.0.2 called"
|
||||
],
|
||||
[
|
||||
1804,
|
||||
"sendProbe 10.0.0.2 returned ICMP 0/0 from 10.0.0.2"
|
||||
]
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
{
|
||||
"frames": {
|
||||
"00000000-0000-0000-0000-000000000401": [
|
||||
{
|
||||
"time": 717,
|
||||
"source": "02:00:00:00:04:11",
|
||||
"destination": "FF:FF:FF:FF:FF:FF",
|
||||
"arp": 1,
|
||||
"senderIp": "10.0.0.1",
|
||||
"targetIp": "10.0.0.2"
|
||||
},
|
||||
{
|
||||
"time": 3468,
|
||||
"source": "02:00:00:00:04:11",
|
||||
"destination": "FF:FF:FF:FF:FF:FF",
|
||||
"arp": 1,
|
||||
"senderIp": "10.0.0.1",
|
||||
"targetIp": "10.0.0.2"
|
||||
}
|
||||
],
|
||||
"00000000-0000-0000-0000-000000000403": [],
|
||||
"00000000-0000-0000-0000-000000000407": [
|
||||
{
|
||||
"time": 838,
|
||||
"source": "02:00:00:00:04:11",
|
||||
"destination": "FF:FF:FF:FF:FF:FF",
|
||||
"arp": 1,
|
||||
"senderIp": "10.0.0.1",
|
||||
"targetIp": "10.0.0.2"
|
||||
},
|
||||
{
|
||||
"time": 3579,
|
||||
"source": "02:00:00:00:04:11",
|
||||
"destination": "FF:FF:FF:FF:FF:FF",
|
||||
"arp": 1,
|
||||
"senderIp": "10.0.0.1",
|
||||
"targetIp": "10.0.0.2"
|
||||
}
|
||||
]
|
||||
},
|
||||
"rows": {
|
||||
"00000000-0000-0000-0000-000000000401": [
|
||||
[
|
||||
"1",
|
||||
"02:00:00:00:04:11",
|
||||
"FF:FF:FF:FF:FF:FF",
|
||||
"",
|
||||
"Netzzugang",
|
||||
"0x806"
|
||||
],
|
||||
[
|
||||
"1",
|
||||
"10.0.0.1",
|
||||
"10.0.0.2",
|
||||
"ARP",
|
||||
"Vermittlung",
|
||||
"Suche nach MAC für 10.0.0.2 [op=REQUEST, sender=02:00:00:00:04:11|10.0.0.1, target=FF:FF:FF:FF:FF:FF|10.0.0.2]"
|
||||
],
|
||||
[
|
||||
"2",
|
||||
"02:00:00:00:04:11",
|
||||
"FF:FF:FF:FF:FF:FF",
|
||||
"",
|
||||
"Netzzugang",
|
||||
"0x806"
|
||||
],
|
||||
[
|
||||
"2",
|
||||
"10.0.0.1",
|
||||
"10.0.0.2",
|
||||
"ARP",
|
||||
"Vermittlung",
|
||||
"Suche nach MAC für 10.0.0.2 [op=REQUEST, sender=02:00:00:00:04:11|10.0.0.1, target=FF:FF:FF:FF:FF:FF|10.0.0.2]"
|
||||
]
|
||||
],
|
||||
"00000000-0000-0000-0000-000000000403": [],
|
||||
"00000000-0000-0000-0000-000000000407": [
|
||||
[
|
||||
"1",
|
||||
"02:00:00:00:04:11",
|
||||
"FF:FF:FF:FF:FF:FF",
|
||||
"",
|
||||
"Netzzugang",
|
||||
"0x806"
|
||||
],
|
||||
[
|
||||
"1",
|
||||
"10.0.0.1",
|
||||
"10.0.0.2",
|
||||
"ARP",
|
||||
"Vermittlung",
|
||||
"Suche nach MAC für 10.0.0.2 [op=REQUEST, sender=02:00:00:00:04:11|10.0.0.1, target=FF:FF:FF:FF:FF:FF|10.0.0.2]"
|
||||
],
|
||||
[
|
||||
"2",
|
||||
"02:00:00:00:04:11",
|
||||
"FF:FF:FF:FF:FF:FF",
|
||||
"",
|
||||
"Netzzugang",
|
||||
"0x806"
|
||||
],
|
||||
[
|
||||
"2",
|
||||
"10.0.0.1",
|
||||
"10.0.0.2",
|
||||
"ARP",
|
||||
"Vermittlung",
|
||||
"Suche nach MAC für 10.0.0.2 [op=REQUEST, sender=02:00:00:00:04:11|10.0.0.1, target=FF:FF:FF:FF:FF:FF|10.0.0.2]"
|
||||
]
|
||||
]
|
||||
},
|
||||
"layeredRows": {
|
||||
"00000000-0000-0000-0000-000000000401": [
|
||||
[
|
||||
"1",
|
||||
"02:00:00:00:04:11",
|
||||
"FF:FF:FF:FF:FF:FF",
|
||||
"",
|
||||
"Netzzugang",
|
||||
"0x806"
|
||||
],
|
||||
[
|
||||
"1",
|
||||
"10.0.0.1",
|
||||
"10.0.0.2",
|
||||
"ARP",
|
||||
"Vermittlung",
|
||||
"Suche nach MAC für 10.0.0.2 [op=REQUEST, sender=02:00:00:00:04:11|10.0.0.1, target=FF:FF:FF:FF:FF:FF|10.0.0.2]"
|
||||
],
|
||||
[
|
||||
"2",
|
||||
"02:00:00:00:04:11",
|
||||
"FF:FF:FF:FF:FF:FF",
|
||||
"",
|
||||
"Netzzugang",
|
||||
"0x806"
|
||||
],
|
||||
[
|
||||
"2",
|
||||
"10.0.0.1",
|
||||
"10.0.0.2",
|
||||
"ARP",
|
||||
"Vermittlung",
|
||||
"Suche nach MAC für 10.0.0.2 [op=REQUEST, sender=02:00:00:00:04:11|10.0.0.1, target=FF:FF:FF:FF:FF:FF|10.0.0.2]"
|
||||
]
|
||||
],
|
||||
"00000000-0000-0000-0000-000000000403": [],
|
||||
"00000000-0000-0000-0000-000000000407": [
|
||||
[
|
||||
"1",
|
||||
"02:00:00:00:04:11",
|
||||
"FF:FF:FF:FF:FF:FF",
|
||||
"",
|
||||
"Netzzugang",
|
||||
"0x806"
|
||||
],
|
||||
[
|
||||
"1",
|
||||
"10.0.0.1",
|
||||
"10.0.0.2",
|
||||
"ARP",
|
||||
"Vermittlung",
|
||||
"Suche nach MAC für 10.0.0.2 [op=REQUEST, sender=02:00:00:00:04:11|10.0.0.1, target=FF:FF:FF:FF:FF:FF|10.0.0.2]"
|
||||
],
|
||||
[
|
||||
"2",
|
||||
"02:00:00:00:04:11",
|
||||
"FF:FF:FF:FF:FF:FF",
|
||||
"",
|
||||
"Netzzugang",
|
||||
"0x806"
|
||||
],
|
||||
[
|
||||
"2",
|
||||
"10.0.0.1",
|
||||
"10.0.0.2",
|
||||
"ARP",
|
||||
"Vermittlung",
|
||||
"Suche nach MAC für 10.0.0.2 [op=REQUEST, sender=02:00:00:00:04:11|10.0.0.1, target=FF:FF:FF:FF:FF:FF|10.0.0.2]"
|
||||
]
|
||||
]
|
||||
},
|
||||
"events": [
|
||||
[
|
||||
713,
|
||||
"sendProbe 10.0.0.2 called"
|
||||
],
|
||||
[
|
||||
6224,
|
||||
"sendProbe 10.0.0.2 returned ICMP 3/1 from 127.0.0.1"
|
||||
]
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
{
|
||||
"seed": 67,
|
||||
"nodes": [
|
||||
{
|
||||
"id": "00000000-0000-0000-0000-000000000401",
|
||||
"kind": "pc",
|
||||
"ports": [
|
||||
{
|
||||
"id": "00000000-0000-0000-0000-000000000411",
|
||||
"label": "eth0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "00000000-0000-0000-0000-000000000403",
|
||||
"kind": "pc",
|
||||
"ports": [
|
||||
{
|
||||
"id": "00000000-0000-0000-0000-000000000413",
|
||||
"label": "eth0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "00000000-0000-0000-0000-000000000407",
|
||||
"kind": "pc",
|
||||
"ports": [
|
||||
{
|
||||
"id": "00000000-0000-0000-0000-000000000417",
|
||||
"label": "eth0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "00000000-0000-0000-0000-000000000404",
|
||||
"kind": "networkSwitch",
|
||||
"ports": [
|
||||
{
|
||||
"id": "00000000-0000-0000-0000-000000000441",
|
||||
"label": "sw1"
|
||||
},
|
||||
{
|
||||
"id": "00000000-0000-0000-0000-000000000442",
|
||||
"label": "sw2"
|
||||
},
|
||||
{
|
||||
"id": "00000000-0000-0000-0000-000000000443",
|
||||
"label": "sw3"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "00000000-0000-0000-0000-000000000405",
|
||||
"kind": "remoteLink",
|
||||
"ports": [
|
||||
{
|
||||
"id": "00000000-0000-0000-0000-000000000451",
|
||||
"label": "remote0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "00000000-0000-0000-0000-000000000406",
|
||||
"kind": "remoteLink",
|
||||
"ports": [
|
||||
{
|
||||
"id": "00000000-0000-0000-0000-000000000461",
|
||||
"label": "remote0"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
{
|
||||
"id": "00000000-0000-0000-0000-00000000F401",
|
||||
"sourceNode": "00000000-0000-0000-0000-000000000401",
|
||||
"sourcePort": "00000000-0000-0000-0000-000000000411",
|
||||
"targetNode": "00000000-0000-0000-0000-000000000404",
|
||||
"targetPort": "00000000-0000-0000-0000-000000000441"
|
||||
},
|
||||
{
|
||||
"id": "00000000-0000-0000-0000-00000000F402",
|
||||
"sourceNode": "00000000-0000-0000-0000-000000000407",
|
||||
"sourcePort": "00000000-0000-0000-0000-000000000417",
|
||||
"targetNode": "00000000-0000-0000-0000-000000000404",
|
||||
"targetPort": "00000000-0000-0000-0000-000000000442"
|
||||
},
|
||||
{
|
||||
"id": "00000000-0000-0000-0000-00000000F403",
|
||||
"sourceNode": "00000000-0000-0000-0000-000000000404",
|
||||
"sourcePort": "00000000-0000-0000-0000-000000000443",
|
||||
"targetNode": "00000000-0000-0000-0000-000000000405",
|
||||
"targetPort": "00000000-0000-0000-0000-000000000451"
|
||||
},
|
||||
{
|
||||
"id": "00000000-0000-0000-0000-00000000F404",
|
||||
"sourceNode": "00000000-0000-0000-0000-000000000406",
|
||||
"sourcePort": "00000000-0000-0000-0000-000000000461",
|
||||
"targetNode": "00000000-0000-0000-0000-000000000403",
|
||||
"targetPort": "00000000-0000-0000-0000-000000000413"
|
||||
}
|
||||
],
|
||||
"devices": {
|
||||
"00000000-0000-0000-0000-000000000401": {
|
||||
"ip": "10.0.0.1",
|
||||
"mask": "255.255.255.0"
|
||||
},
|
||||
"00000000-0000-0000-0000-000000000403": {
|
||||
"ip": "10.0.0.2",
|
||||
"mask": "255.255.255.0"
|
||||
},
|
||||
"00000000-0000-0000-0000-000000000407": {
|
||||
"ip": "10.0.0.3",
|
||||
"mask": "255.255.255.0"
|
||||
}
|
||||
},
|
||||
"modems": {
|
||||
"00000000-0000-0000-0000-000000000405": {
|
||||
"mode": "server",
|
||||
"port": 12345
|
||||
},
|
||||
"00000000-0000-0000-0000-000000000406": {
|
||||
"mode": "client",
|
||||
"port": 12345
|
||||
}
|
||||
},
|
||||
"steps": []
|
||||
}
|
||||
Reference in New Issue
Block a user